diff options
Diffstat (limited to 'libs/libbsarch/delphi')
| -rw-r--r-- | libs/libbsarch/delphi/libbsarch.dpr | 412 | ||||
| -rw-r--r-- | libs/libbsarch/delphi/libbsarch.dproj | 916 | ||||
| -rw-r--r-- | libs/libbsarch/delphi/wbBSArchive.pas | 2637 |
3 files changed, 3965 insertions, 0 deletions
diff --git a/libs/libbsarch/delphi/libbsarch.dpr b/libs/libbsarch/delphi/libbsarch.dpr new file mode 100644 index 0000000..34c0e35 --- /dev/null +++ b/libs/libbsarch/delphi/libbsarch.dpr @@ -0,0 +1,412 @@ +library libbsarch; + +uses + Math, + Classes, + Types, + SysUtils, + wbStreams, + wbBSArchive; + +type + TwbBSResultCode = ( + BSA_RESULT_NONE = 0, + BSA_RESULT_EXCEPTION = -1 + ); + TwbBSResultMessage = packed record + code: ShortInt; + text: array[0..1023] of WideChar; + end; + TwbBSResultMessageBuffer = packed record + buffer: TwbBSResultBuffer; + message: TwbBSResultMessage; + end; + +{Shared} + +function string_to_cstring( + aString: String; + const aStringBufferSize: Cardinal; + const aStringBuffer: PChar +): Integer; stdcall; +begin + Result := Min(aStringBufferSize, Length(AString)); + if (aStringBuffer <> nil) and (Result > 0) then + Move(AString[1], aStringBuffer^, Result * SizeOf(WideChar)); +end; + +function string_to_cstring_end( + aString: String; + const aStringBufferSize: Cardinal; + const aStringBuffer: PChar +): Integer; stdcall; +begin + Result := Min(aStringBufferSize - 1, Length(AString)); + if (aStringBuffer <> nil) and (Result > 0) then + begin + Move(AString[1], aStringBuffer^, Result * SizeOf(WideChar)); + aStringBuffer[Result] := #0; + end; +end; + +procedure exception_handler(E: Exception; var Result: TwbBSResultMessage); stdcall; +begin + Result.code := ShortInt(BSA_RESULT_EXCEPTION); + string_to_cstring_end(E.Message, Length(Result.text), Result.text); +end; + +procedure buffer_exception_handler(E: Exception; var Result: TwbBSResultMessageBuffer); stdcall; +begin + Result.buffer.size := 0; + Result.buffer.data := nil; + Result.message.code := ShortInt(BSA_RESULT_EXCEPTION); + string_to_cstring_end(E.Message, Length(Result.message.text), Result.message.text); +end; + +{BSA File List} + +function bsa_entry_list_create:Pointer; stdcall; +begin + try + Result := TStringList.Create; + except + Result := nil; + end; +end; + +function bsa_entry_list_free(obj: Pointer): TwbBSResultMessage; stdcall; +begin + Result.code := Ord(BSA_RESULT_NONE); + try + TStringList(obj).Clear(); + TStringList(obj).free; + except + on E: Exception do + exception_handler(E, Result); + end; +end; + +function bsa_entry_list_count(obj: Pointer): Integer; stdcall; +begin + try + Result := TStringList(obj).Count; + except + Result := Ord(BSA_RESULT_EXCEPTION); + end; +end; + +function bsa_entry_list_add(obj: Pointer; const aEntryString: PChar): TwbBSResultMessage; stdcall; +begin + Result.code := Ord(BSA_RESULT_NONE); + try + TStringList(obj).Add(String(aEntryString)); + except + on E: Exception do + exception_handler(E, Result); + end; +end; + +function bsa_entry_list_get(obj: Pointer; const aIndex: Cardinal; const aStringBufferSize: Cardinal; const aStringBuffer: PChar): Integer; stdcall; +begin + try + Result := string_to_cstring_end(TStringList(obj).Strings[aIndex], aStringBufferSize, aStringBuffer); + except + Result := Integer(BSA_RESULT_EXCEPTION); + end; +end; + +{BSArchive} + +function bsa_create:Pointer; stdcall; +begin + try + Result := TwbBSArchive.Create; + except + Result := nil; + end; +end; + +function bsa_free(obj: Pointer): TwbBSResultMessage; stdcall; +begin + Result.code := Ord(BSA_RESULT_NONE); + try + TwbBSArchive(obj).free; + except + on E: Exception do + exception_handler(E, Result); + end; +end; + +function bsa_load_from_file(obj: Pointer; const aFilePath: PChar): TwbBSResultMessage; stdcall; +begin + Result.code := Ord(BSA_RESULT_NONE); + try + TwbBSArchive(obj).LoadFromFile(String(aFilePath)); + except + on E: Exception do + exception_handler(E, Result); + end; +end; + +function bsa_create_archive(obj: Pointer; const aFilePath: PChar; aType: TBSArchiveType; aFileList: TStringList): TwbBSResultMessage; stdcall; +begin + Result.code := Ord(BSA_RESULT_NONE); + try + TwbBSArchive(obj).CreateArchive(String(aFilePath), aType, aFileList); + except + on E: Exception do + exception_handler(E, Result); + end; +end; + +function bsa_save(obj: Pointer): TwbBSResultMessage; stdcall; +begin + Result.code := Ord(BSA_RESULT_NONE); + try + TwbBSArchive(obj).Save; + except + on E: Exception do + exception_handler(E, Result); + end; +end; + +function bsa_add_file_from_disk_root(obj: Pointer; const aRootDir, aSourcePath: PChar): TwbBSResultMessage; stdcall; +begin + Result.code := Ord(BSA_RESULT_NONE); + try + TwbBSArchive(obj).AddFileDiskRoot(String(aRootDir), String(aSourcePath)); + except + on E: Exception do + exception_handler(E, Result); + end; +end; + +function bsa_add_file_from_disk(obj: Pointer; const aFilePath, aSourcePath: PChar): TwbBSResultMessage; stdcall; +begin + Result.code := Ord(BSA_RESULT_NONE); + try + TwbBSArchive(obj).AddFileDisk(String(aFilePath), String(aSourcePath)); + except + on E: Exception do + exception_handler(E, Result); + end; +end; + +function bsa_add_file_from_memory(obj: Pointer; const aFilePath: PChar; const aSize: Cardinal; const aData: PByte): TwbBSResultMessage; stdcall; +begin + Result.code := Ord(BSA_RESULT_NONE); + try + TwbBSArchive(obj).AddFileData(String(aFilePath), aSize, aData); + except + on E: Exception do + exception_handler(E, Result); + end; +end; + +function bsa_find_file_record(obj: Pointer; const aFilePath: PChar): Pointer; stdcall; +begin + try + Result := TwbBSArchive(obj).FindFileRecord(String(aFilePath)); + except + Result := nil + end; +end; + +function bsa_extract_file_data_by_record(obj: Pointer; aFileRecord: Pointer): TwbBSResultMessageBuffer; stdcall; +begin + Result.message.code := Ord(BSA_RESULT_NONE); + try + Result.buffer := TwbBSArchive(obj).ExtractFileData(aFileRecord); + except + on E: Exception do + buffer_exception_handler(E, Result); + end; +end; + +function bsa_extract_file_data_by_filename(obj: Pointer; const aFilePath: PChar): TwbBSResultMessageBuffer; stdcall; +begin + Result.message.code := Ord(BSA_RESULT_NONE); + try + Result.buffer := TwbBSArchive(obj).ExtractFileData(String(aFilePath)); + except + on E: Exception do + buffer_exception_handler(E, Result); + end; +end; + +function bsa_file_data_free(obj: Pointer; fileDataResult: TwbBSResultMessageBuffer): TwbBSResultMessage; stdcall; +begin + Result.code := Ord(BSA_RESULT_NONE); + try + TwbBSArchive(obj).ReleaseFileData(fileDataResult.buffer); + except + on E: Exception do + exception_handler(E, Result); + end; +end; + +function bsa_extract_file(obj: Pointer; const aFilePath, aSaveAs: PChar): TwbBSResultMessage; stdcall; +begin + Result.code := Ord(BSA_RESULT_NONE); + try + TwbBSArchive(obj).ExtractFile(String(aFilePath), String(aSaveAs)); + except + on E: Exception do + exception_handler(E, Result); + end; +end; + +function bsa_iterate_files(obj: Pointer; aProc: TBSFileIterationProc; aContext: Pointer): TwbBSResultMessage; stdcall; +begin + Result.code := Ord(BSA_RESULT_NONE); + try + TwbBSArchive(obj).IterateFiles(aProc, aContext); + except + on E: Exception do + exception_handler(E, Result); + end; +end; + +function bsa_file_exists(obj: Pointer; const aFilePath: string): Boolean; stdcall; +begin + try + Result := TwbBSArchive(obj).FileExists(aFilePath); + except + Result := False; + end; +end; + +function bsa_get_resource_list(obj: Pointer; const aEntryResultList: TStringList; aFolder: PChar): TwbBSResultMessage; stdcall; +begin + Result.code := Ord(BSA_RESULT_NONE); + try + TwbBSArchive(obj).ResourceList(aEntryResultList, String(aFolder)); + except + on E: Exception do + exception_handler(E, Result); + end; +end; + +function bsa_close(obj: Pointer): TwbBSResultMessage; stdcall; +begin + Result.code := Ord(BSA_RESULT_NONE); + try + TwbBSArchive(obj).Close; + except + on E: Exception do + exception_handler(E, Result); + end; +end; + +function bsa_filename_get(obj: Pointer; const aStringBufferSize: Cardinal; const aStringBuffer: PChar): Cardinal; stdcall; +begin + Result := string_to_cstring_end(TwbBSArchive(obj).FileName, aStringBufferSize, aStringBuffer); +end; + +function bsa_archive_type_get(obj: Pointer): TBSArchiveType; stdcall; +begin + Result := TwbBSArchive(obj).ArchiveType; +end; + +function bsa_version_get(obj: Pointer): Cardinal; stdcall; +begin + Result := TwbBSArchive(obj).Version; +end; + +function bsa_format_name_get(obj: Pointer; const aStringBufferSize: Cardinal; const aStringBuffer: PChar): Cardinal; stdcall; +begin + Result := string_to_cstring_end(TwbBSArchive(obj).FormatName, aStringBufferSize, aStringBuffer); +end; + +function bsa_file_count_get(obj: Pointer): Cardinal; stdcall; +begin + Result := TwbBSArchive(obj).FileCount; +end; + +function bsa_archive_flags_get(obj: Pointer): Cardinal; stdcall; +begin + Result := TwbBSArchive(obj).ArchiveFlags; +end; + +procedure bsa_archive_flags_set(obj: Pointer; flags: Cardinal); stdcall; +begin + TwbBSArchive(obj).ArchiveFlags := flags; +end; + +function bsa_file_flags_get(obj: Pointer): Cardinal; stdcall; +begin + Result := TwbBSArchive(obj).FileFlags; +end; + +procedure bsa_file_flags_set(obj: Pointer; flags: Cardinal); stdcall; +begin + TwbBSArchive(obj).FileFlags := flags; +end; + +function bsa_compress_get(obj: Pointer): Boolean; stdcall; +begin + Result := TwbBSArchive(obj).Compress; +end; + +procedure bsa_compress_set(obj: Pointer; compress: Boolean); stdcall; +begin + TwbBSArchive(obj).Compress := compress; +end; + +function bsa_share_data_get(obj: Pointer): Boolean; stdcall; +begin + Result := TwbBSArchive(obj).ShareData; +end; + +procedure bsa_share_data_set(obj: Pointer; shareData: Boolean); stdcall; +begin + TwbBSArchive(obj).ShareData := shareData; +end; + +procedure bsa_file_dds_info_callback_set(obj: Pointer; aProc: TBSFileDDSInfoProc; aContext: Pointer); stdcall; +begin + TwbBSArchive(obj).DDSInfoProc := aProc; + TwbBSArchive(obj).DDSInfoProcContext := aContext; +end; + +exports + bsa_entry_list_create, + bsa_entry_list_free, + bsa_entry_list_count, + bsa_entry_list_add, + bsa_entry_list_get, + + bsa_filename_get, + bsa_archive_type_get, + bsa_version_get, + bsa_format_name_get, + bsa_file_count_get, + bsa_archive_flags_get, + bsa_archive_flags_set, + bsa_file_flags_get, + bsa_file_flags_set, + bsa_compress_get, + bsa_compress_set, + bsa_share_data_get, + bsa_share_data_set, + bsa_file_dds_info_callback_set, + + bsa_close, + bsa_get_resource_list, + bsa_file_exists, + bsa_iterate_files, + bsa_extract_file, + bsa_file_data_free, + bsa_extract_file_data_by_filename, + bsa_extract_file_data_by_record, + bsa_find_file_record, + bsa_add_file_from_memory, + bsa_add_file_from_disk_root, + bsa_add_file_from_disk, + bsa_save, + bsa_create_archive, + bsa_load_from_file, + bsa_create, + bsa_free + ; +end. diff --git a/libs/libbsarch/delphi/libbsarch.dproj b/libs/libbsarch/delphi/libbsarch.dproj new file mode 100644 index 0000000..1e928c6 --- /dev/null +++ b/libs/libbsarch/delphi/libbsarch.dproj @@ -0,0 +1,916 @@ +<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <ProjectGuid>{381E4D59-4D34-46B3-B039-1B3C3ABEC58F}</ProjectGuid> + <MainSource>libbsarch.dpr</MainSource> + <Base>True</Base> + <Config Condition="'$(Config)'==''">Release</Config> + <TargetedPlatforms>3</TargetedPlatforms> + <AppType>Library</AppType> + <FrameworkType>None</FrameworkType> + <ProjectVersion>19.5</ProjectVersion> + <Platform Condition="'$(Platform)'==''">Win64</Platform> + </PropertyGroup> + <PropertyGroup Condition="'$(Config)'=='Base' or '$(Base)'!=''"> + <Base>true</Base> + </PropertyGroup> + <PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Base)'=='true') or '$(Base_Win32)'!=''"> + <Base_Win32>true</Base_Win32> + <CfgParent>Base</CfgParent> + <Base>true</Base> + </PropertyGroup> + <PropertyGroup Condition="('$(Platform)'=='Win64' and '$(Base)'=='true') or '$(Base_Win64)'!=''"> + <Base_Win64>true</Base_Win64> + <CfgParent>Base</CfgParent> + <Base>true</Base> + </PropertyGroup> + <PropertyGroup Condition="'$(Config)'=='Release' or '$(Cfg_1)'!=''"> + <Cfg_1>true</Cfg_1> + <CfgParent>Base</CfgParent> + <Base>true</Base> + </PropertyGroup> + <PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Cfg_1)'=='true') or '$(Cfg_1_Win32)'!=''"> + <Cfg_1_Win32>true</Cfg_1_Win32> + <CfgParent>Cfg_1</CfgParent> + <Cfg_1>true</Cfg_1> + <Base>true</Base> + </PropertyGroup> + <PropertyGroup Condition="('$(Platform)'=='Win64' and '$(Cfg_1)'=='true') or '$(Cfg_1_Win64)'!=''"> + <Cfg_1_Win64>true</Cfg_1_Win64> + <CfgParent>Cfg_1</CfgParent> + <Cfg_1>true</Cfg_1> + <Base>true</Base> + </PropertyGroup> + <PropertyGroup Condition="'$(Config)'=='Debug' or '$(Cfg_2)'!=''"> + <Cfg_2>true</Cfg_2> + <CfgParent>Base</CfgParent> + <Base>true</Base> + </PropertyGroup> + <PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Cfg_2)'=='true') or '$(Cfg_2_Win32)'!=''"> + <Cfg_2_Win32>true</Cfg_2_Win32> + <CfgParent>Cfg_2</CfgParent> + <Cfg_2>true</Cfg_2> + <Base>true</Base> + </PropertyGroup> + <PropertyGroup Condition="('$(Platform)'=='Win64' and '$(Cfg_2)'=='true') or '$(Cfg_2_Win64)'!=''"> + <Cfg_2_Win64>true</Cfg_2_Win64> + <CfgParent>Cfg_2</CfgParent> + <Cfg_2>true</Cfg_2> + <Base>true</Base> + </PropertyGroup> + <PropertyGroup Condition="'$(Base)'!=''"> + <DCC_E>false</DCC_E> + <DCC_F>false</DCC_F> + <DCC_K>false</DCC_K> + <DCC_N>false</DCC_N> + <DCC_S>false</DCC_S> + <DCC_ImageBase>00400000</DCC_ImageBase> + <GenDll>true</GenDll> + <SanitizedProjectName>libbsarch</SanitizedProjectName> + <VerInfo_Locale>1033</VerInfo_Locale> + <VerInfo_Keys>CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;CFBundleName=</VerInfo_Keys> + <DCC_Namespace>System;Xml;Data;Datasnap;Web;Soap;Winapi;$(DCC_Namespace)</DCC_Namespace> + <XEditPath>C:\dev\TES5Edit</XEditPath> + <DCC_UnitSearchPath>$(XEditPath)\Core;$(XEditPath)\External\lz4;$(XEditPath)\External\TForge;$(XEditPath)\External\TForge\Source\Include;$(XEditPath)\External\TForge\Source\Shared;$(XEditPath)\External\TForge\Source\Engine\Hashes;$(XEditPath)\External\TForge\Source\Engine\Forge;$(XEditPath)\External\ZLibEx;$(XEditPath)\External\lz4\lib\delphi;$(XEditPath)\External\lz4\common;$(DCC_UnitSearchPath)</DCC_UnitSearchPath> + </PropertyGroup> + <PropertyGroup Condition="'$(Base_Win32)'!=''"> + <DCC_Namespace>System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace)</DCC_Namespace> + <BT_BuildType>Debug</BT_BuildType> + <VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo> + <VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName)</VerInfo_Keys> + <VerInfo_Locale>1033</VerInfo_Locale> + </PropertyGroup> + <PropertyGroup Condition="'$(Base_Win64)'!=''"> + <DCC_Namespace>System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace)</DCC_Namespace> + <BT_BuildType>Debug</BT_BuildType> + <VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo> + <VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=</VerInfo_Keys> + <Manifest_File>(None)</Manifest_File> + <AppDPIAwarenessMode>none</AppDPIAwarenessMode> + </PropertyGroup> + <PropertyGroup Condition="'$(Cfg_1)'!=''"> + <DCC_Define>RELEASE;$(DCC_Define)</DCC_Define> + <DCC_DebugInformation>0</DCC_DebugInformation> + <DCC_LocalDebugSymbols>false</DCC_LocalDebugSymbols> + <DCC_SymbolReferenceInfo>0</DCC_SymbolReferenceInfo> + </PropertyGroup> + <PropertyGroup Condition="'$(Cfg_1_Win32)'!=''"> + <DCC_ExeOutput>Win32\Release\</DCC_ExeOutput> + <VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo> + <VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName)</VerInfo_Keys> + <Manifest_File>(None)</Manifest_File> + <Debugger_HostApplication>Win32\Release\libbsarch-visualstudio-test.exe</Debugger_HostApplication> + </PropertyGroup> + <PropertyGroup Condition="'$(Cfg_1_Win64)'!=''"> + <DCC_ExeOutput>X64\Release\</DCC_ExeOutput> + <VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo> + <VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=</VerInfo_Keys> + <Manifest_File>(None)</Manifest_File> + <Debugger_HostApplication>X64\Release\libbsarch-visualstudio-test.exe</Debugger_HostApplication> + </PropertyGroup> + <PropertyGroup Condition="'$(Cfg_2)'!=''"> + <DCC_Define>DEBUG;$(DCC_Define)</DCC_Define> + <DCC_Optimize>false</DCC_Optimize> + <DCC_GenerateStackFrames>true</DCC_GenerateStackFrames> + </PropertyGroup> + <PropertyGroup Condition="'$(Cfg_2_Win32)'!=''"> + <Debugger_HostApplication>Win32\Debug\libbsarch-visualstudio-test.exe</Debugger_HostApplication> + <DCC_ExeOutput>Win32\Debug\</DCC_ExeOutput> + <VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo> + <VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName)</VerInfo_Keys> + <Manifest_File>(None)</Manifest_File> + </PropertyGroup> + <PropertyGroup Condition="'$(Cfg_2_Win64)'!=''"> + <VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo> + <VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=</VerInfo_Keys> + <Manifest_File>(None)</Manifest_File> + <DCC_ExeOutput>X64\Debug\</DCC_ExeOutput> + <Debugger_HostApplication>D:\Projects\libbsarch\x64\Debug\libbsarch-visualstudio-test.exe</Debugger_HostApplication> + <Debugger_CWD>D:\Projects\libbsarch\x64\Debug</Debugger_CWD> + </PropertyGroup> + <ItemGroup> + <DelphiCompile Include="$(MainSource)"> + <MainSource>MainSource</MainSource> + </DelphiCompile> + <BuildConfiguration Include="Base"> + <Key>Base</Key> + </BuildConfiguration> + <BuildConfiguration Include="Release"> + <Key>Cfg_1</Key> + <CfgParent>Base</CfgParent> + </BuildConfiguration> + <BuildConfiguration Include="Debug"> + <Key>Cfg_2</Key> + <CfgParent>Base</CfgParent> + </BuildConfiguration> + </ItemGroup> + <ProjectExtensions> + <Borland.Personality>Delphi.Personality.12</Borland.Personality> + <Borland.ProjectType/> + <BorlandProject> + <Delphi.Personality> + <Source> + <Source Name="MainSource">libbsarch.dpr</Source> + </Source> + <Excluded_Packages> + <Excluded_Packages Name="$(BDSBIN)\dcloffice2k280.bpl">Microsoft Office 2000 Sample Automation Server Wrapper Components</Excluded_Packages> + <Excluded_Packages Name="$(BDSBIN)\dclofficexp280.bpl">Microsoft Office XP Sample Automation Server Wrapper Components</Excluded_Packages> + </Excluded_Packages> + </Delphi.Personality> + <Platforms> + <Platform value="Win32">True</Platform> + <Platform value="Win64">True</Platform> + </Platforms> + <ModelSupport>False</ModelSupport> + <Deployment Version="4"> + <DeployFile LocalName="$(BDS)\Redist\iossimulator\libcgunwind.1.0.dylib" Class="DependencyModule"> + <Platform Name="iOSSimulator"> + <Overwrite>true</Overwrite> + </Platform> + </DeployFile> + <DeployFile LocalName="$(BDS)\Redist\iossimulator\libpcre.dylib" Class="DependencyModule"> + <Platform Name="iOSSimulator"> + <Overwrite>true</Overwrite> + </Platform> + </DeployFile> + <DeployFile LocalName="$(BDS)\Redist\osx32\libcgunwind.1.0.dylib" Class="DependencyModule"> + <Platform Name="OSX32"> + <Overwrite>true</Overwrite> + </Platform> + </DeployFile> + <DeployFile LocalName="X64\Release\libbsarch.dll" Configuration="Release" Class="ProjectOutput"> + <Platform Name="Win64"> + <RemoteName>libbsarch.dll</RemoteName> + <Overwrite>true</Overwrite> + </Platform> + </DeployFile> + <DeployClass Name="AdditionalDebugSymbols"> + <Platform Name="OSX32"> + <Operation>1</Operation> + </Platform> + <Platform Name="Win32"> + <Operation>0</Operation> + </Platform> + </DeployClass> + <DeployClass Name="AndroidClasses"> + <Platform Name="Android"> + <RemoteDir>classes</RemoteDir> + <Operation>64</Operation> + </Platform> + <Platform Name="Android64"> + <RemoteDir>classes</RemoteDir> + <Operation>64</Operation> + </Platform> + </DeployClass> + <DeployClass Name="AndroidFileProvider"> + <Platform Name="Android"> + <RemoteDir>res\xml</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="Android64"> + <RemoteDir>res\xml</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="AndroidGDBServer"> + <Platform Name="Android"> + <RemoteDir>library\lib\armeabi-v7a</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="AndroidLibnativeArmeabiFile"> + <Platform Name="Android"> + <RemoteDir>library\lib\armeabi</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="Android64"> + <RemoteDir>library\lib\armeabi</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="AndroidLibnativeArmeabiv7aFile"> + <Platform Name="Android64"> + <RemoteDir>library\lib\armeabi-v7a</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="AndroidLibnativeMipsFile"> + <Platform Name="Android"> + <RemoteDir>library\lib\mips</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="Android64"> + <RemoteDir>library\lib\mips</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="AndroidServiceOutput"> + <Platform Name="Android"> + <RemoteDir>library\lib\armeabi-v7a</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="Android64"> + <RemoteDir>library\lib\arm64-v8a</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="AndroidServiceOutput_Android32"> + <Platform Name="Android64"> + <RemoteDir>library\lib\armeabi-v7a</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="AndroidSplashImageDef"> + <Platform Name="Android"> + <RemoteDir>res\drawable</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="Android64"> + <RemoteDir>res\drawable</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="AndroidSplashStyles"> + <Platform Name="Android"> + <RemoteDir>res\values</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="Android64"> + <RemoteDir>res\values</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="AndroidSplashStylesV21"> + <Platform Name="Android"> + <RemoteDir>res\values-v21</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="Android64"> + <RemoteDir>res\values-v21</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="Android_Colors"> + <Platform Name="Android"> + <RemoteDir>res\values</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="Android64"> + <RemoteDir>res\values</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="Android_DefaultAppIcon"> + <Platform Name="Android"> + <RemoteDir>res\drawable</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="Android64"> + <RemoteDir>res\drawable</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="Android_LauncherIcon144"> + <Platform Name="Android"> + <RemoteDir>res\drawable-xxhdpi</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="Android64"> + <RemoteDir>res\drawable-xxhdpi</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="Android_LauncherIcon192"> + <Platform Name="Android"> + <RemoteDir>res\drawable-xxxhdpi</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="Android64"> + <RemoteDir>res\drawable-xxxhdpi</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="Android_LauncherIcon36"> + <Platform Name="Android"> + <RemoteDir>res\drawable-ldpi</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="Android64"> + <RemoteDir>res\drawable-ldpi</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="Android_LauncherIcon48"> + <Platform Name="Android"> + <RemoteDir>res\drawable-mdpi</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="Android64"> + <RemoteDir>res\drawable-mdpi</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="Android_LauncherIcon72"> + <Platform Name="Android"> + <RemoteDir>res\drawable-hdpi</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="Android64"> + <RemoteDir>res\drawable-hdpi</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="Android_LauncherIcon96"> + <Platform Name="Android"> + <RemoteDir>res\drawable-xhdpi</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="Android64"> + <RemoteDir>res\drawable-xhdpi</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="Android_NotificationIcon24"> + <Platform Name="Android"> + <RemoteDir>res\drawable-mdpi</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="Android64"> + <RemoteDir>res\drawable-mdpi</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="Android_NotificationIcon36"> + <Platform Name="Android"> + <RemoteDir>res\drawable-hdpi</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="Android64"> + <RemoteDir>res\drawable-hdpi</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="Android_NotificationIcon48"> + <Platform Name="Android"> + <RemoteDir>res\drawable-xhdpi</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="Android64"> + <RemoteDir>res\drawable-xhdpi</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="Android_NotificationIcon72"> + <Platform Name="Android"> + <RemoteDir>res\drawable-xxhdpi</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="Android64"> + <RemoteDir>res\drawable-xxhdpi</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="Android_NotificationIcon96"> + <Platform Name="Android"> + <RemoteDir>res\drawable-xxxhdpi</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="Android64"> + <RemoteDir>res\drawable-xxxhdpi</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="Android_SplashImage426"> + <Platform Name="Android"> + <RemoteDir>res\drawable-small</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="Android64"> + <RemoteDir>res\drawable-small</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="Android_SplashImage470"> + <Platform Name="Android"> + <RemoteDir>res\drawable-normal</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="Android64"> + <RemoteDir>res\drawable-normal</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="Android_SplashImage640"> + <Platform Name="Android"> + <RemoteDir>res\drawable-large</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="Android64"> + <RemoteDir>res\drawable-large</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="Android_SplashImage960"> + <Platform Name="Android"> + <RemoteDir>res\drawable-xlarge</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="Android64"> + <RemoteDir>res\drawable-xlarge</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="Android_Strings"> + <Platform Name="Android"> + <RemoteDir>res\values</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="Android64"> + <RemoteDir>res\values</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="DebugSymbols"> + <Platform Name="iOSSimulator"> + <Operation>1</Operation> + </Platform> + <Platform Name="OSX32"> + <Operation>1</Operation> + </Platform> + <Platform Name="Win32"> + <Operation>0</Operation> + </Platform> + </DeployClass> + <DeployClass Name="DependencyFramework"> + <Platform Name="OSX32"> + <Operation>1</Operation> + <Extensions>.framework</Extensions> + </Platform> + <Platform Name="OSX64"> + <Operation>1</Operation> + <Extensions>.framework</Extensions> + </Platform> + <Platform Name="OSXARM64"> + <Operation>1</Operation> + <Extensions>.framework</Extensions> + </Platform> + <Platform Name="Win32"> + <Operation>0</Operation> + </Platform> + </DeployClass> + <DeployClass Name="DependencyModule"> + <Platform Name="OSX32"> + <Operation>1</Operation> + <Extensions>.dylib</Extensions> + </Platform> + <Platform Name="OSX64"> + <Operation>1</Operation> + <Extensions>.dylib</Extensions> + </Platform> + <Platform Name="OSXARM64"> + <Operation>1</Operation> + <Extensions>.dylib</Extensions> + </Platform> + <Platform Name="Win32"> + <Operation>0</Operation> + <Extensions>.dll;.bpl</Extensions> + </Platform> + </DeployClass> + <DeployClass Required="true" Name="DependencyPackage"> + <Platform Name="iOSDevice32"> + <Operation>1</Operation> + <Extensions>.dylib</Extensions> + </Platform> + <Platform Name="iOSDevice64"> + <Operation>1</Operation> + <Extensions>.dylib</Extensions> + </Platform> + <Platform Name="iOSSimARM64"> + <Operation>1</Operation> + <Extensions>.dylib</Extensions> + </Platform> + <Platform Name="OSX32"> + <Operation>1</Operation> + <Extensions>.dylib</Extensions> + </Platform> + <Platform Name="OSX64"> + <Operation>1</Operation> + <Extensions>.dylib</Extensions> + </Platform> + <Platform Name="OSXARM64"> + <Operation>1</Operation> + <Extensions>.dylib</Extensions> + </Platform> + <Platform Name="Win32"> + <Operation>0</Operation> + <Extensions>.bpl</Extensions> + </Platform> + </DeployClass> + <DeployClass Name="File"> + <Platform Name="Android"> + <Operation>0</Operation> + </Platform> + <Platform Name="Android64"> + <Operation>0</Operation> + </Platform> + <Platform Name="iOSDevice32"> + <Operation>0</Operation> + </Platform> + <Platform Name="iOSDevice64"> + <Operation>0</Operation> + </Platform> + <Platform Name="iOSSimARM64"> + <Operation>0</Operation> + </Platform> + <Platform Name="OSX32"> + <Operation>0</Operation> + </Platform> + <Platform Name="OSX64"> + <Operation>0</Operation> + </Platform> + <Platform Name="OSXARM64"> + <Operation>0</Operation> + </Platform> + <Platform Name="Win32"> + <Operation>0</Operation> + </Platform> + </DeployClass> + <DeployClass Name="ProjectAndroidManifest"> + <Platform Name="Android"> + <Operation>1</Operation> + </Platform> + <Platform Name="Android64"> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="ProjectOSXDebug"/> + <DeployClass Name="ProjectOSXEntitlements"/> + <DeployClass Name="ProjectOSXInfoPList"/> + <DeployClass Name="ProjectOSXResource"> + <Platform Name="OSX32"> + <RemoteDir>Contents\Resources</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="OSX64"> + <RemoteDir>Contents\Resources</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="OSXARM64"> + <RemoteDir>Contents\Resources</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Required="true" Name="ProjectOutput"> + <Platform Name="Android"> + <RemoteDir>library\lib\armeabi-v7a</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="Android64"> + <RemoteDir>library\lib\arm64-v8a</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="iOSDevice32"> + <Operation>1</Operation> + </Platform> + <Platform Name="iOSDevice64"> + <Operation>1</Operation> + </Platform> + <Platform Name="iOSSimARM64"> + <Operation>1</Operation> + </Platform> + <Platform Name="Linux64"> + <Operation>1</Operation> + </Platform> + <Platform Name="OSX32"> + <Operation>1</Operation> + </Platform> + <Platform Name="OSX64"> + <Operation>1</Operation> + </Platform> + <Platform Name="OSXARM64"> + <Operation>1</Operation> + </Platform> + <Platform Name="Win32"> + <Operation>0</Operation> + </Platform> + </DeployClass> + <DeployClass Name="ProjectOutput_Android32"> + <Platform Name="Android64"> + <RemoteDir>library\lib\armeabi-v7a</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="ProjectUWPManifest"> + <Platform Name="Win32"> + <Operation>1</Operation> + </Platform> + <Platform Name="Win64"> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="ProjectiOSDeviceDebug"> + <Platform Name="iOSDevice32"> + <RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="iOSDevice64"> + <RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="iOSSimARM64"> + <RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="ProjectiOSEntitlements"/> + <DeployClass Name="ProjectiOSInfoPList"/> + <DeployClass Name="ProjectiOSLaunchScreen"/> + <DeployClass Name="ProjectiOSResource"> + <Platform Name="iOSDevice32"> + <Operation>1</Operation> + </Platform> + <Platform Name="iOSDevice64"> + <Operation>1</Operation> + </Platform> + <Platform Name="iOSSimARM64"> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="UWP_DelphiLogo150"> + <Platform Name="Win32"> + <RemoteDir>Assets</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="Win64"> + <RemoteDir>Assets</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="UWP_DelphiLogo44"> + <Platform Name="Win32"> + <RemoteDir>Assets</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="Win64"> + <RemoteDir>Assets</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="iOS_AppStore1024"> + <Platform Name="iOSDevice64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="iOSSimARM64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="iPad_AppIcon152"> + <Platform Name="iOSDevice64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="iOSSimARM64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="iPad_AppIcon167"> + <Platform Name="iOSDevice64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="iOSSimARM64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="iPad_Launch2x"> + <Platform Name="iOSDevice64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="iOSSimARM64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="iPad_LaunchDark2x"> + <Platform Name="iOSDevice64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="iOSSimARM64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="iPad_Notification40"> + <Platform Name="iOSDevice64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="iOSSimARM64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="iPad_Setting58"> + <Platform Name="iOSDevice64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="iOSSimARM64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="iPad_SpotLight80"> + <Platform Name="iOSDevice64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="iOSSimARM64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="iPhone_AppIcon120"> + <Platform Name="iOSDevice64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="iOSSimARM64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="iPhone_AppIcon180"> + <Platform Name="iOSDevice64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="iOSSimARM64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="iPhone_Launch2x"> + <Platform Name="iOSDevice64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="iOSSimARM64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="iPhone_Launch3x"> + <Platform Name="iOSDevice64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="iOSSimARM64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="iPhone_LaunchDark2x"> + <Platform Name="iOSDevice64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="iOSSimARM64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="iPhone_LaunchDark3x"> + <Platform Name="iOSDevice64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="iOSSimARM64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="iPhone_Notification40"> + <Platform Name="iOSDevice64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="iOSSimARM64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="iPhone_Notification60"> + <Platform Name="iOSDevice64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="iOSSimARM64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="iPhone_Setting58"> + <Platform Name="iOSDevice64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="iOSSimARM64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="iPhone_Setting87"> + <Platform Name="iOSDevice64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="iOSSimARM64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="iPhone_Spotlight120"> + <Platform Name="iOSDevice64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="iOSSimARM64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <DeployClass Name="iPhone_Spotlight80"> + <Platform Name="iOSDevice64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir> + <Operation>1</Operation> + </Platform> + <Platform Name="iOSSimARM64"> + <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir> + <Operation>1</Operation> + </Platform> + </DeployClass> + <ProjectRoot Platform="Android" Name="$(PROJECTNAME)"/> + <ProjectRoot Platform="Android64" Name="$(PROJECTNAME)"/> + <ProjectRoot Platform="iOSDevice32" Name="$(PROJECTNAME).app"/> + <ProjectRoot Platform="iOSDevice64" Name="$(PROJECTNAME).app"/> + <ProjectRoot Platform="iOSSimARM64" Name="$(PROJECTNAME).app"/> + <ProjectRoot Platform="Linux64" Name="$(PROJECTNAME)"/> + <ProjectRoot Platform="OSX32" Name="$(PROJECTNAME)"/> + <ProjectRoot Platform="OSX64" Name="$(PROJECTNAME)"/> + <ProjectRoot Platform="OSXARM64" Name="$(PROJECTNAME)"/> + <ProjectRoot Platform="Win32" Name="$(PROJECTNAME)"/> + <ProjectRoot Platform="Win64" Name="$(PROJECTNAME)"/> + </Deployment> + </BorlandProject> + <ProjectFileVersion>12</ProjectFileVersion> + </ProjectExtensions> + <Import Project="$(BDS)\Bin\CodeGear.Delphi.Targets" Condition="Exists('$(BDS)\Bin\CodeGear.Delphi.Targets')"/> + <Import Project="$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj" Condition="Exists('$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj')"/> + <Import Project="$(MSBuildProjectName).deployproj" Condition="Exists('$(MSBuildProjectName).deployproj')"/> +</Project> diff --git a/libs/libbsarch/delphi/wbBSArchive.pas b/libs/libbsarch/delphi/wbBSArchive.pas new file mode 100644 index 0000000..533ee1c --- /dev/null +++ b/libs/libbsarch/delphi/wbBSArchive.pas @@ -0,0 +1,2637 @@ +{****************************************************************************** + + This Source Code Form is subject to the terms of the Mozilla Public License, + v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain + one at https://mozilla.org/MPL/2.0/. + +*******************************************************************************} + +unit wbBSArchive; + +interface + +uses + System.SysUtils, + System.Classes, + Winapi.Windows, + System.Threading, + System.SyncObjs, + System.Generics.Defaults, + System.Generics.Collections, + wbStreams, + tfTypes, + tfMD5; + +const + csBSAVersion = '0.9d'; + +type + // per file compression options + TPackingCompression = (pcGlobal, pcCompress, pcUncompress); + + TBGSCompressionType = (ctZlib, ctLZ4Frame, ctLZ4Block); + + TMagic4 = array [0..3] of AnsiChar; + PMagic4 = ^TMagic4; + + TDXGI = ( + DXGI_FORMAT_UNKNOWN, + DXGI_FORMAT_R32G32B32A32_TYPELESS, + DXGI_FORMAT_R32G32B32A32_FLOAT, + DXGI_FORMAT_R32G32B32A32_UINT, + DXGI_FORMAT_R32G32B32A32_SINT, + DXGI_FORMAT_R32G32B32_TYPELESS, + DXGI_FORMAT_R32G32B32_FLOAT, + DXGI_FORMAT_R32G32B32_UINT, + DXGI_FORMAT_R32G32B32_SINT, + DXGI_FORMAT_R16G16B16A16_TYPELESS, + DXGI_FORMAT_R16G16B16A16_FLOAT, + DXGI_FORMAT_R16G16B16A16_UNORM, + DXGI_FORMAT_R16G16B16A16_UINT, + DXGI_FORMAT_R16G16B16A16_SNORM, + DXGI_FORMAT_R16G16B16A16_SINT, + DXGI_FORMAT_R32G32_TYPELESS, + DXGI_FORMAT_R32G32_FLOAT, + DXGI_FORMAT_R32G32_UINT, + DXGI_FORMAT_R32G32_SINT, + DXGI_FORMAT_R32G8X24_TYPELESS, + DXGI_FORMAT_D32_FLOAT_S8X24_UINT, + DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS, + DXGI_FORMAT_X32_TYPELESS_G8X24_UINT, + DXGI_FORMAT_R10G10B10A2_TYPELESS, + DXGI_FORMAT_R10G10B10A2_UNORM, + DXGI_FORMAT_R10G10B10A2_UINT, + DXGI_FORMAT_R11G11B10_FLOAT, + DXGI_FORMAT_R8G8B8A8_TYPELESS, + DXGI_FORMAT_R8G8B8A8_UNORM, + DXGI_FORMAT_R8G8B8A8_UNORM_SRGB, + DXGI_FORMAT_R8G8B8A8_UINT, + DXGI_FORMAT_R8G8B8A8_SNORM, + DXGI_FORMAT_R8G8B8A8_SINT, + DXGI_FORMAT_R16G16_TYPELESS, + DXGI_FORMAT_R16G16_FLOAT, + DXGI_FORMAT_R16G16_UNORM, + DXGI_FORMAT_R16G16_UINT, + DXGI_FORMAT_R16G16_SNORM, + DXGI_FORMAT_R16G16_SINT, + DXGI_FORMAT_R32_TYPELESS, + DXGI_FORMAT_D32_FLOAT, + DXGI_FORMAT_R32_FLOAT, + DXGI_FORMAT_R32_UINT, + DXGI_FORMAT_R32_SINT, + DXGI_FORMAT_R24G8_TYPELESS, + DXGI_FORMAT_D24_UNORM_S8_UINT, + DXGI_FORMAT_R24_UNORM_X8_TYPELESS, + DXGI_FORMAT_X24_TYPELESS_G8_UINT, + DXGI_FORMAT_R8G8_TYPELESS, + DXGI_FORMAT_R8G8_UNORM, + DXGI_FORMAT_R8G8_UINT, + DXGI_FORMAT_R8G8_SNORM, + DXGI_FORMAT_R8G8_SINT, + DXGI_FORMAT_R16_TYPELESS, + DXGI_FORMAT_R16_FLOAT, + DXGI_FORMAT_D16_UNORM, + DXGI_FORMAT_R16_UNORM, + DXGI_FORMAT_R16_UINT, + DXGI_FORMAT_R16_SNORM, + DXGI_FORMAT_R16_SINT, + DXGI_FORMAT_R8_TYPELESS, + DXGI_FORMAT_R8_UNORM, + DXGI_FORMAT_R8_UINT, + DXGI_FORMAT_R8_SNORM, + DXGI_FORMAT_R8_SINT, + DXGI_FORMAT_A8_UNORM, + DXGI_FORMAT_R1_UNORM, + DXGI_FORMAT_R9G9B9E5_SHAREDEXP, + DXGI_FORMAT_R8G8_B8G8_UNORM, + DXGI_FORMAT_G8R8_G8B8_UNORM, + DXGI_FORMAT_BC1_TYPELESS, + DXGI_FORMAT_BC1_UNORM, + DXGI_FORMAT_BC1_UNORM_SRGB, + DXGI_FORMAT_BC2_TYPELESS, + DXGI_FORMAT_BC2_UNORM, + DXGI_FORMAT_BC2_UNORM_SRGB, + DXGI_FORMAT_BC3_TYPELESS, + DXGI_FORMAT_BC3_UNORM, + DXGI_FORMAT_BC3_UNORM_SRGB, + DXGI_FORMAT_BC4_TYPELESS, + DXGI_FORMAT_BC4_UNORM, + DXGI_FORMAT_BC4_SNORM, + DXGI_FORMAT_BC5_TYPELESS, + DXGI_FORMAT_BC5_UNORM, + DXGI_FORMAT_BC5_SNORM, + DXGI_FORMAT_B5G6R5_UNORM, + DXGI_FORMAT_B5G5R5A1_UNORM, + DXGI_FORMAT_B8G8R8A8_UNORM, + DXGI_FORMAT_B8G8R8X8_UNORM, + DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM, + DXGI_FORMAT_B8G8R8A8_TYPELESS, + DXGI_FORMAT_B8G8R8A8_UNORM_SRGB, + DXGI_FORMAT_B8G8R8X8_TYPELESS, + DXGI_FORMAT_B8G8R8X8_UNORM_SRGB, + DXGI_FORMAT_BC6H_TYPELESS, + DXGI_FORMAT_BC6H_UF16, + DXGI_FORMAT_BC6H_SF16, + DXGI_FORMAT_BC7_TYPELESS, + DXGI_FORMAT_BC7_UNORM, + DXGI_FORMAT_BC7_UNORM_SRGB, + DXGI_FORMAT_AYUV, + DXGI_FORMAT_Y410, + DXGI_FORMAT_Y416, + DXGI_FORMAT_NV12, + DXGI_FORMAT_P010, + DXGI_FORMAT_P016, + DXGI_FORMAT_420_OPAQUE, + DXGI_FORMAT_YUY2, + DXGI_FORMAT_Y210, + DXGI_FORMAT_Y216, + DXGI_FORMAT_NV11, + DXGI_FORMAT_AI44, + DXGI_FORMAT_IA44, + DXGI_FORMAT_P8, + DXGI_FORMAT_A8P8, + DXGI_FORMAT_B4G4R4A4_UNORM, + DXGI_FORMAT_P208, + DXGI_FORMAT_V208, + DXGI_FORMAT_V408 + ); + + TDDSHeader = packed record + Magic: TMagic4; + dwSize: Cardinal; + dwFlags: Cardinal; + dwHeight: Cardinal; + dwWidth: Cardinal; + dwPitchOrLinearSize: Cardinal; + dwDepth: Cardinal; + dwMipMapCount: Cardinal; + dwReserved1: array [0..10] of Cardinal; + ddspf: packed record + dwSize: Cardinal; + dwFlags: Cardinal; + dwFourCC: TMagic4; + dwRGBBitCount: Cardinal; + dwRBitMask: Cardinal; + dwGBitMask: Cardinal; + dwBBitMask: Cardinal; + dwABitMask: Cardinal; + end; + dwCaps: Cardinal; + dwCaps2: Cardinal; + dwCaps3: Cardinal; + dwCaps4: Cardinal; + dwReserved2: Cardinal; + end; + PDDSHeader = ^TDDSHeader; + + TDDSHeaderDX10 = packed record + dxgiFormat: Integer; + resourceDimension: Cardinal; + miscFlags: Cardinal; + arraySize: Cardinal; + miscFlags2: Cardinal; + end; + PDDSHeaderDX10 = ^TDDSHeaderDX10; + +const + DDSD_CAPS = $00000001; + DDSD_HEIGHT = $00000002; + DDSD_WIDTH = $00000004; + DDSD_PITCH = $00000008; + DDSD_PIXELFORMAT = $00001000; + DDSD_MIPMAPCOUNT = $00020000; + DDSD_LINEARSIZE = $00080000; + DDSD_DEPTH = $00800000; + DDSCAPS_COMPLEX = $00000008; + DDSCAPS_TEXTURE = $00001000; + DDSCAPS_MIPMAP = $00400000; + DDSCAPS2_CUBEMAP = $00000200; + DDSCAPS2_POSITIVEX = $00000400; + DDSCAPS2_NEGATIVEX = $00000800; + DDSCAPS2_POSITIVEY = $00001000; + DDSCAPS2_NEGATIVEY = $00002000; + DDSCAPS2_POSITIVEZ = $00004000; + DDSCAPS2_NEGATIVEZ = $00008000; + DDSCAPS2_VOLUME = $00200000; + DDPF_ALPHAPIXELS = $00000001; + DDPF_ALPHA = $00000002; + DDPF_FOURCC = $00000004; + DDPF_RGB = $00000040; + DDPF_YUV = $00000200; + DDPF_LUMINANCE = $00020000; + + // DX10 + DDS_DIMENSION_TEXTURE2D = $00000003; + DDS_RESOURCE_MISC_TEXTURECUBE = $00000004; + +type + TwbBSArchive = class; + + TwbResourceDict = TDictionary<string, TwbNothing>; + + TBSArchiveType = (baNone, baTES3, baTES4, baFO3, baSSE, baFO4, baFO4dds, baSF, baSFdds); + TBSArchiveState = (stReading, stWriting); + TBSArchiveStates = set of TBSArchiveState; + TBSFileIterationProc = function(aArchive: Pointer; const aFileName: string; + aFileRecord: Pointer; aFolderRecord: Pointer; aData: Pointer): Boolean; stdcall; + + TDDSInfo = record Width, Height, MipMaps: Integer; end; + TBSFileDDSInfoProc = procedure(aArchive: Pointer; const aFileName: string; + var aInfo: TDDSInfo; aContext: Pointer); stdcall; + + TwbBSHeaderTES3 = packed record + HashOffset: Cardinal; + FileCount: Cardinal; + end; + + TwbBSFileTES3 = record + Hash: UInt64; + Size: Cardinal; + Offset: Cardinal; + Name: string; + end; + PwbBSFileTES3 = ^TwbBSFileTES3; + + TwbBSHeaderTES4 = packed record + FoldersOffset: Cardinal; + Flags: Cardinal; + FolderCount: Cardinal; + FileCount: Cardinal; + FolderNamesLength: Cardinal; + FileNamesLength: Cardinal; + FileFlags: Cardinal; + end; + + TwbBSFileTES4 = record + Hash: UInt64; + Size: Cardinal; + Offset: Int64; + Name: string; + PackingCompression: TPackingCompression; + function Compress(bsa: TwbBSArchive): Boolean; // compress when packing into a new archive + function Compressed(bsa: TwbBSArchive): Boolean; // compressed in existing archive + function RawSize: Cardinal; + end; + PwbBSFileTES4 = ^TwbBSFileTES4; + + TwbBSFolderTES4 = record + Hash: UInt64; + FileCount: Cardinal; + Unk32: Cardinal; + Offset: Int64; + Name: string; + Files: array of TwbBSFileTES4; + end; + PwbBSFolderTES4 = ^TwbBSFolderTES4; + + TwbBSHeaderFO4 = packed record + Magic: TMagic4; + FileCount: Cardinal; + FileTableOffset: Int64; + end; + + TwbBSHeaderSFv2 = packed record + Unknown1: Cardinal; + Unknown2: Cardinal; + end; + + TwbBSHeaderSFv3 = packed record + Unknown1: Cardinal; + Unknown2: Cardinal; + CompressionMethod: Cardinal; + end; + + TwbBSTexChunkRec = record + Size : Cardinal; + PackedSize : Cardinal; + Offset : Int64; + StartMip : Word; + EndMip : Word; + end; + PwbBSTexChunkRec = ^TwbBSTexChunkRec; + + TwbBSFileFO4 = record + NameHash: Cardinal; + Ext: TMagic4; + DirHash: Cardinal; + // GNRL archive format + Unknown: Cardinal; + Offset: Int64; + PackedSize: Cardinal; + Size: Cardinal; + // + // DX10 archive format + UnknownTex : Byte; + //ChunkHeaderSize: Word; + Height : Word; + Width : Word; + NumMips : Byte; + DXGIFormat : Byte; + CubeMaps : Word; + TexChunks : array of TwbBSTexChunkRec; + // + Name: string; + PackingCompression: TPackingCompression; + function DXGIFormatName: string; + function Compress(bsa: TwbBSArchive): Boolean; // compress when packing into a new archive + function Compressed(bsa: TwbBSArchive): Boolean; // compressed in existing archive + end; + PwbBSFileFO4 = ^TwbBSFileFO4; + + TPackedDataHash = TMD5Digest; + TPackedDataInfo = record + Size: Cardinal; + Hash: TPackedDataHash; + FileRecord: Pointer; + end; + PPackedDataInfo = ^TPackedDataInfo; + + TwbBSResultBuffer = packed record + size: Cardinal; + data: PByte; + end; + + TwbBSArchive = class + private + fStream: TwbBaseCachedFileStream; + fStates: TBSArchiveStates; + fType: TBSArchiveType; + fFileName: string; + fMagic: TMagic4; + fVersion: Cardinal; + fCompress: Boolean; + fCompressionType: TBGSCompressionType; + fShareData: Boolean; + fMultiThreaded: Boolean; + fDDSInfoProc: TBSFileDDSInfoProc; + fDDSInfoProcContext: Pointer; + + fHeaderTES3: TwbBSHeaderTES3; + fFilesTES3: array of TwbBSFileTES3; + + fHeaderTES4: TwbBSHeaderTES4; + fFoldersTES4: array of TwbBSFolderTES4; + + fHeaderFO4: TwbBSHeaderFO4; + fHeaderSFv2: TwbBSHeaderSFv2; + fHeaderSFv3: TwbBSHeaderSFv3; + fFilesFO4: array of TwbBSFileFO4; + fMaxChunkCount: Integer; + fSingleMipChunkX: Integer; + fSingleMipChunkY: Integer; + + fDataOffset: Int64; + + fPackedData: array of TPackedDataInfo; + fPackedDataCount: Integer; + + {$IF CompilerVersion >= 34.0} { Delphi 10.4 } + Sync: TLightweightMREW; + {$ELSE} + Sync: IReadWriteSync; + {$IFEND} + + function GetArchiveFormatName: string; + function GetFileCount: Cardinal; + function GetCreatedArchiveSize: Int64; + procedure SetArchiveFlags(aFlags: Cardinal); + procedure SetMultiThreaded(aValue: Boolean); + function FindFileRecordTES3(const aFileName: string; var aFileIdx: Integer): Boolean; + function FindFileRecordTES4(const aFileName: string; var aFolderIdx, aFileIdx: Integer): Boolean; + function FindFileRecordFO4(const aFileName: string; var aFileIdx: Integer): Boolean; + function GetDDSMipChunkNum(var aDDSInfo: TDDSInfo): Integer; + function CalcDataHash(aData: Pointer; aLen: Cardinal): TPackedDataHash; + function FindPackedData(aSize: Cardinal; aHash: TPackedDataHash; aFileRecord: Pointer): Boolean; + procedure AddPackedData(aSize: Cardinal; aHash: TPackedDataHash; aFileRecord: Pointer); + procedure PackData(aFileRecord: Pointer; const aFileName: string; + aDataHash: TPackedDataHash; aData: PByte; aSize: Integer; + aCompress: Boolean; aDoCompress: Boolean = False); + procedure CompressStream(aSrc, aDst: TStream); + procedure DecompressBuf(aSrc: Pointer; aSrcSize: Integer; aDst: Pointer; aDstSize: Integer); + + public + constructor Create; + destructor Destroy; override; + procedure LoadFromFile(const aFileName: string); + procedure CreateArchive(const aFileName: string; aType: TBSArchiveType; + aFilesList: TStringList = nil); + procedure Save; + procedure AddFileDisk(const aFilePath, aSourcePath: string); + procedure AddFileDiskRoot(const aRootDir, aSourcePath: string); + procedure AddFileData(const aFileName: string; const aSize: Cardinal; const aData: PByte); overload; + function FindFileRecord(const aFileName: string): Pointer; + function ExtractFileData(aFileRecord: Pointer): TwbBSResultBuffer; overload; + function ExtractFileData(const aFileName: string): TwbBSResultBuffer; overload; + procedure ReleaseFileData(fileDataResult: TwbBSResultBuffer); + procedure ExtractFile(const aFileName, aSaveAs: string); + procedure IterateFiles(aProc: TBSFileIterationProc; aData: Pointer = nil; + aSingleThreaded: Boolean = False); + function FileExists(const aFileName: string): Boolean; + procedure ResourceList(const aList: TStrings; aFolder: string = ''); + procedure ResourceDict(const aDict: TwbResourceDict; aFolder: string = ''); + //procedure IterateFolders(aProc: TBSFileIterationProc); + procedure Close; + + procedure SyncBeginWrite; + procedure SyncEndWrite; + + property FileName: string read fFileName; + property ArchiveType: TBSArchiveType read fType; + property Version: Cardinal read fVersion; + property FormatName: string read GetArchiveFormatName; + property FileCount: Cardinal read GetFileCount; + property CreatedArchiveSize: Int64 read GetCreatedArchiveSize; + property ArchiveFlags: Cardinal read fHeaderTES4.Flags write SetArchiveFlags; + property FileFlags: Cardinal read fHeaderTES4.FileFlags write fHeaderTES4.FileFlags; + property Compress: Boolean read fCompress write fCompress; + property ShareData: Boolean read fShareData write fShareData; + property MultiThreaded: Boolean read fMultiThreaded write SetMultiThreaded; + property DDSInfoProc: TBSFileDDSInfoProc read fDDSInfoProc write fDDSInfoProc; + property DDSInfoProcContext: Pointer read fDDSInfoProcContext write fDDSInfoProcContext; + end; + +const + cArchiveFormatNames: array[TBSArchiveType] of string = ( + 'None', + 'Morrowind', + 'Oblivion', + 'Skyrim LE, New Vegas, Fallout 3', + 'Skyrim SE, Skyrim AE', + 'Fallout 4', + 'Fallout 4 DDS', + 'Starfield', + 'Starfield DDS' + ); + + cArchiveTypeExtensions: array[TBSArchiveType] of string = ( + '.bsa', + '.bsa', + '.bsa', + '.bsa', + '.bsa', + '.ba2', + '.ba2', + '.ba2', + '.ba2' + ); + + cArchiveFlagNames: array [0..31] of string = ( + 'Include Directory Names', 'Include File Names', 'Compressed', + 'Retain Directory Names', 'Retain File Names', + 'Retain File Name Offsets', 'XBox 360 Archive', + 'Retain Strings During Startup', + 'Embed File Names', 'XMem Codec', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '' + ); + + cFileFlagNames: array [0..31] of string = ( + 'Meshes', 'Textures', 'Menus', 'Sounds', + 'Voices', 'Shaders', 'Trees', 'Fonts', + 'Misc', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '' + ); + +function SplitDirName(const aFileName: string; var Dir, Name: string): Integer; +function SplitNameExt(const aFileName: string; var Name, Ext: string; aNoExtDot: Boolean = False): Integer; +function CreateHashTES3(const aFileName: string): UInt64; +function CreateHashTES4(const aFileName: string): UInt64; overload; +function CreateHashTES4(const aName, aExt: string): UInt64; overload; +function CreateHashFO4(const aFileName: string): Cardinal; + +implementation + +uses + TypInfo, + zlibEx, + lz4io; + +const + MAGIC_TES3: TMagic4 = #0#1#0#0; + MAGIC_BSA : TMagic4 = 'BSA'#0; + MAGIC_BTDX: TMagic4 = 'BTDX'; + MAGIC_GNRL: TMagic4 = 'GNRL'; + MAGIC_DX10: TMagic4 = 'DX10'; + MAGIC_DDS : TMagic4 = 'DDS '; + MAGIC_DXT1: TMagic4 = 'DXT1'; + MAGIC_DXT3: TMagic4 = 'DXT3'; + MAGIC_DXT5: TMagic4 = 'DXT5'; + MAGIC_ATI1: TMagic4 = 'ATI1'; + MAGIC_ATI2: TMagic4 = 'ATI2'; + MAGIC_BC4S: TMagic4 = 'BC4S'; + MAGIC_BC4U: TMagic4 = 'BC4U'; + MAGIC_BC5S: TMagic4 = 'BC5S'; + MAGIC_BC5U: TMagic4 = 'BC5U'; + + iFileFO4Unknown = $00100100; + iFileFO4Tail = $BAADF00D; + + { https://github.com/jonwd7/bae/blob/master/src/bsa.h } + + // header versions + HEADER_VERSION_TES4 = $67; // Oblivion + HEADER_VERSION_FO3 = $68; // FO3, FNV, TES5 + HEADER_VERSION_SSE = $69; // SSE + HEADER_VERSION_FO4v1 = $01; // FO4 + HEADER_VERSION_SF2 = $02; // SF + HEADER_VERSION_SF3 = $03; // SF + HEADER_VERSION_FO4NGv7 = $07; // FO4NG + HEADER_VERSION_FO4NGv8 = $08; // FO4NG2 + + + // archive flags + ARCHIVE_PATHNAMES = $0001; // Whether the BSA has names for paths + ARCHIVE_FILENAMES = $0002; // Whether the BSA has names for files + ARCHIVE_COMPRESS = $0004; // Whether the files are compressed in archive (invert file's compression flag) + ARCHIVE_RETAINDIR = $0008; + ARCHIVE_RETAINNAME = $0010; + ARCHIVE_RETAINFOFF = $0020; + ARCHIVE_XBOX360 = $0040; + ARCHIVE_STARTUPSTR = $0080; + ARCHIVE_EMBEDNAME = $0100; // Whether the name is prefixed to the data + ARCHIVE_XMEM = $0200; + ARCHIVE_UNKNOWN10 = $0400; + + // file flags + FILE_NIF = $0001; + FILE_DDS = $0002; + FILE_XML = $0004; + FILE_WAV = $0008; + FILE_MP3 = $0010; + FILE_TXT = $0020; // TXT, HTML, BAT, SCC + FILE_SPT = $0040; + FILE_FNT = $0080; // TEX, FNT + FILE_MISC = $0100; // CTL and others + + FILE_SIZE_COMPRESS = $40000000; // Whether the file is compressed + + crc32table : array [0..255] of Cardinal = ( + $00000000, $77073096, $ee0e612c, $990951ba, $076dc419, $706af48f, + $e963a535, $9e6495a3, $0edb8832, $79dcb8a4, $e0d5e91e, $97d2d988, + $09b64c2b, $7eb17cbd, $e7b82d07, $90bf1d91, $1db71064, $6ab020f2, + $f3b97148, $84be41de, $1adad47d, $6ddde4eb, $f4d4b551, $83d385c7, + $136c9856, $646ba8c0, $fd62f97a, $8a65c9ec, $14015c4f, $63066cd9, + $fa0f3d63, $8d080df5, $3b6e20c8, $4c69105e, $d56041e4, $a2677172, + $3c03e4d1, $4b04d447, $d20d85fd, $a50ab56b, $35b5a8fa, $42b2986c, + $dbbbc9d6, $acbcf940, $32d86ce3, $45df5c75, $dcd60dcf, $abd13d59, + $26d930ac, $51de003a, $c8d75180, $bfd06116, $21b4f4b5, $56b3c423, + $cfba9599, $b8bda50f, $2802b89e, $5f058808, $c60cd9b2, $b10be924, + $2f6f7c87, $58684c11, $c1611dab, $b6662d3d, $76dc4190, $01db7106, + $98d220bc, $efd5102a, $71b18589, $06b6b51f, $9fbfe4a5, $e8b8d433, + $7807c9a2, $0f00f934, $9609a88e, $e10e9818, $7f6a0dbb, $086d3d2d, + $91646c97, $e6635c01, $6b6b51f4, $1c6c6162, $856530d8, $f262004e, + $6c0695ed, $1b01a57b, $8208f4c1, $f50fc457, $65b0d9c6, $12b7e950, + $8bbeb8ea, $fcb9887c, $62dd1ddf, $15da2d49, $8cd37cf3, $fbd44c65, + $4db26158, $3ab551ce, $a3bc0074, $d4bb30e2, $4adfa541, $3dd895d7, + $a4d1c46d, $d3d6f4fb, $4369e96a, $346ed9fc, $ad678846, $da60b8d0, + $44042d73, $33031de5, $aa0a4c5f, $dd0d7cc9, $5005713c, $270241aa, + $be0b1010, $c90c2086, $5768b525, $206f85b3, $b966d409, $ce61e49f, + $5edef90e, $29d9c998, $b0d09822, $c7d7a8b4, $59b33d17, $2eb40d81, + $b7bd5c3b, $c0ba6cad, $edb88320, $9abfb3b6, $03b6e20c, $74b1d29a, + $ead54739, $9dd277af, $04db2615, $73dc1683, $e3630b12, $94643b84, + $0d6d6a3e, $7a6a5aa8, $e40ecf0b, $9309ff9d, $0a00ae27, $7d079eb1, + $f00f9344, $8708a3d2, $1e01f268, $6906c2fe, $f762575d, $806567cb, + $196c3671, $6e6b06e7, $fed41b76, $89d32be0, $10da7a5a, $67dd4acc, + $f9b9df6f, $8ebeeff9, $17b7be43, $60b08ed5, $d6d6a3e8, $a1d1937e, + $38d8c2c4, $4fdff252, $d1bb67f1, $a6bc5767, $3fb506dd, $48b2364b, + $d80d2bda, $af0a1b4c, $36034af6, $41047a60, $df60efc3, $a867df55, + $316e8eef, $4669be79, $cb61b38c, $bc66831a, $256fd2a0, $5268e236, + $cc0c7795, $bb0b4703, $220216b9, $5505262f, $c5ba3bbe, $b2bd0b28, + $2bb45a92, $5cb36a04, $c2d7ffa7, $b5d0cf31, $2cd99e8b, $5bdeae1d, + $9b64c2b0, $ec63f226, $756aa39c, $026d930a, $9c0906a9, $eb0e363f, + $72076785, $05005713, $95bf4a82, $e2b87a14, $7bb12bae, $0cb61b38, + $92d28e9b, $e5d5be0d, $7cdcefb7, $0bdbdf21, $86d3d2d4, $f1d4e242, + $68ddb3f8, $1fda836e, $81be16cd, $f6b9265b, $6fb077e1, $18b74777, + $88085ae6, $ff0f6a70, $66063bca, $11010b5c, $8f659eff, $f862ae69, + $616bffd3, $166ccf45, $a00ae278, $d70dd2ee, $4e048354, $3903b3c2, + $a7672661, $d06016f7, $4969474d, $3e6e77db, $aed16a4a, $d9d65adc, + $40df0b66, $37d83bf0, $a9bcae53, $debb9ec5, $47b2cf7f, $30b5ffe9, + $bdbdf21c, $cabac28a, $53b39330, $24b4a3a6, $bad03605, $cdd70693, + $54de5729, $23d967bf, $b3667a2e, $c4614ab8, $5d681b02, $2a6f2b94, + $b40bbe37, $c30c8ea1, $5a05df1b, $2d02ef8d + ); + +type + TPreallocatedMemoryStream = class(TCustomMemoryStream) + public + constructor Create(Ptr: Pointer; Size: Int64); + function Write(const Buffer; Count: Longint): Longint; override; + end; + +constructor TPreallocatedMemoryStream.Create(Ptr: Pointer; Size: Int64); +begin + inherited Create; + SetPointer(Ptr, Size); +end; + +function TPreallocatedMemoryStream.Write(const Buffer; Count: Integer): Longint; +begin + Result := Size-Position; + if Result > Count then + Result := Count; + System.Move(Buffer, Pointer(PByte(Memory) + Position)^, Result); + Seek(Result, soCurrent); +end; + +function Magic2Int(aMagic: TMagic4): Cardinal; inline; +begin + Result := PCardinal(@aMagic)^; +end; + +function Int2Magic(aInt: Cardinal): TMagic4; inline; +begin + Result := PMagic4(@aInt)^; +end; + +function String2Magic(const aStr: string): TMagic4; +begin + Result := #0#0#0#0; + if Length(aStr) > 0 then Result[0] := AnsiChar(aStr[1]); + if Length(aStr) > 1 then Result[1] := AnsiChar(aStr[2]); + if Length(aStr) > 2 then Result[2] := AnsiChar(aStr[3]); + if Length(aStr) > 3 then Result[3] := AnsiChar(aStr[4]); +end; + +function LowerByte(ch: AnsiChar): Byte; inline; +begin + case ch of + 'A'..'Z': + Result := Byte(Ord(ch) + Ord('a')-Ord('A')); + else + Result := Byte(ch); + end; +end; + +function LastCharPos(const s: string; const Chr: char): Integer; inline; +begin + for Result := Length(s) downto 1 do + if s[Result] = Chr then + Exit; + Result := 0; +end; + +function Str2MagicInt(const s: string): Cardinal; +var + i: integer; +begin + Result := 0; + for i := 1 to Length(s) do begin + PByte(PByte(@Result) + i-1)^ := LowerByte(AnsiChar(s[i])); + if i = 4 then Break; + end; +end; + +function SplitDirName(const aFileName: string; var Dir, Name: string): Integer; +begin + Result := LastCharPos(aFileName, '\'); + if Result = 0 then + Result := LastCharPos(aFileName, '/'); + if Result <> 0 then begin + Dir := Copy(aFileName, 1, Pred(Result)); + Name := Copy(aFileName, Succ(Result), Length(aFileName) - Result); + end + else begin + Dir := ''; + Name := aFileName; + end; +end; + +function SplitNameExt(const aFileName: string; var Name, Ext: string; aNoExtDot: Boolean = False): Integer; +begin + Result := LastCharPos(aFileName, '.'); + if Result <> 0 then begin + Name := Copy(aFileName, 1, Pred(Result)); + if aNoExtDot then + Inc(Result); + Ext := Copy(aFileName, Result, Length(aFileName) - Result + 1); + end + else begin + Name := aFileName; + Ext := ''; + end; +end; + +function CreateHashTES3(const aFileName: string): UInt64; +var + s: AnsiString; + i, l: integer; + sum, off, temp, n: Cardinal; +begin + s := AnsiString(aFileName); + l := Length(s) shr 1; + + sum := 0; off := 0; + for i := 1 to l do begin + temp := Cardinal(LowerByte(s[i])) shl (off and $1F); + sum := sum xor temp; + off := off + 8; + end; + Result := UInt64(sum) shl 32; + + sum := 0; off := 0; + for i := l + 1 to Length(s) do begin + temp := Cardinal(LowerByte(s[i])) shl (off and $1F); + sum := sum xor temp; + n := temp and $1F; + sum := (sum shr n) or (sum shl (32 - n)); + off := off + 8; + end; + Result := Result or sum; +end; + +function CreateHashTES4(const aName, aExt: string): UInt64; overload; +var + i, l: integer; + hash: Cardinal; + ext: array [0..3] of Byte; + s, e: AnsiString; +begin + Result := 0; + s := AnsiString(aName); + e := AnsiString(aExt); + + l := Length(s); + if l = 0 then + Exit; + + Result := LowerByte(s[l]); + if l > 2 then + Result := Result or (Cardinal(LowerByte(s[l-1])) shl 8); + Result := Result or (l shl 16); + Result := Result or (Cardinal(LowerByte(s[1])) shl 24); + + PCardinal(@ext)^ := 0; + for i := 1 to Length(e) do begin + ext[i-1] := LowerByte(e[i]); + if i = 4 then Break; + end; + + case PCardinal(@ext)^ of + $00666B2E: Result := Result or $80; // .kf + $66696E2E: Result := Result or $8000; // .nif + $7364642E: Result := Result or $8080; // .dds + $7661772E: Result := Result or $80000000; // .wav + end; + + hash := 0; + for i := 2 to l-2 do + hash := LowerByte(s[i]) + (hash shl 6) + (hash shl 16) - hash; + Result := Result + UInt64(hash) shl 32; + + hash := 0; + for i := 1 to Length(e) do + hash := LowerByte(e[i]) + (hash shl 6) + (hash shl 16) - hash; + Result := Result + UInt64(hash) shl 32; +end; + +function CreateHashTES4(const aFileName: string): UInt64; overload; +var + fname, fext: string; +begin + SplitNameExt(aFileName, fname, fext); + Result := CreateHashTES4(fname, fext); +end; + +function CreateHashFO4(const aFileName: string): Cardinal; +var + i: Integer; + s: AnsiString; + c: AnsiChar; +begin + Result := 0; + s := AnsiString(aFileName); + for i := 1 to Length(s) do begin + c := s[i]; + if Byte(c) > 127 then Continue; + if c = '/' then c := '\'; + Result := (Result shr 8) xor crc32table[(Result xor LowerByte(c)) and $FF]; + end; +end; + +function TwbBSFileTES4.Compress(bsa: TwbBSArchive): Boolean; +begin + case PackingCompression of + pcCompress : Result := True; + pcUncompress: Result := False; + else + Result := bsa.Compress; + end; +end; + +function TwbBSFileTES4.Compressed(bsa: TwbBSArchive): Boolean; +begin + Result := (bsa.ArchiveFlags and ARCHIVE_COMPRESS <> 0) xor (Size and FILE_SIZE_COMPRESS <> 0); +end; + +function TwbBSFileTES4.RawSize: Cardinal; +begin + Result := Size and not FILE_SIZE_COMPRESS; +end; + +function TwbBSFileFO4.DXGIFormatName: string; +begin + Result := GetEnumName(TypeInfo(TDXGI), Integer(DXGIFormat)); +end; + +function TwbBSFileFO4.Compress(bsa: TwbBSArchive): Boolean; +begin + case PackingCompression of + pcCompress : Result := True; + pcUncompress: Result := False; + else + Result := bsa.Compress; + end; +end; + +function TwbBSFileFO4.Compressed(bsa: TwbBSArchive): Boolean; +begin + if bsa.ArchiveType = baFO4 then + Result := PackedSize <> 0 + else + Result := (Length(TexChunks) <> 0) and (TexChunks[0].PackedSize <> 0); +end; + + +{ TwbBSArchive } + +constructor TwbBSArchive.Create; +begin + fType := baNone; + fMaxChunkCount := 4; + fSingleMipChunkX := 512; + fSingleMipChunkY := 512; +end; + +destructor TwbBSArchive.Destroy; +begin + if fStates * [stReading, stWriting] <> [] then + Close; +end; + +function TwbBSArchive.GetArchiveFormatName: string; +begin + Result := cArchiveFormatNames[fType]; +end; + +function TwbBSArchive.GetFileCount: Cardinal; +begin + case fType of + baTES3: + Result := fHeaderTES3.FileCount; + baTES4, baFO3, baSSE: + Result := fHeaderTES4.FileCount; + baFO4, baFO4dds, baSF, baSFdds: + Result := fHeaderFO4.FileCount; + else + Result := 0; + end; +end; + +procedure TwbBSArchive.SetArchiveFlags(aFlags: Cardinal); +begin + if not (fType in [baTES4, baFO3, baSSE]) then + raise Exception.Create('Archive flags are not supported for this archive type'); + + fHeaderTES4.Flags := aFlags; + + // force compression flag if needed + if fCompress then + fHeaderTES4.Flags := fHeaderTES4.Flags or ARCHIVE_COMPRESS; +end; + +procedure TwbBSArchive.SetMultiThreaded(aValue: Boolean); +begin + fMultiThreaded := aValue; + {$IF CompilerVersion < 34.0} + if aValue and not Assigned(Sync) then + Sync := TReadWriteSync.Create; + {$IFEND} +end; + +function TwbBSArchive.FindFileRecordTES3(const aFileName: string; var aFileIdx: Integer): Boolean; +var + h: UInt64; + i: integer; +begin + h := CreateHashTES3(aFileName); + + Result := False; + for i := Low(fFilesTES3) to High(fFilesTES3) do + if fFilesTES3[i].Hash = h then begin + aFileIdx := i; + Result := True; + Exit; + end; +end; + +function TwbBSArchive.FindFileRecordTES4(const aFileName: string; var aFolderIdx, aFileIdx: Integer): Boolean; +var + fdir, fname, name, ext: string; + h: UInt64; + i, j: integer; +begin + SplitDirName(aFileName, fdir, fname); + Result := False; + h := CreateHashTES4(fdir, ''); + for i := Low(fFoldersTES4) to High(fFoldersTES4) do begin + if h <> fFoldersTES4[i].Hash then + // since table is sorted by hash, we can abort when our hash is lesser + if h < fFoldersTES4[i].Hash then + Exit + else + Continue; + + SplitNameExt(fname, name, ext); + h := CreateHashTES4(name, ext); + for j := Low(fFoldersTES4[i].Files) to High(fFoldersTES4[i].Files) do begin + if h <> fFoldersTES4[i].Files[j].Hash then + if h < fFoldersTES4[i].Files[j].Hash then + Exit + else + Continue; + + Result := True; + aFolderIdx := i; + aFileIdx := j; + Exit; + end; + end; +end; + +function TwbBSArchive.FindFileRecordFO4(const aFileName: string; var aFileIdx: Integer): Boolean; +var + fdir, fname, name, ext: string; + hdir, hfile: Cardinal; + hext: TMagic4; + i: integer; +begin + SplitDirName(aFileName, fdir, fname); + SplitNameExt(fname, name, ext, True); + hdir := CreateHashFO4(fdir); + hfile := CreateHashFO4(name); + hext := String2Magic(LowerCase(ext)); + + Result := False; + for i := Low(fFilesFO4) to High(fFilesFO4) do + if (fFilesFO4[i].DirHash = hdir) and (fFilesFO4[i].NameHash = hfile) and (fFilesFO4[i].Ext = hext) then begin + aFileIdx := i; + Result := True; + Exit; + end; +end; + +function TwbBSArchive.FindFileRecord(const aFileName: string): Pointer; +var + i, j: integer; +begin + Result := nil; + + case fType of + baTES3: + if FindFileRecordTES3(aFileName, i) then Result := @fFilesTES3[i]; + baTES4, baFO3, baSSE: + if FindFileRecordTES4(aFileName, i, j) then Result := @fFoldersTES4[i].Files[j]; + baFO4, baFO4dds, baSF, baSFdds: + if FindFileRecordFO4(aFileName, i) then Result := @fFilesFO4[i]; + end; +end; + +function TwbBSArchive.GetDDSMipChunkNum(var aDDSInfo: TDDSInfo): Integer; +var + w, h: Integer; +begin + w := aDDSInfo.Width; + h := aDDSInfo.Height; + Result := 1; + while (Result < aDDSInfo.MipMaps) and + (Result < fMaxChunkCount) and + (w >= fSingleMipChunkX) and + (h >= fSingleMipChunkY) + do begin + Inc(Result); + w := w div 2; + h := h div 2; + end; +end; + +function TwbBSArchive.CalcDataHash(aData: Pointer; aLen: Cardinal): TPackedDataHash; +var + fMD5: TMD5Alg; +begin + fMD5.Init(@fMD5); + fMD5.Update(@fMD5, aData, aLen); + fMD5.Done(@fMD5, @Result); +end; + +function TwbBSArchive.FindPackedData(aSize: Cardinal; aHash: TPackedDataHash; aFileRecord: Pointer): Boolean; +var + i: Integer; +begin + Result := False; + + if not fShareData then + Exit; + + for i := 0 to Pred(fPackedDataCount) do + if (aSize = fPackedData[i].Size) and CompareMem(@aHash, @fPackedData[i].Hash, SizeOf(aHash)) then begin + case fType of + baTES3: begin + PwbBSFileTES3(aFileRecord).Size := PwbBSFileTES3(fPackedData[i].FileRecord).Size; + PwbBSFileTES3(aFileRecord).Offset := PwbBSFileTES3(fPackedData[i].FileRecord).Offset; + end; + baTES4, baFO3, baSSE: begin + PwbBSFileTES4(aFileRecord).Size := PwbBSFileTES4(fPackedData[i].FileRecord).Size; + PwbBSFileTES4(aFileRecord).Offset := PwbBSFileTES4(fPackedData[i].FileRecord).Offset; + end; + baFO4, baSF: begin + PwbBSFileFO4(aFileRecord).Size := PwbBSFileFO4(fPackedData[i].FileRecord).Size; + PwbBSFileFO4(aFileRecord).PackedSize := PwbBSFileFO4(fPackedData[i].FileRecord).PackedSize; + PwbBSFileFO4(aFileRecord).Offset := PwbBSFileFO4(fPackedData[i].FileRecord).Offset; + end; + baFO4dds, baSFdds: begin + PwbBSTexChunkRec(aFileRecord).Size := PwbBSTexChunkRec(fPackedData[i].FileRecord).Size; + PwbBSTexChunkRec(aFileRecord).PackedSize := PwbBSTexChunkRec(fPackedData[i].FileRecord).PackedSize; + PwbBSTexChunkRec(aFileRecord).Offset := PwbBSTexChunkRec(fPackedData[i].FileRecord).Offset; + end; + end; + Result := True; + Exit; + end; +end; + +procedure TwbBSArchive.AddPackedData(aSize: Cardinal; aHash: TPackedDataHash; aFileRecord: Pointer); +begin + if not fShareData then + Exit; + + if fPackedDataCount = Length(fPackedData) then + if Length(fPackedData) = 0 then + SetLength(fPackedData, 2048) + else + SetLength(fPackedData, Length(fPackedData) * 2); + + fPackedData[fPackedDataCount].Size := aSize; + fPackedData[fPackedDataCount].Hash := aHash; + fPackedData[fPackedDataCount].FileRecord := aFileRecord; + Inc(fPackedDataCount); +end; + +procedure TwbBSArchive.LoadFromFile(const aFileName: string); +var + i, j: Integer; +begin + if fStates * [stReading, stWriting] <> [] then + Close; + + fStream := TwbReadOnlyCachedFileStream.Create(aFileName, fmOpenRead or fmShareDenyWrite); + + // magic + fMagic := Int2Magic(fStream.ReadCardinal); + if fMagic = MAGIC_TES3 then fType := baTES3 else + if fMagic = MAGIC_BSA then fType := baTES4 else + if fMagic = MAGIC_BTDX then fType := baFO4 else + raise Exception.Create('Unknown archive format'); + + // archive version except Morrowind + if fType <> baTES3 then begin + fVersion := fStream.ReadCardinal; + fCompressionType := ctZlib; // default compression type + case fVersion of + HEADER_VERSION_TES4: + fType := baTES4; + HEADER_VERSION_FO3 : + fType := baFO3; + HEADER_VERSION_SSE : + fType := baSSE; + HEADER_VERSION_FO4v1, + HEADER_VERSION_FO4NGv7, + HEADER_VERSION_FO4NGv8 : + fType := baFO4; + HEADER_VERSION_SF2, + HEADER_VERSION_SF3: + fType := baSF; + else + raise Exception.Create('Unknown archive version 0x' + IntToHex(fVersion, 8)); + end; + end; + + case fType of + //-------------------------------------------------- + // Morrowind + baTES3: begin + // read header + fStream.ReadBuffer(fHeaderTES3, SizeOf(fHeaderTES3)); + SetLength(fFilesTES3, fHeaderTES3.FileCount); + for i := Low(fFilesTES3) to High(fFilesTES3) do begin + fFilesTES3[i].Size := fStream.ReadCardinal; + fFilesTES3[i].Offset := fStream.ReadCardinal; + end; + // skip name offsets + fStream.Position := fStream.Position + 4 * fHeaderTES3.FileCount; + // read names + for i := Low(fFilesTES3) to High(fFilesTES3) do + fFilesTES3[i].Name := fStream.ReadStringTerm; + // read hashes + for i := Low(fFilesTES3) to High(fFilesTES3) do + fFilesTES3[i].Hash := fStream.ReadUInt64; + // remember binary data offset since stored files offsets are relative + fDataOffset := fStream.Position; + end; + + //-------------------------------------------------- + // Fallout 4, Starfield + baFO4, baSF: begin + // read header + fStream.ReadBuffer(fHeaderFO4, SizeOf(fHeaderFO4)); + // SF header + if fType = baSF then + if fVersion = HEADER_VERSION_SF2 then + fStream.ReadBuffer(fHeaderSFv2, SizeOf(fHeaderSFv2)) + else begin + fStream.ReadBuffer(fHeaderSFv3, SizeOf(fHeaderSFv3)); + if fHeaderSFv3.CompressionMethod = 3 then + fCompressionType := ctLZ4Block; + end; + + // read GNRL files + if fHeaderFO4.Magic = MAGIC_GNRL then begin + SetLength(fFilesFO4, fHeaderFO4.FileCount); + for i := Low(fFilesFO4) to High(fFilesFO4) do begin + fFilesFO4[i].NameHash := fStream.ReadCardinal; + fFilesFO4[i].Ext := Int2Magic(fStream.ReadCardinal); + fFilesFO4[i].DirHash := fStream.ReadCardinal; + fFilesFO4[i].Unknown := fStream.ReadCardinal; + fFilesFO4[i].Offset := fStream.ReadInt64; + fFilesFO4[i].PackedSize := fStream.ReadCardinal; + fFilesFO4[i].Size := fStream.ReadCardinal; + fStream.ReadCardinal; // BAADF00D + end; + end + // read DX10 textures + else if fHeaderFO4.Magic = MAGIC_DX10 then begin + if fType = baFO4 then + fType := baFO4dds + else if fType = baSF then + fType := baSFdds; + SetLength(fFilesFO4, fHeaderFO4.FileCount); + for i := Low(fFilesFO4) to High(fFilesFO4) do begin + fFilesFO4[i].NameHash := fStream.ReadCardinal; + fFilesFO4[i].Ext := Int2Magic(fStream.ReadCardinal); + fFilesFO4[i].DirHash := fStream.ReadCardinal; + fFilesFO4[i].UnknownTex := fStream.ReadByte; + SetLength(fFilesFO4[i].TexChunks, fStream.ReadByte); + fStream.ReadWord; // skip chunkHeaderSize, always 24 + //fFilesFO4[i].ChunkHeaderSize := fStream.ReadWord; + fFilesFO4[i].Height := fStream.ReadWord; + fFilesFO4[i].Width := fStream.ReadWord; + fFilesFO4[i].NumMips := fStream.ReadByte; + fFilesFO4[i].DXGIFormat := fStream.ReadByte; + fFilesFO4[i].CubeMaps := fStream.ReadWord; + for j := Low(fFilesFO4[i].TexChunks) to High(fFilesFO4[i].TexChunks) do + with fFilesFO4[i].TexChunks[j] do begin + Offset := fStream.ReadInt64; + PackedSize := fStream.ReadCardinal; + Size := fStream.ReadCardinal; + StartMip := fStream.ReadWord; + EndMip := fStream.ReadWord; + fStream.ReadCardinal; // skip BAADF00D + end; + end; + end + else + raise Exception.Create('Unknown BA2 archive type'); + + // read file names + fStream.Position := fHeaderFO4.FileTableOffset; + for i := Low(fFilesFO4) to High(fFilesFO4) do + fFilesFO4[i].Name := StringReplace(fStream.ReadStringLen16, '/', '\', [rfReplaceAll]); + end; + + //-------------------------------------------------- + // Oblivion, Fallout 3, New Vegas, Skyrim, Skyrim SE + baTES4, baFO3, baSSE: begin + if fType = baSSE then + fCompressionType := ctLZ4Frame; + + // read header + fStream.ReadBuffer(fHeaderTES4, SizeOf(fHeaderTES4)); + fStream.Position := fHeaderTES4.FoldersOffset; + + // read folder records + SetLength(fFoldersTES4, fHeaderTES4.FolderCount); + for i := Low(fFoldersTES4) to High(fFoldersTES4) do begin + fFoldersTES4[i].Hash := fStream.ReadUInt64; + fFoldersTES4[i].FileCount := fStream.ReadCardinal; + if fType = baSSE then begin + fFoldersTES4[i].Unk32 := fStream.ReadCardinal; + fFoldersTES4[i].Offset := fStream.ReadInt64; + end else + fFoldersTES4[i].Offset := fStream.ReadCardinal; + end; + + // read folder names and file records + for i := Low(fFoldersTES4) to High(fFoldersTES4) do begin + fFoldersTES4[i].Name := fStream.ReadStringLen; + SetLength(fFoldersTES4[i].Files, fFoldersTES4[i].FileCount); + for j := Low(fFoldersTES4[i].Files) to High(fFoldersTES4[i].Files) do begin + fFoldersTES4[i].Files[j].Hash := fStream.ReadUInt64; + fFoldersTES4[i].Files[j].Size := fStream.ReadCardinal; + fFoldersTES4[i].Files[j].Offset := fStream.ReadCardinal; + end; + end; + + // read file names + for i := Low(fFoldersTES4) to High(fFoldersTES4) do + for j := Low(fFoldersTES4[i].Files) to High(fFoldersTES4[i].Files) do + fFoldersTES4[i].Files[j].Name := fStream.ReadStringTerm; + end; + + end; + + fFileName := aFileName; + Include(fStates, stReading); +end; + + +type + THashPair = record DirHash, FileHash: UInt64; pc: TPackingCompression end; + PHashPair = ^THashPair; + +function HashPairSort(List: TStringList; Index1, Index2: Integer): Integer; +var + h1, h2: PHashPair; +begin + h1 := PHashPair(List.Objects[Index1]); + h2 := PHashPair(List.Objects[Index2]); + if h1.DirHash < h2.DirHash then + Result := -1 + else if h1.DirHash > h2.DirHash then + Result := 1 + else if h1.FileHash < h2.FileHash then + Result := -1 + else if h1.FileHash > h2.FileHash then + Result := 1 + else + Result := 0; +end; + +function AlphabeticalSort(List: TStringList; Index1, Index2: Integer): Integer; +begin + Result := CompareStr(List[Index1], List[Index2]); +end; + +procedure TwbBSArchive.CreateArchive(const aFileName: string; aType: TBSArchiveType; + aFilesList: TStringList = nil); +var + HashPairs: array of THashPair; + h: PHashPair; + hdir: UInt64; + s, fdir, fname, fext, name: string; + i, len, folderidx, fileidx: Integer; + Buffer: TBytes; + ddsinfo: TDDSInfo; +begin + if stReading in fStates then + Close; + + if stWriting in fStates then + raise Exception.Create('Archive is already being created'); + + case aType of + baTES3: begin + fMagic := MAGIC_TES3; + end; + baTES4: begin + fVersion := HEADER_VERSION_TES4; + fMagic := MAGIC_BSA; + fHeaderTES4.Flags := ARCHIVE_PATHNAMES or ARCHIVE_FILENAMES or ARCHIVE_EMBEDNAME or ARCHIVE_XMEM or ARCHIVE_UNKNOWN10; + fHeaderTES4.FileFlags := 0; + fHeaderTES4.FoldersOffset := SizeOf(fMagic) + SizeOf(fVersion) + SizeOf(fHeaderTES4); + fCompressionType := ctZlib; + end; + baFO3: begin + fVersion := HEADER_VERSION_FO3; + fMagic := MAGIC_BSA; + fHeaderTES4.Flags := ARCHIVE_PATHNAMES or ARCHIVE_FILENAMES; + fHeaderTES4.FileFlags := 0; + fHeaderTES4.FoldersOffset := SizeOf(fMagic) + SizeOf(fVersion) + SizeOf(fHeaderTES4); + fCompressionType := ctZlib; + end; + baSSE: begin + fVersion := HEADER_VERSION_SSE; + fMagic := MAGIC_BSA; + fHeaderTES4.Flags := ARCHIVE_PATHNAMES or ARCHIVE_FILENAMES; + fHeaderTES4.FileFlags := 0; + fHeaderTES4.FoldersOffset := SizeOf(fMagic) + SizeOf(fVersion) + SizeOf(fHeaderTES4); + fCompressionType := ctLZ4Frame; + end; + baFO4: begin + fMagic := MAGIC_BTDX; + fHeaderFO4.Magic := MAGIC_GNRL; + fVersion := HEADER_VERSION_FO4v1; + fCompressionType := ctZlib; + end; + baFO4dds: begin + fMagic := MAGIC_BTDX; + fHeaderFO4.Magic := MAGIC_DX10; + fVersion := HEADER_VERSION_FO4v1; + fCompressionType := ctZlib; + end; + baSF: begin + fMagic := MAGIC_BTDX; + fHeaderFO4.Magic := MAGIC_GNRL; + fVersion := HEADER_VERSION_SF2; + fCompressionType := ctZlib; + end; + baSFdds: begin + fMagic := MAGIC_BTDX; + fHeaderFO4.Magic := MAGIC_DX10; + fVersion := HEADER_VERSION_SF3; + fCompressionType := ctLZ4Block; + end; + else + raise Exception.Create('Unsupported archive type'); + end; + + fType := aType; + + if fType in [baTES3] then begin + if not Assigned(aFilesList) or (aFilesList.Count = 0) then + raise Exception.Create('Archive requires predefined files list'); + + // sort files by hashes + SetLength(HashPairs, aFilesList.Count); + for i := 0 to Pred(aFilesList.Count) do begin + h := @HashPairs[i]; + h.FileHash := CreateHashTES3(aFilesList[i]); + aFilesList.Objects[i] := Pointer(h); + end; + aFilesList.CustomSort(HashPairSort); + + // create file records and calculate total names length + SetLength(fFilesTES3, aFilesList.Count); + len := 0; + for i := 0 to Pred(aFilesList.Count) do begin + fFilesTES3[i].Hash := PHashPair(aFilesList.Objects[i]).FileHash; + fFilesTES3[i].Name := LowerCase(aFilesList[i]); + Inc(len, Length(fFilesTES3[i].Name) + 1); // include terminator + end; + + // offset to hash table + fDataOffset := SizeOf(fMagic) + SizeOf(fHeaderTES3) + + 8 * Length(fFilesTES3) + // File sizes/offsets + 4 * Length(fFilesTES3) + // Archive directory/name offsets + len; // Filename records + + // stored as minus 12 (for header size) + fHeaderTES3.HashOffset := fDataOffset - 12; + fHeaderTES3.FileCount := aFilesList.Count; + + // offset to files data + fDataOffset := fDataOffset + 8 * Length(fFilesTES3); // Hash table + + // files are stored alphabetically in the data section in vanilla archives + // not really needed but whatever + aFilesList.CustomSort(AlphabeticalSort); + end + + else if fType in [baTES4, baFO3, baSSE] then begin + if not Assigned(aFilesList) or (aFilesList.Count = 0) then + raise Exception.Create('Archive requires predefined files list'); + + fHeaderTES4.FolderNamesLength := 0; + fHeaderTES4.FileNamesLength := 0; + + // dirs and files must be sorted by their hashes + SetLength(HashPairs, aFilesList.Count); + for i := 0 to Pred(aFilesList.Count) do begin + h := @HashPairs[i]; + if SplitDirName(aFilesList[i], fdir, fname) = 0 then + raise Exception.Create('File is missing the folder part: ' + aFilesList[i]); + + // calculate hashes + h.DirHash := CreateHashTES4(fdir, ''); + SplitNameExt(fname, s, fext); + h.FileHash := CreateHashTES4(s, fext); + h.pc := TPackingCompression(aFilesList.Objects[i]); + aFilesList.Objects[i] := Pointer(h); + + fext := LowerCase(fext); + with fHeaderTES4 do begin + // determine file flags + if fdir.StartsWith('meshes\', True) then FileFlags := FileFlags or FILE_NIF else + if fdir.StartsWith('textures\', True) then FileFlags := FileFlags or FILE_DDS else + if fdir.StartsWith('sound\', True) then FileFlags := FileFlags or FILE_WAV or FILE_MP3 else + if fext = '.nif' then FileFlags := FileFlags or FILE_NIF else + if fext = '.lod' then FileFlags := FileFlags or FILE_NIF else + if fext = '.bto' then FileFlags := FileFlags or FILE_NIF else + if fext = '.btr' then FileFlags := FileFlags or FILE_NIF else + if fext = '.btt' then FileFlags := FileFlags or FILE_NIF else + if fext = '.dtl' then FileFlags := FileFlags or FILE_NIF else + if fext = '.kf' then FileFlags := FileFlags or FILE_NIF else + if fext = '.kfm' then FileFlags := FileFlags or FILE_NIF else + if fext = '.hkx' then FileFlags := FileFlags or FILE_NIF else + if fext = '.dds' then FileFlags := FileFlags or FILE_DDS else + if fext = '.xml' then FileFlags := FileFlags or FILE_XML or FILE_MISC else + if fext = '.wav' then FileFlags := FileFlags or FILE_WAV else + if fext = '.fuz' then FileFlags := FileFlags or FILE_WAV else + if fext = '.lip' then FileFlags := FileFlags or FILE_MP3 else + if fext = '.mp3' then FileFlags := FileFlags or FILE_MP3 else + if fext = '.ogg' then FileFlags := FileFlags or FILE_MP3 else + if fext = '.txt' then FileFlags := FileFlags or FILE_TXT else + if fext = '.htm' then FileFlags := FileFlags or FILE_TXT else + if fext = '.bat' then FileFlags := FileFlags or FILE_TXT else + if fext = '.scc' then FileFlags := FileFlags or FILE_TXT else + if fext = '.spt' then FileFlags := FileFlags or FILE_SPT else + if fext = '.fnt' then FileFlags := FileFlags or FILE_FNT else + if fext = '.tex' then FileFlags := FileFlags or FILE_FNT else + FileFlags := FileFlags or FILE_MISC; + + // determine archive flags + + // packed scripts can't be added to objects in the SSE CK if the archive was packed + // without the "RetainNames" flag (the scripts aren't shown in the script adding window) + if fext = '.pex' then ArchiveFlags := ArchiveFlags or ARCHIVE_RETAINNAME; + end; + end; + + // sort by hashes + aFilesList.CustomSort(HashPairSort); + + // create folder and file records + fHeaderTES4.FileCount := 0; + hdir := 0; + folderidx := -1; + fileidx := 0; + for i := 0 to Pred(aFilesList.Count) do begin + SplitDirName(aFilesList[i], fdir, fname); + h := Pointer(aFilesList.Objects[i]); + // new folder + if h.DirHash <> hdir then begin + Inc(folderidx); + fileidx := 0; + hdir := h.DirHash; + SetLength(fFoldersTES4, folderidx + 1); + fFoldersTES4[folderidx].Hash := h.DirHash; + fFoldersTES4[folderidx].Name := LowerCase(fdir); + // calc folder names length + Inc(fHeaderTES4.FolderNamesLength, Length(fdir) + 1); // + terminator only, length prefix is not counted + end; + SetLength(fFoldersTES4[folderidx].Files, fileidx + 1); + fFoldersTES4[folderidx].Files[fileidx].Hash := h.FileHash; + fFoldersTES4[folderidx].Files[fileidx].Name := LowerCase(fname); + fFoldersTES4[folderidx].Files[fileidx].PackingCompression := h.pc; + Inc(fileidx); + Inc(fFoldersTES4[folderidx].FileCount); + Inc(fHeaderTES4.FileCount); + // calculate file names length + Inc(fHeaderTES4.FileNamesLength, Length(fname) + 1); // + terminator + end; + fHeaderTES4.FolderCount := Length(fFoldersTES4); + + // calculate folders offsets + // at the end fDataOffset will hold the total size of header, folder and file records + // in other words the start of files data + fDataOffset := SizeOf(fMagic) + SizeOf(fVersion) + SizeOf(fHeaderTES4) + 16 * Length(fFoldersTES4); + // SSE folder record is 8 bytes larger + if fType = baSSE then + Inc(fDataOffset, 8 * Length(fFoldersTES4)); + // Offsets are stored including this value + Inc(fDataOffset, fHeaderTES4.FileNamesLength); + for i := Low(fFoldersTES4) to High(fFoldersTES4) do begin + fFoldersTES4[i].Offset := fDataOffset; + // add folder name length + Inc(fDataOffset, Length(fFoldersTES4[i].Name) + 2); // + length prefix + terminator + // add file records length + Inc(fDataOffset, 16 * Length(fFoldersTES4[i].Files)); + end; + + // final flags detection + + // misc file flag is not in Skyrim SE + if fType = baSSE then + fHeaderTES4.FileFlags := fHeaderTES4.FileFlags and not FILE_MISC; + + // embedded names in texture only archives + // except Skyrim SE: crashing engine bug if texture is uncompressed and file name is embedded + if (fHeaderTES4.FileFlags = FILE_DDS) and (fType <> baSSE) then + fHeaderTES4.Flags := fHeaderTES4.Flags or ARCHIVE_EMBEDNAME; + + // startupstr flag in archives with meshes + if fHeaderTES4.FileFlags and FILE_NIF <> 0 then + fHeaderTES4.Flags := fHeaderTES4.Flags or ARCHIVE_STARTUPSTR; + + // retainname flag in archives with sounds + if fHeaderTES4.FileFlags and FILE_WAV <> 0 then + fHeaderTES4.Flags := fHeaderTES4.Flags or ARCHIVE_RETAINNAME; + + // txt, xml and fnt file flags are exclusive for Oblivion + if fType <> baTES4 then + fHeaderTES4.FileFlags := fHeaderTES4.FileFlags and not (FILE_XML or FILE_TXT or FILE_FNT); + + // set compression flag if needed + if fCompress then + fHeaderTES4.Flags := fHeaderTES4.Flags or ARCHIVE_COMPRESS; + end + + else if fType in [baFO4, baFO4dds, baSF, baSFdds] then begin + if not Assigned(aFilesList) or (aFilesList.Count = 0) then + raise Exception.Create('Archive requires predefined files list'); + + fHeaderFO4.FileCount := aFilesList.Count; + SetLength(fFilesFO4, aFilesList.Count); + for i := 0 to Pred(aFilesList.Count) do begin + if SplitDirName(aFilesList[i], fdir, fname) = 0 then + raise Exception.Create('File is missing the folder part: ' + aFileName); + + SplitNameExt(fname, name, fext, True); + // archive2.exe uses / + fFilesFO4[i].Name := StringReplace(aFilesList[i], '\', '/', [rfReplaceAll]); + fFilesFO4[i].DirHash := CreateHashFO4(fdir); + fFilesFO4[i].NameHash := CreateHashFO4(name); + fFilesFO4[i].Ext := String2Magic(LowerCase(fext)); + fFilesFO4[i].Unknown := iFileFO4Unknown; + fFilesFO4[i].PackingCompression := TPackingCompression(aFilesList.Objects[i]); + end; + + fDataOffset := SizeOf(fMagic) + SizeOf(fVersion) + SizeOf(fHeaderFO4); + if fType = baSF then + Inc(fDataOffset, SizeOf(fHeaderSFv2)) + else if fType = baSFdds then + Inc(fDataOffset, SizeOf(fHeaderSFv3)); + + // file records have fixed length in general archive + if fType in [baFO4, baSF] then + fDataOffset := fDataOffset + 36 * Length(fFilesFO4) + + // variable file record length depending on DDS chunks number + else if fType in [baFO4dds, baSFdds] then begin + if not Assigned(fDDSInfoProc) then + raise Exception.Create('DDS archive requires DDS file information callback'); + + for i := 0 to Pred(aFilesList.Count) do begin + fDDSInfoProc(Self, aFilesList[i], ddsinfo, Self.fDDSInfoProcContext); + fDataOffset := fDataOffset + 24 {size of file record} + 24 {size of each texchunk} * GetDDSMipChunkNum(ddsinfo); + end; + end; + + end; + + fStream := TwbWriteCachedFileStream.Create(aFileName, fmCreate); + fFileName := aFileName; + Include(fStates, stWriting); + + // reserve space for the header + SetLength(Buffer, fDataOffset); + fStream.Write(Buffer[0], Length(Buffer)); +end; + +procedure TwbBSArchive.Save; +var + i, j: integer; +begin + if not (stWriting in fStates) then + raise Exception.Create('Archive is not in writing mode'); + + case fType of + baTES3: begin + for i := Low(fFilesTES3) to High(fFilesTES3) do + if fFilesTES3[i].Offset = 0 then + raise Exception.Create('Archived file has no data: ' + fFilesTES3[i].Name); + + // write header + fStream.Position := 0; + // magic, header record + fStream.Write(fMagic, SizeOf(fMagic)); + fStream.Write(fHeaderTES3, SizeOf(fHeaderTES3)); + // file sizes/offsets + for i := Low(fFilesTES3) to High(fFilesTES3) do begin + fStream.WriteCardinal(fFilesTES3[i].Size); + fStream.WriteCardinal(fFilesTES3[i].Offset - fDataOffset); // offsets are relative + end; + // Archive directory/name offsets + j := 0; + for i := Low(fFilesTES3) to High(fFilesTES3) do begin + fStream.WriteCardinal(j); + Inc(j, Length(fFilesTES3[i].Name) + 1); // including terminator + end; + // Filename records + for i := Low(fFilesTES3) to High(fFilesTES3) do + fStream.WriteStringTerm(fFilesTES3[i].Name); + // Hash table + for i := Low(fFilesTES3) to High(fFilesTES3) do begin + fStream.WriteCardinal(fFilesTES3[i].Hash shr 32); + fStream.WriteCardinal(fFilesTES3[i].Hash and $FFFFFFFF); + end; + end; + + baTES4, baFO3, baSSE: begin + // check that all files from files table have saved data + for i := Low(fFoldersTES4) to High(fFoldersTES4) do + for j := Low(fFoldersTES4[i].Files) to High(fFoldersTES4[i].Files) do + if fFoldersTES4[i].Files[j].Offset = 0 then + raise Exception.Create('Archived file has no data: ' + fFoldersTES4[i].Name + '\' + fFoldersTES4[i].Files[j].Name); + + // write header + fStream.Position := 0; + // magic, version, header record + fStream.Write(fMagic, SizeOf(fMagic)); + fStream.Write(fVersion, SizeOf(fVersion)); + fStream.Write(fHeaderTES4, SizeOf(fHeaderTES4)); + // folder records + for i := Low(fFoldersTES4) to High(fFoldersTES4) do begin + fStream.WriteUInt64(fFoldersTES4[i].Hash); + fStream.WriteCardinal(fFoldersTES4[i].FileCount); + if fType = baSSE then begin + fStream.WriteCardinal(fFoldersTES4[i].Unk32); + fStream.WriteInt64(fFoldersTES4[i].Offset); + end else + fStream.WriteCardinal(fFoldersTES4[i].Offset); + end; + // file records + for i := Low(fFoldersTES4) to High(fFoldersTES4) do begin + fStream.WriteStringLen(fFoldersTES4[i].Name); + for j := Low(fFoldersTES4[i].Files) to High(fFoldersTES4[i].Files) do begin + fStream.WriteUInt64(fFoldersTES4[i].Files[j].Hash); + fStream.WriteCardinal(fFoldersTES4[i].Files[j].Size); + fStream.WriteCardinal(fFoldersTES4[i].Files[j].Offset); + end; + end; + // file names + for i := Low(fFoldersTES4) to High(fFoldersTES4) do + for j := Low(fFoldersTES4[i].Files) to High(fFoldersTES4[i].Files) do + fStream.WriteStringTerm(fFoldersTES4[i].Files[j].Name); + end; + + baFO4, baSF: begin + for i := Low(fFilesFO4) to High(fFilesFO4) do + if fFilesFO4[i].Offset = 0 then + raise Exception.Create('Archived file has no data: ' + fFilesFO4[i].Name); + + // file names table + fHeaderFO4.FileTableOffset := fStream.Position; + for i := Low(fFilesFO4) to High(fFilesFO4) do + fStream.WriteStringLen16(fFilesFO4[i].Name); + // write header + fStream.Position := 0; + // magic, version, header record + fStream.Write(fMagic, SizeOf(fMagic)); + fStream.Write(fVersion, SizeOf(fVersion)); + fStream.Write(fHeaderFO4, SizeOf(fHeaderFO4)); + // additional SF header + if fType = baSF then begin + fHeaderSFv2.Unknown1 := 1; + fHeaderSFv2.Unknown2 := 0; + fStream.Write(fHeaderSFv2, SizeOf(fHeaderSFv2)); + end; + // file records + for i := Low(fFilesFO4) to High(fFilesFO4) do begin + fStream.WriteCardinal(fFilesFO4[i].NameHash); + fStream.WriteBuffer(fFilesFO4[i].Ext[0], SizeOf(fFilesFO4[i].Ext)); + fStream.WriteCardinal(fFilesFO4[i].DirHash); + fStream.WriteCardinal(fFilesFO4[i].Unknown); + fStream.WriteInt64(fFilesFO4[i].Offset); + fStream.WriteCardinal(fFilesFO4[i].PackedSize); + fStream.WriteCardinal(fFilesFO4[i].Size); + fStream.WriteCardinal(iFileFO4Tail); + end; + end; + + baFO4dds, baSFdds: begin + for i := Low(fFilesFO4) to High(fFilesFO4) do + if Length(fFilesFO4[i].TexChunks) = 0 then + raise Exception.Create('Archived file has no data: ' + fFilesFO4[i].Name); + + // file names table + fHeaderFO4.FileTableOffset := fStream.Position; + for i := Low(fFilesFO4) to High(fFilesFO4) do + fStream.WriteStringLen16(fFilesFO4[i].Name); + // write header + fStream.Position := 0; + // magic, version, header record + fStream.Write(fMagic, SizeOf(fMagic)); + fStream.Write(fVersion, SizeOf(fVersion)); + fStream.Write(fHeaderFO4, SizeOf(fHeaderFO4)); + // additional SF header + if fType = baSFdds then begin + fHeaderSFv3.Unknown1 := 1; + fHeaderSFv3.Unknown2 := 0; + fHeaderSFv3.CompressionMethod := 3; // lz4 + fStream.Write(fHeaderSFv3, SizeOf(fHeaderSFv3)); + end; + // file records + for i := Low(fFilesFO4) to High(fFilesFO4) do begin + fStream.WriteCardinal(fFilesFO4[i].NameHash); + fStream.WriteBuffer(fFilesFO4[i].Ext[0], SizeOf(fFilesFO4[i].Ext)); + fStream.WriteCardinal(fFilesFO4[i].DirHash); + fStream.WriteByte(fFilesFO4[i].UnknownTex); + fStream.WriteByte(Length(fFilesFO4[i].TexChunks)); + fStream.WriteWord(24); // fixed chunk header size + fStream.WriteWord(fFilesFO4[i].Height); + fStream.WriteWord(fFilesFO4[i].Width); + fStream.WriteByte(fFilesFO4[i].NumMips); + fStream.WriteByte(fFilesFO4[i].DXGIFormat); + fStream.WriteWord(fFilesFO4[i].CubeMaps); + for j := Low(fFilesFO4[i].TexChunks) to High(fFilesFO4[i].TexChunks) do + with fFilesFO4[i].TexChunks[j] do begin + fStream.WriteUInt64(Offset); + fStream.WriteCardinal(PackedSize); + fStream.WriteCardinal(Size); + fStream.WriteWord(StartMip); + fStream.WriteWord(EndMip); + fStream.WriteCardinal(iFileFO4Tail); + end; + end; + end; + + end; + + FreeAndNil(fStream); + Exclude(fStates, stWriting); + Close; +end; + +function TwbBSArchive.GetCreatedArchiveSize: Int64; +begin + if (stWriting in fStates) and Assigned(fStream) then + Result := fStream.Position + else + Result := 0; +end; + +procedure TwbBSArchive.AddFileDisk(const aFilePath, aSourcePath: string); +var + fname: string; + i: integer; + buffer: PByte; + stream: TFileStream; +begin + if not (stWriting in fStates) then + raise Exception.Create('Archive is not in writing mode'); + + stream := TFileStream.Create(aSourcePath, fmOpenRead + fmShareDenyNone); + try + // Modified: Make sure memory is zeroed when allocated + buffer := AllocMem(stream.Size); + try + stream.Read(buffer^, stream.Size); + AddFileData(aFilePath, stream.Size, buffer); + finally + if Assigned(buffer) then + FreeMem(buffer); + end; + finally + stream.Free; + end; +end; + +procedure TwbBSArchive.AddFileDiskRoot(const aRootDir, aSourcePath: string); +var + fname: string; + i: integer; +begin + i := Length(aRootDir); + if (i > 1) and (aRootDir[Length(aRootDir)] <> '\') then + Inc(i); + + fname := Copy(aSourcePath, i + 1, Length(aSourcePath)); + + AddFileDisk(fname, aSourcePath); +end; + +procedure TwbBSArchive.SyncBeginWrite; +begin + if fMultiThreaded then + Sync.BeginWrite; +end; + +procedure TwbBSArchive.SyncEndWrite; +begin + if fMultiThreaded then + Sync.EndWrite; +end; + +procedure TwbBSArchive.CompressStream(aSrc, aDst: TStream); +begin + case fCompressionType of + ctZlib: ZCompressStream(aSrc, aDst); + ctLZ4Frame: lz4CompressStream(aSrc, aDst); + ctLZ4Block: lz4BlockCompressStream(aSrc, aDst); + else + raise Exception.Create('Archive compression type is undefined'); + end; +end; + +procedure TwbBSArchive.DecompressBuf(aSrc: Pointer; aSrcSize: Integer; aDst: Pointer; aDstSize: Integer); +begin + case fCompressionType of + ctZlib: try + DecompressToUserBuf(aSrc, aSrcSize, aDst, aDstSize); + except + // ignore zlib's Buffer error since it happens in vanilla "Fallout - Misc.bsa" + // Bethesda probably used old buggy zlib version when packing it + on E: Exception do if E.Message <> 'Buffer error' then raise; + end; + ctLZ4Frame: lz4DecompressToUserBuf(aSrc, aSrcSize, aDst, aDstSize); + ctLZ4Block: lz4BlockDecompressToUserBuf(aSrc, aSrcSize, aDst, aDstSize); + end; +end; + +procedure TwbBSArchive.PackData(aFileRecord: Pointer; const aFileName: string; + aDataHash: TPackedDataHash; aData: PByte; aSize: Integer; + aCompress: Boolean; aDoCompress: Boolean = False); +var + zStream: TBytesStream; + msData: TPreallocatedMemoryStream; + DataSize: Integer; + Position: Int64; +begin + DataSize := aSize; + zStream := nil; + msData := nil; + + if FindPackedData(DataSize, aDataHash, aFileRecord) then + Exit; + + try + if aCompress then begin + // compressing in parallel when multithreaded + SyncEndWrite; + try + zStream := TBytesStream.Create; + msData := TPreallocatedMemoryStream.Create(aData, aSize); + CompressStream(msData, zStream); + + // leave as compressed if compression reduced the size + // by at least let's say 32 bytes + // or data is forced to be compressed + if aDoCompress or (zStream.Size + 32 < aSize) then begin + aData := @zStream.Bytes[0]; + aSize := zStream.Size; + end else + aCompress := False; + finally + SyncBeginWrite; + end; + end; + + // let's try to find existing data again if multithreaded + // maybe some other thread has written the same data while we've been busy compressing + // zStream exists if we've really spent time compressing + if fMultiThreaded and Assigned(zStream) then + if FindPackedData(DataSize, aDataHash, aFileRecord) then + Exit; + + Position := fStream.Position; + + // embedded name for Fallout 3/NV/Skyrim/Skyrim SE + if (fType in [baFO3, baSSE]) and (fHeaderTES4.Flags and ARCHIVE_EMBEDNAME <> 0) then + fStream.WriteStringLen(aFileName, False); + + // if compressed then write uncompressed size first for Oblivion/Fallout 3/NV/Skyrim/Skyrim SE + if (fType in [baTES4, baFO3, baSSE]) and aCompress then + fStream.WriteCardinal(DataSize); + + fStream.Write(aData^, aSize); + + // updating file record + case fType of + baTES3: with PwbBSFileTES3(aFileRecord)^ do begin + Offset := Position; + Size := DataSize; + end; + baTES4, baFO3, baSSE: with PwbBSFileTES4(aFileRecord)^ do begin + Offset := Position; + Size := fStream.Position - Offset; + // compress flag in Size inverts compression status from the header + // set it if archive's compression doesn't match file's compression + if Self.fCompress xor aCompress then + Size := Size or FILE_SIZE_COMPRESS; + end; + baFO4, baSF: with PwbBSFileFO4(aFileRecord)^ do begin + Offset := Position; + Size := DataSize; + if aCompress then + PackedSize := aSize; + end; + baFO4dds, baSFdds: with PwbBSTexChunkRec(aFileRecord)^ do begin + Offset := Position; + Size := DataSize; + if aCompress then + PackedSize := aSize; + end; + end; + + AddPackedData(DataSize, aDataHash, aFileRecord); + + finally + if Assigned(zStream) then + zStream.Free; + if Assigned(msData) then + msData.Free; + end; +end; + +procedure TwbBSArchive.AddFileData(const aFileName: string; const aSize: Cardinal; const aData: PByte); +var + i, j, Off, MipSize, BitsPerPixel: integer; + DataHash: TPackedDataHash; + DDSHeader: PDDSHeader; + DDSHeaderDX10: PDDSHeaderDX10; + DDSInfo: TDDSInfo; +begin + if not (stWriting in fStates) then + raise Exception.Create('Archive is not in writing mode'); + + // dds mipmaps have their own partial hash calculation down below + if fShareData and not (fType in [baFO4dds, baSFdds]) then + DataHash := CalcDataHash(@aData[0], aSize); + + SyncBeginWrite; + try + case fType of + baTES3: begin + if not FindFileRecordTES3(aFileName, i) then + raise Exception.Create('File not found in files table: ' + aFileName); + + PackData(@fFilesTES3[i], aFileName, DataHash, @aData[0], aSize, False); + end; + + baTES4, baFO3, baSSE: begin + if not FindFileRecordTES4(aFileName, i, j) then + raise Exception.Create('File not found in files table: ' + aFileName); + + PackData( + @fFoldersTES4[i].Files[j], fFoldersTES4[i].Name + '\' + fFoldersTES4[i].Files[j].Name, + DataHash, @aData[0], aSize, fFoldersTES4[i].Files[j].Compress(Self) + ); + end; + + baFO4, baSF: begin + if not FindFileRecordFO4(aFileName, i) then + raise Exception.Create('File not found in files table: ' + aFileName); + + fFilesFO4[i].Offset := fStream.Position; + fFilesFO4[i].Size := aSize; + + PackData( + @fFilesFO4[i], fFilesFO4[i].Name, + DataHash, @aData[0], aSize, fFilesFO4[i].Compress(Self) + ); + end; + + baFO4dds, baSFdds: begin + if not FindFileRecordFO4(aFileName, i) then + raise Exception.Create('File not found in files table: ' + aFileName); + + fFilesFO4[i].UnknownTex := 0; + // DDS file parameters + DDSHeader := @aData[0]; + Off := SizeOf(DDSHeader^); // offset to image data + fFilesFO4[i].Width := DDSHeader.dwWidth; + fFilesFO4[i].Height := DDSHeader.dwHeight; + fFilesFO4[i].NumMips := DDSHeader.dwMipMapCount; + // no mipmaps is equal to a single one + if fFilesFO4[i].NumMips = 0 then + fFilesFO4[i].NumMips := 1; + + // DXGI detection + if DDSHeader.ddspf.dwFourCC = MAGIC_DXT1 then + fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_BC1_UNORM) + else if DDSHeader.ddspf.dwFourCC = MAGIC_DXT3 then + fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_BC2_UNORM) + else if DDSHeader.ddspf.dwFourCC = MAGIC_DXT5 then + fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_BC3_UNORM) + else if DDSHeader.ddspf.dwFourCC = MAGIC_ATI1 then + fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_BC4_UNORM) + else if DDSHeader.ddspf.dwFourCC = MAGIC_BC4U then + fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_BC4_UNORM) + else if DDSHeader.ddspf.dwFourCC = MAGIC_BC4S then + fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_BC4_SNORM) + else if DDSHeader.ddspf.dwFourCC = MAGIC_ATI2 then + fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_BC5_UNORM) + else if DDSHeader.ddspf.dwFourCC = MAGIC_BC5U then + fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_BC5_UNORM) + else if DDSHeader.ddspf.dwFourCC = MAGIC_BC5S then + fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_BC5_SNORM) + else if DDSHeader.ddspf.dwFourCC = MAGIC_DX10 then begin + DDSHeaderDX10 := @aData[Off]; + Off := Off + SizeOf(DDSHeaderDX10^); + fFilesFO4[i].DXGIFormat := Byte(DDSHeaderDX10.dxgiFormat); + end + else begin + if DDSHeader.ddspf.dwRGBBitCount = 32 then + if DDSHeader.ddspf.dwFlags and DDPF_ALPHAPIXELS = 0 then + fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_B8G8R8X8_UNORM) + else if DDSHeader.ddspf.dwRBitMask = $000000FF then + fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_R8G8B8A8_UNORM) + else + fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_B8G8R8A8_UNORM) + + else if DDSHeader.ddspf.dwRGBBitCount = 16 then + if (DDSHeader.ddspf.dwRBitMask = $F800) and (DDSHeader.ddspf.dwGBitMask = $07E0) and + (DDSHeader.ddspf.dwBBitMask = $001F) and (DDSHeader.ddspf.dwABitMask = $0000) then + fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_B5G6R5_UNORM) + else if (DDSHeader.ddspf.dwRBitMask = $7C00) and (DDSHeader.ddspf.dwGBitMask = $03E0) and + (DDSHeader.ddspf.dwBBitMask = $001F) and (DDSHeader.ddspf.dwABitMask = $8000) then + fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_B5G5R5A1_UNORM) + else + fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_R8G8_UNORM) + + else if DDSHeader.ddspf.dwRGBBitCount = 8 then + if DDSHeader.ddspf.dwFlags and DDPF_ALPHA <> 0 then + fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_A8_UNORM) + else + fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_R8_UNORM) + + else + raise Exception.Create('Unsupported uncompressed DDS format'); + end; + + // MipMap size detection + case TDXGI(fFilesFO4[i].DXGIFormat) of + DXGI_FORMAT_BC1_UNORM, DXGI_FORMAT_BC1_UNORM_SRGB, + DXGI_FORMAT_BC4_UNORM, DXGI_FORMAT_BC4_SNORM: + BitsPerPixel := 4; + DXGI_FORMAT_BC2_UNORM, DXGI_FORMAT_BC2_UNORM_SRGB, + DXGI_FORMAT_BC3_UNORM, DXGI_FORMAT_BC3_UNORM_SRGB, + DXGI_FORMAT_BC5_UNORM, DXGI_FORMAT_BC5_SNORM, + DXGI_FORMAT_BC6H_SF16, DXGI_FORMAT_BC6H_UF16, + DXGI_FORMAT_BC7_UNORM, DXGI_FORMAT_BC7_UNORM_SRGB, + DXGI_FORMAT_A8_UNORM, + DXGI_FORMAT_R8_SINT, DXGI_FORMAT_R8_SNORM, + DXGI_FORMAT_R8_UINT, DXGI_FORMAT_R8_UNORM: + BitsPerPixel := 8; + DXGI_FORMAT_B5G6R5_UNORM, DXGI_FORMAT_B5G5R5A1_UNORM, + DXGI_FORMAT_R8G8_SINT, DXGI_FORMAT_R8G8_UINT, DXGI_FORMAT_R8G8_UNORM: + BitsPerPixel := 16; + DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_B8G8R8A8_UNORM_SRGB, + DXGI_FORMAT_B8G8R8X8_UNORM, DXGI_FORMAT_B8G8R8X8_UNORM_SRGB, + DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_R8G8B8A8_SINT, + DXGI_FORMAT_R8G8B8A8_UINT, DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: + BitsPerPixel := 32; + else + raise Exception.Create('Unsupported DDS format'); + end; + MipSize := (fFilesFO4[i].Width * fFilesFO4[i].Height * BitsPerPixel) shr 3; + + // cubemaps detection + fFilesFO4[i].CubeMaps := $800; + if DDSHeader.dwCaps2 and DDSCAPS2_CUBEMAP <> 0 then + fFilesFO4[i].CubeMaps := fFilesFO4[i].CubeMaps or 1; + + // number of chunks to store in file record + DDSInfo.Width := fFilesFO4[i].Width; + DDSInfo.Height := fFilesFO4[i].Height; + DDSInfo.MipMaps := fFilesFO4[i].NumMips; + SetLength(fFilesFO4[i].TexChunks, GetDDSMipChunkNum(DDSInfo)); + + // storing chunks + for j := Low(fFilesFO4[i].TexChunks) to High(fFilesFO4[i].TexChunks) do begin + fFilesFO4[i].TexChunks[j].StartMip := j; + if j < High(fFilesFO4[i].TexChunks) then + fFilesFO4[i].TexChunks[j].EndMip := j + else begin + // last chunk stores all remaining mipmaps + fFilesFO4[i].TexChunks[j].EndMip := Pred(fFilesFO4[i].NumMips); + MipSize := Integer(aSize) - Off; + end; + + DataHash := CalcDataHash(@aData[Off], MipSize); + + PackData( + @fFilesFO4[i].TexChunks[j], fFilesFO4[i].Name, + DataHash, @aData[Off], MipSize, + fFilesFO4[i].Compress(Self), fFilesFO4[i].Compress(Self) // force compression + ); + Inc(Off, MipSize); + MipSize := MipSize div 4; + end; + end; + + end; + finally + SyncEndWrite; + end; +end; + +function TwbBSArchive.ExtractFileData(aFileRecord: Pointer): TwbBSResultBuffer; +var + FileTES3: PwbBSFileTES3; + FileTES4: PwbBSFileTES4; + FileFO4: PwbBSFileFO4; + DDSHeader: PDDSHeader; + DDSHeaderDX10: PDDSHeaderDX10; + i, size, TexSize: integer; + bCompressed: Boolean; + Buffer: TBytes; +begin + if not (stReading in fStates) then + raise Exception.Create('Archive is not loaded'); + + if aFileRecord = nil then + Exit; + + SyncBeginWrite; + try + case fType of + baTES3: begin + FileTES3 := aFileRecord; + fStream.Position := fDataOffset + FileTES3.Offset; + Result.size := FileTES3.Size; + // Modified: Make sure memory is zeroed when allocated + Result.data := AllocMem(FileTES3.Size); + end; + + baTES4, baFO3, baSSE: begin + FileTES4 := aFileRecord; + fStream.Position := FileTES4.Offset; + size := FileTES4.Size; + bCompressed := size and FILE_SIZE_COMPRESS <> 0; + if bCompressed then + size := size and not FILE_SIZE_COMPRESS; + if fHeaderTES4.Flags and ARCHIVE_COMPRESS <> 0 then + bCompressed := not bCompressed; + + // skip embedded file name + length prefix + if (fType in [baFO3, baSSE]) and (fHeaderTES4.Flags and ARCHIVE_EMBEDNAME <> 0) then + size := size - (Length(fStream.ReadStringLen(False)) + 1); + + if bCompressed then begin + // reading uncompressed size + Result.size := fStream.ReadCardinal; + // Modified: Make sure memory is zeroed when allocated + Result.data := AllocMem(Result.size); + dec(size, SizeOf(Cardinal)); + if (Result.size > 0) and (size > 0) then begin + SetLength(Buffer, size); + fStream.ReadBuffer(Buffer[0], Length(Buffer)); + SyncEndWrite; + try + DecompressBuf(@Buffer[0], Length(Buffer), @Result.data[0], Result.size); + finally + SyncBeginWrite; + end; + end; + end + else begin + Result.size := size; + // Modified: Make sure memory is zeroed when allocated + Result.data := AllocMem(Result.size); + if size > 0 then + fStream.ReadBuffer(Result.data[0], Result.size); + end; + end; + + baFO4, baSF: begin + FileFO4 := aFileRecord; + fStream.Position := FileFO4.Offset; + if FileFO4.PackedSize <> 0 then begin + SetLength(Buffer, FileFO4.PackedSize); + fStream.ReadBuffer(Buffer[0], Length(Buffer)); + Result.size := FileFO4.Size; + // Modified: Make sure memory is zeroed when allocated + Result.data := AllocMem(Result.size); + SyncEndWrite; + try + DecompressBuf(@Buffer[0], Length(Buffer), @Result.data[0], Result.size); + finally + SyncBeginWrite; + end; + end + else begin + Result.size := FileFO4.Size; + // Modified: Make sure memory is zeroed when allocated + Result.data := AllocMem(Result.size); + fStream.ReadBuffer(Result.data[0], Result.size); + end; + end; + + baFO4dds, baSFdds: begin + FileFO4 := aFileRecord; + + TexSize := SizeOf(TDDSHeader); + for i := Low(FileFO4.TexChunks) to High(FileFO4.TexChunks) do + Inc(TexSize, FileFO4.TexChunks[i].Size); + Result.size := Texsize; + + // Modified: Make sure memory is zeroed when allocated + Result.data := AllocMem(Result.size); + + DDSHeader := @Result.data[0]; + DDSHeader.Magic := MAGIC_DDS; + DDSHeader.dwSize := SizeOf(TDDSHeader) - SizeOf(TMagic4); + DDSHeader.dwWidth := FileFO4.Width; + DDSHeader.dwHeight := FileFO4.Height; + DDSHeader.dwFlags := DDSD_CAPS or DDSD_PIXELFORMAT or + DDSD_WIDTH or DDSD_HEIGHT or DDSD_MIPMAPCOUNT; + DDSHeader.dwCaps := DDSCAPS_TEXTURE; + DDSHeader.dwMipMapCount := FileFO4.NumMips; + if DDSHeader.dwMipMapCount > 1 then + DDSHeader.dwCaps := DDSHeader.dwCaps or DDSCAPS_MIPMAP or DDSCAPS_COMPLEX; + DDSHeader.dwDepth := 1; + + DDSHeaderDX10 := @Result.data[SizeOf(TDDSHeader)]; + DDSHeaderDX10.resourceDimension := DDS_DIMENSION_TEXTURE2D; + DDSHeaderDX10.arraySize := 1; + + if FileFO4.CubeMaps = 2049 then begin + // Archive2.exe creates invalid textures like this + //DDSHeader.dwCaps := DDSHeader.dwCaps or DDSCAPS2_CUBEMAP or DDSCAPS_COMPLEX + // or DDSCAPS2_POSITIVEX or DDSCAPS2_NEGATIVEX + // or DDSCAPS2_POSITIVEY or DDSCAPS2_NEGATIVEY + // or DDSCAPS2_POSITIVEZ or DDSCAPS2_NEGATIVEZ; + // This is the correct way + DDSHeader.dwCaps := DDSHeader.dwCaps or DDSCAPS_COMPLEX; + DDSHeader.dwCaps2 := DDSCAPS2_CUBEMAP + or DDSCAPS2_POSITIVEX or DDSCAPS2_NEGATIVEX + or DDSCAPS2_POSITIVEY or DDSCAPS2_NEGATIVEY + or DDSCAPS2_POSITIVEZ or DDSCAPS2_NEGATIVEZ; + DDSHeaderDX10.miscFlags := DDS_RESOURCE_MISC_TEXTURECUBE; + end; + DDSHeader.ddspf.dwSize := SizeOf(DDSHeader.ddspf); + + case TDXGI(FileFO4.DXGIFormat) of + DXGI_FORMAT_BC1_UNORM: begin + DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_LINEARSIZE; + DDSHeader.ddspf.dwFlags := DDPF_FOURCC; + DDSHeader.ddspf.dwFourCC := MAGIC_DXT1; + DDSHeader.dwPitchOrLinearSize := FileFO4.Width * FileFO4.Height div 2; + end; + DXGI_FORMAT_BC2_UNORM: begin + DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_LINEARSIZE; + DDSHeader.ddspf.dwFlags := DDPF_FOURCC; + DDSHeader.ddspf.dwFourCC := MAGIC_DXT3; + DDSHeader.dwPitchOrLinearSize := FileFO4.Width * FileFO4.Height; + end; + DXGI_FORMAT_BC3_UNORM: begin + DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_LINEARSIZE; + DDSHeader.ddspf.dwFlags := DDPF_FOURCC; + DDSHeader.ddspf.dwFourCC := MAGIC_DXT5; + DDSHeader.dwPitchOrLinearSize := FileFO4.Width * FileFO4.Height; + end; + DXGI_FORMAT_BC4_SNORM: begin + DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_LINEARSIZE; + DDSHeader.ddspf.dwFlags := DDPF_FOURCC; + DDSHeader.ddspf.dwFourCC := MAGIC_BC4S; + DDSHeader.dwPitchOrLinearSize := FileFO4.Width * FileFO4.Height div 2; + end; + DXGI_FORMAT_BC4_UNORM: begin + DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_LINEARSIZE; + DDSHeader.ddspf.dwFlags := DDPF_FOURCC; + DDSHeader.ddspf.dwFourCC := MAGIC_BC4U; + DDSHeader.dwPitchOrLinearSize := FileFO4.Width * FileFO4.Height div 2; + end; + DXGI_FORMAT_BC5_SNORM: begin + DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_LINEARSIZE; + DDSHeader.ddspf.dwFlags := DDPF_FOURCC; + DDSHeader.ddspf.dwFourCC := MAGIC_BC5S; + DDSHeader.dwPitchOrLinearSize := FileFO4.Width * FileFO4.Height; + end; + DXGI_FORMAT_BC5_UNORM: begin + DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_LINEARSIZE; + DDSHeader.ddspf.dwFlags := DDPF_FOURCC; + DDSHeader.ddspf.dwFourCC := MAGIC_BC5U; + DDSHeader.dwPitchOrLinearSize := FileFO4.Width * FileFO4.Height; + end; + DXGI_FORMAT_BC1_UNORM_SRGB: begin + DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_LINEARSIZE; + DDSHeader.ddspf.dwFlags := DDPF_FOURCC; + DDSHeader.ddspf.dwFourCC := MAGIC_DX10; + DDSHeaderDX10.dxgiFormat := Integer(FileFO4.DXGIFormat); + DDSHeader.dwPitchOrLinearSize := FileFO4.Width * FileFO4.Height div 2; + end; + DXGI_FORMAT_BC2_UNORM_SRGB, DXGI_FORMAT_BC3_UNORM_SRGB, + DXGI_FORMAT_BC6H_UF16, DXGI_FORMAT_BC6H_SF16, + DXGI_FORMAT_BC7_UNORM, DXGI_FORMAT_BC7_UNORM_SRGB: begin + DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_LINEARSIZE; + DDSHeader.ddspf.dwFlags := DDPF_FOURCC; + DDSHeader.ddspf.dwFourCC := MAGIC_DX10; + DDSHeaderDX10.dxgiFormat := Integer(FileFO4.DXGIFormat); + DDSHeader.dwPitchOrLinearSize := FileFO4.Width * FileFO4.Height; + end; + DXGI_FORMAT_B8G8R8A8_UNORM_SRGB, DXGI_FORMAT_B8G8R8X8_UNORM_SRGB, + DXGI_FORMAT_R8G8B8A8_SINT, DXGI_FORMAT_R8G8B8A8_UINT, DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: begin + DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_PITCH; + DDSHeader.ddspf.dwFlags := DDPF_FOURCC; + DDSHeader.ddspf.dwFourCC := MAGIC_DX10; + DDSHeaderDX10.dxgiFormat := Integer(FileFO4.DXGIFormat); + DDSHeader.dwPitchOrLinearSize := FileFO4.Width * 4; + end; + DXGI_FORMAT_R8G8_SINT, DXGI_FORMAT_R8G8_UINT: begin + DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_PITCH; + DDSHeader.ddspf.dwFlags := DDPF_FOURCC; + DDSHeader.ddspf.dwFourCC := MAGIC_DX10; + DDSHeaderDX10.dxgiFormat := Integer(FileFO4.DXGIFormat); + DDSHeader.dwPitchOrLinearSize := FileFO4.Width * 2; + end; + DXGI_FORMAT_R8_SINT, DXGI_FORMAT_R8_SNORM, DXGI_FORMAT_R8_UINT: begin + DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_PITCH; + DDSHeader.ddspf.dwFlags := DDPF_FOURCC; + DDSHeader.ddspf.dwFourCC := MAGIC_DX10; + DDSHeaderDX10.dxgiFormat := Integer(FileFO4.DXGIFormat); + DDSHeader.dwPitchOrLinearSize := FileFO4.Width; + end; + DXGI_FORMAT_R8G8B8A8_UNORM: begin + DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_PITCH; + DDSHeader.ddspf.dwFlags := DDPF_RGB or DDPF_ALPHAPIXELS; + DDSHeader.ddspf.dwRGBBitCount := 32; + DDSHeader.ddspf.dwRBitMask := $000000FF; + DDSHeader.ddspf.dwGBitMask := $0000FF00; + DDSHeader.ddspf.dwBBitMask := $00FF0000; + DDSHeader.ddspf.dwABitMask := $FF000000; + DDSHeader.dwPitchOrLinearSize := FileFO4.Width * 4; + end; + DXGI_FORMAT_B8G8R8A8_UNORM: begin + DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_PITCH; + DDSHeader.ddspf.dwFlags := DDPF_RGB or DDPF_ALPHAPIXELS; + DDSHeader.ddspf.dwRGBBitCount := 32; + DDSHeader.ddspf.dwRBitMask := $00FF0000; + DDSHeader.ddspf.dwGBitMask := $0000FF00; + DDSHeader.ddspf.dwBBitMask := $000000FF; + DDSHeader.ddspf.dwABitMask := $FF000000; + DDSHeader.dwPitchOrLinearSize := FileFO4.Width * 4; + end; + DXGI_FORMAT_B8G8R8X8_UNORM: begin + DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_PITCH; + DDSHeader.ddspf.dwFlags := DDPF_RGB; + DDSHeader.ddspf.dwRGBBitCount := 32; + DDSHeader.ddspf.dwRBitMask := $00FF0000; + DDSHeader.ddspf.dwGBitMask := $0000FF00; + DDSHeader.ddspf.dwBBitMask := $000000FF; + DDSHeader.dwPitchOrLinearSize := FileFO4.Width * 4; + end; + DXGI_FORMAT_B5G6R5_UNORM: begin + DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_PITCH; + DDSHeader.ddspf.dwFlags := DDPF_RGB; + DDSHeader.ddspf.dwRGBBitCount := 16; + DDSHeader.ddspf.dwRBitMask := $0000F800; + DDSHeader.ddspf.dwGBitMask := $000007E0; + DDSHeader.ddspf.dwBBitMask := $0000001F; + DDSHeader.dwPitchOrLinearSize := FileFO4.Width * 2; + end; + DXGI_FORMAT_B5G5R5A1_UNORM: begin + DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_PITCH; + DDSHeader.ddspf.dwFlags := DDPF_RGB or DDPF_ALPHAPIXELS; + DDSHeader.ddspf.dwRGBBitCount := 16; + DDSHeader.ddspf.dwRBitMask := $00007C00; + DDSHeader.ddspf.dwGBitMask := $000003E0; + DDSHeader.ddspf.dwBBitMask := $0000001F; + DDSHeader.ddspf.dwABitMask := $00008000; + DDSHeader.dwPitchOrLinearSize := FileFO4.Width * 2; + end; + DXGI_FORMAT_R8G8_UNORM: begin + DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_PITCH; + DDSHeader.ddspf.dwFlags := DDPF_LUMINANCE OR DDPF_ALPHAPIXELS; + DDSHeader.ddspf.dwRGBBitCount := 16; + DDSHeader.ddspf.dwRBitMask := $000000FF; + DDSHeader.ddspf.dwABitMask := $0000FF00; + DDSHeader.dwPitchOrLinearSize := FileFO4.Width * 2; + end; + DXGI_FORMAT_A8_UNORM: begin + DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_PITCH; + DDSHeader.ddspf.dwFlags := DDPF_ALPHA; + DDSHeader.ddspf.dwRGBBitCount := 8; + DDSHeader.ddspf.dwABitMask := $000000FF; + DDSHeader.dwPitchOrLinearSize := FileFO4.Width; + end; + DXGI_FORMAT_R8_UNORM: begin + DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_PITCH; + DDSHeader.ddspf.dwFlags := DDPF_LUMINANCE; + DDSHeader.ddspf.dwRGBBitCount := 8; + DDSHeader.ddspf.dwRBitMask := $000000FF; + DDSHeader.dwPitchOrLinearSize := FileFO4.Width; + end; + end; + + TexSize := SizeOf(TDDSHeader); + if DDSHeader.ddspf.dwFourCC = MAGIC_DX10 then begin + ReallocMem(Result.data, Result.size + SizeOf(TDDSHeaderDX10)); + Inc(TexSize, SizeOf(TDDSHeaderDX10)); + Result.size := TexSize; + end; + + // append chunks + for i := Low(FileFO4.TexChunks) to High(FileFO4.TexChunks) do with FileFO4.TexChunks[i] do begin + fStream.Position := Offset; + // compressed chunk + if PackedSize <> 0 then begin + SetLength(Buffer, PackedSize); + fStream.ReadBuffer(Buffer[0], Length(Buffer)); + SyncEndWrite; + try + DecompressBuf(@Buffer[0], Length(Buffer), @Result.data[TexSize], Size); + finally + SyncBeginWrite; + end; + end + // uncompressed chunk + else + fStream.ReadBuffer(Result.data[TexSize], Size); + + Inc(TexSize, Size); + Result.size := TexSize; + end; + end + + else + raise Exception.Create('Extraction is not supported for this archive'); + end; + finally + SyncEndWrite; + end; +end; + +function TwbBSArchive.ExtractFileData(const aFileName: string): TwbBSResultBuffer; +var + FileRecord: Pointer; +begin + if not (stReading in fStates) then + raise Exception.Create('Archive is not loaded'); + + FileRecord := FindFileRecord(aFileName); + + if not Assigned(FileRecord) then + raise Exception.Create('File not found in archive'); + + Result := ExtractFileData(FileRecord); +end; + +// Addded: For use in non-Borland C/C++ +procedure TwbBSArchive.ReleaseFileData(fileDataResult: TwbBSResultBuffer); +begin + FreeMem(fileDataResult.data); + fileDataResult.size := 0; +end; + +procedure TwbBSArchive.ExtractFile(const aFileName, aSaveAs: string); +var + fs: TFileStream; + fileData: TwbBSResultBuffer; +begin + if not (stReading in fStates) then + raise Exception.Create('Archive is not loaded'); + + fs := TFileStream.Create(aSaveAs, fmCreate); + try + fileData := ExtractFileData(aFileName); + fs.Write(fileData.data[0], fileData.size); + finally + ReleaseFileData(fileData); + fs.Free; + end; +end; + +procedure TwbBSArchive.IterateFiles(aProc: TBSFileIterationProc; aData: Pointer = nil; + aSingleThreaded: Boolean = False); +var + i, j: Integer; +begin + if not Assigned(aProc) then + Exit; + + if fMultiThreaded and not aSingleThreaded then + case fType of + baTES3: + TParallel.&For(Low(fFilesTES3), High(fFilesTES3), procedure(i: Integer; LoopState: TParallel.TLoopState) begin + if aProc(Self, fFilesTES3[i].Name, @fFilesTES3[i], nil, aData) then + LoopState.Stop; + end); + baTES4, baFO3, baSSE: + TParallel.&For(Low(fFoldersTES4), High(fFoldersTES4), procedure(i: Integer; OuterLoopState: TParallel.TLoopState) begin + TParallel.&For(Low(fFoldersTES4[i].Files), High(fFoldersTES4[i].Files), procedure(j: Integer; InnerLoopState: TParallel.TLoopState) begin + if aProc(Self, fFoldersTES4[i].Name + '\' + fFoldersTES4[i].Files[j].Name, @fFoldersTES4[i].Files[j], @fFoldersTES4[i], aData) then begin + OuterLoopState.Stop; + InnerLoopState.Stop; + end; + end); + end); + baFO4, baFO4dds, baSF, baSFdds: + TParallel.&For(Low(fFilesFO4), High(fFilesFO4), procedure(i: Integer; LoopState: TParallel.TLoopState) begin + if aProc(Self, fFilesFO4[i].Name, @fFilesFO4[i], nil, aData) then + LoopState.Stop; + end); + end + else + case fType of + baTES3: + for i := Low(fFilesTES3) to High(fFilesTES3) do + if aProc(Self, fFilesTES3[i].Name, @fFilesTES3[i], nil, aData) then + Break; + baTES4, baFO3, baSSE: + for i := Low(fFoldersTES4) to High(fFoldersTES4) do + for j := Low(fFoldersTES4[i].Files) to High(fFoldersTES4[i].Files) do + if aProc(Self, fFoldersTES4[i].Name + '\' + fFoldersTES4[i].Files[j].Name, @fFoldersTES4[i].Files[j], @fFoldersTES4[i], aData) then + Break; + baFO4, baFO4dds, baSF, baSFdds: + for i := Low(fFilesFO4) to High(fFilesFO4) do + if aProc(Self, fFilesFO4[i].Name, @fFilesFO4[i], nil, aData) then + Break; + end; +end; + +{procedure TwbBSArchive.IterateFolders(aProc: TBSFileIterationProc); +var + i: Integer; +begin + if not Assigned(aProc) then + Exit; + + if fType in [baTES4, baFO3, baSSE] then + for i := Low(fFoldersTES4) to High(fFoldersTES4) do + aProc(Self, fFoldersTES4[i].Name, nil, @fFoldersTES4[i]); +end;} + +function TwbBSArchive.FileExists(const aFileName: string): Boolean; +begin + Result := Assigned(FindFileRecord(aFileName)); +end; + +procedure TwbBSArchive.Close; +begin + if Assigned(fStream) then + FreeAndNil(fStream); + + if stWriting in fStates then + System.SysUtils.DeleteFile(fFileName); + + fStates := []; + fType := baNone; + fFileName := ''; + fDataOffset := 0; + FillChar(fHeaderTES3, SizeOf(fHeaderTES3), 0); + SetLength(fFilesTES3, 0); + FillChar(fHeaderTES4, SizeOf(fHeaderTES4), 0); + SetLength(fFoldersTES4, 0); + FillChar(fHeaderFO4, SizeOf(fHeaderFO4), 0); + SetLength(fFilesFO4, 0); + + if fShareData then begin + SetLength(fPackedData, 0); + fPackedDataCount := 0; + end; +end; + +procedure TwbBSArchive.ResourceDict(const aDict: TwbResourceDict; aFolder: string); +var + Folder : string; + i, j : Integer; +begin + if not Assigned(aDict) then + Exit; + + Folder := ExcludeTrailingPathDelimiter(aFolder); + case fType of + baTES3: + for i := Low(fFilesTES3) to High(fFilesTES3) do + with fFilesTES3[i] do + if (Folder = '') or Name.StartsWith(Folder, True) then + aDict.TryAdd(Name, wbNothing); + baTES4, baFO3, baSSE: + for i := Low(fFoldersTES4) to High(fFoldersTES4) do + with fFoldersTES4[i] do begin + if (Folder = '') or Name.StartsWith(Folder, True) then + for j := Low(Files) to High(Files) do begin + var lName := Name + '\' + Files[j].Name; + aDict.TryAdd(lName, wbNothing); + end; + end; + baFO4, baFO4dds, baSF, baSFdds: + for i := Low(fFilesFO4) to High(fFilesFO4) do + with fFilesFO4[i] do + if (Folder = '') or Name.StartsWith(Folder, True) then + aDict.TryAdd(Name, wbNothing); + end; +end; + +procedure TwbBSArchive.ResourceList(const aList: TStrings; aFolder: string = ''); +var + Folder : string; + i, j : Integer; +begin + if not Assigned(aList) then + Exit; + + Folder := ExcludeTrailingPathDelimiter(aFolder); + case fType of + baTES3: + for i := Low(fFilesTES3) to High(fFilesTES3) do + with fFilesTES3[i] do + if (Folder = '') or Name.StartsWith(Folder, True) then + aList.Add(Name); + baTES4, baFO3, baSSE: + for i := Low(fFoldersTES4) to High(fFoldersTES4) do + with fFoldersTES4[i] do begin + if (Folder = '') or Name.StartsWith(Folder, True) then + for j := Low(Files) to High(Files) do + aList.Add(Name + '\' + Files[j].Name); + end; + baFO4, baFO4dds, baSF, baSFdds: + for i := Low(fFilesFO4) to High(fFilesFO4) do + with fFilesFO4[i] do + if (Folder = '') or Name.StartsWith(Folder, True) then + aList.Add(Name); + end; + +end; + +end. |
