From edc33b430f58acfd5501535731a22edfc7440ec9 Mon Sep 17 00:00:00 2001 From: SeriousCache <104311035+SeriousCache@users.noreply.github.com> Date: Sun, 24 Apr 2022 18:31:34 +0200 Subject: [PATCH] UABE rework, Initial OSS release "Still alive" edition The application part of the reworked UABE is mostly written from scratch. Key changes: Several bundle files can be edited at once. Files can be opened and closed without closing all files first. The selection in the file list/tree determines the files shown for editing, and multiple indepentent selections can be managed using tabs. Operations on opened files can run asynchronously (currently set to 4 threads, but no inherent limit). The entire UABE application code is moved to shared libraries and can be referenced by plugins. Generic asset editing is now possible within the application. Resource editing is supported, currently only used for the generic asset editor. Removed all mentions of neurotoxin. UABE is now available under the Eclipse Public License v2.0. --- .gitignore | 10 + AssetsTools/AssetBundleFileFormat.cpp | 2836 ++++++ AssetsTools/AssetBundleFileFormat.h | 209 + AssetsTools/AssetBundleFileTable.cpp | 906 ++ AssetsTools/AssetBundleFileTable.h | 93 + AssetsTools/AssetTypeClass.cpp | 930 ++ AssetsTools/AssetTypeClass.h | 359 + AssetsTools/AssetsFileFormat.cpp | 2148 +++++ AssetsTools/AssetsFileFormat.h | 48 + AssetsTools/AssetsFileFormatTypes.h | 226 + AssetsTools/AssetsFileReader.cpp | 2188 +++++ AssetsTools/AssetsFileReader.h | 182 + AssetsTools/AssetsFileTable.cpp | 214 + AssetsTools/AssetsFileTable.h | 67 + AssetsTools/AssetsReplacer.cpp | 630 ++ AssetsTools/AssetsReplacer.h | 105 + AssetsTools/BundleReplacer.cpp | 1210 +++ AssetsTools/BundleReplacer.h | 106 + AssetsTools/CMakeLists.txt | 27 + AssetsTools/ClassDatabaseFile.cpp | 1622 ++++ AssetsTools/ClassDatabaseFile.h | 157 + AssetsTools/EngineVersion.cpp | 22 + AssetsTools/EngineVersion.h | 13 + AssetsTools/InternalAssetsReplacer.h | 159 + AssetsTools/InternalBundleReplacer.h | 209 + AssetsTools/ResourceManagerFile.cpp | 364 + AssetsTools/ResourceManagerFile.h | 70 + AssetsTools/TextureFileFormat.cpp | 2323 +++++ AssetsTools/TextureFileFormat.h | 155 + AssetsTools/defines.h | 40 + AssetsTools/dllmain.cpp | 24 + AssetsTools/stdafx.cpp | 1 + AssetsTools/stdafx.h | 18 + CMakeLists.txt | 34 + CMakeModules/FindPVRTexTool.cmake | 17 + CMakeModules/Findfmod.cmake | 24 + CMakeSettings.Example.json | 73 + Changelog.md | 272 - CrnlibWrap/CMakeLists.txt | 11 + CrnlibWrap/CrnlibWrap.cpp | 165 + CrnlibWrap/CrnlibWrap.h | 14 + CrnlibWrap/CrnlibWrapLegacy.def | 3 + CrnlibWrap/CrnlibWrapUnity.def | 3 + Launcher/AssetBundleExtractor.cpp | 84 + Launcher/AssetBundleExtractor.ico | Bin 0 -> 23558 bytes Launcher/AssetBundleExtractor.manifest | 30 + Launcher/CMakeLists.txt | 23 + Launcher/Launcher.rc | 50 + Launcher/resource.h | 8 + Legacy.md | 198 - Licenses/LodePNG_license.txt | 22 + Licenses/assimp_license.txt | 63 + Licenses/astcenc_license.txt | 175 + Licenses/cecil_license.txt | 21 + Licenses/crunch-unity-license.txt | 22 + Licenses/half_license.txt | 15 + Licenses/ispc_texcomp_license.txt | 19 + Licenses/jsmn_license.txt | 19 + Licenses/libfgen_lgpl.txt | 165 + Licenses/libfgen_license.txt | 15 + Licenses/libsquish_license.txt | 20 + Licenses/license.txt | 277 + Licenses/lz4_license.txt | 24 + Licenses/mctrl_license.txt | 503 ++ Licenses/pthreads_license.txt | 504 ++ Licenses/texgenpack_license.txt | 13 + Licenses/vgmstream_license.txt | 22 + ModInstaller/CMakeLists.txt | 7 + ModInstaller/Dialogs.rc | Bin 0 -> 13426 bytes ModInstaller/InstallDialog.cpp | 1215 +++ ModInstaller/InstallDialog.h | 163 + ModInstaller/InstallerDataFormat.cpp | 195 + ModInstaller/InstallerDataFormat.h | 62 + ModInstaller/Licences.h | 27 + ModInstaller/MakeIconResource.cpp | 206 + ModInstaller/MakeIconResource.h | 6 + ModInstaller/MakeInstaller.cpp | 324 + ModInstaller/ModInstaller.cpp | 739 ++ ModInstaller/ModInstaller.h | 25 + ModInstaller/ModInstaller.ico | Bin 0 -> 15086 bytes ModInstaller/ModInstaller.manifest | 30 + ModInstaller/dllmain.cpp | 20 + ModInstaller/resource.h | Bin 0 -> 4470 bytes ModInstaller/stdafx.cpp | 1 + ModInstaller/stdafx.h | 12 + ModInstaller/targetver.h | 3 + Plugins/AudioClip/AudioClip.cpp | 494 ++ Plugins/AudioClip/AudioClip.def | 3 + Plugins/AudioClip/CMakeLists.txt | 17 + Plugins/AudioClip/wavfile.cpp | 115 + Plugins/AudioClip/wavfile.h | 35 + Plugins/CMakeLists.txt | 5 + Plugins/Mesh/AssimpMesh.cpp | 438 + Plugins/Mesh/AssimpMesh.h | 14 + Plugins/Mesh/CMakeLists.txt | 8 + Plugins/Mesh/Mesh.cpp | 1170 +++ Plugins/Mesh/Mesh.def | 3 + Plugins/Mesh/Mesh.h | 287 + Plugins/Mesh/MeshStructures.cpp | 997 +++ Plugins/TextAsset/CMakeLists.txt | 7 + Plugins/TextAsset/TextAsset.cpp | 294 + Plugins/TextAsset/TextAsset.def | 3 + Plugins/Texture/CMakeLists.txt | 7 + Plugins/Texture/Texture.cpp | 282 + Plugins/Texture/Texture.def | 3 + Plugins/Texture/Texture.h | 36 + Plugins/Texture/Texture.rc | Bin 0 -> 11474 bytes Plugins/Texture/TextureWin32.cpp | 953 ++ Plugins/Texture/defines.h | 515 ++ Plugins/Texture/lodepng.cpp | 6174 +++++++++++++ Plugins/Texture/lodepng.h | 1723 ++++ Plugins/Texture/resource.h | Bin 0 -> 4390 bytes Plugins/Texture/stb_image.h | 7661 +++++++++++++++++ Plugins/Texture/stb_image_write.h | 1666 ++++ Plugins/Utility/CMakeLists.txt | 7 + Plugins/Utility/Utility.cpp | 454 + Plugins/Utility/Utility.def | 3 + README.md | 34 +- Readme.License.txt | 27 + TexToolWrap/CMakeLists.txt | 24 + TexToolWrap/ETexFmts.h | 221 + TexToolWrap/TexToolWrap.cpp | 88 + TexToolWrap/exports.def | 5 + Tools/Mono.Cecil.Rocks.dll | Bin 0 -> 23040 bytes Tools/Mono.Cecil.dll | Bin 0 -> 279552 bytes Tools/TypeTreeGenerator.exe | Bin 0 -> 49664 bytes TypeTreeGenerator/CMakeLists.txt | 14 + TypeTreeGenerator/CMakeSettings.json | 28 + TypeTreeGenerator/ClassDatabaseFile2.cs | 274 + TypeTreeGenerator/EngineVersion.cs | 9 + TypeTreeGenerator/HelperClass.cs | 855 ++ TypeTreeGenerator/Logger.cs | 127 + TypeTreeGenerator/Program.cs | 272 + TypeTreeGenerator/TypeField.cs | 967 +++ UABE_Generic/AppContext.cpp | 833 ++ UABE_Generic/AppContext.h | 201 + UABE_Generic/AssetContainerList.cpp | 455 + UABE_Generic/AssetContainerList.h | 142 + UABE_Generic/AssetIterator.cpp | 304 + UABE_Generic/AssetIterator.h | 116 + UABE_Generic/AssetPluginUtil.cpp | 1697 ++++ UABE_Generic/AssetPluginUtil.h | 308 + UABE_Generic/AsyncTask.cpp | 413 + UABE_Generic/AsyncTask.h | 132 + UABE_Generic/CMakeLists.txt | 8 + UABE_Generic/CreateEmptyValueField.cpp | 247 + UABE_Generic/CreateEmptyValueField.h | 14 + UABE_Generic/FileContext.cpp | 646 ++ UABE_Generic/FileContext.h | 351 + UABE_Generic/FileContextInfo.cpp | 2230 +++++ UABE_Generic/FileContextInfo.h | 498 ++ UABE_Generic/FileModTree.cpp | 304 + UABE_Generic/FileModTree.h | 223 + UABE_Generic/IAssetBatchImportDesc.h | 39 + UABE_Generic/IProgressIndicator.cpp | 4 + UABE_Generic/IProgressIndicator.h | 57 + UABE_Generic/PluginManager.cpp | 7 + UABE_Generic/PluginManager.h | 108 + UABE_Generic/TaskStatusTracker.cpp | 230 + UABE_Generic/TaskStatusTracker.h | 80 + UABE_Generic/api.h | 6 + UABE_Win32/AddAssetDialog.cpp | 728 ++ UABE_Win32/AddAssetDialog.h | 40 + UABE_Win32/AssetBundleExtractor.rc | Bin 0 -> 67382 bytes UABE_Win32/AssetDependDialog.cpp | 710 ++ UABE_Win32/AssetDependDialog.h | 56 + UABE_Win32/AssetListDialog.cpp | 2764 ++++++ UABE_Win32/AssetListDialog.h | 472 + UABE_Win32/AssetViewModifyDialog.cpp | 2348 +++++ UABE_Win32/AssetViewModifyDialog.h | 352 + UABE_Win32/BatchImportDialog.cpp | 588 ++ UABE_Win32/BatchImportDialog.h | 71 + UABE_Win32/BundleDialog.cpp | 738 ++ UABE_Win32/BundleDialog.h | 59 + UABE_Win32/CMakeLists.txt | 6 + UABE_Win32/FileDialog.cpp | 868 ++ UABE_Win32/FileDialog.h | 40 + UABE_Win32/MainWindow2.cpp | 2441 ++++++ UABE_Win32/MainWindow2.h | 478 + UABE_Win32/ModInstallerEditor2.cpp | 818 ++ UABE_Win32/ModInstallerEditor2.h | 41 + UABE_Win32/ModPackageLoader.cpp | 398 + UABE_Win32/ModPackageLoader.h | 21 + UABE_Win32/MonoBehaviourManager.cpp | 613 ++ UABE_Win32/MonoBehaviourManager.h | 15 + UABE_Win32/ProgressDialog.cpp | 579 ++ UABE_Win32/ProgressDialog.h | 92 + UABE_Win32/SelectClassDbDialog.cpp | 202 + UABE_Win32/SelectClassDbDialog.h | 67 + UABE_Win32/SplitterControlHandler.cpp | 158 + UABE_Win32/SplitterControlHandler.h | 44 + UABE_Win32/TypeDatabaseEditor.cpp | 1197 +++ UABE_Win32/TypeDatabaseEditor.h | 3 + UABE_Win32/TypeDbPackageEditor.cpp | 419 + UABE_Win32/TypeDbPackageEditor.h | 3 + UABE_Win32/UABE_Win32.manifest | 30 + UABE_Win32/Win32AppContext.cpp | 306 + UABE_Win32/Win32AppContext.h | 82 + UABE_Win32/Win32BatchImportDesc.cpp | 29 + UABE_Win32/Win32BatchImportDesc.h | 34 + UABE_Win32/Win32ModTreeDialogBase.cpp | 261 + UABE_Win32/Win32ModTreeDialogBase.h | 27 + UABE_Win32/Win32PluginManager.cpp | 105 + UABE_Win32/Win32PluginManager.h | 61 + UABE_Win32/Win32TaskStatusTracker.cpp | 801 ++ UABE_Win32/Win32TaskStatusTracker.h | 52 + UABE_Win32/api.h | 6 + UABE_Win32/resource.h | Bin 0 -> 27332 bytes UABE_Win32/stdafx.h | 23 + UABE_Win32/targetver.h | 3 + classdata.tpk | Bin 0 -> 72945 bytes depend.cmake | 321 + ...9bdbf90ce626475635815ee18537718a05b1.patch | 531 ++ ...81ed5abc202c6f06be9302d193ba44a765c9.patch | 12 + ...0648c8a440b4397f1d96ea5cf5700f830417.patch | 39 + ...900eca8ec609d279270e72936258f81ddfb7.patch | 26 + ...5130f5286850eafe8de65f51e05604a02929.patch | 63 + fetchcontent/libsquish-1.15.tgz | Bin 0 -> 59199 bytes ...4bfbfffbb1530e69213199e775e54edbad21.patch | 2419 ++++++ ...cc211d626f28e05ecbb0c10f739bd36d6442.patch | 32 + ...8ef583ca9592a55ea217b0ec43a2e25b9cbe.patch | 891 ++ inc/LZMA/7zTypes.h | 256 + inc/LZMA/LzmaDec.h | 231 + inc/LZMA/LzmaEnc.h | 80 + inc/LZMA/LzmaLib.h | 40 + inc/LZMA/Types.h | 254 + inc/LZMA/api.h | 8 + inc/half.hpp | 2909 +++++++ libCompression/7zTypes.h | 256 + libCompression/CMakeLists.txt | 2 + libCompression/Compiler.h | 32 + libCompression/LzFind.c | 1044 +++ libCompression/LzFind.h | 117 + libCompression/LzFindMt.c | 803 ++ libCompression/LzFindMt.h | 101 + libCompression/LzHash.h | 57 + libCompression/LzmaDec.c | 1100 +++ libCompression/LzmaDec.h | 227 + libCompression/LzmaEnc.c | 2351 +++++ libCompression/LzmaEnc.h | 78 + libCompression/Precomp.h | 10 + libCompression/Threads.c | 93 + libCompression/Threads.h | 67 + libCompression/api.h | 8 + libCompression/lz4.c | 2519 ++++++ libCompression/lz4.h | 774 ++ libCompression/lz4_LICENSE | 24 + libCompression/lz4dec.cpp | 374 + libCompression/lz4dec.h | 7 + libCompression/lz4e.h | 19 + libCompression/lz4enc.cpp | 1015 +++ libCompression/lz4enc.h | 4 + libCompression/lz4frame.c | 1920 +++++ libCompression/lz4frame.h | 623 ++ libCompression/lz4frame_static.h | 47 + libCompression/lz4hc.c | 1621 ++++ libCompression/lz4hc.h | 413 + libCompression/xxhash.c | 1030 +++ libCompression/xxhash.h | 328 + libStringConverter/CMakeLists.txt | 3 + libStringConverter/convert.cpp | 59 + libStringConverter/convert.h | 144 + 262 files changed, 103739 insertions(+), 478 deletions(-) create mode 100644 .gitignore create mode 100644 AssetsTools/AssetBundleFileFormat.cpp create mode 100644 AssetsTools/AssetBundleFileFormat.h create mode 100644 AssetsTools/AssetBundleFileTable.cpp create mode 100644 AssetsTools/AssetBundleFileTable.h create mode 100644 AssetsTools/AssetTypeClass.cpp create mode 100644 AssetsTools/AssetTypeClass.h create mode 100644 AssetsTools/AssetsFileFormat.cpp create mode 100644 AssetsTools/AssetsFileFormat.h create mode 100644 AssetsTools/AssetsFileFormatTypes.h create mode 100644 AssetsTools/AssetsFileReader.cpp create mode 100644 AssetsTools/AssetsFileReader.h create mode 100644 AssetsTools/AssetsFileTable.cpp create mode 100644 AssetsTools/AssetsFileTable.h create mode 100644 AssetsTools/AssetsReplacer.cpp create mode 100644 AssetsTools/AssetsReplacer.h create mode 100644 AssetsTools/BundleReplacer.cpp create mode 100644 AssetsTools/BundleReplacer.h create mode 100644 AssetsTools/CMakeLists.txt create mode 100644 AssetsTools/ClassDatabaseFile.cpp create mode 100644 AssetsTools/ClassDatabaseFile.h create mode 100644 AssetsTools/EngineVersion.cpp create mode 100644 AssetsTools/EngineVersion.h create mode 100644 AssetsTools/InternalAssetsReplacer.h create mode 100644 AssetsTools/InternalBundleReplacer.h create mode 100644 AssetsTools/ResourceManagerFile.cpp create mode 100644 AssetsTools/ResourceManagerFile.h create mode 100644 AssetsTools/TextureFileFormat.cpp create mode 100644 AssetsTools/TextureFileFormat.h create mode 100644 AssetsTools/defines.h create mode 100644 AssetsTools/dllmain.cpp create mode 100644 AssetsTools/stdafx.cpp create mode 100644 AssetsTools/stdafx.h create mode 100644 CMakeLists.txt create mode 100644 CMakeModules/FindPVRTexTool.cmake create mode 100644 CMakeModules/Findfmod.cmake create mode 100644 CMakeSettings.Example.json delete mode 100644 Changelog.md create mode 100644 CrnlibWrap/CMakeLists.txt create mode 100644 CrnlibWrap/CrnlibWrap.cpp create mode 100644 CrnlibWrap/CrnlibWrap.h create mode 100644 CrnlibWrap/CrnlibWrapLegacy.def create mode 100644 CrnlibWrap/CrnlibWrapUnity.def create mode 100644 Launcher/AssetBundleExtractor.cpp create mode 100644 Launcher/AssetBundleExtractor.ico create mode 100644 Launcher/AssetBundleExtractor.manifest create mode 100644 Launcher/CMakeLists.txt create mode 100644 Launcher/Launcher.rc create mode 100644 Launcher/resource.h delete mode 100644 Legacy.md create mode 100644 Licenses/LodePNG_license.txt create mode 100644 Licenses/assimp_license.txt create mode 100644 Licenses/astcenc_license.txt create mode 100644 Licenses/cecil_license.txt create mode 100644 Licenses/crunch-unity-license.txt create mode 100644 Licenses/half_license.txt create mode 100644 Licenses/ispc_texcomp_license.txt create mode 100644 Licenses/jsmn_license.txt create mode 100644 Licenses/libfgen_lgpl.txt create mode 100644 Licenses/libfgen_license.txt create mode 100644 Licenses/libsquish_license.txt create mode 100644 Licenses/license.txt create mode 100644 Licenses/lz4_license.txt create mode 100644 Licenses/mctrl_license.txt create mode 100644 Licenses/pthreads_license.txt create mode 100644 Licenses/texgenpack_license.txt create mode 100644 Licenses/vgmstream_license.txt create mode 100644 ModInstaller/CMakeLists.txt create mode 100644 ModInstaller/Dialogs.rc create mode 100644 ModInstaller/InstallDialog.cpp create mode 100644 ModInstaller/InstallDialog.h create mode 100644 ModInstaller/InstallerDataFormat.cpp create mode 100644 ModInstaller/InstallerDataFormat.h create mode 100644 ModInstaller/Licences.h create mode 100644 ModInstaller/MakeIconResource.cpp create mode 100644 ModInstaller/MakeIconResource.h create mode 100644 ModInstaller/MakeInstaller.cpp create mode 100644 ModInstaller/ModInstaller.cpp create mode 100644 ModInstaller/ModInstaller.h create mode 100644 ModInstaller/ModInstaller.ico create mode 100644 ModInstaller/ModInstaller.manifest create mode 100644 ModInstaller/dllmain.cpp create mode 100644 ModInstaller/resource.h create mode 100644 ModInstaller/stdafx.cpp create mode 100644 ModInstaller/stdafx.h create mode 100644 ModInstaller/targetver.h create mode 100644 Plugins/AudioClip/AudioClip.cpp create mode 100644 Plugins/AudioClip/AudioClip.def create mode 100644 Plugins/AudioClip/CMakeLists.txt create mode 100644 Plugins/AudioClip/wavfile.cpp create mode 100644 Plugins/AudioClip/wavfile.h create mode 100644 Plugins/CMakeLists.txt create mode 100644 Plugins/Mesh/AssimpMesh.cpp create mode 100644 Plugins/Mesh/AssimpMesh.h create mode 100644 Plugins/Mesh/CMakeLists.txt create mode 100644 Plugins/Mesh/Mesh.cpp create mode 100644 Plugins/Mesh/Mesh.def create mode 100644 Plugins/Mesh/Mesh.h create mode 100644 Plugins/Mesh/MeshStructures.cpp create mode 100644 Plugins/TextAsset/CMakeLists.txt create mode 100644 Plugins/TextAsset/TextAsset.cpp create mode 100644 Plugins/TextAsset/TextAsset.def create mode 100644 Plugins/Texture/CMakeLists.txt create mode 100644 Plugins/Texture/Texture.cpp create mode 100644 Plugins/Texture/Texture.def create mode 100644 Plugins/Texture/Texture.h create mode 100644 Plugins/Texture/Texture.rc create mode 100644 Plugins/Texture/TextureWin32.cpp create mode 100644 Plugins/Texture/defines.h create mode 100644 Plugins/Texture/lodepng.cpp create mode 100644 Plugins/Texture/lodepng.h create mode 100644 Plugins/Texture/resource.h create mode 100644 Plugins/Texture/stb_image.h create mode 100644 Plugins/Texture/stb_image_write.h create mode 100644 Plugins/Utility/CMakeLists.txt create mode 100644 Plugins/Utility/Utility.cpp create mode 100644 Plugins/Utility/Utility.def create mode 100644 Readme.License.txt create mode 100644 TexToolWrap/CMakeLists.txt create mode 100644 TexToolWrap/ETexFmts.h create mode 100644 TexToolWrap/TexToolWrap.cpp create mode 100644 TexToolWrap/exports.def create mode 100644 Tools/Mono.Cecil.Rocks.dll create mode 100644 Tools/Mono.Cecil.dll create mode 100644 Tools/TypeTreeGenerator.exe create mode 100644 TypeTreeGenerator/CMakeLists.txt create mode 100644 TypeTreeGenerator/CMakeSettings.json create mode 100644 TypeTreeGenerator/ClassDatabaseFile2.cs create mode 100644 TypeTreeGenerator/EngineVersion.cs create mode 100644 TypeTreeGenerator/HelperClass.cs create mode 100644 TypeTreeGenerator/Logger.cs create mode 100644 TypeTreeGenerator/Program.cs create mode 100644 TypeTreeGenerator/TypeField.cs create mode 100644 UABE_Generic/AppContext.cpp create mode 100644 UABE_Generic/AppContext.h create mode 100644 UABE_Generic/AssetContainerList.cpp create mode 100644 UABE_Generic/AssetContainerList.h create mode 100644 UABE_Generic/AssetIterator.cpp create mode 100644 UABE_Generic/AssetIterator.h create mode 100644 UABE_Generic/AssetPluginUtil.cpp create mode 100644 UABE_Generic/AssetPluginUtil.h create mode 100644 UABE_Generic/AsyncTask.cpp create mode 100644 UABE_Generic/AsyncTask.h create mode 100644 UABE_Generic/CMakeLists.txt create mode 100644 UABE_Generic/CreateEmptyValueField.cpp create mode 100644 UABE_Generic/CreateEmptyValueField.h create mode 100644 UABE_Generic/FileContext.cpp create mode 100644 UABE_Generic/FileContext.h create mode 100644 UABE_Generic/FileContextInfo.cpp create mode 100644 UABE_Generic/FileContextInfo.h create mode 100644 UABE_Generic/FileModTree.cpp create mode 100644 UABE_Generic/FileModTree.h create mode 100644 UABE_Generic/IAssetBatchImportDesc.h create mode 100644 UABE_Generic/IProgressIndicator.cpp create mode 100644 UABE_Generic/IProgressIndicator.h create mode 100644 UABE_Generic/PluginManager.cpp create mode 100644 UABE_Generic/PluginManager.h create mode 100644 UABE_Generic/TaskStatusTracker.cpp create mode 100644 UABE_Generic/TaskStatusTracker.h create mode 100644 UABE_Generic/api.h create mode 100644 UABE_Win32/AddAssetDialog.cpp create mode 100644 UABE_Win32/AddAssetDialog.h create mode 100644 UABE_Win32/AssetBundleExtractor.rc create mode 100644 UABE_Win32/AssetDependDialog.cpp create mode 100644 UABE_Win32/AssetDependDialog.h create mode 100644 UABE_Win32/AssetListDialog.cpp create mode 100644 UABE_Win32/AssetListDialog.h create mode 100644 UABE_Win32/AssetViewModifyDialog.cpp create mode 100644 UABE_Win32/AssetViewModifyDialog.h create mode 100644 UABE_Win32/BatchImportDialog.cpp create mode 100644 UABE_Win32/BatchImportDialog.h create mode 100644 UABE_Win32/BundleDialog.cpp create mode 100644 UABE_Win32/BundleDialog.h create mode 100644 UABE_Win32/CMakeLists.txt create mode 100644 UABE_Win32/FileDialog.cpp create mode 100644 UABE_Win32/FileDialog.h create mode 100644 UABE_Win32/MainWindow2.cpp create mode 100644 UABE_Win32/MainWindow2.h create mode 100644 UABE_Win32/ModInstallerEditor2.cpp create mode 100644 UABE_Win32/ModInstallerEditor2.h create mode 100644 UABE_Win32/ModPackageLoader.cpp create mode 100644 UABE_Win32/ModPackageLoader.h create mode 100644 UABE_Win32/MonoBehaviourManager.cpp create mode 100644 UABE_Win32/MonoBehaviourManager.h create mode 100644 UABE_Win32/ProgressDialog.cpp create mode 100644 UABE_Win32/ProgressDialog.h create mode 100644 UABE_Win32/SelectClassDbDialog.cpp create mode 100644 UABE_Win32/SelectClassDbDialog.h create mode 100644 UABE_Win32/SplitterControlHandler.cpp create mode 100644 UABE_Win32/SplitterControlHandler.h create mode 100644 UABE_Win32/TypeDatabaseEditor.cpp create mode 100644 UABE_Win32/TypeDatabaseEditor.h create mode 100644 UABE_Win32/TypeDbPackageEditor.cpp create mode 100644 UABE_Win32/TypeDbPackageEditor.h create mode 100644 UABE_Win32/UABE_Win32.manifest create mode 100644 UABE_Win32/Win32AppContext.cpp create mode 100644 UABE_Win32/Win32AppContext.h create mode 100644 UABE_Win32/Win32BatchImportDesc.cpp create mode 100644 UABE_Win32/Win32BatchImportDesc.h create mode 100644 UABE_Win32/Win32ModTreeDialogBase.cpp create mode 100644 UABE_Win32/Win32ModTreeDialogBase.h create mode 100644 UABE_Win32/Win32PluginManager.cpp create mode 100644 UABE_Win32/Win32PluginManager.h create mode 100644 UABE_Win32/Win32TaskStatusTracker.cpp create mode 100644 UABE_Win32/Win32TaskStatusTracker.h create mode 100644 UABE_Win32/api.h create mode 100644 UABE_Win32/resource.h create mode 100644 UABE_Win32/stdafx.h create mode 100644 UABE_Win32/targetver.h create mode 100644 classdata.tpk create mode 100644 depend.cmake create mode 100644 fetchcontent/assimp-patch-80799bdbf90ce626475635815ee18537718a05b1.patch create mode 100644 fetchcontent/astcenc-x86-32-popcntu64-patch-7e2a81ed5abc202c6f06be9302d193ba44a765c9.patch create mode 100644 fetchcontent/crunch-legacy-patch-671a0648c8a440b4397f1d96ea5cf5700f830417.patch create mode 100644 fetchcontent/crunch-unity-patch-8708900eca8ec609d279270e72936258f81ddfb7.patch create mode 100644 fetchcontent/libfgen-patch-071e5130f5286850eafe8de65f51e05604a02929.patch create mode 100644 fetchcontent/libsquish-1.15.tgz create mode 100644 fetchcontent/mctrl-patch-42334bfbfffbb1530e69213199e775e54edbad21.patch create mode 100644 fetchcontent/pthreads4w-patch-02fecc211d626f28e05ecbb0c10f739bd36d6442.patch create mode 100644 fetchcontent/texgenpack-patch-cf548ef583ca9592a55ea217b0ec43a2e25b9cbe.patch create mode 100644 inc/LZMA/7zTypes.h create mode 100644 inc/LZMA/LzmaDec.h create mode 100644 inc/LZMA/LzmaEnc.h create mode 100644 inc/LZMA/LzmaLib.h create mode 100644 inc/LZMA/Types.h create mode 100644 inc/LZMA/api.h create mode 100644 inc/half.hpp create mode 100644 libCompression/7zTypes.h create mode 100644 libCompression/CMakeLists.txt create mode 100644 libCompression/Compiler.h create mode 100644 libCompression/LzFind.c create mode 100644 libCompression/LzFind.h create mode 100644 libCompression/LzFindMt.c create mode 100644 libCompression/LzFindMt.h create mode 100644 libCompression/LzHash.h create mode 100644 libCompression/LzmaDec.c create mode 100644 libCompression/LzmaDec.h create mode 100644 libCompression/LzmaEnc.c create mode 100644 libCompression/LzmaEnc.h create mode 100644 libCompression/Precomp.h create mode 100644 libCompression/Threads.c create mode 100644 libCompression/Threads.h create mode 100644 libCompression/api.h create mode 100644 libCompression/lz4.c create mode 100644 libCompression/lz4.h create mode 100644 libCompression/lz4_LICENSE create mode 100644 libCompression/lz4dec.cpp create mode 100644 libCompression/lz4dec.h create mode 100644 libCompression/lz4e.h create mode 100644 libCompression/lz4enc.cpp create mode 100644 libCompression/lz4enc.h create mode 100644 libCompression/lz4frame.c create mode 100644 libCompression/lz4frame.h create mode 100644 libCompression/lz4frame_static.h create mode 100644 libCompression/lz4hc.c create mode 100644 libCompression/lz4hc.h create mode 100644 libCompression/xxhash.c create mode 100644 libCompression/xxhash.h create mode 100644 libStringConverter/CMakeLists.txt create mode 100644 libStringConverter/convert.cpp create mode 100644 libStringConverter/convert.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b8e3f81 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +/.vs/ +/out/ +**.aps +**.suo +**.vcxproj.user +/fetchcontent/*-src/ +/fetchcontent/ispc_compiler/ +/TypeTreeGenerator/.vs +/TypeTreeGenerator/out +/Tools/TypeTreeGenerator.pdb diff --git a/AssetsTools/AssetBundleFileFormat.cpp b/AssetsTools/AssetBundleFileFormat.cpp new file mode 100644 index 0000000..109879f --- /dev/null +++ b/AssetsTools/AssetBundleFileFormat.cpp @@ -0,0 +1,2836 @@ +#include "stdafx.h" +#include "AssetBundleFileFormat.h" +#include "AssetsFileReader.h" +#include "AssetsFileFormat.h" +#include "../inc/LZMA/LzmaDec.h" +#include "../inc/LZMA/LzmaEnc.h" +#include "../libCompression/lz4.h" +#include "../libCompression/lz4dec.h" +#include "../libCompression/lz4enc.h" +#include +#include +#include + +ASSETSTOOLS_API bool AssetBundleBlockAndDirectoryList06::Read(QWORD filePos, IAssetsReader *pReader, AssetsFileVerifyLogger errorLogger) +{ + /*uint8_t *dataBuf = (uint8_t*)malloc(sizeComp); + if (!dataBuf) + goto __goto_allocerror; + if (!reader(filePos, sizeComp, dataBuf, lPar)) + goto __goto_readerror; + switch (compressionType) + { + case 2: + case 3: + //LZ4 + uint8_t *outBuf = (uint8_t*)malloc(sizeUncomp); + if (!outBuf) + goto __goto_allocerror; + if (LZ4_decompress_safe((char*)dataBuf, (char*)outBuf, (int)sizeComp & 0x7FFFFFFF, (int)sizeUncomp & 0x7FFFFFFF) != sizeUncomp) + { + free(dataBuf); + free(outBuf); + goto __goto_decomperror; + } + free(dataBuf); + dataBuf = outBuf; + break; + case 1: + //LZMA + //maybe support this, maybe don't (reading this structure isn't really required in our case if the bundle is compressed) + free(dataBuf); + return false; + break; + default: + break; + }*/ + if (!pReader->Read(filePos, 8, &this->checksumLow)) + goto __goto_readerror; + filePos += 8; + if (!pReader->Read(-1, 8, &this->checksumHigh)) + goto __goto_readerror; + filePos += 8; + + if (!pReader->Read(-1, 4, &this->blockCount)) + goto __goto_readerror; + filePos += 4; + SwapEndians_(this->blockCount); + blockInf = (AssetBundleBlockInfo06*)malloc(this->blockCount * sizeof(AssetBundleBlockInfo06)); + if (!blockInf) + goto __goto_allocerror; + for (uint32_t i = 0; i < this->blockCount; i++) + { + if (!pReader->Read(-1, 4, &blockInf[i].decompressedSize)) + goto __goto_readerror; + SwapEndians_(blockInf[i].decompressedSize); + if (!pReader->Read(-1, 4, &blockInf[i].compressedSize)) + goto __goto_readerror; + SwapEndians_(blockInf[i].compressedSize); + if (!pReader->Read(-1, 2, &blockInf[i].flags)) + goto __goto_readerror; + SwapEndians_(blockInf[i].flags); + filePos += 10; + } + + if (!pReader->Read(-1, 4, &this->directoryCount)) + goto __goto_readerror; + filePos += 4; + SwapEndians_(this->directoryCount); + dirInf = (AssetBundleDirectoryInfo06*)malloc(this->directoryCount * sizeof(AssetBundleDirectoryInfo06)); + if (!dirInf) + goto __goto_allocerror; + for (uint32_t i = 0; i < this->directoryCount; i++) + { + if (!pReader->Read(-1, 8, &dirInf[i].offset)) + goto __goto_readerror; + SwapEndians_(dirInf[i].offset); + if (!pReader->Read(-1, 8, &dirInf[i].decompressedSize)) + goto __goto_readerror; + SwapEndians_(dirInf[i].decompressedSize); + if (!pReader->Read(-1, 4, &dirInf[i].flags)) + goto __goto_readerror; + SwapEndians_(dirInf[i].flags); + filePos += 20; + + char nameBuffer[40]; + bool eosFound = false; size_t strLen = 0; + while (!eosFound) + { + QWORD nRead = pReader->Read(-1, 40, &nameBuffer); + if (!nRead) + goto __goto_readerror; + for (size_t i = 0; i < (size_t)nRead; i++) + { + if (nameBuffer[i] == 0) + { + eosFound = true; + strLen += (i+1); + break; + } + } + if (!eosFound) + strLen += (size_t)nRead; + } + dirInf[i].name = (char*)malloc(strLen); + if (dirInf[i].name == NULL) + goto __goto_allocerror; + if (pReader->Read(filePos, strLen, const_cast(dirInf[i].name)) != strLen) + goto __goto_readerror; + const_cast(dirInf[i].name)[strLen-1] = 0; + filePos += strLen; + } + return true; + __goto_allocerror: + if (errorLogger) errorLogger("AssetBundleBlockAndDirectoryList06 : Out of memory!"); + return false; + __goto_readerror: + if (errorLogger) errorLogger("AssetBundleBlockAndDirectoryList06 : A file read error occured!"); + return false; + /*__goto_decomperror: + if (errorLogger) errorLogger("AssetBundleBlockAndDirectoryList06 : A decompress error occured!"); + return false;*/ +} +ASSETSTOOLS_API void AssetBundleBlockAndDirectoryList06::Free() +{ + if (this->blockInf) + free(this->blockInf); + if (this->dirInf) + { + for (uint32_t i = 0; i < this->directoryCount; i++) + { + if (this->dirInf[i].name) + free(const_cast(this->dirInf[i].name)); + } + free(this->dirInf); + } + memset(this, 0, sizeof(AssetBundleBlockAndDirectoryList06)); +} +//Write doesn't compress +ASSETSTOOLS_API bool AssetBundleBlockAndDirectoryList06::Write(IAssetsWriter *pWriter, QWORD &curFilePos, AssetsFileVerifyLogger errorLogger) +{ + uint32_t dwTmp; + if (!pWriter->Write(curFilePos, 8, &this->checksumLow)) + goto __goto_writeerror; + curFilePos += 8; + if (!pWriter->Write(-1, 8, &this->checksumHigh)) + goto __goto_writeerror; + curFilePos += 8; + + dwTmp = SwapEndians(this->blockCount); + if (!pWriter->Write(-1, 4, &dwTmp)) + goto __goto_writeerror; + curFilePos += 4; + for (uint32_t i = 0; i < this->blockCount; i++) + { + dwTmp = SwapEndians(this->blockInf[i].decompressedSize); + if (!pWriter->Write(-1, 4, &dwTmp)) + goto __goto_writeerror; + curFilePos += 4; + dwTmp = SwapEndians(this->blockInf[i].compressedSize); + if (!pWriter->Write(-1, 4, &dwTmp)) + goto __goto_writeerror; + curFilePos += 4; + uint16_t wTmp = SwapEndians(this->blockInf[i].flags); + if (!pWriter->Write(-1, 2, &wTmp)) + goto __goto_writeerror; + curFilePos += 2; + } + + dwTmp = SwapEndians(this->directoryCount); + if (!pWriter->Write(-1, 4, &dwTmp)) + goto __goto_writeerror; + curFilePos += 4; + + for (uint32_t i = 0; i < this->directoryCount; i++) + { + QWORD qwTmp = SwapEndians(this->dirInf[i].offset); + if (!pWriter->Write(-1, 8, &qwTmp)) + goto __goto_writeerror; + curFilePos += 8; + qwTmp = SwapEndians(this->dirInf[i].decompressedSize); + if (!pWriter->Write(-1, 8, &qwTmp)) + goto __goto_writeerror; + curFilePos += 8; + dwTmp = SwapEndians(this->dirInf[i].flags); + if (!pWriter->Write(-1, 4, &dwTmp)) + goto __goto_writeerror; + curFilePos += 4; + + size_t curStrLen = strlen(this->dirInf[i].name)+1; + curFilePos += curStrLen; + if (!pWriter->Write(-1, curStrLen, this->dirInf[i].name)) + goto __goto_writeerror; + } + + return true; + __goto_writeerror: + if (errorLogger) errorLogger("AssetBundleHeader06 : A file write error occured!"); + return false; +} +ASSETSTOOLS_API bool AssetBundleHeader06::ReadInitial(IAssetsReader *pReader, AssetsFileVerifyLogger errorLogger) +{ + QWORD curFilePos = 0; + char curChar; + for (unsigned int i = 0;; i++) + { + if (!pReader->Read(curFilePos, 1, &curChar)) + goto __goto_readerror; + curFilePos++; + if (i < 13) signature[i] = curChar; + if (curChar == 0) break; + } + signature[12] = 0; + + if (!strcmp(signature, "UnityArchive")) + this->fileVersion = 6; + else + { + if (!pReader->Read(-1, 4, &this->fileVersion)) + goto __goto_readerror; + SwapEndians_(this->fileVersion); + } + + return true; + __goto_readerror: + if (errorLogger) errorLogger("AssetBundleHeader06 : A file read error occured!"); + return false; +} +ASSETSTOOLS_API bool AssetBundleHeader06::Read(IAssetsReader *pReader, AssetsFileVerifyLogger errorLogger) +{ + QWORD curPos = 0; + char curChar; + for (unsigned int i = 0;; i++) + { + if (!pReader->Read(curPos, 1, &curChar)) + goto __goto_readerror; + curPos++; + if (i < 13) signature[i] = curChar; + if (curChar == 0) break; + } + signature[12] = 0; + + if (!strcmp(signature, "UnityArchive")) + this->fileVersion = 6; + else + { + if (!pReader->Read(-1, 4, &this->fileVersion)) + goto __goto_readerror; + SwapEndians_(this->fileVersion); + if (this->fileVersion != 6 && this->fileVersion != 7) + { + if (errorLogger) errorLogger("That file version is unknown!"); + return false; + } + } + + for (unsigned int i = 0;; i++) + { + if (!pReader->Read(-1, 1, &curChar)) + goto __goto_readerror; + if (i < sizeof(minPlayerVersion)) minPlayerVersion[i] = curChar; + if (curChar == 0) break; + } + minPlayerVersion[sizeof(minPlayerVersion) - 1] = 0; + + for (unsigned int i = 0;; i++) + { + if (!pReader->Read(-1, 1, &curChar)) + goto __goto_readerror; + if (i < sizeof(fileEngineVersion)) fileEngineVersion[i] = curChar; + if (curChar == 0) break; + } + fileEngineVersion[sizeof(fileEngineVersion) - 1] = 0; + + if (!pReader->Read(-1, 8, &this->totalFileSize)) + goto __goto_readerror; + SwapEndians_(this->totalFileSize); + + if (!pReader->Read(-1, 4, &this->compressedSize)) + goto __goto_readerror; + SwapEndians_(this->compressedSize); + if (!pReader->Read(-1, 4, &this->decompressedSize)) + goto __goto_readerror; + SwapEndians_(this->decompressedSize); + + if (!pReader->Read(-1, 4, &this->flags)) + goto __goto_readerror; + SwapEndians_(this->flags); + + return true; + __goto_readerror: + if (errorLogger) errorLogger("AssetBundleHeader06 : A file read error occured!"); + return false; +} +ASSETSTOOLS_API bool AssetBundleHeader06::Write(IAssetsWriter *pWriter, QWORD &curFilePos, AssetsFileVerifyLogger errorLogger) +{ + uint32_t dwTmp; + QWORD qwTmp; + QWORD startPos = curFilePos; + size_t curStrLen = strlen(this->signature)+1; + if (!pWriter->Write(curFilePos, curStrLen, &this->signature)) + goto __goto_writeerror; + curFilePos += curStrLen; + + dwTmp = SwapEndians(this->fileVersion); + if (!pWriter->Write(-1, 4, &dwTmp)) + goto __goto_writeerror; + curFilePos += 4; + + curStrLen = strlen(this->minPlayerVersion)+1; + curFilePos += curStrLen; + if (!pWriter->Write(-1, curStrLen, &this->minPlayerVersion)) + goto __goto_writeerror; + + curStrLen = strlen(this->fileEngineVersion)+1; + curFilePos += curStrLen; + if (!pWriter->Write(-1, curStrLen, &this->fileEngineVersion)) + goto __goto_writeerror; + + qwTmp = SwapEndians(this->totalFileSize); + if (!pWriter->Write(-1, 8, &qwTmp)) + goto __goto_writeerror; + curFilePos += 8; + + dwTmp = SwapEndians(this->compressedSize); + if (!pWriter->Write(-1, 4, &dwTmp)) + goto __goto_writeerror; + curFilePos += 4; + + dwTmp = SwapEndians(this->decompressedSize); + if (!pWriter->Write(-1, 4, &dwTmp)) + goto __goto_writeerror; + curFilePos += 4; + + dwTmp = SwapEndians(this->flags); + if (!pWriter->Write(-1, 4, &dwTmp)) + goto __goto_writeerror; + curFilePos += 4; + + if (!strcmp(signature, "UnityWeb") || !strcmp(signature, "UnityRaw")) + { + dwTmp = 0; + if (!pWriter->Write(-1, 1, &dwTmp)) + goto __goto_writeerror; + curFilePos ++; + } + if (this->fileVersion >= 7) + { + QWORD alignmentLen = (((curFilePos - startPos) + 15) & ~15) - (curFilePos - startPos); + uint64_t alignment[2] = {0,0}; + if (!pWriter->Write(-1, alignmentLen, &alignment[0])) + goto __goto_writeerror; + curFilePos += alignmentLen; + } + + return true; + __goto_writeerror: + if (errorLogger) errorLogger("AssetBundleHeader06 : A file write error occured!"); + return false; +} + +ASSETSTOOLS_API bool AssetBundleHeader03::Read(IAssetsReader *pReader, AssetsFileVerifyLogger errorLogger) +{ + if (!pReader->Read(0, 9, &this->signature)) + goto __goto_readerror; + if (_strnicmp(this->signature, "UnityRaw", 9) && _strnicmp(this->signature, "UnityWeb", 9)) + { + /*if (!_strnicmp(this->signature, "UnityWeb", 9)) + { + if (errorLogger) errorLogger("AssetBundleHeader : I can't decompress compressed files!"); + return false; + }*/ + if (errorLogger) errorLogger("AssetBundleHeader : Unknown file type!"); + return false; + } + + if (!pReader->Read(-1, 4, &this->fileVersion)) + goto __goto_readerror; + SwapEndians_(this->fileVersion); + if (this->fileVersion != 3) + { + if (errorLogger) errorLogger("AssetBundleHeader : Either that file is invalid or it uses an unknown file version!"); + return false; + } + + char curChar; + for (unsigned int i = 0;; i++) + { + if (!pReader->Read(-1, 1, &curChar)) + goto __goto_readerror; + if (i < sizeof(minPlayerVersion)) minPlayerVersion[i] = curChar; + if (curChar == 0) break; + } + minPlayerVersion[sizeof(minPlayerVersion)-1] = 0; + + for (unsigned int i = 0;; i++) + { + if (!pReader->Read(-1, 1, &curChar)) + goto __goto_readerror; + if (i < sizeof(fileEngineVersion)) fileEngineVersion[i] = curChar; + if (curChar == 0) break; + } + fileEngineVersion[sizeof(fileEngineVersion)-1] = 0; + + if (!pReader->Read(-1, 4, &this->minimumStreamedBytes)) + goto __goto_readerror; + SwapEndians_(this->minimumStreamedBytes); + if (!pReader->Read(-1, 4, &this->bundleDataOffs)) + goto __goto_readerror; + SwapEndians_(this->bundleDataOffs); + if (!pReader->Read(-1, 4, &this->numberOfAssetsToDownload)) + goto __goto_readerror; + SwapEndians_(this->numberOfAssetsToDownload); + if (!pReader->Read(-1, 4, &this->blockCount)) + goto __goto_readerror; + SwapEndians_(this->blockCount); + if (this->pBlockList != NULL) free(this->pBlockList); + this->pBlockList = (AssetBundleOffsetPair*)malloc(sizeof(AssetBundleOffsetPair) * blockCount); + if (this->pBlockList == NULL) //out of memory + goto __goto_outofmemory; + for (uint32_t i = 0; i < blockCount; i++) + { + if (!pReader->Read(-1, 4, &this->pBlockList[i].compressed)) + goto __goto_readerror; + this->pBlockList[i].compressed = SwapEndians(this->pBlockList[i].compressed); + if (!pReader->Read(-1, 4, &this->pBlockList[i].uncompressed)) + goto __goto_readerror; + this->pBlockList[i].uncompressed = SwapEndians(this->pBlockList[i].uncompressed); + } + if (this->fileVersion >= 2) + { + if (!pReader->Read(-1, 4, &this->fileSize2)) + goto __goto_readerror; + SwapEndians_(this->fileSize2); + } + if (this->fileVersion >= 3) + { + if (!pReader->Read(-1, 4, &this->unknown2)) + goto __goto_readerror; + this->unknown2 = SwapEndians(this->unknown2); + } + if (!pReader->Read(-1, 1, &this->unknown3)) + goto __goto_readerror; + return true; + + __goto_readerror: + if (errorLogger) errorLogger("AssetBundleHeader : A file read error occured!"); + return false; + + __goto_outofmemory: + if (errorLogger) errorLogger("AssetBundleHeader : Out of memory!"); + return false; +} +ASSETSTOOLS_API bool AssetBundleHeader03::Write(IAssetsWriter *pWriter, QWORD &curFilePos, AssetsFileVerifyLogger errorLogger) +{ + uint32_t dwTmp; + size_t curStrLen; + if (!pWriter->Write(curFilePos, 9, &this->signature)) + goto __goto_writeerror; + curFilePos += 9; + dwTmp = SwapEndians(this->fileVersion); + if (!pWriter->Write(-1, 4, &dwTmp)) + goto __goto_writeerror; + curFilePos += 4; + + curStrLen = strlen(this->minPlayerVersion)+1; + curFilePos += curStrLen; + if (!pWriter->Write(-1, curStrLen, &this->minPlayerVersion)) + goto __goto_writeerror; + curFilePos += curStrLen; + curStrLen = strlen(this->fileEngineVersion)+1; + if (!pWriter->Write(-1, curStrLen, &this->fileEngineVersion)) + goto __goto_writeerror; + + dwTmp = SwapEndians(this->minimumStreamedBytes); + if (!pWriter->Write(-1, 4, &dwTmp)) + goto __goto_writeerror; + curFilePos += 4; + dwTmp = SwapEndians(this->bundleDataOffs); + if (!pWriter->Write(-1, 4, &dwTmp)) + goto __goto_writeerror; + curFilePos += 4; + dwTmp = SwapEndians(this->numberOfAssetsToDownload); + if (!pWriter->Write(-1, 4, &dwTmp)) + goto __goto_writeerror; + curFilePos += 4; + dwTmp = SwapEndians(this->blockCount); + if (!pWriter->Write(-1, 4, &dwTmp)) + goto __goto_writeerror; + curFilePos += 4; + for (uint32_t i = 0; i < blockCount; i++) + { + dwTmp = SwapEndians(this->pBlockList[i].compressed); + if (!pWriter->Write(-1, 4, &dwTmp)) + goto __goto_writeerror; + curFilePos += 4; + dwTmp = SwapEndians(this->pBlockList[i].uncompressed); + if (!pWriter->Write(-1, 4, &dwTmp)) + goto __goto_writeerror; + curFilePos += 4; + } + if (this->fileVersion >= 2) + { + dwTmp = SwapEndians(this->fileSize2); + if (!pWriter->Write(-1, 4, &dwTmp)) + goto __goto_writeerror; + curFilePos += 4; + } + if (this->fileVersion >= 3) + { + dwTmp = SwapEndians(this->unknown2); + if (!pWriter->Write(-1, 4, &dwTmp)) + goto __goto_writeerror; + curFilePos += 4; + } + if (!pWriter->Write(-1, 1, &this->unknown3)) + goto __goto_writeerror; + curFilePos++; + return true; + __goto_writeerror: + if (errorLogger) errorLogger("AssetBundleHeader : A file write error occured!"); + return false; +} + +ASSETSTOOLS_API QWORD AssetBundleDirectoryInfo06::GetAbsolutePos(AssetBundleHeader06 *pHeader) +{ + return (this->offset + pHeader->GetFileDataOffset()); +} +ASSETSTOOLS_API QWORD AssetBundleDirectoryInfo06::GetAbsolutePos(class AssetBundleFile *pFile) +{ + return GetAbsolutePos(&pFile->bundleHeader6); +} + +ASSETSTOOLS_API unsigned int AssetBundleEntry::GetAbsolutePos(AssetBundleHeader03 *pHeader)//, uint32_t listIndex) +{ + unsigned int ret = (offset + pHeader->bundleDataOffs); + /*for (uint32_t i = 0; i < listIndex; i++) + { + ret += pHeader->pLevelList[i].uncompressed; + }*/ + return ret; +} +ASSETSTOOLS_API unsigned int AssetBundleEntry::GetAbsolutePos(class AssetBundleFile *pFile)//, uint32_t listIndex) +{ + return GetAbsolutePos(&pFile->bundleHeader3);//, listIndex); +} +ASSETSTOOLS_API bool AssetsList::Read(IAssetsReader *pReader, QWORD &curFilePos, AssetsFileVerifyLogger errorLogger) +{ + this->pos = (uint32_t)curFilePos; + uint32_t dwTmp; + if (!pReader->Read(curFilePos, 4, &dwTmp)) + goto __goto_readerror; + curFilePos += 4; + this->count = SwapEndians(dwTmp); + + for (uint32_t i = this->count; i < allocatedCount; i++) + { + free(ppEntries[i]); + } + ppEntries = (AssetBundleEntry**)realloc(ppEntries, this->count * sizeof(AssetBundleEntry*)); + if (ppEntries == NULL) + goto __goto_outofmemory; + for (uint32_t i = allocatedCount; i < this->count; i++) + ppEntries[i] = NULL; + + for (unsigned int i = 0; i < this->count; i++) + { + char nameBuffer[40]; + bool eosFound = false; size_t strLen = 0; + while (!eosFound) + { + QWORD nRead = pReader->Read(-1, 40, &nameBuffer); + if (!nRead) + goto __goto_readerror; + for (size_t i = 0; i < (size_t)nRead; i++) + { + if (nameBuffer[i] == 0) + { + eosFound = true; + strLen += (i+1); + break; + } + } + if (!eosFound) + strLen += (size_t)nRead; + } + ppEntries[i] = (AssetBundleEntry*)realloc(ppEntries[i], sizeof(AssetBundleEntry) - 1 + strLen); + if (ppEntries[i] == NULL) + goto __goto_outofmemory; + if (pReader->Read(curFilePos, strLen, ppEntries[i]->name) != strLen) + goto __goto_readerror; + ppEntries[i]->name[strLen-1] = 0; + if (!pReader->Read(-1, 4, &ppEntries[i]->offset)) + goto __goto_readerror; + SwapEndians_(ppEntries[i]->offset); + if (!pReader->Read(-1, 4, &ppEntries[i]->length)) + goto __goto_readerror; + SwapEndians_(ppEntries[i]->length); + curFilePos += (strLen + 4 + 4); + } + allocatedCount = this->count; + return true; + + __goto_readerror: + if (errorLogger) errorLogger("AssetsList : A file read error occured!"); + return false; + __goto_outofmemory: + if (errorLogger) errorLogger("AssetsList : Out of memory!"); + return false; +} +ASSETSTOOLS_API void AssetsList::Free() +{ + for (uint32_t i = 0; i < allocatedCount; i++) + { + free(ppEntries[i]); + } + if (ppEntries) + free(ppEntries); + ppEntries = NULL; + allocatedCount = 0; +} + +ASSETSTOOLS_API bool AssetsList::Write(IAssetsWriter *pWriter, QWORD &curFilePos, AssetsFileVerifyLogger errorLogger) +{ + uint32_t dwTmp; + dwTmp = SwapEndians(this->count); + if (!pWriter->Write(curFilePos, 4, &dwTmp)) + goto __goto_writeerror; + curFilePos += 4; + + for (unsigned int i = 0; i < this->count; i++) + { + uint32_t nameStringLen = (uint32_t)strlen(ppEntries[i]->name)+1; + if (!pWriter->Write(-1, nameStringLen, &ppEntries[i]->name)) + goto __goto_writeerror; + curFilePos += nameStringLen; + dwTmp = SwapEndians(ppEntries[i]->offset); + if (!pWriter->Write(-1, 4, &dwTmp)) + goto __goto_writeerror; + dwTmp = SwapEndians(ppEntries[i]->length); + if (!pWriter->Write(-1, 4, &dwTmp)) + goto __goto_writeerror; + curFilePos += 8; + } + + __goto_writeerror: + if (errorLogger) errorLogger("AssetsList : A file write error occured!"); + return false; +} +ASSETSTOOLS_API bool AssetsList::Write(IAssetsReader *pReader, + IAssetsWriter *pWriter, bool doWriteAssets, QWORD &curFilePos, QWORD *curWritePos, + AssetsFileVerifyLogger errorLogger) +{ + uint32_t estimatedBeginOffs, curEstimatedOffset; + QWORD writePos = curWritePos ? (*curWritePos) : (QWORD)-1; + uint32_t dwTmp; + dwTmp = SwapEndians(this->count); + if (!pWriter->Write(writePos, 4, &dwTmp)) + goto __goto_writeerror; + estimatedBeginOffs = 4; + for (unsigned int i = 0; i < this->count; i++) + { + estimatedBeginOffs += (uint32_t)strlen(ppEntries[i]->name)+1 + 8; + } + curEstimatedOffset = estimatedBeginOffs; + if (curWritePos) + *curWritePos += curEstimatedOffset; + + for (unsigned int i = 0; i < this->count; i++) + { + uint32_t nameStringLen = (uint32_t)strlen(ppEntries[i]->name)+1; + if (!pWriter->Write(-1, nameStringLen, &ppEntries[i]->name)) + goto __goto_writeerror; + curEstimatedOffset = (curEstimatedOffset + 3) & (~3); + dwTmp = SwapEndians(curEstimatedOffset/*ppEntries[i]->offset*/); + if (!pWriter->Write(-1, 4, &dwTmp)) + goto __goto_writeerror; + dwTmp = SwapEndians(ppEntries[i]->length); + if (!pWriter->Write(-1, 4, &dwTmp)) + goto __goto_writeerror; + curEstimatedOffset += ppEntries[i]->length; + } + + if (doWriteAssets) + { + uint8_t _stackTransferBuffer[256]; + uint32_t transferBufferLen = 256; + uint8_t *pTransferBuffer = (uint8_t*)malloc(1024 * 1024); + if (!pTransferBuffer) + pTransferBuffer = _stackTransferBuffer; + else + transferBufferLen = 1024 * 1024; + + QWORD relPos = estimatedBeginOffs; + + for (unsigned int i = 0; i < this->count; i++) + { + uint32_t nullCount = 3 - (((relPos & 3) - 1) & 3); + dwTmp = 0; + pWriter->Write(-1, nullCount, &dwTmp); + uint32_t remaining = ppEntries[i]->length; + bool setReadPos = false; + while (remaining > transferBufferLen) + { + pReader->Read(setReadPos ? -1 : (this->pos + ppEntries[i]->offset), transferBufferLen, pTransferBuffer); + pWriter->Write(-1, transferBufferLen, pTransferBuffer); + setReadPos = true; + } + if (remaining) + { + pReader->Read(setReadPos ? -1 : (this->pos + ppEntries[i]->offset), remaining, pTransferBuffer); + pWriter->Write(-1, remaining, pTransferBuffer); + } + relPos += nullCount + ppEntries[i]->length; + } + if (pTransferBuffer != _stackTransferBuffer) + free(pTransferBuffer); + + curFilePos += relPos; + } + return true; + + __goto_writeerror: + if (errorLogger) errorLogger("AssetsList : A file write error occured!"); + return false; +} + +ASSETSTOOLS_API AssetBundleFile::AssetBundleFile() +{ + bundleHeader3.blockCount = 0; + bundleHeader3.pBlockList = NULL; + assetsLists3 = NULL; +} +ASSETSTOOLS_API AssetBundleFile::~AssetBundleFile() +{ + this->Close(); +} +ASSETSTOOLS_API void AssetBundleFile::Close() +{ + if (bundleHeader3.fileVersion < 6) + { + if (bundleHeader3.blockCount > 0) + { + free(bundleHeader3.pBlockList); + bundleHeader3.pBlockList = NULL; + } + if (assetsLists3) + { + bundleHeader3.blockCount = 0; + assetsLists3->Free(); + free(assetsLists3); + } + assetsLists3 = NULL; + } + else + { + if (bundleInf6) + { + bundleInf6->Free(); + free(bundleInf6); + } + bundleInf6 = NULL; + } +} +static void *SzAlloc(void *p, size_t size) { (p); return malloc(size); } +static void SzFree(void *p, void *address) { (p); free(address); } +static ISzAlloc g_Alloc = { SzAlloc, SzFree }; + +static SRes LZMADecompressBlock(QWORD *fileInPos, QWORD *fileOutPos, QWORD compressedSize, IAssetsReader *pReader, IAssetsWriter *pWriter) +{ + #define SizePerBuffer (1024*1024) + unsigned char header[LZMA_PROPS_SIZE/* + 8*/]; + if (pReader->Read(*fileInPos, sizeof(header), header) < sizeof(header)) + { + return SZ_ERROR_FAIL; + } + CLzmaDec dec; + LzmaDec_Construct(&dec); + SRes res = LzmaDec_Allocate(&dec, header, LZMA_PROPS_SIZE, &g_Alloc); + if (res != SZ_OK) + { + return SZ_ERROR_FAIL; + } + LzmaDec_Init(&dec); + QWORD compProcessCount = sizeof(header); + QWORD decompCount = 0; + void *pCompBuf = malloc((compressedSize > SizePerBuffer) ? SizePerBuffer : compressedSize); + if (!pCompBuf) + { + LzmaDec_Free(&dec, &g_Alloc); + return SZ_ERROR_MEM; + } + void *pDecompBuf = malloc(SizePerBuffer); + if (!pDecompBuf) + { + LzmaDec_Free(&dec, &g_Alloc); + free(pCompBuf); + return SZ_ERROR_MEM; + } + + while (compProcessCount < compressedSize) + { + QWORD bytesToProcess = compressedSize - compProcessCount; + bytesToProcess = bytesToProcess > SizePerBuffer ? SizePerBuffer : bytesToProcess; + QWORD bytesAvailable = pReader->Read(*fileInPos + compProcessCount, bytesToProcess, pCompBuf); + if (bytesAvailable != bytesToProcess) + { + LzmaDec_Free(&dec, &g_Alloc); + free(pCompBuf); + free(pDecompBuf); + return SZ_ERROR_INPUT_EOF; + } + + SizeT compLen = bytesAvailable; + SizeT decompLen = SizePerBuffer; + ELzmaStatus stat = LZMA_STATUS_NOT_SPECIFIED; + res = LzmaDec_DecodeToBuf(&dec, (Byte*)pDecompBuf, &decompLen, (Byte*)pCompBuf, &compLen, + LZMA_FINISH_ANY, &stat); + //if ((res == SZ_OK) && !decompLen) + // res = SZ_ERROR_FAIL; + if (res != SZ_OK) + { + LzmaDec_Free(&dec, &g_Alloc); + free(pCompBuf); + free(pDecompBuf); + return res; + } + compProcessCount += compLen; + if (pWriter->Write(*fileOutPos + decompCount, decompLen, pDecompBuf) < decompLen) + { + LzmaDec_Free(&dec, &g_Alloc); + free(pCompBuf); + free(pDecompBuf); + return SZ_ERROR_OUTPUT_EOF; + } + decompCount += decompLen; + } + LzmaDec_Free(&dec, &g_Alloc); + free(pCompBuf); + free(pDecompBuf); + *fileInPos += compProcessCount; + *fileOutPos += decompCount; + return SZ_OK; +} +struct LZ4DecompressBlock_Read_User_t +{ + QWORD dataPos; + QWORD dataSize; + IAssetsReader *pReader; +}; +static int LZ4DecompressBlock_Read(void *buffer, int size, LZ4e_instream_t *stream) +{ + if (size <= 0) return 0; + LZ4DecompressBlock_Read_User_t *user = (LZ4DecompressBlock_Read_User_t*)stream->user; + + QWORD readSize = (QWORD)size; + if (stream->pos >= user->dataSize) return 0; + if ((stream->pos + size) > user->dataSize) + readSize = user->dataSize - stream->pos; + + return (int) user->pReader->Read(stream->pos + user->dataPos, readSize, buffer, false); +} +static int LZ4DecompressBlock_Write(const void *buffer, int size, LZ4e_outstream_t *stream) +{ + if (size <= 0) return 0; + const uint8_t *pCur = (const uint8_t*)buffer; int remaining = size; int lastRead; + while (remaining && (lastRead = (int) ((IAssetsWriter*)stream->user)->Write((QWORD)size, buffer)) > 0) + { + pCur += lastRead; + remaining -= lastRead; + } + return size - remaining;//(int) ((IAssetsWriter*)stream->user)->Write((QWORD)size, buffer); +} +static bool LZ4DecompressBlock(QWORD *fileInPos, QWORD *fileOutPos, QWORD compressedSize, IAssetsReader *pReader, IAssetsWriter *pWriter) +{ + static const size_t compBufferSize = (1*1024*1024); + static const size_t decompBufferSize = (1*1024*1024); + LZ4DecompressBlock_Read_User_t inuser; + LZ4e_instream_t instream; + LZ4e_outstream_t outstream; + void *pCompBuf = malloc(compBufferSize + decompBufferSize); + if (!pCompBuf) + return false; + void *pDecompBuf = ((uint8_t*)pCompBuf) + compBufferSize; + + instream.pos = 0; + instream.callback = LZ4DecompressBlock_Read; + instream.user = &inuser; + inuser.dataPos = *fileInPos; + inuser.dataSize = compressedSize; + inuser.pReader = pReader; + + outstream.callback = LZ4DecompressBlock_Write; + outstream.user = pWriter; + + QWORD oldWriterPos = *fileOutPos; + pWriter->Tell(oldWriterPos); + + bool ret = LZ4e_decompress_safe((char*)pCompBuf, (char*)pDecompBuf, compBufferSize, decompBufferSize, &instream, &outstream) > 0; + + QWORD newWriterPos = oldWriterPos; + pWriter->Tell(newWriterPos); + *fileOutPos += (newWriterPos - oldWriterPos); + + *fileInPos += compressedSize; + + free(pCompBuf); + return ret; +} +static bool LZ4CompressBlock(QWORD *fileInPos, QWORD *fileOutPos, QWORD decompressedSize, IAssetsReader *pReader, IAssetsWriter *pWriter) +{ + if (decompressedSize > 0x7FFFFFFF) + return false; + LZ4DecompressBlock_Read_User_t inuser; + LZ4e_instream_t instream; + LZ4e_outstream_t outstream; + + instream.pos = 0; + instream.callback = LZ4DecompressBlock_Read; + instream.user = &inuser; + inuser.dataPos = *fileInPos; + inuser.dataSize = decompressedSize; + inuser.pReader = pReader; + + outstream.callback = LZ4DecompressBlock_Write; + outstream.user = pWriter; + + QWORD oldWriterPos = *fileOutPos; + pWriter->Tell(oldWriterPos); + + bool ret = LZ4e_compress_fast(&instream, &outstream, 1, (unsigned int)decompressedSize) > 0; + + QWORD newWriterPos = oldWriterPos; + pWriter->Tell(newWriterPos); + *fileOutPos += (newWriterPos - oldWriterPos); + + *fileInPos += decompressedSize; + return ret; +} + +ASSETSTOOLS_API bool AssetBundleFile::Unpack(IAssetsReader *pReader, IAssetsWriter *pWriter) +{ + if (!Read(pReader, NULL, true)) + return false; + //bundleHeader6.fileVersion == bundleHeader3.fileVersion + if (bundleHeader6.fileVersion >= 6) + { + uint8_t compressionType = (bundleHeader6.flags & 0x3F); + if (compressionType < 4) + { + QWORD curFilePos = 0; + QWORD curUpFilePos = 0; + if (bundleHeader6.flags & 0x100) //originally was UnityWeb + strcpy(bundleHeader6.signature, "UnityWeb"); + bundleHeader6.Write(pWriter, curFilePos); + if (bundleHeader6.flags & 0x100) //originally was UnityWeb + strcpy(bundleHeader6.signature, "UnityFS"); + curFilePos = bundleHeader6.GetBundleInfoOffset(); + curUpFilePos = curFilePos; + + void *fileTableBuf = malloc(bundleHeader6.decompressedSize); + if (!fileTableBuf) + return false; + IAssetsWriter *pTempWriter = Create_AssetsWriterToMemory(fileTableBuf, bundleHeader6.decompressedSize); + if (!pTempWriter) + { + free(fileTableBuf); + return false; + } + curUpFilePos = 0; + bool decompressSuccess = false; + switch (compressionType) + { + case 0: + if (bundleHeader6.compressedSize == bundleHeader6.decompressedSize + && pReader->Read(curFilePos, bundleHeader6.compressedSize, fileTableBuf) == bundleHeader6.decompressedSize) + decompressSuccess = true; + break; + case 1: //LZMA + if (LZMADecompressBlock(&curFilePos, &curUpFilePos, bundleHeader6.compressedSize, pReader, pTempWriter) == SZ_OK) + decompressSuccess = true; + break; + case 2: case 3: //LZ4 + if (LZ4DecompressBlock(&curFilePos, &curUpFilePos, bundleHeader6.compressedSize, pReader, pTempWriter)) + decompressSuccess = true; + break; + } + Free_AssetsWriter(pTempWriter); + if (!decompressSuccess || curUpFilePos != bundleHeader6.decompressedSize) + { + free(fileTableBuf); + return false; + } + + IAssetsReader *pTempReader = Create_AssetsReaderFromMemory(fileTableBuf, bundleHeader6.decompressedSize, false); + if (!pTempReader) + { + free(fileTableBuf); + return false; + } + AssetBundleBlockAndDirectoryList06 list = {0}; + bool res = list.Read(0, pTempReader); + Free_AssetsReader(pTempReader); + free(fileTableBuf); + curFilePos = bundleHeader6.GetFileDataOffset(); + if (bundleHeader6.flags & 0x80) + curUpFilePos = bundleHeader6.GetFileDataOffset(); + else + { + curUpFilePos = bundleHeader6.GetBundleInfoOffset(); + QWORD oldUpFilePos = curUpFilePos; + list.Write(pWriter, curUpFilePos); + bundleHeader6.decompressedSize = (uint32_t)(curUpFilePos - oldUpFilePos); + } + + if (!res) + { + free(fileTableBuf); + return false; + } + for (uint32_t i = 0; i < list.blockCount; i++) + { + //QWORD oldUpFilePos = curUpFilePos; + switch (list.blockInf[i].GetCompressionType()) + { + case 0: //none + if (list.blockInf[i].compressedSize == list.blockInf[i].decompressedSize) + { + uint32_t copiedCount = 0; + uint32_t copyBufLen = 1024 * 1024; + void *copyBuf = malloc(1024 * 1024); uint8_t tmp[256]; + if (!copyBuf) + { + copyBuf = tmp; copyBufLen = 256; + } + while (copiedCount < list.blockInf[i].compressedSize) + { + uint32_t bytesToCopy = copyBufLen; + if (bytesToCopy > (list.blockInf[i].compressedSize - copiedCount)) + bytesToCopy = (list.blockInf[i].compressedSize - copiedCount); + uint32_t bytesRead = (uint32_t)pReader->Read(curFilePos, bytesToCopy, copyBuf); + uint32_t bytesWritten = (uint32_t)pWriter->Write(curUpFilePos, bytesRead, copyBuf); + curFilePos += bytesRead; + curUpFilePos += bytesWritten; + if (bytesRead != bytesToCopy || bytesWritten != bytesRead) + { + if (copyBuf != tmp) + free(copyBuf); + list.Free(); + return false; + //break; + } + copiedCount += bytesRead; + } + if (copyBuf != tmp) + free(copyBuf); + } + else + { + list.Free(); + return false; + } + break; + case 1: //LZMA + if (LZMADecompressBlock(&curFilePos, &curUpFilePos, list.blockInf[i].compressedSize, pReader, pWriter) != SZ_OK) + { + list.Free(); + return false; + } + break; + case 2: + case 3: + if (!LZ4DecompressBlock(&curFilePos, &curUpFilePos, list.blockInf[i].compressedSize, pReader, pWriter)) + { + list.Free(); + return false; + } + break; + default: + { + list.Free(); + return false; + } + break; + } + + list.blockInf[i].compressedSize = list.blockInf[i].decompressedSize; + list.blockInf[i].flags &= ~(0x3F); //compression = 0 + //list.blockInf[i].decompressedSize = curUpFilePos - oldUpFilePos; + } + if (bundleHeader6.flags & 0x80) + { + QWORD oldUpFilePos = curUpFilePos; + list.Write(pWriter, curUpFilePos); + bundleHeader6.decompressedSize = (uint32_t)(curUpFilePos - oldUpFilePos); + } + bundleHeader6.totalFileSize = curUpFilePos; + bundleHeader6.compressedSize = bundleHeader6.decompressedSize; + curUpFilePos = 0; + bundleHeader6.flags &= ~(0x3F); + + if (bundleHeader6.flags & 0x100) //originally was UnityWeb + strcpy(bundleHeader6.signature, "UnityWeb"); + bundleHeader6.Write(pWriter, curUpFilePos); + if (bundleHeader6.flags & 0x100) //originally was UnityWeb + strcpy(bundleHeader6.signature, "UnityFS"); + if (bundleHeader6.flags & 0x80) + curUpFilePos = bundleHeader6.GetBundleInfoOffset(); + list.Write(pWriter, curUpFilePos); + list.Free(); + return true; + } + } + else if (bundleHeader3.fileVersion == 3) + { + if (!strcmp(bundleHeader3.signature, "UnityWeb")) + { + if ((bundleHeader3.blockCount == 0) || (bundleHeader3.pBlockList[0].compressed < (LZMA_PROPS_SIZE+8))) + return false; + #define SizePerBuffer (1024*1024) + void *buffers = malloc(2 * SizePerBuffer); + if (!buffers) + return false; + uint8_t *compBuffer = (uint8_t*)buffers; + uint8_t *decompBuffer = &compBuffer[SizePerBuffer]; + QWORD curFilePos = 0; + QWORD curUpFilePos = 0; + bundleHeader3.Write(pWriter, curFilePos); + curFilePos = bundleHeader3.bundleDataOffs; + curUpFilePos = bundleHeader3.bundleDataOffs; + + /*QWORD compressedSize = 0; + QWORD decompressedSize = 0; + for (uint32_t i = 0; i < bundleHeader.levelCount; i++) + { + if (bundleHeader.pLevelList[i].uncompressed > decompressedSize) + { + compressedSize = bundleHeader.pLevelList[i].compressed; + decompressedSize = bundleHeader.pLevelList[i].uncompressed; + } + }*/ + QWORD decompCount = 0; + //for (uint32_t i = 0; i < bundleHeader.bundleCount; i++) + { + unsigned char header[LZMA_PROPS_SIZE + 8]; + if (!pReader->Read(curFilePos, sizeof(header), header)) + { + free(buffers); + return false; + } + CLzmaDec dec; + LzmaDec_Construct(&dec); + SRes res = LzmaDec_Allocate(&dec, header, LZMA_PROPS_SIZE, &g_Alloc); + if (res != SZ_OK) + { + free(buffers); + return false; + } + LzmaDec_Init(&dec); + QWORD compProcessCount = 0; + + AssetsList decompEntryList; ZeroMemory(&decompEntryList, sizeof(AssetsList)); + { + //read the entry list + QWORD bytesToProcess = bundleHeader3.pBlockList[0].compressed - compProcessCount; + bytesToProcess = bytesToProcess > SizePerBuffer ? SizePerBuffer : bytesToProcess; + pReader->Read(curFilePos + sizeof(header) + compProcessCount, bytesToProcess, compBuffer); + SizeT compLen = bytesToProcess; + SizeT decompLen = SizePerBuffer; + ELzmaStatus stat = LZMA_STATUS_NOT_SPECIFIED; + res = LzmaDec_DecodeToBuf(&dec, decompBuffer, &decompLen, compBuffer, &compLen, + LZMA_FINISH_ANY, &stat); + if ((compLen == 0) || (res != SZ_OK)) + { + LzmaDec_Free(&dec, &g_Alloc); + free(buffers); + return false; + } + IAssetsReader *pMemoryReader = Create_AssetsReaderFromMemory(decompBuffer, decompLen, false); + if (pMemoryReader == NULL) + { + LzmaDec_Free(&dec, &g_Alloc); + free(buffers); + return false; + } + QWORD _qwTmp = 0; + decompEntryList.Read(pMemoryReader, _qwTmp); + Free_AssetsReader(pMemoryReader); + + //sort the entries (this one is slow but the entry lists aren't that large + bool repeat = false; + for (uint32_t i = 0; i < decompEntryList.count; i++) + { + if (repeat) + i--; + repeat = false; + for (uint32_t k = i+1; k < decompEntryList.count; k++) + { + if (decompEntryList.ppEntries[k]->offset < decompEntryList.ppEntries[i]->offset) + { + AssetBundleEntry *pEntry = decompEntryList.ppEntries[k]; + memcpy(&decompEntryList.ppEntries[i+1], &decompEntryList.ppEntries[i], (k - i) * sizeof(void*)); + decompEntryList.ppEntries[i] = pEntry; + //repeat = true; + //firstFileOffs = decompEntryList.ppEntries[i]->offset; + } + } + } + pWriter->Write(curUpFilePos, decompEntryList.ppEntries[0]->offset, decompBuffer); + + //get the exact amount of compressed bytes to the first file + //reset the decoder (it possibly isn't able to revert its position) + LzmaDec_Free(&dec, &g_Alloc); + LzmaDec_Construct(&dec); + LzmaDec_Allocate(&dec, header, LZMA_PROPS_SIZE, &g_Alloc); + LzmaDec_Init(&dec); + + compLen = bytesToProcess; + decompLen = decompEntryList.ppEntries[0]->offset; + stat = LZMA_STATUS_NOT_SPECIFIED; + res = LzmaDec_DecodeToBuf(&dec, decompBuffer, &decompLen, compBuffer, &compLen, + LZMA_FINISH_ANY, &stat); + + compProcessCount += compLen; + decompCount += decompLen; + } + + uint32_t levelIndex = 0; + for (uint32_t i = 0; i < decompEntryList.count; i++) + { + QWORD decompressBytes = decompEntryList.ppEntries[i]->length; + if ((i+1) < decompEntryList.count) + { + //add empty bytes to read in between (if there are any) + decompressBytes += decompEntryList.ppEntries[i+1]->offset - + (decompEntryList.ppEntries[i]->offset + decompEntryList.ppEntries[i]->length); + } + do + { + ZeroMemory(buffers, 2 * SizePerBuffer); + //QWORD bytesToProcess = compressedSize - compProcessCount; + //bytesToProcess = bytesToProcess > SizePerBuffer ? SizePerBuffer : bytesToProcess; + QWORD bytesToDecompress = (decompEntryList.ppEntries[i]->offset + decompressBytes) - decompCount; + bytesToDecompress = bytesToDecompress > SizePerBuffer ? SizePerBuffer : bytesToDecompress; + pReader->Read(curFilePos + sizeof(header) + compProcessCount, SizePerBuffer, compBuffer); + SizeT compLen = SizePerBuffer;//bytesToProcess; + SizeT decompLen = bytesToDecompress; + ELzmaStatus stat = LZMA_STATUS_NOT_SPECIFIED; + res = LzmaDec_DecodeToBuf(&dec, decompBuffer, &decompLen, compBuffer, &compLen, + LZMA_FINISH_ANY, &stat); + if (compLen == 0 || res != SZ_OK) + { + LzmaDec_Free(&dec, &g_Alloc); + free(buffers); + return false; + } + pWriter->Write(curUpFilePos + decompCount, decompLen, decompBuffer); + compProcessCount += compLen; + decompCount += decompLen; + } while (decompCount < (decompEntryList.ppEntries[i]->offset + decompressBytes)); + if ((1 == decompEntryList.count || + !strcmp(decompEntryList.ppEntries[i]->name, "mainData") || + !strncmp(decompEntryList.ppEntries[i]->name, "level", 5)) && + (levelIndex < this->bundleHeader3.blockCount)) + { + this->bundleHeader3.pBlockList[levelIndex].compressed = + this->bundleHeader3.pBlockList[levelIndex].uncompressed = + (decompEntryList.ppEntries[i]->offset + decompEntryList.ppEntries[i]->length); + levelIndex++; + } + } + LzmaDec_Free(&dec, &g_Alloc); + decompEntryList.Free(); + //curFilePos += bundleHeader.pBundleList[i].compressed; + //curUpFilePos += bundleHeader.pBundleList[i].uncompressed; + } + free(buffers); + AssetBundleHeader03 headerCopy; + memcpy(&headerCopy, &this->bundleHeader3, sizeof(AssetBundleHeader03)); + strcpy(headerCopy.signature, "UnityRaw"); + headerCopy.minimumStreamedBytes = headerCopy.fileSize2 = headerCopy.bundleDataOffs + (uint32_t)decompCount; + /*for (uint32_t i = 0; i < bundleHeader.bundleCount; i++) + { + headerCopy.pBundleList[i].compressed = headerCopy.pBundleList[i].uncompressed; + }*/ + /*if (bundleHeader.bundleCount) + { + bundleHeader.bundleCount = 1; + headerCopy.pBundleList[0].compressed = headerCopy.pBundleList[0].uncompressed = decompressedSize; + }*/ + curFilePos = 0; + headerCopy.Write(pWriter, curFilePos); + return true; + } + } + return false; +} + +typedef struct +{ + ISeqInStream SeqInStream; + std::mutex critSect; + std::mutex critSectReading; //To sync freeing the stream. + HANDLE hDataEvent; HANDLE hRequestDataEvent; //TODO: Replace by some std mechanism. + bool isDone; + size_t bufferSize; + uint8_t *dataBuffer; +} LZMAInStream; +static SRes LZMAInStream_Read(void *p, void *buf, size_t *size) +{ + LZMAInStream *ctx = (LZMAInStream*)p; + if (ctx->isDone) + { + *size = 0; + return SZ_OK; + } + size_t readSize = 0; + + std::scoped_lock critSectReadingLock(ctx->critSectReading); + { + std::unique_lock critSectLock(ctx->critSect); + ResetEvent(ctx->hDataEvent); + size_t toCopy = std::min(ctx->bufferSize, (*size) - readSize); + memcpy(&((uint8_t*)buf)[readSize], ctx->dataBuffer, toCopy); + readSize += toCopy; + + uint8_t *bufferTemp = new uint8_t[ctx->bufferSize - toCopy]; + memcpy(bufferTemp, &ctx->dataBuffer[toCopy], ctx->bufferSize - toCopy); + if (ctx->dataBuffer) delete[] ctx->dataBuffer; + ctx->dataBuffer = bufferTemp; + ctx->bufferSize -= toCopy; + critSectLock.unlock(); + if (readSize == 0)//< (*size)) + { + SetEvent(ctx->hRequestDataEvent); + if ((WaitForSingleObjectEx(ctx->hDataEvent, INFINITE, FALSE) != WAIT_OBJECT_0) || ctx->isDone) + { + *size = readSize; + return SZ_OK; + } + critSectLock.lock(); + size_t toCopy = std::min(ctx->bufferSize, (*size) - readSize); + memcpy(&((uint8_t*)buf)[readSize], ctx->dataBuffer, toCopy); + readSize += toCopy; + + uint8_t *bufferTemp = new uint8_t[ctx->bufferSize - toCopy]; + memcpy(bufferTemp, &ctx->dataBuffer[toCopy], ctx->bufferSize - toCopy); + if (ctx->dataBuffer) delete[] ctx->dataBuffer; + ctx->dataBuffer = bufferTemp; + ctx->bufferSize -= toCopy; + } + } + *size = readSize; + return SZ_OK; +} +static void LZMAInStream_Init(LZMAInStream *pStream) +{ + pStream->SeqInStream.Read = LZMAInStream_Read; + pStream->hDataEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + pStream->hRequestDataEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + pStream->isDone = false; + pStream->bufferSize = 0; + pStream->dataBuffer = NULL; +} +static void LZMAInStream_Free(LZMAInStream *pStream) +{ + pStream->isDone = true; + SetEvent(pStream->hDataEvent); + SetEvent(pStream->hRequestDataEvent); + Sleep(0); //Prevent entering the critical section between LZMAInStream_Read's isDone check and its EnterCriticalSection. + pStream->critSectReading.lock(); + pStream->critSectReading.unlock(); + CloseHandle(pStream->hDataEvent); + CloseHandle(pStream->hRequestDataEvent); + if (pStream->dataBuffer) + { + delete[] pStream->dataBuffer; + pStream->dataBuffer = NULL; + } + pStream->bufferSize = 0; +} +static void LZMAInStream_Put(LZMAInStream *pStream, void *data, size_t size, bool waitForData) +{ + if (pStream->isDone) + return; + std::unique_lock critSectLock(pStream->critSect); + uint8_t *bufferTemp = new uint8_t[pStream->bufferSize + size]; + memcpy(bufferTemp, pStream->dataBuffer, pStream->bufferSize); + memcpy(&bufferTemp[pStream->bufferSize], data, size); + if (pStream->dataBuffer) delete[] pStream->dataBuffer; + pStream->dataBuffer = bufferTemp; + pStream->bufferSize += size; + SetEvent(pStream->hDataEvent); + critSectLock.unlock(); + if (waitForData) + WaitForSingleObject(pStream->hRequestDataEvent, INFINITE); +} + +typedef struct +{ + ISeqOutStream SeqOutStream; + IAssetsWriter *pWriter; + QWORD writeOffset; + QWORD writeCount; +} LZMAOutStream; +static size_t LZMAOutStream_Write(void *p, const void *buf, size_t size) +{ + LZMAOutStream *ctx = (LZMAOutStream*)p; + ctx->pWriter->Write(ctx->writeOffset + ctx->writeCount, size, buf); + ctx->writeCount += size; + return size; +} + +struct LZMACompressThreadData +{ + LZMAInStream *pIn; + LZMAOutStream *pOut; + CLzmaEncHandle enc; + SRes result; +}; +static void LZMACompressThread(void *pData) +{ + LZMACompressThreadData *pThreadData = (LZMACompressThreadData*)pData; + pThreadData->result = LzmaEnc_Encode(pThreadData->enc, &pThreadData->pOut->SeqOutStream, &pThreadData->pIn->SeqInStream, NULL, &g_Alloc, &g_Alloc); + //LZMAInStream_Free(pThreadData->pIn); + pThreadData->pIn->isDone = true; + SetEvent(pThreadData->pIn->hRequestDataEvent); +} + + +static SRes LZMACompressBlock(QWORD *fileInPos, QWORD *fileOutPos, QWORD decompressedSize, uint32_t alignmentBytes, IAssetsReader *pReader, IAssetsWriter *pWriter) +{ + #define SizePerBuffer (1024*1024) + CLzmaEncHandle enc = LzmaEnc_Create(&g_Alloc); + if (!enc) + { + return SZ_ERROR_FAIL; + } + + CLzmaEncProps props; + LzmaEncProps_Init(&props); + props.level = 0; + props.writeEndMark = 1; +#ifdef _DEBUG + props.dictSize = 4096; +#else + props.dictSize = 524288; +#endif + props.numThreads = 2; + + SRes res = LzmaEnc_SetProps(enc, &props); + if (res != SZ_OK) + { + LzmaEnc_Destroy(enc, &g_Alloc, &g_Alloc); + return res; + } + + size_t headerSize = LZMA_PROPS_SIZE; + unsigned char header[LZMA_PROPS_SIZE]; + LzmaEnc_WriteProperties(enc, header, &headerSize); + if (res != SZ_OK || headerSize != LZMA_PROPS_SIZE) + { + LzmaEnc_Destroy(enc, &g_Alloc, &g_Alloc); + return SZ_ERROR_FAIL; + } + pWriter->Write(*fileOutPos, headerSize, header); + QWORD decompProcessCount = 0; + void *pDecompBuf = malloc((decompressedSize > SizePerBuffer) ? SizePerBuffer : decompressedSize); + if (!pDecompBuf) + { + LzmaEnc_Destroy(enc, &g_Alloc, &g_Alloc); + return SZ_ERROR_MEM; + } + + LZMAInStream seqInStream; LZMAInStream_Init(&seqInStream); + LZMAOutStream seqOutStream = {&LZMAOutStream_Write, pWriter, *fileOutPos, headerSize}; + LZMACompressThreadData threadData; + threadData.pIn = &seqInStream; + threadData.pOut = &seqOutStream; + threadData.enc = enc; + threadData.result = SZ_OK; + + std::future compressFuture = std::async(LZMACompressThread, &threadData); + + if (alignmentBytes > 0) + { + uint8_t *alignment = new uint8_t[alignmentBytes]; + memset(alignment, 0, alignmentBytes); + LZMAInStream_Put(&seqInStream, alignment, alignmentBytes, true); + delete[] alignment; + } + + SRes ret = SZ_OK; + while (decompProcessCount < decompressedSize) + { + QWORD bytesToProcess = decompressedSize - decompProcessCount; + bytesToProcess = bytesToProcess > SizePerBuffer ? SizePerBuffer : bytesToProcess; + QWORD bytesAvailable = pReader->Read(*fileInPos + decompProcessCount, bytesToProcess, pDecompBuf); + if (bytesAvailable != bytesToProcess) + { + ret = SZ_ERROR_INPUT_EOF; + goto _GOTO_DO_RETURN; + } + + LZMAInStream_Put(&seqInStream, pDecompBuf, bytesToProcess, true); + + decompProcessCount += bytesToProcess; + } +_GOTO_DO_RETURN: + free(pDecompBuf); + LZMAInStream_Free(&seqInStream); + assert(compressFuture.valid()); + compressFuture.wait(); + LzmaEnc_Destroy(enc, &g_Alloc, &g_Alloc); + *fileOutPos += threadData.pOut->writeCount; + *fileInPos += decompProcessCount; + return ret; +} +/*static bool LZ4CompressBlocks(QWORD *fileInPos, QWORD *fileOutPos, QWORD decompressedSize, IAssetsReader *pReader, IAssetsWriter *pWriter, + std::vector &blockSizes) +{ + #define SizePerBuffer (2*1024*1024) + LZ4_stream_t *pStream = LZ4_createStream(); + if (!pStream) + return false; + memset(pStream, 0, sizeof(LZ4_stream_t)); + QWORD decompProcessCount = 0; + QWORD compCount = 0; + //pDecompBuf is a ring buffer with elements. LZ4 uses the previous buffers as a dictionary, except the one to be compressed at a time. + size_t decompSizePerBuffer = (decompressedSize > SizePerBuffer) ? SizePerBuffer : decompressedSize; +#define decompBufCount 2 + uint8_t *pDecompBuf = (uint8_t*)malloc(decompBufCount * decompSizePerBuffer); + if (!pDecompBuf) + { + LZ4_freeStream(pStream); + return false; + } + size_t decompBufIndex = 0; + void *pCompBuf = malloc(LZ4_COMPRESSBOUND(SizePerBuffer)); + if (!pCompBuf) + { + LZ4_freeStream(pStream); + free(pDecompBuf); + return false; + } + while (decompProcessCount < decompressedSize) + { + QWORD bytesToProcess = decompressedSize - decompProcessCount; + bytesToProcess = bytesToProcess > SizePerBuffer ? SizePerBuffer : bytesToProcess; + QWORD bytesAvailable = pReader->Read(*fileInPos + decompProcessCount, bytesToProcess, &pDecompBuf[decompBufIndex*decompSizePerBuffer]); + if (bytesAvailable != bytesToProcess) + { + LZ4_freeStream(pStream); + free(pCompBuf); + free(pDecompBuf); + return false; + } + + SizeT decompLen = bytesAvailable; + SizeT compLen = LZ4_COMPRESSBOUND(SizePerBuffer); + compLen = LZ4_compress_fast_continue(pStream, (char*)&pDecompBuf[decompBufIndex*decompSizePerBuffer], (char*)pCompBuf, decompLen, compLen, 1); + if (compLen == 0) + { + LZ4_freeStream(pStream); + free(pCompBuf); + free(pDecompBuf); + return false; + } + AssetBundleOffsetPair sizePair; sizePair.compressed = (uint32_t)compLen; sizePair.uncompressed = (uint32_t)decompLen; + blockSizes.push_back(sizePair); + decompProcessCount += decompLen; + decompBufIndex++; + if (decompBufIndex > decompBufCount) + decompBufIndex = 0; + if (pWriter->Write(*fileOutPos + compCount, compLen, pCompBuf) < compLen) + { + LZ4_freeStream(pStream); + free(pCompBuf); + free(pDecompBuf); + return false; + } + compCount += compLen; + } + LZ4_freeStream(pStream); + free(pCompBuf); + free(pDecompBuf); + *fileInPos += decompProcessCount; + *fileOutPos += compCount; + return true; +}*/ + +static bool CopyBlock(QWORD *fileInPos, QWORD *fileOutPos, QWORD totalSize, IAssetsReader *pReader, IAssetsWriter *pWriter) +{ + #define SizePerBuffer (1024*1024) + QWORD processedSize = 0; + void *pDecompBuf = malloc((totalSize > SizePerBuffer) ? SizePerBuffer : totalSize); + if (!pDecompBuf) + { + return false; + } + + bool ret = true; + while (processedSize < totalSize) + { + QWORD bytesToProcess = totalSize - processedSize; + bytesToProcess = bytesToProcess > SizePerBuffer ? SizePerBuffer : bytesToProcess; + QWORD bytesAvailable = pReader->Read(*fileInPos + processedSize, bytesToProcess, pDecompBuf); + if (bytesAvailable != bytesToProcess) + { + ret = false; + goto _GOTO_DO_RETURN; + } + + QWORD bytesProcessed = pWriter->Write(*fileOutPos + processedSize, bytesToProcess, pDecompBuf); + processedSize += bytesProcessed; + + if (bytesProcessed != bytesToProcess) + { + ret = false; + goto _GOTO_DO_RETURN; + } + } +_GOTO_DO_RETURN: + free(pDecompBuf); + *fileOutPos += processedSize; + *fileInPos += processedSize; + return ret; +} + +ASSETSTOOLS_API bool AssetBundleFile::Pack(IAssetsReader *pReader, IAssetsWriter *pWriter, ECompressionTypes *settings, ECompressionTypes fileTableSettings) +{ + if (!Read(pReader, NULL, false)) + return false; + if (bundleHeader3.fileVersion == 3) + { + if (!strcmp(bundleHeader3.signature, "UnityRaw")) + { + //sort the entries (this one is slow but the entry lists aren't that large) + bool repeat = false; + for (uint32_t i = 0; i < this->assetsLists3[0].count; i++) + { + if (repeat) + i--; + repeat = false; + for (uint32_t k = i+1; k < this->assetsLists3[0].count; k++) + { + if (this->assetsLists3[0].ppEntries[k]->offset < this->assetsLists3[0].ppEntries[i]->offset) + { + AssetBundleEntry *pEntry = this->assetsLists3[0].ppEntries[k]; + memcpy(&this->assetsLists3[0].ppEntries[i+1], &this->assetsLists3[0].ppEntries[i], (k - i) * sizeof(void*)); + this->assetsLists3[0].ppEntries[i] = pEntry; + //repeat = true; + //firstFileOffs = decompEntryList.ppEntries[i]->offset; + } + } + } + #define SizePerBuffer (1024*1024) + void *buffers = malloc(/*11 * */SizePerBuffer); + if (!buffers) + return false; + std::vector pairs; + + uint8_t *decompBuffer = (uint8_t*)buffers; + //uint8_t *compBuffer = &decompBuffer[SizePerBuffer]; + QWORD curFilePos = 0; + QWORD curCompFilePos = 0; + bundleHeader3.Write(pWriter, curFilePos); + curFilePos = bundleHeader3.bundleDataOffs; + curCompFilePos = bundleHeader3.bundleDataOffs; + + QWORD curCompressOffs = 0; + //for (uint32_t i = 0; i < bundleHeader.bundleCount; i++) + { + CLzmaEncHandle enc = LzmaEnc_Create(&g_Alloc); + if (!enc) + { + free(buffers); + return false; + } + + CLzmaEncProps props; + LzmaEncProps_Init(&props); + props.level = 0; + props.writeEndMark = 1; + props.dictSize = 524288; + props.numThreads = 2; + + SRes res = LzmaEnc_SetProps(enc, &props); + if (res != SZ_OK) + { + free(buffers); + LzmaEnc_Destroy(enc, &g_Alloc, &g_Alloc); + return false; + } + unsigned char header[LZMA_PROPS_SIZE+8]; + size_t headerSize = LZMA_PROPS_SIZE; + LzmaEnc_WriteProperties(enc, header, &headerSize); + if (res != SZ_OK || headerSize != LZMA_PROPS_SIZE) + { + free(buffers); + LzmaEnc_Destroy(enc, &g_Alloc, &g_Alloc); + return false; + } + headerSize = LZMA_PROPS_SIZE+8; + *(QWORD*)&header[LZMA_PROPS_SIZE] = 0; + QWORD lzmaHeaderFilePos = curFilePos; + pWriter->Write(curFilePos, headerSize, header); + curCompressOffs += headerSize; //? + + LZMAInStream seqInStream; LZMAInStream_Init(&seqInStream); + LZMAOutStream seqOutStream = {&LZMAOutStream_Write, pWriter, lzmaHeaderFilePos, headerSize}; + LZMACompressThreadData threadData; + threadData.pIn = &seqInStream; + threadData.pOut = &seqOutStream; + threadData.enc = enc; + threadData.result = SZ_OK; + std::future compressFuture = std::async(LZMACompressThread, &threadData); + + QWORD curUncompressOffs = 0; + uint32_t levelIndex = 0; + { + size_t destLen = SizePerBuffer; + size_t srcLen; + if (this->assetsLists3->count > 0) + srcLen = this->assetsLists3->ppEntries[0]->offset; + else + srcLen = 4; //count dword (= 0) + QWORD oldWriteCount = seqOutStream.writeCount; + pReader->Read(curFilePos, srcLen, decompBuffer); + LZMAInStream_Put(&seqInStream, decompBuffer, srcLen, true); + destLen = seqOutStream.writeCount - oldWriteCount; + /*SRes result = LzmaEnc_MemEncode(enc, compBuffer, &destLen, decompBuffer, srcLen, (this->assetsLists3->count > 0) ? 0 : 1, + NULL, &g_Alloc, &g_Alloc); + if ((destLen == 0) || (result != SZ_OK)) + { + free(buffers); + LzmaEnc_Destroy(enc, &g_Alloc, &g_Alloc); + return false; + } + writer(-1, destLen, compBuffer, writerPar);*/ + curCompressOffs += destLen; + curUncompressOffs += srcLen; + if (this->assetsLists3->count == 0 && this->bundleHeader3.blockCount > 0) + { + this->bundleHeader3.pBlockList[levelIndex].compressed = (uint32_t)curCompressOffs; + this->bundleHeader3.pBlockList[levelIndex].uncompressed = (uint32_t)curUncompressOffs; + levelIndex++; + } + } + + for (uint32_t i = 0; i < this->assetsLists3->count; i++) + { + QWORD compressBytes = this->assetsLists3->ppEntries[i]->length; + if ((i+1) < this->assetsLists3->count) + { + //add empty bytes to compress in between (if there are any) + compressBytes += this->assetsLists3->ppEntries[i+1]->offset - + (this->assetsLists3->ppEntries[i]->offset + this->assetsLists3->ppEntries[i]->length); + } + QWORD curBytesProcessed = 0; + while (curBytesProcessed < compressBytes) + { + size_t writeLen = compressBytes - curBytesProcessed; + size_t destLen;// = 10*SizePerBuffer; + size_t srcLen = writeLen > SizePerBuffer ? SizePerBuffer : writeLen; + QWORD oldWriteCount = seqOutStream.writeCount; + pReader->Read(curFilePos + this->assetsLists3->ppEntries[i]->offset + curBytesProcessed, srcLen, decompBuffer); + if (srcLen > 0) + LZMAInStream_Put(&seqInStream, decompBuffer, srcLen, true); + destLen = seqOutStream.writeCount - oldWriteCount; + /*SRes result = LzmaEnc_MemEncode(enc, compBuffer, &destLen, decompBuffer, srcLen, + (((i+1) >= this->assetsLists3->count) && (srcLen >= writeLen)) ? 1 : 0, + NULL, &g_Alloc, &g_Alloc); + if ((destLen == 0) || (result != SZ_OK)) + { + free(buffers); + LzmaEnc_Destroy(enc, &g_Alloc, &g_Alloc); + return false; + } + writer(-1, destLen, compBuffer, writerPar);*/ + curCompressOffs += destLen; + curBytesProcessed += srcLen; + curUncompressOffs += srcLen; + } + if ((1 == this->assetsLists3->count || + !strcmp(this->assetsLists3->ppEntries[i]->name, "mainData") || + !strncmp(this->assetsLists3->ppEntries[i]->name, "level", 5)) && + (levelIndex < this->bundleHeader3.blockCount)) + { + this->bundleHeader3.pBlockList[levelIndex].compressed = (uint32_t)curCompressOffs; + this->bundleHeader3.pBlockList[levelIndex].uncompressed = (uint32_t)curUncompressOffs; + levelIndex++; + } + } + LZMAInStream_Free(&seqInStream); + assert(compressFuture.valid()); + compressFuture.wait(); + curCompressOffs = threadData.pOut->writeCount; + LzmaEnc_Destroy(enc, &g_Alloc, &g_Alloc); + *(QWORD*)&header[LZMA_PROPS_SIZE] = curUncompressOffs; + pWriter->Write(lzmaHeaderFilePos, headerSize, header); + } + free(buffers); + AssetBundleHeader03 headerCopy; + memcpy(&headerCopy, &this->bundleHeader3, sizeof(AssetBundleHeader03)); + strcpy(headerCopy.signature, "UnityWeb"); + headerCopy.minimumStreamedBytes = headerCopy.fileSize2 = headerCopy.bundleDataOffs + (uint32_t)curCompressOffs; + curFilePos = 0; + headerCopy.Write(pWriter, curFilePos); + return true; + } + } + else if (bundleHeader3.fileVersion >= 6) + { + //sort the entries (this one is slow but the entry lists aren't that large) + bool repeat = false; + for (uint32_t i = 0; i < this->bundleInf6->directoryCount; i++) + { + if (repeat) + i--; + repeat = false; + for (uint32_t k = i+1; k < this->bundleInf6->directoryCount; k++) + { + if (this->bundleInf6->dirInf[k].offset < this->bundleInf6->dirInf[i].offset) + { + AssetBundleDirectoryInfo06 entry = this->bundleInf6->dirInf[k]; + memcpy(&this->bundleInf6->dirInf[i+1], &this->bundleInf6->dirInf[i], (k - i) * sizeof(AssetBundleDirectoryInfo06)); + this->bundleInf6->dirInf[i] = entry; + } + } + } + std::vector newBlockInfo; + std::vector newDirectory; + + QWORD curFilePos = 0; + QWORD curCompFilePos = 0; + + AssetBundleHeader06 headerCopy; + memcpy(&headerCopy, &this->bundleHeader6, sizeof(AssetBundleHeader06)); + strcpy(headerCopy.signature, "UnityFS"); + //strcpy(headerCopy.fileEngineVersion, "5.3.4p10"); + headerCopy.flags = 0x80 | 0x40; //Don't know the block sizes before having written everything, so let's simply append the block info. + headerCopy.Write(pWriter, curFilePos); + curFilePos = bundleHeader6.GetFileDataOffset(); + curCompFilePos = headerCopy.GetFileDataOffset(); + + QWORD curCompressOffs = 0; + QWORD curAbsOffs = curCompFilePos; //"cleaned" file pointer to the new file, as if it was uncompressed. + uint8_t nullBuf[16] = {}; + for (uint32_t i = 0; i < bundleInf6->directoryCount; i++) + { + AssetBundleDirectoryInfo06 *pDir = &bundleInf6->dirInf[i]; + curFilePos = bundleHeader6.GetFileDataOffset() + pDir->offset; + + AssetBundleBlockInfo06 newBlock; + AssetBundleDirectoryInfo06 newDir; + newDir.decompressedSize = pDir->decompressedSize; + newDir.flags = pDir->flags; + newDir.name = pDir->name; + newDir.offset = curAbsOffs - headerCopy.GetFileDataOffset(); + newDirectory.push_back(newDir); + size_t nameLen = strlen(pDir->name); + uint32_t curType = COMPRESS_LZ4; + if (!settings) + { + if ((nameLen > 9 && !strnicmp(&pDir->name[nameLen - 9], ".resource", 9)) + || (nameLen > 10 && !strnicmp(&pDir->name[nameLen - 10], ".resources", 10)) + || (nameLen > 5 && !strnicmp(&pDir->name[nameLen - 5], ".resS", 5))) + curType = COMPRESS_NONE; + } + else + { + curType = settings[i]; + if (curType > (uint32_t)COMPRESS_LZ4) curType = COMPRESS_NONE; + } + QWORD entryBytesProcessed = 0; + QWORD blockLimit = ((uint32_t)std::numeric_limits::max()) & ~15; //0x7FFFFFF0 + while (entryBytesProcessed < pDir->decompressedSize) + { + QWORD bytesToProcess = pDir->decompressedSize; + bytesToProcess = std::min(bytesToProcess, blockLimit); + QWORD preFileCompPos = curCompFilePos; + switch (curType) + { + case COMPRESS_NONE: + if (!CopyBlock(&curFilePos, &curCompFilePos, bytesToProcess, pReader, pWriter)) + return false; + newBlock.flags = 0; //uncompressed + break; + case COMPRESS_LZMA: + if (LZMACompressBlock(&curFilePos, &curCompFilePos, bytesToProcess, 0, pReader, pWriter) != SZ_OK) + return false; + newBlock.flags = 1; //LZMA, not streamed + break; + case COMPRESS_LZ4: + if (!LZ4CompressBlock(&curFilePos, &curCompFilePos, bytesToProcess, pReader, pWriter)) + return false; + newBlock.flags = 2; //LZ4, not streamed + break; + } + newBlock.compressedSize = (uint32_t)(curCompFilePos - preFileCompPos); + newBlock.decompressedSize = (uint32_t)bytesToProcess; + newBlockInfo.push_back(newBlock); + curAbsOffs += newBlock.decompressedSize; + } + } + AssetBundleBlockAndDirectoryList06 listCopy; + listCopy.checksumLow = 0; + listCopy.checksumHigh = 0; + listCopy.blockCount = (uint32_t)newBlockInfo.size(); + listCopy.blockInf = newBlockInfo.data(); + listCopy.directoryCount = (uint32_t)newDirectory.size(); + listCopy.dirInf = newDirectory.data(); + QWORD listBeginPos = curCompFilePos; + + IAssetsWriterToMemory *pListWriter = Create_AssetsWriterToMemory(); QWORD listWriterPos = 0; + listCopy.Write(pListWriter, listWriterPos); + pListWriter->SetFreeBuffer(true); + void *pListRaw = nullptr; size_t listRawSize = 0; + if (!pListWriter->GetBuffer(pListRaw, listRawSize)) + { + Free_AssetsWriter(pListWriter); + return false; + } + switch (fileTableSettings) + { + default: + case COMPRESS_NONE: + if (pWriter->Write(curCompFilePos, listRawSize, pListRaw) < listRawSize) + { + Free_AssetsWriter(pListWriter); + return false; + } + curCompFilePos += listRawSize; + headerCopy.flags = (headerCopy.flags & ~0x3F) | 0; + break; + case COMPRESS_LZMA: + { + QWORD listReaderPos = 0; + IAssetsReader *pListReader = Create_AssetsReaderFromMemory(pListRaw, listRawSize, false); + if (LZMACompressBlock(&listReaderPos, &curCompFilePos, listRawSize, 0, pListReader, pWriter) != SZ_OK) + { + Free_AssetsReader(pListReader); + Free_AssetsWriter(pListWriter); + return false; + } + headerCopy.flags = (headerCopy.flags & ~0x3F) | 1; + } + break; + case COMPRESS_LZ4: + { + QWORD listReaderPos = 0; + IAssetsReader *pListReader = Create_AssetsReaderFromMemory(pListRaw, listRawSize, false); + if (!LZ4CompressBlock(&listReaderPos, &curCompFilePos, listRawSize, pListReader, pWriter)) + { + Free_AssetsReader(pListReader); + Free_AssetsWriter(pListWriter); + return false; + } + headerCopy.flags = (headerCopy.flags & ~0x3F) | 2; + } + break; + } + Free_AssetsWriter(pListWriter); + //listCopy.Write(pWriter, curCompFilePos); + headerCopy.totalFileSize = curCompFilePos; + headerCopy.decompressedSize = (uint32_t)listRawSize; + headerCopy.compressedSize = (uint32_t)(curCompFilePos - listBeginPos); + + curCompFilePos = 0; + headerCopy.Write(pWriter, curCompFilePos); + return true; + } + else + return false; + return true; +} +ASSETSTOOLS_API bool AssetBundleFile::Write(IAssetsReader *pReader, + IAssetsWriter *pWriter, + BundleReplacer **pReplacers, size_t replacerCount, + AssetsFileVerifyLogger errorLogger, ClassDatabaseFile *typeMeta) +{ + if (!this->bundleInf6) + { + if (errorLogger) errorLogger("ERROR : Invalid bundle file!"); + return false; + } + struct BundleReplacerInfo + { + BundleReplacer *pReplacer; + size_t predecessorIndex; //Currently used for fileVersion >= 6 only + unsigned int bundleEntryIndex; + bool skipFlag; //Currently used for fileVersion >= 6 only + BundleReplacerInfo() + : pReplacer(nullptr), predecessorIndex((size_t)-1), + bundleEntryIndex((unsigned int)-1), skipFlag(false) + {} + }; + std::vector> replacersToDelete; + std::vector replacers; + //Internal directory: Removed entries are still counted (no shifting of higher indices). + // -> The first bundleInf6[0].directoryCount> indices represent original directory entries. + //Output directory: Removed entries are no longer listed. + //Maps entry names to internal directory indices. + std::unordered_map internalDirectoryIndexByName; + //Maps internal directory indices to output directory indices. + std::vector internalToOutputDirectoryMap; + //Maps internal directory indices to the latest replacer index for that entry. + std::vector internalDirectoryLatestReplacerMap; + size_t numOutputDirectories = 0; + uint32_t numOriginalDirectories = 0; + std::vector outputDirectoryNames; + if (bundleHeader3.fileVersion >= 6) + { + numOriginalDirectories = this->bundleInf6->directoryCount; + internalToOutputDirectoryMap.resize(numOriginalDirectories); + internalDirectoryLatestReplacerMap.resize(numOriginalDirectories); + numOutputDirectories = numOriginalDirectories; + outputDirectoryNames.resize(numOriginalDirectories); + for (uint32_t k = 0; k < numOriginalDirectories; k++) + { + internalDirectoryIndexByName[this->bundleInf6->dirInf[k].name] = k; + outputDirectoryNames[k] = this->bundleInf6->dirInf[k].name; + internalToOutputDirectoryMap[k] = k; + internalDirectoryLatestReplacerMap[k] = (size_t)-1; + } + } + else if (bundleHeader3.fileVersion == 3) + { + numOriginalDirectories = this->assetsLists3->count; + internalToOutputDirectoryMap.resize(numOriginalDirectories); + internalDirectoryLatestReplacerMap.resize(numOriginalDirectories); + numOutputDirectories = numOriginalDirectories; + outputDirectoryNames.resize(numOriginalDirectories); + for (uint32_t k = 0; k < numOriginalDirectories; k++) + { + internalDirectoryIndexByName[this->assetsLists3->ppEntries[k]->name] = k; + outputDirectoryNames[k] = this->assetsLists3->ppEntries[k]->name; + internalToOutputDirectoryMap[k] = k; + internalDirectoryLatestReplacerMap[k] = (size_t)-1; + } + } + else + return false; + + //Generate the directory mappings and internal replacer list. + replacers.resize(replacerCount); + for (size_t i = 0; i < replacerCount; i++) + { + replacers[i].pReplacer = pReplacers[i]; + replacers[i].pReplacer->Uninit(); + unsigned int bundleListIndex = pReplacers[i]->GetBundleListIndex(); + const char *entryName = pReplacers[i]->GetOriginalEntryName(); + if (bundleListIndex == (unsigned int)-1 && entryName != NULL) + { + //Find the internal directory index by the entry name. + auto entryNameIt = internalDirectoryIndexByName.find(entryName); + if (entryNameIt != internalDirectoryIndexByName.end()) + bundleListIndex = entryNameIt->second; + } + if (bundleListIndex == (unsigned int)-1 + && (entryName != NULL || (entryName = pReplacers[i]->GetEntryName()) != NULL) + && pReplacers[i]->GetType() == BundleReplacement_AddOrModify + && !pReplacers[i]->RequiresEntryReader()) + { + //New entry adder. + //-> Add it to the directory mappings. + bundleListIndex = (unsigned int)internalToOutputDirectoryMap.size(); + internalDirectoryIndexByName[entryName] = bundleListIndex; + + assert(outputDirectoryNames.size() == numOutputDirectories); + outputDirectoryNames.push_back(entryName); + internalToOutputDirectoryMap.push_back(numOutputDirectories); + internalDirectoryLatestReplacerMap.push_back(i); + ++numOutputDirectories; + } + else + { + //Evaluate the effect on the directory and name mappings. + switch (pReplacers[i]->GetType()) + { + case BundleReplacement_Remove: + if (entryName != NULL && bundleListIndex != (unsigned int)-1) + { + assert(numOutputDirectories > 0); + assert(internalToOutputDirectoryMap[bundleListIndex] != -1); + if (internalToOutputDirectoryMap[bundleListIndex] != -1) + outputDirectoryNames.erase(outputDirectoryNames.begin() + internalToOutputDirectoryMap[bundleListIndex]); + internalToOutputDirectoryMap[bundleListIndex] = (size_t)-1; + for (size_t k = bundleListIndex + 1; k < internalToOutputDirectoryMap.size(); ++k) + { + --internalToOutputDirectoryMap[k]; + } + + --numOutputDirectories; + internalDirectoryIndexByName.erase(entryName); + assert(outputDirectoryNames.size() == numOutputDirectories); + } + break; + case BundleReplacement_Rename: + case BundleReplacement_AddOrModify: + { + const char *newEntryName = pReplacers[i]->GetEntryName(); + if (entryName != NULL && bundleListIndex != (unsigned int)-1 && newEntryName != nullptr) + { + internalDirectoryIndexByName.erase(entryName); + internalDirectoryIndexByName[newEntryName] = bundleListIndex; + assert(internalToOutputDirectoryMap[bundleListIndex] != -1); + if (internalToOutputDirectoryMap[bundleListIndex] != -1) + outputDirectoryNames[internalToOutputDirectoryMap[bundleListIndex]] = newEntryName; + } + } + break; + } + } + if (bundleListIndex != (unsigned int)-1) + { + //Link the replacer in the by-directoryIndex mapping. + if (internalDirectoryLatestReplacerMap[bundleListIndex] != (size_t)-1 + && internalDirectoryLatestReplacerMap[bundleListIndex] != i) + { + replacers[i].predecessorIndex = internalDirectoryLatestReplacerMap[bundleListIndex]; + } + internalDirectoryLatestReplacerMap[bundleListIndex] = i; + } + if (!replacers[i].pReplacer->RequiresEntryReader()) + { + if (pReplacers[i]->GetType() != BundleReplacement_Rename) + { + //Any previous replacer result is not required. + //-> Mark the predecessors as skippable. + size_t k = replacers[i].predecessorIndex; + while (k != (size_t)-1) + { + replacers[k].skipFlag = true; + k = replacers[k].predecessorIndex; + } + replacers[i].predecessorIndex = (size_t) -1; + } + } + else if (bundleListIndex >= numOriginalDirectories && replacers[i].predecessorIndex == (size_t)-1) + { + //The replacer cannot be applied, as it is based on previous entry data that doesn't exist. + //Shouldn't happen, unless an invalid replacers file is read in. + assert(false); + replacers[i].skipFlag = true; //Invalid replacer. + if (bundleListIndex != (unsigned int)-1) + { + //Unlink the replacer and remove its generated directory entry. + assert(internalToOutputDirectoryMap[bundleListIndex] != -1); + if (internalToOutputDirectoryMap[bundleListIndex] != -1) + outputDirectoryNames.erase(outputDirectoryNames.begin() + internalToOutputDirectoryMap[bundleListIndex]); + internalToOutputDirectoryMap[bundleListIndex] = (size_t)-1; + for (size_t k = bundleListIndex + 1; k < internalToOutputDirectoryMap.size(); ++k) + { + --internalToOutputDirectoryMap[k]; + } + --numOutputDirectories; + internalDirectoryIndexByName.erase(entryName); + assert(outputDirectoryNames.size() == numOutputDirectories); + } + } + if (bundleListIndex == (unsigned int)-1) + { + //Shouldn't happen, unless an invalid replacers file is read in. + assert(false); + replacers[i].skipFlag = true; //Invalid replacer. + } + replacers[i].bundleEntryIndex = bundleListIndex; + } + //Prepare the replacers for each output directory entry. + std::vector> tempReaderReferences; + for (size_t i = 0; i < internalToOutputDirectoryMap.size(); i++) + { + if (internalToOutputDirectoryMap[i] == (size_t)-1) + continue; //Entry was removed. + std::vector replacerReverseOrder; + size_t k = internalDirectoryLatestReplacerMap[i]; + while (k != (size_t)-1 && !replacers[k].skipFlag) + { + if (replacers[k].pReplacer->GetType() != BundleReplacement_Rename) //Skip renamers here. + replacerReverseOrder.push_back(k); + k = replacers[k].predecessorIndex; + } + bool error = false; + if (replacerReverseOrder.empty()) + { + size_t outputIndex = internalToOutputDirectoryMap[i]; + if (i >= numOriginalDirectories) + error = true; + else + { + assert(outputDirectoryNames.size() > outputIndex); + BundleReplacerInfo newReplacer; + QWORD decompressedSize = 0; + QWORD absolutePos = 0; + if (this->bundleHeader3.fileVersion >= 6) + { + decompressedSize = this->bundleInf6->dirInf[i].decompressedSize; + absolutePos = this->bundleInf6->dirInf[i].GetAbsolutePos(this); + } + else if (this->bundleHeader3.fileVersion == 3) + { + decompressedSize = this->assetsLists3->ppEntries[i]->length; + absolutePos = this->assetsLists3->ppEntries[i]->GetAbsolutePos(this); + } + bool hasSerializedData = this->IsAssetsFile(pReader, i); + newReplacer.pReplacer = MakeBundleEntryModifier(this->GetEntryName(i), outputDirectoryNames[outputIndex].c_str(), + hasSerializedData, pReader, NULL, decompressedSize, absolutePos, 16384, (unsigned int)outputIndex); + replacersToDelete.push_back(std::unique_ptr(newReplacer.pReplacer, FreeBundleReplacer)); + newReplacer.bundleEntryIndex = (unsigned int)i; + replacers.push_back(newReplacer); + internalDirectoryLatestReplacerMap[i] = replacers.size() - 1; + replacerReverseOrder.push_back(replacers.size() - 1); + } + } + std::shared_ptr pInitReader_refholder; + IAssetsReader *pInitReader = nullptr; + QWORD initSize = 0; + QWORD initPos = 0; + if (i < numOriginalDirectories && replacers[replacerReverseOrder.back()].pReplacer->RequiresEntryReader()) + { + pInitReader = pReader; + if (this->bundleHeader3.fileVersion >= 6) + { + initSize = this->bundleInf6->dirInf[i].decompressedSize; + initPos = this->bundleInf6->dirInf[i].GetAbsolutePos(this); + } + else if (this->bundleHeader3.fileVersion == 3) + { + initSize = this->assetsLists3->ppEntries[i]->length; + initPos = this->assetsLists3->ppEntries[i]->GetAbsolutePos(this); + } + } + //Iterate through the replacers (first to last) to initialize them. + for (auto it = replacerReverseOrder.rbegin(); !error && it != replacerReverseOrder.rend(); ++it) + { + size_t curReplacerIdx = *it; + BundleReplacerInfo &curReplacer = replacers[curReplacerIdx]; + if (!curReplacer.pReplacer->Init(this, pInitReader, initPos, initSize, typeMeta)) + { + error = true; + break; + } + auto next_it = it; ++next_it; + if (next_it == replacerReverseOrder.rend()) + { + break; + } + struct { void operator()(BundleReplacer*){} } dummyDeleter; + auto pNextReader = MakeReaderFromBundleEntryModifier(std::shared_ptr(curReplacer.pReplacer, dummyDeleter)); + if (pNextReader != nullptr) + { + //If this is a simple file/memory-based replacer, we can use its internal resource to generate a reader. + curReplacer.pReplacer->Uninit(); + pInitReader_refholder = pNextReader; + pInitReader = pNextReader.get(); + if (!pNextReader->Seek(AssetsSeek_End, 0) || !pNextReader->Tell(initSize) || !pNextReader->Seek(AssetsSeek_Begin, 0)) + { + error = true; + break; + } + initPos = 0; + continue; + } + //The replacer appears to be a more complex one, i.e. FromBundle or FromAssets. + //Write the replaced file to a memory buffer, and proceed using that buffer as input for the next replacer. + IAssetsWriterToMemory *pTempWriter = Create_AssetsWriterToMemory(); + initSize = curReplacer.pReplacer->Write(0, pTempWriter); + curReplacer.pReplacer->Uninit(); + if (initSize == 0) + { + //Assume this is an error, as the output should always have a header. + Free_AssetsWriter(pTempWriter); + error = true; + break; + } + void *memBuffer = nullptr; size_t memBufferSize = 0; + if (!pTempWriter->GetBuffer(memBuffer, memBufferSize) || memBufferSize < initSize || !pTempWriter->SetFreeBuffer(false)) + { + //This really shouldn't happen. + assert(false); + Free_AssetsWriter(pTempWriter); + error = true; + break; + } + Free_AssetsWriter(pTempWriter); + initPos = 0; + pNextReader.reset(Create_AssetsReaderFromMemory(memBuffer, memBufferSize, false, Free_AssetsWriterToMemory_DynBuf), Free_AssetsReader); + pInitReader_refholder = pNextReader; + pInitReader = pNextReader.get(); + } + if (error) + { + //Remove the current directory entry. + size_t outputIndex = internalToOutputDirectoryMap[i]; + std::string entryName = std::move(outputDirectoryNames[outputIndex]); + outputDirectoryNames.erase(outputDirectoryNames.begin() + outputIndex); + internalToOutputDirectoryMap[i] = (size_t)-1; + for (size_t k = i + 1; k < internalToOutputDirectoryMap.size(); ++k) + { + --internalToOutputDirectoryMap[k]; + } + --numOutputDirectories; + internalDirectoryIndexByName.erase(entryName); + assert(outputDirectoryNames.size() == numOutputDirectories); + + std::string warningMessage = std::string("Error : Unable to prepare the entry modification for entry ") + entryName + "."; + if (errorLogger) errorLogger(warningMessage.c_str()); + continue; + } + // Unlink any Renamers in front of the last true modifier. + internalDirectoryLatestReplacerMap[i] = replacerReverseOrder.front(); + if (pInitReader_refholder != nullptr) + tempReaderReferences.push_back(pInitReader_refholder); + } + struct _Lambda_FreeReplacers { + std::vector &replacers; + _Lambda_FreeReplacers(std::vector &replacers) : replacers(replacers) {} + void operator()() + { + for (size_t i = 0; i < replacers.size(); ++i) + if (replacers[i].pReplacer != nullptr) replacers[i].pReplacer->Uninit(); + } + } lambda_uninitReplacers(replacers); + assert(outputDirectoryNames.size() == numOutputDirectories); + assert(numOutputDirectories <= UINT_MAX); + if (numOutputDirectories > UINT_MAX) + { + lambda_uninitReplacers(); + return false; + } + if (bundleHeader3.fileVersion >= 6) + { + //std::vector orderedReplacers; + std::vector directories(numOutputDirectories); + std::vector blocks; + QWORD curFilePos = 0; + AssetBundleHeader06 header = this->bundleHeader6; + if (header.flags & 0x100) + strcpy(header.signature, "UnityWeb"); + header.flags &= ~0x3F; //No directory/block list compression. + header.flags |= 0x40; //Has directory info. + header.Write(pWriter, curFilePos); + //Create dummy block info (we don't have exact sizes but have to assume that <= or > 4 GiB can be differentiated). + //Also assign directory names. + for (size_t i = 0; i < internalToOutputDirectoryMap.size(); i++) + { + size_t outputDirectoryIdx = internalToOutputDirectoryMap[i]; + if (outputDirectoryIdx == (size_t)-1) + continue; //Entry was removed. + assert(outputDirectoryIdx < numOutputDirectories); + if (outputDirectoryIdx >= numOutputDirectories) + { + lambda_uninitReplacers(); + return false; //Shouldn't happen + } + directories[outputDirectoryIdx].name = outputDirectoryNames[outputDirectoryIdx].c_str(); + size_t replacerIdx = internalDirectoryLatestReplacerMap[i]; + assert(replacerIdx != (size_t)-1 && !replacers[replacerIdx].skipFlag); + if (replacerIdx == (size_t)-1 || replacers[replacerIdx].skipFlag) + { + lambda_uninitReplacers(); + return false; //Shouldn't happen + } + BundleReplacerInfo &replacer = replacers[replacerIdx]; + if (replacer.pReplacer->GetType() == BundleReplacement_AddOrModify) + { + QWORD estimatedSize = replacer.pReplacer->GetSize(); + while (estimatedSize >= 0xFFFFFFFFULL) + { + AssetBundleBlockInfo06 dummyBlock = {}; + blocks.push_back(dummyBlock); + estimatedSize -= 0xFFFFFFFFULL; + } + if (estimatedSize) + { + AssetBundleBlockInfo06 dummyBlock = {}; + blocks.push_back(dummyBlock); + } + } + } + QWORD blockAndDirListPos = curFilePos; + AssetBundleBlockAndDirectoryList06 blockAndDirList = {}; + blockAndDirList.blockCount = (uint32_t)blocks.size(); + blockAndDirList.blockInf = blocks.data(); + blockAndDirList.directoryCount = (uint32_t)numOutputDirectories; + blockAndDirList.dirInf = directories.data(); + blockAndDirList.Write(pWriter, curFilePos); + QWORD bundleDataPos = curFilePos; + //Fix the sizes + header.compressedSize = header.decompressedSize = (uint32_t)(curFilePos - blockAndDirListPos); + header.flags &= ~(0x80); + size_t blockIndex = 0; + for (size_t i = 0; i < internalToOutputDirectoryMap.size(); i++) + { + size_t outputDirectoryIdx = internalToOutputDirectoryMap[i]; + if (outputDirectoryIdx == (size_t)-1) + continue; //Entry was removed. + size_t replacerIdx = internalDirectoryLatestReplacerMap[i]; + BundleReplacerInfo &replacer = replacers[replacerIdx]; + if (replacer.pReplacer->GetType() == BundleReplacement_AddOrModify) + { + //fix the sizes in the directory info + QWORD beginFilePos = curFilePos; + curFilePos = replacer.pReplacer->Write(curFilePos, pWriter); + if (replacer.pReplacer->HasSerializedData()) + directories[outputDirectoryIdx].flags |= 4; + replacer.pReplacer->Uninit(); + directories[outputDirectoryIdx].decompressedSize = curFilePos - beginFilePos; + directories[outputDirectoryIdx].offset = beginFilePos - header.GetFileDataOffset(); + //fix the sizes in the block info + QWORD remainingSize = curFilePos - beginFilePos; + while (remainingSize >= 0xFFFFFFFFULL) + { + if (blocks.size() <= blockIndex) + break; + blocks[blockIndex].compressedSize = blocks[blockIndex].decompressedSize = 0xFFFFFFFFULL; + blocks[blockIndex].flags = 0x40; //TODO: Check if the 'streamed' flag was set before, because it isn't always. + blockIndex++; + remainingSize -= 0xFFFFFFFFULL; + } + if (remainingSize && (blocks.size() > blockIndex)) + { + blocks[blockIndex].compressedSize = blocks[blockIndex].decompressedSize = (uint32_t)remainingSize; + blocks[blockIndex].flags = 0x40; + blockIndex++; + } + } + } + header.totalFileSize = curFilePos; + curFilePos = 0; + header.Write(pWriter, curFilePos); + blockAndDirList.blockInf = blocks.data(); + blockAndDirList.dirInf = directories.data(); + curFilePos = blockAndDirListPos; + blockAndDirList.Write(pWriter, curFilePos); + pWriter->SetPosition(header.totalFileSize); + return true; + } + else if (bundleHeader3.fileVersion == 3) + { + std::vector directories; + std::vector blocks; + //Create dummy block info + //-> The file table gets its own block + { + AssetBundleOffsetPair dummyBlock = {}; + blocks.push_back(dummyBlock); + } + bool error = false; + //Create dummy block info for replacers and also assign directory names. + for (size_t i = 0; i < internalToOutputDirectoryMap.size(); i++) + { + size_t outputDirectoryIdx = internalToOutputDirectoryMap[i]; + if (outputDirectoryIdx == (size_t)-1) + continue; //Entry was removed. + assert(outputDirectoryIdx < numOutputDirectories); + if (outputDirectoryIdx >= numOutputDirectories) + { + error = true; //Shouldn't happen + break; + } + size_t replacerIdx = internalDirectoryLatestReplacerMap[i]; + assert(replacerIdx != (size_t)-1 && !replacers[replacerIdx].skipFlag); + if (replacerIdx == (size_t)-1 || replacers[replacerIdx].skipFlag) + { + error = true; //Shouldn't happen + break; + } + assert(directories.size() == outputDirectoryIdx); + + AssetBundleEntry *newDirectory = (AssetBundleEntry*)new uint8_t[8 + sizeof(char) * (outputDirectoryNames[outputDirectoryIdx].size() + 1)]; + newDirectory->length = newDirectory->offset = 0; + memcpy(newDirectory->name, outputDirectoryNames[outputDirectoryIdx].data(), sizeof(char) * (outputDirectoryNames[outputDirectoryIdx].size() + 1)); + directories.push_back(newDirectory); + BundleReplacerInfo &replacer = replacers[replacerIdx]; + if (replacer.pReplacer->GetType() == BundleReplacement_AddOrModify) + { + QWORD estimatedSize = replacer.pReplacer->GetSize(); + while (estimatedSize >= 0xFFFFFFFFULL) + { + AssetBundleOffsetPair dummyBlock = {}; + blocks.push_back(dummyBlock); + estimatedSize -= 0xFFFFFFFFULL; + } + if (estimatedSize) + { + AssetBundleOffsetPair dummyBlock = {}; + blocks.push_back(dummyBlock); + } + } + } + if (error) + { + for (size_t i = 0; i < directories.size(); i++) + { + delete[] ((uint8_t*)directories[i]); + } + lambda_uninitReplacers(); + return false; + } + AssetBundleHeader03 header = this->bundleHeader3; + QWORD curFilePos = 0; + header.blockCount = (uint32_t)blocks.size(); + header.pBlockList = blocks.data(); + header.Write(pWriter, curFilePos); + + uint32_t alignBytes = (uint32_t)(((curFilePos + 3) & (~3)) - curFilePos); + uint32_t dwNull = 0; + pWriter->Write(curFilePos, alignBytes, &dwNull); + curFilePos += alignBytes; + + QWORD assetsListPos = curFilePos; + AssetsList assetsList = {}; + assetsList.allocatedCount = assetsList.count = (uint32_t)directories.size(); + assetsList.pos = (uint32_t)curFilePos; + assetsList.ppEntries = directories.data(); + assetsList.Write(pWriter, curFilePos); + //fix the sizes + header.bundleDataOffs = (uint32_t)assetsListPos; + header.unknown2 = (uint32_t)(curFilePos - assetsListPos); //not sure if this is right + blocks[0].compressed = blocks[0].uncompressed = header.unknown2; + size_t blockIndex = 1; + for (size_t i = 0; i < internalToOutputDirectoryMap.size(); i++) + { + size_t outputDirectoryIdx = internalToOutputDirectoryMap[i]; + if (outputDirectoryIdx == (size_t)-1) + continue; //Entry was removed. + size_t replacerIdx = internalDirectoryLatestReplacerMap[i]; + BundleReplacerInfo &replacer = replacers[replacerIdx]; + if (replacer.pReplacer->GetType() == BundleReplacement_AddOrModify) + { + //align to 4 bytes + uint32_t alignBytes = (uint32_t)(((curFilePos + 3) & (~3)) - curFilePos); + uint32_t dwNull = 0; + pWriter->Write(curFilePos, alignBytes, &dwNull); + curFilePos += alignBytes; + + QWORD beginFilePos = curFilePos; + curFilePos = replacer.pReplacer->Write(curFilePos, pWriter); + replacer.pReplacer->Uninit(); + //fix the sizes in the directory info + directories[replacer.bundleEntryIndex]->length = (uint32_t)(curFilePos - beginFilePos); + directories[replacer.bundleEntryIndex]->offset = (uint32_t)(beginFilePos - header.bundleDataOffs); + //fix the sizes in the block info + QWORD remainingSize = curFilePos - beginFilePos; + while (remainingSize >= 0xFFFFFFFFULL) + { + if (blocks.size() <= blockIndex) + break; + blocks[blockIndex].compressed = blocks[blockIndex].uncompressed = 0xFFFFFFFFULL; + blockIndex++; + remainingSize -= 0xFFFFFFFFULL; + } + if (remainingSize && (blocks.size() > blockIndex)) + { + blocks[blockIndex].compressed = blocks[blockIndex].uncompressed = (uint32_t)((remainingSize + 3) & (~3)); + blockIndex++; + } + } + } + header.minimumStreamedBytes = (uint32_t)curFilePos; + header.fileSize2 = (uint32_t)curFilePos; + curFilePos = 0; + header.pBlockList = blocks.data(); + header.Write(pWriter, curFilePos); + curFilePos = assetsListPos; + assetsList.ppEntries = directories.data(); + assetsList.Write(pWriter, curFilePos); + pWriter->SetPosition(header.fileSize2); + for (size_t i = 0; i < directories.size(); i++) + { + delete[] ((uint8_t*)directories[i]); + } + return true; + } + else //unknown bundle file format + return false; +} +ASSETSTOOLS_API bool AssetBundleFile::Read(IAssetsReader *pReader, AssetsFileVerifyLogger errorLogger, bool allowCompressed, uint32_t maxFileTableLen) +{ + Close(); + //this->reader = reader; + if (!bundleHeader6.ReadInitial(pReader, errorLogger)) + return false; + IAssetsReader *pTempReader = nullptr; + if (bundleHeader6.fileVersion >= 6) + { + memset(&bundleHeader6, 0, sizeof(AssetBundleHeader06)); + if (!bundleHeader6.Read(pReader, errorLogger)) + return false; + if (!strcmp(bundleHeader6.signature, "UnityArchive")) + { + //bundleHeader6.flags &= 0xFFFFFFC0; + bundleHeader6.flags |= 0x40; + } + else if (!strcmp(bundleHeader6.signature, "UnityWeb")) + { + strcpy(bundleHeader6.signature, "UnityFS"); + //bundleHeader6.flags &= 0xFFFFFF80; + bundleHeader6.flags |= 0x100; + //bundleHeader6.flags &= ~0x100; + } + else if (!strcmp(bundleHeader6.signature, "UnityRaw")) + { + //bundleHeader6.flags &= 0xFFFFFFC0; + bundleHeader6.flags |= 0x40; + } + + void *fileTableBuf = nullptr; + QWORD fileTablePos = bundleHeader6.GetBundleInfoOffset(); + IAssetsReader *pFileTableReader = pReader; + if ((bundleHeader6.flags & 0x3F) != 0) + { + uint8_t compressionType = (bundleHeader6.flags & 0x3F); + if (compressionType < 4) + { + fileTableBuf = malloc(bundleHeader6.decompressedSize); + if (!fileTableBuf) + goto __goto_outofmemory; + IAssetsWriter *pTempWriter = Create_AssetsWriterToMemory(fileTableBuf, bundleHeader6.decompressedSize); + if (!pTempWriter) + { + free(fileTableBuf); + goto __goto_outofmemory; + } + pTempReader = Create_AssetsReaderFromMemory(fileTableBuf, bundleHeader6.decompressedSize, false); + if (!pTempReader) + { + Free_AssetsWriter(pTempWriter); + free(fileTableBuf); + goto __goto_outofmemory; + } + bool decompressSuccess = false; + QWORD curInPos = fileTablePos; + QWORD curOutPos = 0; + switch (compressionType) + { + case 1: //LZMA + if (LZMADecompressBlock(&curInPos, &curOutPos, bundleHeader6.compressedSize, pReader, pTempWriter) == SZ_OK) + decompressSuccess = true; + break; + case 2: case 3: //LZ4 + if (LZ4DecompressBlock(&curInPos, &curOutPos, bundleHeader6.compressedSize, pReader, pTempWriter)) + decompressSuccess = true; + break; + } + Free_AssetsWriter(pTempWriter); + if (!decompressSuccess || curOutPos != bundleHeader6.decompressedSize) + { + Free_AssetsReader(pTempReader); + free(fileTableBuf); + if (errorLogger) errorLogger("AssetBundleFile.Read : Failed to decompress the directory!"); + return false; + } + pFileTableReader = pTempReader; + fileTablePos = 0; + } + } + if (fileTableBuf || (bundleHeader6.flags & 0x3F) == 0) + { + bundleInf6 = (AssetBundleBlockAndDirectoryList06*)malloc(sizeof(AssetBundleBlockAndDirectoryList06)); + if (!bundleInf6) + goto __goto_outofmemory; + memset(bundleInf6, 0, sizeof(AssetBundleBlockAndDirectoryList06)); + if (!bundleInf6->Read(fileTablePos, pFileTableReader)) + goto __goto_readerror; + if (pTempReader) + { + Free_AssetsReader(pTempReader); + pTempReader = nullptr; + } + if (fileTableBuf) + free(fileTableBuf); + } + else if (!allowCompressed) + { + Close(); + return false; + } + } + else if (bundleHeader6.fileVersion == 3) + { + memset(&bundleHeader3, 0, sizeof(AssetBundleHeader03)); + if (!bundleHeader3.Read(pReader, errorLogger)) + return false; + if (!strcmp(bundleHeader3.signature, "UnityRaw")) //compressed bundles only have an uncompressed header + { + QWORD curFilePos = bundleHeader3.bundleDataOffs; + assetsLists3 = (AssetsList*)malloc(sizeof(AssetsList)); + if (!assetsLists3) + goto __goto_outofmemory; + ZeroMemory(assetsLists3, sizeof(AssetsList)); + + QWORD tmpFilePos = curFilePos; + if (!assetsLists3->Read(pReader, tmpFilePos, errorLogger)) + goto __goto_readerror; + for (uint32_t i = 0; i < bundleHeader3.blockCount; i++) + curFilePos += (bundleHeader3.pBlockList[i].uncompressed + 3) & (~3); + } + else if (!allowCompressed) + { + Close(); + return false; + } + } + else + { + if (errorLogger) errorLogger("AssetBundleFile.Read : Unknown file version!"); + return false; + } + return true; + + __goto_readerror: + if (errorLogger) errorLogger("AssetBundleFile.Read : A file read error occured!"); + goto __goto_errfreebuffers; + __goto_outofmemory: + if (errorLogger) errorLogger("AssetBundleFile.Read : Out of memory!"); + goto __goto_errfreebuffers; + __goto_errfreebuffers: + if (bundleHeader6.fileVersion == 3) + { + if (assetsLists3 != NULL) + { + assetsLists3->Free(); + free(assetsLists3); + } + assetsLists3 = NULL; + } + else if (bundleHeader6.fileVersion >= 6) + { + if (bundleInf6 != NULL) + { + bundleInf6->Free(); + free(bundleInf6); + } + bundleInf6 = NULL; + } + if (pTempReader != nullptr) + { + Free_AssetsReader(pTempReader); + } + return false; +} +ASSETSTOOLS_API bool AssetBundleFile::IsCompressed() +{ + if (assetsLists3 == NULL || bundleInf6 == NULL) return true; + if (bundleHeader6.fileVersion >= 6) + { + for (uint32_t i = 0; i < bundleInf6->blockCount; i++) + { + if ((bundleInf6->blockInf[i].flags & 0x3F) != 0) + return true; + } + } + //Version 3 bundles are compressed in a single block. + return false; +} +ASSETSTOOLS_API bool AssetBundleFile::IsAssetsFile(IAssetsReader *pReader, AssetBundleDirectoryInfo06 *pEntry) +{ + if (pEntry->name) + { + size_t entryNameLen = strlen(pEntry->name); + if (entryNameLen >= 5 && !stricmp(&pEntry->name[entryNameLen - 5], ".resS")) + return false; + if (entryNameLen >= 9 && !stricmp(&pEntry->name[entryNameLen - 9], ".resource")) + return false; + } + QWORD pos = pEntry->GetAbsolutePos(this); + char buf[8]; + pReader->Read(pos, 8, buf); + //mdb, sound bank, dll + if (((*(QWORD*)&buf[0]) == 0x45E82623FD7FA614ULL) || !strncmp(buf, "FSB5", 4) || !strncmp(buf, "MZ", 2)) + return false; + IAssetsReaderFromReaderRange *pReaderRange = Create_AssetsReaderFromReaderRange(pReader, pos, pEntry->decompressedSize); + if (pReaderRange) + { + AssetsFile testAssetsFile(pReaderRange); + bool ret = testAssetsFile.VerifyAssetsFile(); + Free_AssetsReader(pReaderRange); + return ret; + } + return true; +} +ASSETSTOOLS_API bool AssetBundleFile::IsAssetsFile(IAssetsReader *pReader, AssetBundleEntry *pEntry) +{ + unsigned int pos = pEntry->GetAbsolutePos(this); + char buf[8]; + pReader->Read(pos, 8, buf); + if (((*(QWORD*)&buf[0]) == 0x45E82623FD7FA614ULL) || !strncmp(buf, "FSB5", 4) || !strncmp(buf, "MZ", 2)) + return false; + return true; +} +ASSETSTOOLS_API bool AssetBundleFile::IsAssetsFile(IAssetsReader *pReader, size_t entryIdx) +{ + if (bundleHeader6.fileVersion >= 6) + return this->bundleInf6 && this->bundleInf6->directoryCount > entryIdx && IsAssetsFile(pReader, &this->bundleInf6->dirInf[entryIdx]); + else if (bundleHeader6.fileVersion == 3) + return this->assetsLists3 && this->assetsLists3->count > entryIdx && IsAssetsFile(pReader, this->assetsLists3->ppEntries[entryIdx]); + return false; +} +ASSETSTOOLS_API IAssetsReader *AssetBundleFile::MakeAssetsFileReader(IAssetsReader *pReader, AssetBundleDirectoryInfo06 *pEntry) +{ + QWORD absoluteEntryPos = pEntry->GetAbsolutePos(this); + QWORD entryLength = pEntry->decompressedSize; + return Create_AssetsReaderFromReaderRange(pReader, absoluteEntryPos, entryLength); +} +ASSETSTOOLS_API IAssetsReader *AssetBundleFile::MakeAssetsFileReader(IAssetsReader *pReader, AssetBundleEntry *pEntry) +{ + for (uint32_t i = 0; i < this->assetsLists3->count; i++) + { + if (this->assetsLists3->ppEntries[i] == pEntry) + { + goto __goto_break; + } + } + return NULL; +__goto_break: + QWORD absoluteEntryPos = pEntry->GetAbsolutePos(this); + QWORD entryLength = pEntry->length; + return Create_AssetsReaderFromReaderRange(pReader, absoluteEntryPos, entryLength); +} +ASSETSTOOLS_API void FreeAssetBundle_FileReader(IAssetsReader *pReader) +{ + Free_AssetsReader(pReader); +} \ No newline at end of file diff --git a/AssetsTools/AssetBundleFileFormat.h b/AssetsTools/AssetBundleFileFormat.h new file mode 100644 index 0000000..d9e0c9b --- /dev/null +++ b/AssetsTools/AssetBundleFileFormat.h @@ -0,0 +1,209 @@ +#pragma once +#ifndef __AssetsTools__AssetBundleFormat_Header +#define __AssetsTools__AssetBundleFormat_Header +#include "defines.h" +#include "BundleReplacer.h" +#include "ClassDatabaseFile.h" + +class AssetBundleFile; +struct AssetBundleHeader06; +struct AssetBundleHeader03; +struct AssetBundleEntry; +struct AssetBundleList; + +struct AssetBundleDirectoryInfo06 +{ + QWORD offset; + QWORD decompressedSize; + uint32_t flags; //(flags & 4) : has serialized data + const char *name; + ASSETSTOOLS_API QWORD GetAbsolutePos(AssetBundleHeader06 *pHeader); + ASSETSTOOLS_API QWORD GetAbsolutePos(class AssetBundleFile *pFile); +}; +struct AssetBundleBlockInfo06 +{ + uint32_t decompressedSize; + uint32_t compressedSize; + uint16_t flags; //(flags & 0x40) : is streamed (read in 64KiB blocks); (flags & 0x3F) : compression info; + inline uint8_t GetCompressionType() { return (uint8_t)(flags & 0x3F); } + //example flags (LZMA, streamed) : 0x41 + //example flags (LZ4, not streamed) : 0x03 + //https://docs.unity3d.com/530/Documentation/Manual/AssetBundleCompression.html + //LZMA is used as stream-based compression (the whole file is decompressed before usage), + // LZ4 as chunk-based compression (many equally large, independent LZ4 chunks for random access, decompressed in memory). +}; +struct AssetBundleBlockAndDirectoryList06 +{ + QWORD checksumLow; + QWORD checksumHigh; + uint32_t blockCount; + AssetBundleBlockInfo06 *blockInf; + uint32_t directoryCount; + AssetBundleDirectoryInfo06 *dirInf; + + ASSETSTOOLS_API void Free(); + ASSETSTOOLS_API bool Read(QWORD filePos, IAssetsReader *pReader, AssetsFileVerifyLogger errorLogger = NULL); + //Write doesn't compress + ASSETSTOOLS_API bool Write(IAssetsWriter *pWriter, QWORD &curFilePos, AssetsFileVerifyLogger errorLogger = NULL); +}; + +#define LargestBundleHeader AssetBundleHeader03 +//Unity 5.3+ +struct AssetBundleHeader06 +{ + //no alignment in this struct! + char signature[13]; //0-terminated; UnityFS, UnityRaw, UnityWeb or UnityArchive + uint32_t fileVersion; //big-endian, = 6 + char minPlayerVersion[24]; //0-terminated; 5.x.x + char fileEngineVersion[64]; //0-terminated; exact unity engine version + QWORD totalFileSize; + //sizes for the blocks info : + uint32_t compressedSize; + uint32_t decompressedSize; + //(flags & 0x3F) is the compression mode (0 = none; 1 = LZMA; 2-3 = LZ4) + //(flags & 0x40) says whether the bundle has directory info + //(flags & 0x80) says whether the block and directory list is at the end + uint32_t flags; + + ASSETSTOOLS_API bool ReadInitial(IAssetsReader *pReader, AssetsFileVerifyLogger errorLogger = NULL); + ASSETSTOOLS_API bool Read(IAssetsReader *pReader, AssetsFileVerifyLogger errorLogger = NULL); + ASSETSTOOLS_API bool Write(IAssetsWriter *pWriter, QWORD &curFilePos, AssetsFileVerifyLogger errorLogger = NULL); + inline QWORD GetBundleInfoOffset() + { + if (this->flags & 0x80) + { + if (this->totalFileSize == 0) + return -1; + return this->totalFileSize - this->compressedSize; + } + else + { + //if (!strcmp(this->signature, "UnityWeb") || !strcmp(this->signature, "UnityRaw")) + // return 9; + QWORD ret = strlen(minPlayerVersion) + strlen(fileEngineVersion) + 0x1A; + if (this->flags & 0x100) + ret = (ret + 0x0A); + else + ret = (ret + strlen(signature) + 1); + if (this->fileVersion >= 7) + ret = (ret + 15) & ~15; //16 byte alignment + return ret; + } + } + inline uint32_t GetFileDataOffset() + { + uint32_t ret = 0; + if (!strcmp(this->signature, "UnityArchive")) + return this->compressedSize; + else if (!strcmp(this->signature, "UnityFS") || !strcmp(this->signature, "UnityWeb")) + { + ret = (uint32_t)strlen(minPlayerVersion) + (uint32_t)strlen(fileEngineVersion) + 0x1A; + if (this->flags & 0x100) + ret += 0x0A; + else + ret += (uint32_t)strlen(signature) + 1; + if (this->fileVersion >= 7) + ret = (ret + 15) & ~15; //16 byte alignment + } + if (!(this->flags & 0x80)) + ret += this->compressedSize; + return ret; + } +}; + +struct AssetBundleHeader03 +{ + char signature[13]; //0-terminated; UnityWeb or UnityRaw + uint32_t fileVersion; //big-endian; 3 : Unity 3.5 and 4; + char minPlayerVersion[24]; //0-terminated; 3.x.x -> Unity 3.x.x/4.x.x; 5.x.x + char fileEngineVersion[64]; //0-terminated; exact unity engine version + uint32_t minimumStreamedBytes; //big-endian; not always the file's size + uint32_t bundleDataOffs; //big-endian; + uint32_t numberOfAssetsToDownload; //big-endian; + uint32_t blockCount; //big-endian; + struct AssetBundleOffsetPair *pBlockList; + uint32_t fileSize2; //big-endian; for fileVersion >= 2 + uint32_t unknown2; //big-endian; for fileVersion >= 3 + uint8_t unknown3; + + ASSETSTOOLS_API bool Read(IAssetsReader *pReader, AssetsFileVerifyLogger errorLogger = NULL); + ASSETSTOOLS_API bool Write(IAssetsWriter *pWriter, QWORD &curFilePos, AssetsFileVerifyLogger errorLogger = NULL); +}; + +struct AssetBundleEntry +{ + uint32_t offset; + uint32_t length; + char name[1]; + ASSETSTOOLS_API unsigned int GetAbsolutePos(AssetBundleHeader03 *pHeader);//, uint32_t listIndex); + ASSETSTOOLS_API unsigned int GetAbsolutePos(class AssetBundleFile *pFile);//, uint32_t listIndex); +}; +struct AssetsList +{ + uint32_t pos; + uint32_t count; + AssetBundleEntry **ppEntries; + uint32_t allocatedCount; + //AssetBundleEntry entries[0]; + ASSETSTOOLS_API void Free(); + ASSETSTOOLS_API bool Read(IAssetsReader *pReader, QWORD &curFilePos, AssetsFileVerifyLogger errorLogger = NULL); + ASSETSTOOLS_API bool Write(IAssetsWriter *pWriter, QWORD &curFilePos, AssetsFileVerifyLogger errorLogger = NULL); + ASSETSTOOLS_API bool Write(IAssetsReader *pReader, + IAssetsWriter *pWriter, bool doWriteAssets, QWORD &curReadPos, QWORD *curWritePos = NULL, + AssetsFileVerifyLogger errorLogger = NULL); +}; +struct AssetBundleOffsetPair +{ + uint32_t compressed; + uint32_t uncompressed; +}; +enum ECompressionTypes +{ + COMPRESS_NONE, + COMPRESS_LZMA, + COMPRESS_LZ4, //experimental, compressor may have bugs + COMPRESS_MAX +}; +class AssetBundleFile +{ + public: + union { + AssetBundleHeader03 bundleHeader3; + AssetBundleHeader06 bundleHeader6; + }; + union { + AssetsList *assetsLists3; + AssetBundleBlockAndDirectoryList06 *bundleInf6; + }; + + ASSETSTOOLS_API AssetBundleFile(); + ASSETSTOOLS_API ~AssetBundleFile(); + ASSETSTOOLS_API void Close(); + //allowCompressed : Don't fail if there are compressed blocks, or if the directory list exceeds maxDirectoryLen. + //maxDirectoryLen : If the file table is compressed, this specifies the maximum compressed and decompressed length for in-memory decompression. + ASSETSTOOLS_API bool Read(IAssetsReader *pReader, AssetsFileVerifyLogger errorLogger = NULL, bool allowCompressed = false, uint32_t maxDirectoryLen = 16*1024*1024); + ASSETSTOOLS_API bool IsCompressed(); + ASSETSTOOLS_API bool Write(IAssetsReader *pReader, + IAssetsWriter *pWriter, + class BundleReplacer **pReplacers, size_t replacerCount, + AssetsFileVerifyLogger errorLogger = NULL, ClassDatabaseFile *typeMeta = NULL); + ASSETSTOOLS_API bool Unpack(IAssetsReader *pReader, IAssetsWriter *pWriter); + //settings : array of ECompressionTypes values, or NULL for default settings; settings and fileTableCompression unused for version 3 bundles + ASSETSTOOLS_API bool Pack(IAssetsReader *pReader, IAssetsWriter *pWriter, ECompressionTypes *settings = NULL, ECompressionTypes fileTableCompression = COMPRESS_LZ4); + ASSETSTOOLS_API bool IsAssetsFile(IAssetsReader *pReader, AssetBundleDirectoryInfo06 *pEntry); + ASSETSTOOLS_API bool IsAssetsFile(IAssetsReader *pReader, AssetBundleEntry *pEntry); + ASSETSTOOLS_API bool IsAssetsFile(IAssetsReader *pReader, size_t entryIdx); + inline const char *GetEntryName(size_t entryIdx) + { + if (bundleHeader6.fileVersion >= 6) + return (bundleInf6 == nullptr || bundleInf6->directoryCount <= entryIdx) ? nullptr : bundleInf6->dirInf[entryIdx].name; + else if (bundleHeader6.fileVersion == 3) + return (assetsLists3 == nullptr || assetsLists3->count <= entryIdx) ? nullptr : assetsLists3->ppEntries[entryIdx]->name; + return nullptr; + } + ASSETSTOOLS_API IAssetsReader *MakeAssetsFileReader(IAssetsReader *pReader, AssetBundleDirectoryInfo06 *pEntry); + ASSETSTOOLS_API IAssetsReader *MakeAssetsFileReader(IAssetsReader *pReader, AssetBundleEntry *pEntry); +}; +ASSETSTOOLS_API void FreeAssetBundle_FileReader(IAssetsReader *pReader); + +#endif \ No newline at end of file diff --git a/AssetsTools/AssetBundleFileTable.cpp b/AssetsTools/AssetBundleFileTable.cpp new file mode 100644 index 0000000..d129fbb --- /dev/null +++ b/AssetsTools/AssetBundleFileTable.cpp @@ -0,0 +1,906 @@ +#include "stdafx.h" +#include "AssetsFileFormat.h" +#include "AssetBundleFileTable.h" +#include "AssetsFileReader.h" +#include "AssetTypeClass.h" + +#define fmread(target,count) {memcpy(target, &((uint8_t*)data)[*filePos], count); *filePos = *filePos + count;} +#define fmwrite(source,count) {if ((filePos+count)>bufferLen){return false;} memcpy(&((uint8_t*)buffer)[filePos], source, count); filePos = filePos + count;} +#define _fmalign(fpos) (fpos + 3) & ~3 +#define fmalign() {*filePos = _fmalign(*filePos);} +#define fmwalign() {int newFilePos = _fmalign(filePos); memset(&((uint8_t*)buffer)[filePos], 0, newFilePos-filePos); filePos = newFilePos;} + +char _nullChar = 0; +ASSETSTOOLS_API void AssetBundleAsset::FlushChanges() +{ + if (isRead && unityVersion == -1) + { + AssetTypeValueField *pFile = pAssetType->GetBaseField(); + + AssetTypeValueField *pPreloadTable = pFile ? pFile->Get("m_PreloadTable")->Get(0U) : NULL; + if (pPreloadTable && (preloadArrayLen > pPreloadTable->GetChildrenCount())) + { + //AssetTypeInstance::SetChildList frees this memory + void *pNewValueList = malloc(preloadArrayLen * (3 * sizeof(AssetTypeValueField*) + 3 * sizeof(AssetTypeValueField) + 2 * sizeof(AssetTypeValue))); + if (pNewValueList != NULL) + { + AssetTypeValueField** basePointerList = (AssetTypeValueField**)pNewValueList; + AssetTypeValueField** varPointerList = (AssetTypeValueField**)(&((uint8_t*)pNewValueList)[preloadArrayLen * sizeof(AssetTypeValueField*)]); + AssetTypeValueField* valueFields = (AssetTypeValueField*)(&((uint8_t*)pNewValueList)[preloadArrayLen * 3 * sizeof(AssetTypeValueField*)]); + AssetTypeValue* values = (AssetTypeValue*)(&((uint8_t*)pNewValueList)[preloadArrayLen * (3 * sizeof(AssetTypeValueField*) + 3 * sizeof(AssetTypeValueField))]); + + for (uint32_t i = 0; i < preloadArrayLen; i++) + { + basePointerList[i] = &valueFields[i*3]; + + varPointerList[i*2] = &valueFields[i*3+1]; + varPointerList[i*2+1] = &valueFields[i*3+2]; + + valueFields[i*3].Read(NULL, &pPreloadTable->GetTemplateField()->children[1], 2, &varPointerList[i*2]); //data + + valueFields[i*3+1].Read(&values[i*2], &pPreloadTable->GetTemplateField()->children[1].children[0], 0, NULL); //fileID + valueFields[i*3+2].Read(&values[i*2+1], &pPreloadTable->GetTemplateField()->children[1].children[1], 0, NULL); //pathID + + values[i*2] = AssetTypeValue(pPreloadTable->GetTemplateField()->children[1].children[0].valueType, &preloadArray[i].fileId); + values[i*2+1] = AssetTypeValue(pPreloadTable->GetTemplateField()->children[1].children[1].valueType, &preloadArray[i].pathId); + } + + pAssetType->SetChildList(pPreloadTable, basePointerList, preloadArrayLen, true); + if (pPreloadTable->GetValue() != NULL) + pPreloadTable->GetValue()->AsArray()->size = preloadArrayLen; + } + } + else + { + pPreloadTable->SetChildrenList(pPreloadTable->GetChildrenList(), pPreloadTable->GetChildrenCount()); + if (pPreloadTable->GetValue() != NULL) + pPreloadTable->GetValue()->AsArray()->size = preloadArrayLen; + for (uint32_t i = 0; i < preloadArrayLen; i++) + { + { + AssetTypeValue valueTmp = AssetTypeValue(pPreloadTable->GetTemplateField()->children[1].children[0].valueType, &preloadArray[i].fileId); + memcpy(pPreloadTable->Get(i)->Get(0U)->GetValue(), &valueTmp, sizeof(AssetTypeValue)); + } + { + AssetTypeValue valueTmp = AssetTypeValue(pPreloadTable->GetTemplateField()->children[1].children[1].valueType, &preloadArray[i].pathId); + memcpy(pPreloadTable->Get(i)->Get(1)->GetValue(), &valueTmp, sizeof(AssetTypeValue)); + } + } + } + + AssetTypeValueField *pContainerTable = pFile->Get("m_Container")->Get(0U); + if (containerArrayLen > pPreloadTable->GetChildrenCount()) + { + //AssetTypeInstance::SetChildList frees this memory + void *pNewValueList = malloc( + containerArrayLen * (8 * sizeof(AssetTypeValueField*) + 8 * sizeof(AssetTypeValueField) + 5 * sizeof(AssetTypeValue)) + + 1); + if (pNewValueList != NULL) + { + AssetTypeValueField** dataPointerList = (AssetTypeValueField**)pNewValueList; + AssetTypeValueField** varPointerList = (AssetTypeValueField**)(&((uint8_t*)pNewValueList)[containerArrayLen * sizeof(AssetTypeValueField*)]); + AssetTypeValueField* valueFields = (AssetTypeValueField*)(&((uint8_t*)pNewValueList)[containerArrayLen * 8 * sizeof(AssetTypeValueField*)]); + AssetTypeValue* values = (AssetTypeValue*)(&((uint8_t*)pNewValueList)[containerArrayLen * (8 * sizeof(AssetTypeValueField*) + 8 * sizeof(AssetTypeValueField))]); + char* nullChar = (char*)(&((uint8_t*)pNewValueList)[containerArrayLen * (8 * sizeof(AssetTypeValueField*) + 8 * sizeof(AssetTypeValueField) + 5 * sizeof(AssetTypeValue))]); + nullChar[0] = 0; + + for (uint32_t i = 0; i < containerArrayLen; i++) + { + dataPointerList[i] = &valueFields[i*8]; + + varPointerList[i*7] = &valueFields[i*8+1]; //data.first (string) + varPointerList[i*7+1] = &valueFields[i*8+2]; //data.second (AssetInfo) + + varPointerList[i*7+2] = &valueFields[i*8+3]; //data.second.preloadIndex + varPointerList[i*7+3] = &valueFields[i*8+4]; //data.second.preloadSize + varPointerList[i*7+4] = &valueFields[i*8+5]; //data.second.asset (PPtr) + + varPointerList[i*7+5] = &valueFields[i*8+6]; //data.second.asset.fileID + varPointerList[i*7+6] = &valueFields[i*8+7]; //data.second.asset.pathID + + valueFields[i*8].Read(NULL, &pContainerTable->GetTemplateField()->children[1], 2, &varPointerList[i*7]); //data + + valueFields[i*8+1].Read(&values[i*5], &pContainerTable->GetTemplateField()->children[1].children[0], 0, NULL); //data.first (string) + valueFields[i*8+2].Read(NULL, &pContainerTable->GetTemplateField()->children[1].children[1], 3, &varPointerList[i*7+2]); //data.second (AssetInfo) + valueFields[i*8+3].Read(&values[i*5+1], &pContainerTable->GetTemplateField()->children[1].children[1].children[0], 0, NULL); //data.second.preloadIndex + valueFields[i*8+4].Read(&values[i*5+2], &pContainerTable->GetTemplateField()->children[1].children[1].children[1], 0, NULL); //data.second.preloadSize + valueFields[i*8+5].Read(NULL, &pContainerTable->GetTemplateField()->children[1].children[1].children[2], 2, &varPointerList[i*7+5]); //data.second.asset (PPtr) + valueFields[i*8+6].Read(&values[i*5+3], &pContainerTable->GetTemplateField()->children[1].children[1].children[2].children[0], 0, NULL); //data.second.asset.fileID + valueFields[i*8+7].Read(&values[i*5+4], &pContainerTable->GetTemplateField()->children[1].children[1].children[2].children[1], 0, NULL); //data.second.asset.pathID + + char *curName = containerArray[i].name; + if (curName == NULL) + curName = nullChar; + values[i*5] = AssetTypeValue(ValueType_String, curName); + values[i*5+1] = AssetTypeValue(pContainerTable->GetTemplateField()->children[1].children[1].children[0].valueType, &containerArray[i].preloadIndex); + values[i*5+2] = AssetTypeValue(pContainerTable->GetTemplateField()->children[1].children[1].children[1].valueType, &containerArray[i].preloadSize); + values[i*5+3] = AssetTypeValue(pContainerTable->GetTemplateField()->children[1].children[1].children[2].children[0].valueType, &containerArray[i].ids.fileId); + values[i*5+4] = AssetTypeValue(pContainerTable->GetTemplateField()->children[1].children[1].children[2].children[1].valueType, &containerArray[i].ids.pathId); + } + + pAssetType->SetChildList(pContainerTable, dataPointerList, containerArrayLen, true); + if (pContainerTable->GetValue() != NULL) + pContainerTable->GetValue()->AsArray()->size = containerArrayLen; + } + } + else + { + pContainerTable->SetChildrenList(pContainerTable->GetChildrenList(), pContainerTable->GetChildrenCount()); + if (pContainerTable->GetValue() != NULL) + pContainerTable->GetValue()->AsArray()->size = containerArrayLen; + for (uint32_t i = 0; i < containerArrayLen; i++) + { + { + char *curName = containerArray[i].name; + if (curName == NULL) + curName = &_nullChar; + AssetTypeValue valueTmp = AssetTypeValue(pContainerTable->GetTemplateField()->children[1].children[0].valueType, curName); + memcpy(pContainerTable->Get(i)->Get(0U)->GetValue(), &valueTmp, sizeof(AssetTypeValue)); + } + { + AssetTypeValue valueTmp = AssetTypeValue( + pContainerTable->GetTemplateField()->children[1].children[1].children[0].valueType, &containerArray[i].preloadIndex); + memcpy(pContainerTable->Get(i)->Get(1)->Get(0U)->GetValue(), &valueTmp, sizeof(AssetTypeValue)); + } + { + AssetTypeValue valueTmp = AssetTypeValue( + pContainerTable->GetTemplateField()->children[1].children[1].children[1].valueType, &containerArray[i].preloadSize); + memcpy(pContainerTable->Get(i)->Get(1)->Get(1)->GetValue(), &valueTmp, sizeof(AssetTypeValue)); + } + { + AssetTypeValue valueTmp = AssetTypeValue( + pContainerTable->GetTemplateField()->children[1].children[1].children[2].children[0].valueType, &containerArray[i].ids.fileId); + memcpy(pContainerTable->Get(i)->Get(1)->Get(2)->Get(0U)->GetValue(), &valueTmp, sizeof(AssetTypeValue)); + } + { + AssetTypeValue valueTmp = AssetTypeValue( + pContainerTable->GetTemplateField()->children[1].children[1].children[2].children[1].valueType, &containerArray[i].ids.pathId); + memcpy(pContainerTable->Get(i)->Get(1)->Get(2)->Get(1)->GetValue(), &valueTmp, sizeof(AssetTypeValue)); + } + } + } + } +} + +ASSETSTOOLS_API bool AssetBundleAsset::WriteBundleFile(void *buffer, size_t bufferLen, size_t *size, bool bigEndian) +{ + if (!isRead || !pAssetType->GetBaseField()) + return false; + if (unityVersion == -1) + { + IAssetsWriter *pWriter = Create_AssetsWriterToMemory(buffer, (QWORD)bufferLen); + if (pWriter == NULL) + return false; + *size = (int)pAssetType->GetBaseField()->Write(pWriter, 0, bigEndian); + Free_AssetsWriter(pWriter); + return true; + } + int filePos = 0; + int iTmp = (int)strlen(name); + fmwrite(&iTmp, 4); + fmwrite(name, iTmp); + fmwalign(); + + fmwrite(&preloadArrayLen, 4); + for (uint32_t i = 0; i < preloadArrayLen; i++) + { + fmwrite(&preloadArray[i].fileId, 4); + fmwrite(&preloadArray[i].pathId, ((unityVersion>=0x0E)?8:4)); + } + + fmwrite(&containerArrayLen, 4); + for (uint32_t i = 0; i < containerArrayLen; i++) + { + ContainerData *cd = &containerArray[i]; + + if (cd->name != NULL) + { + iTmp = (int)strlen(cd->name); + fmwrite(&iTmp, 4); + fmwrite(cd->name, iTmp); + } + else + { + iTmp = 0; + fmwrite(&iTmp, 4); + } + fmwalign(); + + fmwrite(&cd->preloadIndex, 4); + fmwrite(&cd->preloadSize, 4); + fmwrite(&cd->ids.fileId, 4); + fmwrite(&cd->ids.pathId, ((unityVersion>=0x0E)?8:4)); + } + + { + ContainerData *cd = &mainAsset; + + fmwrite(&cd->preloadIndex, 4); + fmwrite(&cd->preloadSize, 4); + fmwrite(&cd->ids.fileId, 4); + fmwrite(&cd->ids.pathId, ((unityVersion>=0x0E)?8:4)); + } + + if (unityVersion < 0x0E) + { + fmwrite(&scriptCompatibilityArrayLen, 4); + for (int i = 0; i < scriptCompatibilityArrayLen; i++) + { + ScriptCompatibilityData *scd = &scriptCompatibilityArray[i]; + + iTmp = (int)strlen(scd->className); + fmwrite(&iTmp, 4); + fmwrite(scd->className, iTmp); + fmwalign(); + + iTmp = (int)strlen(scd->namespaceName); + fmwrite(&iTmp, 4); + fmwrite(scd->namespaceName, iTmp); + fmwalign(); + + iTmp = (int)strlen(scd->assemblyName); + fmwrite(&iTmp, 4); + fmwrite(scd->assemblyName, iTmp); + fmwalign(); + + fmwrite(&scd->hash, 4); + } + + fmwrite(&classCompatibilityArrayLen, 4); + for (int i = 0; i < classCompatibilityArrayLen; i++) + { + fmwrite(&classCompatibilityArray[i].first, 4); + fmwrite(&classCompatibilityArray[i].second, 4); + } + } + + fmwrite(&runtimeCompatibility, 4); + + if (unityVersion >= 0x0E) + { + iTmp = (int)strlen(assetBundleName); + fmwrite(&iTmp, 4); + fmwrite(assetBundleName, iTmp); + fmwalign(); + + fmwrite(&dependenciesArrayLen, 4); + for (int i = 0; i < dependenciesArrayLen; i++) + { + iTmp = (int)strlen(dependencies[i]); + fmwrite(&iTmp, 4); + fmwrite(dependencies[i], iTmp); + fmwalign(); + } + + iTmp = (int)isStreamedSceneAssetBundle; + fmwrite(&iTmp, 4); + } + + *size = filePos; + return true; +} + +ASSETSTOOLS_API int AssetBundleAsset::GetFileSize() +{ + if (!isRead || !this->pAssetType->GetBaseField()) + return -1; + if (unityVersion == -1) + { + return (int)this->pAssetType->GetBaseField()->GetByteSize();//this->pBaseValueField->GetByteSize(); + } + int ret = 0; + ret += 4; //strlen(name) + if (name) + ret += _fmalign(strlen(name)); + ret += 4; //preloadArrayLen + if (preloadArray) + ret += preloadArrayLen * ((unityVersion>=0x0E)?12:8); + ret += 4; //containerArrayLen + if (containerArray) + { + for (uint32_t i = 0; i < containerArrayLen; i++) + { + ret += 4; //strlen(name) + if (containerArray[i].name) + ret += _fmalign(strlen(containerArray[i].name)); + } + ret += (8 + ((unityVersion>=0x0E)?12:8)) * containerArrayLen; + } + + ret += (8 + ((unityVersion>=0x0E)?12:8)); + + if (unityVersion < 0x0E) + { + ret += 4; //scriptCompatibilityArrayLen + if (scriptCompatibilityArray) + { + for (int i = 0; i < scriptCompatibilityArrayLen; i++) + { + ScriptCompatibilityData *sca = &scriptCompatibilityArray[i]; + ret += 4; //strlen(className) + if (sca->className) + ret += _fmalign(strlen(sca->className)); + ret += 4; //strlen(namespaceName) + if (sca->namespaceName) + ret += _fmalign(strlen(sca->namespaceName)); + ret += 4; //strlen(assemblyName) + if (sca->assemblyName) + ret += _fmalign(strlen(sca->assemblyName)); + + ret += 4; //hash + } + } + + ret += 4; //classCompatibilityArrayLen + if (classCompatibilityArray) + ret += classCompatibilityArrayLen * sizeof(ClassCompatibilityData); + } + + ret += 4; //runtimeCompatibility + + if (unityVersion >= 0x0E) + { + ret += 4; //strlen(assetBundleName) + if (assetBundleName != NULL) + ret += _fmalign(strlen(assetBundleName)); + + ret += 4; //dependenciesArrayLen + for (int i = 0; i < dependenciesArrayLen; i++) + { + ret += 4; //strlen(dependencies[i]) + if (dependencies[i] != NULL) + ret += _fmalign(strlen(dependencies[i])); + } + + ret += _fmalign(1); //isStreamedSceneAssetBundle + } + return ret; +} + +ASSETSTOOLS_API AssetBundleAsset::AssetBundleAsset() +{ + isModified = false; + isRead = false; + unityVersion = 0; + pAssetType = NULL; + //pBaseValueField = NULL; + name = NULL; + preloadArrayLen = 0; + preloadArray = NULL; + containerArrayLen = 0; + containerArray = NULL; + memset(&mainAsset, 0, sizeof(ContainerData)); + scriptCompatibilityArrayLen = 0; + scriptCompatibilityArray = NULL; + classCompatibilityArrayLen = 0; + classCompatibilityArray = NULL; + runtimeCompatibility = 0; + assetBundleName = NULL; + dependenciesArrayLen = 0; + dependencies = NULL; +} + +ASSETSTOOLS_API void AssetBundleAsset::Clear() +{ + if (!isRead) + return; + isRead = false; + if (pAssetType != NULL) + { + delete pAssetType; pAssetType = NULL; + } + if (name != NULL) + { + delete[] name; name = NULL; + } + if (preloadArray != NULL) + { + delete[] preloadArray; preloadArray = NULL; + preloadArrayLen = 0; + } + if (containerArray != NULL) + { + for (uint32_t i = 0; i < containerArrayLen; i++) + { + if (containerArray[i].name != NULL) + delete[] containerArray[i].name; + } + delete[] containerArray; containerArray = NULL; + containerArrayLen = 0; + } + if (scriptCompatibilityArray != NULL) + { + for (int i = 0; i < scriptCompatibilityArrayLen; i++) + { + ScriptCompatibilityData *sca = &scriptCompatibilityArray[i]; + if (sca->className != NULL) + delete[] sca->className; + if (sca->namespaceName != NULL) + delete[] sca->namespaceName; + if (sca->assemblyName != NULL) + delete[] sca->assemblyName; + } + delete[] scriptCompatibilityArray; scriptCompatibilityArray = NULL; + scriptCompatibilityArrayLen = 0; + } + if (classCompatibilityArray != NULL) + { + delete[] classCompatibilityArray; classCompatibilityArray = NULL; + classCompatibilityArrayLen = 0; + } + if (assetBundleName != NULL) + { + delete[] assetBundleName; assetBundleName = NULL; + } + if (dependencies != NULL) + { + for (int i = 0; i < dependenciesArrayLen; i++) + delete[] dependencies[i]; + delete[] dependencies; dependencies = NULL; + dependenciesArrayLen = 0; + } +} + +ASSETSTOOLS_API AssetBundleAsset::~AssetBundleAsset() +{ + this->Clear(); +} + +ASSETSTOOLS_API bool AssetBundleAsset::ReadBundleFile(void *data, size_t dataLen, size_t *filePos, AssetTypeTemplateField *pBaseField, bool bigEndian) +{ + Clear(); + unityVersion = -1; + IAssetsReader *pReader = Create_AssetsReaderFromMemory(data, dataLen, false); + if (pReader == NULL) + return false; + + AssetTypeInstance *pType = new AssetTypeInstance(1, &pBaseField, (QWORD)dataLen, pReader, bigEndian); + //pBaseField->MakeValue(pReader, 0, &pFile); + Free_AssetsReader(pReader); + //this->pBaseValueField = pFile; + AssetTypeValueField *pFile = pType->GetBaseField(); + if (!pFile || pFile->IsDummy()) + { + delete pType; + return false; + } + this->pAssetType = pType; + + AssetTypeValue *nameValue = (*pFile)["m_Name"]->GetValue(); + if (nameValue != NULL) + { + char *nameString = nameValue->AsString(); + name = new char[strlen(nameString)+1]; + strcpy(name, nameString); + //name = nameValue->AsString(); + } + else + name = NULL; + AssetTypeValueField *preloadTable = pFile->Get("m_PreloadTable")->Get(0U); //Base.m_PreloadTable.Array + if (preloadTable->IsDummy()) + preloadArrayLen = 0; + else + preloadArrayLen = preloadTable->GetChildrenCount(); + preloadArray = new PreloadData[preloadArrayLen]; + for (uint32_t i = 0; i < preloadArrayLen; i++) + { + AssetTypeValueField *dataItem = preloadTable->Get(i); + AssetTypeValueField *fileIDItem = (*dataItem)["m_FileID"]; + AssetTypeValueField *pathIDItem = (*dataItem)["m_PathID"]; + if (fileIDItem->GetValue()) + preloadArray[i].fileId = fileIDItem->GetValue()->AsInt(); + else + preloadArray[i].fileId = 0; + if (pathIDItem->GetValue()) + preloadArray[i].pathId = pathIDItem->GetValue()->AsInt64(); + else + preloadArray[i].pathId = 0; + } + + AssetTypeValueField *pContainerList = pFile->Get("m_Container")->Get(0U); + if (pContainerList->IsDummy()) + containerArrayLen = 0; + else + containerArrayLen = pContainerList->GetChildrenCount(); + containerArray = new ContainerData[containerArrayLen]; + memset(containerArray, 0, containerArrayLen * sizeof(ContainerData)); + for (uint32_t i = 0; i < containerArrayLen; i++) + { + AssetTypeValueField *dataItem = pContainerList->Get(i); + AssetTypeValueField *nameItem = (*dataItem)["first"]; + if (nameItem->GetValue()) + { + char *nameString = nameItem->GetValue()->AsString(); + containerArray[i].name = new char[strlen(nameString)+1]; + strcpy(containerArray[i].name, nameString); + //containerArray[i].name = nameItem->GetValue()->AsString(); + } + else + containerArray[i].name = NULL; + AssetTypeValueField *assetItem = (*dataItem)["second"]; + AssetTypeValueField *preloadIndexItem = (*assetItem)["preloadIndex"]; + if (preloadIndexItem->GetValue()) + containerArray[i].preloadIndex = preloadIndexItem->GetValue()->AsInt(); + AssetTypeValueField *preloadSizeItem = (*assetItem)["preloadSize"]; + if (preloadSizeItem->GetValue()) + containerArray[i].preloadSize = preloadSizeItem->GetValue()->AsInt(); + AssetTypeValueField *fileIDItem = (*assetItem)["asset"]->Get("m_FileID"); + if (fileIDItem->GetValue()) + containerArray[i].ids.fileId = fileIDItem->GetValue()->AsInt(); + AssetTypeValueField *pathIDItem = (*assetItem)["asset"]->Get("m_PathID"); + if (pathIDItem->GetValue()) + containerArray[i].ids.pathId = pathIDItem->GetValue()->AsInt64(); + } + + {//main asset + + AssetTypeValueField *mainAssetItem = pFile->Get("m_MainAsset"); + mainAsset.name = NULL; + + AssetTypeValueField *preloadIndexItem = (*mainAssetItem)["preloadIndex"]; + if (preloadIndexItem->GetValue()) + mainAsset.preloadIndex = preloadIndexItem->GetValue()->AsInt(); + AssetTypeValueField *preloadSizeItem = (*mainAssetItem)["preloadSize"]; + if (preloadSizeItem->GetValue()) + mainAsset.preloadSize = preloadSizeItem->GetValue()->AsInt(); + AssetTypeValueField *fileIDItem = (*mainAssetItem)["asset"]->Get("m_FileID"); + if (fileIDItem->GetValue()) + mainAsset.ids.fileId = fileIDItem->GetValue()->AsInt(); + AssetTypeValueField *pathIDItem = (*mainAssetItem)["asset"]->Get("m_PathID"); + if (pathIDItem->GetValue()) + mainAsset.ids.pathId = pathIDItem->GetValue()->AsInt64(); + } + AssetTypeValueField *runtimeCompatibilityItem = pFile->Get("m_RuntimeCompatibility"); + if (runtimeCompatibilityItem->GetValue()) + runtimeCompatibility = runtimeCompatibilityItem->GetValue()->AsUInt(); + isRead = true; + return true; +} +ASSETSTOOLS_API void AssetBundleAsset::ReadBundleFile(void *data, size_t dataLen, size_t *filePos, int assetsVersion, bool bigEndian) +{ + Clear(); + unityVersion = assetsVersion; + isModified = false; + { + int nameSize; fmread(&nameSize, 4); + if (bigEndian) + SwapEndians_(nameSize); + name = new char[nameSize+1]; + fmread(name, nameSize); name[nameSize] = 0; + fmalign(); + } + + fmread(&preloadArrayLen, 4); + if (bigEndian) + SwapEndians_(preloadArrayLen); + preloadArray = new PreloadData[preloadArrayLen]; + for (uint32_t i = 0; i < preloadArrayLen; i++) + { + fmread(&preloadArray[i].fileId, 4); + if (bigEndian) + SwapEndians_(preloadArray[i].fileId); + preloadArray[i].pathId = 0; + if (assetsVersion>=0x0E) + { + fmread(&preloadArray[i].pathId, 8); + if (bigEndian) + SwapEndians_(preloadArray[i].pathId); + } + else + { + fmread(&preloadArray[i].pathId, 4); + if (bigEndian) + SwapEndians_(*(uint32_t*)&preloadArray[i].pathId); + } + } + + fmread(&containerArrayLen, 4); + if (bigEndian) + SwapEndians_(containerArrayLen); + containerArray = new ContainerData[containerArrayLen]; + for (uint32_t i = 0; i < containerArrayLen; i++) + { + int assetNameSize; fmread(&assetNameSize, 4); + if (bigEndian) + SwapEndians_(assetNameSize); + char *assetName = new char[assetNameSize+1]; + fmread(assetName, assetNameSize); assetName[assetNameSize] = 0; + containerArray[i].name = assetName; + fmalign(); + + fmread(&containerArray[i].preloadIndex, 4); + if (bigEndian) + SwapEndians_(containerArray[i].preloadIndex); + fmread(&containerArray[i].preloadSize, 4); + if (bigEndian) + SwapEndians_(containerArray[i].preloadSize); + fmread(&containerArray[i].ids.fileId, 4); + if (bigEndian) + SwapEndians_(containerArray[i].ids.fileId); + containerArray[i].ids.pathId = 0; + if (assetsVersion>=0x0E) + { + fmread(&containerArray[i].ids.pathId, 8); + if (bigEndian) + SwapEndians_(containerArray[i].ids.pathId); + } + else + { + fmread(&containerArray[i].ids.pathId, 4); + if (bigEndian) + SwapEndians_(*(uint32_t*)&containerArray[i].ids.pathId); + } + } + + {//main asset + mainAsset.name = NULL; + fmread(&mainAsset.preloadIndex, 4); + if (bigEndian) + SwapEndians_(mainAsset.preloadIndex); + fmread(&mainAsset.preloadSize, 4); + if (bigEndian) + SwapEndians_(mainAsset.preloadSize); + fmread(&mainAsset.ids.fileId, 4); + if (bigEndian) + SwapEndians_(mainAsset.ids.fileId); + mainAsset.ids.pathId = 0; + if (assetsVersion>=0x0E) + { + fmread(&mainAsset.ids.pathId, 8); + if (bigEndian) + SwapEndians_(mainAsset.ids.pathId); + } + else + { + fmread(&mainAsset.ids.pathId, 4); + if (bigEndian) + SwapEndians_(*(uint32_t*)&mainAsset.ids.pathId); + } + } + + if (assetsVersion < 0x0E) + { + fmread(&scriptCompatibilityArrayLen, 4); + if (bigEndian) + SwapEndians_(scriptCompatibilityArrayLen); + scriptCompatibilityArray = new ScriptCompatibilityData[scriptCompatibilityArrayLen]; + for (int i = 0; i < scriptCompatibilityArrayLen; i++) + { + int classNameSize; fmread(&classNameSize, 4); + if (bigEndian) + SwapEndians_(classNameSize); + char *className = new char[classNameSize+1]; + fmread(className, classNameSize); className[classNameSize] = 0; + scriptCompatibilityArray[i].className = className; + fmalign(); + + int nameSpaceSize; fmread(&nameSpaceSize, 4); + if (bigEndian) + SwapEndians_(nameSpaceSize); + char *nameSpace = new char[nameSpaceSize+1]; + fmread(nameSpace, nameSpaceSize); nameSpace[nameSpaceSize] = 0; + scriptCompatibilityArray[i].namespaceName = nameSpace; + fmalign(); + + int assemblyNameSize; fmread(&assemblyNameSize, 4); + if (bigEndian) + SwapEndians_(assemblyNameSize); + char *assemblyName = new char[assemblyNameSize+1]; + fmread(assemblyName, assemblyNameSize); assemblyName[assemblyNameSize] = 0; + scriptCompatibilityArray[i].assemblyName = assemblyName; + fmalign(); + + unsigned int hash; fmread(&hash, 4); + if (bigEndian) + SwapEndians_(hash); + scriptCompatibilityArray[i].hash = hash; + } + + fmread(&classCompatibilityArrayLen, 4); + if (bigEndian) + SwapEndians_(classCompatibilityArrayLen); + classCompatibilityArray = new ClassCompatibilityData[classCompatibilityArrayLen]; + for (int i = 0; i < classCompatibilityArrayLen; i++) + { + fmread(&classCompatibilityArray[i].first, 4); + if (bigEndian) + SwapEndians_(classCompatibilityArray[i].first); + fmread(&classCompatibilityArray[i].second, 4); + if (bigEndian) + SwapEndians_(classCompatibilityArray[i].second); + } + } + else + { + scriptCompatibilityArrayLen = 0; + scriptCompatibilityArray = NULL; + classCompatibilityArrayLen = 0; + classCompatibilityArray = NULL; + } + fmread(&this->runtimeCompatibility, 4); + if (bigEndian) + SwapEndians_(this->runtimeCompatibility); + if (assetsVersion >= 0x0E) + { + int assetBundleNameSize; fmread(&assetBundleNameSize, 4); + if (bigEndian) + SwapEndians_(assetBundleNameSize); + assetBundleName = new char[assetBundleNameSize+1]; + fmread(assetBundleName, assetBundleNameSize); assetBundleName[assetBundleNameSize] = 0; + fmalign(); + + fmread(&dependenciesArrayLen, 4); + if (bigEndian) + SwapEndians_(dependenciesArrayLen); + dependencies = new char*[dependenciesArrayLen]; + for (int i = 0; i < dependenciesArrayLen; i++) + { + int dependencyNameSize; fmread(&dependencyNameSize, 4); + if (bigEndian) + SwapEndians_(dependencyNameSize); + dependencies[i] = new char[dependencyNameSize+1]; + fmread(dependencies[i], dependencyNameSize); dependencies[i][dependencyNameSize] = 0; + fmalign(); + } + + fmread(&isStreamedSceneAssetBundle, 1); + uint32_t dwTmp; + fmread(&dwTmp, 3); + } + isRead = true; +} + + +ASSETSTOOLS_API void ASSETBUNDLEASSET_DecreaseIndexRefs(AssetBundleAsset *pFile, int iToRemove) +{ + for (uint32_t i = 0; i < pFile->containerArrayLen; i++) + { + ContainerData *cd = &pFile->containerArray[i]; + int minIndex = cd->preloadIndex; + if ((minIndex > iToRemove)) + { + cd->preloadIndex--; + } + } +} +ASSETSTOOLS_API void ASSETBUNDLEASSET_IncreaseIndexRefs(AssetBundleAsset *pFile, int iToAdd) +{ + for (uint32_t i = 0; i < pFile->containerArrayLen; i++) + { + ContainerData *cd = &pFile->containerArray[i]; + int minIndex = cd->preloadIndex; + if ((minIndex >= iToAdd)) + { + cd->preloadIndex++; + } + } +} +ASSETSTOOLS_API void ASSETBUNDLEASSET_Optimize(AssetBundleAsset *pFile) +{ + for (uint32_t i = 0; i < pFile->preloadArrayLen; i++) + { + PreloadData *pd = &pFile->preloadArray[i]; + bool found = false; + for (uint32_t k = 0; k < pFile->containerArrayLen; k++) + { + ContainerData *cd = &pFile->containerArray[k]; + uint32_t minIndex = (uint32_t)cd->preloadIndex; + uint32_t maxIndex = (uint32_t)(minIndex + cd->preloadSize - 1); + if (minIndex > maxIndex) + continue; + if ((i >= minIndex) && (i <= maxIndex)) + { + found = true; + break; + } + } + if (!found) + { + ASSETBUNDLEASSET_DecreaseIndexRefs(pFile, i); + PreloadData *newPd = new PreloadData[pFile->preloadArrayLen-1]; + if (i > 0) + memcpy(newPd, pFile->preloadArray, sizeof(PreloadData) * (i-1)); + for (uint32_t _i = i+1; _i < pFile->preloadArrayLen; _i++) + memcpy(&newPd[_i-1], &pFile->preloadArray[_i], sizeof(PreloadData)); + pFile->preloadArrayLen--; + delete[] pFile->preloadArray; + pFile->preloadArray = newPd; + i--; + } + } +} +ASSETSTOOLS_API int ASSETBUNDLEASSET_GetRefCount(AssetBundleAsset *pFile, int preloadIndex) +{ + int ret = 0; + bool found = false; + for (uint32_t k = 0; k < pFile->containerArrayLen; k++) + { + ContainerData *cd = &pFile->containerArray[k]; + int minIndex = cd->preloadIndex; + int maxIndex = minIndex + cd->preloadSize - 1; + if (minIndex > maxIndex) + continue; + if ((preloadIndex >= minIndex) && (preloadIndex <= maxIndex)) + { + ret++; + break; + } + } + return ret; +} + +ASSETSTOOLS_API int AssetBundleAsset::AddContainer(ContainerData *cd) +{ + PreloadData *newPd = new PreloadData[preloadArrayLen+1]; + memcpy(newPd, preloadArray, sizeof(PreloadData) * preloadArrayLen); + + newPd[preloadArrayLen].fileId = cd->ids.fileId; + newPd[preloadArrayLen].pathId = cd->ids.pathId; + int preloadIndex = preloadArrayLen; + + preloadArrayLen++; + delete[] preloadArray; + preloadArray = newPd; + + ContainerData *newCd = new ContainerData[containerArrayLen+1]; + memcpy(newCd, containerArray, sizeof(ContainerData) * containerArrayLen); + + newCd[containerArrayLen].preloadIndex = preloadIndex; + newCd[containerArrayLen].preloadSize = 1; + newCd[containerArrayLen].name = cd->name; + memcpy(&newCd[containerArrayLen].ids, &cd->ids, sizeof(PreloadData)); + + containerArrayLen++; + delete[] containerArray; + containerArray = newCd; + + return (preloadArrayLen-1); +} +ASSETSTOOLS_API void AssetBundleAsset::UpdatePreloadArray(uint32_t containerIndex) +{ + if ((containerArrayLen > containerIndex) && (containerIndex >= 0)) + { + ContainerData *cd = &containerArray[containerIndex]; + if (preloadArrayLen > (uint32_t)cd->preloadIndex) + { + if (ASSETBUNDLEASSET_GetRefCount(this, cd->preloadIndex) == 1) + { + preloadArray[cd->preloadIndex].fileId = cd->ids.fileId; + preloadArray[cd->preloadIndex].pathId = cd->ids.pathId; + } + else + { + PreloadData *newPd = new PreloadData[preloadArrayLen+cd->preloadSize]; + memcpy(newPd, preloadArray, sizeof(PreloadData)*preloadArrayLen); + memcpy(&newPd[preloadArrayLen], &newPd[cd->preloadIndex], sizeof(PreloadData)*cd->preloadSize); + + newPd[preloadArrayLen].fileId = cd->ids.fileId; + newPd[preloadArrayLen].pathId = cd->ids.pathId; + cd->preloadIndex = preloadArrayLen; + preloadArrayLen += cd->preloadSize; + + delete[] preloadArray; + preloadArray = newPd; + + ASSETBUNDLEASSET_Optimize(this); + } + } + } +} +ASSETSTOOLS_API void AssetBundleAsset::RemoveContainer(uint32_t index) +{ + if ((containerArrayLen > index) && index >= 0) + { + ContainerData *newCd = new ContainerData[containerArrayLen-1]; + + if (index > 0) + memcpy(newCd, containerArray, sizeof(ContainerData) * (index)); + for (uint32_t _i = index+1; _i < containerArrayLen; _i++) + memcpy(&newCd[_i-1], &containerArray[_i], sizeof(ContainerData)); + + containerArrayLen--; + delete[] containerArray; + containerArray = newCd; + ASSETBUNDLEASSET_Optimize(this); + } +} \ No newline at end of file diff --git a/AssetsTools/AssetBundleFileTable.h b/AssetsTools/AssetBundleFileTable.h new file mode 100644 index 0000000..896ff39 --- /dev/null +++ b/AssetsTools/AssetBundleFileTable.h @@ -0,0 +1,93 @@ +#pragma once +#include "defines.h" +#include "AssetTypeClass.h" + +#define ASSETTYPE_ASSETBUNDLE 0x8E +struct PreloadData +{ + int fileId; + __int64 pathId; +}; +struct ContainerData +{ + char *name; + + int preloadIndex; + int preloadSize; + PreloadData ids; +}; +struct ScriptCompatibilityData +{ + char *className; + char *namespaceName; + char *assemblyName; + + unsigned int hash; +}; +struct ClassCompatibilityData +{ + int first; + unsigned int second; +}; +class AssetBundleAsset +{ + bool isModified; + bool isRead; + int unityVersion; + AssetTypeInstance *pAssetType; + //AssetTypeValueField *pBaseValueField; +public: + char *name; + + uint32_t preloadArrayLen; + PreloadData *preloadArray; + + uint32_t containerArrayLen; + ContainerData *containerArray; + ContainerData mainAsset; //this has no name field + + int scriptCompatibilityArrayLen; //before Unity 5 + ScriptCompatibilityData *scriptCompatibilityArray; //before Unity 5 + int classCompatibilityArrayLen; //before Unity 5 + ClassCompatibilityData *classCompatibilityArray; //before Unity 5 + + unsigned int runtimeCompatibility; + + char *assetBundleName; //Unity 5 + + int dependenciesArrayLen; + char **dependencies; //Unity 5 + + bool isStreamedSceneAssetBundle; //Unity 5 + +public: + ASSETSTOOLS_API AssetBundleAsset(); + ASSETSTOOLS_API ~AssetBundleAsset(); + + ASSETSTOOLS_API bool ReadBundleFile(void *data, size_t dataLen, size_t *filePos, AssetTypeTemplateField *pBaseField, bool bigEndian); + ASSETSTOOLS_API void ReadBundleFile(void *data, size_t dataLen, size_t *filePos, int assetsVersion, bool bigEndian); + ASSETSTOOLS_API void FlushChanges(); + ASSETSTOOLS_API int GetFileSize(); + ASSETSTOOLS_API bool WriteBundleFile(void *buffer, size_t bufferLen, size_t *size, bool bigEndian); + + inline void SetModified() + { + isModified = true; + } + inline bool IsModified() const + { + return isModified; + } + inline bool IsRead() const + { + return isRead; + } + + ASSETSTOOLS_API int AddContainer(ContainerData *cd); + ASSETSTOOLS_API void UpdatePreloadArray(uint32_t containerIndex); + ASSETSTOOLS_API void RemoveContainer(uint32_t index); + + +private: + ASSETSTOOLS_API void Clear(); +}; \ No newline at end of file diff --git a/AssetsTools/AssetTypeClass.cpp b/AssetsTools/AssetTypeClass.cpp new file mode 100644 index 0000000..c92c0ec --- /dev/null +++ b/AssetsTools/AssetTypeClass.cpp @@ -0,0 +1,930 @@ +#include "stdafx.h" +#include "../AssetsTools/AssetTypeClass.h" +#include +#include + +ASSETSTOOLS_API AssetTypeValue::AssetTypeValue(EnumValueTypes type, void *valueContainer) +{ + //freeValue = false; + this->type = type; + Set(valueContainer); +} +ASSETSTOOLS_API AssetTypeValue::AssetTypeValue(const AssetTypeValue &other) +{ + //freeValue = false; + this->type = other.type; + this->value = other.value; +} +ASSETSTOOLS_API void AssetTypeValue::Set(void *valueContainer, EnumValueTypes contType) +{ + bool mismatch = false; + memset(&value, 0, sizeof(ValueTypes)); + switch (this->type) + { + case ValueType_None: + break; + case ValueType_Bool: + if (contType >= ValueType_None && contType <= ValueType_UInt64) + value.asBool = *(bool*)valueContainer; + else mismatch = true; + break; + case ValueType_Int8: + case ValueType_UInt8: + if (contType >= ValueType_None && contType <= ValueType_UInt64) + value.asInt8 = *(char*)valueContainer; + else mismatch = true; + break; + case ValueType_Int16: + case ValueType_UInt16: + if (contType == ValueType_None || (contType >= ValueType_Int16 && contType <= ValueType_UInt64)) + value.asInt16 = *(short*)valueContainer; + else mismatch = true; + break; + case ValueType_Int32: + case ValueType_UInt32: + if (contType == ValueType_None || (contType >= ValueType_Int32 && contType <= ValueType_UInt64)) + value.asInt32 = *(int*)valueContainer; + else mismatch = true; + break; + case ValueType_Int64: + case ValueType_UInt64: + if (contType == ValueType_None || (contType >= ValueType_Int64 && contType <= ValueType_UInt64)) + value.asInt64 = *(long long int*)valueContainer; + else mismatch = true; + break; + case ValueType_Float: + if (contType == ValueType_None || contType == ValueType_Float) + value.asFloat = *(float*)valueContainer; + else mismatch = true; + break; + case ValueType_Double: + if (contType == ValueType_None || contType == ValueType_Double) + value.asDouble = *(double*)valueContainer; + else mismatch = true; + break; + case ValueType_String: + if (contType == ValueType_None || contType == ValueType_String) + value.asString = (char*)valueContainer; + else mismatch = true; + //freeValue = freeIfPointer; + break; + case ValueType_Array: + if (contType == ValueType_None || contType == ValueType_Array) + memcpy(&this->value.asArray, valueContainer, sizeof(AssetTypeArray)); + else mismatch = true; + //if (freeIfPointer) + // free(valueContainer); + break; + case ValueType_ByteArray: + if (contType == ValueType_None || contType == ValueType_ByteArray) + memcpy(&this->value.asByteArray, valueContainer, sizeof(AssetTypeByteArray)); + else mismatch = true; + break; + } + if (mismatch) + throw AssetTypeValue_ConfusionError("AssetTypeValue::Set: Mismatching value type supplied."); +} +ASSETSTOOLS_API AssetTypeValue::~AssetTypeValue() +{ + /*if (freeValue && ((type == ValueType_String) || (type == ValueType_Array))) + { + free(value.asString); + value.asString = NULL; + }*/ +} + +ASSETSTOOLS_API AssetTypeValueField* AssetTypeValueField::operator[](const char* name) const +{ + if (childrenCount == -1) + return GetDummyAssetTypeField(); + for (uint32_t i = 0; i < childrenCount; i++) + { + if (pChildren[i]->templateField != NULL) + { + if (pChildren[i]->templateField->name == name) + return pChildren[i]; + } + } + return GetDummyAssetTypeField(); +} + +ASSETSTOOLS_API AssetTypeValueField* AssetTypeValueField::operator[](uint32_t index) const +{ + if (childrenCount == -1) + return GetDummyAssetTypeField(); + if (index >= childrenCount) + return GetDummyAssetTypeField(); + return pChildren[index]; +} +ASSETSTOOLS_API void AssetTypeValueField::Read(AssetTypeValue *pValue, AssetTypeTemplateField *pTemplate, uint32_t childrenCount, AssetTypeValueField **pChildren) +{ + this->value = pValue; + this->templateField = pTemplate; + this->childrenCount = childrenCount; + this->pChildren = pChildren; + //char valueContainer[16]; + //value = new AssetTypeValue(templateField->type, valueContainer); +} +ASSETSTOOLS_API QWORD AssetTypeValueField::Write(IAssetsWriter *pWriter, QWORD filePos, bool bigEndian) +{ + QWORD qwValueTmp; + float fValueTmp; + double dValueTmp; + uint32_t dwValueTmp; + uint8_t byValueTmp; + bool doPadding = this->templateField->align; + if (this->templateField->children.empty() + && this->value != NULL && this->value->GetType() != ValueType_ByteArray) + { + switch (this->templateField->valueType) + { + case ValueType_Bool: + case ValueType_Int8: + case ValueType_UInt8: + byValueTmp = this->value->AsInt() & 0xFF; + pWriter->Write(filePos, 1, &byValueTmp); filePos++; + break; + case ValueType_Int16: + case ValueType_UInt16: + dwValueTmp = this->value->AsInt() & 0xFFFF; + if (bigEndian) + SwapEndians_((dwValueTmp <<= 16)); + pWriter->Write(filePos, 2, &dwValueTmp); filePos+=2; + break; + case ValueType_Int32: + case ValueType_UInt32: + dwValueTmp = this->value->AsInt(); + if (bigEndian) + SwapEndians_(dwValueTmp); + pWriter->Write(filePos, 4, &dwValueTmp); filePos+=4; + break; + case ValueType_Int64: + case ValueType_UInt64: + qwValueTmp = this->value->AsInt64(); + if (bigEndian) + SwapEndians_(qwValueTmp); + pWriter->Write(filePos, 8, &qwValueTmp); filePos+=8; + break; + case ValueType_Float: + fValueTmp = this->value->AsFloat(); + if (bigEndian) + SwapEndians_(fValueTmp); + pWriter->Write(filePos, 4, &fValueTmp); filePos+=4; + break; + case ValueType_Double: + dValueTmp = this->value->AsDouble(); + if (bigEndian) + SwapEndians_(dValueTmp); + pWriter->Write(filePos, 8, &dValueTmp); filePos+=8; + break; + } + } + else if (this->value != NULL && this->value->GetType() == ValueType_String) + { + const char *strVal = this->value->AsString(); + if (strVal == NULL) + strVal = ""; + uint32_t curStrLen = (uint32_t)strlen(strVal); + dwValueTmp = curStrLen; + if (bigEndian) + SwapEndians_(dwValueTmp); + pWriter->Write(filePos, 4, &dwValueTmp); filePos+=4; + pWriter->Write(filePos, curStrLen, strVal); filePos+=curStrLen; + if ((this->templateField->children.size() > 0) && this->templateField->children[0].align) + doPadding = true; + } + else if (this->value != NULL + && (this->value->GetType() == ValueType_Array || this->value->GetType() == ValueType_ByteArray)) + { + if (this->value->GetType() == ValueType_ByteArray) + { + uint32_t curByteLen = this->value->AsByteArray()->size; + dwValueTmp = curByteLen; + if (bigEndian) + SwapEndians_(dwValueTmp); + pWriter->Write(filePos, 4, &dwValueTmp); filePos += 4; + pWriter->Write(filePos, curByteLen, this->value->AsByteArray()->data); filePos += curByteLen; + } + else + { + uint32_t curArrLen = this->value->AsArray()->size; + dwValueTmp = curArrLen; + pWriter->Write(filePos, 4, &dwValueTmp); filePos += 4; + for (uint32_t i = 0; i < curArrLen; i++) + { + filePos = this->pChildren[i]->Write(pWriter, filePos, bigEndian); + } + } + } + else if (this->childrenCount > 0) + { + for (uint32_t i = 0; i < this->childrenCount; i++) + { + filePos = this->pChildren[i]->Write(pWriter, filePos, bigEndian); + } + } + if (doPadding) + { + int paddingLen = 3-(((filePos&3)-1) & 3); + if (paddingLen > 0) + { + dwValueTmp = 0; + pWriter->Write(filePos, paddingLen, &dwValueTmp); filePos += paddingLen; + } + } + return filePos; +} +ASSETSTOOLS_API QWORD AssetTypeValueField::GetByteSize(QWORD filePos) +{ + bool doPadding = this->templateField->align; + if ((this->templateField->children.empty()) && (this->value != NULL)) + { + switch (this->templateField->valueType) + { + case ValueType_Bool: + case ValueType_Int8: + case ValueType_UInt8: + filePos++; + break; + case ValueType_Int16: + case ValueType_UInt16: + filePos+=2; + break; + case ValueType_Int32: + case ValueType_UInt32: + case ValueType_Float: + filePos+=4; + break; + case ValueType_Int64: + case ValueType_UInt64: + case ValueType_Double: + filePos+=8; + break; + } + } + else if ((this->templateField->valueType == ValueType_String) && (this->value != NULL)) + { + filePos+=4 + strlen(this->value->AsString()); + if ((this->templateField->children.size() > 0) && this->templateField->children[0].align) + doPadding = true; + } + else if (this->templateField->isArray && (this->value != NULL)) + { + filePos += 4; + if (!_stricmp(this->templateField->type.c_str(), "TypelessData")) + filePos += this->value->AsByteArray()->size; + else + { + for (uint32_t i = 0; i < this->value->AsArray()->size; i++) + { + filePos = this->pChildren[i]->GetByteSize(filePos); + } + } + } + else if (this->childrenCount > 0) + { + for (uint32_t i = 0; i < this->childrenCount; i++) + { + filePos = this->pChildren[i]->GetByteSize(filePos); + } + } + if (doPadding) + filePos = (filePos+3)&(~3); + return filePos; +} + +void ClearAssetTypeValueField(AssetTypeValueField *pValueField) +{ + free(pValueField); +} + +ASSETSTOOLS_API bool AssetTypeValueField::IsDummy() const +{ + return (childrenCount == -1); +} + +class _DummyAssetTypeField : public AssetTypeValueField +{ +public: + _DummyAssetTypeField() + { + this->pChildren = NULL; + this->childrenCount = -1; + this->templateField = NULL; + this->value = NULL; + } +}; + +_DummyAssetTypeField dummyAssetTypeField = _DummyAssetTypeField(); +AssetTypeValueField* GetDummyAssetTypeField() +{ + return &dummyAssetTypeField; +} + +EnumValueTypes GetValueTypeByTypeName(const char *type) +{ + EnumValueTypes ret = ValueType_None; + if (!_stricmp(type, "string")) + { + ret = ValueType_String; + } + else if (!_stricmp(type, "SInt8") || !_stricmp(type, "char")) + { + ret = ValueType_Int8; + } + else if (!_stricmp(type, "UInt8") || !_stricmp(type, "unsigned char")) + { + ret = ValueType_UInt8; + } + else if (!_stricmp(type, "SInt16") || !_stricmp(type, "short")) + { + ret = ValueType_Int16; + } + else if (!_stricmp(type, "UInt16") || !_stricmp(type, "unsigned short")) + { + ret = ValueType_UInt16; + } + else if (!_stricmp(type, "SInt32") || !_stricmp(type, "int") || !_stricmp(type, "Type*")) + { + ret = ValueType_Int32; + } + else if (!_stricmp(type, "UInt32") || !_stricmp(type, "unsigned int")) + { + ret = ValueType_UInt32; + } + else if (!_stricmp(type, "SInt64") || !_stricmp(type, "long")) + { + ret = ValueType_Int64; + } + else if (!_stricmp(type, "UInt64") || !_stricmp(type, "FileSize") || !_stricmp(type, "unsigned long")) + { + ret = ValueType_UInt64; + } + else if (!_stricmp(type, "float")) + { + ret = ValueType_Float; + } + else if (!_stricmp(type, "double")) + { + ret = ValueType_Double; + } + else if (!_stricmp(type, "bool")) + { + ret = ValueType_Bool; + } + return ret; +} + +int _RecursiveGetValueFieldCount(AssetTypeTemplateField *pChild, IAssetsReader *pReader, QWORD maxFilePos, QWORD *pFilePos, size_t *pValueByteLen, size_t *pChildListLen, size_t *pRawDataLen, bool *pReadFailed, bool endianness) +{ + QWORD filePos = *pFilePos; + size_t valueByteLen = *pValueByteLen; + size_t childListLen = *pChildListLen; + size_t rawDataLen = *pRawDataLen; + int ret = 0; + if (!(*pReadFailed)) + { + ret = 1; + if (pChild->isArray && (pChild->children.size() == 2)) + { + valueByteLen += sizeof(AssetTypeValue); + unsigned int arrayLen; + if ((pChild->children[0].valueType == ValueType_Int32) || (pChild->children[0].valueType == ValueType_UInt32)) + { + pReader->Read(filePos, 4, &arrayLen); filePos += 4; + if (endianness) + SwapEndians_(arrayLen); + if (!_stricmp(pChild->type.c_str(), "TypelessData")) + { + rawDataLen += arrayLen; + filePos += arrayLen; + if (filePos > maxFilePos) + { + *pReadFailed = true; + } + } + else + { + childListLen += sizeof(AssetTypeValueField*) * arrayLen; + for (uint32_t i = 0; i < arrayLen; i++) + { + ret += _RecursiveGetValueFieldCount(&pChild->children[1], pReader, maxFilePos, &filePos, &valueByteLen, &childListLen, &rawDataLen, pReadFailed, endianness); + if ((*pReadFailed) || (filePos > maxFilePos)) + { + *pReadFailed = true; + break; + } + } + } + if (pChild->align) + filePos = (filePos + 3) & (~3); + } + else + assert(false); + //else + // MessageBox(0, TEXT("Invalid array value type!"), TEXT("ERROR"), 16); + } + else if (pChild->valueType == ValueType_String) + { + unsigned int stringLen; + pReader->Read(filePos, 4, &stringLen); filePos += 4; + if (endianness) + SwapEndians_(stringLen); + if ((filePos + stringLen) > maxFilePos) + { + *pReadFailed = true; + } + else + { + filePos += stringLen; + if (pChild->align || ((pChild->children.size() > 0) && pChild->children[0].align)) + filePos = (filePos+3)&(~3); + valueByteLen += (sizeof(AssetTypeValue) + stringLen + 1); + } + } + else if (pChild->children.empty()) + { + switch (pChild->valueType) + { + case ValueType_Bool: + case ValueType_Int8: + case ValueType_UInt8: + filePos++; + break; + case ValueType_Int16: + case ValueType_UInt16: + filePos+=2; + break; + case ValueType_Int32: + case ValueType_UInt32: + case ValueType_Float: + filePos+=4; + break; + case ValueType_Int64: + case ValueType_UInt64: + case ValueType_Double: + filePos+=8; + break; + } + valueByteLen += sizeof(AssetTypeValue); + if (pChild->align) + filePos = (filePos+3)&(~3); + if (filePos > maxFilePos) + { + *pReadFailed = true; + } + } + else + { + childListLen += sizeof(AssetTypeValueField*) * pChild->children.size(); + for (uint32_t i = 0; i < (uint32_t)pChild->children.size(); i++) + { + ret += _RecursiveGetValueFieldCount(&pChild->children[i], pReader, maxFilePos, &filePos, &valueByteLen, &childListLen, &rawDataLen, pReadFailed, endianness); + } + if (pChild->align) + filePos = (filePos+3)&(~3); + } + } + *pRawDataLen = rawDataLen; + *pChildListLen = childListLen; + *pValueByteLen = valueByteLen; + *pFilePos = filePos; + return ret; +} +QWORD _RecursiveMakeValues(AssetTypeTemplateField *pTemplate, IAssetsReader *pReader, QWORD filePos, QWORD maxFilePos, + AssetTypeValueField *pValueFields, uint32_t &valueFieldIndex, AssetTypeValue *&pCurValue, AssetTypeValueField**&pCurValueFieldList, + uint8_t *&pCurRawData, bool bigEndian) +{ + if (pTemplate->isArray) + { + if (pTemplate->children.size() == 2) + { + if ((pTemplate->children[0].valueType == ValueType_Int32) || (pTemplate->children[0].valueType == ValueType_UInt32)) + { + unsigned int arrayLen; + pReader->Read(filePos, 4, &arrayLen); filePos += 4; + if (bigEndian) + SwapEndians_(arrayLen); + if (!_stricmp(pTemplate->type.c_str(), "TypelessData")) + { + AssetTypeByteArray _tmpArray; + _tmpArray.size = arrayLen; + _tmpArray.data = pCurRawData; + pReader->Read(filePos, arrayLen, pCurRawData); filePos += arrayLen; + if (filePos <= maxFilePos) + { + pCurRawData = &pCurRawData[arrayLen]; + AssetTypeValue _tmpValue = AssetTypeValue(ValueType_ByteArray, &_tmpArray); + memcpy(pCurValue, &_tmpValue, sizeof(AssetTypeValue)); + pValueFields[valueFieldIndex].Read(pCurValue, pTemplate, 0, NULL); + valueFieldIndex++; + pCurValue = (AssetTypeValue*)((uintptr_t)pCurValue + sizeof(AssetTypeValue)); + } + } + else + { + AssetTypeArray _tmpArray; + _tmpArray.size = arrayLen; + //_tmpArray.dataField = &pValueFields[valueFieldIndex+1]; + + AssetTypeValue _tmpValue = AssetTypeValue(ValueType_Array, &_tmpArray); + memcpy(pCurValue, &_tmpValue, sizeof(AssetTypeValue)); + + AssetTypeValueField** arrayItemList = pCurValueFieldList; + pCurValueFieldList = (AssetTypeValueField**)((uintptr_t)pCurValueFieldList + (sizeof(AssetTypeValueField*) * arrayLen)); + uint32_t curValueFieldIndex = 0; + + pValueFields[valueFieldIndex].Read(pCurValue, pTemplate, arrayLen, arrayItemList); + pCurValue = (AssetTypeValue*)((uintptr_t)pCurValue + sizeof(AssetTypeValue)); + valueFieldIndex++; + for (uint32_t i = 0; i < arrayLen; i++) + { + arrayItemList[curValueFieldIndex] = &pValueFields[valueFieldIndex]; + filePos = _RecursiveMakeValues(&pTemplate->children[1], pReader, filePos, maxFilePos, pValueFields, valueFieldIndex, pCurValue, pCurValueFieldList, pCurRawData, bigEndian); + curValueFieldIndex++; + if (filePos > maxFilePos) + break; + } + } + if (pTemplate->align) + filePos = (filePos + 3) & (~3); + } + else + assert(false); + //else + // MessageBox(0, TEXT("Invalid array value type!"), TEXT("ERROR"), 16); + } + else + assert(false); + //else + // MessageBox(0, TEXT("Invalid array!"), TEXT("ERROR"), 16); + } + else if (pTemplate->valueType == ValueType_String) + { + unsigned int stringLen; + pReader->Read(filePos, 4, &stringLen); filePos += 4; + if (bigEndian) + SwapEndians_(stringLen); + if ((filePos + stringLen) > maxFilePos) + stringLen = (unsigned int)(maxFilePos - filePos); + char *stringLocation = (char*)((uintptr_t)pCurValue + sizeof(AssetTypeValue)); stringLocation[stringLen] = 0; + pReader->Read(filePos, stringLen, stringLocation); filePos += stringLen; + AssetTypeValue _tmpValue = AssetTypeValue(ValueType_String, stringLocation); + memcpy(pCurValue, &_tmpValue, sizeof(AssetTypeValue)); + pValueFields[valueFieldIndex].Read(pCurValue, pTemplate, 0, NULL); + + if (pTemplate->align || ((pTemplate->children.size() > 0) && pTemplate->children[0].align)) + filePos = (filePos+3)&(~3); + valueFieldIndex++; + pCurValue = (AssetTypeValue*)((uintptr_t)pCurValue + sizeof(AssetTypeValue) + stringLen + 1); + } + else if (pTemplate->children.empty()) + { + char _valueContainer[8] = {0,0,0,0,0,0,0,0}; + char *valueContainer = _valueContainer; + switch (pTemplate->valueType) + { + case ValueType_Bool: + case ValueType_Int8: + case ValueType_UInt8: + pReader->Read(filePos, 1, valueContainer); + filePos++; + break; + case ValueType_Int16: + case ValueType_UInt16: + pReader->Read(filePos, 2, valueContainer); + if (bigEndian) + SwapEndians_(*(uint16_t*)valueContainer); + filePos+=2; + break; + case ValueType_Int32: + case ValueType_UInt32: + case ValueType_Float: + pReader->Read(filePos, 4, valueContainer); + if (bigEndian) + SwapEndians_(*(uint32_t*)valueContainer); + filePos+=4; + break; + case ValueType_Int64: + case ValueType_UInt64: + case ValueType_Double: + pReader->Read(filePos, 8, valueContainer); + if (bigEndian) + SwapEndians_(*(QWORD*)valueContainer); + filePos+=8; + break; + case ValueType_String: + filePos = filePos; + break; + } + if (pTemplate->align) + filePos = (filePos+3)&(~3); + if (filePos <= maxFilePos) + { + AssetTypeValue _tmpValue = AssetTypeValue(pTemplate->valueType, valueContainer); + memcpy(pCurValue, &_tmpValue, sizeof(AssetTypeValue)); + pValueFields[valueFieldIndex].Read(pCurValue, pTemplate, 0, NULL); + valueFieldIndex++; + pCurValue = (AssetTypeValue*)((uintptr_t)pCurValue + sizeof(AssetTypeValue)); + } + } + else + { + AssetTypeValueField** templateChildList = pCurValueFieldList; + pCurValueFieldList = (AssetTypeValueField**)((uintptr_t)pCurValueFieldList + (sizeof(AssetTypeValueField*) * pTemplate->children.size())); + uint32_t curValueFieldIndex = 0; + + pValueFields[valueFieldIndex].Read(NULL, pTemplate, (uint32_t)pTemplate->children.size(), templateChildList); + valueFieldIndex++; + + for (uint32_t i = 0; i < (uint32_t)pTemplate->children.size(); i++) + { + templateChildList[curValueFieldIndex] = &pValueFields[valueFieldIndex]; + filePos = _RecursiveMakeValues(&pTemplate->children[i], pReader, filePos, maxFilePos, pValueFields, valueFieldIndex, pCurValue, pCurValueFieldList, pCurRawData, bigEndian); + curValueFieldIndex++; + } + if (pTemplate->align) + filePos = (filePos+3)&(~3); + } + return filePos; +} + +ASSETSTOOLS_API AssetTypeTemplateField::AssetTypeTemplateField() + : valueType(ValueType_None), + isArray(false), align(false), hasValue(false) +{} +ASSETSTOOLS_API void AssetTypeTemplateField::Clear() +{ + name.clear(); + type.clear(); + children.clear(); +} +ASSETSTOOLS_API AssetTypeTemplateField::~AssetTypeTemplateField() +{} + +ASSETSTOOLS_API QWORD AssetTypeTemplateField::MakeValue(IAssetsReader *pReader, QWORD filePos, QWORD fileLen, AssetTypeValueField **ppValueField, bool endianness) +{ + AssetTypeValue *newValue = NULL; + QWORD tmpFilePos = filePos; + size_t newValueByteLen = 0; size_t childListByteLen = 0; size_t rawDataByteLen = 0; + //Set to true if it goes EOF while reading an array; This allows parsing empty files and having them filled with zeros without risking crashes on invalid files. + bool readFailed = false; + int newChildrenCount = _RecursiveGetValueFieldCount(this, pReader, filePos+fileLen, &tmpFilePos, &newValueByteLen, &childListByteLen, &rawDataByteLen, &readFailed, endianness); + //ppValueField will be set to pValueFieldMemory so the caller knows which pointer to free + if (readFailed) + { + *ppValueField = NULL; + return filePos; + } + void *pValueFieldMemory = malloc((newChildrenCount * sizeof(AssetTypeValueField)) + newValueByteLen + childListByteLen + rawDataByteLen); + if (pValueFieldMemory == NULL) + { + *ppValueField = NULL; + return filePos; + } + AssetTypeValueField *pValueFields = (AssetTypeValueField*)pValueFieldMemory; + AssetTypeValue *pCurValue = (AssetTypeValue*)(&((uint8_t*)pValueFieldMemory)[newChildrenCount * sizeof(AssetTypeValueField)]); + + AssetTypeValueField **pCurValueList = (AssetTypeValueField**)(&((uint8_t*)pValueFieldMemory)[newChildrenCount * sizeof(AssetTypeValueField) + newValueByteLen]); + uint8_t *pCurRawByte = (uint8_t*)(&((uint8_t*)pValueFieldMemory)[newChildrenCount * sizeof(AssetTypeValueField) + newValueByteLen + childListByteLen]); + + uint32_t valueFieldIndex = 0; + filePos = _RecursiveMakeValues(this, pReader, filePos, filePos+fileLen, pValueFields, valueFieldIndex, pCurValue, pCurValueList, pCurRawByte, endianness); + //_RecursiveDumpValues(pValueFields, 0); + *ppValueField = &pValueFields[0]; + return filePos; +} + +ASSETSTOOLS_API bool AssetTypeTemplateField::From0D(Type_0D *pU5Type, uint32_t fieldIndex) +{ + if (pU5Type->typeFieldsExCount <= fieldIndex) + { + memset(this, 0, sizeof(AssetTypeTemplateField)); + return false; + } + TypeField_0D *pTypeField = &pU5Type->pTypeFieldsEx[fieldIndex]; + type = pTypeField->GetTypeString(pU5Type->pStringTable, pU5Type->stringTableLen); + name = pTypeField->GetNameString(pU5Type->pStringTable, pU5Type->stringTableLen); + if (!type.empty()) + valueType = GetValueTypeByTypeName(type.c_str()); + else + valueType = ValueType_None; + isArray = (pTypeField->isArray & 1) != 0; + if (isArray) + valueType = ValueType_Array; + align = (pTypeField->flags & 0x4000) != 0; + + size_t newChildCount = 0; uint8_t directChildDepth = 0; + for (uint32_t i = fieldIndex+1; i < pU5Type->typeFieldsExCount; i++) + { + if (pU5Type->pTypeFieldsEx[i].depth <= pTypeField->depth) + break; + if (!directChildDepth) + { + directChildDepth = pU5Type->pTypeFieldsEx[i].depth; + newChildCount++; + } + else + { + if (pU5Type->pTypeFieldsEx[i].depth == directChildDepth) + newChildCount++; + } + } + hasValue = (newChildCount == 0); + children.resize(newChildCount); + size_t childIndex = 0; bool ret = true; + for (uint32_t i = fieldIndex+1; i < pU5Type->typeFieldsExCount; i++) + { + if (pU5Type->pTypeFieldsEx[i].depth <= pTypeField->depth) + break; + if (pU5Type->pTypeFieldsEx[i].depth == directChildDepth) + { + if (!children[childIndex].From0D(pU5Type, i)) + ret = false; + childIndex++; + } + } + return ret; +} +ASSETSTOOLS_API bool AssetTypeTemplateField::FromClassDatabase(ClassDatabaseFile *pFile, ClassDatabaseType *pType, uint32_t fieldIndex) +{ + if (pType->fields.size() <= fieldIndex) + { + memset(this, 0, sizeof(AssetTypeTemplateField)); + return false; + } + children.clear(); + ClassDatabaseTypeField *pTypeField = &pType->fields[fieldIndex]; + isArray = (pTypeField->isArray & 1) != 0; + name = pTypeField->fieldName.GetString(pFile); + type = pTypeField->typeName.GetString(pFile); + if (!type.empty()) + valueType = GetValueTypeByTypeName(type.c_str()); + else + valueType = ValueType_None; + align = (pTypeField->flags2 & 0x4000) != 0; + + size_t newChildCount = 0; + uint8_t directChildDepth = 0; + for (uint32_t i = fieldIndex+1; i < pType->fields.size(); i++) + { + if (pType->fields[i].depth <= pTypeField->depth) + break; + if (!directChildDepth) + { + directChildDepth = pType->fields[i].depth; + newChildCount++; + } + else + { + if (pType->fields[i].depth == directChildDepth) + newChildCount++; + } + } + hasValue = (pType->fields.size() > (fieldIndex+1)) ? (newChildCount == 0) : true; + children.resize(newChildCount); + size_t childIndex = 0; bool ret = true; + for (uint32_t i = fieldIndex+1; i < pType->fields.size(); i++) + { + if (pType->fields[i].depth <= pTypeField->depth) + break; + if (pType->fields[i].depth == directChildDepth) + { + if (!children[childIndex].FromClassDatabase(pFile, pType, i)) + ret = false; + childIndex++; + } + } + return ret; +} +ASSETSTOOLS_API bool AssetTypeTemplateField::From07(TypeField_07 *pTypeField) +{ + isArray = pTypeField->arrayFlag != 0; + align = (pTypeField->flags2 & 0x4000) != NULL; + name = pTypeField->name; + type = pTypeField->type; + if (!type.empty()) + valueType = GetValueTypeByTypeName(type.c_str()); + else + valueType = ValueType_None; + + hasValue = (pTypeField->childrenCount == 0); + children.resize(pTypeField->childrenCount); + bool ret = true; + for (uint32_t i = 0; i < pTypeField->childrenCount; i++) + { + if (!children[i].From07(&pTypeField->children[i])) + ret = false; + } + return ret; +} +ASSETSTOOLS_API bool AssetTypeTemplateField::AddChildren(uint32_t count) +{ + if ((children.size() + count) < children.size()) //overflow + return false; + children.resize(children.size() + count); + return true; +} +ASSETSTOOLS_API AssetTypeTemplateField *AssetTypeTemplateField::SearchChild(const char* name) +{ + for (size_t i = 0; i < children.size(); i++) + { + if (children[i].name == name) + return &children[i]; + } + return NULL; +} + +ASSETSTOOLS_API AssetTypeInstance::AssetTypeInstance(uint32_t baseFieldCount, AssetTypeTemplateField **ppBaseFields, QWORD fileLen, IAssetsReader *pReader, bool bigEndian, QWORD filePos) +{ + this->baseFields.resize(baseFieldCount); + this->memoryToClear.resize(baseFieldCount); + QWORD nullPos = filePos; + for (uint32_t i = 0; i < baseFieldCount; i++) + { + filePos = ppBaseFields[i]->MakeValue(pReader, filePos, fileLen - (filePos - nullPos), &this->baseFields[i], bigEndian); + this->memoryToClear[i] = this->baseFields[i]; + if (this->baseFields[i] == NULL) + { + this->baseFields.resize(i); + this->memoryToClear.resize(i); + break; + } + } +} +ASSETSTOOLS_API bool AssetTypeInstance::SetChildList(AssetTypeValueField *pValueField, AssetTypeValueField **pChildrenList, uint32_t childrenCount, bool freeMemory) +{ + if (pValueField->GetChildrenList() == pChildrenList) + { + if (pValueField->GetChildrenCount() == childrenCount) + return true; + pValueField->SetChildrenList(pChildrenList, childrenCount); + return true; + } + for (size_t i = 0; i < this->memoryToClear.size(); i++) + { + if (this->memoryToClear[i] == pValueField->GetChildrenList()) + { + for (uint32_t _i = 0; _i < pValueField->GetChildrenCount(); _i++) + { + bool found = false; AssetTypeValueField *pTarget = pValueField->Get(_i); + for (uint32_t _k = 0; _k < childrenCount; _k++) + { + if (pTarget == pChildrenList[_k]) + { + found = true; + break; + } + } + if (!found) + { + for (size_t k = 0; k < this->memoryToClear.size(); k++) + { + if (this->memoryToClear[k] == pTarget) + { + this->memoryToClear.erase(this->memoryToClear.begin() + k); + break; + } + } + } + } + free(this->memoryToClear[i]); + pValueField->SetChildrenList(pChildrenList, childrenCount); + if (freeMemory) + { + this->memoryToClear[i] = pChildrenList; + } + else + { + this->memoryToClear.erase(this->memoryToClear.begin() + i); + } + return true; + } + } + pValueField->SetChildrenList(pChildrenList, childrenCount); + if (freeMemory) + { + this->memoryToClear.push_back(pChildrenList); + } + return true; +} +ASSETSTOOLS_API bool AssetTypeInstance::AddTempMemory(void *pMemory) +{ + this->memoryToClear.push_back(pMemory); + return true; +} +void AssetTypeInstance::Clear() +{ + for (size_t i = 0; i < this->memoryToClear.size(); i++) + { + if (this->memoryToClear[i]) + free(this->memoryToClear[i]); + } + this->memoryToClear.clear(); + this->baseFields.clear(); +} + +ASSETSTOOLS_API AssetTypeInstance::~AssetTypeInstance() +{ + Clear(); +} \ No newline at end of file diff --git a/AssetsTools/AssetTypeClass.h b/AssetsTools/AssetTypeClass.h new file mode 100644 index 0000000..2aa2465 --- /dev/null +++ b/AssetsTools/AssetTypeClass.h @@ -0,0 +1,359 @@ +#pragma once +#include "defines.h" +#include "AssetsFileFormat.h" +#include "ClassDatabaseFile.h" +#include +#include + +//Exception type for AssetTypeValue::Set. +class AssetTypeValue_ConfusionError : public std::exception +{ + std::string desc; +public: + inline AssetTypeValue_ConfusionError(std::string _desc) + : desc(std::move(_desc)) + {} + inline const char* what() + { + return desc.c_str(); + } +}; + +class AssetTypeValueField; +//class to read asset files using type information +struct AssetTypeArray +{ + uint32_t size; + //AssetTypeValueField *dataField; +}; +struct AssetTypeByteArray +{ + uint32_t size; + uint8_t *data; +}; + +enum EnumValueTypes +{ + ValueType_None, + ValueType_Bool, + ValueType_Int8, + ValueType_UInt8, + ValueType_Int16, + ValueType_UInt16, + ValueType_Int32, + ValueType_UInt32, + ValueType_Int64, + ValueType_UInt64, + ValueType_Float, + ValueType_Double, + ValueType_String, + ValueType_Array, + ValueType_ByteArray +}; + +class AssetTypeValue +{ +public: + union ValueTypes + { + AssetTypeArray asArray; + AssetTypeByteArray asByteArray; + + bool asBool; + + char asInt8; + unsigned char asUInt8; + + short asInt16; + unsigned short asUInt16; + + int asInt32; + unsigned int asUInt32; + + long long int asInt64; + unsigned long long int asUInt64; + + float asFloat; + double asDouble; + + char *asString; + }; +protected: + //bool freeValue; + EnumValueTypes type; + ValueTypes value; +public: + //Creates an AssetTypeValue. + //type : the value type which valueContainer stores + //valueContainer : the buffer for the value type + //freeIfPointer : should the value get freed if value type is Array/String + ASSETSTOOLS_API AssetTypeValue(EnumValueTypes type, void *valueContainer); + ASSETSTOOLS_API AssetTypeValue(const AssetTypeValue &other); + ASSETSTOOLS_API ~AssetTypeValue(); + inline EnumValueTypes GetType() const + { + return type; + } + //valueContainer : + // - For primitive types (bool, int, uint, float, double) this is a pointer to the value. + // - For strings, this is the char* C-style string pointer. + // - For Array and ByteArray, this is the AssetTypeArray* / AssetTypeByteArray* value. + //type : If set, is checked against GetType() to prevent type confusion. Does NOT change the set value type! + // -> Bigger (u)ints can be assigned to smaller; no signed/unsigned check, bools treated as (u)int8. + // For instance, ValueType_UInt64 can be assigned to AssetTypeValue with GetType()==ValueType_Int32 + // but not the other way round. + // -> Other types require an exact match. + // Throws an AssetTypeValue_ConfusionError if a critical mismatch is detected. + ASSETSTOOLS_API void Set(void *valueContainer, EnumValueTypes type = ValueType_None); + inline AssetTypeArray *AsArray() + { + return (type == ValueType_Array) ? &value.asArray : NULL; + } + inline AssetTypeByteArray *AsByteArray() + { + return (type == ValueType_ByteArray) ? &value.asByteArray : NULL; + } + inline char *AsString() + { + return (type == ValueType_String) ? value.asString : NULL; + } + inline bool AsBool() const + { + switch (type) + { + case ValueType_Float: + case ValueType_Double: + case ValueType_String: + case ValueType_ByteArray: + case ValueType_Array: + return false; + default: + return value.asBool; + } + } + inline int AsInt() const + { + switch (type) + { + case ValueType_Float: + return (int)value.asFloat; + case ValueType_Double: + return (int)value.asDouble; + case ValueType_String: + case ValueType_ByteArray: + case ValueType_Array: + return 0; + case ValueType_Int8: + return (int)value.asInt8; + case ValueType_Int16: + return (int)value.asInt16; + case ValueType_Int64: + return (int)value.asInt64; + default: + return value.asInt32; + } + } + inline unsigned int AsUInt() const + { + switch (type) + { + case ValueType_Float: + return (unsigned int)value.asFloat; + case ValueType_Double: + return (unsigned int)value.asDouble; + case ValueType_String: + case ValueType_ByteArray: + case ValueType_Array: + return 0; + default: + return value.asUInt32; + } + } + inline long long int AsInt64() const + { + switch (type) + { + case ValueType_Float: + return (long long int)value.asFloat; + case ValueType_Double: + return (long long int)value.asDouble; + case ValueType_String: + case ValueType_ByteArray: + case ValueType_Array: + return 0; + case ValueType_Int8: + return (long long int)value.asInt8; + case ValueType_Int16: + return (long long int)value.asInt16; + case ValueType_Int32: + return (long long int)value.asInt32; + default: + return value.asInt64; + } + } + inline unsigned long long int AsUInt64() const + { + switch (type) + { + case ValueType_Float: + return (unsigned int)value.asFloat; + case ValueType_Double: + return (unsigned long long int)value.asDouble; + case ValueType_String: + case ValueType_ByteArray: + case ValueType_Array: + return 0; + default: + return value.asUInt64; + } + } + inline float AsFloat() const + { + switch (type) + { + case ValueType_Float: + return value.asFloat; + case ValueType_Double: + return (float)value.asDouble; + case ValueType_String: + case ValueType_ByteArray: + case ValueType_Array: + return 0; + case ValueType_Int8: + return (float)value.asInt8; + case ValueType_Int16: + return (float)value.asInt16; + case ValueType_Int32: + return (float)value.asInt32; + case ValueType_Int64: + return (float)value.asInt64; + default: + return (float)value.asUInt64; + } + } + inline double AsDouble() const + { + switch (type) + { + case ValueType_Float: + return (double)value.asFloat; + case ValueType_Double: + return value.asDouble; + case ValueType_String: + case ValueType_ByteArray: + case ValueType_Array: + return 0; + case ValueType_Int8: + return (double)value.asInt8; + case ValueType_Int16: + return (double)value.asInt16; + case ValueType_Int32: + return (double)value.asInt32; + case ValueType_Int64: + return (double)value.asInt64; + default: + return (double)value.asUInt64; + } + } +}; + +class AssetTypeValueField; +class AssetTypeTemplateField +{ +public: + std::string name; + std::string type; + EnumValueTypes valueType; + bool isArray; + bool align; + bool hasValue; + std::vector children; + +public: + ASSETSTOOLS_API AssetTypeTemplateField(); + ASSETSTOOLS_API ~AssetTypeTemplateField(); + ASSETSTOOLS_API void Clear(); + ASSETSTOOLS_API bool From0D(Type_0D *pU5Type, uint32_t fieldIndex); + ASSETSTOOLS_API bool FromClassDatabase(ClassDatabaseFile *pFile, ClassDatabaseType *pType, uint32_t fieldIndex); + ASSETSTOOLS_API bool From07(TypeField_07 *pTypeField); + ASSETSTOOLS_API QWORD MakeValue(IAssetsReader *pReader, QWORD filePos, QWORD fileLen, AssetTypeValueField **ppValueField, bool bigEndian); + ASSETSTOOLS_API bool AddChildren(uint32_t count); + + ASSETSTOOLS_API AssetTypeTemplateField *SearchChild(const char* name); +}; +ASSETSTOOLS_API void ClearAssetTypeValueField(AssetTypeValueField *pValueField); +class AssetTypeValueField +{ +protected: + AssetTypeTemplateField *templateField; + + uint32_t childrenCount; + AssetTypeValueField **pChildren; + AssetTypeValue *value; //pointer so it may also have no value (NULL) +public: + + ASSETSTOOLS_API void Read(AssetTypeValue *pValue, AssetTypeTemplateField *pTemplate, uint32_t childrenCount, AssetTypeValueField **pChildren); + ASSETSTOOLS_API QWORD Write(IAssetsWriter *pWriter, QWORD filePos, bool bigEndian); + + //ASSETSTOOLS_API void Clear(); + + //get a child field by its name + ASSETSTOOLS_API AssetTypeValueField* operator[](const char* name) const; + //get a child field by its index + ASSETSTOOLS_API AssetTypeValueField* operator[](uint32_t index) const; + + inline AssetTypeValueField* Get(const char* name) const { return (*this)[name]; } + inline AssetTypeValueField* Get(unsigned int index) const { return (*this)[index]; } + + inline const std::string &GetName() const { return templateField->name; } + inline const std::string &GetType() const { return templateField->type; } + inline AssetTypeValue *GetValue() const { return value; } + inline AssetTypeTemplateField *GetTemplateField() const { return templateField; } + inline AssetTypeValueField **GetChildrenList() const { return pChildren; } + inline void SetChildrenList(AssetTypeValueField **pChildren, uint32_t childrenCount) { this->pChildren = pChildren; this->childrenCount = childrenCount; } + + inline uint32_t GetChildrenCount() const { return childrenCount; } + + ASSETSTOOLS_API bool IsDummy() const; + + ASSETSTOOLS_API QWORD GetByteSize(QWORD filePos = 0); +}; +ASSETSTOOLS_API EnumValueTypes GetValueTypeByTypeName(const char *type); +ASSETSTOOLS_API AssetTypeValueField* GetDummyAssetTypeField(); + +class AssetTypeInstance +{ + std::vector baseFields; + std::vector memoryToClear; + + ASSETSTOOLS_API void Clear(); + +private: + ASSETSTOOLS_API AssetTypeInstance(AssetTypeInstance &other) = delete; + ASSETSTOOLS_API AssetTypeInstance &operator=(const AssetTypeInstance &other) = delete; +public: + inline AssetTypeInstance() + {} + inline AssetTypeInstance(AssetTypeInstance &&other) noexcept + { + (*this) = std::move(other); + } + inline AssetTypeInstance &operator=(AssetTypeInstance&& other) noexcept + { + this->Clear(); + baseFields = std::move(other.baseFields); + memoryToClear = std::move(other.memoryToClear); + return *this; + } + ASSETSTOOLS_API AssetTypeInstance(uint32_t baseFieldCount, AssetTypeTemplateField **ppBaseFields, QWORD fileLen, IAssetsReader *pReader, bool bigEndian, QWORD filePos = 0); + ASSETSTOOLS_API bool SetChildList(AssetTypeValueField *pValueField, AssetTypeValueField **pChildrenList, uint32_t childrenCount, bool freeMemory = true); + ASSETSTOOLS_API bool AddTempMemory(void *pMemory); + ASSETSTOOLS_API ~AssetTypeInstance(); + + inline AssetTypeValueField *GetBaseField(uint32_t index = 0) + { + if (index >= baseFields.size()) + return GetDummyAssetTypeField(); + return baseFields[index]; + } +}; \ No newline at end of file diff --git a/AssetsTools/AssetsFileFormat.cpp b/AssetsTools/AssetsFileFormat.cpp new file mode 100644 index 0000000..58a5d14 --- /dev/null +++ b/AssetsTools/AssetsFileFormat.cpp @@ -0,0 +1,2148 @@ +#include "stdafx.h" +#include "../AssetsTools/AssetsFileFormat.h" +#include "../AssetsTools/AssetsFileReader.h" +#include "../AssetsTools/AssetsReplacer.h" +#include "InternalAssetsReplacer.h" +#include +#include +#include +#include + +ASSETSTOOLS_API uint32_t AssetFileInfo::GetSize(uint32_t version) +{ + if (version >= 0x16) + return 24; + else if (version >= 0x11) + return 20; + else if (version >= 0x10) + return 23; + else if (version >= 0x0F) + return 25; + else if (version == 0x0E) + return 24; + else if (version >= 0x0B) + return 20; + else //if (version >= 0x07) + return 20; +} + +ASSETSTOOLS_API QWORD AssetFileInfo::Read(uint32_t version, QWORD pos, IAssetsReader *pReader, bool bigEndian) +{ + uint8_t fileInfoData[AssetFileInfo_MaxSize]; + if (version >= 0x0E) + pos = (pos + 3) & ~3; + pReader->Read(pos, GetSize(version), fileInfoData); pos += GetSize(version); + this->index = 0; + int fileInfoDataOffs = 0; + memcpy(&this->index, &fileInfoData[fileInfoDataOffs], (version >= 0x0E) ? 8 : 4); fileInfoDataOffs += ((version >= 0x0E) ? 8 : 4); + if (bigEndian) + { + if (version >= 0x0E) + SwapEndians_(this->index); + else + SwapEndians_(*(uint32_t*)&this->index); + } + this->offs_curFile = 0; + memcpy(&this->offs_curFile, &fileInfoData[fileInfoDataOffs], (version >= 0x16) ? 8 : 4); fileInfoDataOffs += (version >= 0x16) ? 8 : 4; //always 4-byte aligned + if (bigEndian) + { + if (version >= 0x16) + SwapEndians_(this->offs_curFile); + else + SwapEndians_(*(uint32_t*)&this->offs_curFile); + } + memcpy(&this->curFileSize, &fileInfoData[fileInfoDataOffs], 4); fileInfoDataOffs += 4; + if (bigEndian) + SwapEndians_(this->curFileSize); + memcpy(&this->curFileTypeOrIndex, &fileInfoData[fileInfoDataOffs], 4); fileInfoDataOffs += 4; + if (bigEndian) + SwapEndians_(this->curFileTypeOrIndex); + if (version < 0x10) + { + memcpy(&this->inheritedUnityClass, &fileInfoData[fileInfoDataOffs], 2); fileInfoDataOffs += 2; + if (bigEndian) + SwapEndians_(this->inheritedUnityClass); + } + else + this->inheritedUnityClass = 0; + if (version < 0x0B) + fileInfoDataOffs += 2; + if ((version >= 0x0B) && (version <= 0x10)) + { + memcpy(&this->scriptIndex, &fileInfoData[fileInfoDataOffs], 2); fileInfoDataOffs += 2; + if (bigEndian) + SwapEndians_(this->scriptIndex); + } + else + { + this->scriptIndex = 0xFFFF; + } + if ((version >= 0x0F) && (version <= 0x10)) + { + memcpy(&this->unknown1, &fileInfoData[fileInfoDataOffs], 1); fileInfoDataOffs += 1; + } + else + this->unknown1 = 0; + return pos; +} +ASSETSTOOLS_API QWORD AssetFileInfo::Write(uint32_t version, QWORD pos, IAssetsWriter *pWriter, bool bigEndian) +{ + uint8_t fileInfoData[AssetFileInfo_MaxSize]; + if (version >= 0x0E) + { + uint32_t dwTmp = 0; + pWriter->Write(pos, 3-((pos-1)&3), &dwTmp); + pos = (pos + 3) & ~3; + } + int fileInfoDataOffs = 0; + QWORD tempQW; uint32_t tempDW; uint16_t tempW; + + tempQW = this->index; + if (bigEndian) + { + if (version < 0x0E) tempQW <<= 32; + SwapEndians_(tempQW); + } + memcpy(&fileInfoData[fileInfoDataOffs], &tempQW, (version >= 0x0E) ? 8 : 4); fileInfoDataOffs += ((version >= 0x0E) ? 8 : 4); + + tempQW = this->offs_curFile; + if (bigEndian) + { + if (version < 0x16) tempQW <<= 32; + SwapEndians_(tempQW); + } + memcpy(&fileInfoData[fileInfoDataOffs], &tempQW, (version >= 0x16) ? 8 : 4); fileInfoDataOffs += ((version >= 0x16) ? 8 : 4); + + tempDW = this->curFileSize; + if (bigEndian) + SwapEndians_(tempDW); + memcpy(&fileInfoData[fileInfoDataOffs], &tempDW, 4); fileInfoDataOffs += 4; + + tempDW = this->curFileTypeOrIndex; + if (bigEndian) + SwapEndians_(tempDW); + memcpy(&fileInfoData[fileInfoDataOffs], &tempDW, 4); fileInfoDataOffs += 4; + if (version < 0x10) + { + tempW = this->inheritedUnityClass; + if (bigEndian) + SwapEndians_(tempW); + memcpy(&fileInfoData[fileInfoDataOffs], &tempW, 2); fileInfoDataOffs += 2; + } + if (version < 0x11) + { + if (version >= 0x0B) + { + tempW = this->scriptIndex; + if (bigEndian) + SwapEndians_(tempW); + memcpy(&fileInfoData[fileInfoDataOffs], &tempW, 2); + } + else + { + uint16_t wordTmp = 0; memcpy(&fileInfoData[fileInfoDataOffs], &wordTmp, 2); + } + fileInfoDataOffs += 2; + if (version >= 0x0F) + { + memcpy(&fileInfoData[fileInfoDataOffs], &this->unknown1, 1); fileInfoDataOffs += 1; + } + } + pWriter->Write(pos, fileInfoDataOffs, fileInfoData); pos += fileInfoDataOffs; + return pos; +} +ASSETSTOOLS_API unsigned int AssetFileList::GetSizeBytes(uint32_t version) +{ + if (version < 0x0F || version > 0x10) + return this->sizeFiles * AssetFileInfo::GetSize(version); + else + { + if (this->sizeFiles == 0) + return 0; + unsigned int ret = 0; + unsigned int sizePerFile = AssetFileInfo::GetSize(version); + ret = ((sizePerFile+3)&(~3)) * (this->sizeFiles-1) + sizePerFile; + return ret; + /*for (unsigned int i = 0; i < this->sizeFiles; i++) + { + ret = (ret+3)&(~3); + ret += sizePerFile; + }*/ + } +} +ASSETSTOOLS_API QWORD AssetFileList::Read(uint32_t version, QWORD pos, IAssetsReader *pReader, bool bigEndian) +{ + for (unsigned int i = 0; i < this->sizeFiles; i++) + { + pos = fileInfs[i].Read(version, pos, pReader, bigEndian); + } + return pos; +} +ASSETSTOOLS_API QWORD AssetFileList::Write(uint32_t version, QWORD pos, IAssetsWriter *pWriter, bool bigEndian) +{ + for (unsigned int i = 0; i < this->sizeFiles; i++) + { + pos = fileInfs[i].Write(version, pos, pWriter, bigEndian); + } + return pos; +} + +ASSETSTOOLS_API QWORD AssetsFileHeader::Read(QWORD absFilePos, IAssetsReader *pReader) +{ + QWORD curFilePos = absFilePos; + uint32_t dw00, dw04, dw0C; + pReader->Read(curFilePos, 4, &dw00); SwapEndians_(dw00); curFilePos += 4; + pReader->Read(curFilePos, 4, &dw04); SwapEndians_(dw04); curFilePos += 4; + pReader->Read(curFilePos, 4, &this->format); SwapEndians_(this->format); curFilePos += 4; + pReader->Read(curFilePos, 4, &dw0C); SwapEndians_(dw0C); curFilePos += 4; + if (this->format >= 0x16) + { + this->unknown00 = (static_cast(dw00) << 32) | dw04; + //dw0C is padding for format >= 0x16 + pReader->Read(curFilePos, 8, &this->metadataSize); SwapEndians_(this->metadataSize); curFilePos += 8; + pReader->Read(curFilePos, 8, &this->fileSize); SwapEndians_(this->fileSize); curFilePos += 8; + pReader->Read(curFilePos, 8, &this->offs_firstFile); SwapEndians_(this->offs_firstFile); curFilePos += 8; + pReader->Read(curFilePos, 1, &this->endianness); curFilePos += 1; + pReader->Read(curFilePos, 3, this->unknown); curFilePos += 3; + curFilePos += 4; //Padding + } + else + { + this->unknown00 = 0; + this->metadataSize = dw00; + this->fileSize = dw04; + this->offs_firstFile = dw0C; + if (this->format < 9 && (this->fileSize > this->metadataSize)) + { + this->unknown[0] = this->unknown[1] = this->unknown[2] = 0; + pReader->Read(absFilePos + this->fileSize - this->metadataSize, 1, &this->endianness); + } + else + { + pReader->Read(curFilePos, 1, &this->endianness); curFilePos += 1; + pReader->Read(curFilePos, 3, this->unknown); curFilePos += 3; + } + } + + return curFilePos; +} +ASSETSTOOLS_API QWORD AssetsFileHeader::Write(QWORD pos, IAssetsWriter *pWriter) +{ + QWORD qwTmp; + uint32_t dwTmp; + if (format >= 0x16) + { + qwTmp = SwapEndians(this->unknown00); + pWriter->Write(pos, 8, &qwTmp); pos += 8; + + dwTmp = SwapEndians(this->format); + pWriter->Write(pos, 4, &dwTmp); pos += 4; + dwTmp = 0; + pWriter->Write(pos, 4, &dwTmp); pos += 4; //Padding + + qwTmp = SwapEndians(this->metadataSize); + pWriter->Write(pos, 8, &qwTmp); pos += 8; + + qwTmp = SwapEndians(this->fileSize); + pWriter->Write(pos, 8, &qwTmp); pos += 8; + + qwTmp = SwapEndians(this->offs_firstFile); + pWriter->Write(pos, 8, &qwTmp); pos += 8; + + pWriter->Write(pos, 1, &this->endianness); pos += 1; + pWriter->Write(pos, 3, this->unknown); pos += 3; + dwTmp = 0; + pWriter->Write(pos, 4, &dwTmp); pos += 4; //Padding + } + else + { + dwTmp = SwapEndians(static_cast(this->metadataSize)); + pWriter->Write(pos, 4, &dwTmp); pos += 4; + dwTmp = SwapEndians(static_cast(this->fileSize)); + pWriter->Write(pos, 4, &dwTmp); pos += 4; + dwTmp = SwapEndians(this->format); + pWriter->Write(pos, 4, &dwTmp); pos += 4; + dwTmp = SwapEndians(static_cast(this->offs_firstFile)); + pWriter->Write(pos, 4, &dwTmp); pos += 4; + /*if (this->format < 9 && (this->fileSize < this->metadataSize)) + { + writer(pos - 16 + this->fileSize - this->metadataSize, 1, &this->endianness, writerPar); pos += 1; + } + else*/ + if (format >= 9) + { + pWriter->Write(pos, 1, &this->endianness); pos += 1; + pWriter->Write(pos, 3, this->unknown); pos += 3; + } + } + + + return pos; +} +ASSETSTOOLS_API unsigned int AssetsFileHeader::GetSizeBytes() +{ + return (format >= 0x16) ? 0x30 : 0x14;// + ((unsigned int)strlen(this->unityVersion) + 1); +} + +const uint8_t GlobalTypeTreeStringTable[] = { + 0x41, 0x41, 0x42, 0x42, 0x00, 0x41, 0x6E, 0x69, 0x6D, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x43, 0x6C, 0x69, 0x70, 0x00, 0x41, 0x6E, 0x69, 0x6D, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x43, 0x75, 0x72, 0x76, 0x65, 0x00, 0x41, 0x6E, 0x69, 0x6D, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x53, 0x74, 0x61, 0x74, 0x65, 0x00, 0x41, 0x72, 0x72, 0x61, 0x79, 0x00, 0x42, 0x61, 0x73, 0x65, 0x00, 0x42, 0x69, 0x74, 0x46, 0x69, 0x65, 0x6C, 0x64, 0x00, 0x62, 0x69, 0x74, 0x73, 0x65, 0x74, 0x00, 0x62, 0x6F, 0x6F, 0x6C, 0x00, 0x63, 0x68, 0x61, 0x72, 0x00, 0x43, 0x6F, 0x6C, 0x6F, 0x72, 0x52, 0x47, 0x42, 0x41, 0x00, 0x43, 0x6F, 0x6D, 0x70, 0x6F, 0x6E, 0x65, 0x6E, 0x74, 0x00, 0x64, 0x61, 0x74, 0x61, 0x00, 0x64, 0x65, 0x71, 0x75, 0x65, 0x00, 0x64, 0x6F, 0x75, 0x62, 0x6C, 0x65, 0x00, 0x64, 0x79, 0x6E, 0x61, 0x6D, 0x69, 0x63, 0x5F, 0x61, 0x72, 0x72, 0x61, 0x79, 0x00, 0x46, 0x61, 0x73, 0x74, 0x50, 0x72, 0x6F, 0x70, 0x65, 0x72, 0x74, 0x79, 0x4E, 0x61, 0x6D, 0x65, 0x00, 0x66, 0x69, 0x72, 0x73, 0x74, 0x00, 0x66, 0x6C, 0x6F, 0x61, 0x74, 0x00, 0x46, 0x6F, 0x6E, 0x74, 0x00, 0x47, 0x61, 0x6D, 0x65, 0x4F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x00, 0x47, 0x65, 0x6E, 0x65, 0x72, 0x69, 0x63, 0x20, 0x4D, 0x6F, 0x6E, 0x6F, 0x00, 0x47, 0x72, 0x61, 0x64, 0x69, 0x65, 0x6E, 0x74, 0x4E, 0x45, 0x57, 0x00, 0x47, 0x55, 0x49, 0x44, 0x00, 0x47, 0x55, 0x49, 0x53, 0x74, 0x79, 0x6C, 0x65, 0x00, 0x69, 0x6E, 0x74, 0x00, 0x6C, 0x69, 0x73, 0x74, 0x00, 0x6C, 0x6F, 0x6E, 0x67, 0x20, 0x6C, 0x6F, 0x6E, 0x67, 0x00, 0x6D, 0x61, 0x70, 0x00, 0x4D, 0x61, 0x74, 0x72, 0x69, 0x78, 0x34, 0x78, 0x34, 0x66, 0x00, 0x4D, 0x64, 0x46, 0x6F, 0x75, 0x72, 0x00, 0x4D, 0x6F, 0x6E, 0x6F, 0x42, 0x65, 0x68, 0x61, 0x76, 0x69, 0x6F, 0x75, 0x72, 0x00, 0x4D, 0x6F, 0x6E, 0x6F, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x00, 0x6D, 0x5F, 0x42, 0x79, 0x74, 0x65, 0x53, 0x69, 0x7A, 0x65, 0x00, 0x6D, 0x5F, 0x43, 0x75, 0x72, 0x76, 0x65, 0x00, 0x6D, 0x5F, 0x45, 0x64, 0x69, 0x74, 0x6F, 0x72, 0x43, 0x6C, 0x61, 0x73, 0x73, 0x49, 0x64, 0x65, 0x6E, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x00, 0x6D, 0x5F, 0x45, 0x64, 0x69, 0x74, 0x6F, 0x72, 0x48, 0x69, 0x64, 0x65, 0x46, 0x6C, 0x61, 0x67, 0x73, 0x00, 0x6D, 0x5F, 0x45, 0x6E, 0x61, 0x62, 0x6C, 0x65, 0x64, 0x00, 0x6D, 0x5F, 0x45, 0x78, 0x74, 0x65, 0x6E, 0x73, 0x69, 0x6F, 0x6E, 0x50, 0x74, 0x72, 0x00, 0x6D, 0x5F, 0x47, 0x61, 0x6D, 0x65, 0x4F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x00, 0x6D, 0x5F, 0x49, 0x6E, 0x64, 0x65, 0x78, 0x00, 0x6D, 0x5F, 0x49, 0x73, 0x41, 0x72, 0x72, 0x61, 0x79, 0x00, 0x6D, 0x5F, 0x49, 0x73, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x00, 0x6D, 0x5F, 0x4D, 0x65, 0x74, 0x61, 0x46, 0x6C, 0x61, 0x67, 0x00, 0x6D, 0x5F, 0x4E, 0x61, 0x6D, 0x65, 0x00, 0x6D, 0x5F, 0x4F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x48, 0x69, 0x64, 0x65, 0x46, 0x6C, 0x61, 0x67, 0x73, 0x00, 0x6D, 0x5F, 0x50, 0x72, 0x65, 0x66, 0x61, 0x62, 0x49, 0x6E, 0x74, 0x65, 0x72, 0x6E, 0x61, 0x6C, 0x00, 0x6D, 0x5F, 0x50, 0x72, 0x65, 0x66, 0x61, 0x62, 0x50, 0x61, 0x72, 0x65, 0x6E, 0x74, 0x4F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x00, 0x6D, 0x5F, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x00, 0x6D, 0x5F, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x45, 0x64, 0x69, 0x74, 0x6F, 0x72, 0x46, 0x6C, 0x61, 0x67, 0x73, 0x00, 0x6D, 0x5F, 0x54, 0x79, 0x70, 0x65, 0x00, 0x6D, 0x5F, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x00, 0x4F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x00, 0x70, 0x61, 0x69, 0x72, 0x00, 0x50, 0x50, 0x74, 0x72, 0x3C, 0x43, 0x6F, 0x6D, 0x70, 0x6F, 0x6E, 0x65, 0x6E, 0x74, 0x3E, 0x00, 0x50, 0x50, 0x74, 0x72, 0x3C, 0x47, 0x61, 0x6D, 0x65, 0x4F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x3E, 0x00, 0x50, 0x50, 0x74, 0x72, 0x3C, 0x4D, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6C, 0x3E, 0x00, 0x50, 0x50, 0x74, 0x72, 0x3C, 0x4D, 0x6F, 0x6E, 0x6F, 0x42, 0x65, 0x68, 0x61, 0x76, 0x69, 0x6F, 0x75, 0x72, 0x3E, 0x00, 0x50, 0x50, 0x74, 0x72, 0x3C, 0x4D, 0x6F, 0x6E, 0x6F, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3E, 0x00, 0x50, 0x50, 0x74, 0x72, 0x3C, 0x4F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x3E, 0x00, 0x50, 0x50, 0x74, 0x72, 0x3C, 0x50, 0x72, 0x65, 0x66, 0x61, 0x62, 0x3E, 0x00, 0x50, 0x50, 0x74, 0x72, 0x3C, 0x53, 0x70, 0x72, 0x69, 0x74, 0x65, 0x3E, 0x00, 0x50, 0x50, 0x74, 0x72, 0x3C, 0x54, 0x65, 0x78, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x3E, 0x00, 0x50, 0x50, 0x74, 0x72, 0x3C, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x3E, 0x00, 0x50, 0x50, 0x74, 0x72, 0x3C, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x32, 0x44, 0x3E, 0x00, 0x50, 0x50, 0x74, 0x72, 0x3C, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x66, 0x6F, 0x72, 0x6D, 0x3E, 0x00, 0x50, 0x72, 0x65, 0x66, 0x61, 0x62, 0x00, 0x51, 0x75, 0x61, 0x74, 0x65, 0x72, 0x6E, 0x69, 0x6F, 0x6E, 0x66, 0x00, 0x52, 0x65, 0x63, 0x74, 0x66, 0x00, 0x52, 0x65, 0x63, 0x74, 0x49, 0x6E, 0x74, 0x00, 0x52, 0x65, 0x63, 0x74, 0x4F, 0x66, 0x66, 0x73, 0x65, 0x74, 0x00, 0x73, 0x65, 0x63, 0x6F, 0x6E, 0x64, 0x00, 0x73, 0x65, 0x74, 0x00, 0x73, 0x68, 0x6F, 0x72, 0x74, 0x00, 0x73, 0x69, 0x7A, 0x65, 0x00, 0x53, 0x49, 0x6E, 0x74, 0x31, 0x36, 0x00, 0x53, 0x49, 0x6E, 0x74, 0x33, 0x32, 0x00, 0x53, 0x49, 0x6E, 0x74, 0x36, 0x34, 0x00, 0x53, 0x49, 0x6E, 0x74, 0x38, 0x00, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x76, 0x65, 0x63, 0x74, 0x6F, 0x72, 0x00, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x00, 0x54, 0x65, 0x78, 0x74, 0x41, 0x73, 0x73, 0x65, 0x74, 0x00, 0x54, 0x65, 0x78, 0x74, 0x4D, 0x65, 0x73, 0x68, 0x00, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x00, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x32, 0x44, 0x00, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x66, 0x6F, 0x72, 0x6D, 0x00, 0x54, 0x79, 0x70, 0x65, 0x6C, 0x65, 0x73, 0x73, 0x44, 0x61, 0x74, 0x61, 0x00, 0x55, 0x49, 0x6E, 0x74, 0x31, 0x36, 0x00, 0x55, 0x49, 0x6E, 0x74, 0x33, 0x32, 0x00, 0x55, 0x49, 0x6E, 0x74, 0x36, 0x34, 0x00, 0x55, 0x49, 0x6E, 0x74, 0x38, 0x00, 0x75, 0x6E, 0x73, 0x69, 0x67, 0x6E, 0x65, 0x64, 0x20, 0x69, 0x6E, 0x74, 0x00, 0x75, 0x6E, 0x73, 0x69, 0x67, 0x6E, 0x65, 0x64, 0x20, 0x6C, 0x6F, 0x6E, 0x67, 0x20, 0x6C, 0x6F, 0x6E, 0x67, 0x00, 0x75, 0x6E, 0x73, 0x69, 0x67, 0x6E, 0x65, 0x64, 0x20, 0x73, 0x68, 0x6F, 0x72, 0x74, 0x00, 0x76, 0x65, 0x63, 0x74, 0x6F, 0x72, 0x00, 0x56, 0x65, 0x63, 0x74, 0x6F, 0x72, 0x32, 0x66, 0x00, 0x56, 0x65, 0x63, 0x74, 0x6F, 0x72, 0x33, 0x66, 0x00, 0x56, 0x65, 0x63, 0x74, 0x6F, 0x72, 0x34, 0x66, 0x00, 0x6D, 0x5F, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6E, 0x67, 0x43, 0x6C, 0x61, 0x73, 0x73, 0x49, 0x64, 0x65, 0x6E, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x00, 0x47, 0x72, 0x61, 0x64, 0x69, 0x65, 0x6E, 0x74, 0x00, 0x54, 0x79, 0x70, 0x65, 0x2A, 0x00, 0x69, 0x6E, 0x74, 0x32, 0x5F, 0x73, 0x74, 0x6F, 0x72, 0x61, 0x67, 0x65, 0x00, 0x69, 0x6E, 0x74, 0x33, 0x5F, 0x73, 0x74, 0x6F, 0x72, 0x61, 0x67, 0x65, 0x00, 0x42, 0x6F, 0x75, 0x6E, 0x64, 0x73, 0x49, 0x6E, 0x74, 0x00, 0x6D, 0x5F, 0x43, 0x6F, 0x72, 0x72, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x64, 0x69, 0x6E, 0x67, 0x53, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x4F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x00, 0x6D, 0x5F, 0x50, 0x72, 0x65, 0x66, 0x61, 0x62, 0x49, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x63, 0x65, 0x00, 0x6D, 0x5F, 0x50, 0x72, 0x65, 0x66, 0x61, 0x62, 0x41, 0x73, 0x73, 0x65, 0x74, 0x00, 0x46, 0x69, 0x6C, 0x65, 0x53, 0x69, 0x7A, 0x65, 0x00, 0x48, 0x61, 0x73, 0x68, 0x31, 0x32, 0x38, 0x00, 0x00 +}; + +ASSETSTOOLS_API QWORD TypeField_0D::Read(QWORD curFilePos, IAssetsReader *pReader, uint32_t format, bool bigEndian) +{ + uint8_t _isArrayTemp; + + pReader->Read(curFilePos, 2, &this->version); curFilePos += 2; + if (bigEndian) + SwapEndians_(this->version); + pReader->Read(curFilePos, 1, &this->depth); curFilePos ++; + pReader->Read(curFilePos, 1, &_isArrayTemp); curFilePos ++; + this->isArray = (format >= 0x13) ? _isArrayTemp : (_isArrayTemp != 0 ? 1 : 0); + pReader->Read(curFilePos, 4, &this->typeStringOffset); curFilePos += 4; + if (bigEndian) + SwapEndians_(this->typeStringOffset); + pReader->Read(curFilePos, 4, &this->nameStringOffset); curFilePos += 4; + if (bigEndian) + SwapEndians_(this->nameStringOffset); + pReader->Read(curFilePos, 4, &this->size); curFilePos += 4; + if (bigEndian) + SwapEndians_(this->size); + pReader->Read(curFilePos, 4, &this->index); curFilePos += 4; + if (bigEndian) + SwapEndians_(this->index); + pReader->Read(curFilePos, 4, &this->flags); curFilePos += 4; + if (bigEndian) + SwapEndians_(this->flags); + + if (format >= 0x12) + { + pReader->Read(curFilePos, 8, &this->unknown1); curFilePos += 8; + } + + return curFilePos; +} +ASSETSTOOLS_API QWORD TypeField_0D::Write(QWORD curFilePos, IAssetsWriter *pWriter, uint32_t format, bool bigEndian) +{ + uint16_t wTmp; uint32_t dwTmp; + + wTmp = this->version; + if (bigEndian) + SwapEndians_(wTmp); + pWriter->Write(curFilePos, 2, &wTmp); curFilePos += 2; + + pWriter->Write(curFilePos, 1, &this->depth); curFilePos ++; + pWriter->Write(curFilePos, 1, &this->isArray); curFilePos ++; + + dwTmp = this->typeStringOffset; + if (bigEndian) + SwapEndians_(dwTmp); + pWriter->Write(curFilePos, 4, &dwTmp); curFilePos += 4; + + dwTmp = this->nameStringOffset; + if (bigEndian) + SwapEndians_(dwTmp); + pWriter->Write(curFilePos, 4, &dwTmp); curFilePos += 4; + + dwTmp = this->size; + if (bigEndian) + SwapEndians_(dwTmp); + pWriter->Write(curFilePos, 4, &dwTmp); curFilePos += 4; + + dwTmp = this->index; + if (bigEndian) + SwapEndians_(dwTmp); + pWriter->Write(curFilePos, 4, &dwTmp); curFilePos += 4; + + dwTmp = this->flags; + if (bigEndian) + SwapEndians_(dwTmp); + pWriter->Write(curFilePos, 4, &dwTmp); curFilePos += 4; + + if (format >= 0x12) + { + pWriter->Write(curFilePos, 8, &this->unknown1); curFilePos += 8; + } + + return curFilePos; +} +ASSETSTOOLS_API const char *TypeField_0D::GetTypeString(const char *stringTable, size_t stringTableLen) +{ + const char *type = NULL; + if (typeStringOffset & 0x80000000) + { + if ((typeStringOffset & 0x7FFFFFFF) < (sizeof(GlobalTypeTreeStringTable)-1)) + type = (const char*)&GlobalTypeTreeStringTable[typeStringOffset & 0x7FFFFFFF]; + } + else if (typeStringOffset < (stringTableLen-1)) + { + type = &stringTable[typeStringOffset]; + } + return type; +} +ASSETSTOOLS_API const char *TypeField_0D::GetNameString(const char *stringTable, size_t stringTableLen) +{ + const char *name = NULL; + if (nameStringOffset & 0x80000000) + { + if ((nameStringOffset & 0x7FFFFFFF) < (sizeof(GlobalTypeTreeStringTable)-1)) + name = (const char*)&GlobalTypeTreeStringTable[nameStringOffset & 0x7FFFFFFF]; + } + else if (nameStringOffset < (stringTableLen-1)) + { + name = &stringTable[nameStringOffset]; + } + return name; +} + +ASSETSTOOLS_API QWORD Type_0D::Write(bool hasTypeTree, QWORD absFilePos, IAssetsWriter *pWriter, uint32_t version, bool bigEndian, bool secondaryTypeTree) +{ + uint32_t dwTmp; uint16_t wTmp; + pWriter->Write(absFilePos, 4, &classId); absFilePos += 4; + if (version >= 16) + { + pWriter->Write(absFilePos, 1, &this->unknown16_1); absFilePos += 1; + if (version >= 17) + { + wTmp = this->scriptIndex; + if (bigEndian) + SwapEndians_(wTmp); + pWriter->Write(absFilePos, 2, &wTmp); absFilePos += 2; + } + } + if ((classId < 0) || (this->classId == 0x72) || (this->classId == 0x7C90B5B3) || ((short)this->scriptIndex) >= 0) + { + pWriter->Write(absFilePos, 16, this->scriptIDHash.qValue); absFilePos += 16; + } + pWriter->Write(absFilePos, 16, this->typeHash.qValue); absFilePos += 16; + + if (hasTypeTree) + { + dwTmp = this->typeFieldsExCount; + if (bigEndian) + SwapEndians_(dwTmp); + pWriter->Write(absFilePos, 4, &dwTmp); absFilePos += 4; + + dwTmp = this->stringTableLen; + if (bigEndian) + SwapEndians_(dwTmp); + pWriter->Write(absFilePos, 4, &dwTmp); absFilePos += 4; + for (uint32_t i = 0; i < this->typeFieldsExCount; i++) + { + absFilePos = this->pTypeFieldsEx[i].Write(absFilePos, pWriter, version, bigEndian); + } + pWriter->Write(absFilePos, this->stringTableLen, this->pStringTable); absFilePos += this->stringTableLen; + + if (version >= 0x15) + { + if (!secondaryTypeTree) + { + dwTmp = this->depListLen; + if (bigEndian) + SwapEndians_(dwTmp); + pWriter->Write(absFilePos, 4, &dwTmp); absFilePos += 4; + if (bigEndian) + { + for (uint32_t i = 0; i < this->depListLen; i++) + { + dwTmp = SwapEndians(this->pDepList[i]); + pWriter->Write(absFilePos, 4, &dwTmp); absFilePos += 4; + } + } + else + { + pWriter->Write(absFilePos, this->depListLen * 4, this->pDepList); absFilePos += this->depListLen * 4; + } + } + else + { + const char *header1 = this->header1 ? this->header1 : ""; + const char *header2 = this->header2 ? this->header2 : ""; + const char *header3 = this->header3 ? this->header3 : ""; + size_t header1Len = strlen(header1) * sizeof(char) + 1; + size_t header2Len = strlen(header2) * sizeof(char) + 1; + size_t header3Len = strlen(header3) * sizeof(char) + 1; + pWriter->Write(absFilePos, header1Len, this->header1); absFilePos += header1Len; + pWriter->Write(absFilePos, header2Len, this->header2); absFilePos += header2Len; + pWriter->Write(absFilePos, header3Len, this->header3); absFilePos += header3Len; + } + } + } + return absFilePos; +} +ASSETSTOOLS_API QWORD Type_0D::Read(bool hasTypeTree, QWORD absFilePos, IAssetsReader *pReader, uint32_t version, bool bigEndian, bool secondaryTypeTree) +{ + QWORD curFilePos = absFilePos; + if (version >= 0x0D) //Unity 5 + { + pReader->Read(curFilePos, 4, &this->classId); curFilePos += 4; + if (bigEndian) + SwapEndians_(this->classId); + if (version >= 16) + { + pReader->Read(curFilePos, 1, &this->unknown16_1); curFilePos += 1; + } + else + { + this->unknown16_1 = 0; + } + if (version >= 17) + { + pReader->Read(curFilePos, 2, &this->scriptIndex); curFilePos += 2; + if (bigEndian) + SwapEndians_(this->scriptIndex); + } + else + { + this->scriptIndex = 0xFFFF; + } + if ((classId < 0) || (this->classId == 0x72) || (this->classId == 0x7C90B5B3) || ((short)this->scriptIndex) >= 0) //MonoBehaviour + { + pReader->Read(curFilePos, 16, this->scriptIDHash.qValue); curFilePos += 16; + } + pReader->Read(curFilePos, 16, this->typeHash.qValue); curFilePos += 16; + this->typeFieldsExCount = 0; + this->pTypeFieldsEx = NULL; + this->stringTableLen = 0; + this->pStringTable = NULL; + this->depListLen = 0; + this->pDepList = nullptr; + this->header1 = this->header2 = this->header3 = nullptr; + if (hasTypeTree) + { + uint32_t dwVariableCount; + uint32_t dwStringTableLen; + pReader->Read(curFilePos, 4, &dwVariableCount); curFilePos += 4; + if (bigEndian) + SwapEndians_(dwVariableCount); + pReader->Read(curFilePos, 4, &dwStringTableLen); curFilePos += 4; + if (bigEndian) + SwapEndians_(dwStringTableLen); + uint32_t variableFieldsLen = (dwVariableCount * (version >= 0x12 ? 32 : 24)); + uint32_t typeTreeLen = variableFieldsLen + dwStringTableLen; + void *pTreeBuffer = malloc(typeTreeLen + 1); + if (pTreeBuffer == NULL) + { + curFilePos += typeTreeLen; + } + else + { + pReader->Read(curFilePos, typeTreeLen, pTreeBuffer); curFilePos += typeTreeLen; + //((uint8_t*)pTreeBuffer)[typeTreeLen] = 0; //make sure the string table is null-terminated + IAssetsReader *pNewReader = Create_AssetsReaderFromMemory(pTreeBuffer, typeTreeLen, false); + if (pNewReader != NULL) + { + TypeField_0D *pTypeFields = new TypeField_0D[dwVariableCount]; + QWORD newFilePos = 0; + for (uint32_t i = 0; i < dwVariableCount; i++) + { + newFilePos = pTypeFields[i].Read(newFilePos, pNewReader, version, bigEndian); + } + this->typeFieldsExCount = dwVariableCount; + this->pTypeFieldsEx = pTypeFields; + + bool appendNullTerminator = (typeTreeLen == 0) || (((uint8_t*)pTreeBuffer)[typeTreeLen-1]) != 0; + void *pStringTable = malloc(dwStringTableLen + (appendNullTerminator?1:0)); + if (pStringTable != NULL) + { + memcpy(pStringTable, &((uint8_t*)pTreeBuffer)[variableFieldsLen], dwStringTableLen); + if (appendNullTerminator) + ((uint8_t*)pStringTable)[dwStringTableLen] = 0; + this->stringTableLen = dwStringTableLen; + this->pStringTable = (char*)pStringTable; + } + else + { + this->stringTableLen = 0; + this->pStringTable = NULL; + } + Free_AssetsReader(pNewReader); + } + free(pTreeBuffer); + } + if (version >= 0x15) + { + if (!secondaryTypeTree) + { + pReader->Read(curFilePos, 4, &this->depListLen); curFilePos += 4; + if (bigEndian) + SwapEndians_(this->depListLen); + if (static_cast(this->depListLen) >= 0) + { + this->pDepList = new unsigned int[this->depListLen]; + pReader->Read(curFilePos, this->depListLen * 4, this->pDepList); curFilePos += this->depListLen * 4; + if (bigEndian) + { + for (uint32_t i = 0; i < this->depListLen; i++) + SwapEndians_(this->pDepList[i]); + } + } + else + this->depListLen = 0; + } + else //if (secondaryTypeTree) + { + char *header[3] = {nullptr, nullptr, nullptr}; + std::vector headerBuf; + for (int i = 0; i < 3; i++) + { + headerBuf.clear(); + size_t start = 0; + bool foundNullCharacter = false; + do { + headerBuf.resize(headerBuf.size() + 32); + pReader->Read(curFilePos + start, 32, headerBuf.data() + start); + for (size_t i = start; i < start + 32; i++) + { + if (headerBuf[i] == 0) + { + foundNullCharacter = true; + headerBuf.resize(i + 1); + break; + } + } + start = headerBuf.size(); + } while (!foundNullCharacter); + curFilePos += headerBuf.size(); + header[i] = new char[headerBuf.size()]; + memcpy(header[i], headerBuf.data(), headerBuf.size()); + } + this->header1 = header[0]; + this->header2 = header[1]; + this->header3 = header[2]; + } + } + } + return curFilePos; + } + else + { + memset(this, 0, sizeof(Type_0D)); + return Type_07().Read(hasTypeTree, absFilePos, pReader, version, bigEndian); + } +} + +ASSETSTOOLS_API QWORD TypeField_07::Read(bool hasTypeTree, QWORD absFilePos, IAssetsReader *pReader, uint32_t version, bool bigEndian) +{ + QWORD curFilePos = absFilePos; + pReader->Read(curFilePos, 256, this->type); + this->type[255] = 0; + curFilePos += (strlen(this->type)+1); + + pReader->Read(curFilePos, 256, this->name); + this->name[255] = 0; + curFilePos += (strlen(this->name)+1); + + pReader->Read(curFilePos, 4, &this->size); curFilePos += 4; + if (bigEndian) + SwapEndians_(this->size); + if (version == 2) curFilePos += 4; + if (version == 3) { this->index = (uint32_t)-1; } + else + { + pReader->Read(curFilePos, 4, &this->index); curFilePos += 4; + if (bigEndian) + SwapEndians_(this->index); + } + pReader->Read(curFilePos, 4, &this->arrayFlag); curFilePos += 4; + if (bigEndian) + SwapEndians_(this->arrayFlag); + pReader->Read(curFilePos, 4, &this->flags1); curFilePos += 4; + if (bigEndian) + SwapEndians_(this->flags1); + if (version == 3) { this->flags2 = (uint32_t)-1; } + else + { + pReader->Read(curFilePos, 4, &this->flags2); curFilePos += 4; + if (bigEndian) + SwapEndians_(this->flags2); + } + + if (hasTypeTree) + { + pReader->Read(curFilePos, 4, &this->childrenCount); curFilePos += 4; + if (bigEndian) + SwapEndians_(this->childrenCount); + this->children = new TypeField_07[childrenCount](); + for (uint32_t i = 0; i < childrenCount; i++) + curFilePos = this->children[i].Read(hasTypeTree, curFilePos, pReader, version, bigEndian); + } + return curFilePos; +} +ASSETSTOOLS_API QWORD TypeField_07::Write(bool hasTypeTree, QWORD absFilePos, IAssetsWriter *pWriter, bool bigEndian) +{ + uint32_t dwTmp; + QWORD curFilePos = absFilePos; + pWriter->Write(curFilePos, strlen(this->type)+1, this->type); curFilePos += (strlen(this->type)+1); + + pWriter->Write(curFilePos, strlen(this->name)+1, this->name); curFilePos += (strlen(this->name)+1); + + dwTmp = this->size; + if (bigEndian) + SwapEndians_(dwTmp); + pWriter->Write(curFilePos, 4, &dwTmp); curFilePos += 4; + + dwTmp = this->index; + if (bigEndian) + SwapEndians_(dwTmp); + pWriter->Write(curFilePos, 4, &dwTmp); curFilePos += 4; + + dwTmp = this->arrayFlag; + if (bigEndian) + SwapEndians_(dwTmp); + pWriter->Write(curFilePos, 4, &dwTmp); curFilePos += 4; + + dwTmp = this->flags1; + if (bigEndian) + SwapEndians_(dwTmp); + pWriter->Write(curFilePos, 4, &dwTmp); curFilePos += 4; + + dwTmp = this->flags2; + if (bigEndian) + SwapEndians_(dwTmp); + pWriter->Write(curFilePos, 4, &dwTmp); curFilePos += 4; + + if (hasTypeTree) + { + dwTmp = this->childrenCount; + if (bigEndian) + SwapEndians_(dwTmp); + pWriter->Write(curFilePos, 4, &dwTmp); curFilePos += 4; + for (uint32_t i = 0; i < childrenCount; i++) + curFilePos = this->children[i].Write(hasTypeTree, curFilePos, pWriter, bigEndian); + } + return curFilePos; +} +ASSETSTOOLS_API QWORD Type_07::Write(bool hasTypeTree, QWORD absFilePos, IAssetsWriter *pWriter, bool bigEndian) +{ + QWORD curFilePos = absFilePos; + + uint32_t dwTmp = classId; + if (bigEndian) + SwapEndians_(dwTmp); + pWriter->Write(curFilePos, 4, &dwTmp); curFilePos += 4; + curFilePos = base.Write(hasTypeTree, curFilePos, pWriter, bigEndian); + + return curFilePos; +} +ASSETSTOOLS_API QWORD Type_07::Read(bool hasTypeTree, QWORD absFilePos, IAssetsReader *pReader, uint32_t version, bool bigEndian) +{ + QWORD curFilePos = absFilePos; + + if (version >= 0x0D) + { + memset(this, 0, sizeof(Type_07)); + return Type_0D().Read(hasTypeTree, absFilePos, pReader, version, bigEndian); + } + + pReader->Read(curFilePos, 4, &classId); curFilePos += 4; + if (bigEndian) + SwapEndians_(classId); + curFilePos = base.Read(hasTypeTree, curFilePos, pReader, version, bigEndian); + + return curFilePos; +} + +ASSETSTOOLS_API QWORD TypeTree::Write(QWORD absFilePos, IAssetsWriter *pWriter, uint32_t version, bool bigEndian) //Minimum AssetsFile format : 7 +{ + uint32_t dwTmp; + QWORD curFilePos = absFilePos; + pWriter->Write(curFilePos, strlen(this->unityVersion)+1, this->unityVersion); curFilePos += strlen(this->unityVersion)+1; + + dwTmp = this->platform; + if (bigEndian) + SwapEndians_(dwTmp); + pWriter->Write(curFilePos, 4, &dwTmp); curFilePos += 4; + if (version >= 0x0D) //Unity 5 + { + pWriter->Write(curFilePos, 1, &hasTypeTree); curFilePos++; + } + + dwTmp = this->fieldCount; + if (bigEndian) + SwapEndians_(dwTmp); + pWriter->Write(curFilePos, 4, &dwTmp); curFilePos += 4; + if (this->fieldCount > 0) + { + if (version < 0x0D) + { + if (this->pTypes_Unity4 != NULL) + { + for (uint32_t i = 0; i < fieldCount; i++) + curFilePos = this->pTypes_Unity4[i].Write(hasTypeTree, curFilePos, pWriter, bigEndian); + } + } + else + { + if (this->pTypes_Unity5 != NULL) + { + for (uint32_t i = 0; i < fieldCount; i++) + curFilePos = this->pTypes_Unity5[i].Write(hasTypeTree, curFilePos, pWriter, version, bigEndian); + } + } + } + if (version < 0x0E) + { + dwTmp = this->dwUnknown; + if (bigEndian) + SwapEndians_(dwTmp); + //actually belongs to the asset file info tree + pWriter->Write(curFilePos, 4, &dwTmp); curFilePos += 4; + } + return curFilePos; +} +ASSETSTOOLS_API QWORD TypeTree::Read(QWORD absFilePos, IAssetsReader *pReader, uint32_t version, bool bigEndian) //Minimum AssetsFile format : 7 +{ + _fmt = version; + hasTypeTree = true; + + QWORD curFilePos = absFilePos; + if (version > 6) + { + pReader->Read(curFilePos, sizeof(this->unityVersion), this->unityVersion); + this->unityVersion[sizeof(this->unityVersion) - 1] = 0; + curFilePos += (strlen(this->unityVersion) + 1); + if (this->unityVersion[0] < '0' || this->unityVersion[0] > '9') + { + this->fieldCount = 0; + return curFilePos; + } + pReader->Read(curFilePos, 4, &this->platform); curFilePos += 4; + if (bigEndian) + SwapEndians_(this->platform); + } + else + { + this->platform = 0; + if (version == 6) + { + strcpy(this->unityVersion, "Unsupported 2.6+"); + } + else if (version == 5) + { + strcpy(this->unityVersion, "Unsupported 2.0+"); + } + else + { + strcpy(this->unityVersion, "Unsupported Unknown"); + } + this->fieldCount = 0; //not supported + return curFilePos; + } + + if (version >= 0x0D) //Unity 5 + { + pReader->Read(curFilePos, 1, &hasTypeTree); curFilePos++; + } + pReader->Read(curFilePos, 4, &this->fieldCount); curFilePos += 4; + if (bigEndian) + SwapEndians_(this->fieldCount); + if (this->fieldCount > 0) + { + if (version < 0x0D) + { + this->pTypes_Unity4 = new Type_07[fieldCount](); + for (uint32_t i = 0; i < fieldCount; i++) + { + curFilePos = this->pTypes_Unity4[i].Read(hasTypeTree, curFilePos, pReader, version, bigEndian); + } + } + else + { + this->pTypes_Unity5 = new Type_0D[fieldCount](); + for (uint32_t i = 0; i < fieldCount; i++) + curFilePos = this->pTypes_Unity5[i].Read(hasTypeTree, curFilePos, pReader, version, bigEndian); + } + } + else + { + this->pTypes_Unity4 = NULL; + this->pTypes_Unity5 = NULL; + } + if (version < 0x0E) + { + //actually belongs to the asset file info tree + pReader->Read(curFilePos, 4, &this->dwUnknown); curFilePos += 4; + if (bigEndian) + SwapEndians_(this->dwUnknown); + } + else + this->dwUnknown = 0; + return curFilePos; +} +void __RecursiveDeleteChildren(TypeField_07 *pCurTarget) +{ + for (uint32_t i = 0; i < pCurTarget->childrenCount; i++) + { + if (pCurTarget->children[i].childrenCount > 0) + __RecursiveDeleteChildren(&pCurTarget->children[i]); + } + if (pCurTarget->childrenCount > 0) + { + delete[] pCurTarget->children; + pCurTarget->children = NULL; + pCurTarget->childrenCount = 0; + } +} +ASSETSTOOLS_API void TypeTree::Clear() +{ + if (fieldCount > 0) + { + if (_fmt >= 0x0D) + { + for (uint32_t i = 0; i < fieldCount; i++) + { + if (pTypes_Unity5[i].stringTableLen > 0) + free(pTypes_Unity5[i].pStringTable); + if (pTypes_Unity5[i].typeFieldsExCount > 0) + delete[] pTypes_Unity5[i].pTypeFieldsEx; + if (pTypes_Unity5[i].pDepList) + delete[] pTypes_Unity5[i].pDepList; + if (pTypes_Unity5[i].header1) + delete[] pTypes_Unity5[i].header1; + if (pTypes_Unity5[i].header2) + delete[] pTypes_Unity5[i].header2; + if (pTypes_Unity5[i].header3) + delete[] pTypes_Unity5[i].header3; + } + delete[] pTypes_Unity5; + pTypes_Unity5 = NULL; + fieldCount = 0; + } + else + { + for (uint32_t i = 0; i < fieldCount; i++) + { + TypeField_07 *pBaseField = &pTypes_Unity4[i].base; + while (pBaseField->childrenCount > 0) + { + __RecursiveDeleteChildren(pBaseField); + } + } + delete[] pTypes_Unity4; + pTypes_Unity4 = NULL; + fieldCount = 0; + } + } +} + +ASSETSTOOLS_API QWORD AssetsFileDependency::GUID128::Read(QWORD absFilePos, IAssetsReader *pReader) +{ + pReader->Read(absFilePos, 8, &this->mostSignificant); absFilePos += 8; + SwapEndians_<__int64>(this->mostSignificant); + pReader->Read(absFilePos, 8, &this->leastSignificant); absFilePos += 8; + SwapEndians_<__int64>(this->leastSignificant); + return absFilePos; +} +ASSETSTOOLS_API QWORD AssetsFileDependency::GUID128::Write(QWORD absFilePos, IAssetsWriter *pWriter) +{ + __int64 qwTmp = SwapEndians<__int64>(this->mostSignificant); + pWriter->Write(absFilePos, 8, &qwTmp); absFilePos += 8; + qwTmp = SwapEndians<__int64>(this->leastSignificant); + pWriter->Write(absFilePos, 8, &qwTmp); absFilePos += 8; + return absFilePos; +} + +ASSETSTOOLS_API QWORD AssetsFileDependency::Read(QWORD absFilePos, IAssetsReader *pReader, uint32_t format, bool bigEndian) +{ + if (format >= 6) + { + pReader->Read(absFilePos, 255, this->bufferedPath); + this->bufferedPath[255] = 0; + if (strlen(this->bufferedPath) == 255) + { + absFilePos += 255; + char buf[17]; + buf[16] = 0; + size_t bufLen; + do + { + buf[0] = 0; + pReader->Read(absFilePos, 16, buf); + bufLen = strlen(buf); + absFilePos += bufLen; + } while (bufLen == 16); + absFilePos++; + } + else + absFilePos += (strlen(this->bufferedPath) + 1); + } + else + this->bufferedPath[0] = 0; + + if (format >= 5) + { + absFilePos = this->guid.Read(absFilePos, pReader); + + pReader->Read(absFilePos, 4, &this->type); absFilePos += 4; + if (bigEndian) + SwapEndians_(this->type); + } + else + { + this->guid.leastSignificant = this->guid.mostSignificant = 0; + this->type = 0; + } + + pReader->Read(absFilePos, 255, this->assetPath); + this->assetPath[255] = 0; + if (strlen(this->assetPath) == 255) + { + absFilePos += 255; + char buf[17]; + buf[16] = 0; + size_t bufLen; + do + { + buf[0] = 0; + pReader->Read(absFilePos, 16, buf); + bufLen = strlen(buf); + absFilePos += bufLen; + } while (bufLen == 16); + absFilePos++; + } + else + absFilePos += (strlen(this->assetPath) + 1); + + return absFilePos; +} +ASSETSTOOLS_API QWORD AssetsFileDependency::Write(QWORD absFilePos, IAssetsWriter *pWriter, uint32_t format, bool bigEndian) +{ + if (format >= 6) + { + pWriter->Write(absFilePos, strlen(this->bufferedPath)+1, this->bufferedPath); absFilePos += strlen(this->bufferedPath)+1; + } + + if (format >= 5) + { + absFilePos = this->guid.Write(absFilePos, pWriter); + + uint32_t dwTmp = this->type; + if (bigEndian) + SwapEndians_(dwTmp); + pWriter->Write(absFilePos, 4, &dwTmp); absFilePos += 4; + } + + pWriter->Write(absFilePos, strlen(this->assetPath)+1, this->assetPath); absFilePos += strlen(this->assetPath)+1; + + return absFilePos; +} + +ASSETSTOOLS_API QWORD AssetsFileDependencyList::Write(QWORD absFilePos, IAssetsWriter *pWriter, uint32_t format, bool bigEndian) +{ + uint32_t dwTmp = this->dependencyCount; + if (bigEndian) + SwapEndians_(dwTmp); + pWriter->Write(absFilePos, 4, &dwTmp); absFilePos += 4; + //writer(absFilePos, 1, &this->unknown, writerPar); absFilePos += 1; + for (uint32_t i = 0; i < this->dependencyCount; i++) + absFilePos = this->pDependencies[i].Write(absFilePos, pWriter, format, bigEndian); + + return absFilePos; +} +ASSETSTOOLS_API QWORD AssetsFileDependencyList::Read(QWORD absFilePos, IAssetsReader *pReader, uint32_t format, bool bigEndian) +{ + //if (format >= 0x0E) //actually another field + // absFilePos += 4; + pReader->Read(absFilePos, 4, &this->dependencyCount); absFilePos += 4; + if (bigEndian) + SwapEndians_(this->dependencyCount); + //reader(absFilePos, 1, &this->unknown, readerPar); absFilePos += 1; + if (this->dependencyCount > 0) + { + this->pDependencies = (AssetsFileDependency*)malloc(this->dependencyCount * sizeof(AssetsFileDependency)); + if (this->pDependencies != NULL) + { + for (uint32_t i = 0; i < this->dependencyCount; i++) + absFilePos = this->pDependencies[i].Read(absFilePos, pReader, format, bigEndian); + } + else + { + for (uint32_t i = 0; i < this->dependencyCount; i++) + absFilePos = AssetsFileDependency().Read(absFilePos, pReader, format, bigEndian); + this->dependencyCount = 0; + } + } + else + this->pDependencies = NULL; + + return absFilePos; +} + +static QWORD SecondaryTypeList_Write(AssetsFile *pAssetsFile, QWORD absFilePos, IAssetsWriter *pWriter, uint32_t format, bool bigEndian) +{ + if (format >= 0x14) + { + uint32_t dwTmp = pAssetsFile->secondaryTypeCount; + if (bigEndian) + SwapEndians_(dwTmp); + pWriter->Write(absFilePos, 4, &dwTmp); absFilePos += 4; + if (pAssetsFile->pSecondaryTypeList != NULL) + { + for (uint32_t i = 0; i < pAssetsFile->secondaryTypeCount; i++) + absFilePos = pAssetsFile->pSecondaryTypeList[i].Write(pAssetsFile->typeTree.hasTypeTree, absFilePos, pWriter, format, bigEndian, true); + } + } + return absFilePos; +} +static QWORD SecondaryTypeList_Read(AssetsFile *pAssetsFile, QWORD absFilePos, IAssetsReader *pReader, uint32_t format, bool bigEndian) +{ + if (format >= 0x14) + { + pReader->Read(absFilePos, 4, &pAssetsFile->secondaryTypeCount); absFilePos += 4; + if (bigEndian) + SwapEndians_(pAssetsFile->secondaryTypeCount); + if (pAssetsFile->secondaryTypeCount > 0) + { + pAssetsFile->pSecondaryTypeList = new Type_0D[pAssetsFile->secondaryTypeCount](); + for (uint32_t i = 0; i < pAssetsFile->secondaryTypeCount; i++) + absFilePos = pAssetsFile->pSecondaryTypeList[i].Read(pAssetsFile->typeTree.hasTypeTree, absFilePos, pReader, format, bigEndian, true); + } + else + pAssetsFile->pSecondaryTypeList = NULL; + } + else + { + pAssetsFile->secondaryTypeCount = 0; + pAssetsFile->pSecondaryTypeList = NULL; + } + return absFilePos; +} + +static QWORD UnknownString_Write(AssetsFile *pAssetsFile, QWORD absFilePos, IAssetsWriter *pWriter, uint32_t format) +{ + if (format >= 5) + { + pWriter->Write(absFilePos, strlen(pAssetsFile->unknownString)+1, pAssetsFile->unknownString); + absFilePos += strlen(pAssetsFile->unknownString)+1; + } + return absFilePos; +} +static QWORD UnknownString_Read(AssetsFile *pAssetsFile, QWORD absFilePos, IAssetsReader *pReader, uint32_t format) +{ + if (format >= 5) + { + pReader->Read(absFilePos, 63, pAssetsFile->unknownString); + pAssetsFile->unknownString[63] = 0; + if (strlen(pAssetsFile->unknownString) == 63) + { + absFilePos += 63; + char buf[17]; + buf[16] = 0; + size_t bufLen; + do + { + buf[0] = 0; + pReader->Read(absFilePos, 16, buf); + bufLen = strlen(buf); + absFilePos += bufLen; + } while (bufLen == 16); + absFilePos++; + } + else + absFilePos += (strlen(pAssetsFile->unknownString) + 1); + } + else + pAssetsFile->unknownString[0] = 0; + return absFilePos; +} + + +ASSETSTOOLS_API QWORD PreloadList::Write(QWORD absFilePos, IAssetsWriter *pWriter, uint32_t format, bool bigEndian) +{ + uint32_t dwTmp; + + dwTmp = len; + if (bigEndian) + SwapEndians_(dwTmp); + pWriter->Write(absFilePos, 4, &dwTmp); absFilePos += 4; + for (uint32_t i = 0; i < len; i++) + { + dwTmp = items[i].fileID; + if (bigEndian) + SwapEndians_(dwTmp); + pWriter->Write(absFilePos, 4, &dwTmp); absFilePos += 4; + if (format >= 0x0E) + { + absFilePos = ((absFilePos + 3) & (~0x3)); + + QWORD qwTmp = items[i].pathID; + if (bigEndian) + SwapEndians_(qwTmp); + pWriter->Write(absFilePos, 8, &qwTmp); absFilePos += 8; + } + else + { + dwTmp = (uint32_t)items[i].pathID; + if (bigEndian) + SwapEndians_(dwTmp); + pWriter->Write(absFilePos, 4, &dwTmp); absFilePos += 4; + } + } + return absFilePos; +} +ASSETSTOOLS_API QWORD PreloadList::Read(QWORD absFilePos, IAssetsReader *pReader, uint32_t format, bool bigEndian) +{ + pReader->Read(absFilePos, 4, &len); absFilePos += 4; + if (bigEndian) + SwapEndians_(this->len); + if (len > 0) + this->items = new AssetPPtr[len]; + for (uint32_t i = 0; i < len; i++) + { + pReader->Read(absFilePos, 4, &items[i].fileID); absFilePos += 4; + if (bigEndian) + SwapEndians_(items[i].fileID); + if (format >= 0x0E) + { + absFilePos = ((absFilePos + 3) & (~0x3)); + pReader->Read(absFilePos, 8, &items[i].pathID); absFilePos += 8; + if (bigEndian) + SwapEndians_(items[i].pathID); + } + else + { + uint32_t pathID; + pReader->Read(absFilePos, 4, &pathID); absFilePos += 4; + if (bigEndian) + SwapEndians_(pathID); + items[i].pathID = (QWORD)pathID; + } + } + return absFilePos; +} +ASSETSTOOLS_API QWORD AssetsFile::Write(IAssetsWriter *pWriter, QWORD filePos, AssetsReplacer **replacers, size_t replacerCount, uint32_t fileID, + ClassDatabaseFile *typeMeta) +{ + bool isOffsetWriter = filePos != 0; + QWORD filePosOffset = filePos; + if (isOffsetWriter) + { + pWriter = Create_AssetsWriterToWriterOffset(pWriter, filePos); + if (pWriter == NULL) + return 0; + filePos = 0; + } + QWORD headerPos = filePos; + AssetsFileHeader tempHeader = this->header; + //tempHeader.endianness = 0; + filePos = tempHeader.Write(filePos, pWriter); + unsigned int fileListLen = 0; + + + this->pReader->Read(this->AssetTablePos, 4, &fileListLen); + struct exAssetFileInfo + { + AssetFileInfo finf; + uint32_t actualFileType; + bool isUntouched; + bool isPreload; + AssetsEntryReplacer *pReplacer; + }; + + TypeTree newTypeTree = this->typeTree; + std::vector types0D; + std::vector assetFiles; + std::vector assetWriteOrder; + std::list> pendingReplacers; + std::vector preloadDependencies; + if (this->header.format >= 0x0D) + { + types0D.assign(&this->typeTree.pTypes_Unity5[0], &this->typeTree.pTypes_Unity5[this->typeTree.fieldCount]); + preloadDependencies.assign(&this->preloadTable.items[0], &this->preloadTable.items[this->preloadTable.len]); + } + const AssetsFileDependency *pDependencies = this->dependencies.pDependencies; + size_t dependencyCount = this->dependencies.dependencyCount; + + for (size_t i = 0; i < replacerCount; ++i) + { + if (replacers[i] != nullptr + && (replacers[i]->GetFileID() == fileID + || replacers[i]->GetFileID() == 0 + || fileID == (uint32_t)-1)) + pendingReplacers.push_back({ i, replacers[i] }); + } + for (auto pendingReplacersIt = pendingReplacers.begin(); pendingReplacersIt != pendingReplacers.end();) + { + AssetsReplacer *pendingReplacer = pendingReplacersIt->second; + if (this->header.format >= 0x0D + && (pendingReplacer->GetType() == AssetsReplacement_Remove + || pendingReplacer->GetType() == AssetsReplacement_AddOrModify)) + { + const AssetPPtr *replacerPreloads = nullptr; + size_t replacerPreloadCount = 0; + reinterpret_cast(pendingReplacer) + ->GetPreloadDependencies(replacerPreloads, replacerPreloadCount); + + if (pendingReplacer->GetType() == AssetsReplacement_Remove) + { + for (size_t j = 0; j < replacerPreloadCount; j++) + { + const AssetPPtr &curReplacerPreload = replacerPreloads[j]; + for (size_t k = 0; k < preloadDependencies.size(); k++) + { + if (preloadDependencies[k].fileID == curReplacerPreload.fileID && + preloadDependencies[k].pathID == curReplacerPreload.pathID) + { + preloadDependencies.erase(preloadDependencies.begin() + k); + break; + } + } + } + } + else //AddOrModify + { + for (size_t j = 0; j < replacerPreloadCount; j++) + { + const AssetPPtr &curReplacerPreload = replacerPreloads[j]; + bool alreadyHasPreload = false; + for (size_t k = 0; k < preloadDependencies.size(); k++) + { + if (preloadDependencies[k].fileID == curReplacerPreload.fileID && + preloadDependencies[k].pathID == curReplacerPreload.pathID) + { + alreadyHasPreload = true; + break; + } + } + if (!alreadyHasPreload) + preloadDependencies.push_back(curReplacerPreload); + } + } + } + else if (pendingReplacer->GetType() == AssetsReplacement_Dependencies) + { + auto* pDepModifier = reinterpret_cast(pendingReplacer); + const std::vector &dependencies = pDepModifier->GetDependencies(); + pDependencies = dependencies.data(); + dependencyCount = dependencies.size(); + } + ++pendingReplacersIt; + } + + size_t untouchedAssetCount = 0; + assetFiles.reserve(fileListLen); + //For each existing asset in AssetsFile: + // - Process the replacers for the current path ID in the order of pReplacers. + // -> The replacer will be removed from pendingReplacers. + // -> If a remover is encountered, leave all further replacers for that path ID intact. + // (Further replacers may add a new asset with that ID, but are not processed in this stage). + // - If the asset is removed by one of the replacers, skip it. + // - Otherwise (if the asset is kept unchanged or is modified without having been removed), + // add an entry to assetFiles. + { + std::unordered_map> pendingReplacersLookup(pendingReplacers.size()); + for (auto pendingReplacersIt = pendingReplacers.begin(); pendingReplacersIt != pendingReplacers.end(); ++pendingReplacersIt) + { + AssetsReplacer* pendingReplacer = pendingReplacersIt->second; + if ((pendingReplacer->GetType() == AssetsReplacement_Remove + || pendingReplacer->GetType() == AssetsReplacement_AddOrModify)) + { + uint64_t pathID = reinterpret_cast(pendingReplacer)->GetPathID(); + pendingReplacersLookup[pathID].push_back(pendingReplacersIt); + } + } + QWORD readerFilePos = this->AssetTablePos + 4; + for (unsigned int i = 0; i < fileListLen; i++) + { + exAssetFileInfo fileInfo; + fileInfo.isUntouched = true; + readerFilePos = fileInfo.finf.Read(this->header.format, readerFilePos, this->pReader, this->header.endianness == 1); + + if (this->header.format < 0x10) + { + fileInfo.actualFileType = fileInfo.finf.curFileTypeOrIndex; + if (((int)fileInfo.actualFileType) < 0) + fileInfo.finf.scriptIndex = (uint16_t)(-((int)fileInfo.actualFileType + 1)); + } + else + { + if (fileInfo.finf.curFileTypeOrIndex >= types0D.size()) + fileInfo.actualFileType = fileInfo.finf.inheritedUnityClass = 0; + else + { + fileInfo.actualFileType = types0D[fileInfo.finf.curFileTypeOrIndex].classId; + fileInfo.finf.inheritedUnityClass = (uint16_t)types0D[fileInfo.finf.curFileTypeOrIndex].classId; + if (this->header.format >= 0x11) + fileInfo.finf.scriptIndex = types0D[fileInfo.finf.curFileTypeOrIndex].scriptIndex; + } + } + + fileInfo.pReplacer = NULL; + bool doIgnore = false; + //Find the replacers for this asset. + auto replacersLookupIt = pendingReplacersLookup.find(fileInfo.finf.index); + if (replacersLookupIt != pendingReplacersLookup.end()) + { + auto& replacersItVec = replacersLookupIt->second; + size_t iProcessedReplacer = 0; + for (iProcessedReplacer = 0; iProcessedReplacer < replacersItVec.size() && !doIgnore; ++iProcessedReplacer) + { + AssetsReplacer* pReplacerUnchecked = replacersItVec[iProcessedReplacer]->second; + unsigned int replacerFileID = pReplacerUnchecked->GetFileID(); + if (pReplacerUnchecked->GetType() == AssetsReplacement_Remove + || pReplacerUnchecked->GetType() == AssetsReplacement_AddOrModify) + { + AssetsEntryReplacer* pCurReplacer = reinterpret_cast(pReplacerUnchecked); + assert(pCurReplacer->GetPathID() == fileInfo.finf.index); + if (pCurReplacer->GetPathID() != fileInfo.finf.index) + { + assert(false); //Lookup is not valid (even though it should be). + continue; + } + if (pCurReplacer->GetType() == AssetsReplacement_Remove) + doIgnore = true; + else if (pCurReplacer->GetType() == AssetsReplacement_AddOrModify) + { + bool typeChanged = false; + if ( (pCurReplacer->GetClassID() < 0 + && pCurReplacer->GetMonoScriptID() != fileInfo.finf.scriptIndex) + || (pCurReplacer->GetClassID() >= 0 + && pCurReplacer->GetClassID() != fileInfo.actualFileType)) + { + //If the replacer changes the asset type, + // behave as if a remover was applied first. + doIgnore = true; + //break so the replacer is not removed from pendingReplacers. + break; + } + fileInfo.finf.curFileSize = (uint32_t)pCurReplacer->GetSize(); + fileInfo.pReplacer = pCurReplacer; + } + else + break; + } + else + assert(false); + pendingReplacers.erase(replacersItVec[iProcessedReplacer]); + } + replacersItVec.erase(replacersItVec.begin(), replacersItVec.begin() + iProcessedReplacer); + } + if (!doIgnore) + { + if (fileInfo.pReplacer == NULL) + untouchedAssetCount++; + else + fileInfo.isUntouched = false; + fileInfo.isPreload = false; + for (uint32_t i = 0; i < preloadDependencies.size(); i++) + { + if (preloadDependencies[i].fileID == 0 && preloadDependencies[i].pathID == fileInfo.finf.index) + { + fileInfo.isPreload = true; + break; + } + } + if (fileInfo.isPreload) + assetWriteOrder.push_back(assetFiles.size()); + assetFiles.push_back(fileInfo); + } + } + //Add the remaining assetFiles to assetWriteOrder. + assetWriteOrder.reserve(assetFiles.size()); + for (size_t i = 0; i < assetFiles.size(); i++) + { + if (!assetFiles[i].isPreload) + assetWriteOrder.push_back(i); + } + if (assetWriteOrder.size() != assetFiles.size()) + return NULL; //TODO : Add an error message + } + + //Process all entry replacers that are not based on assets in the original AssetsFile. + //TODO: Add a faster assetFiles entry lookup and maintain it inside the loop (as entries are added or removed). + for (auto pendingReplacersIt = pendingReplacers.begin(); pendingReplacersIt != pendingReplacers.end(); ++pendingReplacersIt) + { + AssetsReplacer *pReplacer = pendingReplacersIt->second; + if (pReplacer->GetType() != AssetsReplacement_Remove + && pReplacer->GetType() != AssetsReplacement_AddOrModify) + continue; + AssetsEntryReplacer *pCurReplacer = reinterpret_cast(pReplacer); + if ((pCurReplacer->GetFileID() == fileID || pCurReplacer->GetFileID() == 0 || fileID == (uint32_t)-1) + && pCurReplacer->GetPathID() != 0) + { + __int64 curPathID = (__int64)pCurReplacer->GetPathID(); + bool alreadyExists = false; + exAssetFileInfo fileInfo; + exAssetFileInfo *pFileInfo = &fileInfo; size_t existingFileIndex = 0; + for (size_t k = 0; k < assetFiles.size(); k++) + { + if (assetFiles[k].finf.index == curPathID) + { + alreadyExists = true; + pFileInfo = &assetFiles[k]; + existingFileIndex = k; + } + } + if (pCurReplacer->GetType() == AssetsReplacement_AddOrModify) + { + pFileInfo->pReplacer = pCurReplacer; + pFileInfo->finf.curFileSize = (uint32_t)pCurReplacer->GetSize(); + if (this->header.format < 0x10) + { + pFileInfo->finf.curFileTypeOrIndex = pCurReplacer->GetClassID(); + pFileInfo->actualFileType = pCurReplacer->GetClassID(); + } + else + { + bool hasIndex = false; + for (size_t i = 0; i < types0D.size(); i++) + { + if ((pCurReplacer->GetClassID() >= 0) + ? (pCurReplacer->GetClassID() == types0D[i].classId) + : ((types0D[i].classId == 114) && (types0D[i].scriptIndex == pCurReplacer->GetMonoScriptID()))) + { + pFileInfo->finf.curFileTypeOrIndex = (uint32_t)i; + pFileInfo->actualFileType = (uint32_t)pCurReplacer->GetClassID(); + pFileInfo->finf.inheritedUnityClass = (uint16_t)types0D[i].classId; + hasIndex = true; + break; + } + } + if (!hasIndex) + { + Type_0D newType = {}; + newType.classId = (pCurReplacer->GetClassID() >= 0) ? pCurReplacer->GetClassID() : 114; //MonoBehaviour + newType.pStringTable = NULL; + newType.pTypeFieldsEx = NULL; + newType.stringTableLen = 0; + newType.typeFieldsExCount = 0; + newType.depListLen = 0; + newType.pDepList = nullptr; + newType.header1 = newType.header2 = newType.header3 = nullptr; + newType.scriptIndex = (pCurReplacer->GetClassID() >= 0) ? 0xFFFF : pCurReplacer->GetMonoScriptID(); + newType.unknown16_1 = 0; + ClassDatabaseType *pTypeMetaType = nullptr; + if (typeMeta) + { + for (size_t i = 0; i < typeMeta->classes.size(); i++) + { + ClassDatabaseType &curType = typeMeta->classes[i]; + if (curType.classId == newType.classId) + { + pTypeMetaType = &curType; + break; + } + } + } + //Properties hash + Hash128 hash; bool hasHash = false; + if (pCurReplacer->GetPropertiesHash(hash)) hasHash = true; + else if (pTypeMetaType) + { + hash = pTypeMetaType->MakeTypeHash(typeMeta); + hasHash = true; + } + if (hasHash) + { + newType.typeHash = hash; + } + //ScriptID hash + if (pCurReplacer->GetScriptIDHash(hash)) + { + newType.scriptIDHash = hash; + } + if (typeTree.hasTypeTree) + { + std::shared_ptr pCurFile = nullptr; + ClassDatabaseType *pCurType = nullptr; + if (!pCurReplacer->GetTypeInfo(pCurFile, pCurType)) + { + pCurFile.reset(typeMeta, FreeClassDatabase_Dummy); + pCurType = pTypeMetaType; + } + if (pCurType) + { + //Generate TypeField_0Ds and the string table from a class database type + //TODO: Refactor (move to a new function, e.g. a static one). + uint32_t newTypeFieldsCount = (uint32_t)pCurType->fields.size(); + TypeField_0D *pNewTypeFields = new TypeField_0D[newTypeFieldsCount]; + char *pNewStringTable = NULL; + uint32_t newStringTableLen = 0; + for (uint32_t k = 0; k < newTypeFieldsCount; k++) + { + bool foundNameStringOffset = false; + bool foundTypeStringOffset = false; + ClassDatabaseTypeField &curField = pCurType->fields[k]; + pNewTypeFields[k].version = curField.version; + pNewTypeFields[k].depth = curField.depth; + pNewTypeFields[k].isArray = curField.isArray; + pNewTypeFields[k].size = curField.size; + pNewTypeFields[k].index = (uint32_t)k; + pNewTypeFields[k].flags = curField.flags2; + memset(&pNewTypeFields[k].unknown1, 0, sizeof(pNewTypeFields[k].unknown1)); + const char *nameString = curField.fieldName.GetString(pCurFile.get()); + const char *typeString = curField.typeName.GetString(pCurFile.get()); + size_t nameStringLen = strlen(nameString); + size_t typeStringLen = strlen(typeString); + //GlobalTypeTreeStringTable has 1042 bytes in Unity 5.5.0f3 (format 0x11) + if (typeStringLen < (sizeof(GlobalTypeTreeStringTable)-1)) + { + for (uint32_t l = 0; l < (sizeof(GlobalTypeTreeStringTable)-typeStringLen-1); l++) + { + if (!memcmp(&GlobalTypeTreeStringTable[l], typeString, typeStringLen+1)) + { + pNewTypeFields[k].typeStringOffset = l | 0x80000000; + foundTypeStringOffset = true; + break; + } + } + } + if (nameStringLen < (sizeof(GlobalTypeTreeStringTable)-1)) + { + for (uint32_t l = 0; l < (sizeof(GlobalTypeTreeStringTable)-nameStringLen-1); l++) + { + if (!memcmp(&GlobalTypeTreeStringTable[l], nameString, nameStringLen+1)) + { + pNewTypeFields[k].nameStringOffset = l | 0x80000000; + foundNameStringOffset = true; + break; + } + } + } + if (!foundTypeStringOffset && newStringTableLen && typeStringLen < (newStringTableLen-1)) + { + for (uint32_t l = 0; l < (newStringTableLen-typeStringLen); l++) + { + if (!memcmp(&pNewStringTable[l], typeString, typeStringLen+1)) + { + pNewTypeFields[k].typeStringOffset = l; + foundTypeStringOffset = true; + break; + } + } + } + if (!foundNameStringOffset && newStringTableLen && nameStringLen < (newStringTableLen-1)) + { + for (uint32_t l = 0; l < (newStringTableLen-nameStringLen); l++) + { + if (!memcmp(&pNewStringTable[l], nameString, nameStringLen+1)) + { + pNewTypeFields[k].nameStringOffset = l; + foundNameStringOffset = true; + break; + } + } + } + if (!foundTypeStringOffset) + { + char *_tmp = (char*)realloc(pNewStringTable, newStringTableLen + typeStringLen + 1); + if (!_tmp) + { + delete[] pNewTypeFields; + newTypeFieldsCount = 0; + if (pNewStringTable) + free(pNewStringTable); + newStringTableLen = 0; + break; + } + pNewStringTable = _tmp; + memcpy(&pNewStringTable[newStringTableLen], typeString, typeStringLen + 1); + pNewTypeFields[k].typeStringOffset = newStringTableLen; + newStringTableLen += typeStringLen + 1; + } + if (!foundNameStringOffset) + { + char *_tmp = (char*)realloc(pNewStringTable, newStringTableLen + nameStringLen + 1); + if (!_tmp) + { + delete[] pNewTypeFields; + newTypeFieldsCount = 0; + if (pNewStringTable) + free(pNewStringTable); + newStringTableLen = 0; + break; + } + pNewStringTable = _tmp; + memcpy(&pNewStringTable[newStringTableLen], nameString, nameStringLen + 1); + pNewTypeFields[k].nameStringOffset = newStringTableLen; + newStringTableLen += nameStringLen + 1; + } + } + newType.pTypeFieldsEx = pNewTypeFields; + newType.typeFieldsExCount = newTypeFieldsCount; + newType.pStringTable = pNewStringTable; + newType.stringTableLen = newStringTableLen; + } + } + pFileInfo->finf.curFileTypeOrIndex = (uint32_t)types0D.size(); + pFileInfo->actualFileType = (uint32_t)pCurReplacer->GetClassID(); + pFileInfo->finf.inheritedUnityClass = (uint16_t)pCurReplacer->GetMonoScriptID(); + types0D.push_back(newType); + } + } + pFileInfo->finf.index = curPathID; + pFileInfo->isUntouched = false; + pFileInfo->finf.offs_curFile = 0; + if (this->header.format < 0x10) + { + if (((int)pFileInfo->finf.curFileTypeOrIndex) < 0) + pFileInfo->finf.inheritedUnityClass = 114; //MonoBehaviour + else + pFileInfo->finf.inheritedUnityClass = (uint16_t)pFileInfo->finf.curFileTypeOrIndex; + } + pFileInfo->finf.scriptIndex = pCurReplacer->GetMonoScriptID(); + pFileInfo->finf.unknown1 = 0; + if (!alreadyExists) + { + assetWriteOrder.push_back(assetFiles.size()); + assetFiles.push_back(fileInfo); + } + } + else if (pCurReplacer->GetType() == AssetsReplacement_Remove && alreadyExists) + { + assetWriteOrder.erase(assetWriteOrder.begin()+existingFileIndex); + assetFiles.erase(assetFiles.begin()+existingFileIndex); + } + } + } + if (assetWriteOrder.size() != assetFiles.size()) + return NULL; //TODO : Add an error message + + uint64_t nextAssetOffset = (this->header.format < 9) ? 0x10 : 0; + + std::vector untouchedModifiers; + untouchedModifiers.resize(untouchedAssetCount); + uint64_t initializedModifierIndex = 0; + + //Generate the asset offsets and original copy replacers. + for (unsigned int i = 0; i < assetWriteOrder.size(); i++) + { + exAssetFileInfo &fileInfo = assetFiles[assetWriteOrder[i]]; + if (fileInfo.pReplacer == NULL) //is this file untouched? + { + assert(initializedModifierIndex < untouchedAssetCount); + if (initializedModifierIndex < untouchedAssetCount) + { + assert(fileInfo.isUntouched); + fileInfo.isUntouched = true; + untouchedModifiers[initializedModifierIndex] = + AssetModifierFromReader(fileID, fileInfo.finf.index, fileInfo.actualFileType, fileInfo.finf.scriptIndex, + this->pReader, fileInfo.finf.curFileSize, this->header.offs_firstFile + fileInfo.finf.offs_curFile, 16384); + fileInfo.pReplacer = &untouchedModifiers[initializedModifierIndex]; + initializedModifierIndex++; + } + } + fileInfo.finf.offs_curFile = nextAssetOffset; + nextAssetOffset += fileInfo.finf.curFileSize; + nextAssetOffset = (nextAssetOffset+7)&(~7); //use 8-byte alignment here because Unity wants me to + } + + QWORD metadataSize = 0; QWORD firstFilePos = headerPos; + if (this->header.format < 9) + { + //firstFilePos = 0x10; //for Step 2 + goto WriteAssetsFile_Step2; + } + +WriteAssetsFile_Step1: + { + if (this->header.format >= 0x0D) + { + newTypeTree.pTypes_Unity5 = types0D.data(); + newTypeTree.fieldCount = types0D.size(); + } + QWORD typeTreePos = filePos; + filePos = newTypeTree.Write(filePos, pWriter, this->header.format, this->header.endianness ? true : false); + QWORD fileTablePos = filePos; + + uint32_t dwTmp = (uint32_t)assetFiles.size(); + if (this->header.endianness) + SwapEndians_(dwTmp); + pWriter->Write(filePos, 4, &dwTmp); filePos += 4; + for (unsigned int i = 0; i < assetFiles.size(); i++) + { + exAssetFileInfo &fileInfo = assetFiles[i]; + //Write the asset info + filePos = fileInfo.finf.Write(this->header.format, filePos, pWriter, this->header.endianness ? true : false); + } + QWORD preloadTablePos = filePos; + if (this->header.format >= 0x0B) + { + //Modified preloadTable. + PreloadList tempPreloadTable; + tempPreloadTable.items = preloadDependencies.data(); + tempPreloadTable.len = preloadDependencies.size(); + filePos = tempPreloadTable.Write(filePos, pWriter, this->header.format, this->header.endianness ? true : false); + } + QWORD dependencyTablePos = filePos; + AssetsFileDependencyList _dependencies; + _dependencies.pDependencies = const_cast(pDependencies); + _dependencies.dependencyCount = (uint32_t)dependencyCount; + filePos = _dependencies.Write(filePos, pWriter, this->header.format, this->header.endianness ? true : false); + filePos = SecondaryTypeList_Write(this, filePos, pWriter, this->header.format, this->header.endianness ? true : false); + filePos = UnknownString_Write(this, filePos, pWriter, this->header.format); + metadataSize = filePos; + if (this->header.format < 9) + { + //firstFilePos = 0; //for writing the header + metadataSize -= typeTreePos; //TODO: Make sure this still works + //(previously increased metadataSize by 1 because of the endianness byte, but it was more likely because of the missing field after the dependencies list) + } + else + { + metadataSize -= (headerPos + this->header.GetSizeBytes()); + if (filePos < this->header.offs_firstFile) + firstFilePos = this->header.offs_firstFile; + else + firstFilePos = (filePos+15) & (~15);//(filePos+0xFFF) & (~0xFFF); + } + //QWORD firstFilePos;//= (filePos+15) & (~15); + } + if (this->header.format < 9) + goto WriteAssetsFile_Step3; +WriteAssetsFile_Step2: + { + filePos = firstFilePos; + QWORD qwNull = 0; + for (unsigned int i = 0; i < assetWriteOrder.size(); i++) + { + exAssetFileInfo *fileInfo = &assetFiles[assetWriteOrder[i]]; + QWORD targetPos = firstFilePos + fileInfo->finf.offs_curFile; + while ((targetPos-filePos) > 8) + { + if (filePos >= (headerPos + 0x10)) //for header.format < 9 + pWriter->Write(filePos, 8, &qwNull); + filePos += 8; + } + if ((targetPos-filePos) > 0) + { + pWriter->Write(filePos, (targetPos-filePos), &qwNull); + filePos = targetPos; + } + filePos = fileInfo->pReplacer->Write(targetPos, pWriter); + } + } + if (this->header.format < 9) + { + pWriter->Write(filePos, 1, &this->header.endianness); filePos++; + goto WriteAssetsFile_Step1; + } +WriteAssetsFile_Step3: + for (size_t i = this->typeTree.fieldCount; i < types0D.size(); i++) + { + if (types0D[i].pStringTable) + free(types0D[i].pStringTable); + if (types0D[i].pTypeFieldsEx) + delete[] types0D[i].pTypeFieldsEx; + if (types0D[i].pDepList) + delete[] types0D[i].pDepList; + if (types0D[i].header1) + delete[] types0D[i].header1; + if (types0D[i].header2) + delete[] types0D[i].header2; + if (types0D[i].header3) + delete[] types0D[i].header3; + } + + AssetsFileHeader newHeader = this->header; + newHeader.metadataSize = metadataSize; + newHeader.fileSize = (filePos - headerPos); + newHeader.offs_firstFile = (firstFilePos - headerPos); + //newHeader.endianness = 0; + newHeader.Write(headerPos, pWriter); + if (isOffsetWriter) + Free_AssetsWriter(pWriter); + return newHeader.fileSize + headerPos + filePosOffset; +} +ASSETSTOOLS_API AssetsFile::AssetsFile(IAssetsReader *pReader) +{ + pReader->Seek(AssetsSeek_Begin, 0); + this->pReader = pReader; + QWORD filePos; + filePos = this->header.Read(0, pReader); + //simple validity check + if (!this->header.format || this->header.format > 0x40) + goto AssetsFile_Break_InvalidFile; + //if (this->header.endianness) //-> big endian not supported + // goto AssetsFile_Break_InvalidFile; + if (this->header.format < 9) + filePos = this->header.fileSize - this->header.metadataSize + 1; + filePos = this->typeTree.Read(filePos, pReader, this->header.format, this->header.endianness == 1); + if (this->typeTree.unityVersion[0] < '0' || this->typeTree.unityVersion[0] > '9') + { + this->typeTree.Clear(); + goto AssetsFile_Break_InvalidFile; + } + this->AssetTablePos = (uint32_t)filePos; + + { + AssetFileList tmpFileList; tmpFileList.sizeFiles = 0; + pReader->Read(filePos, 4, &tmpFileList.sizeFiles); + if (this->header.endianness) + SwapEndians_(tmpFileList.sizeFiles); + this->AssetCount = tmpFileList.sizeFiles; + filePos += 4; + + if (this->header.format >= 0x0E && (this->AssetCount > 0)) + filePos = ((filePos + 3) & (~0x3)); + filePos += tmpFileList.GetSizeBytes(this->header.format); + } + if (this->header.format >= 0x0B) + { + filePos = this->preloadTable.Read(filePos, pReader, this->header.format, this->header.endianness == 1); + } + else + { + this->preloadTable.len = 0; + this->preloadTable.items = NULL; + } + filePos = this->dependencies.Read(filePos, pReader, this->header.format, this->header.endianness == 1); + filePos = SecondaryTypeList_Read(this, filePos, pReader, this->header.format, this->header.endianness == 1); + filePos = UnknownString_Read(this, filePos, pReader, this->header.format); + return; + AssetsFile_Break_InvalidFile: + { + this->preloadTable.len = 0; + this->preloadTable.items = NULL; + this->AssetTablePos = 0; + this->AssetCount = 0; + this->typeTree.fieldCount = 0; + this->dependencies.dependencyCount = 0; + this->dependencies.pDependencies = NULL; + this->secondaryTypeCount = 0; + this->pSecondaryTypeList = NULL; + this->unknownString[0] = 0; + return; + } +} +ASSETSTOOLS_API AssetsFile::~AssetsFile() +{ + this->typeTree.Clear(); + if (this->preloadTable.len > 0) + { + free(this->preloadTable.items); + this->preloadTable.items = NULL; + this->preloadTable.len = 0; + } + if (this->dependencies.dependencyCount > 0) + { + free(this->dependencies.pDependencies); + this->dependencies.pDependencies = NULL; + this->dependencies.dependencyCount = 0; + } + if (this->secondaryTypeCount > 0) + { + for (uint32_t i = 0; i < this->secondaryTypeCount; i++) + { + if (this->pSecondaryTypeList[i].stringTableLen > 0) + free(this->pSecondaryTypeList[i].pStringTable); + if (this->pSecondaryTypeList[i].typeFieldsExCount > 0) + delete[] this->pSecondaryTypeList[i].pTypeFieldsEx; + if (this->pSecondaryTypeList[i].pDepList) + delete[] this->pSecondaryTypeList[i].pDepList; + if (this->pSecondaryTypeList[i].header1) + delete[] this->pSecondaryTypeList[i].header1; + if (this->pSecondaryTypeList[i].header2) + delete[] this->pSecondaryTypeList[i].header2; + if (this->pSecondaryTypeList[i].header3) + delete[] this->pSecondaryTypeList[i].header3; + } + delete[] this->pSecondaryTypeList; + this->pSecondaryTypeList = NULL; + this->secondaryTypeCount = 0; + } +} + +uint32_t SwapEndians(uint32_t old) +{ + uint32_t ret = (((old & 0xFF000000) >> 24) + ((old & 0x00FF0000) >> 8) + ((old & 0x0000FF00) << 8) + ((old & 0x000000FF) << 24)); + return ret; +} +void SwapEndians_(uint32_t& old) +{ + old = (((old & 0xFF000000) >> 24) + ((old & 0x00FF0000) >> 8) + ((old & 0x0000FF00) << 8) + ((old & 0x000000FF) << 24)); +} + +bool HasName(uint32_t type) +{ + switch (type) + { + case 21: + case 27: + case 28: + case 43: + case 48: + case 49: + case 62: + case 72: + case 74: + case 83: + case 84: + case 86: + case 89: + case 90: + case 91: + case 93: + case 109: + case 115: + case 117: + case 121: + case 128: + case 134: + case 142: + case 150: + case 152: + case 156: + case 158: + case 171: + case 184: + case 185: + case 186: + case 187: + case 188: + case 194: + case 200: + case 207: + case 213: + case 221: + case 226: + case 228: + case 237: + case 238: + case 240: + case 258: + case 271: + case 272: + case 273: + case 290: + case 319: + case 329: + case 363: + case 850595691: + case 1480428607: + case 687078895: + case 825902497: + case 2083778819: + case 1953259897: + case 2058629509: + return true; + default: + return false; + } +} + +void _BlankVerifyLogger(const char *message){} +ASSETSTOOLS_API bool AssetsFile::VerifyAssetsFile(AssetsFileVerifyLogger logger) +{ + char sprntTmp[100]; bool ret; + QWORD fileListOffs = 0; int fileListSize = 0; + AssetFileList *pFileList = NULL; //AssetsFileHeader *pFileHeader = NULL; + const void *errorData = 0; void *errorData2 = 0; + uint8_t tmpLastFile; AssetFileInfo *pLastFileInfo; + if (logger == NULL) logger = &_BlankVerifyLogger; + if (pReader == NULL) { + logger("ERROR: The AssetsFileReader is NULL!"); + return false; + } + size_t allocCount; + + /*pFileHeader = (AssetsFileHeader*)malloc(sizeof(AssetsFileHeader) + 11); + if (!pFileHeader) + { + errorData = (void*)(sizeof(AssetsFileHeader) + 11); + goto _mallocError; + } + ZeroMemory(pFileHeader, sizeof(AssetsFileHeader) + 11); + if (!reader(0, sizeof(AssetsFileHeader) + 10, pFileHeader, readerPar)) + { + errorData = (void*)0; errorData2 = (void*)(sizeof(AssetsFileHeader) + 10); + goto _readerError; + }*/ + if (!this->header.format || this->header.format > 0x40) + { + errorData = "Invalid file format"; + goto _fileFormatError; + } + if (this->typeTree.unityVersion[0] == 0 || this->typeTree.unityVersion[0] < '0' || this->typeTree.unityVersion[0] > '9') + { + char sprntTmp2[100]; + sprintf_s(sprntTmp2, "Invalid version string at %llX", (uint64_t)((uintptr_t)(&this->typeTree.unityVersion[0]) - (uintptr_t)(&this->header))); + errorData = sprntTmp2; + goto _fileFormatError; + } + sprintf_s(sprntTmp, "INFO: The .assets file was built for Unity %s.", this->typeTree.unityVersion); + logger(sprntTmp); + + if (this->header.format > 0x16 || this->header.format < 0x08) + logger("WARNING: AssetsTools (for .assets versions 8-22) wasn't tested with this .assets' version, likely parsing or writing the file won't work properly!"); + + fileListOffs = this->AssetTablePos; + fileListSize = 0; + if (!pReader->Read(fileListOffs, 4, &fileListSize)) + { + errorData = (void*)fileListOffs; errorData2 = (void*)4; + goto _readerError; + } + if (this->header.endianness) + SwapEndians_(fileListSize); + fileListOffs += 4; + if (this->header.format >= 0x0E) + fileListOffs = ((fileListOffs + 3) & (~0x3)); //align to 4-byte boundary + + + allocCount = (uintptr_t)&pFileList->fileInfs[fileListSize];//(fileListSize * sizeof(AssetFileInfo)) + 4 + 4; + pFileList = (AssetFileList*)malloc(allocCount); + if (!pFileList) + { + errorData = (void*)(allocCount); + goto _mallocError; + } + pFileList->sizeFiles = fileListSize; + pFileList->Read(this->header.format, fileListOffs, pReader, this->header.endianness); + /*if (!reader(fileListOffs, fileListSize, pFileList->fileInfs, readerPar)) + { + errorData = (void*)fileListOffs; errorData2 = (void*)(4 + fileListSize); + goto _readerError; + }*/ + + sprintf_s(sprntTmp, "INFO: The .assets file has %u assets (info list : %u bytes).", pFileList->sizeFiles, pFileList->GetSizeBytes(this->header.format)); + logger(sprntTmp); + + if (pFileList->sizeFiles > 0) + { + if (this->header.metadataSize < 8) + { + errorData = "Invalid metadata size"; + goto _fileFormatError; + } + pLastFileInfo = &pFileList->fileInfs[pFileList->sizeFiles-1]; + if ((this->header.offs_firstFile + pLastFileInfo->offs_curFile + pLastFileInfo->curFileSize - 1) < this->header.metadataSize) + { + errorData = "Last asset begins before the header ends"; + goto _fileFormatError; + } + //sprintf_s(sprntTmp, "%u;%u;%u.", pLastFileInfo->index, pLastFileInfo->offs_curFile, pLastFileInfo->curFileSize); + if (!pReader->Read(this->header.offs_firstFile + pLastFileInfo->offs_curFile + pLastFileInfo->curFileSize - 1, 1, &tmpLastFile)) + { + errorData = "File data are cut off"; + goto _fileFormatError; + } + } + + logger("SUCCESS: The .assets file seems to be ok!"); + ret = true; + goto _cleanup; + + _cleanup: + //if (pFileHeader != NULL) free(pFileHeader); + if (pFileList != NULL) free(pFileList); + return ret; + + _readerError: + ret = false; + sprintf_s(sprntTmp, "ERROR: Invalid .assets file (reading %u bytes at %p in the .assets file failed)!", (unsigned int)errorData2, errorData); + logger(sprntTmp); + goto _cleanup; + + _mallocError: + ret = false; + sprintf_s(sprntTmp, "ERROR: Out of Memory : Allocating %u bytes failed!", (unsigned int)errorData); + logger(sprntTmp); + goto _cleanup; + + _fileFormatError: + ret = false; + sprintf_s(sprntTmp, "ERROR: Invalid .assets file (error message : '%s')!", (const char*)errorData); + logger(sprntTmp); + goto _cleanup; +} diff --git a/AssetsTools/AssetsFileFormat.h b/AssetsTools/AssetsFileFormat.h new file mode 100644 index 0000000..0e4decf --- /dev/null +++ b/AssetsTools/AssetsFileFormat.h @@ -0,0 +1,48 @@ +#pragma once +#include +#include +#include "defines.h" +#include "ClassDatabaseFile.h" +#include "AssetsReplacer.h" +#include "AssetsFileFormatTypes.h" + +class AssetsFile +{ + public: + AssetsFileHeader header; + TypeTree typeTree; + + PreloadList preloadTable; + AssetsFileDependencyList dependencies; + + uint32_t secondaryTypeCount; //format >= 0x14 + Type_0D *pSecondaryTypeList; //format >= 0x14 + char unknownString[64]; //format >= 5; seemingly always empty + + uint32_t AssetTablePos; + uint32_t AssetCount; + + IAssetsReader *pReader; + + ASSETSTOOLS_API AssetsFile(IAssetsReader *pReader); + AssetsFile(const AssetsFile& other) = delete; + AssetsFile(AssetsFile&& other) = delete; + ASSETSTOOLS_API ~AssetsFile(); + AssetsFile &operator=(const AssetsFile& other) = delete; + AssetsFile &operator=(AssetsFile&& other) = delete; + + //set fileID to -1 if all replacers are for this .assets file but don't have the fileID set to the same one + //typeMeta is used to add the type information (hash and type fields) for format >= 0x10 if necessary + ASSETSTOOLS_API QWORD Write(IAssetsWriter *pWriter, QWORD filePos, class AssetsReplacer **pReplacers, size_t replacerCount, uint32_t fileID, + class ClassDatabaseFile *typeMeta = NULL); + + ASSETSTOOLS_API bool VerifyAssetsFile(AssetsFileVerifyLogger logger = NULL); +}; + +//Returns whether a Unity type is known to start with a m_Name string field. +// May not be accurate depending on the engine version, +// since this does not take type information into account. +//Types with an m_Name field, but not at the beginning (i.e. where HasName also returns false): +// GameObject (1) +// MonoBehaviour (114) +ASSETSTOOLS_API bool HasName(uint32_t type); diff --git a/AssetsTools/AssetsFileFormatTypes.h b/AssetsTools/AssetsFileFormatTypes.h new file mode 100644 index 0000000..9f22be5 --- /dev/null +++ b/AssetsTools/AssetsFileFormatTypes.h @@ -0,0 +1,226 @@ +#pragma once +#include +#include +#include "defines.h" +#include "AssetsFileReader.h" + +template inline T SwapEndians(T old) +{ + T ret; size_t sizeof_T = sizeof(T); + for (size_t i = 0; i < sizeof_T; i++) + ((uint8_t*)&ret)[sizeof_T - i - 1] = ((uint8_t*)&old)[i]; + return ret; +} +template inline void SwapEndians_(T& _old) +{ + T old = _old; + T ret; size_t sizeof_T = sizeof(T); + for (size_t i = 0; i < sizeof_T; i++) + ((uint8_t*)&ret)[sizeof_T - i - 1] = ((uint8_t*)&old)[i]; + _old = ret; +} +ASSETSTOOLS_API uint32_t SwapEndians(uint32_t old); +ASSETSTOOLS_API void SwapEndians_(uint32_t& old); + +#define AssetFileInfo_MaxSize 25 +class AssetFileInfo //little-endian or big-endian (=> header.endianness) +{ +public: + uint64_t index; //0x00 //version < 0x0E : only uint32_t + uint64_t offs_curFile; //0x08 //version < 0x16 : only uint32_t + uint32_t curFileSize; //0x0C + uint32_t curFileTypeOrIndex; //0x10 //starting with version 0x10, this is an index into the type tree + //inheritedUnityClass : for Unity classes, this is curFileType; for MonoBehaviours, this is 114 + //version < 0x0B : inheritedUnityClass is uint32_t, no scriptIndex exists + uint16_t inheritedUnityClass; //0x14 //(MonoScript) //only version < 0x10 + //scriptIndex : for Unity classes, this is 0xFFFF; + //for MonoBehaviours, this is an index of the mono class, counted separately for each .assets file + uint16_t scriptIndex; //0x16 //only version <= 0x10 + uint8_t unknown1; //0x18 //only 0x0F <= version <= 0x10 //with alignment always a uint32_t + ASSETSTOOLS_API static uint32_t GetSize(uint32_t version); + ASSETSTOOLS_API QWORD Read(uint32_t version, QWORD pos, IAssetsReader* pReader, bool bigEndian); + ASSETSTOOLS_API QWORD Write(uint32_t version, QWORD pos, IAssetsWriter* pWriter, bool bigEndian); +}; + +struct AssetFileList //little-endian or big-endian (=> header.endianness) +{ + unsigned int sizeFiles; //0x00 + AssetFileInfo fileInfs[1]; //0x04 + + ASSETSTOOLS_API unsigned int GetSizeBytes(uint32_t version); + ASSETSTOOLS_API QWORD Read(uint32_t version, QWORD pos, IAssetsReader* pReader, bool bigEndian); + ASSETSTOOLS_API QWORD Write(uint32_t version, QWORD pos, IAssetsWriter* pWriter, bool bigEndian); +}; +struct AssetsFileHeader //Always big-endian +{ + uint64_t unknown00; //0x00 //format >= 0x16 only. Always 0? + uint32_t format; //0x08 + uint64_t metadataSize; //0x10 //format < 0x16: uint32_t @ 0x00; + uint64_t fileSize; //0x18 //format < 0x16: uint32_t @ 0x04; + uint64_t offs_firstFile; //0x20 //format < 0x16: uint32_t @ 0x0C; + //0 == little-endian; 1 == big-endian + uint8_t endianness; //0x20, for format < 0x16 @ 0x10, for format < 9 at (fileSize - metadataSize) right before TypeTree + uint8_t unknown[3]; //0x21, for format < 0x16 @ 0x11, exists for format >= 9 + + ASSETSTOOLS_API unsigned int GetSizeBytes(); + ASSETSTOOLS_API QWORD Read(QWORD absFilePos, IAssetsReader* pReader); + //does NOT write the endianness byte for format < 9! + ASSETSTOOLS_API QWORD Write(QWORD pos, IAssetsWriter* pWriter); +}; +struct AssetsFileDependency +{ + //version < 6 : no bufferedPath + //version < 5 : no bufferedPath, guid, type + char bufferedPath[256]; //for buffered (type=1) + struct GUID128 + { + __int64 mostSignificant; //64-127 //big-endian + __int64 leastSignificant; //0-63 //big-endian + ASSETSTOOLS_API QWORD Read(QWORD absFilePos, IAssetsReader* pReader); + ASSETSTOOLS_API QWORD Write(QWORD absFilePos, IAssetsWriter* pWriter); + } guid; + int type; + char assetPath[256]; //path to the .assets file + inline AssetsFileDependency() + : type(0), guid{} + { + bufferedPath[0] = 0; + assetPath[0] = 0; + } + ASSETSTOOLS_API QWORD Read(QWORD absFilePos, IAssetsReader* pReader, uint32_t format, bool bigEndian); + ASSETSTOOLS_API QWORD Write(QWORD absFilePos, IAssetsWriter* pWriter, uint32_t format, bool bigEndian); +}; +struct AssetsFileDependencyList +{ + uint32_t dependencyCount; + //uint8_t unknown; //seemingly always 0 + struct AssetsFileDependency* pDependencies; + ASSETSTOOLS_API QWORD Read(QWORD absFilePos, IAssetsReader* pReader, uint32_t format, bool bigEndian); + ASSETSTOOLS_API QWORD Write(QWORD absFilePos, IAssetsWriter* pWriter, uint32_t format, bool bigEndian); +}; + +struct TypeField_0D +{ + uint16_t version; //0x00 + uint8_t depth; //0x02 //specifies the amount of parents + //0x01 : IsArray + //0x02 : IsRef + //0x04 : IsRegistry + //0x08 : IsArrayOfRefs + uint8_t isArray; //0x03 //actually a bool for format <= 0x12, uint8_t since format 0x13 + uint32_t typeStringOffset; //0x04 + uint32_t nameStringOffset; //0x08 + uint32_t size; //0x0C //size in bytes; if not static (if it contains an array), set to -1 + uint32_t index; //0x10 + //0x0001 : is invisible(?), set for m_FileID and m_PathID; ignored if no parent field exists or the type is neither ColorRGBA, PPtr nor string + //0x0100 : ? is bool + //0x1000 : ? + //0x4000 : align bytes + //0x8000 : any child has the align bytes flag + //=> if flags & 0xC000 and size != 0xFFFFFFFF, the size field matches the total length of this field plus its children. + //0x400000 : ? + //0x800000 : ? is non-primitive type + //0x02000000 : ? is UInt16 (called char) + //0x08000000 : has fixed buffer size? related to Array (i.e. this field or its only child or its father is an array), should be set for vector, Array and the size and data fields. + uint32_t flags; //0x14 + uint8_t unknown1[8]; //0x18 //since format 0x12 + + ASSETSTOOLS_API QWORD Read(QWORD absFilePos, IAssetsReader* pReader, uint32_t format, bool bigEndian); + ASSETSTOOLS_API QWORD Write(QWORD curFilePos, IAssetsWriter* pWriter, uint32_t format, bool bigEndian); + ASSETSTOOLS_API const char* GetTypeString(const char* stringTable, size_t stringTableLen); + ASSETSTOOLS_API const char* GetNameString(const char* stringTable, size_t stringTableLen); +};//0x18 + +struct Type_0D //everything big endian +{ + //Starting with U5.5, all MonoBehaviour types have MonoBehaviour's classId (114) + //Before, the different MonoBehaviours had different negative classIds, starting with -1 + int classId; //0x00 + + uint8_t unknown16_1; //format >= 0x10 + uint16_t scriptIndex; //format >= 0x11 U5.5+, index to the MonoManager (usually 0xFFFF) + + //Script ID (md4 hash) + Hash128 scriptIDHash; //if classId < 0 //0x04..0x13 + + //Type hash / properties hash (md4) + Hash128 typeHash; //0x04..0x13 or 0x14..0x23 + + uint32_t typeFieldsExCount; //if (TypeTree.enabled) //0x14 or 0x24 + TypeField_0D* pTypeFieldsEx; + + uint32_t stringTableLen; //if (TypeTree.enabled) //0x18 or 0x28 + char* pStringTable; + + //For types from assetsFile.pSecondaryTypeList : + uint32_t depListLen; //format >= 0x15 + unsigned int* pDepList; //format >= 0x15 + + //For types in assetsFile.typeTree : + char* header1; //format >= 0x15 + char* header2; //format >= 0x15 + char* header3; //format >= 0x15 + + ASSETSTOOLS_API QWORD Read(bool hasTypeTree, QWORD absFilePos, IAssetsReader* pReader, uint32_t version, bool bigEndian, bool secondaryTypeTree = false); + ASSETSTOOLS_API QWORD Write(bool hasTypeTree, QWORD absFilePos, IAssetsWriter* pWriter, uint32_t version, bool bigEndian, bool secondaryTypeTree = false); +}; + +struct TypeField_07 //everything big endian +{ + char type[256] = {0}; //null-terminated + char name[256] = {0}; //null-terminated + uint32_t size = 0; + uint32_t index = 0; + uint32_t arrayFlag = 0; + uint32_t flags1 = 0; + uint32_t flags2 = 0; //Flag 0x4000 : align to 4 bytes after this field. + uint32_t childrenCount = 0; + TypeField_07* children = nullptr; + + ASSETSTOOLS_API QWORD Read(bool hasTypeTree, QWORD absFilePos, IAssetsReader* pReader, uint32_t version, bool bigEndian); + ASSETSTOOLS_API QWORD Write(bool hasTypeTree, QWORD absFilePos, IAssetsWriter* pWriter, bool bigEndian); +}; +struct Type_07 +{ + int classId; //big endian + TypeField_07 base; + + ASSETSTOOLS_API QWORD Read(bool hasTypeTree, QWORD absFilePos, IAssetsReader* pReader, uint32_t version, bool bigEndian); + ASSETSTOOLS_API QWORD Write(bool hasTypeTree, QWORD absFilePos, IAssetsWriter* pWriter, bool bigEndian); +}; +struct TypeTree +{ + //The actual 4-byte-alignment base starts here. Using the header as the base still works since its length is 20. + char unityVersion[64] = {0}; //null-terminated; stored for .assets format > 6 + uint32_t platform = 0; //big endian; stored for .assets format > 6 + bool hasTypeTree = false; //stored for .assets format >= 13; Unity 5 only stores some metadata if it's set to false + uint32_t fieldCount = 0; //big endian; + + union + { + Type_0D* pTypes_Unity5; + Type_07* pTypes_Unity4; + }; + + uint32_t dwUnknown; //actually belongs to the asset list; stored for .assets format < 14 + uint32_t _fmt; //not stored here in the .assets file, the variable is just to remember the .assets file version + + ASSETSTOOLS_API QWORD Read(QWORD absFilePos, IAssetsReader* pReader, uint32_t version, bool bigEndian); //Minimum AssetsFile format : 6 + ASSETSTOOLS_API QWORD Write(QWORD absFilePos, IAssetsWriter* pWriter, uint32_t version, bool bigEndian); + + ASSETSTOOLS_API void Clear(); +}; + +struct AssetPPtr +{ + uint32_t fileID; + QWORD pathID; +}; +struct PreloadList +{ + uint32_t len; + AssetPPtr* items; + + ASSETSTOOLS_API QWORD Read(QWORD absFilePos, IAssetsReader* pReader, uint32_t format, bool bigEndian); + ASSETSTOOLS_API QWORD Write(QWORD absFilePos, IAssetsWriter* pWriter, uint32_t format, bool bigEndian); +}; diff --git a/AssetsTools/AssetsFileReader.cpp b/AssetsTools/AssetsFileReader.cpp new file mode 100644 index 0000000..aedc764 --- /dev/null +++ b/AssetsTools/AssetsFileReader.cpp @@ -0,0 +1,2188 @@ +#include "stdafx.h" +#include "AssetsFileReader.h" +#include "../libStringConverter/convert.h" +#include +#include +#include + +void AssetsVerifyLoggerToConsole(const char *message) +{ + printf("%s\n", message); +} + +std::vector reopenableInstances; +std::mutex reopenableInstancesMutex; +void AddReopenable(IAssetsReopenable *pReopenable) +{ + std::scoped_lock reopenableInstancesLock(reopenableInstancesMutex); + reopenableInstances.push_back(pReopenable); +} +void RemoveReopenable(IAssetsReopenable *pReopenable) +{ + std::scoped_lock reopenableInstancesLock(reopenableInstancesMutex); + for (size_t i = 1; i <= reopenableInstances.size(); i++) + { + if (reopenableInstances[i-1] == pReopenable) + { + reopenableInstances.erase(reopenableInstances.begin() + (i-1)); + i--; + } + } +} +bool GarbageCollectReopenables(int nMax = 16) +{ + bool collected = false; + std::scoped_lock reopenableInstancesLock(reopenableInstancesMutex); + for (size_t i = 0; i < reopenableInstances.size(); i++) + { + if (!nMax) + break; + if (reopenableInstances[i]->IsOpen() && reopenableInstances[i]->Close()) + { + collected = true; + nMax--; + } + } + return collected; +} + +void Free_AssetsReopenable(IAssetsReopenable *pObject) +{ + delete pObject; +} + +IAssetsReopenable::~IAssetsReopenable() {} + +class AssetsReaderFromFile : public IAssetsReader +{ + AssetsRWOpenFlags flags; +protected: + bool binary; + TCHAR *filePath; + + bool wasOpened; + QWORD lastFilePos; + + FILE *pFile; + std::recursive_mutex fileOperationMutex; +public: + AssetsReaderFromFile(const TCHAR *filePath, bool binary, AssetsRWOpenFlags flags) + { + this->flags = flags; + this->binary = binary; + + size_t pathLen = wcslen(filePath) + 1; + this->filePath = new TCHAR[pathLen]; + memcpy(this->filePath, filePath, pathLen * sizeof(TCHAR)); + + this->wasOpened = false; + this->pFile = NULL; + this->lastFilePos = 0; + } + AssetsReaderFromFile(FILE *pFile) + { + this->pFile = pFile; + + this->flags = RWOpenFlags_Immediately | RWOpenFlags_Unclosable; + this->binary = true; + + this->filePath = NULL; + + this->wasOpened = true; + this->lastFilePos = 0; + } + ~AssetsReaderFromFile() + { + RemoveReopenable(this); + if (filePath) + { + free(filePath); + filePath = NULL; + flags = RWOpenFlags_None; //RWOpenFlags_Unclosable could otherwise prevent closing the file. + Close(); + } + } + bool Reopen() + { + bool ret = true; + std::scoped_lock fileOperationLock(fileOperationMutex); + if (IsOpen()) + ret = true; + else if (filePath == NULL) + ret = false; + else + { + FILE *pTempFile = NULL; + TCHAR mode[3] = {_T('r'), binary ? _T('b') : 0, 0}; + errno_t err = _tfopen_s(&pTempFile, filePath, mode); + if (err != 0) + { + if (err == ENFILE || err == EMFILE) + { + GarbageCollectReopenables(); + err = _tfopen_s(&pTempFile, filePath, mode); + if (err != 0) + ret = false; + } + else + ret = false; + } + if (ret) + { + pFile = pTempFile; + if (wasOpened && !SetPosition(lastFilePos)) + { + fclose(pTempFile); + ret = false; + } + else + wasOpened = true; + } + } + return ret; + } + bool IsOpen() + { + return pFile != NULL; + } + bool Close() + { + bool ret; + std::unique_lock fileOperationLock(fileOperationMutex, std::defer_lock); + if (filePath != NULL) + fileOperationLock.lock(); + if (IsOpen()) + { + if (flags & RWOpenFlags_Unclosable) + ret = false; + else if (!Tell(lastFilePos)) + ret = false; + else + { + fclose(pFile); + pFile = NULL; + ret = true; + } + } + else + ret = true; + return ret; + } + + AssetsRWTypes GetType() { return AssetsRWType_Reader; } + AssetsRWClasses GetClass() { return AssetsRWClass_ReaderFromFile; } + bool IsView() { return false; } + + bool Tell(QWORD &pos) + { + std::scoped_lock fileOperationLock(fileOperationMutex); + bool ret; + if (IsOpen()) + { + long long posv = _ftelli64(pFile); + if (posv < 0) + { + pos = 0; + ret = false; + } + else + { + pos = (QWORD)posv; + ret = true; + } + } + else + { + pos = lastFilePos; + ret = true; + } + return ret; + } + bool Seek(AssetsSeekTypes origin, long long offset) + { + std::scoped_lock fileOperationLock(fileOperationMutex); + bool ret; + if (!IsOpen() && !Reopen()) + ret = false; + else + ret = _fseeki64(pFile, offset, (int)origin) == 0; + return ret; + } + bool SetPosition(QWORD pos) + { + std::scoped_lock fileOperationLock(fileOperationMutex); + bool ret; + if (((long long)pos) < 0) //fpos_t is signed + ret = false; + else if (!IsOpen() && !Reopen()) + ret = false; + else + ret = _fseeki64(pFile, (long long)pos, SEEK_SET) == 0; + return ret; + } + + QWORD Read(QWORD pos, QWORD size, void *outBuffer, bool nullUnread) + { + std::unique_lock fileOperationLock(fileOperationMutex); + QWORD ret; + if (!IsOpen() && !Reopen()) + ret = 0; + else if ((pos != (QWORD)-1) && !SetPosition(pos)) + ret = 0; + else + ret = fread(outBuffer, 1, size, pFile); + fileOperationLock.unlock(); + if (nullUnread && (ret < size)) + memset(&((uint8_t*)outBuffer)[ret], 0, size - ret); + return ret; + } + + IAssetsReader *CreateView(); + friend class AssetsReaderFromFile_View; +}; +class AssetsReaderFromFile_View : public IAssetsReader +{ + AssetsReaderFromFile *pBaseReader; + QWORD filePos; +public: + AssetsReaderFromFile_View(AssetsReaderFromFile *pBaseReader) + : pBaseReader(pBaseReader), filePos(0) + {} + + ~AssetsReaderFromFile_View(){} + + bool Reopen() { return pBaseReader->Reopen(); } + bool IsOpen() { return pBaseReader->IsOpen();} + bool Close() { return false; } + + AssetsRWTypes GetType() { return AssetsRWType_Reader; } + AssetsRWClasses GetClass() { return AssetsRWClass_ReaderFromFile; } + bool IsView() { return true; } + + bool Tell(QWORD &pos) { pos = filePos; return true; } + bool Seek(AssetsSeekTypes origin, long long offset) + { + bool ret = false; + QWORD newPos = filePos; + switch (origin) + { + case AssetsSeek_Begin: + if (offset < 0) return false; + newPos = offset; + //Seek the base reader to determine whether the operation is valid. + ret = pBaseReader->Seek(AssetsSeek_Begin, offset); + break; + case AssetsSeek_Cur: + if (offset < 0) + { + offset = -offset; + if (offset < 0) return false; //INT64_MIN + if ((unsigned long long)offset > newPos) return false; + newPos -= (unsigned long long)offset; + } + else + newPos += offset; + //Seek the base reader to determine whether the operation is valid. + ret = pBaseReader->Seek(AssetsSeek_Begin, newPos); + break; + case AssetsSeek_End: + if (offset > 0) return false; + std::scoped_lock fileOperationLock(pBaseReader->fileOperationMutex); + ret = pBaseReader->Seek(AssetsSeek_End, offset) + && pBaseReader->Tell(newPos); + break; + } + if (ret) + filePos = newPos; + return ret; + } + bool SetPosition(QWORD pos) + { + //Seek the base reader to determine whether the operation is valid. + if (pBaseReader->SetPosition(pos)) + { + filePos = pos; + return true; + } + return false; + } + + QWORD Read(QWORD pos, QWORD size, void *outBuffer, bool nullUnread) + { + if (pos == (QWORD)-1) + pos = filePos; + else + filePos = pos; + QWORD result = pBaseReader->Read(pos, size, outBuffer, nullUnread); + filePos += result; + return result; + } + + IAssetsReader *CreateView() { return pBaseReader->CreateView(); } +}; +IAssetsReader *AssetsReaderFromFile::CreateView() +{ + return new AssetsReaderFromFile_View(this); +} + +IAssetsReader *Create_AssetsReaderFromFile(const wchar_t *filePath, bool binary, AssetsRWOpenFlags openFlags) +{ + if (filePath == NULL) return NULL; + + AssetsReaderFromFile *pReader = new AssetsReaderFromFile(filePath, binary, openFlags); + + if (openFlags & RWOpenFlags_Immediately) + { + if (!pReader->Reopen()) + { + delete pReader; + return NULL; + } + } + + AddReopenable(pReader); + return pReader; +} +IAssetsReader *Create_AssetsReaderFromFile(const char *filePath, bool binary, AssetsRWOpenFlags openFlags) +{ + if (filePath == NULL) return NULL; + + size_t tcLen = 0; + TCHAR *tcPath = _MultiByteToWide(filePath, tcLen); + + IAssetsReader *pReader = Create_AssetsReaderFromFile(tcPath, binary, openFlags); + + _FreeWCHAR(tcPath); + + return pReader; +} +IAssetsReader *Create_AssetsReaderFromFile(FILE *pFile) +{ + if (pFile == NULL) return NULL; + + IAssetsReader *pReader = new AssetsReaderFromFile(pFile); + + return pReader; +} + + +class AssetsReaderFromSplitFile : public AssetsReaderFromFile +{ + AssetsRWOpenFlags flags; +protected: + struct FileSizeEntry + { + QWORD absolutePos; + QWORD size; + FILE *pFile; + }; + std::vector splitSizes; + QWORD totalFileSize; + + //Used for fast seek while the file is opened. + size_t curSplitIndex; QWORD curSplitPos; + //Used to find the absolute file position when reopening. + QWORD absoluteSplitFilePos; + + bool allowUpdate; + + bool findSplitFile(QWORD absolutePos, size_t &splitFileIndex, QWORD &splitFileOffset) + { + if (curSplitIndex < splitSizes.size() + && splitSizes[curSplitIndex].absolutePos <= absolutePos + && splitSizes[curSplitIndex].absolutePos + splitSizes[curSplitIndex].size > absolutePos) + { + splitFileIndex = curSplitIndex; + splitFileOffset = absolutePos - splitSizes[curSplitIndex].absolutePos; + return true; + } + splitFileIndex = splitSizes.size() / 2; + //Binary search. + size_t left = 0, right = splitSizes.size(); + while (true) + { + QWORD startPos = splitSizes[splitFileIndex].absolutePos; + QWORD endPos = startPos + splitSizes[splitFileIndex].size; + if (endPos < startPos) //Overflow + { + return false; + } + if (startPos > absolutePos) + right = splitFileIndex; + else if (endPos <= absolutePos) + left = splitFileIndex + 1; + else + break; + if (left == right) //Did not find the correct split + { + return false; + } + splitFileIndex = left + (right - left) / 2; + } + splitFileOffset = absolutePos - splitSizes[splitFileIndex].absolutePos; + return true; + } +public: + AssetsReaderFromSplitFile(const TCHAR *filePath, bool binary, bool allowUpdate, AssetsRWOpenFlags flags) + : AssetsReaderFromFile(filePath, binary, flags & (~RWOpenFlags_Unclosable)) + { + this->allowUpdate = allowUpdate; + this->flags = flags; + this->totalFileSize = 0; + this->curSplitIndex = 0; + this->curSplitPos = 0; + this->absoluteSplitFilePos = 0; + } + ~AssetsReaderFromSplitFile() + { + Close(); + } + bool Reopen() + { + bool ret = true; + bool wasOpened = this->wasOpened; + size_t filePathLen = 0; + + std::scoped_lock fileOperationLock(fileOperationMutex); + + if (IsOpen()) + ret = true; + else if (filePath == NULL || (filePathLen = _tcslen(filePath)) == 0) + ret = false; + else if (filePathLen < 5 || _tcscmp(&filePath[filePathLen - 4], _T("0000"))) + ret = false; + else + { + filePath[filePathLen - 3] = 0; //the actual first name is .split0, the four additional nulls are just reserved space. + if (!AssetsReaderFromFile::Reopen()) + ret = false; + else if (!wasOpened || allowUpdate) + { + splitSizes.clear(); totalFileSize = 0; + QWORD oldBasePos = 0; + AssetsReaderFromFile::Tell(oldBasePos); + if (!AssetsReaderFromFile::Seek(AssetsSeek_End, 0) || !AssetsReaderFromFile::Tell(totalFileSize)) + { + ret = false; + AssetsReaderFromFile::Close(); + } + else + { + AssetsReaderFromFile::SetPosition(oldBasePos); + FileSizeEntry entry = {0, totalFileSize, this->pFile}; + splitSizes.push_back(entry); + } + } + if (ret && splitSizes.size() > 0) + { + int curOpenSplitIndex = 0; + while ((++curOpenSplitIndex) < 10000) + { + if ((wasOpened && !allowUpdate) && curOpenSplitIndex >= splitSizes.size()) + break; + FILE *pTempFile = NULL; + + _stprintf_p(&filePath[filePathLen - 4], 5, _T("%d"), curOpenSplitIndex); + filePath[filePathLen] = 0; + + TCHAR mode[3] = {_T('r'), binary ? _T('b') : 0, 0}; + + errno_t err = _tfopen_s(&pTempFile, filePath, mode); + if (err != 0) + { + pTempFile = NULL; + if (err == ENFILE || err == EMFILE) + { + GarbageCollectReopenables(128); + err = _tfopen_s(&pTempFile, filePath, mode); + if (err != 0) + pTempFile = NULL; + } + } + if (pTempFile != NULL) + { + if (fseek(pTempFile, 0, SEEK_END) == 0) + { + long long tempSize = _ftelli64(pTempFile); + if (tempSize > 0) + { + fseek(pTempFile, 0, SEEK_SET); + if (!wasOpened || allowUpdate) + { + size_t lastIndex = splitSizes.size() - 1; + FileSizeEntry entry = {splitSizes[lastIndex].absolutePos + splitSizes[lastIndex].size, (QWORD)tempSize, pTempFile}; + totalFileSize = entry.absolutePos + entry.size; + splitSizes.push_back(entry); + } + else if ((QWORD)tempSize != splitSizes[curOpenSplitIndex].size) + { + fclose(pTempFile); + pTempFile = NULL; + } + } + else + { + fclose(pTempFile); + pTempFile = NULL; + } + } + else + { + fclose(pTempFile); + pTempFile = NULL; + } + } + if (pTempFile == NULL) + { + if (wasOpened && !allowUpdate) + { + //Split file not fully openable anymore; failed reopening + AssetsReaderFromFile::Close(); + splitSizes[0].pFile = NULL; + for (size_t i = 1; i < splitSizes.size(); i++) + { + if (splitSizes[i].pFile != NULL) + { + fclose(splitSizes[i].pFile); + splitSizes[i].pFile = NULL; + } + } + ret = false; + } + break; + } + else + splitSizes[curOpenSplitIndex].pFile = pTempFile; + } + } + _tcscpy(&filePath[filePathLen - 4], _T("0000")); + if (wasOpened) + { + if (splitSizes.size() > 0) + { + //Go to the previous position. + if (!findSplitFile(absoluteSplitFilePos, curSplitIndex, curSplitPos) + || (_fseeki64(splitSizes[curSplitIndex].pFile, (long long)curSplitPos, SEEK_SET) != 0)) + { + Close(); + ret = false; + } + } + else + { + Close(); + ret = false; + } + } + } + return ret; + } + bool IsOpen() + { + return AssetsReaderFromFile::IsOpen(); + } + bool Close() + { + bool ret; + std::unique_lock fileOperationLock(fileOperationMutex, std::defer_lock); + if (filePath != NULL) + fileOperationLock.lock(); + if (IsOpen()) + { + if (flags & RWOpenFlags_Unclosable) + ret = false; + else if (!Tell(absoluteSplitFilePos)) + ret = false; + else if (splitSizes.size() == 0) + ret = false; + else if (!AssetsReaderFromFile::Close()) + ret = false; + else + { + this->lastFilePos = 0; + splitSizes[0].pFile = NULL; + for (size_t i = 1; i < splitSizes.size(); i++) + { + if (splitSizes[i].pFile != NULL) + { + fclose(splitSizes[i].pFile); + splitSizes[i].pFile = NULL; + } + } + ret = true; + } + } + else + ret = true; + return ret; + } + + AssetsRWTypes GetType() { return AssetsRWType_Reader; } + AssetsRWClasses GetClass() { return AssetsRWClass_ReaderFromSplitFile; } + bool IsView() { return false; } + + bool Tell(QWORD &pos) + { + std::scoped_lock fileOperationLock(fileOperationMutex); + bool ret; + if (IsOpen()) + { + pos = 0; + if (splitSizes.size() <= curSplitIndex) + { + ret = false; + } + else + { + pos = splitSizes[curSplitIndex].absolutePos + curSplitPos; + ret = true; + } + } + else + { + pos = absoluteSplitFilePos; + ret = true; + } + return ret; + } + bool Seek(AssetsSeekTypes origin, long long offset) + { + std::scoped_lock fileOperationLock(fileOperationMutex); + bool ret; + if (offset < 0) + ret = false; + else if (!IsOpen() && !Reopen()) + ret = false; + else + { + switch (origin) + { + case AssetsSeek_Begin: + { + if (offset < 0) + ret = false; + else + ret = SetPosition((QWORD)offset); + break; + } + case AssetsSeek_End: + { + if (offset > 0) + ret = false; + else + { + long long tempAbsOffset = -offset; + if (tempAbsOffset < 0) //if offset is the minimum signed long long + ret = false; + else + { + if (splitSizes.size() > 0) + { + QWORD endPos = splitSizes[splitSizes.size() - 1].absolutePos + splitSizes[splitSizes.size() - 1].size; + if ((QWORD)tempAbsOffset > endPos) + ret = false; + else + { + ret = SetPosition(endPos - (QWORD)tempAbsOffset); + } + } + else if (tempAbsOffset > 0) + ret = false; + } + } + break; + } + case AssetsSeek_Cur: + { + if (offset == 0) + { + ret = true; + } + else if (curSplitIndex >= splitSizes.size()) + { + ret = false; + } + else if (offset > 0) + { + QWORD curAbsolutePos = splitSizes[curSplitIndex].absolutePos + curSplitPos; + QWORD targetPos = curAbsolutePos + offset; + if (targetPos < curAbsolutePos) //Overflow + ret = false; + else + { + ret = SetPosition(targetPos); + } + } + else //if (offset < 0) + { + long long tempRemainingOffset = -offset; + if (tempRemainingOffset < 0) //if offset is the minimum signed long long + ret = false; + else + { + QWORD curAbsolutePos = splitSizes[curSplitIndex].absolutePos + curSplitPos; + if ((QWORD)tempRemainingOffset > curAbsolutePos) + ret = false; + else + { + ret = SetPosition(curAbsolutePos - (QWORD)tempRemainingOffset); + } + } + } + break; + } + } + } + return ret; + } + bool SetPosition(QWORD pos) + { + std::scoped_lock fileOperationLock(fileOperationMutex); + bool ret; + if (((long long)pos) < 0) //_fseeki64 uses a signed offset + ret = false; + else if (!IsOpen() && !Reopen()) + ret = false; + else + { + if (findSplitFile(pos, curSplitIndex, curSplitPos) + && (_fseeki64(splitSizes[curSplitIndex].pFile, (long long)curSplitPos, SEEK_SET) == 0)) + ret = true; + else + ret = false; + } + return ret; + } + + QWORD Read(QWORD pos, QWORD size, void *outBuffer, bool nullUnread) + { + std::unique_lock fileOperationLock(fileOperationMutex); + QWORD ret; + if (!IsOpen() && !Reopen()) + ret = 0; + else if ((pos != (QWORD)-1) && !SetPosition(pos)) + ret = 0; + else if ((curSplitIndex >= splitSizes.size()) || (curSplitPos > splitSizes[curSplitIndex].size)) + ret = 0; + else + { + QWORD remainingSize = size; + while (remainingSize > 0) + { + QWORD curReadSize = splitSizes[curSplitIndex].size - curSplitPos; + if (curReadSize > remainingSize) curReadSize = remainingSize; + if (curReadSize > 0) + { + if (splitSizes[curSplitIndex].pFile == NULL) + break; + QWORD read = fread(&((uint8_t*)outBuffer)[size - remainingSize], 1, curReadSize, splitSizes[curSplitIndex].pFile); + curSplitPos += read; + remainingSize -= read; + if (read < curReadSize) //curSplitPos is also allowed to be equal to the current split size. + break; + } + if (curSplitPos >= splitSizes[curSplitIndex].size) + { + if (splitSizes.size() > (curSplitIndex + 1)) + { + curSplitIndex++; + curSplitPos = 0; + if (splitSizes[curSplitIndex].pFile) fseek(splitSizes[curSplitIndex].pFile, 0, SEEK_SET); + } + else + break; + } + } + ret = size - remainingSize; + } + fileOperationLock.unlock(); + if (nullUnread && (ret < size)) + memset(&((uint8_t*)outBuffer)[ret], 0, size - ret); + return ret; + } + IAssetsReader *CreateView(); + friend class AssetsReaderFromSplitFile_View; +}; +class AssetsReaderFromSplitFile_View : public IAssetsReader +{ + AssetsReaderFromSplitFile *pBaseReader; + QWORD filePos; + size_t splitFileIndex; + QWORD splitFilePos; +public: + AssetsReaderFromSplitFile_View(AssetsReaderFromSplitFile *pBaseReader) + : pBaseReader(pBaseReader), filePos(0), splitFileIndex(0), splitFilePos(0) + {} + + ~AssetsReaderFromSplitFile_View(){} + + bool Reopen() { return pBaseReader->SetPosition(filePos); } + bool IsOpen() { return pBaseReader->IsOpen(); } + bool Close() { return false; } + + AssetsRWTypes GetType() { return AssetsRWType_Reader; } + AssetsRWClasses GetClass() { return AssetsRWClass_ReaderFromSplitFile; } + bool IsView() { return true; } + + bool Tell(QWORD &pos) { pos = filePos; return true; } + bool Seek(AssetsSeekTypes origin, long long offset) + { + bool ret = false; + QWORD newPos = filePos; + switch (origin) + { + case AssetsSeek_Begin: + if (offset < 0) return false; + newPos = offset; + break; + case AssetsSeek_Cur: + if (offset < 0) + { + offset = -offset; + if (offset < 0) return false; //INT64_MIN + if ((unsigned long long)offset > newPos) return false; + newPos -= (unsigned long long)offset; + } + else + newPos += (unsigned long long)offset; + break; + case AssetsSeek_End: + if (offset > 0) return false; + offset = -offset; + if (offset < 0) return false; //INT64_MIN + if ((unsigned long long)offset > pBaseReader->totalFileSize) return false; + newPos = pBaseReader->totalFileSize - (unsigned long long)offset; + break; + } + std::scoped_lock fileOperationLock(pBaseReader->fileOperationMutex); + ret = pBaseReader->Seek(AssetsSeek_Begin, newPos); + size_t newSplitIndex = pBaseReader->curSplitIndex; + QWORD newSplitPos = pBaseReader->curSplitPos; + if (ret) + { + filePos = newPos; + splitFileIndex = newSplitIndex; + splitFilePos = newSplitPos; + } + return ret; + } + bool SetPosition(QWORD pos) + { + std::scoped_lock fileOperationLock(pBaseReader->fileOperationMutex); + bool ret = pBaseReader->SetPosition(pos); + if (ret) + { + filePos = pos; + splitFileIndex = pBaseReader->curSplitIndex; + splitFilePos = pBaseReader->curSplitPos; + } + return ret; + } + + QWORD Read(QWORD pos, QWORD size, void *outBuffer, bool nullUnread) + { + std::scoped_lock fileOperationLock(pBaseReader->fileOperationMutex); + if (pos == (QWORD)-1) + { + pos = filePos; + //Give the base reader a hint where the data is located. + pBaseReader->curSplitIndex = splitFileIndex; + pBaseReader->curSplitPos = splitFilePos; + } + else + filePos = pos; + QWORD result = pBaseReader->Read(pos, size, outBuffer, nullUnread); + splitFileIndex = pBaseReader->curSplitIndex; + splitFilePos = pBaseReader->curSplitPos; + filePos += result; + return result; + } + + IAssetsReader *CreateView() { return pBaseReader->CreateView(); } +}; +IAssetsReader *AssetsReaderFromSplitFile::CreateView() +{ + return new AssetsReaderFromSplitFile_View(this); +} + +IAssetsReader *Create_AssetsReaderFromSplitFile(const wchar_t *filePath, bool binary, bool allowUpdate, AssetsRWOpenFlags openFlags) +{ + if (filePath == NULL) return NULL; + size_t filePathLen = wcslen(filePath); + if ((filePathLen < 7) || wcscmp(&filePath[filePathLen - 7], L".split0")) + return NULL; + + std::wstring tempFilePath = std::wstring(filePath) + L"000"; + + AssetsReaderFromSplitFile *pReader = new AssetsReaderFromSplitFile(tempFilePath.c_str(), binary, allowUpdate, openFlags); + + if (openFlags & RWOpenFlags_Immediately) + { + if (!pReader->Reopen()) + { + delete pReader; + return NULL; + } + } + + AddReopenable(pReader); + return pReader; +} +IAssetsReader *Create_AssetsReaderFromSplitFile(const char *filePath, bool binary, bool allowUpdate, AssetsRWOpenFlags openFlags) +{ + if (filePath == NULL) return NULL; + + size_t tcLen = 0; + TCHAR *tcPath = _MultiByteToWide(filePath, tcLen); + + IAssetsReader *pReader = Create_AssetsReaderFromSplitFile(tcPath, binary, allowUpdate, openFlags); + + _FreeWCHAR(tcPath); + + return pReader; +} + +class AssetsReaderFromMemory : public IAssetsReader +{ +protected: + const void *buf; + size_t bufLen; + cbFreeMemoryResource freeBufCallback; + + void *ownBuf; //null if copyBuf was passed as false + + size_t readerPos; +public: + AssetsReaderFromMemory(const void *buf, size_t bufLen, bool copyBuf, cbFreeMemoryResource freeBufCallback) + : freeBufCallback(freeBufCallback) + { + this->readerPos = 0; + if (copyBuf) + { + this->buf = this->ownBuf = malloc(bufLen); + if (this->ownBuf == nullptr) + this->bufLen = 0; + else + { + memcpy(this->ownBuf, buf, bufLen); + this->bufLen = bufLen; + } + } + else + { + ownBuf = nullptr; + this->buf = buf; + this->bufLen = bufLen; + } + } + ~AssetsReaderFromMemory() + { + if (ownBuf != nullptr) + { + free(ownBuf); + ownBuf = nullptr; + } + else if (buf != nullptr) + { + if (freeBufCallback) + freeBufCallback(const_cast(buf)); + } + buf = nullptr; bufLen = 0; readerPos = 0; + } + bool Reopen() + { + return true; + } + bool IsOpen() + { + return true; + } + bool Close() + { + return false; + } + + AssetsRWTypes GetType() { return AssetsRWType_Reader; } + AssetsRWClasses GetClass() { return AssetsRWClass_ReaderFromMemory; } + bool IsView() { return false; } + + bool Tell(QWORD &pos) + { + pos = readerPos; + return true; + } + bool Seek(AssetsSeekTypes origin, long long offset) + { + bool ret; + switch (origin) + { + case AssetsSeek_Begin: + { + if (offset < 0) + ret = false; + else if ((unsigned long long)offset > this->bufLen) + ret = false; + else + { + this->readerPos = (size_t)offset; + ret = true; + } + break; + } + case AssetsSeek_Cur: + { + if (offset < 0) + { + offset = -offset; + if (offset < 0) //=> offset is the minimum signed long long + ret = false; + else if ((unsigned long long)offset > this->readerPos) + ret = false; + else + { + this->readerPos -= (unsigned long long)offset; + ret = true; + } + } + else if ((unsigned long long)offset > (this->bufLen - this->readerPos)) + ret = false; + else + { + this->readerPos += (unsigned long long)offset; + ret = true; + } + break; + } + case AssetsSeek_End: + { + if (offset > 0) + ret = false; + else + { + offset = -offset; + if (offset < 0) //=> offset is the minimum signed long long + ret = false; + else if ((unsigned long long)offset > this->bufLen) + ret = false; + else + { + this->readerPos = this->bufLen - (unsigned long long)offset; + ret = true; + } + } + break; + } + default: ret = false; + } + return ret; + } + bool SetPosition(QWORD pos) + { + bool ret; + if (pos > this->bufLen) + ret = false; + else + { + this->readerPos = pos; + ret = true; + } + return ret; + } + + QWORD Read(QWORD pos, QWORD size, void *outBuffer, bool nullUnread) + { + QWORD ret; + if ((pos != (QWORD)-1) && !SetPosition(pos)) + ret = 0; + else + { + QWORD actualSize = size; + if (actualSize > (this->bufLen - this->readerPos)) + { + actualSize = this->bufLen - this->readerPos; + } + memcpy(outBuffer, &((uint8_t*)this->buf)[this->readerPos], (size_t)actualSize); + ret = actualSize; + this->readerPos += ret; + } + if (nullUnread && (ret < size)) + memset(&((uint8_t*)outBuffer)[ret], 0, size - ret); + return ret; + } + + bool HasBuffer() + { + return buf != NULL || bufLen == 0; + } + + IAssetsReader *CreateView(); + friend class AssetsReaderFromMemory_View; +}; +class AssetsReaderFromMemory_View : public IAssetsReader +{ + AssetsReaderFromMemory *pBaseReader; + size_t pos; +public: + AssetsReaderFromMemory_View(AssetsReaderFromMemory *pBaseReader) + : pBaseReader(pBaseReader), pos(0) + {} + + ~AssetsReaderFromMemory_View(){} + + bool Reopen() { return true; } + bool IsOpen() { return true; } + bool Close() { return false; } + + AssetsRWTypes GetType() { return AssetsRWType_Reader; } + AssetsRWClasses GetClass() { return AssetsRWClass_ReaderFromMemory; } + bool IsView() { return true; } + + bool Tell(QWORD &pos) { pos = this->pos; return true; } + bool Seek(AssetsSeekTypes origin, long long offset) + { + bool ret = false; + QWORD newPos = pos; + switch (origin) + { + case AssetsSeek_Begin: + if (offset < 0) return false; + newPos = offset; + break; + case AssetsSeek_Cur: + if (offset < 0) + { + offset = -offset; + if (offset < 0) return false; //INT64_MIN + if ((unsigned long long)offset > newPos) return false; + newPos -= (unsigned long long)offset; + } + else + newPos += offset; + break; + case AssetsSeek_End: + if (offset > 0) return false; + offset = -offset; + if (offset < 0) return false; //INT64_MIN + if ((unsigned long long)offset > pBaseReader->bufLen) return false; + newPos = pBaseReader->bufLen - (unsigned long long)offset; + break; + } + if (newPos <= (QWORD)pBaseReader->bufLen) + { + pos = newPos; + return true; + } + return false; + } + bool SetPosition(QWORD pos) + { + if (pos <= (QWORD)pBaseReader->bufLen) + { + this->pos = pos; + return true; + } + return false; + } + + QWORD Read(QWORD pos, QWORD size, void *outBuffer, bool nullUnread) + { + QWORD result = pBaseReader->Read(pos, size, outBuffer, nullUnread); + pos += result; + return result; + } + + IAssetsReader *CreateView() { return pBaseReader->CreateView(); } +}; +IAssetsReader *AssetsReaderFromMemory::CreateView() +{ + return new AssetsReaderFromMemory_View(this); +} + +IAssetsReader *Create_AssetsReaderFromMemory(const void *buf, size_t bufLen, bool copyBuf, cbFreeMemoryResource freeBufCallback) +{ + if (buf == NULL && bufLen > 0) return NULL; + + AssetsReaderFromMemory *pReader = new AssetsReaderFromMemory(buf, bufLen, copyBuf, freeBufCallback); + if (copyBuf && !pReader->HasBuffer()) + { + delete pReader; + pReader = NULL; + } + //AssetsReaderFromMemory isn't garbage collectable + //else + // AddReopenable(pReader); + return pReader; +} + +class AssetsReaderFromReaderRange : public IAssetsReaderFromReaderRange +{ +protected: + IAssetsReader *pChild; + QWORD rangeStart; QWORD rangeSize; + QWORD readerPos; + bool doSeek; bool alwaysSeek; +public: + AssetsReaderFromReaderRange(IAssetsReader *pChild, QWORD rangeStart, QWORD rangeSize, bool alwaysSeekChild) + { + this->pChild = pChild; + this->rangeStart = rangeStart; + this->rangeSize = rangeSize; + this->alwaysSeek = alwaysSeekChild; + this->readerPos = 0; + this->doSeek = false; + } + ~AssetsReaderFromReaderRange() + { + this->pChild = NULL; + this->rangeStart = 0; + this->rangeSize = 0; + this->readerPos = 0; + } + bool Reopen() + { + bool ret = false; + if (pChild != NULL) + ret = pChild->Reopen(); + return ret; + } + bool IsOpen() + { + bool ret = false; + if (pChild != NULL) + ret = pChild->IsOpen(); + return ret; + } + bool Close() + { + bool ret = false; + if (pChild != NULL) + { + ret = pChild->Close(); + if (ret) + this->doSeek = true; + } + return ret; + } + + AssetsRWTypes GetType() { return AssetsRWType_Reader; } + AssetsRWClasses GetClass() { return AssetsRWClass_ReaderFromReaderRange; } + bool IsView() { return false; } + + bool Tell(QWORD &pos) + { + pos = readerPos; + return true; + } + bool Seek(AssetsSeekTypes origin, long long offset, QWORD &newPos) + { + bool ret; + switch (origin) + { + case AssetsSeek_Begin: + { + if (offset < 0) + ret = false; + else if ((unsigned long long)offset > rangeSize) + ret = false; + else + { + this->readerPos = newPos = (size_t)offset; + this->doSeek = true; + ret = true; + } + break; + } + case AssetsSeek_Cur: + { + if (offset < 0) + { + offset = -offset; + if (offset < 0) //=> offset is the minimum signed long long + ret = false; + else if ((unsigned long long)offset > this->readerPos) + ret = false; + else + { + this->readerPos -= (unsigned long long)offset; + this->doSeek = true; + ret = true; + } + } + else if ((unsigned long long)offset > (this->rangeSize - this->readerPos)) + ret = false; + else + { + this->readerPos += (unsigned long long)offset; + this->doSeek = true; + ret = true; + } + break; + } + case AssetsSeek_End: + { + if (offset > 0) + ret = false; + else + { + offset = -offset; + if (offset < 0) //=> offset is the minimum signed long long + ret = false; + else if ((unsigned long long)offset > this->rangeSize) + ret = false; + else + { + this->readerPos = this->rangeSize - (unsigned long long)offset; + this->doSeek = true; + ret = true; + } + } + break; + } + default: ret = false; + } + return ret; + } + bool Seek(AssetsSeekTypes origin, long long offset) + { + QWORD newPos; + return Seek(origin, offset, newPos); + } + bool SetPosition(QWORD pos) + { + bool ret; + if (pos > this->rangeSize) + ret = false; + else + { + this->readerPos = pos; + this->doSeek = true; + ret = true; + } + return ret; + } + + QWORD Read(QWORD pos, QWORD size, void *outBuffer, bool nullUnread) + { + QWORD ret; + if (pChild == NULL) + ret = 0; + else if ((pos != (QWORD)-1) && !SetPosition(pos)) + ret = 0; + //else if ((doSeek || alwaysSeek) && !pChild->SetPosition(this->rangeStart + this->readerPos)) + // ret = 0; + else + { + if (pos == (QWORD)-1) + pos = this->readerPos; + QWORD actualSize = size; + if (actualSize > (this->rangeSize - pos)) + { + actualSize = this->rangeSize - pos; + } + QWORD readPos = (doSeek || alwaysSeek) ? (this->rangeStart + pos) : ((QWORD)-1); + ret = pChild->Read(readPos, actualSize, outBuffer, false); + this->readerPos = pos + ret; + doSeek = false; + } + if (nullUnread && (ret < size)) + memset(&((uint8_t*)outBuffer)[ret], 0, size - ret); + return ret; + } + + bool GetChild(IAssetsReader *&pReader) + { + pReader = pChild; + return (pChild != NULL); + } + IAssetsReader *CreateView() + { + //This reader type qualifies as a view already. + alwaysSeek = true; + return Create_AssetsReaderFromReaderRange(pChild, rangeStart, rangeSize, true); + } +}; + +IAssetsReaderFromReaderRange *Create_AssetsReaderFromReaderRange(IAssetsReader *pChild, QWORD rangeStart, QWORD rangeSize, bool alwaysSeek) +{ + if (pChild == NULL) return NULL; + + AssetsReaderFromReaderRange *pReader = new AssetsReaderFromReaderRange(pChild, rangeStart, rangeSize, alwaysSeek); + + //AssetsReaderFromReaderRange isn't garbage collectable + //AddReopenable(pReader); + return pReader; +} + +class AssetsWriterToFile : public IAssetsWriter +{ + AssetsRWOpenFlags flags; +protected: + bool discard; + bool binary; + TCHAR *filePath; + + bool wasOpened; + QWORD lastFilePos; + + FILE *pFile; + std::recursive_mutex fileOperationMutex; +public: + AssetsWriterToFile(const TCHAR *filePath, bool discard, bool binary, AssetsRWOpenFlags flags) + { + this->flags = flags; + + this->discard = discard; + this->binary = binary; + + size_t pathLen = wcslen(filePath) + 1; + this->filePath = new TCHAR[pathLen]; + memcpy(this->filePath, filePath, pathLen * sizeof(TCHAR)); + + this->wasOpened = false; + this->pFile = NULL; + this->lastFilePos = 0; + } + AssetsWriterToFile(FILE *pFile) + { + this->pFile = pFile; + + this->flags = RWOpenFlags_Unclosable; + + this->discard = this->binary = true; + this->filePath = NULL; + this->wasOpened = true; + this->lastFilePos = 0; + } + ~AssetsWriterToFile() + { + RemoveReopenable(this); + if (filePath) + { + free(filePath); + filePath = NULL; + flags = RWOpenFlags_None; //RWOpenFlags_Unclosable could otherwise prevent closing the file. + Close(); + } + } + bool Reopen() + { + bool ret = true; + std::scoped_lock fileOperationLock(fileOperationMutex); + if (IsOpen()) + ret = true; + else if (filePath == NULL) + ret = false; + else + { + FILE *pTempFile = NULL; + TCHAR mode[4] = {0,0,0,0}; + if (discard) + { + mode[0] = _T('w'); + mode[1] = binary ? _T('b') : 0; + //Further reopens must not discard the contents again. + //Without this, unpredictable behaviour would occur since a Close() call can be issued by garbage collection at any time. + discard = false; + } + else + { + mode[0] = _T('r'); + mode[1] = _T('+'); + mode[2] = binary ? _T('b') : 0; + } + + errno_t err = _tfopen_s(&pTempFile, filePath, mode); + if (err != 0) + { + if (err == ENFILE || err == EMFILE) + { + GarbageCollectReopenables(); + err = _tfopen_s(&pTempFile, filePath, mode); + if (err != 0) + ret = false; + } + else + ret = false; + } + if (ret) + { + pFile = pTempFile; + if (wasOpened && !SetPosition(lastFilePos)) + { + fclose(pTempFile); + ret = false; + } + else + wasOpened = true; + } + } + return ret; + } + bool IsOpen() + { + return pFile != NULL; + } + bool Close() + { + bool ret; + std::unique_lock fileOperationLock(fileOperationMutex, std::defer_lock); + if (filePath != NULL) + fileOperationLock.lock(); + if (IsOpen()) + { + if (flags & RWOpenFlags_Unclosable) + ret = false; + else if (!Tell(lastFilePos)) + ret = false; + else + { + fclose(pFile); + pFile = NULL; + ret = true; + } + } + else + ret = true; + return ret; + } + + AssetsRWTypes GetType() { return AssetsRWType_Writer; } + AssetsRWClasses GetClass() { return AssetsRWClass_WriterToFile; } + bool IsView() { return false; } + + bool Tell(QWORD &pos) + { + std::scoped_lock fileOperationLock(fileOperationMutex); + bool ret; + if (IsOpen()) + { + long long posv = _ftelli64(pFile); + if (posv < 0) + { + pos = 0; + ret = false; + } + else + { + pos = (QWORD)posv; + ret = true; + } + } + else + { + pos = lastFilePos; + ret = true; + } + return ret; + } + bool Seek(AssetsSeekTypes origin, long long offset) + { + std::scoped_lock fileOperationLock(fileOperationMutex); + bool ret; + if (!IsOpen() && !Reopen()) + ret = false; + else + ret = _fseeki64(pFile, offset, (int)origin) == 0; + return ret; + } + bool SetPosition(QWORD pos) + { + std::scoped_lock fileOperationLock(fileOperationMutex); + bool ret; + if (((long long)pos) < 0) //fpos_t is signed + ret = false; + else if (!IsOpen() && !Reopen()) + ret = false; + else + ret = _fseeki64(pFile, (long long)pos, SEEK_SET) == 0; + return ret; + } + + QWORD Write(QWORD pos, QWORD size, const void *inBuffer) + { + std::scoped_lock fileOperationLock(fileOperationMutex); + QWORD ret; + if (!IsOpen() && !Reopen()) + ret = 0; + else if ((pos != (QWORD)-1) && !SetPosition(pos)) + ret = 0; + else + ret = fwrite(inBuffer, 1, size, pFile); + return ret; + } + + bool Flush() + { + std::scoped_lock fileOperationLock(fileOperationMutex); + bool ret; + if (!IsOpen()) + ret = true; + else + ret = fflush(pFile) == 0; + return ret; + } +}; + +IAssetsWriter *Create_AssetsWriterToFile(const wchar_t *filePath, bool discard, bool binary, AssetsRWOpenFlags openFlags) +{ + if (filePath == NULL) return NULL; + + AssetsWriterToFile *pWriter = new AssetsWriterToFile(filePath, discard, binary, openFlags); + + if (openFlags & RWOpenFlags_Immediately) + { + if (!pWriter->Reopen()) + { + delete pWriter; + return NULL; + } + } + + AddReopenable(pWriter); + return pWriter; +} +IAssetsWriter *Create_AssetsWriterToFile(const char *filePath, bool discard, bool binary, AssetsRWOpenFlags openFlags) +{ + if (filePath == NULL) return NULL; + + size_t tcLen = 0; + TCHAR *tcPath = _MultiByteToWide(filePath, tcLen); + + IAssetsWriter *pWriter = Create_AssetsWriterToFile(tcPath, discard, binary, openFlags); + + _FreeWCHAR(tcPath); + + return pWriter; +} +IAssetsWriter *Create_AssetsWriterToFile(FILE *pFile) +{ + if (pFile == NULL) return NULL; + + AssetsWriterToFile *pWriter = new AssetsWriterToFile(pFile); + + return pWriter; +} + + +class AssetsWriterToMemory : public IAssetsWriterToMemory +{ +protected: + void *data; + size_t dataLen; + size_t dataBufLen; + + void *ownBuf; //NULL if the caller passed a buffer through the constructor + size_t ownBufMaxLen; + + size_t writerPos; + + bool isDynamic; bool freeOnClose; +public: + AssetsWriterToMemory(void *buf, size_t bufLen, size_t initialDataLen) + { + this->data = buf; + this->dataLen = initialDataLen; + this->dataBufLen = bufLen; + this->ownBuf = NULL; + this->ownBufMaxLen = 0; + this->writerPos = 0; + this->isDynamic = false; + this->freeOnClose = false; + memset(&((uint8_t*)buf)[initialDataLen], 0, bufLen - initialDataLen); + } + AssetsWriterToMemory(size_t maximumLen, size_t initialLen) + { + this->ownBuf = initialLen ? malloc(initialLen) : 0; + if (this->ownBuf == NULL) + initialLen = 0; + else + memset(this->ownBuf, 0, initialLen); + this->dataBufLen = initialLen; + + this->data = this->ownBuf; + this->dataLen = 0; + + this->ownBufMaxLen = maximumLen; + this->writerPos = 0; + + this->isDynamic = true; + this->freeOnClose = true; + } + ~AssetsWriterToMemory() + { + if (ownBuf != NULL) + { + if (freeOnClose) + free(ownBuf); + ownBuf = NULL; + } + dataBufLen = 0; + data = NULL; dataLen = 0; + writerPos = 0; + } + bool Reopen() + { + return true; + } + bool IsOpen() + { + return true; + } + bool Close() + { + return false; + } + + + AssetsRWTypes GetType() { return AssetsRWType_Writer; } + AssetsRWClasses GetClass() { return AssetsRWClass_WriterToMemory; } + bool IsView() { return false; } + + bool Tell(QWORD &pos) + { + pos = writerPos; + return true; + } + bool Seek(AssetsSeekTypes origin, long long offset) + { + bool ret; + switch (origin) + { + case AssetsSeek_Begin: + { + if (offset < 0) + ret = false; + else + { + if ((unsigned long long)offset > this->dataBufLen) + Resize((size_t)std::min(offset, SIZE_MAX)); + if ((unsigned long long)offset > this->dataBufLen) + ret = false; + else + { + if ((unsigned long long)offset > this->dataLen) + this->dataLen = (size_t)offset; + this->writerPos = (size_t)offset; + ret = true; + } + } + break; + } + case AssetsSeek_Cur: + { + if (offset < 0) + { + offset = -offset; + if (offset < 0) //=> offset is the minimum signed long long + ret = false; + else if ((unsigned long long)offset > this->writerPos) + ret = false; + else + { + this->writerPos -= (unsigned long long)offset; + ret = true; + } + } + else + { + unsigned long long _offset = (unsigned long long)offset + this->writerPos; + if (_offset > this->dataBufLen) + Resize(std::min(_offset, SIZE_MAX)); + if (_offset > this->dataBufLen) + ret = false; + else + { + if (_offset > this->dataLen) + this->dataLen = (size_t)_offset; + this->writerPos = (size_t)_offset; + ret = true; + } + } + break; + } + case AssetsSeek_End: + { + if (offset > 0) + { + unsigned long long _offset = offset + this->dataLen; + if (_offset > this->dataBufLen) + Resize((size_t)std::min(_offset, SIZE_MAX)); + if (_offset > this->dataBufLen) + ret = false; + else + { + if (_offset > this->dataLen) + this->dataLen = (size_t)offset; + this->writerPos = (size_t)offset; + ret = true; + } + } + else + { + offset = -offset; + if (offset < 0) //=> offset is the minimum signed long long + ret = false; + else if ((unsigned long long)offset > this->dataLen) + ret = false; + else + { + this->writerPos = this->dataLen - (unsigned long long)offset; + ret = true; + } + } + break; + } + default: ret = false; + } + return ret; + } + bool SetPosition(QWORD pos) + { + bool ret; + if (pos > this->dataBufLen) + Resize(pos); + if (pos > this->dataBufLen) + ret = false; + else + { + if (pos > this->dataLen) + this->dataLen = (size_t)pos; + this->writerPos = pos; + ret = true; + } + return ret; + } + + QWORD Write(QWORD pos, QWORD size, const void *inBuffer) + { + QWORD ret; + if ((pos != (QWORD)-1) && !SetPosition(pos)) + ret = 0; + else + { + if (this->writerPos > this->dataBufLen) + return 0; + QWORD actualSize = size; + if (actualSize > (this->dataBufLen - this->writerPos)) + { + if (isDynamic && !Resize(this->writerPos + actualSize)) + return 0; + if (actualSize > (this->dataBufLen - this->writerPos)) + actualSize = this->dataBufLen - this->writerPos; + } + memcpy(&((uint8_t*)data)[writerPos], inBuffer, (size_t)actualSize); + ret = actualSize; + this->writerPos += ret; + if (this->writerPos > this->dataLen) + this->dataLen = this->writerPos; + } + return ret; + } + + bool Flush() + { + return true; + } + + bool GetBuffer(void *&buffer, size_t &dataSize) + { + buffer = data; + dataSize = dataLen; + return true; + } + + bool IsDynamicBuffer() + { + return isDynamic; + } + + bool SetFreeBuffer(bool doFree) + { + if (!isDynamic && doFree) + return false; + this->freeOnClose = doFree; + return true; + } + + bool Resize(size_t targetLen) + { + if (!isDynamic) + return false; + + targetLen = (targetLen + 2047) & ~(2047); + if (targetLen > ownBufMaxLen) + targetLen = ownBufMaxLen; + + void *newBuf = realloc(ownBuf, targetLen); + if (newBuf == NULL) + return false; + + ownBuf = newBuf; + data = newBuf; + if (targetLen > dataBufLen) + memset(&((uint8_t*)newBuf)[dataBufLen], 0, targetLen - dataBufLen); + dataBufLen = targetLen; + if (dataLen > targetLen) + dataLen = targetLen; + return true; + } +}; + +IAssetsWriterToMemory *Create_AssetsWriterToMemory(void *buf, size_t bufLen, size_t initialDataLen) +{ + if (buf == NULL && bufLen > 0) return NULL; + if (initialDataLen > bufLen) return NULL; + + AssetsWriterToMemory *pWriter = new AssetsWriterToMemory(buf, bufLen, initialDataLen); + + //AssetsWriterToMemory isn't garbage collectable + //AddReopenable(pWriter); + return pWriter; +} +IAssetsWriterToMemory *Create_AssetsWriterToMemory(size_t initialLen, size_t maximumLen) +{ + if (initialLen > maximumLen) return NULL; + + AssetsWriterToMemory *pWriter = new AssetsWriterToMemory(maximumLen, initialLen); + + //AssetsWriterToMemory isn't garbage collectable + //AddReopenable(pWriter); + return pWriter; +} +void Free_AssetsWriterToMemory_DynBuf(void *buffer) +{ + if (buffer) + free(buffer); +} + +class AssetsWriterToWriterOffset : public IAssetsWriterToWriterOffset +{ +protected: + IAssetsWriter *pChild; + QWORD childOffset; + QWORD writerPos; + bool doSeek; bool alwaysSeek; +public: + AssetsWriterToWriterOffset(IAssetsWriter *pChild, QWORD offset, bool alwaysSeekChild) + { + this->pChild = pChild; + this->childOffset = offset; + this->alwaysSeek = alwaysSeekChild; + this->writerPos = 0; + this->doSeek = false; + } + ~AssetsWriterToWriterOffset() + { + this->pChild = NULL; + this->childOffset = 0; + this->writerPos = 0; + } + bool Reopen() + { + bool ret = false; + if (pChild != NULL) + ret = pChild->Reopen(); + return ret; + } + bool IsOpen() + { + bool ret = false; + if (pChild != NULL) + ret = pChild->IsOpen(); + return ret; + } + bool Close() + { + bool ret = false; + if (pChild != NULL) + { + ret = pChild->Close(); + if (ret) + this->doSeek = true; + } + return ret; + } + + AssetsRWTypes GetType() { return AssetsRWType_Writer; } + AssetsRWClasses GetClass() { return AssetsRWClass_WriterToWriterOffset; } + bool IsView() { return false; } + + bool Tell(QWORD &pos) + { + pos = writerPos; + return true; + } + bool Seek(AssetsSeekTypes origin, long long offset) + { + bool ret; + if (pChild == NULL) + ret = false; + else + { + switch (origin) + { + case AssetsSeek_Begin: + { + if (offset < 0) + ret = false; + else + { + if (ret = pChild->Seek(AssetsSeek_Begin, this->childOffset + offset)) + { + QWORD tempPos = this->childOffset + (size_t)offset; + if (pChild->Tell(tempPos) && ((tempPos - this->childOffset) != (size_t)offset)) + { + this->doSeek = true; + ret = false; + } + else + { + this->writerPos = (size_t)offset; + this->doSeek = false; + ret = true; + } + } + else + ret = false; + } + break; + } + case AssetsSeek_Cur: + { + if (offset < 0) + { + offset = -offset; + if (offset < 0) //=> offset is the minimum signed long long + ret = false; + else if ((unsigned long long)offset > this->writerPos) + ret = false; + else + { + this->writerPos -= (unsigned long long)offset; + this->doSeek = true; + ret = true; + } + } + else + { + if (ret = pChild->Seek(AssetsSeek_Cur, offset)) + { + QWORD tempPos = this->childOffset + (unsigned long long)offset; + if (pChild->Tell(tempPos) && ((tempPos - this->childOffset) != this->writerPos)) + { + this->doSeek = true; + ret = false; + } + else + { + this->writerPos += (unsigned long long)offset; + this->doSeek = false; + ret = true; + } + } + else + ret = false; + } + break; + } + case AssetsSeek_End: + { + if (offset > 0) + ret = false; + else + { + offset = -offset; + if (offset < 0) //=> offset is the minimum signed long long + ret = false; + else + { + if (ret = pChild->Seek(AssetsSeek_End, -offset)) + { + QWORD tempPos = 0; + if (!pChild->Tell(tempPos) || (tempPos < this->childOffset)) + { + this->doSeek = true; + ret = false; + } + else + { + this->writerPos = tempPos - this->childOffset; + this->doSeek = false; + } + } + else + ret = false; + } + } + break; + } + default: ret = false; + } + } + return ret; + } + bool SetPosition(QWORD pos) + { + bool ret; + if (pChild == NULL) + ret = false; + else if (pChild->SetPosition(pos + this->childOffset)) + { + QWORD tempPos = pos + this->childOffset; + pChild->Tell(tempPos); + if (tempPos != (pos + this->childOffset)) + { + this->doSeek = true; + ret = false; + } + else + { + this->writerPos = pos; + this->doSeek = false; + ret = true; + } + } + else + ret = false; + return ret; + } + + QWORD Write(QWORD pos, QWORD size, const void *inBuffer) + { + QWORD ret; + if (pChild == NULL) + ret = 0; + else if ((pos != (QWORD)-1) && !SetPosition(pos)) + ret = 0; + else + { + QWORD writePos = (doSeek || alwaysSeek) ? (this->childOffset + this->writerPos) : ((QWORD)-1); + ret = pChild->Write(writePos, size, inBuffer); + this->writerPos += ret; + doSeek = false; + } + return ret; + } + + bool Flush() + { + return (pChild != NULL) && pChild->Flush(); + } + + bool GetChild(IAssetsWriter *&pWriter) + { + pWriter = pChild; + return (pChild != NULL); + } +}; + +IAssetsWriterToWriterOffset *Create_AssetsWriterToWriterOffset(IAssetsWriter *pChild, QWORD offset, bool alwaysSeek) +{ + if (pChild == NULL) return NULL; + + AssetsWriterToWriterOffset *pWriter = new AssetsWriterToWriterOffset(pChild, offset, alwaysSeek); + + //AssetsWriterToWriterOffset isn't garbage collectable + //AddReopenable(pReader); + return pWriter; +} \ No newline at end of file diff --git a/AssetsTools/AssetsFileReader.h b/AssetsTools/AssetsFileReader.h new file mode 100644 index 0000000..90f042c --- /dev/null +++ b/AssetsTools/AssetsFileReader.h @@ -0,0 +1,182 @@ +#pragma once + +#include "defines.h" + +ASSETSTOOLS_API void AssetsVerifyLoggerToMessageBox(const char *message); +ASSETSTOOLS_API void AssetsVerifyLoggerToConsole(const char *message); + +//Abstract class for resources that can be closed and restored when needed. +//This is used to keep "opened" FILEs beyond standard library limits without using OS-specific functions. +//Used by all AssetsReader and AssetsWriter classes; if they aren't restorable, Close() will fail. +class IAssetsReopenable +{ +public: + ASSETSTOOLS_API virtual ~IAssetsReopenable()=0; + + //Reopen a closed instance. Returns false on failure, true on success or if the instance already was opened. + virtual bool Reopen()=0; + + //Returns whether the instance is open. + virtual bool IsOpen()=0; + + //Close an opened instance. Returns false on failure, true on success or if the instance already was closed. + virtual bool Close()=0; +}; + +//Enum to distinguish readers and writers. +enum AssetsRWTypes +{ + AssetsRWType_Unknown, + AssetsRWType_Reader, + AssetsRWType_Writer, +}; +//Enum to get the reader or writer type. +enum AssetsRWClasses +{ + AssetsRWClass_Unknown, //Use for custom types. + AssetsRWClass_ReaderFromFile, + AssetsRWClass_ReaderFromSplitFile, + AssetsRWClass_ReaderFromMemory, + AssetsRWClass_ReaderFromReaderRange, + AssetsRWClass_WriterToFile, + AssetsRWClass_WriterToMemory, + AssetsRWClass_WriterToWriterOffset, +}; + +//Seek origin types. +enum AssetsSeekTypes +{ + //Use the beginning of the stream as the origin. + AssetsSeek_Begin, + //Use the current position as the origin. + AssetsSeek_Cur, + //Use the end of the stream as the origin. + AssetsSeek_End, +}; + +class IAssetsRW : public IAssetsReopenable +{ +public: + //Get the instance's type (reader or writer). + virtual AssetsRWTypes GetType()=0; + //Get the instance's class. For views, this is the base instance's class. + virtual AssetsRWClasses GetClass()=0; + //Determine whether the instance is a special view object of another instance. + //For some types (e.g. ReaderFromReaderRange), an object created with CreateView() can also return false. + virtual bool IsView()=0; + + //Get the stream's current position. + virtual bool Tell(QWORD &pos)=0; + //Move the stream's current position relative to an origin. + virtual bool Seek(AssetsSeekTypes origin, long long offset)=0; + //Set the stream's position. + virtual bool SetPosition(QWORD pos)=0; +}; + +class IAssetsReader : public IAssetsRW +{ +public: + //Read [size] bytes of data from the stream to outBuffer, starting at [pos] (or the current position if -1). + //Returns the amount of bytes read and copied to outBuffer. If the return value is smaller than size, a read error or EOF occured. + //On failure, if nullUnread is set, the rest of outBuffer will be filled with zeros. + virtual QWORD Read(QWORD pos, QWORD size, void *outBuffer, bool nullUnread = true)=0; + //Read [size] bytes of data from the stream to outBuffer, starting at the current position. + inline QWORD Read(QWORD size, void *outBuffer) { return Read((QWORD)-1, size, outBuffer); } + + //Create a view of this reader. + //While all individual operations of base readers (i.e. not views) are thread safe, the file positions are per instance. + //Views affect the position of the base instance but keep track of their own position. + //Views can be freed with Free_AssetsReader(.). The base reader must only be freed once the views are no longer used. + //Note: Depending on the reader type (especially files), accessing several views in parallel can be slower than doing so on a single thread. + virtual IAssetsReader *CreateView()=0; +}; + +class IAssetsWriter : public IAssetsRW +{ +public: + //Write [size] bytes of data from inBuffer to the stream, starting at [pos] (or the current position if -1) and filling skipped bytes with null. + //Returns the amount of bytes written. If the return value is smaller than size, a write error occured. + virtual QWORD Write(QWORD pos, QWORD size, const void *inBuffer)=0; + //Write [size] bytes of data from inBuffer to the stream, starting at the current position. + inline QWORD Write(QWORD size, const void *inBuffer) { return Write((QWORD)-1, size, inBuffer); } + + //Write all remaining data from internal buffers to the file, if applicable. + virtual bool Flush()=0; +}; + +class IAssetsReaderFromReaderRange : public IAssetsReader +{ +public: + //Returns the child reader. + virtual bool GetChild(IAssetsReader *&pReader)=0; +}; + +class IAssetsWriterToMemory : public IAssetsWriter +{ +public: + //Returns this writer's current memory buffer and the amount of commited bytes. + virtual bool GetBuffer(void *&buffer, size_t &dataSize)=0; + //Determine whether this writer uses a dynamic or a fixed-length buffer. + virtual bool IsDynamicBuffer()=0; + //Specify whether the writer should be allowed to resize the dynamic buffer if required or free it with the writer. + virtual bool SetFreeBuffer(bool doFree)=0; + //Resize the internal buffer. If targetLen is smaller than the current buffer size, some data will get discarded. + virtual bool Resize(size_t targetLen)=0; +}; + +class IAssetsWriterToWriterOffset : public IAssetsWriter +{ +public: + //Returns the child reader. + virtual bool GetChild(IAssetsWriter *&pWriter)=0; +}; + +//Open flags for garbage collectable file readers and writers. Combinable with logical OR, except RWOpenFlags_None. +typedef unsigned int AssetsRWOpenFlags; +#define RWOpenFlags_None 0U //Default : Open the underlying file handles on the first use and temporarily close them if the handle limit is reached. +#define RWOpenFlags_Immediately 1U //Immediately open all file handles, before returning from the Create function. If that fails, Create returns NULL. +#define RWOpenFlags_Unclosable 2U //Never temporarily close the handles. + +//Close and free an IAssetsReader or IAssetsWriter. +ASSETSTOOLS_API void Free_AssetsReopenable(IAssetsReopenable *pObject); +inline void Free_AssetsReader(IAssetsReader *pReader) { Free_AssetsReopenable(pReader); } +inline void Free_AssetsWriter(IAssetsWriter *pWriter) { Free_AssetsReopenable(pWriter); } + +//Create a reader from a file stored in [filePath] (UTF-8) in text or binary mode. +ASSETSTOOLS_API IAssetsReader *Create_AssetsReaderFromFile(const char *filePath, bool binary, AssetsRWOpenFlags openFlags); +//Create a reader from a file stored in [filePath] (UTF-16) in text or binary mode. +ASSETSTOOLS_API IAssetsReader *Create_AssetsReaderFromFile(const wchar_t *filePath, bool binary, AssetsRWOpenFlags openFlags); +//Create a reader using a FILE* opened by the caller. Use this if the caller requires the FILE* handle (hidden by the other methods to enable closing&reopening). +ASSETSTOOLS_API IAssetsReader *Create_AssetsReaderFromFile(FILE *pFile); + +//Create a reader from .splitX files, with .split0 stored in [filePath] (UTF-8) in text or binary mode. +//allowUpdate : Does not require all split segments to be open at the same time if set to true. Otherwise, requires all split segments to be open at the same time. +ASSETSTOOLS_API IAssetsReader *Create_AssetsReaderFromSplitFile(const char *filePath, bool binary, bool allowUpdate, AssetsRWOpenFlags openFlags); +//Create a reader from .splitX files, with .split0 stored in [filePath] (UTF-16) in text or binary mode. +//allowUpdate : Does not require all split segments to be open at the same time if set to true. Otherwise, requires all split segments to be open at the same time. +ASSETSTOOLS_API IAssetsReader *Create_AssetsReaderFromSplitFile(const wchar_t *filePath, bool binary, bool allowUpdate, AssetsRWOpenFlags openFlags); + +//Create a reader from [bufLen] bytes of data stored in [buf], optionally creating a local copy for the reader's lifetime if [copyBuf] == true. +// If [copyBuf] == false, freeBufCallback(buf) will be called on destruction if [freeBufCallback] != nullptr. +ASSETSTOOLS_API IAssetsReader *Create_AssetsReaderFromMemory(const void *buf, size_t bufLen, bool copyBuf, cbFreeMemoryResource freeBufCallback = nullptr); + +//Create a reader using a [rangeSize] bytes long range starting at [rangeStart] in the child reader [pChild], optionally setting the child reader position for each Read call. +ASSETSTOOLS_API IAssetsReaderFromReaderRange *Create_AssetsReaderFromReaderRange(IAssetsReader *pChild, QWORD rangeStart, QWORD rangeSize, bool alwaysSeek = true); + +//Create a writer to a file stored in [filePath] (UTF-8) in text or binary mode, replacing existing data in that file if discard is set. +ASSETSTOOLS_API IAssetsWriter *Create_AssetsWriterToFile(const char *filePath, bool discard, bool binary, AssetsRWOpenFlags openFlags); +//Create a writer to a file stored in [filePath] (UTF-16) in text or binary mode, replacing existing data in that file if discard is set. +ASSETSTOOLS_API IAssetsWriter *Create_AssetsWriterToFile(const wchar_t *filePath, bool discard, bool binary, AssetsRWOpenFlags openFlags); +//Create a writer using a FILE* opened by the caller. Use this if the caller requires the FILE* otherwise hidden to allow garbage collection. +ASSETSTOOLS_API IAssetsWriter *Create_AssetsWriterToFile(FILE *pFile); + +//Create a writer to a [bufLen] bytes long buffer [buf] with [initialDataLen] precommited bytes. +ASSETSTOOLS_API IAssetsWriterToMemory *Create_AssetsWriterToMemory(void *buf, size_t bufLen, size_t initialDataLen = 0); +//Create a writer to a dynamic buffer with a maximum length of [maximumLen] bytes and a initial buffer size of [initialLen] bytes. +// The buffer will be freed on destruction. This can be overridden through writer->SetFreeBuffer(false). +ASSETSTOOLS_API IAssetsWriterToMemory *Create_AssetsWriterToMemory(size_t initialLen = 0, size_t maximumLen = ~(size_t)0); +//Free the dynamic buffer retrieved through IAssetsWriterToMemory::GetBuffer. +ASSETSTOOLS_API void Free_AssetsWriterToMemory_DynBuf(void *buffer); + +//Create a writer using a child writer [pChild] with a data offset of [offset] bytes, optionally setting the child writer position for each Write call. +ASSETSTOOLS_API IAssetsWriterToWriterOffset *Create_AssetsWriterToWriterOffset(IAssetsWriter *pChild, QWORD offset, bool alwaysSeek = true); \ No newline at end of file diff --git a/AssetsTools/AssetsFileTable.cpp b/AssetsTools/AssetsFileTable.cpp new file mode 100644 index 0000000..6a99206 --- /dev/null +++ b/AssetsTools/AssetsFileTable.cpp @@ -0,0 +1,214 @@ +#include "stdafx.h" +#include "AssetsFileTable.h" +#include +#include +#include + +ASSETSTOOLS_API AssetsFileTable::AssetsFileTable(AssetsFile *pFile/*, bool readNames*/) +{ + this->pFile = pFile; + this->pReader = pFile->pReader; + //this->pLookupBase = NULL; + + //AssetsFileHeader header; + //reader(0, sizeof(AssetsFileHeader), &header, readerPar); + + QWORD fileListPos = pFile->AssetTablePos; + unsigned int fileCount = 0; + pReader->Read(fileListPos, 4, &fileCount); + if (pFile->header.endianness) + SwapEndians_(fileCount); + fileListPos += 4; + if (pFile->header.format >= 0x0E) + fileListPos = ((fileListPos + 3) & (~0x3)); //align to 4-byte boundary + + this->assetFileInfoCount = fileCount; + + //AssetFileInfo *pFileInfo = new AssetFileInfo[fileCount]; + AssetFileInfoEx *pFileInfoEx = new AssetFileInfoEx[fileCount]; + + for (unsigned int i = 0; i < fileCount; i++) + { + fileListPos = pFileInfoEx[i].Read(pFile->header.format, fileListPos, pReader, pFile->header.endianness); + if (pFile->header.format >= 0x10) + { + if (pFileInfoEx[i].curFileTypeOrIndex >= pFile->typeTree.fieldCount) + { + pFileInfoEx[i].curFileType = 0x80000000; + pFileInfoEx[i].inheritedUnityClass = 0xFFFF; + pFileInfoEx[i].scriptIndex = 0xFFFF; + } + else + { + uint32_t classId = pFile->typeTree.pTypes_Unity5[pFileInfoEx[i].curFileTypeOrIndex].classId; + if (pFile->typeTree.pTypes_Unity5[pFileInfoEx[i].curFileTypeOrIndex].scriptIndex != 0xFFFF) + { + pFileInfoEx[i].curFileType = (int)(-1 - (int)pFile->typeTree.pTypes_Unity5[pFileInfoEx[i].curFileTypeOrIndex].scriptIndex); + pFileInfoEx[i].inheritedUnityClass = (uint16_t)classId; + pFileInfoEx[i].scriptIndex = pFile->typeTree.pTypes_Unity5[pFileInfoEx[i].curFileTypeOrIndex].scriptIndex; + } + else + { + pFileInfoEx[i].curFileType = classId; + pFileInfoEx[i].inheritedUnityClass = (uint16_t)classId; + pFileInfoEx[i].scriptIndex = 0xFFFF; + } + } + } + else + pFileInfoEx[i].curFileType = pFileInfoEx[i].curFileTypeOrIndex; + pFileInfoEx[i].absolutePos = pFile->header.offs_firstFile + pFileInfoEx[i].offs_curFile; + } + this->pAssetFileInfo = pFileInfoEx; +} +ASSETSTOOLS_API bool AssetFileInfoEx::ReadName(AssetsFile *pFile, std::string &out, IAssetsReader *pReaderView) +{ + out.clear(); + if (HasName(this->curFileType)) + { + if (!pReaderView) + pReaderView = pFile->pReader; + unsigned int nameSize = 0; + if (pReaderView->Read(this->absolutePos, 4, &nameSize) != 4) + return false; + + if (pFile->header.endianness != 0) SwapEndians_(nameSize); + if (nameSize + 4 >= this->curFileSize || nameSize >= 4092) + return false; + + out.resize(nameSize, 0); + if (pReaderView->Read(this->absolutePos + 4, nameSize, out.data()) != nameSize) + { + out.clear(); + return false; + } + for (size_t i = 0; i < nameSize; i++) + { + if (out[i] < 0x20) + { + out.clear(); + return false; + } + } + return true; + } + else + { + return false; + } +} + +ASSETSTOOLS_API AssetFileInfoEx *AssetsFileTable::getAssetInfo(QWORD pathId) +{ + if (!lookup.empty()) + { + AssetFileInfoEx tmp; + tmp.index = pathId; + auto it = lookup.find(&tmp); + if (it != lookup.end()) + { + return it->pInfo; + } + } + for (unsigned int i = 0; i < assetFileInfoCount; i++) + { + if (pAssetFileInfo[i].index == pathId) + return &pAssetFileInfo[i]; + } + return NULL; +} + +ASSETSTOOLS_API AssetsFile *AssetsFileTable::getAssetsFile() +{ + return pFile; +} +ASSETSTOOLS_API IAssetsReader *AssetsFileTable::getReader() +{ + return pReader; +} + +ASSETSTOOLS_API bool AssetsFileTable::GenerateQuickLookupTree() +{ + { + std::set tmp; + lookup.swap(tmp); + } + struct LookupIterator + { + AssetFileInfoEx *pFileTable; + size_t fileTableSize; + size_t i; + inline LookupIterator() + : pFileTable(nullptr), fileTableSize(0), i(0) + {} + inline LookupIterator(AssetFileInfoEx *pFileTable, size_t fileTableSize, size_t i) + : pFileTable(pFileTable), fileTableSize(fileTableSize), i(i) + {} + inline LookupIterator(LookupIterator &&other) + { + (*this) = std::move(other); + } + inline LookupIterator(const LookupIterator &other) + { + (*this) = other; + } + inline LookupIterator &operator=(const LookupIterator &other) + { + this->pFileTable = other.pFileTable; + this->fileTableSize = other.fileTableSize; + this->i = other.i; + return (*this); + } + inline LookupIterator &operator=(LookupIterator &&other) + { + this->pFileTable = other.pFileTable; + this->fileTableSize = other.fileTableSize; + this->i = other.i; + other.i = other.fileTableSize; + return (*this); + } + inline bool operator==(const LookupIterator &other) + { + return (pFileTable == other.pFileTable && fileTableSize == other.fileTableSize && i == other.i) + || (i >= fileTableSize && other.i >= other.fileTableSize); + } + inline bool operator!=(const LookupIterator &other) + { + return !((*this) == other); + } + inline LookupIterator &operator++() //pre-increment + { + if (i < fileTableSize) i++; + return (*this); + } + inline LookupIterator operator++(int) //post-increment + { + LookupIterator ret = (*this); + ++(*this); + return ret; + } + AssetFileInfoEx_KeyRef resultTmp; + inline AssetFileInfoEx_KeyRef &operator*() + { + assert(i < fileTableSize); + resultTmp = AssetFileInfoEx_KeyRef(&pFileTable[i]); + return resultTmp; + } + inline AssetFileInfoEx_KeyRef *operator->() + { + return &(*(*this)); //&this->operator*() + } + }; + lookup.insert(LookupIterator(pAssetFileInfo, assetFileInfoCount, 0), LookupIterator()); + return true; + //FreeQuickLookupTree(pLookupBase); + //pLookupBase = NULL; + //return ::GenerateQuickLookupTree(this, pLookupBase); +} + +ASSETSTOOLS_API AssetsFileTable::~AssetsFileTable() +{ + if (pAssetFileInfo != NULL) + delete[] pAssetFileInfo; + //FreeQuickLookupTree(pLookupBase); +} diff --git a/AssetsTools/AssetsFileTable.h b/AssetsTools/AssetsFileTable.h new file mode 100644 index 0000000..299768a --- /dev/null +++ b/AssetsTools/AssetsFileTable.h @@ -0,0 +1,67 @@ +#pragma once +#include "AssetsFileFormat.h" +#include +#include + +#include "defines.h" + +class AssetFileInfoEx : public AssetFileInfo +{ + public: + //AssetsHeader format < 0x10 : equals curFileTypeOrIndex + //AssetsHeader format >= 0x10 : equals TypeTree.pTypes_Unity5[curFileTypeOrIndex].classId or (uint32_t)INT_MIN if the index is out of bounds + uint32_t curFileType; + QWORD absolutePos; + + //If the file type id is known to have a name field, it reads up to 100 characters (including the null-terminator) to "out". + ASSETSTOOLS_API bool ReadName(AssetsFile *pFile, std::string &out, IAssetsReader *pReaderView = nullptr); +}; + +class AssetFileInfoEx_KeyRef +{ +public: + AssetFileInfoEx *pInfo; + inline AssetFileInfoEx_KeyRef() + : pInfo(nullptr) + {} + inline AssetFileInfoEx_KeyRef(AssetFileInfoEx *pInfo) + : pInfo(pInfo) + {} + inline bool operator<(const AssetFileInfoEx_KeyRef other) const + { + assert(pInfo && other.pInfo); + return pInfo->index < other.pInfo->index; + } + inline bool operator==(const AssetFileInfoEx_KeyRef other) const + { + assert(pInfo && other.pInfo); + return pInfo->index == other.pInfo->index; + } +}; + +//Provides additional info for assets and an O(log(assetFileInfoCount)) lookup by path ID. +//The names are always read from the asset itself if possible, +// see AssetBundleFileTable or ResourceManagerFile for more detailed names (usually not set for all assets with m_Name fields). +class AssetsFileTable +{ + AssetsFile *pFile; + IAssetsReader *pReader; + + std::set lookup; + + public: + AssetFileInfoEx *pAssetFileInfo; + unsigned int assetFileInfoCount; + + public: + ASSETSTOOLS_API AssetsFileTable(AssetsFile *pFile); + ASSETSTOOLS_API ~AssetsFileTable(); + + ASSETSTOOLS_API AssetFileInfoEx *getAssetInfo(QWORD pathId); + + //Improves the performance of getAssetInfo but uses additional memory. + ASSETSTOOLS_API bool GenerateQuickLookupTree(); + + ASSETSTOOLS_API AssetsFile *getAssetsFile(); + ASSETSTOOLS_API IAssetsReader *getReader(); +}; diff --git a/AssetsTools/AssetsReplacer.cpp b/AssetsTools/AssetsReplacer.cpp new file mode 100644 index 0000000..fce6a49 --- /dev/null +++ b/AssetsTools/AssetsReplacer.cpp @@ -0,0 +1,630 @@ +#include "stdafx.h" +#include "../AssetsTools/AssetsReplacer.h" +#include "InternalAssetsReplacer.h" + +GenericReplacer::~GenericReplacer() +{ +} + +#pragma region AssetsReplacerBase +AssetsEntryReplacerBase::AssetsEntryReplacerBase() +{ + this->fileID = 0; + this->pathID = 0; + this->classID = 0; + this->monoScriptIndex = 0; +} +AssetsEntryReplacerBase::AssetsEntryReplacerBase(uint32_t fileID, QWORD pathID, int classID, uint16_t monoScriptIndex) +{ + this->fileID = fileID; + this->pathID = pathID; + this->classID = classID; + this->monoScriptIndex = monoScriptIndex; +} +uint32_t AssetsEntryReplacerBase::GetFileID() const {return this->fileID;} +QWORD AssetsEntryReplacerBase::GetPathID() const {return this->pathID;} +int AssetsEntryReplacerBase::GetClassID() const {return this->classID;} +uint16_t AssetsEntryReplacerBase::GetMonoScriptID() const {return this->monoScriptIndex;} +void AssetsEntryReplacerBase::SetMonoScriptID(uint16_t scriptID) {this->monoScriptIndex = scriptID;} +bool AssetsEntryReplacerBase::GetPropertiesHash(Hash128 &propertiesHash) { propertiesHash = Hash128(); return false; } +bool AssetsEntryReplacerBase::SetPropertiesHash(const Hash128 &propertiesHash) { return false; } +bool AssetsEntryReplacerBase::GetScriptIDHash(Hash128 &scriptIDHash) { scriptIDHash = Hash128(); return false; } +bool AssetsEntryReplacerBase::SetScriptIDHash(const Hash128 &scriptIDHash) { return false; } +bool AssetsEntryReplacerBase::GetTypeInfo(std::shared_ptr &pFile, ClassDatabaseType *&pType) +{ + pFile.reset(); pType = nullptr; + return false; +} +bool AssetsEntryReplacerBase::SetTypeInfo(std::shared_ptr pFile, ClassDatabaseType *pType) +{ + return false; +} + +QWORD AssetsEntryReplacerBase::WriteReplacer(QWORD pos, IAssetsWriter *pWriter) +{ + uint8_t fileVersion = 1; + pos += pWriter->Write(pos, 1, &fileVersion); + uint32_t writeFileID = 0; + pos += pWriter->Write(pos, 4, &writeFileID); + pos += pWriter->Write(pos, 8, &this->pathID); + pos += pWriter->Write(pos, 4, &this->classID); + pos += pWriter->Write(pos, 2, &this->monoScriptIndex); + + uint32_t preloadDepCount = (this->preloadDependencies.size() > 0xFFFFFFFFULL) ? 0xFFFFFFFF : (uint32_t)this->preloadDependencies.size(); + pos += pWriter->Write(pos, 4, &preloadDepCount); + for (uint32_t i = 0; i < preloadDepCount; i++) + { + pos += pWriter->Write(pos, 4, &this->preloadDependencies[i].fileID); + pos += pWriter->Write(pos, 8, &this->preloadDependencies[i].pathID); + } + return pos; +} +bool AssetsEntryReplacerBase::GetPreloadDependencies(const AssetPPtr *&pPreloadList, size_t &preloadListSize) +{ + preloadListSize = this->preloadDependencies.size(); + pPreloadList = this->preloadDependencies.data(); + return true; +} +bool AssetsEntryReplacerBase::SetPreloadDependencies(const AssetPPtr *pPreloadList, size_t preloadListSize) +{ + this->preloadDependencies.clear(); + this->preloadDependencies.resize(preloadListSize); + memcpy(this->preloadDependencies.data(), pPreloadList, sizeof(AssetPPtr) * preloadListSize); + return true; +} +bool AssetsEntryReplacerBase::AddPreloadDependency(const AssetPPtr &dependency) +{ + this->preloadDependencies.push_back(dependency); + return true; +} +#pragma endregion + +#pragma region AssetModifierBase +AssetEntryModifierBase::AssetEntryModifierBase() +{ + this->hasPropertiesHash = false; + this->hasScriptIDHash = false; + this->pClassFile = nullptr; + this->pClassType = nullptr; +} +AssetEntryModifierBase::AssetEntryModifierBase(uint32_t fileID, QWORD pathID, int classID, uint16_t monoScriptIndex) + : AssetsEntryReplacerBase(fileID, pathID, classID, monoScriptIndex) +{ + this->hasPropertiesHash = false; + this->hasScriptIDHash = false; + this->pClassFile = nullptr; + this->pClassType = nullptr; +} +AssetEntryModifierBase::~AssetEntryModifierBase() +{} +bool AssetEntryModifierBase::GetPropertiesHash(Hash128 &propertiesHash) +{ + propertiesHash = this->hasPropertiesHash ? this->propertiesHash : Hash128(); + return this->hasPropertiesHash; +} +bool AssetEntryModifierBase::SetPropertiesHash(const Hash128 &propertiesHash) +{ + this->propertiesHash = propertiesHash; + this->hasPropertiesHash = true; + return true; +} +bool AssetEntryModifierBase::GetScriptIDHash(Hash128 &scriptIDHash) +{ + scriptIDHash = this->hasScriptIDHash ? this->scriptIDHash : Hash128(); + return this->hasScriptIDHash; +} +bool AssetEntryModifierBase::SetScriptIDHash(const Hash128 &scriptIDHash) +{ + this->scriptIDHash = scriptIDHash; + this->hasScriptIDHash = true; + return true; +} +bool AssetEntryModifierBase::SetTypeInfo(std::shared_ptr pFile, ClassDatabaseType *pType) +{ + if ((pFile != nullptr) ^ (pType != nullptr)) //Exactly one of the two parameters is null, which is not allowed. + return false; + this->pClassFile = pFile; + this->pClassType = pType; + return true; +} +bool AssetEntryModifierBase::GetTypeInfo(std::shared_ptr &pFile, ClassDatabaseType *&pType) +{ + if (this->pClassFile && this->pClassType) + { + pFile = this->pClassFile; + pType = this->pClassType; + return true; + } + pFile.reset(); + pType = nullptr; + return false; +} +QWORD AssetEntryModifierBase::WriteReplacer(QWORD pos, IAssetsWriter *pWriter) +{ + pos = AssetsEntryReplacerBase::WriteReplacer(pos, pWriter); + uint8_t fileVersion = 0; + pos += pWriter->Write(pos, 1, &fileVersion); + + uint8_t hasPropertiesHash = this->hasPropertiesHash ? 1 : 0; + pos += pWriter->Write(pos, 1, &hasPropertiesHash); + if (hasPropertiesHash) + pos += pWriter->Write(pos, 16, this->propertiesHash.bValue); + + uint8_t hasScriptIDHash = this->hasScriptIDHash ? 1 : 0; + pos += pWriter->Write(pos, 1, &hasScriptIDHash); + if (hasScriptIDHash) + pos += pWriter->Write(pos, 16, this->scriptIDHash.bValue); + + uint8_t hasTypeInfo = 0; + if (this->pClassFile && this->pClassType) + { + hasTypeInfo = 1; + ClassDatabaseFile tempFile; + ClassDatabaseFile *pWriteFile = this->pClassFile.get(); + if (this->pClassFile->classes.size() > 1) + { + if (tempFile.InsertFrom(this->pClassFile.get(), this->pClassType) && tempFile.classes.size() == 1) + pWriteFile = &tempFile; + else + hasTypeInfo = 0; + } + pos += pWriter->Write(pos, 1, &hasTypeInfo); + if (hasTypeInfo) + pos = pWriteFile->Write(pWriter, pos); + } + else + pos += pWriter->Write(pos, 1, &hasTypeInfo); + return pos; +} +#pragma endregion + +#pragma region AssetRemover +AssetRemover::AssetRemover(uint32_t fileID, QWORD pathID, int classID, uint16_t monoScriptIndex) + : AssetsEntryReplacerBase(fileID, pathID, classID, monoScriptIndex) +{ +} +AssetRemover::~AssetRemover() +{ +} +AssetsReplacementType AssetRemover::GetType() const +{ + return AssetsReplacement_Remove; +} + + +QWORD AssetRemover::GetSize() const{return 0;} +QWORD AssetRemover::Write(QWORD pos, IAssetsWriter *pWriter){return pos;} +QWORD AssetRemover::WriteReplacer(QWORD pos, IAssetsWriter *pWriter) +{ + uint16_t replacerType = AssetsReplacer_AssetRemover; + pos += pWriter->Write(pos, 2, &replacerType); + uint8_t fileVersion = 1; + pos += pWriter->Write(pos, 1, &fileVersion); + pos = AssetsEntryReplacerBase::WriteReplacer(pos, pWriter); + return pos; +} +#pragma endregion +#pragma region AssetModifierFromReader +AssetModifierFromReader::AssetModifierFromReader() +{ + this->pReader = nullptr; + this->size = 0; + this->readerPos = 0; + this->copyBufferLen = 0; + this->freeReader = false; +} +AssetModifierFromReader::AssetModifierFromReader(uint32_t fileID, QWORD pathID, int classID, uint16_t monoScriptIndex, + IAssetsReader *pReader, QWORD size, QWORD readerPos, + size_t copyBufferLen, bool freeReader, + std::shared_ptr ref_pReader) + : AssetEntryModifierBase(fileID, pathID, classID, monoScriptIndex) +{ + this->pReader = pReader; + this->size = size; + this->readerPos = readerPos; + this->copyBufferLen = copyBufferLen; + this->freeReader = freeReader; + this->ref_pReader = std::move(ref_pReader); +} +AssetModifierFromReader::~AssetModifierFromReader() +{ + if (this->freeReader && this->pReader) + { + Free_AssetsReader(this->pReader); + this->pReader = nullptr; + } +} +AssetsReplacementType AssetModifierFromReader::GetType() const +{ + return AssetsReplacement_AddOrModify; +} + +QWORD AssetModifierFromReader::GetSize() const{return size;} +QWORD AssetModifierFromReader::Write(QWORD pos, IAssetsWriter *pWriter) +{ + if (size == 0 || pReader == nullptr) + return pos; + uint8_t stackBuffer[512]; + void *copyBuffer = stackBuffer; + size_t curBufferLen = 512; + if (copyBufferLen > 512) + { + copyBuffer = malloc(copyBufferLen); + if (copyBuffer == NULL) + copyBuffer = stackBuffer; + else + curBufferLen = copyBufferLen; + } + QWORD remainingBytes = size; QWORD curReaderPos = readerPos; + while (remainingBytes > curBufferLen) + { + QWORD curRead = pReader->Read(curReaderPos, curBufferLen, copyBuffer); + if (!curRead) + { + remainingBytes = 0; + break; + } + QWORD curWritten = pWriter->Write(pos, curRead, copyBuffer); + remainingBytes -= curWritten; + curReaderPos += curWritten; + pos += curWritten; + if (curWritten == 0) + break; + } + if (remainingBytes > 0) + { + QWORD curRead = pReader->Read(curReaderPos, remainingBytes, copyBuffer); + QWORD curWritten = pWriter->Write(pos, curRead, copyBuffer); + pos += curWritten; + } + if (curBufferLen > 512) + free(copyBuffer); + return pos; +} +QWORD AssetModifierFromReader::WriteReplacer(QWORD pos, IAssetsWriter *pWriter) +{ + //store it the as a modifier from memory (no difference) + uint16_t replacerType = AssetsReplacer_AssetModifierFromMemory; + pos += pWriter->Write(pos, 2, &replacerType); + uint8_t fileVersion = 1; + pos += pWriter->Write(pos, 1, &fileVersion); + pos = AssetEntryModifierBase::WriteReplacer(pos, pWriter); + pos += pWriter->Write(pos, 8, &this->size); + pos = Write(pos, pWriter); + return pos; +} +#pragma endregion +#pragma region AssetModifierFromMemory +AssetModifierFromMemory::AssetModifierFromMemory(uint32_t fileID, QWORD pathID, int classID, uint16_t monoScriptIndex, + void *buffer, size_t size, cbFreeMemoryResource freeMemCallback) + : AssetEntryModifierBase(fileID, pathID, classID, monoScriptIndex) +{ + this->buffer = buffer; + this->size = size; + this->freeMemCallback = freeMemCallback; +} +AssetModifierFromMemory::~AssetModifierFromMemory() +{ + if (freeMemCallback != NULL) + freeMemCallback(this->buffer); +} +AssetsReplacementType AssetModifierFromMemory::GetType() const +{ + return AssetsReplacement_AddOrModify; +} + +QWORD AssetModifierFromMemory::GetSize() const{return size;} +QWORD AssetModifierFromMemory::Write(QWORD pos, IAssetsWriter *pWriter) +{ + if (!size) + return pos; + return pos + pWriter->Write(pos, size, buffer); +} +QWORD AssetModifierFromMemory::WriteReplacer(QWORD pos, IAssetsWriter *pWriter) +{ + uint16_t replacerType = AssetsReplacer_AssetModifierFromMemory; + pos += pWriter->Write(pos, 2, &replacerType); + uint8_t fileVersion = 1; + pos += pWriter->Write(pos, 1, &fileVersion); + pos = AssetEntryModifierBase::WriteReplacer(pos, pWriter); + QWORD ullSize = this->size; + pos += pWriter->Write(pos, 8, &ullSize); + pos = Write(pos, pWriter); + return pos; +} +#pragma endregion +#pragma region AssetModifierFromFile + +AssetModifierFromFile::AssetModifierFromFile(uint32_t fileID, QWORD pathID, int classID, uint16_t monoScriptIndex, + FILE *pFile, QWORD offs, QWORD size, size_t copyBufferLen, bool freeFile) + : AssetEntryModifierBase(fileID, pathID, classID, monoScriptIndex) +{ + this->pFile = pFile; + this->offs = offs; + this->size = size; + this->copyBufferLen = copyBufferLen; + this->freeFile = freeFile; +} +AssetModifierFromFile::~AssetModifierFromFile() +{ + if (freeFile) + fclose(this->pFile); +} +AssetsReplacementType AssetModifierFromFile::GetType() const +{ + return AssetsReplacement_AddOrModify; +} + +QWORD AssetModifierFromFile::GetSize() const{return size;} +QWORD AssetModifierFromFile::Write(QWORD pos, IAssetsWriter *pWriter) +{ + if (!size) + return pos; + uint8_t stackBuffer[256]; + void *copyBuffer = stackBuffer; + size_t curBufferLen = 256; + if (copyBufferLen > 256) + { + copyBuffer = malloc(copyBufferLen); + if (copyBuffer == NULL) + copyBuffer = stackBuffer; + else + curBufferLen = copyBufferLen; + } + QWORD remainingBytes = size; fseek(pFile, (long)this->offs, SEEK_SET); + while (remainingBytes > curBufferLen) + { + fread(copyBuffer, curBufferLen, 1, pFile); + remainingBytes -= curBufferLen; + pWriter->Write(pos, curBufferLen, copyBuffer); + pos += curBufferLen; + } + if (remainingBytes > 0) + { + fread(copyBuffer, (size_t)remainingBytes, 1, pFile); + pWriter->Write(pos, remainingBytes, copyBuffer); + pos += remainingBytes; + } + if (curBufferLen > 256) + free(copyBuffer); + return pos; +} +QWORD AssetModifierFromFile::WriteReplacer(QWORD pos, IAssetsWriter *pWriter) +{ + //store it the as a modifier from memory (no difference) + uint16_t replacerType = AssetsReplacer_AssetModifierFromMemory; + pos += pWriter->Write(pos, 2, &replacerType); + uint8_t fileVersion = 1; + pos += pWriter->Write(pos, 1, &fileVersion); + pos = AssetEntryModifierBase::WriteReplacer(pos, pWriter); + pos += pWriter->Write(pos, 8, &this->size); + pos = Write(pos, pWriter); + return pos; +} +#pragma endregion +#pragma region AssetsDependenciesReplacerImpl +AssetsDependenciesReplacerImpl::AssetsDependenciesReplacerImpl(uint32_t fileID, std::vector _dependencies) + : fileID(fileID), dependencies(std::move(_dependencies)) +{} +AssetsReplacementType AssetsDependenciesReplacerImpl::GetType() const +{ + return AssetsReplacement_Dependencies; +} +uint32_t AssetsDependenciesReplacerImpl::GetFileID() const +{ + return fileID; +} +const std::vector& AssetsDependenciesReplacerImpl::GetDependencies() const +{ + return dependencies; +} +QWORD AssetsDependenciesReplacerImpl::WriteReplacer(QWORD pos, IAssetsWriter* pWriter) +{ + //store it the as a modifier from memory (no difference) + uint16_t replacerType = AssetsReplacer_Dependencies; + pos += pWriter->Write(pos, 2, &replacerType); + uint8_t fileVersion = 0; + pos += pWriter->Write(pos, 1, &fileVersion); + uint32_t writeFileID = 0; + pos += pWriter->Write(pos, 4, &writeFileID); + + uint32_t dependenciesCount = (uint32_t)dependencies.size(); + pos += pWriter->Write(pos, 4, &dependenciesCount); + for (uint32_t i = 0; i < dependenciesCount; ++i) + { + pos = dependencies[i].Write(pos, pWriter, 13, false); + } + return pos; +} +#pragma endregion + +void FreeMemoryResource_ReadAssetsReplacer(void *pResource) +{ + if (pResource) + free(pResource); +} +static AssetsReplacer *ReadAssetsEntryReplacer(QWORD& pos, IAssetsReader* pReader, std::shared_ptr ref_pReader, bool prefReplacerInMemory, + uint16_t replacerType) +{ + uint8_t replacerFileVersion = 0; + pos += pReader->Read(pos, 1, &replacerFileVersion); + + uint8_t replacerBaseVersion = 0; + if (replacerFileVersion >= 1) + { + //See AssetsReplacerBase::WriteReplacer + pos += pReader->Read(pos, 1, &replacerBaseVersion); + if (replacerBaseVersion > 1) + return NULL; + } + uint32_t fileID = 0; QWORD pathID = 0; int classID = 0; uint16_t monoScriptIndex = 0; + std::vector preloadDependencies; + pos += pReader->Read(pos, 4, &fileID); + pos += pReader->Read(pos, 8, &pathID); + pos += pReader->Read(pos, 4, &classID); + pos += pReader->Read(pos, 2, &monoScriptIndex); + if (replacerBaseVersion >= 1) + { + uint32_t count = 0; + pos += pReader->Read(pos, 4, &count); + preloadDependencies.resize(count); + for (uint32_t i = 0; i < count; i++) + { + pos += pReader->Read(pos, 4, &preloadDependencies[i].fileID); + pos += pReader->Read(pos, 8, &preloadDependencies[i].pathID); + } + } + switch (replacerType) + { + case AssetsReplacer_AssetRemover: + { + if (replacerFileVersion > 1) + return NULL; + AssetsEntryReplacer *pReplacer = new AssetRemover(fileID, pathID, classID, monoScriptIndex); + pReplacer->SetPreloadDependencies(preloadDependencies.data(), (uint32_t)preloadDependencies.size()); + return pReplacer; + } + case AssetsReplacer_AssetModifierFromReader: + case AssetsReplacer_AssetModifierFromMemory: + case AssetsReplacer_AssetModifierFromFile: + { + if (replacerFileVersion > 1) + return NULL; + + Hash128 propertiesHash = Hash128(); uint8_t hasPropertiesHash = 0; + Hash128 scriptIDHash = Hash128(); uint8_t hasScriptIDHash = 0; + uint8_t hasTypeInfo = 0; + std::shared_ptr pClassFile; ClassDatabaseType *pClassType = nullptr; + if (replacerFileVersion >= 1) + { + //See AssetModifierBase::WriteReplacer + uint8_t modifierBaseVersion = 0; + pos += pReader->Read(pos, 1, &modifierBaseVersion); + if (modifierBaseVersion > 0) + return NULL; + + pos += pReader->Read(pos, 1, &hasPropertiesHash); + if (hasPropertiesHash) + pos += pReader->Read(pos, 16, propertiesHash.bValue); + + pos += pReader->Read(pos, 1, &hasScriptIDHash); + if (hasScriptIDHash) + pos += pReader->Read(pos, 16, scriptIDHash.bValue); + + pos += pReader->Read(pos, 1, &hasTypeInfo); + if (hasTypeInfo) + { + pClassFile = std::make_shared(); + pos = pClassFile->Read(pReader, pos); + if (pClassFile->IsValid() && pClassFile->classes.size() == 1) + { + pClassType = &pClassFile->classes[0]; + } + else + { + pClassFile.reset(); + hasTypeInfo = 0; + } + } + } + + QWORD size = 0; + pos += pReader->Read(pos, 8, &size); + AssetsEntryReplacer *pReplacer = NULL; + if (prefReplacerInMemory) + { + void *pMem = malloc(size); + if (pMem) + { + QWORD actualRead = pReader->Read(pos, size, pMem); + memset(&((uint8_t*)pMem)[actualRead], 0, size - actualRead); + pReplacer = new AssetModifierFromMemory(fileID, pathID, classID, monoScriptIndex, pMem, size, + FreeMemoryResource_ReadAssetsReplacer); + } + } + if (!pReplacer) + pReplacer = + new AssetModifierFromReader(fileID, pathID, classID, monoScriptIndex, pReader, size, pos, 0, false, ref_pReader); + if (hasPropertiesHash) + pReplacer->SetPropertiesHash(propertiesHash); + if (hasScriptIDHash) + pReplacer->SetScriptIDHash(scriptIDHash); + if (pClassFile && pClassType) + pReplacer->SetTypeInfo(std::move(pClassFile), pClassType); + pReplacer->SetPreloadDependencies(preloadDependencies.data(), (uint32_t)preloadDependencies.size()); + pos += size; + return pReplacer; + } + } + return nullptr; +} +static AssetsReplacer *ReadAssetsReplacer(QWORD &pos, IAssetsReader *pReader, std::shared_ptr ref_pReader, bool prefReplacerInMemory) +{ + uint16_t replacerType = 0xFFFF; + pos += pReader->Read(pos, 2, &replacerType); + switch (replacerType) + { + case AssetsReplacer_AssetRemover: + case AssetsReplacer_AssetModifierFromReader: + case AssetsReplacer_AssetModifierFromMemory: + case AssetsReplacer_AssetModifierFromFile: + return ReadAssetsEntryReplacer(pos, pReader, std::move(ref_pReader), prefReplacerInMemory, replacerType); + case AssetsReplacer_Dependencies: + { + uint8_t fileVersion = 0; + pos += pReader->Read(pos, 1, &fileVersion); + if (fileVersion > 0) + return nullptr; //Unsupported + uint32_t fileID = 0; + pos += pReader->Read(pos, 4, &fileID); + + uint32_t dependenciesCount = 0; + pos += pReader->Read(pos, 4, &dependenciesCount); + std::vector dependencies(dependenciesCount); + for (uint32_t i = 0; i < dependenciesCount; ++i) + { + pos = dependencies[i].Read(pos, pReader, 13, false); + } + return new AssetsDependenciesReplacerImpl(fileID, std::move(dependencies)); + } + } + return nullptr; +} +ASSETSTOOLS_API AssetsReplacer *ReadAssetsReplacer(QWORD &pos, IAssetsReader *pReader, bool prefReplacerInMemory) +{ + return ReadAssetsReplacer(pos, pReader, nullptr, prefReplacerInMemory); +} +ASSETSTOOLS_API AssetsReplacer *ReadAssetsReplacer(QWORD &pos, std::shared_ptr pReader, bool prefReplacerInMemory) +{ + IAssetsReader *_pReader = pReader.get(); + return ReadAssetsReplacer(pos, _pReader, std::move(pReader), prefReplacerInMemory); +} +ASSETSTOOLS_API AssetsEntryReplacer *MakeAssetRemover(uint32_t fileID, QWORD pathID, int classID, uint16_t monoScriptIndex) +{ + return new AssetRemover(fileID, pathID, classID, monoScriptIndex); +} + +ASSETSTOOLS_API AssetsEntryReplacer *MakeAssetModifierFromReader(uint32_t fileID, QWORD pathID, int classID, uint16_t monoScriptIndex, + IAssetsReader *pReader, QWORD size, QWORD readerPos, + size_t copyBufferLen, bool freeReader) +{ + return new AssetModifierFromReader(fileID, pathID, classID, monoScriptIndex, pReader, size, readerPos, copyBufferLen, freeReader); +} +ASSETSTOOLS_API AssetsEntryReplacer *MakeAssetModifierFromMemory(uint32_t fileID, QWORD pathID, int classID, uint16_t monoScriptIndex, + void *buffer, size_t size, cbFreeMemoryResource freeResourceCallback) +{ + return new AssetModifierFromMemory(fileID, pathID, classID, monoScriptIndex, buffer, size, freeResourceCallback); +} +ASSETSTOOLS_API AssetsEntryReplacer *MakeAssetModifierFromFile(uint32_t fileID, QWORD pathID, int classID, uint16_t monoScriptIndex, + FILE *pFile, QWORD offs, QWORD size, size_t copyBufferLen, bool freeFile) +{ + return new AssetModifierFromFile(fileID, pathID, classID, monoScriptIndex, pFile, offs, size, copyBufferLen, freeFile); +} +ASSETSTOOLS_API AssetsDependenciesReplacer *MakeAssetsDependenciesReplacer(uint32_t fileID, std::vector dependencies) +{ + return new AssetsDependenciesReplacerImpl(fileID, std::move(dependencies)); +} +ASSETSTOOLS_API void FreeAssetsReplacer(AssetsReplacer *pReplacer) +{ + delete pReplacer; +} \ No newline at end of file diff --git a/AssetsTools/AssetsReplacer.h b/AssetsTools/AssetsReplacer.h new file mode 100644 index 0000000..289e067 --- /dev/null +++ b/AssetsTools/AssetsReplacer.h @@ -0,0 +1,105 @@ +#pragma once +#include "defines.h" +#include "AssetsFileFormat.h" +#include "AssetsFileReader.h" +#include "ClassDatabaseFile.h" +#include "AssetsFileFormatTypes.h" +#include + +class GenericReplacer +{ + public: + virtual ~GenericReplacer() + #ifndef ASSETSTOOLS_EXPORTS + = 0 + #endif + ; +}; + +enum AssetsReplacementType +{ + AssetsReplacement_AddOrModify, //class AssetsEntryReplacer + AssetsReplacement_Remove, //class AssetsEntryReplacer + AssetsReplacement_Dependencies, //class AssetsDependenciesReplacer +}; +class AssetsReplacer : public GenericReplacer +{ + public: + virtual AssetsReplacementType GetType() const = 0; + + //Base properties of the asset this replacer refers to. + virtual uint32_t GetFileID() const = 0; + + //Outputs a binary representation of this replacer. The read counterpart of this function is ReadAssetsReplacer. + //Always writes 0 for the file id. + //pos : Absolute writer file position to write the data. + //Returns the new absolute writer file position. + virtual QWORD WriteReplacer(QWORD pos, IAssetsWriter *pWriter) = 0; +}; +class AssetsEntryReplacer : public AssetsReplacer +{ + public: + //Base properties of the asset this replacer refers to. + virtual QWORD GetPathID() const = 0; + virtual int GetClassID() const = 0; + virtual uint16_t GetMonoScriptID() const = 0; + + virtual void SetMonoScriptID(uint16_t scriptID) = 0; + + //For add and modify + + //Returns false if the replacer has no properties hash (always the case for removers in practice). + //The properties hash is for the full type, also for MonoBehaviours. + virtual bool GetPropertiesHash(Hash128 &propertiesHash) = 0; + virtual bool SetPropertiesHash(const Hash128 &propertiesHash) = 0; + //Returns false if the replacer has no script hash (always the case for removers and non-MonoBehaviour types in practice). + //ScriptID is the hash for the specific MonoBehaviour type except the MonoBehaviour header. + virtual bool GetScriptIDHash(Hash128 &scriptIDHash) = 0; + virtual bool SetScriptIDHash(const Hash128 &scriptIDHash) = 0; + + virtual bool GetTypeInfo(std::shared_ptr &pFile, class ClassDatabaseType *&pType) = 0; + virtual bool SetTypeInfo(std::shared_ptr pFile, class ClassDatabaseType *pType) = 0; + + //Retrieves the list of preload dependencies of this replacer; Do not reuse the preload list pointer after calling Set/Add. + virtual bool GetPreloadDependencies(const struct AssetPPtr *&pPreloadList, size_t &preloadListSize) = 0; + //Overwrites this replacers' list of preload dependencies. + virtual bool SetPreloadDependencies(const struct AssetPPtr *pPreloadList, size_t preloadListSize) = 0; + //Adds a new preload dependency. Does not check for duplicates. + virtual bool AddPreloadDependency(const struct AssetPPtr &dependency) = 0; + + //Retrieves the data size of this replacer. + virtual QWORD GetSize() const = 0; + //Outputs the data of this replacer into a writer. + //pos : Absolute writer file position to write the data. + //Returns the new absolute writer file position. + virtual QWORD Write(QWORD pos, IAssetsWriter *pWriter) = 0; +}; +class AssetsDependenciesReplacer : public AssetsReplacer +{ + public: + virtual const std::vector& GetDependencies() const = 0; +}; + +ASSETSTOOLS_API AssetsReplacer *ReadAssetsReplacer(QWORD &pos, IAssetsReader *pReader, bool prefReplacerInMemory = false); +ASSETSTOOLS_API AssetsReplacer *ReadAssetsReplacer(QWORD &pos, std::shared_ptr pReader, bool prefReplacerInMemory = false); + +ASSETSTOOLS_API AssetsEntryReplacer *MakeAssetRemover(uint32_t fileID, QWORD pathID, int classID, uint16_t monoScriptIndex = 0xFFFF); + +ASSETSTOOLS_API AssetsEntryReplacer *MakeAssetModifierFromReader( + uint32_t fileID, QWORD pathID, int classID, uint16_t monoScriptIndex, + IAssetsReader *pReader, QWORD size, QWORD readerPos=0, + size_t copyBufferLen=0, bool freeReader=false); + +ASSETSTOOLS_API AssetsEntryReplacer *MakeAssetModifierFromMemory( + uint32_t fileID, QWORD pathID, int classID, uint16_t monoScriptIndex, + void *buffer, size_t size, cbFreeMemoryResource freeResourceCallback); + +ASSETSTOOLS_API AssetsEntryReplacer *MakeAssetModifierFromFile( + uint32_t fileID, QWORD pathID, int classID, uint16_t monoScriptIndex, + FILE *pFile, QWORD offs, QWORD size, size_t copyBufferLen=0, bool freeFile=true); + +ASSETSTOOLS_API AssetsDependenciesReplacer *MakeAssetsDependenciesReplacer( + uint32_t fileID, std::vector dependencies); + +//Note: Equivalent to 'delete pReplacer'. +ASSETSTOOLS_API void FreeAssetsReplacer(AssetsReplacer *pReplacer); \ No newline at end of file diff --git a/AssetsTools/BundleReplacer.cpp b/AssetsTools/BundleReplacer.cpp new file mode 100644 index 0000000..ee3e55f --- /dev/null +++ b/AssetsTools/BundleReplacer.cpp @@ -0,0 +1,1210 @@ +#include "stdafx.h" +#include "../AssetsTools/BundleReplacer.h" +#include "../AssetsTools/AssetsFileTable.h" +#include "InternalBundleReplacer.h" +#include + +//class BundleEntryRemover : public BundleReplacer +//{ + BundleEntryRemover::BundleEntryRemover(const char *name, unsigned int bundleListIndex) + { + this->bundleListIndex = bundleListIndex; + if (name) + { + size_t nameLen = strlen(name); + originalEntryName = new char[nameLen + 1]; + memcpy(originalEntryName, name, nameLen+1); + } + else + originalEntryName = NULL; + } + BundleReplacementType BundleEntryRemover::GetType() { return BundleReplacement_Remove; } + BundleEntryRemover::~BundleEntryRemover() + { + if (originalEntryName) + delete[] originalEntryName; + } + + unsigned int BundleEntryRemover::GetBundleListIndex() { return bundleListIndex; } + + const char *BundleEntryRemover::GetOriginalEntryName() { return originalEntryName; } + const char *BundleEntryRemover::GetEntryName() { return originalEntryName; } + + QWORD BundleEntryRemover::GetSize() { return 0; } + + bool BundleEntryRemover::Init(AssetBundleFile *pBundleFile, + IAssetsReader *pEntryReader, + QWORD entryPos, QWORD entrySize, + ClassDatabaseFile *typeMeta){return true;} + void BundleEntryRemover::Uninit(){} + + QWORD BundleEntryRemover::Write(QWORD pos, IAssetsWriter *pWriter) {return pos;} + QWORD BundleEntryRemover::WriteReplacer(QWORD pos, IAssetsWriter *pWriter) + { + uint16_t replacerType = BundleReplacer_BundleEntryRemover; + pos += pWriter->Write(pos, 2, &replacerType); + uint8_t fileVersion = 1; + pos += pWriter->Write(pos, 1, &fileVersion); + + uint8_t hasOriginalName = (this->originalEntryName != nullptr) ? 1 : 0; + pos += pWriter->Write(pos, 1, &hasOriginalName); + if (hasOriginalName) + { + uint16_t originalNameLen = (uint16_t)strlen(this->originalEntryName); + pos += pWriter->Write(pos, 2, &originalNameLen); + pos += pWriter->Write(pos, originalNameLen, originalEntryName); + } + return pos; + } + + bool BundleEntryRemover::HasSerializedData() { return false; } + + bool BundleEntryRemover::RequiresEntryReader() { return false; } +//}; + +//class BundleEntryRenamer : public BundleReplacer +//{ + BundleEntryRenamer::BundleEntryRenamer(const char *oldName, const char *newName, unsigned int bundleListIndex, bool hasSerializedData) + { + this->bundleListIndex = bundleListIndex; + if (oldName) + { + size_t nameLen = strlen(oldName); + originalEntryName = new char[nameLen + 1]; + memcpy(originalEntryName, oldName, nameLen+1); + } + else + originalEntryName = NULL; + if (newName) + { + size_t nameLen = strlen(newName); + newEntryName = new char[nameLen + 1]; + memcpy(newEntryName, newName, nameLen+1); + } + else + newEntryName = originalEntryName; + this->hasSerializedData = hasSerializedData; + } + BundleReplacementType BundleEntryRenamer::GetType() { return BundleReplacement_Rename; } + BundleEntryRenamer::~BundleEntryRenamer() + { + if (newEntryName && (newEntryName != originalEntryName)) + delete[] newEntryName; + if (originalEntryName) + delete[] originalEntryName; + } + + unsigned int BundleEntryRenamer::GetBundleListIndex() { return bundleListIndex; } + + const char *BundleEntryRenamer::GetOriginalEntryName() { return originalEntryName; } + const char *BundleEntryRenamer::GetEntryName() { return newEntryName; } + + QWORD BundleEntryRenamer::GetSize() { return (QWORD)-1; } + + bool BundleEntryRenamer::Init(AssetBundleFile *pBundleFile, + IAssetsReader *pEntryReader, + QWORD entryPos, QWORD entrySize, + ClassDatabaseFile *typeMeta){return true;} + void BundleEntryRenamer::Uninit(){} + + QWORD BundleEntryRenamer::Write(QWORD pos, IAssetsWriter *pWriter) {return pos;} + QWORD BundleEntryRenamer::WriteReplacer(QWORD pos, IAssetsWriter *pWriter) + { + uint16_t replacerType = BundleReplacer_BundleEntryRenamer; + pos += pWriter->Write(pos, 2, &replacerType); + uint8_t fileVersion = 1; + pos += pWriter->Write(pos, 1, &fileVersion); + + uint8_t hasOriginalName = (this->originalEntryName != nullptr) ? 1 : 0; + pos += pWriter->Write(pos, 1, &hasOriginalName); + if (hasOriginalName) + { + uint16_t originalNameLen = (uint16_t)strlen(this->originalEntryName); + pos += pWriter->Write(pos, 2, &originalNameLen); + pos += pWriter->Write(pos, originalNameLen, originalEntryName); + } + + uint8_t hasNewName = (this->newEntryName != nullptr) ? 1 : 0; + pos += pWriter->Write(pos, 1, &hasNewName); + if (hasNewName) + { + uint16_t newNameLen = (uint16_t)strlen(newEntryName); + pos += pWriter->Write(pos, 2, &newNameLen); + pos += pWriter->Write(pos, newNameLen, newEntryName); + } + + uint8_t temp = hasSerializedData ? 1 : 0; + pos += pWriter->Write(pos, 1, &temp); + return pos; + } + + bool BundleEntryRenamer::HasSerializedData() { return hasSerializedData; } + + bool BundleEntryRenamer::RequiresEntryReader() { return false; } +//}; + +//class BundleEntryModifier : public BundleEntryRenamer +//{ + BundleEntryModifier::BundleEntryModifier(const char *oldName, const char *newName, unsigned int bundleListIndex, bool hasSerializedData, + std::shared_ptr pReader, QWORD size, QWORD readerPos, + size_t copyBufferLen) + : BundleEntryRenamer(oldName, newName, bundleListIndex, hasSerializedData) + { + this->pReader = std::move(pReader); + this->size = size; + this->readerPos = readerPos; + this->copyBufferLen = copyBufferLen; + } + BundleReplacementType BundleEntryModifier::GetType() { return BundleReplacement_AddOrModify; } + BundleEntryModifier::~BundleEntryModifier() + {} + + QWORD BundleEntryModifier::GetSize() { return size; } + + bool BundleEntryModifier::Init(AssetBundleFile *pBundleFile, + IAssetsReader *pEntryReader, + QWORD entryPos, QWORD entrySize, + ClassDatabaseFile *typeMeta){return true;} + void BundleEntryModifier::Uninit(){} + + QWORD BundleEntryModifier::Write(QWORD writerPos, IAssetsWriter *pWriter) + { + QWORD curReaderPos = readerPos; + uint8_t stackCopyBuffer[1024]; size_t actualBufferLen = copyBufferLen; + void *pCopyBuffer = NULL; + if (copyBufferLen) + pCopyBuffer = malloc(copyBufferLen); + if (!pCopyBuffer) + { + pCopyBuffer = stackCopyBuffer; + actualBufferLen = 1024; + } + + QWORD remainingSize = size; + while (remainingSize) + { + size_t curSize = (remainingSize > actualBufferLen) ? actualBufferLen : remainingSize; + QWORD len = pReader->Read(curReaderPos, curSize, pCopyBuffer); + curReaderPos += len; + writerPos += pWriter->Write(writerPos, len, pCopyBuffer); + remainingSize -= len; + if (!len) + break; //read error + } + + if (pCopyBuffer != stackCopyBuffer) + free(pCopyBuffer); + return writerPos; + } + QWORD BundleEntryModifier::WriteReplacer(QWORD pos, IAssetsWriter *pWriter) + { + uint16_t replacerType = BundleReplacer_BundleEntryModifier; + pos += pWriter->Write(pos, 2, &replacerType); + uint8_t fileVersion = 1; + pos += pWriter->Write(pos, 1, &fileVersion); + + uint8_t hasOriginalName = (this->originalEntryName != nullptr) ? 1 : 0; + pos += pWriter->Write(pos, 1, &hasOriginalName); + if (hasOriginalName) + { + uint16_t originalNameLen = (uint16_t)strlen(this->originalEntryName); + pos += pWriter->Write(pos, 2, &originalNameLen); + pos += pWriter->Write(pos, originalNameLen, originalEntryName); + } + + uint8_t hasNewName = (this->newEntryName != nullptr) ? 1 : 0; + pos += pWriter->Write(pos, 1, &hasNewName); + if (hasNewName) + { + uint16_t newNameLen = (uint16_t)strlen(newEntryName); + pos += pWriter->Write(pos, 2, &newNameLen); + pos += pWriter->Write(pos, newNameLen, newEntryName); + } + + uint8_t temp = hasSerializedData ? 1 : 0; + pos += pWriter->Write(pos, 1, &temp); + + pos += pWriter->Write(pos, 8, &size); + pos = Write(pos, pWriter); + return pos; + } +//}; + +//class BundleEntryModifierFromMem : public BundleEntryRenamer +//{ + BundleEntryModifierFromMem::BundleEntryModifierFromMem(const char *oldName, const char *newName, unsigned int bundleListIndex, bool hasSerializedData, + void *pMem, size_t size, cbFreeMemoryResource freeResourceCallback) + : BundleEntryRenamer(oldName, newName, bundleListIndex, hasSerializedData) + { + this->pMem = pMem; + this->size = size; + this->freeResourceCallback = freeResourceCallback; + } + BundleReplacementType BundleEntryModifierFromMem::GetType() { return BundleReplacement_AddOrModify; } + BundleEntryModifierFromMem::~BundleEntryModifierFromMem() + { + if (pMem && freeResourceCallback) + { + freeResourceCallback(pMem); + pMem = NULL; + freeResourceCallback = NULL; + size = 0; + } + } + + QWORD BundleEntryModifierFromMem::GetSize() { return size; } + + bool BundleEntryModifierFromMem::Init(AssetBundleFile *pBundleFile, + IAssetsReader *pEntryReader, + QWORD entryPos, QWORD entrySize, + ClassDatabaseFile *typeMeta){return true;} + void BundleEntryModifierFromMem::Uninit(){} + + QWORD BundleEntryModifierFromMem::Write(QWORD writerPos, IAssetsWriter *pWriter) + { + return writerPos + pWriter->Write(writerPos, size, pMem); + } + QWORD BundleEntryModifierFromMem::WriteReplacer(QWORD pos, IAssetsWriter *pWriter) + { + uint16_t replacerType = BundleReplacer_BundleEntryModifierFromMem; + pos += pWriter->Write(pos, 2, &replacerType); + uint8_t fileVersion = 1; + pos += pWriter->Write(pos, 1, &fileVersion); + + uint8_t hasOriginalName = (this->originalEntryName != nullptr) ? 1 : 0; + pos += pWriter->Write(pos, 1, &hasOriginalName); + if (hasOriginalName) + { + uint16_t originalNameLen = (uint16_t)strlen(this->originalEntryName); + pos += pWriter->Write(pos, 2, &originalNameLen); + pos += pWriter->Write(pos, originalNameLen, originalEntryName); + } + + uint8_t hasNewName = (this->newEntryName != nullptr) ? 1 : 0; + pos += pWriter->Write(pos, 1, &hasNewName); + if (hasNewName) + { + uint16_t newNameLen = (uint16_t)strlen(newEntryName); + pos += pWriter->Write(pos, 2, &newNameLen); + pos += pWriter->Write(pos, newNameLen, newEntryName); + } + + uint8_t temp = hasSerializedData ? 1 : 0; + pos += pWriter->Write(pos, 1, &temp); + + QWORD qwSize = size; + pos += pWriter->Write(pos, 8, &qwSize); + pos = Write(pos, pWriter); + return pos; + } +//}; + +//class BundleEntryModifierFromAssets : public BundleEntryRenamer +//{ + BundleEntryModifierFromAssets::BundleEntryModifierFromAssets(const char *oldName, const char *newName, unsigned int bundleListIndex, + AssetsFile *pAssetsFile, AssetsReplacer **pReplacers, size_t replacerCount, uint32_t fileId) + : BundleEntryRenamer(oldName, newName, bundleListIndex, true) + { + this->pAssetsFile = pAssetsFile; + this->pReplacers.assign(&pReplacers[0], &pReplacers[replacerCount]); + this->fileId = fileId; + this->freeAssetsFile = false; + this->typeMeta = NULL; + } + BundleEntryModifierFromAssets::BundleEntryModifierFromAssets(const char *oldName, const char *newName, unsigned int bundleListIndex, + std::shared_ptr typeMeta, std::vector> pReplacers, uint32_t fileId) + : BundleEntryRenamer(oldName, newName, bundleListIndex, true) + { + this->pAssetsFile = nullptr; + this->typeMeta = typeMeta.get(); + this->typeMeta_shared = std::move(typeMeta); + this->pReplacers.resize(pReplacers.size()); + for (size_t i = 0; i < pReplacers.size(); i++) + this->pReplacers[i] = pReplacers[i].get(); + this->pReplacers_shared = std::move(pReplacers); + this->freeAssetsFile = false; + this->fileId = fileId; + } + BundleReplacementType BundleEntryModifierFromAssets::GetType() { return BundleReplacement_AddOrModify; } + BundleEntryModifierFromAssets::~BundleEntryModifierFromAssets() + { + Uninit(); + } + + //Relatively precise; returns all raw asset sizes, the size plus alignment of the file table, the header size and the dependencies list size. + //Does not count the TypeTree size and ignores dependency list replacers. + QWORD BundleEntryModifierFromAssets::GetSize() + { + if (!pAssetsFile) + return 0; + QWORD ret = pAssetsFile->header.GetSizeBytes(); + ret += 5; + AssetsFileDependencyList dependencyList = pAssetsFile->dependencies; + + AssetsFileTable fileTable = AssetsFileTable(pAssetsFile); + for (size_t i = 0; i < fileTable.assetFileInfoCount; i++) + { + QWORD pathId = fileTable.pAssetFileInfo[i].index; + QWORD fileSize = fileTable.pAssetFileInfo[i].curFileSize; + bool isModified = false, isDeleted = false; + for (size_t k = pReplacers.size(); k > 0; k--) + { + if (pReplacers[k-1]->GetType() == AssetsReplacement_Dependencies) + { + AssetsDependenciesReplacer* pDepReplacer = + reinterpret_cast(pReplacers[k - 1]); + const std::vector &dependencies = pDepReplacer->GetDependencies(); + dependencyList.pDependencies = const_cast(dependencies.data()); + dependencyList.dependencyCount = (uint32_t)dependencies.size(); + } + if (pReplacers[k-1]->GetType() != AssetsReplacement_AddOrModify + && pReplacers[k-1]->GetType() != AssetsReplacement_Remove) + continue; + AssetsEntryReplacer *pReplacer = reinterpret_cast(pReplacers[k-1]); + if (pathId == pReplacer->GetPathID()) + { + if (!isModified && (pReplacer->GetType() == AssetsReplacement_AddOrModify)) + { + fileSize = pReplacer->GetSize(); + isModified = true; + continue; + } + if (pReplacer->GetType() == AssetsReplacement_Remove) + { + isDeleted = true; + break; + } + } + } + if (!isDeleted) + ret += ((fileSize + 3) & (~3)) + ((fileTable.pAssetFileInfo[i].GetSize(pAssetsFile->header.format) + 3) & (~3)); + } + + for (uint32_t i = 0; i < dependencyList.dependencyCount; i++) + { + ret += strlen(dependencyList.pDependencies[i].assetPath) + 1; + ret += strlen(dependencyList.pDependencies[i].bufferedPath) + 1; + ret += 20; //GUID and type + } + + return ret; + } + + bool BundleEntryModifierFromAssets::Init(AssetBundleFile *pBundleFile, + IAssetsReader *pEntryReader, + QWORD entryPos, QWORD entrySize, + ClassDatabaseFile *typeMeta) + { + Uninit(); + if (pAssetsFile) + return true; + if (pEntryReader == nullptr) + return false; + pEntryReader = Create_AssetsReaderFromReaderRange(pEntryReader, entryPos, entrySize); + pAssetsFile = new AssetsFile(pEntryReader); + if (!pAssetsFile->VerifyAssetsFile()) + { + Free_AssetsReader(pEntryReader); + delete pAssetsFile; + pAssetsFile = NULL; + return false; + } + freeAssetsFile = true; + if (this->typeMeta == nullptr) + this->typeMeta = typeMeta; + return true; + } + void BundleEntryModifierFromAssets::Uninit() + { + if (pAssetsFile && freeAssetsFile) + { + Free_AssetsReader(pAssetsFile->pReader); + delete pAssetsFile; + pAssetsFile = NULL; + freeAssetsFile = false; + } + } + + QWORD BundleEntryModifierFromAssets::Write(QWORD writerPos, IAssetsWriter *pWriter) + { + return pAssetsFile->Write(pWriter, writerPos, pReplacers.data(), pReplacers.size(), fileId, typeMeta); + } + QWORD BundleEntryModifierFromAssets::WriteReplacer(QWORD pos, IAssetsWriter *pWriter) + { + uint16_t replacerType = BundleReplacer_BundleEntryModifierFromAssets; + pos += pWriter->Write(pos, 2, &replacerType); + uint8_t fileVersion = 1; + pos += pWriter->Write(pos, 1, &fileVersion); + + uint8_t hasOriginalName = (this->originalEntryName != nullptr) ? 1 : 0; + pos += pWriter->Write(pos, 1, &hasOriginalName); + if (hasOriginalName) + { + uint16_t originalNameLen = (uint16_t)strlen(this->originalEntryName); + pos += pWriter->Write(pos, 2, &originalNameLen); + pos += pWriter->Write(pos, originalNameLen, originalEntryName); + } + + uint8_t hasNewName = (this->newEntryName != nullptr) ? 1 : 0; + pos += pWriter->Write(pos, 1, &hasNewName); + if (hasNewName) + { + uint16_t newNameLen = (uint16_t)strlen(newEntryName); + pos += pWriter->Write(pos, 2, &newNameLen); + pos += pWriter->Write(pos, newNameLen, newEntryName); + } + + bool temp = true; + pos += pWriter->Write(pos, 1, &temp); + + QWORD ullReplacerCount = pReplacers.size(); + pos += pWriter->Write(pos, 8, &ullReplacerCount); + for (size_t i = 0; i < pReplacers.size(); i++) + { + pos = pReplacers[i]->WriteReplacer(pos, pWriter); + } + return pos; + } + AssetsReplacer **BundleEntryModifierFromAssets::GetReplacers(size_t &count) + { + count = pReplacers.size(); + return pReplacers.data(); + } + AssetsFile *BundleEntryModifierFromAssets::GetAssignedAssetsFile() + { + return pAssetsFile; + } + uint32_t BundleEntryModifierFromAssets::GetFileID() + { + return fileId; + } + + bool BundleEntryModifierFromAssets::RequiresEntryReader() { return true; } +//}; + +//class BundleEntryModifierFromBundle : public BundleEntryRenamer +//{ + BundleEntryModifierFromBundle::BundleEntryModifierFromBundle(const char *oldName, const char *newName, unsigned int bundleListIndex, + BundleReplacer **pReplacers, size_t replacerCount) + : BundleEntryRenamer(oldName, newName, bundleListIndex, true) + { + this->pBundleFile = nullptr; + this->pBundleReader = nullptr; + this->pReplacers.assign(&pReplacers[0], &pReplacers[replacerCount]); + this->freeBundleFile = false; + this->typeMeta = nullptr; + } + BundleEntryModifierFromBundle::BundleEntryModifierFromBundle(const char *oldName, const char *newName, unsigned int bundleListIndex, + std::vector> pReplacers) + : BundleEntryRenamer(oldName, newName, bundleListIndex, true) + { + this->pBundleFile = nullptr; + this->pBundleReader = nullptr; + this->pReplacers.resize(pReplacers.size()); + for (size_t i = 0; i < pReplacers.size(); i++) + this->pReplacers[i] = pReplacers[i].get(); + this->pReplacers_unique = std::move(pReplacers); + this->freeBundleFile = false; + this->typeMeta = NULL; + } + BundleReplacementType BundleEntryModifierFromBundle::GetType() { return BundleReplacement_AddOrModify; } + BundleEntryModifierFromBundle::~BundleEntryModifierFromBundle() + { + Uninit(); + } + + //Returns a rough estimate of the new file size. + QWORD BundleEntryModifierFromBundle::GetSize() + { + if (!pBundleFile) + return 0; + QWORD ret = 0; + uint32_t directorySize = 0; + if (pBundleFile->bundleHeader3.fileVersion == 3) + { + ret += 128 + pBundleFile->bundleHeader3.blockCount * 8; + directorySize = (pBundleFile->assetsLists3 != nullptr) ? pBundleFile->assetsLists3->count : 0; + } + else if (pBundleFile->bundleHeader6.fileVersion >= 6) + { + ret += pBundleFile->bundleHeader6.GetFileDataOffset(); + if ((pBundleFile->bundleHeader6.flags & 0x80) != 0) + ret += pBundleFile->bundleHeader6.decompressedSize; + directorySize = (pBundleFile->bundleInf6 != nullptr) ? pBundleFile->bundleInf6->directoryCount : 0; + } + std::vector newReplacers; + std::vector directoryToReplacerMapping(directorySize); + for (size_t i = 0; i < this->pReplacers.size(); i++) + { + unsigned int listIndex = this->pReplacers[i]->GetBundleListIndex(); + if (listIndex < directorySize) + directoryToReplacerMapping[listIndex] = this->pReplacers[i]; + else if (this->pReplacers[i]->GetType() == BundleReplacement_AddOrModify) + newReplacers.push_back(this->pReplacers[i]); + } + + for (uint32_t i = 0; i < directorySize; i++) + { + QWORD entryByteLen = 0; + const char *newEntryName = nullptr; + if (pBundleFile->bundleHeader3.fileVersion == 3) + { + entryByteLen = pBundleFile->assetsLists3->ppEntries[i]->length; + newEntryName = pBundleFile->assetsLists3->ppEntries[i]->name; + } + else if (pBundleFile->bundleHeader3.fileVersion >= 6) + { + entryByteLen = pBundleFile->bundleInf6->dirInf[i].decompressedSize; + newEntryName = pBundleFile->bundleInf6->dirInf[i].name; + } + if (directoryToReplacerMapping[i] == nullptr) + { } + else if (directoryToReplacerMapping[i]->GetType() == BundleReplacement_AddOrModify) + { + QWORD entrySizeVal = directoryToReplacerMapping[i]->GetSize(); + if (entrySizeVal != (QWORD)-1) + entryByteLen = entrySizeVal; + } + else if (directoryToReplacerMapping[i]->GetType() == BundleReplacement_Remove) + continue; + + if (directoryToReplacerMapping[i] != nullptr) + { + const char *replacerNewName = directoryToReplacerMapping[i]->GetEntryName(); + if (replacerNewName != nullptr) + newEntryName = replacerNewName; + } + ret += ((newEntryName == nullptr) ? 0 : strlen(newEntryName)) + 20; + ret += entryByteLen + 16; + } + for (size_t i = 0; i < newReplacers.size(); i++) + { + const char *newEntryName = newReplacers[i]->GetEntryName(); + QWORD entryByteLen = newReplacers[i]->GetSize(); + if (entryByteLen == (QWORD)-1) + entryByteLen = 0; + ret += ((newEntryName == nullptr) ? 0 : strlen(newEntryName)) + 20; + ret += entryByteLen + 16; + } + + return ret; + } + + bool BundleEntryModifierFromBundle::Init(AssetBundleFile *pBundleFile, + IAssetsReader *pEntryReader, + QWORD entryPos, QWORD entrySize, + ClassDatabaseFile *typeMeta) + { + Uninit(); + if (pEntryReader == nullptr) + return false; + this->pBundleReader = Create_AssetsReaderFromReaderRange(pEntryReader, entryPos, entrySize); + this->pBundleFile = new AssetBundleFile(); + if (!this->pBundleFile->Read(this->pBundleReader) || + (this->pBundleFile->bundleHeader3.fileVersion == 3 && this->pBundleFile->assetsLists3 == nullptr) || + (this->pBundleFile->bundleHeader6.fileVersion >= 6 && this->pBundleFile->bundleInf6 == nullptr) || + (this->pBundleFile->bundleHeader6.fileVersion < 6 && this->pBundleFile->bundleHeader3.fileVersion != 3)) + { + Free_AssetsReader(this->pBundleReader); + this->pBundleReader = nullptr; + delete this->pBundleFile; + this->pBundleFile = nullptr; + return false; + } + this->freeBundleFile = true; + this->typeMeta = typeMeta; + //Call Init for all child bundle replacers. + for (size_t i = 0; i < this->pReplacers.size(); i++) + { + this->pReplacers[i]->Uninit(); + unsigned int bundleListIdx = this->pReplacers[i]->GetBundleListIndex(); + uint64_t bundleEntryPos = 0, bundleEntryLen = 0; + if (bundleListIdx == (unsigned int)-1) + { + const char *entryName = this->pReplacers[i]->GetOriginalEntryName(); + if (entryName == nullptr) + continue; + if (this->pBundleFile->bundleHeader3.fileVersion == 3) + { + for (uint32_t iDirEntry = 0; iDirEntry < this->pBundleFile->assetsLists3->count; iDirEntry++) + { + if (!strcmp(entryName, this->pBundleFile->assetsLists3->ppEntries[iDirEntry]->name)) + { + bundleListIdx = iDirEntry; + break; + } + } + } + else if (this->pBundleFile->bundleHeader6.fileVersion >= 6) + { + for (uint32_t iDirEntry = 0; iDirEntry < this->pBundleFile->bundleInf6->directoryCount; iDirEntry++) + { + if (!strcmp(entryName, this->pBundleFile->bundleInf6->dirInf[iDirEntry].name)) + { + bundleListIdx = iDirEntry; + break; + } + } + } + if (bundleListIdx == (unsigned int)-1) + continue; + } + if (this->pBundleFile->bundleHeader3.fileVersion == 3) + { + bundleEntryPos = this->pBundleFile->assetsLists3->ppEntries[bundleListIdx]->GetAbsolutePos(pBundleFile); + bundleEntryLen = this->pBundleFile->assetsLists3->ppEntries[bundleListIdx]->length; + } + else if (this->pBundleFile->bundleHeader6.fileVersion >= 6) + { + bundleEntryPos = this->pBundleFile->bundleInf6->dirInf[bundleListIdx].GetAbsolutePos(pBundleFile); + bundleEntryLen = this->pBundleFile->bundleInf6->dirInf[bundleListIdx].decompressedSize; + } + else + { + assert(false); + continue; + } + this->pReplacers[i]->Init(this->pBundleFile, this->pBundleReader, bundleEntryPos, bundleEntryLen, typeMeta); + } + return true; + } + void BundleEntryModifierFromBundle::Uninit() + { + for (size_t i = 0; i < this->pReplacers.size(); i++) + this->pReplacers[i]->Uninit(); + if (this->pBundleFile && freeBundleFile) + { + Free_AssetsReader(this->pBundleReader); + delete this->pBundleFile; + freeBundleFile = false; + } + this->pBundleReader = nullptr; + this->pBundleFile = nullptr; + } + + QWORD BundleEntryModifierFromBundle::Write(QWORD writerPos, IAssetsWriter *pWriter) + { + IAssetsWriterToWriterOffset *pWriterWrapper = Create_AssetsWriterToWriterOffset(pWriter, writerPos); + pBundleFile->Write(this->pBundleReader, pWriterWrapper, this->pReplacers.data(), this->pReplacers.size(), nullptr, this->typeMeta); + QWORD ret = writerPos; + pWriterWrapper->Tell(ret); + Free_AssetsWriter(pWriterWrapper); + return ret + writerPos; + } + QWORD BundleEntryModifierFromBundle::WriteReplacer(QWORD pos, IAssetsWriter *pWriter) + { + uint16_t replacerType = BundleReplacer_BundleEntryModifierFromBundle; + pos += pWriter->Write(pos, 2, &replacerType); + uint8_t fileVersion = 1; + pos += pWriter->Write(pos, 1, &fileVersion); + + uint8_t hasOriginalName = (this->originalEntryName != nullptr) ? 1 : 0; + pos += pWriter->Write(pos, 1, &hasOriginalName); + if (hasOriginalName) + { + uint16_t originalNameLen = (uint16_t)strlen(this->originalEntryName); + pos += pWriter->Write(pos, 2, &originalNameLen); + pos += pWriter->Write(pos, originalNameLen, originalEntryName); + } + + uint8_t hasNewName = (this->newEntryName != nullptr) ? 1 : 0; + pos += pWriter->Write(pos, 1, &hasNewName); + if (hasNewName) + { + uint16_t newNameLen = (uint16_t)strlen(newEntryName); + pos += pWriter->Write(pos, 2, &newNameLen); + pos += pWriter->Write(pos, newNameLen, newEntryName); + } + + bool temp = true; + pos += pWriter->Write(pos, 1, &temp); + + QWORD ullReplacerCount = this->pReplacers.size(); + pos += pWriter->Write(pos, 8, &ullReplacerCount); + for (size_t i = 0; i < this->pReplacers.size(); i++) + { + pos = pReplacers[i]->WriteReplacer(pos, pWriter); + } + return pos; + } + BundleReplacer **BundleEntryModifierFromBundle::GetReplacers(size_t &count) + { + count = this->pReplacers.size(); + return this->pReplacers.data(); + } + + bool BundleEntryModifierFromBundle::RequiresEntryReader() { return true; } +//}; + +//class BundleEntryModifierByResources : public BundleEntryRenamer +//{ + BundleEntryModifierByResources::BundleEntryModifierByResources(const char* oldName, const char* newName, unsigned int bundleListIndex, + std::vector _resources, + size_t copyBufferLen) + : BundleEntryRenamer(oldName, newName, bundleListIndex, false) + { + this->resources = std::move(_resources); + this->copyBufferLen = copyBufferLen; + } + BundleReplacementType BundleEntryModifierByResources::GetType() { return BundleReplacement_AddOrModify; } + BundleEntryModifierByResources::~BundleEntryModifierByResources() + {} + + QWORD BundleEntryModifierByResources::GetSize() { return getSize(); } + + bool BundleEntryModifierByResources::Init(AssetBundleFile* pBundleFile, + IAssetsReader* pEntryReader, + QWORD entryPos, QWORD entrySize, + ClassDatabaseFile* typeMeta) + { + this->pEntryReader = pEntryReader; + this->entryPos = entryPos; + this->entrySize = entrySize; + return (pEntryReader != nullptr || !RequiresEntryReader()); + } + void BundleEntryModifierByResources::Uninit() + { + this->pEntryReader = nullptr; + } + bool BundleEntryModifierByResources::RequiresEntryReader() + { + for (size_t i = 0; i < resources.size(); ++i) + { + if (resources[i].fromOriginalFile) + return true; + } + return false; + } + + QWORD BundleEntryModifierByResources::Write(QWORD writerPos, IAssetsWriter* pWriter) + { + std::vector zeroBuffer; + QWORD writerPos_pre = writerPos; + for (size_t i = 0; i < resources.size(); ++i) + { + IAssetsReader* pReader = nullptr; + QWORD readerPos = 0, readerRange = 0; + if (resources[i].reader != nullptr) + { + pReader = resources[i].reader.get(); + readerPos = resources[i].inRangeBegin; + readerRange = resources[i].rangeSize; + } + else if (resources[i].fromOriginalFile) + { + pReader = pEntryReader; + readerPos = entryPos + resources[i].inRangeBegin; + readerRange = resources[i].rangeSize; + if (resources[i].inRangeBegin + readerRange > entrySize) + { + if (resources[i].inRangeBegin > entrySize) + readerRange = 0; + else + readerRange = entrySize - resources[i].inRangeBegin; + } + } + if (readerRange > 0 && pReader != nullptr) + { + BundleEntryModifier tmpCopier(nullptr, nullptr, (unsigned int)-1, false, + std::shared_ptr(pReader, [](IAssetsReader*) {}), + readerRange, readerPos, copyBufferLen); + writerPos = tmpCopier.Write(writerPos, pWriter); + } + QWORD remaining = (resources[i].outRangeBegin + resources[i].rangeSize) - (writerPos - writerPos_pre); + if (resources.size() > (i + 1) && resources[i + 1].outRangeBegin > (writerPos - writerPos_pre)) + { + remaining = resources[i + 1].outRangeBegin - (writerPos - writerPos_pre); + } + zeroBuffer.resize((size_t)std::min(copyBufferLen, remaining), 0); + while (remaining > 0) + { + size_t curCopyDepth = (size_t)std::min(copyBufferLen, remaining); + QWORD written = pWriter->Write(writerPos, curCopyDepth, zeroBuffer.data()); + if (written == 0) + return writerPos; + writerPos += written; + assert(written <= remaining); + remaining -= written; + } + } + return writerPos; + } + QWORD BundleEntryModifierByResources::WriteReplacer(QWORD pos, IAssetsWriter* pWriter) + { + uint16_t replacerType = BundleReplacer_BundleEntryModifierByResources; + pos += pWriter->Write(pos, 2, &replacerType); + uint8_t fileVersion = 1; + pos += pWriter->Write(pos, 1, &fileVersion); + + uint8_t hasOriginalName = (this->originalEntryName != nullptr) ? 1 : 0; + pos += pWriter->Write(pos, 1, &hasOriginalName); + if (hasOriginalName) + { + uint16_t originalNameLen = (uint16_t)strlen(this->originalEntryName); + pos += pWriter->Write(pos, 2, &originalNameLen); + pos += pWriter->Write(pos, originalNameLen, originalEntryName); + } + + uint8_t hasNewName = (this->newEntryName != nullptr) ? 1 : 0; + pos += pWriter->Write(pos, 1, &hasNewName); + if (hasNewName) + { + uint16_t newNameLen = (uint16_t)strlen(newEntryName); + pos += pWriter->Write(pos, 2, &newNameLen); + pos += pWriter->Write(pos, newNameLen, newEntryName); + } + + uint8_t temp = hasSerializedData ? 1 : 0; + pos += pWriter->Write(pos, 1, &temp); + + uint32_t numResources = (uint32_t)std::min(resources.size(), std::numeric_limits::max()); + pos += pWriter->Write(pos, 4, &numResources); + + uint32_t i = 0; + uint64_t curRangePos = 0; + for (auto resIt = resources.begin(); resIt != resources.end() && i < numResources; ++resIt, ++i) + { + assert(resIt->outRangeBegin == curRangePos); + pos += pWriter->Write(pos, 8, &resIt->rangeSize); + + uint8_t resourceFlags = + ((resIt->reader != nullptr) ? (1 << 0) : 0) + | ((resIt->fromOriginalFile) ? (1 << 1) : 0); + pos += pWriter->Write(pos, 1, &resourceFlags); + + if (resIt->reader != nullptr) + { + BundleEntryModifier tmpCopier(nullptr, nullptr, (unsigned int)-1, false, + resIt->reader, + resIt->rangeSize, resIt->inRangeBegin, copyBufferLen); + pos = tmpCopier.Write(pos, pWriter); + } + if (resIt->fromOriginalFile) + { + pos += pWriter->Write(pos, 8, &resIt->inRangeBegin); + } + + curRangePos += resIt->rangeSize; + } + return pos; + } +//}; + +void _cdecl FreeMemoryResource_ReadBundleReplacer(void *pResource) +{ + if (pResource) + free(pResource); +} +//On allocation error for the names, it returns a valid replacer with NULL names (or doesn't return because the new operator fails) +static BundleReplacer *ReadBundleReplacer(QWORD &pos, IAssetsReader *pReader, std::shared_ptr ref_pReader, bool prefReplacerInMemory) +{ + uint16_t replacerType = 0xFFFF; + pos += pReader->Read(pos, 2, &replacerType); + if (replacerType >= BundleReplacer_MAX) + return NULL; + uint8_t replacerFileVersion = 0; + pos += pReader->Read(pos, 1, &replacerFileVersion); + uint8_t hasOriginalName = 1; + if (replacerFileVersion >= 1) + pos += pReader->Read(pos, 1, &hasOriginalName); + uint16_t originalNameLen = 0; + std::unique_ptr originalName; + if (hasOriginalName) + { + pos += pReader->Read(pos, 2, &originalNameLen); + originalName.reset(new char[(uint32_t)originalNameLen + 1]); + pos += pReader->Read(pos, originalNameLen, originalName.get()); + originalName[originalNameLen] = 0; + } + std::unique_ptr newName; + uint16_t newNameLen = 0; + bool hasSerializedData = false; + if (replacerType != BundleReplacer_BundleEntryRemover) + { + uint8_t hasNewName = 1; + if (replacerFileVersion >= 1) + pos += pReader->Read(pos, 1, &hasNewName); + if (hasNewName) + { + pos += pReader->Read(pos, 2, &newNameLen); + newName.reset(new char[(uint32_t)newNameLen + 1]); + pos += pReader->Read(pos, newNameLen, newName.get()); + newName[newNameLen] = 0; + } + + pos += pReader->Read(pos, 1, &hasSerializedData); + } + + switch (replacerType) + { + case BundleReplacer_BundleEntryRemover: + { + if (replacerFileVersion > 1) + return NULL; + BundleReplacer *ret = new BundleEntryRemover(originalName.get(), (unsigned int)-1); + return ret; + } + case BundleReplacer_BundleEntryRenamer: + { + if (replacerFileVersion > 1) + return NULL; + BundleReplacer *ret = new BundleEntryRenamer(originalName.get(), newName.get(), (unsigned int)-1, hasSerializedData); + return ret; + } + case BundleReplacer_BundleEntryModifier: + case BundleReplacer_BundleEntryModifierFromMem: + { + if (replacerFileVersion > 1) + return NULL; + QWORD size = 0; + pos += pReader->Read(pos, 8, &size); + BundleReplacer *ret = NULL; + if (prefReplacerInMemory) + { + void *pMem = malloc(size); + if (pMem) + { + QWORD actualRead = pReader->Read(pos, size, pMem); + memset(&((uint8_t*)pMem)[actualRead], 0, size - actualRead); + ret = new BundleEntryModifierFromMem(originalName.get(), newName.get(), (unsigned int)-1, hasSerializedData, pMem, size, + FreeMemoryResource_ReadBundleReplacer); + } + } + if (!ret) + { + struct { + void operator()(IAssetsReader*){} + } nullDeleter; + std::shared_ptr inner_pReader = ref_pReader; + if (inner_pReader == nullptr) + inner_pReader = std::shared_ptr(pReader, nullDeleter); + else + inner_pReader = std::shared_ptr(inner_pReader, pReader); + ret = new BundleEntryModifier(originalName.get(), newName.get(), (unsigned int)-1, hasSerializedData, inner_pReader, size, pos, 0); + } + pos += size; + return ret; + } + case BundleReplacer_BundleEntryModifierFromAssets: + { + if (replacerFileVersion > 1) + return NULL; + QWORD ullReplacerCount = 0; + pos += pReader->Read(pos, 8, &ullReplacerCount); + size_t replacerCount = (size_t)ullReplacerCount; + std::vector> pReplacers(replacerCount); + for (size_t i = 0; i < replacerCount; i++) + { + if (ref_pReader) + pReplacers[i].reset(ReadAssetsReplacer(pos, ref_pReader)); + else + pReplacers[i].reset(ReadAssetsReplacer(pos, pReader)); + } + BundleReplacer *ret = + new BundleEntryModifierFromAssets(originalName.get(), newName.get(), (unsigned int)-1, NULL, std::move(pReplacers), (uint32_t)-1); + return ret; + } + case BundleReplacer_BundleEntryModifierFromBundle: + { + if (replacerFileVersion > 1) + return NULL; + QWORD ullReplacerCount = 0; + pos += pReader->Read(pos, 8, &ullReplacerCount); + size_t replacerCount = (size_t)ullReplacerCount; + std::vector> pReplacers(replacerCount); + for (size_t i = 0; i < replacerCount; i++) + { + if (ref_pReader) + pReplacers[i].reset(ReadBundleReplacer(pos, ref_pReader)); + else + pReplacers[i].reset(ReadBundleReplacer(pos, pReader)); + } + BundleReplacer *ret = + new BundleEntryModifierFromBundle(originalName.get(), newName.get(), (unsigned int)-1, std::move(pReplacers)); + return ret; + } + case BundleReplacer_BundleEntryModifierByResources: + { + if (replacerFileVersion > 1) + return NULL; + uint32_t resourceCount = 0; + pos += pReader->Read(pos, 4, &resourceCount); + std::vector resources; + resources.resize(resourceCount); + uint64_t curPos = 0; + for (uint32_t i = 0; i < resourceCount; ++i) + { + resources[i].outRangeBegin = curPos; + pos += pReader->Read(pos, 8, &resources[i].rangeSize); + resources[i].fromOriginalFile = false; + uint8_t resourceFlags = 0; + pos += pReader->Read(pos, 1, &resourceFlags); + if (resourceFlags & (1 << 0)) + { + //Resource is defined by following data. + if (prefReplacerInMemory) + { + void* pMem = malloc(resources[i].rangeSize); + if (pMem) + { + QWORD actualRead = pReader->Read(pos, resources[i].rangeSize, pMem); + memset(&((uint8_t*)pMem)[actualRead], 0, resources[i].rangeSize - actualRead); + resources[i].reader = std::shared_ptr( + Create_AssetsReaderFromMemory(pMem, resources[i].rangeSize, false, [](void* buf) {if (buf)free(buf); })); + resources[i].inRangeBegin = 0; + } + } + if (!resources[i].reader) + { + struct { + void operator()(IAssetsReader*) {} + } nullDeleter; + std::shared_ptr inner_pReader = ref_pReader; + if (inner_pReader == nullptr) + resources[i].reader = std::shared_ptr(pReader, nullDeleter); + else + resources[i].reader = std::shared_ptr(inner_pReader, pReader); + resources[i].inRangeBegin = pos; + } + pos += resources[i].rangeSize; + } + if (resourceFlags & (1 << 1)) + { + //Resource starts at an offset of its reader + // (only used if based on some resource in the original file). + uint64_t inRangeBegin = 0; + pos += pReader->Read(pos, 8, &inRangeBegin); + if (resources[i].reader == nullptr) + { + resources[i].inRangeBegin = inRangeBegin; + resources[i].fromOriginalFile = true; + } + } + curPos += resources[i].rangeSize; + } + BundleReplacer *ret = + new BundleEntryModifierByResources(originalName.get(), newName.get(), (unsigned int)-1, std::move(resources), 0); + return ret; + } + } + return NULL; +} +ASSETSTOOLS_API BundleReplacer *ReadBundleReplacer(QWORD &pos, IAssetsReader *pReader, bool prefReplacerInMemory) +{ + return ReadBundleReplacer(pos, pReader, nullptr, prefReplacerInMemory); +} +ASSETSTOOLS_API BundleReplacer *ReadBundleReplacer(QWORD &pos, std::shared_ptr pReader, bool prefReplacerInMemory) +{ + IAssetsReader *_pReader = pReader.get(); + return ReadBundleReplacer(pos, _pReader, std::move(pReader), prefReplacerInMemory); +} +ASSETSTOOLS_API BundleReplacer *MakeBundleEntryRemover(const char *name, + unsigned int bundleListIndex) +{ + return new BundleEntryRemover(name, bundleListIndex); +} +ASSETSTOOLS_API BundleReplacer *MakeBundleEntryRenamer(const char *oldName, const char *newName, bool hasSerializedData, + unsigned int bundleListIndex) +{ + return new BundleEntryRenamer(oldName, newName, bundleListIndex, hasSerializedData); +} +ASSETSTOOLS_API BundleReplacer *MakeBundleEntryModifier(const char *oldName, const char *newName, bool hasSerializedData, + IAssetsReader *pReader, cbFreeReaderResource freeReaderCallback, QWORD size, QWORD readerPos, + size_t copyBufferLen, + unsigned int bundleListIndex) +{ + struct { + void operator()(IAssetsReader*){} + } nullDeleter; + std::shared_ptr pReader_shared; + if (freeReaderCallback == nullptr) + pReader_shared = std::shared_ptr(pReader, nullDeleter); + else + pReader_shared = std::shared_ptr(pReader, freeReaderCallback); + return new BundleEntryModifier(oldName, newName, bundleListIndex, hasSerializedData, std::move(pReader_shared), size, readerPos, copyBufferLen); +} +ASSETSTOOLS_API BundleReplacer *MakeBundleEntryModifier(const char *oldName, const char *newName, bool hasSerializedData, + std::shared_ptr pReader, QWORD size, QWORD readerPos, + size_t copyBufferLen, + unsigned int bundleListIndex) +{ + return new BundleEntryModifier(oldName, newName, bundleListIndex, hasSerializedData, std::move(pReader), size, readerPos, copyBufferLen); +} +ASSETSTOOLS_API BundleReplacer *MakeBundleEntryModifierFromMem(const char *oldName, const char *newName, bool hasSerializedData, + void *pMem, size_t size, + unsigned int bundleListIndex, cbFreeMemoryResource freeResourceCallback) +{ + return new BundleEntryModifierFromMem(oldName, newName, bundleListIndex, hasSerializedData, + pMem, size, freeResourceCallback); +} +ASSETSTOOLS_API BundleReplacer *MakeBundleEntryModifierFromAssets(const char *oldName, const char *newName, + AssetsFile *pAssetsFile, AssetsReplacer **pReplacers, size_t replacerCount, uint32_t fileId, + unsigned int bundleListIndex) +{ + return new BundleEntryModifierFromAssets(oldName, newName, bundleListIndex, + pAssetsFile, pReplacers, replacerCount, fileId); +} +ASSETSTOOLS_API std::unique_ptr MakeBundleEntryModifierFromAssets(const char *oldName, const char *newName, + std::shared_ptr typeMeta, std::vector> pReplacers, uint32_t fileId, + unsigned int bundleListIndex) +{ + return std::unique_ptr(new BundleEntryModifierFromAssets(oldName, newName, bundleListIndex, + std::move(typeMeta), std::move(pReplacers), fileId)); +} +ASSETSTOOLS_API BundleReplacer *MakeBundleEntryModifierFromBundle(const char *oldName, const char *newName, + BundleReplacer **pReplacers, size_t replacerCount, + unsigned int bundleListIndex) +{ + return new BundleEntryModifierFromBundle(oldName, newName, bundleListIndex, pReplacers, replacerCount); +} +ASSETSTOOLS_API std::unique_ptr MakeBundleEntryModifierFromBundle(const char *oldName, const char *newName, + std::vector> pReplacers, + unsigned int bundleListIndex) +{ + return std::unique_ptr( + new BundleEntryModifierFromBundle(oldName, newName, bundleListIndex, std::move(pReplacers))); +} +ASSETSTOOLS_API std::unique_ptr MakeBundleEntryModifierByResources(const char* oldName, const char* newName, + std::vector resources, size_t copyBufferLen, + unsigned int bundleListIndex) +{ + return std::unique_ptr( + new BundleEntryModifierByResources(oldName, newName, bundleListIndex, std::move(resources), copyBufferLen)); +} +ASSETSTOOLS_API void FreeBundleReplacer(BundleReplacer *pReplacer) +{ + delete pReplacer; +} + +//Tries to create a reader from a BundleReplacer. +//Works only for replacers created through MakeBundleEntryModifier or MakeBundleEntryModifierFromMem. +//For other kinds of replacers, nullptr will be returned. +//-> For MakeBundleEntryModifier, the internal reader will be reused. +//-> For MakeBundleEntryModifierFromMem, the internal buffer will be reused and the returned shared_ptr will also keep a BundleReplacer reference. +ASSETSTOOLS_API std::shared_ptr MakeReaderFromBundleEntryModifier(std::shared_ptr pReplacer) +{ + if (BundleEntryModifierFromMem *pEntryModifier = dynamic_cast(pReplacer.get())) + { + struct { + std::shared_ptr pReplacer; + void operator()(IAssetsReader* pReader) { Free_AssetsReader(pReader); pReplacer.reset(); } + } deleter; + deleter.pReplacer = pReplacer; + return std::shared_ptr( + Create_AssetsReaderFromMemory(pEntryModifier->pMem, pEntryModifier->size, false), + deleter); + } + else if (BundleEntryModifier *pEntryModifier = dynamic_cast(pReplacer.get())) + { + struct { + std::shared_ptr pSourceReader; + void operator()(IAssetsReader* pReader) { Free_AssetsReader(pReader); pSourceReader.reset(); } + } deleter; + deleter.pSourceReader = pEntryModifier->pReader; + return std::shared_ptr( + Create_AssetsReaderFromReaderRange(pEntryModifier->pReader.get(), pEntryModifier->readerPos, pEntryModifier->size, true), + deleter); + } + return nullptr; +} + diff --git a/AssetsTools/BundleReplacer.h b/AssetsTools/BundleReplacer.h new file mode 100644 index 0000000..4b32d47 --- /dev/null +++ b/AssetsTools/BundleReplacer.h @@ -0,0 +1,106 @@ +#pragma once +#include "defines.h" +#include "AssetsFileFormat.h" +#include "AssetBundleFileFormat.h" +#include "AssetsFileReader.h" +#include + +enum BundleReplacementType +{ + BundleReplacement_AddOrModify, + BundleReplacement_Rename, + BundleReplacement_Remove +}; +class BundleReplacer : public GenericReplacer +{ + public: + virtual BundleReplacementType GetType() = 0; + + virtual unsigned int GetBundleListIndex() = 0; //use only if there are multiple entries with the name, is -1 if unknown + + //The name of the entry (before ANY rename) + virtual const char *GetOriginalEntryName() = 0; + virtual const char *GetEntryName() = 0; + //returns -1 if the original entry data should be used; + //DO NOT rely on the value returned : some modifiers can't easily calculate the target size, so 0 is returned + virtual QWORD GetSize() = 0; + + virtual bool Init(class AssetBundleFile *pBundleFile, + IAssetsReader *pEntryReader, + QWORD entryPos, QWORD entrySize, + ClassDatabaseFile *typeMeta = NULL) = 0; + virtual void Uninit() = 0; + + //should allow writes multiple times (although probably not used) + virtual QWORD Write(QWORD pos, IAssetsWriter *pWriter) = 0; + + //writes a binary representation of the replacer + virtual QWORD WriteReplacer(QWORD pos, IAssetsWriter *pWriter) = 0; + + //if true, this is an .assets file (at least I don't think there are any "serialized" files that aren't .assets) + virtual bool HasSerializedData() = 0; + + //if true, this replacer depends on the existing state as provided through Init(..). + virtual bool RequiresEntryReader() = 0; +}; + +//use NULL for newName to keep the old name + +ASSETSTOOLS_API BundleReplacer *ReadBundleReplacer(QWORD &pos, IAssetsReader *pEntryReader, bool prefReplacerInMemory = false); +ASSETSTOOLS_API BundleReplacer *ReadBundleReplacer(QWORD &pos, std::shared_ptr pEntryReader, bool prefReplacerInMemory = false); +ASSETSTOOLS_API BundleReplacer *MakeBundleEntryRemover(const char *name, unsigned int bundleListIndex = (unsigned int)-1); +ASSETSTOOLS_API BundleReplacer *MakeBundleEntryRenamer(const char *oldName, const char *newName, bool hasSerializedData, + unsigned int bundleListIndex = (unsigned int)-1); +ASSETSTOOLS_API BundleReplacer *MakeBundleEntryModifier(const char *oldName, const char *newName, bool hasSerializedData, + IAssetsReader *pEntryReader, cbFreeReaderResource freeReaderCallback, QWORD size, QWORD readerPos=0, + size_t copyBufferLen=0, + unsigned int bundleListIndex = (unsigned int)-1); +ASSETSTOOLS_API BundleReplacer *MakeBundleEntryModifier(const char *oldName, const char *newName, bool hasSerializedData, + std::shared_ptr pEntryReader, QWORD size, QWORD readerPos=0, + size_t copyBufferLen=0, + unsigned int bundleListIndex = (unsigned int)-1); +ASSETSTOOLS_API BundleReplacer *MakeBundleEntryModifierFromMem(const char *oldName, const char *newName, bool hasSerializedData, + void *pMem, size_t size, + unsigned int bundleListIndex = (unsigned int)-1, + cbFreeMemoryResource freeResourceCallback = NULL); +ASSETSTOOLS_API BundleReplacer *MakeBundleEntryModifierFromAssets(const char *oldName, const char *newName, + AssetsFile *pAssetsFile, AssetsReplacer **pReplacers, size_t replacerCount, uint32_t fileId, + unsigned int bundleListIndex = (unsigned int)-1); +ASSETSTOOLS_API std::unique_ptr MakeBundleEntryModifierFromAssets(const char *oldName, const char *newName, + std::shared_ptr typeMeta, std::vector> pReplacers, uint32_t fileId, + unsigned int bundleListIndex = (unsigned int)-1); +ASSETSTOOLS_API BundleReplacer *MakeBundleEntryModifierFromBundle(const char *oldName, const char *newName, + BundleReplacer **pReplacers, size_t replacerCount, + unsigned int bundleListIndex); +ASSETSTOOLS_API std::unique_ptr MakeBundleEntryModifierFromBundle(const char *oldName, const char *newName, + std::vector> pReplacers, unsigned int bundleListIndex); + + +struct ReplacedResourceDesc +{ + //Invariant: outRangeBegin must be equal to the previous data range end location, or 0 for the first range. + uint64_t outRangeBegin; + //Size of the data range (both in and out). + uint64_t rangeSize; + //Replacement reader. + //- non-null: Replacement data copied from reader. + //- nullptr, fromOriginalFile true: Data copied from original reader. + //- nullptr, fromOriginalFile false: Empty block (zero data). + std::shared_ptr reader; + //Start offset in the original file (i.e. fromOriginalFile set, reader nullptr). + uint64_t inRangeBegin; + //Set if this resource data range is copied directly from the source file (-> reader nullptr). + bool fromOriginalFile; +}; +ASSETSTOOLS_API std::unique_ptr MakeBundleEntryModifierByResources(const char* oldName, const char* newName, + std::vector resources, size_t copyBufferLen = 0, + unsigned int bundleListIndex = (unsigned int)-1); + +ASSETSTOOLS_API void FreeBundleReplacer(BundleReplacer *pReplacer); + +//Tries to create a reader from a BundleReplacer. +//Works only for replacers created through MakeBundleEntryModifier or MakeBundleEntryModifierFromMem. +//For other kinds of replacers, nullptr will be returned. +//-> For MakeBundleEntryModifier, the internal reader will be reused. +//-> For MakeBundleEntryModifierFromMem, the internal buffer will be reused and the returned shared_ptr will also keep a BundleReplacer reference. +ASSETSTOOLS_API std::shared_ptr MakeReaderFromBundleEntryModifier(std::shared_ptr pReplacer); diff --git a/AssetsTools/CMakeLists.txt b/AssetsTools/CMakeLists.txt new file mode 100644 index 0000000..89afcf4 --- /dev/null +++ b/AssetsTools/CMakeLists.txt @@ -0,0 +1,27 @@ +add_library (AssetsTools_Static STATIC AssetBundleFileFormat.cpp AssetBundleFileTable.cpp AssetsFileFormat.cpp AssetsFileReader.cpp AssetsFileTable.cpp AssetsReplacer.cpp AssetTypeClass.cpp BundleReplacer.cpp ClassDatabaseFile.cpp EngineVersion.cpp ResourceManagerFile.cpp) +target_include_directories (AssetsTools_Static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +target_compile_definitions(AssetsTools_Static PUBLIC ASSETSTOOLS_IMPORTSTATIC) +target_link_libraries(AssetsTools_Static PUBLIC libCompression libStringConverter) + +add_library (AssetsTools SHARED AssetBundleFileFormat.cpp AssetBundleFileTable.cpp AssetsFileFormat.cpp AssetsFileReader.cpp AssetsFileTable.cpp AssetsReplacer.cpp AssetTypeClass.cpp BundleReplacer.cpp ClassDatabaseFile.cpp EngineVersion.cpp ResourceManagerFile.cpp dllmain.cpp stdafx.cpp TextureFileFormat.cpp) +target_include_directories (AssetsTools PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_include_directories (AssetsTools PRIVATE + ${LIBSQUISH_INCLUDE_DIR} + ${CRNLIB_INCLUDE_DIR} +) + +target_compile_definitions(AssetsTools PRIVATE ASSETSTOOLS_EXPORTS) +set_target_properties(AssetsTools PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") +target_link_libraries(AssetsTools PUBLIC libStringConverter) +target_link_libraries(AssetsTools PRIVATE + libCompression + libStringConverter + CrnlibWrapLegacy CrnlibWrapUnity + squish + ispc_texcomp + texgenpack + ${CRNLIB_LIBRARIES} + astcenc-sse4.1-static +) + diff --git a/AssetsTools/ClassDatabaseFile.cpp b/AssetsTools/ClassDatabaseFile.cpp new file mode 100644 index 0000000..8e6c86c --- /dev/null +++ b/AssetsTools/ClassDatabaseFile.cpp @@ -0,0 +1,1622 @@ +#include "stdafx.h" +#include "../AssetsTools/ClassDatabaseFile.h" +#include "../AssetsTools/AssetsFileReader.h" +#include "../AssetsTools/AssetTypeClass.h" +#include "../libCompression/lz4.h" +#include "..\inc\LZMA\LzmaLib.h" +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#include +#pragma comment(lib, "Advapi32.lib") + +ASSETSTOOLS_API const char *ClassDatabaseFileString::GetString(ClassDatabaseFile *pFile) +{ + if (!fromStringTable) + return str.string; + if (str.stringTableOffset >= pFile->header.stringTableLen) + return ""; + return &pFile->stringTable[str.stringTableOffset]; +} +ASSETSTOOLS_API QWORD ClassDatabaseFileString::Read(IAssetsReader *pReader, QWORD filePos) +{ + fromStringTable = true; + pReader->Read(filePos, 4, &str.stringTableOffset); + return (filePos+4); +} +ASSETSTOOLS_API QWORD ClassDatabaseFileString::Write(IAssetsWriter *pWriter, QWORD filePos) +{ + pWriter->Write(filePos, 4, &str.stringTableOffset); + return (filePos+4); +} + +ASSETSTOOLS_API QWORD ClassDatabaseTypeField::Read(IAssetsReader *pReader, QWORD filePos, int version) +{ + filePos = typeName.Read(pReader, filePos); + filePos = fieldName.Read(pReader, filePos); + pReader->Read(filePos, 1, &depth); filePos++; + pReader->Read(filePos, 1, &isArray); filePos++; + pReader->Read(filePos, 4, &size); filePos+=4; + this->version = 1; + if (version < 1) + { + uint32_t index; + pReader->Read(filePos, 4, &index); filePos+=4; + if (index & 0x80000000) + { + pReader->Read(filePos, 2, &this->version); filePos+=2; + } + } + else if (version >= 3) + { + pReader->Read(filePos, 2, &this->version); filePos+=2; + } + pReader->Read(filePos, 4, &flags2); filePos+=4; + return filePos; +} +ASSETSTOOLS_API QWORD ClassDatabaseTypeField::Write(IAssetsWriter *pWriter, QWORD filePos, int version) +{ + filePos = typeName.Write(pWriter, filePos); + filePos = fieldName.Write(pWriter, filePos); + pWriter->Write(filePos, 1, &depth); filePos++; + pWriter->Write(filePos, 1, &isArray); filePos++; + pWriter->Write(filePos, 4, &size); filePos+=4; + //pWriter->Write(filePos, 4, &index); filePos+=4; + if (version >= 3) + { + pWriter->Write(filePos, 2, &this->version); filePos+=2; + } + pWriter->Write(filePos, 4, &flags2); filePos+=4; + return filePos; +} + +ASSETSTOOLS_API ClassDatabaseType::ClassDatabaseType() +{ + baseClass = -1; + classId = -1; + //ZeroMemory(&name, sizeof(ClassDatabaseFileString)); + this->fields = std::vector(); + this->fields.reserve(1); +} +ASSETSTOOLS_API ClassDatabaseType::ClassDatabaseType(const ClassDatabaseType& other) +{ + baseClass = other.baseClass; + classId = other.classId; + memcpy(&name, &other.name, sizeof(ClassDatabaseFileString)); + memcpy(&assemblyFileName, &other.assemblyFileName, sizeof(ClassDatabaseFileString)); + fields.reserve(other.fields.size()); + for (size_t i = 0; i < other.fields.size(); i++) + fields.push_back(ClassDatabaseTypeField(other.fields[i])); +} +class AssetTypeTemplateField; +int _RecursiveAddTemplateFieldToClassDatabase(std::vector &list, uint8_t depth, AssetTypeTemplateField *pTemplate) +{ + list.resize(list.size() + 1); + ClassDatabaseTypeField &ownField = list[list.size() - 1]; + ownField.fieldName.fromStringTable = false; + ownField.fieldName.str.string = pTemplate->name.c_str(); + ownField.typeName.fromStringTable = false; + ownField.typeName.str.string = pTemplate->type.c_str(); + ownField.depth = depth; + ownField.isArray = pTemplate->isArray; + ownField.size = 0; + if (pTemplate->isArray & 1) + ownField.size = -1; + else + { + switch (GetValueTypeByTypeName(pTemplate->type.c_str())) + { + case ValueType_Bool: + case ValueType_Int8: + case ValueType_UInt8: + ownField.size = 1; + break; + case ValueType_Int16: + case ValueType_UInt16: + ownField.size = 2; + break; + case ValueType_Int32: + case ValueType_UInt32: + case ValueType_Float: + ownField.size = 4; + break; + case ValueType_Int64: + case ValueType_UInt64: + case ValueType_Double: + ownField.size = 8; + break; + default: + ownField.size = 0; + } + } + ownField.version = 1; + ownField.flags2 = pTemplate->align ? 0x4000 : 0; + if (depth < 255) + { + for (uint32_t i = 0; i < pTemplate->children.size(); i++) + { + int curSize = _RecursiveAddTemplateFieldToClassDatabase(list, depth + 1, &pTemplate->children[i]); + if (!(ownField.isArray & 1) && (ownField.size != -1)) + { + if (curSize == -1) + ownField.size = -1; + else + ownField.size += curSize; + } + } + } + return ownField.size; +} +ASSETSTOOLS_API bool ClassDatabaseType::FromTemplateField(int classId, int baseClass, AssetTypeTemplateField *pTemplateBase) +{ + if (!pTemplateBase) return false; + this->classId = classId; + this->baseClass = baseClass; + this->name.fromStringTable = false; + this->name.str.string = pTemplateBase->type.c_str(); + this->assemblyFileName.fromStringTable = false; + this->assemblyFileName.str.string = ""; + this->fields.clear(); + _RecursiveAddTemplateFieldToClassDatabase(this->fields, 0, pTemplateBase); + return true; +} +ASSETSTOOLS_API ClassDatabaseType::~ClassDatabaseType() +{ + /*if (freeStrings) + { + if (!name.fromStringTable && name.str.string != NULL) + { + free(const_cast(name.str.string)); + name.str.string = NULL; + } + if (!assemblyFileName.fromStringTable && assemblyFileName.str.string != NULL) + { + free(const_cast(assemblyFileName.str.string)); + assemblyFileName.str.string = NULL; + } + for (size_t i = 0; i < fields.size(); i++) + { + ClassDatabaseTypeField &field = fields[i]; + if (!field.fieldName.fromStringTable && field.fieldName.str.string != NULL) + { + free(const_cast(field.fieldName.str.string)); + field.fieldName.str.string = NULL; + } + if (!field.typeName.fromStringTable && field.typeName.str.string != NULL) + { + free(const_cast(field.typeName.str.string)); + field.typeName.str.string = NULL; + } + } + }*/ + //if (this->fields.size() > 0) + // this->fields.clear(); +} +ASSETSTOOLS_API QWORD ClassDatabaseType::Read(IAssetsReader *pReader, QWORD filePos, int version, uint8_t flags) +{ + uint32_t fieldCount; + pReader->Read(filePos, 4, &classId); filePos+=4; + pReader->Read(filePos, 4, &baseClass); filePos+=4; + filePos = name.Read(pReader, filePos); + if (flags & 1) + filePos = assemblyFileName.Read(pReader, filePos); + else + { + assemblyFileName.str.string = NULL; + assemblyFileName.fromStringTable = false; + } + pReader->Read(filePos, 4, &fieldCount); filePos+=4; + + /*fields = (ClassDatabaseTypeField*)malloc(sizeof(ClassDatabaseTypeField) * fieldCount); + if (fields == NULL) + { + for (uint32_t i = 0; i < fieldCount; i++) + filePos = ClassDatabaseTypeField().Read(reader, readerPar, filePos); + fieldCount = 0; + } + for (uint32_t i = 0; i < fieldCount; i++) + { + filePos = fields[i].Read(reader, readerPar, filePos); + }*/ + fields.reserve(fieldCount); + for (uint32_t i = 0; i < fieldCount; i++) + { + ClassDatabaseTypeField field; + filePos = field.Read(pReader, filePos, version); + fields.push_back(field); + } + return filePos; +} +ASSETSTOOLS_API QWORD ClassDatabaseType::Write(IAssetsWriter *pWriter, QWORD filePos, int version, uint8_t flags) +{ + uint32_t fieldCount = (uint32_t)fields.size(); + + pWriter->Write(filePos, 4, &classId); filePos+=4; + pWriter->Write(filePos, 4, &baseClass); filePos+=4; + filePos = name.Write(pWriter, filePos); + if (flags & 1) + filePos = assemblyFileName.Write(pWriter, filePos); + pWriter->Write(filePos, 4, &fieldCount); filePos+=4; + + for (uint32_t i = 0; i < fieldCount; i++) + { + filePos = fields[i].Write(pWriter, filePos, version); + } + return filePos; +} +ASSETSTOOLS_API Hash128 ClassDatabaseType::MakeTypeHash(ClassDatabaseFile *pDatabaseFile) +{ + Hash128 ret = {}; + HCRYPTPROV hContext; + if (!CryptAcquireContext(&hContext, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) + return ret; + HCRYPTHASH hHash; + if (!CryptCreateHash(hContext, CALG_MD4, 0, 0, &hHash)) + { + CryptReleaseContext(hContext, 0); + return ret; + } + for (size_t k = 0; k < fields.size(); k++) + { + const char *typeName = fields[k].typeName.GetString(pDatabaseFile); + const char *fieldName = fields[k].fieldName.GetString(pDatabaseFile); + uint32_t size = fields[k].size; + uint32_t isArray = fields[k].isArray; + uint32_t version = fields[k].version; + uint32_t flag = fields[k].flags2 & 0x4000; + CryptHashData(hHash, (const uint8_t*)typeName, strlen(typeName), 0); + CryptHashData(hHash, (const uint8_t*)fieldName, strlen(fieldName), 0); + CryptHashData(hHash, (const uint8_t*)&size, 4, 0); + CryptHashData(hHash, (const uint8_t*)&isArray, 4, 0); + CryptHashData(hHash, (const uint8_t*)&version, 4, 0); + CryptHashData(hHash, (const uint8_t*)&flag, 4, 0); + } + DWORD len = 16; + CryptGetHashParam(hHash, HP_HASHVAL, ret.bValue, &len, 0); //if it returns FALSE, it still needs to be freed + CryptDestroyHash(hHash); + CryptReleaseContext(hContext, 0); + return ret; +} + +ASSETSTOOLS_API Hash128 MakeScriptID(const char *scriptName, const char *scriptNamespace, const char *scriptAssembly) +{ + Hash128 ret = {}; + HCRYPTPROV hContext; + if (!CryptAcquireContext(&hContext, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) + return ret; + HCRYPTHASH hHash; + if (!CryptCreateHash(hContext, CALG_MD4, 0, 0, &hHash)) + { + CryptReleaseContext(hContext, 0); + return ret; + } + CryptHashData(hHash, (const uint8_t*)scriptName, strlen(scriptName), 0); + CryptHashData(hHash, (const uint8_t*)scriptNamespace, strlen(scriptNamespace), 0); + CryptHashData(hHash, (const uint8_t*)scriptAssembly, strlen(scriptAssembly), 0); + DWORD len = 16; + CryptGetHashParam(hHash, HP_HASHVAL, ret.bValue, &len, 0); //if it returns FALSE, it still needs to be freed + CryptDestroyHash(hHash); + CryptReleaseContext(hContext, 0); + return ret; +} + +ASSETSTOOLS_API QWORD ClassDatabaseFileHeader::Read(IAssetsReader *pReader, QWORD filePos) +{ + if (!pReader->Read(filePos, 4, header)) + return 0; + if (memcmp(header, "cldb", 4)) + return 0; + filePos += 4; + pReader->Read(filePos, 1, &fileVersion); filePos++; + if (fileVersion >= 4) + { + pReader->Read(filePos, 1, &flags); filePos++; + } + else + flags = 0; + if (fileVersion >= 2) + { + pReader->Read(filePos, 1, &compressionType); filePos++; + pReader->Read(filePos, 4, &compressedSize); filePos+=4; + pReader->Read(filePos, 4, &uncompressedSize); filePos+=4; + } + else + { + compressionType = 0; + compressedSize = uncompressedSize = 0; + } + if (fileVersion == 0) + { + uint8_t assetsVersionCount; + pReader->Read(filePos, 1, &assetsVersionCount); filePos++; + filePos += assetsVersionCount; + } + else + { + pReader->Read(filePos, 1, &unityVersionCount); filePos++; + QWORD firstVersionPos = filePos; + size_t bufferLen = unityVersionCount * sizeof(char*); + for (uint32_t i = 0; i < unityVersionCount; i++) + { + uint8_t byTmp; + pReader->Read(filePos, 1, &byTmp); filePos++; + filePos += byTmp; + bufferLen += (byTmp+1); //plus null-terminator + } + QWORD postVersionPos = filePos; + pUnityVersions = (char**)malloc(bufferLen); + if (pUnityVersions != NULL) + { + size_t bufferPos = unityVersionCount * sizeof(char*); + filePos = firstVersionPos; + for (uint32_t i = 0; i < unityVersionCount; i++) + { + char *charBuffer = &((char*)pUnityVersions)[bufferPos]; + pUnityVersions[i] = charBuffer; + uint8_t byTmp; + pReader->Read(filePos, 1, &byTmp); filePos++; + pReader->Read(filePos, byTmp, charBuffer); + charBuffer[byTmp] = 0; + filePos += byTmp; + bufferPos += (byTmp+1); //plus null-terminator + } + } + else + unityVersionCount = 0; + + } + pReader->Read(filePos, 4, &stringTableLen); filePos+=4; + pReader->Read(filePos, 4, &stringTablePos); filePos+=4; + return filePos; +} +ASSETSTOOLS_API QWORD ClassDatabaseFileHeader::Write(IAssetsWriter *pWriter, QWORD filePos) +{ + pWriter->Write(filePos, 4, "cldb"); filePos += 4; + pWriter->Write(filePos, 1, &fileVersion); filePos++; + if (fileVersion >= 4) + { + pWriter->Write(filePos, 1, &flags); filePos++; + } + if (fileVersion >= 2) + { + pWriter->Write(filePos, 1, &compressionType); filePos++; + pWriter->Write(filePos, 4, &compressedSize); filePos+=4; + pWriter->Write(filePos, 4, &uncompressedSize); filePos+=4; + } + pWriter->Write(filePos, 1, &unityVersionCount); filePos++; + for (uint32_t i = 0; i < unityVersionCount; i++) + { + uint8_t byTmp = (uint8_t)strlen(pUnityVersions[i]); + pWriter->Write(filePos, 1, &byTmp); filePos++; + pWriter->Write(filePos, byTmp, pUnityVersions[i]); filePos+=byTmp; + } + pWriter->Write(filePos, 4, &stringTableLen); filePos+=4; + pWriter->Write(filePos, 4, &stringTablePos); filePos+=4; + return filePos; +} + +ASSETSTOOLS_API QWORD ClassDatabaseFile::Read(IAssetsReader *pReader, QWORD filePos) +{ + valid = false; + this->dontFreeStringTable = false; + char *uncompressedBuf = NULL; + QWORD _filePos = header.Read(pReader, filePos); + if (_filePos == filePos) + return filePos; + filePos = _filePos; + QWORD postHeaderPos = filePos; + QWORD compressedFilePos = filePos; + + if (header.compressionType && header.compressionType < 3) + { + char *compressedBuf = (char*)malloc(header.compressedSize); + if (!compressedBuf) + return postHeaderPos + header.compressedSize; + if (pReader->Read(filePos, header.compressedSize, compressedBuf) != header.compressedSize) + { + free(compressedBuf); + return postHeaderPos + header.compressedSize; + } + compressedFilePos = filePos + header.compressedSize; + //add 1 to see if it decompresses more bytes than the header says there are + uncompressedBuf = (char*)malloc(header.uncompressedSize + 1); + if (!uncompressedBuf) + { + free(compressedBuf); + return postHeaderPos + header.compressedSize; + } + uint32_t uncompressedSize = 0; + if (header.compressionType == 1) + { +#ifdef _WITHOUT_LZ4 + free(compressedBuf); + return postHeaderPos + header.compressedSize; +#else + uncompressedSize = (uint32_t)LZ4_decompress_safe(compressedBuf, uncompressedBuf, header.compressedSize, header.uncompressedSize + 1); +#endif + } + else if (header.compressionType == 2 && header.compressedSize > LZMA_PROPS_SIZE) + { + size_t lz_uncompressedSize = header.uncompressedSize; + size_t compressedWithoutProps = header.compressedSize - LZMA_PROPS_SIZE; + int result = LzmaUncompress((Byte*)uncompressedBuf, &lz_uncompressedSize, + (Byte*)&compressedBuf[LZMA_PROPS_SIZE], &compressedWithoutProps, + (Byte*)compressedBuf, LZMA_PROPS_SIZE); + if (result == SZ_OK) + uncompressedSize = (uint32_t)lz_uncompressedSize; + } + free(compressedBuf); + if (uncompressedSize != header.uncompressedSize) + { + free(uncompressedBuf); + return postHeaderPos + header.compressedSize; + } + pReader = Create_AssetsReaderFromMemory(uncompressedBuf, header.uncompressedSize, false); + if (pReader == NULL) + { + free(uncompressedBuf); + return postHeaderPos + header.compressedSize; + } + filePos = 0; + } + + uint32_t classCount; + pReader->Read(filePos, 4, &classCount); filePos += 4; + classes.clear(); + classes.resize(classCount); + for (uint32_t i = 0; i < classCount; i++) + { + filePos = classes[i].Read(pReader, filePos, header.fileVersion, header.flags); + } + /*classes = (ClassDatabaseType*)malloc(sizeof(ClassDatabaseType) * classCount); + if (classes == NULL) + { + for (uint32_t i = 0; i < classCount; i++) + filePos = ClassDatabaseType().Read(reader, readerPar, filePos); + classCount = 0; + } + for (uint32_t i = 0; i < classCount; i++) + { + filePos = classes[i].Read(reader, readerPar, filePos); + }*/ + + stringTable = (char*)malloc((header.stringTableLen + 1) * sizeof(char)); + if (stringTable == NULL) + { + if (uncompressedBuf) + { + Free_AssetsReader(pReader); + free(uncompressedBuf); + } + return postHeaderPos + header.compressedSize; + } + if (pReader->Read(header.stringTablePos, header.stringTableLen, stringTable) != header.stringTableLen) + { + if (uncompressedBuf) + { + Free_AssetsReader(pReader); + free(uncompressedBuf); + } + return postHeaderPos + header.compressedSize; + } + stringTable[header.stringTableLen] = 0; + filePos += header.stringTableLen; + + if (uncompressedBuf) + { + Free_AssetsReader(pReader); + free(uncompressedBuf); + } + + valid = true; + return postHeaderPos + header.compressedSize; +} +ASSETSTOOLS_API bool ClassDatabaseFile::Read(IAssetsReader *pReader) +{ + Read(pReader, 0ULL); + return valid; +} + +enum EStringTableWriter_AddString_OptModes +{ + //better result without AddStringsOptimized and less memory overhead but slower than hash table + StringTableWriter_Opt_LinearSearch, + //memory overhead of 512KiB(32)/1MiB(64) + sizeof(size_t) * stringCount; only better than linear with AddStringsOptimized called before + StringTableWriter_Opt_HashTableSearch, +}; +struct StringTableOptTmp +{ + size_t strLen; + const char *str; + StringTableOptTmp *parent; +}; +struct StringTableHashEntryTmp +{ + size_t matchCount; + StringTableOptTmp **matches; +}; +class StringTableWriter +{ + char *stringTable; + size_t stringTableBufferLen; + size_t stringTableLen; + //EStringTableWriter_AddString_OptModes optMode; + struct StringTableHashEntry + { + size_t matchCount; + size_t *matches; //offsets into stringTable + }* stringHashTable; //only for making AddString optimization faster (searching for double entries) +public: + StringTableWriter(EStringTableWriter_AddString_OptModes optMode = StringTableWriter_Opt_LinearSearch) + { + stringTable = NULL; + stringTableLen = stringTableBufferLen = 0; + //this->optMode = optMode; + if (optMode == StringTableWriter_Opt_HashTableSearch) + { + stringHashTable = (StringTableHashEntry*)malloc(sizeof(StringTableHashEntry) * (MAXWORD+1)); //1 MiB + memset(stringHashTable, 0, sizeof(StringTableHashEntry) * (MAXWORD+1)); + } + else + { + stringHashTable = NULL; + } + } + //linear only + StringTableWriter(char *stringTable, size_t stringTableLen) + { + this->stringTable = stringTable; + this->stringTableLen = this->stringTableBufferLen = stringTableLen; + this->stringHashTable = NULL; + } + ~StringTableWriter() + { + if (stringHashTable) + free(stringHashTable); + } + //a small bit smaller stringtables than only AddStrings (1260485 vs. 1261082 Bytes) but A LOT slower (~0:02 vs. ~1:35) + bool AddStringsOptimized(const char **strings, size_t count) + { + size_t stringCount = count; + StringTableHashEntryTmp *_TmpStringHashTable = NULL; + //StringTableHashEntryTmp *_TmpStringHashTable = (StringTableHashEntryTmp*)malloc(sizeof(StringTableHashEntryTmp) * (MAXWORD+1)); //1 MiB + //memset(_TmpStringHashTable, 0, sizeof(StringTableHashEntryTmp) * (MAXWORD+1)); + StringTableOptTmp *_TmpStringTable = (StringTableOptTmp*)malloc(sizeof(StringTableOptTmp) * stringCount); + if (_TmpStringTable) + { + if (!_TmpStringHashTable) + { + size_t curStringIndex = 0; + for (uint32_t i = 0; i < stringCount; i++) + { + _TmpStringTable[curStringIndex].str = strings[i]; + _TmpStringTable[curStringIndex].strLen = (_TmpStringTable[curStringIndex].str ? strlen(_TmpStringTable[curStringIndex].str) : 0); + _TmpStringTable[curStringIndex].parent = NULL; + curStringIndex++; + } + } + else + { + size_t curStringIndex = 0; + for (uint32_t i = 0; i < stringCount; i++) + { + _TmpStringTable[curStringIndex].str = strings[i]; + _TmpStringTable[curStringIndex].strLen = (_TmpStringTable[curStringIndex].str ? strlen(_TmpStringTable[curStringIndex].str) : 0); + _TmpStringTable[curStringIndex].parent = NULL; + curStringIndex++; + if (_TmpStringTable[curStringIndex-1].str) + { + StringTableHashEntryTmp *hashEntry = + &_TmpStringHashTable[_TmpStringTable[curStringIndex-1].strLen ? (*(const uint16_t*)strings[i]) : 0]; + { + bool isDuplicate = false; + for (uint32_t l = 0; l < hashEntry->matchCount; l++) + { + if ((hashEntry->matches[l]->strLen == _TmpStringTable[curStringIndex-1].strLen) && + !memcmp(_TmpStringTable[curStringIndex-1].str, + hashEntry->matches[l]->str, + hashEntry->matches[l]->strLen * sizeof(char))) + { + _TmpStringTable[curStringIndex-1].parent = hashEntry->matches[l]; + isDuplicate = true; + break; + } + } + if (isDuplicate) + continue; + } + if (!hashEntry->matchCount || !((hashEntry->matchCount + 1) & 3)) + { + void *tmp = realloc(hashEntry->matches, ((hashEntry->matchCount + 5) & (~3)) * sizeof(void*)); + if (!tmp) + { + if (hashEntry->matches) + { + free(hashEntry->matches); + hashEntry->matches = NULL; + } + hashEntry->matchCount = 0; + continue; + } + hashEntry->matches = (StringTableOptTmp**)tmp; + } + hashEntry->matches[hashEntry->matchCount] = &_TmpStringTable[curStringIndex-1]; + hashEntry->matchCount++; + } + } + for (uint32_t i = 0; i < (MAXWORD+1); i++) + if (_TmpStringHashTable[i].matches) + free(_TmpStringHashTable[i].matches); + free(_TmpStringHashTable); + } + for (uint32_t i = 0; i < stringCount; i++) + { + if (_TmpStringTable[i].str == NULL) + continue; + for (uint32_t k = 0; k < stringCount; k++) + { + if ((k == i) || (_TmpStringTable[k].strLen > _TmpStringTable[i].strLen)) + continue; + if ((_TmpStringTable[k].str == NULL) || (_TmpStringTable[k].parent != NULL)) + continue; + //for (size_t l = 0; l <= (_TmpStringTable[i].strLen - _TmpStringTable[k].strLen); l++) + { + const char *cmpTarget = &_TmpStringTable[i].str[_TmpStringTable[i].strLen-_TmpStringTable[k].strLen]; + if (!memcmp( _TmpStringTable[k].str, cmpTarget, (_TmpStringTable[k].strLen+1) * sizeof(char))) + { + _TmpStringTable[k].str = cmpTarget; + _TmpStringTable[k].parent = &_TmpStringTable[i]; + //break; + } + } + } + } + for (uint32_t i = 0; i < stringCount; i++) + { + if (_TmpStringTable[i].str && !_TmpStringTable[i].parent) + { + AddString(_TmpStringTable[i].str, false); + } + } + free(_TmpStringTable); + return true; + } + else + return false; + //MessageBox(NULL, TEXT("Out of memory while optimizing the string table!"), TEXT("ERROR"), 0); + } + size_t AddString(const char *string, bool optimize) + { + if (string == NULL) + return 0; + size_t strLen = strlen(string); + uint16_t hash = 0; + for (size_t i = 0; i < strLen; i++) + hash += string[i]; + //uint16_t hash = strLen ? (*(const uint16_t*)string) : 0; + if (optimize) + { + if (stringHashTable) + { + //only detects 100% equal strings -> best if AddStringsOptimized was called before + StringTableHashEntry *pHashEntry = &stringHashTable[hash]; + for (size_t i = 0; i < pHashEntry->matchCount; i++) + if (!strncmp(&stringTable[pHashEntry->matches[i]], string, strLen+1)) + return pHashEntry->matches[i]; + } + else + { + for (size_t i = 0; i < stringTableLen; i++) + if (!strncmp(&stringTable[i], string, strLen+1)) + return i; + } + } + size_t ret = stringTableLen; + if ((stringTableLen+strLen+1) > stringTableBufferLen) + { + char *pNewStringTable = (char*)realloc(stringTable, stringTableBufferLen=((stringTableLen+strLen+1)+15)&(~15)); + if (!pNewStringTable) + return -1; + stringTable = pNewStringTable; + } + if (stringHashTable) + { + //if out of memory, it works less efficiently (depending on how many of the matches arrays are affected) + StringTableHashEntry *pHashEntry = &stringHashTable[hash]; + bool resized = true; + if (!pHashEntry->matchCount || !((pHashEntry->matchCount + 1) & 3)) + { + size_t *tmp = (size_t*)realloc(pHashEntry->matches, ((pHashEntry->matchCount + 5) & (~3)) * sizeof(size_t)); + if (!tmp) + { + if (pHashEntry->matches) + free(pHashEntry->matches); + pHashEntry->matchCount = 0; + resized = false; + } + pHashEntry->matches = (size_t*)tmp; + } + if (resized) + { + pHashEntry->matches[pHashEntry->matchCount] = stringTableLen; + pHashEntry->matchCount++; + } + } + strncpy(&stringTable[ret], string, strLen+1); + stringTableLen += (strLen+1); + return ret; + } + char *GetStringTable() + { + return stringTable; + } + size_t GetStringTableLen() + { + return stringTableLen; + } +}; +ASSETSTOOLS_API QWORD ClassDatabaseFile::Write(IAssetsWriter *pWriter, QWORD filePos, int optimizeStringTable, uint32_t compress, bool writeStringTable) +{ + uint32_t classCount = (uint32_t)classes.size(); + if (writeStringTable) + { + StringTableWriter strTableWriter; + //puts only the strings that can't be interpreted as a part of another string into the string table + if (optimizeStringTable == 2) + { + size_t stringCount = classCount; + for (uint32_t i = 0; i < classCount; i++) + { + ClassDatabaseType *pType = &classes[i]; + stringCount += ((uint32_t)pType->fields.size()) * 2; + } + const char **stringList = (const char**)malloc(sizeof(char*) * stringCount); + bool optResult = false; + if (stringList) + { + size_t curStringIndex = 0; + for (uint32_t i = 0; i < classCount; i++) + { + ClassDatabaseType *pType = &classes[i]; + stringList[curStringIndex] = pType->name.GetString(this); + curStringIndex++; + for (uint32_t k = 0; k < (uint32_t)pType->fields.size(); k++) + { + stringList[curStringIndex] = pType->fields[k].fieldName.GetString(this); + curStringIndex++; + stringList[curStringIndex] = pType->fields[k].typeName.GetString(this); + curStringIndex++; + } + } + optResult = strTableWriter.AddStringsOptimized(stringList, curStringIndex); + free(stringList); + } + if (!optResult) + MessageBox(NULL, TEXT("Out of memory while optimizing the string table!"), TEXT("ERROR"), 0); + } + for (uint32_t i = 0; i < classCount; i++) + { + ClassDatabaseType *pType = &classes[i]; + pType->name.str.stringTableOffset = (uint32_t)strTableWriter.AddString(pType->name.GetString(this), optimizeStringTable ? true : false); + pType->name.fromStringTable = true; + pType->assemblyFileName.str.stringTableOffset = (uint32_t)strTableWriter.AddString(pType->assemblyFileName.GetString(this), optimizeStringTable ? true : false); + pType->assemblyFileName.fromStringTable = true; + for (uint32_t k = 0; k < pType->fields.size(); k++) + { + ClassDatabaseTypeField *pTypeField = &pType->fields[k]; + pTypeField->fieldName.str.stringTableOffset = (uint32_t)strTableWriter.AddString(pTypeField->fieldName.GetString(this), optimizeStringTable ? true : false); + pTypeField->fieldName.fromStringTable = true; + pTypeField->typeName.str.stringTableOffset = (uint32_t)strTableWriter.AddString(pTypeField->typeName.GetString(this), optimizeStringTable ? true : false); + pTypeField->typeName.fromStringTable = true; + } + } + if (!this->dontFreeStringTable && this->stringTable != NULL) + free(this->stringTable); + this->stringTable = strTableWriter.GetStringTable(); + header.stringTableLen = (uint32_t)strTableWriter.GetStringTableLen(); + this->dontFreeStringTable = false; + } + + QWORD headerPos = filePos; + if (compress > 2) + compress = 0; +#ifdef _WITHOUT_LZ4 + if (compress == 1) + compress = 0; +#endif +#if (ClassDatabaseFileVersion==2) + if (!compress) + header.fileVersion = 1; + else +#endif +#if (ClassDatabaseFileVersion==3)||(ClassDatabaseFileVersion==4) + bool needsVersion3 = false; + for (size_t i = 0; i < this->classes.size(); i++) + { + ClassDatabaseType &curType = this->classes[i]; + for (size_t k = 0; k < curType.fields.size(); k++) + { + if (curType.fields[k].version != 1) + { + needsVersion3 = true; + goto goto_post_checkNeeds3; + } + } + } +goto_post_checkNeeds3: + if (!needsVersion3) + header.fileVersion = compress ? 2 : 1; + else +#endif +#if (ClassDatabaseFileVersion==4) + header.fileVersion = 3; + if (header.flags) +#endif + header.fileVersion = ClassDatabaseFileVersion; + header.compressionType = compress;// ? ClassDatabaseCompressionType : 0; + filePos = header.Write(pWriter, filePos); + QWORD postHeaderPos = filePos; + IAssetsWriter *pDataWriter = pWriter; + if (compress) + { + IAssetsWriter *pMemWriter = Create_AssetsWriterToMemory(); + if (!pMemWriter) + { + //we don't want to throw an error here, so silently don't compress the file + header.compressionType = 0; + compress = false; + } + else + { + pDataWriter = pMemWriter; + filePos = 0; + } + } + + pDataWriter->Write(filePos, 4, &classCount); filePos += 4; + for (uint32_t i = 0; i < classCount; i++) + { + filePos = classes[i].Write(pDataWriter, filePos, header.fileVersion, header.flags); + } + header.stringTablePos = (uint32_t)filePos; + if (writeStringTable && (header.stringTableLen > 0)) + stringTable[header.stringTableLen-1] = 0; + pDataWriter->Write(header.stringTablePos, header.stringTableLen, stringTable); + filePos += header.stringTableLen; + header.uncompressedSize = filePos - (compress ? 0 : postHeaderPos); //doesn't include the header + + if (compress) + { + size_t memWriterPos; void *memWriterBuf = NULL; + ((IAssetsWriterToMemory*)pDataWriter)->GetBuffer(memWriterBuf, memWriterPos); + if (!memWriterBuf || (memWriterPos != filePos)) + { + Free_AssetsWriter(pDataWriter); + compress = false; + return 0; //the memory writer went out of memory, we can't change that + } + uint32_t maxSize; + if (compress == 2) + maxSize = (uint32_t)(memWriterPos + memWriterPos / 3 + 128 + LZMA_PROPS_SIZE); +#ifndef _WITHOUT_LZ4 + else + maxSize = (uint32_t)LZ4_compressBound((int)filePos); +#endif + void *compressBuf = malloc(maxSize); + if (compressBuf) + { +#ifndef _WITHOUT_LZ4 + if (compress == 1) + header.compressedSize = (uint32_t)LZ4_compress_default((char*)memWriterBuf, (char*)compressBuf, (int)memWriterPos, maxSize); + else +#endif + if (compress == 2) + { + size_t destLen = maxSize - LZMA_PROPS_SIZE; size_t propsLen = LZMA_PROPS_SIZE; + int result = LzmaCompress( + &((uint8_t*)compressBuf)[LZMA_PROPS_SIZE], &destLen, + (uint8_t*)memWriterBuf, memWriterPos, + (uint8_t*)compressBuf, &propsLen, + -1, 0, -1, -1, -1, -1, -1); + if ((propsLen != LZMA_PROPS_SIZE) || (result != SZ_OK)) + header.compressedSize = 0; + else + header.compressedSize = (uint32_t)(destLen + propsLen); + } + else + header.compressedSize = 0; + } + if (!compressBuf || !header.compressedSize) + { + //out of memory or compression failure, but we can still write the uncompressed data + header.compressionType = 0; + compress = false; + pWriter->Write(postHeaderPos, memWriterPos, memWriterBuf); + Free_AssetsWriter(pDataWriter); + } + else + { + //compression succeeded + Free_AssetsWriter(pDataWriter); + pWriter->Write(postHeaderPos, header.compressedSize, compressBuf); + } + if (compressBuf) + free(compressBuf); + } + else + header.compressedSize = header.uncompressedSize; + header.Write(pWriter, headerPos); + + return postHeaderPos + header.compressedSize; +} +ASSETSTOOLS_API bool ClassDatabaseFile::IsValid() +{ + return valid; +} + +ASSETSTOOLS_API bool ClassDatabaseFile::InsertFrom(ClassDatabaseFile *pOther, ClassDatabaseType *pType) +{ + if (this->dontFreeStringTable) + return false; + StringTableWriter strTableWriter = StringTableWriter(this->stringTable, this->header.stringTableLen); + + classes.resize(classes.size()+1); + ClassDatabaseType &newType = classes[classes.size()-1]; + newType.baseClass = pType->baseClass; + newType.classId = pType->classId; + size_t typeNameOffset = strTableWriter.AddString(pType->name.GetString(pOther), true); + newType.name.fromStringTable = true; + newType.name.str.stringTableOffset = typeNameOffset; + newType.fields.resize(pType->fields.size()); + for (size_t i = 0; i < pType->fields.size(); i++) + { + ClassDatabaseTypeField &ownField = newType.fields[i]; + ClassDatabaseTypeField &otherField = pType->fields[i]; + size_t nameOffset = strTableWriter.AddString(otherField.fieldName.GetString(pOther), true); + size_t typeOffset = strTableWriter.AddString(otherField.typeName.GetString(pOther), true); + + ownField.typeName.fromStringTable = true; + ownField.typeName.str.stringTableOffset = typeOffset; + ownField.fieldName.fromStringTable = true; + ownField.fieldName.str.stringTableOffset = nameOffset; + ownField.depth = otherField.depth; + ownField.isArray = otherField.isArray; + ownField.size = otherField.size; + ownField.version = otherField.version; + ownField.flags2 = otherField.flags2; + } + this->stringTable = strTableWriter.GetStringTable(); + this->header.stringTableLen = strTableWriter.GetStringTableLen(); + return true; +} + +ASSETSTOOLS_API ClassDatabaseFile::ClassDatabaseFile() +{ + this->stringTable = NULL; + this->valid = false; this->dontFreeStringTable = false; + //ZeroMemory(this, sizeof(ClassDatabaseFile)); + //this->classes = std::vector(); +} +ASSETSTOOLS_API ClassDatabaseFile &ClassDatabaseFile::operator=(const ClassDatabaseFile& other) +{ + valid = other.valid; + memcpy(&header, &other.header, sizeof(ClassDatabaseFileHeader)); + if (header.unityVersionCount > 0) + { + size_t newListLen = header.unityVersionCount * sizeof(char*); + for (uint32_t i = 0; i < header.unityVersionCount; i++) + newListLen += strlen(header.pUnityVersions[i])+1; + header.pUnityVersions = (char**)malloc(newListLen); + if (header.pUnityVersions == NULL) + { + header.unityVersionCount = 0; + } + else + { + size_t stringPos = header.unityVersionCount * sizeof(char*); + for (uint32_t i = 0; i < other.header.unityVersionCount; i++) + { + header.pUnityVersions[i] = &((char*)header.pUnityVersions)[stringPos]; + size_t strLen = strlen(other.header.pUnityVersions[i]); + memcpy(header.pUnityVersions[i], other.header.pUnityVersions[i], strLen+1); + stringPos += (strLen+1); + } + } + } + else + header.pUnityVersions = NULL; + stringTable = (char*)malloc(header.stringTableLen); + if (stringTable == NULL) + header.stringTableLen = 0; + else + memcpy(stringTable, other.stringTable, header.stringTableLen); + + classes.reserve(other.classes.size()); + for (size_t i = 0; i < other.classes.size(); i++) + classes.push_back(ClassDatabaseType(other.classes[i])); + return (*this); +} +ASSETSTOOLS_API ClassDatabaseFile::ClassDatabaseFile(const ClassDatabaseFile& other) +{ + (*this) = other; +} +ASSETSTOOLS_API ClassDatabaseFile::ClassDatabaseFile(ClassDatabaseFile&& other) +{ + valid = other.valid; + dontFreeStringTable = other.dontFreeStringTable; + header = other.header; + other.header.unityVersionCount = 0; + other.header.pUnityVersions = nullptr; + classes.swap(other.classes); + stringTable = other.stringTable; + other.stringTable = nullptr; +} +ASSETSTOOLS_API void ClassDatabaseFile::Clear() +{ + /*if ((header.assetsVersions != NULL) && (header.assetsVersions != (uint8_t*)&header._tmp)) + { + free(header.assetsVersions); + header.assetsVersions = NULL; + }*/ + if (header.pUnityVersions != NULL) + { + free(header.pUnityVersions); + header.pUnityVersions = NULL; + header.unityVersionCount = 0; + } + /*if (classes != NULL) + { + for (uint32_t i = 0; i < classCount; i++) + { + if (classes[i].fields != NULL) + free(classes[i].fields); + } + free(classes); + classes = NULL; + classCount = 0; + }*/ + if (!this->dontFreeStringTable && stringTable != NULL) + { + free(stringTable); + stringTable = NULL; + header.stringTableLen = 0; + } + classes.clear(); +} +ASSETSTOOLS_API ClassDatabaseFile::~ClassDatabaseFile() +{ + Clear(); +} + +ASSETSTOOLS_API void FreeClassDatabase_Dummy(ClassDatabaseFile *pFile) +{} + +ASSETSTOOLS_API QWORD ClassDatabasePackageHeader::Read(IAssetsReader *pReader, QWORD filePos) +{ + if (pReader->Read(filePos, 4, &magic[0]) != 4) + return 0; + filePos += 4; + if (memcmp(&magic[0], "CLPK", 4)) + return 0; + if (pReader->Read(-1, 1, &fileVersion) != 1) + return 0; + filePos += 1; + + if (fileVersion > 1) + return 0; + + if (pReader->Read(-1, 1, &compressionType) != 1) + return 0; + filePos += 1; + if (pReader->Read(-1, 4, &stringTableOffset) != 4) + return 0; + filePos += 4; + if (pReader->Read(-1, 4, &stringTableLenUncompressed) != 4) + return 0; + filePos += 4; + if (pReader->Read(-1, 4, &stringTableLenCompressed) != 4) + return 0; + filePos += 4; + if (fileVersion >= 1) + { + if (pReader->Read(-1, 4, &fileBlockSize) != 4) + return 0; + filePos += 4; + } + else + fileBlockSize = 0; + if (pReader->Read(-1, 4, &fileCount) != 4) + return 0; + filePos += 4; + files.clear(); + files.reserve(fileCount); + for (uint32_t i = 0; i < fileCount; i++) + { + ClassDatabaseFileRef ref; + if (pReader->Read(-1, 4, &ref.offset) != 4) + return 0; + filePos += 4; + if (pReader->Read(-1, 4, &ref.length) != 4) + return 0; + filePos += 4; + if (pReader->Read(-1, 15, &ref.name[0]) != 15) + return 0; + ref.name[15] = 0; + filePos += 15; + files.push_back(ref); + } + return filePos; +} +ASSETSTOOLS_API QWORD ClassDatabasePackageHeader::Write(IAssetsWriter *pWriter, QWORD filePos) +{ + if (pWriter->Write(filePos, 4, "CLPK") != 4) + return 0; + filePos += 4; + if (pWriter->Write(-1, 1, &fileVersion) != 1) + return 0; + filePos += 1; + if (pWriter->Write(-1, 1, &compressionType) != 1) + return 0; + filePos += 1; + if (fileVersion > 1) + return 0; + if (pWriter->Write(-1, 4, &stringTableOffset) != 4) + return 0; + filePos += 4; + if (pWriter->Write(-1, 4, &stringTableLenUncompressed) != 4) + return 0; + filePos += 4; + if (pWriter->Write(-1, 4, &stringTableLenCompressed) != 4) + return 0; + filePos += 4; + if (fileVersion >= 1) + { + if (pWriter->Write(-1, 4, &fileBlockSize) != 4) + return 0; + filePos += 4; + } + if (pWriter->Write(-1, 4, &fileCount) != 4) + return 0; + filePos += 4; + for (uint32_t i = 0; i < fileCount; i++) + { + ClassDatabaseFileRef &ref = files[i]; + if (pWriter->Write(-1, 4, &ref.offset) != 4) + return 0; + filePos += 4; + if (pWriter->Write(-1, 4, &ref.length) != 4) + return 0; + filePos += 4; + if (pWriter->Write(-1, 15, &ref.name[0]) != 15) + return 0; + filePos += 15; + } + return filePos; +} + +ASSETSTOOLS_API void ClassDatabasePackage::Clear() +{ + if (files) + { + for (uint32_t i = 0; i < header.fileCount; i++) + { + if (files[i]) + delete files[i]; + } + delete[] files; + files = NULL; + } + if (stringTable) + { + free(stringTable); + stringTable = NULL; + } +} +ASSETSTOOLS_API bool ClassDatabasePackage::Read(IAssetsReader *pReader) +{ + Clear(); + valid = false; + QWORD filePos = header.Read(pReader, 0); + if (filePos == 0) + { + Clear(); + return false; + } + uint8_t compressAlgo = header.compressionType & 0x1F; + + IAssetsReader *pDatabasesReader = pReader; + char *databasesBuf = nullptr; + if (header.compressionType & 0x80) //Compress files in one block + { + QWORD compressedSize = (QWORD)header.stringTableOffset - filePos; + char *compressedBuf = (char*)malloc((size_t)compressedSize); + if (!compressedBuf) + { + Clear(); + return false; + } + if (pReader->Read(filePos, compressedSize, compressedBuf) != compressedSize) + { + free(compressedBuf); + Clear(); + return false; + } + if (compressAlgo == 0 || (header.compressionType & 0x20)) + { + pDatabasesReader = Create_AssetsReaderFromMemory(compressedBuf, (size_t)compressedSize, false); + databasesBuf = compressedBuf; + } + else + { + databasesBuf = (char*)malloc(header.fileBlockSize); + if (!databasesBuf) + { + free(compressedBuf); + Clear(); + return false; + } + uint32_t uncompressedSize = 0; + if (compressAlgo == 1) + { +#ifdef _WITHOUT_LZ4 + free(compressedBuf); + Clear(); + return false; +#else + uncompressedSize = (uint32_t)LZ4_decompress_safe( + compressedBuf, databasesBuf, + compressedSize, header.fileBlockSize + ); +#endif + } + else if (compressAlgo == 2 && compressedSize > LZMA_PROPS_SIZE) + { + size_t lz_uncompressedSize = header.fileBlockSize; + size_t compressedWithoutProps = compressedSize - LZMA_PROPS_SIZE; + int result = LzmaUncompress((Byte*)databasesBuf, &lz_uncompressedSize, + (Byte*)&compressedBuf[LZMA_PROPS_SIZE], &compressedWithoutProps, + (Byte*)compressedBuf, LZMA_PROPS_SIZE); + if (result == SZ_OK) + uncompressedSize = (uint32_t)lz_uncompressedSize; + } + free(compressedBuf); + if (uncompressedSize != header.fileBlockSize) + { + free(databasesBuf); + Clear(); + return false; + } + pDatabasesReader = Create_AssetsReaderFromMemory(databasesBuf, (size_t)header.fileBlockSize, false); + } + } + files = new PClassDatabaseFile[header.fileCount]; + ZeroMemory(files, header.fileCount * sizeof(PClassDatabaseFile)); + for (uint32_t i = 0; i < header.fileCount; i++) + { + files[i] = new ClassDatabaseFile(); + IAssetsReader *pTempReader = Create_AssetsReaderFromReaderRange(pDatabasesReader, header.files[i].offset, header.files[i].length); + if (!pTempReader || !files[i]->Read(pTempReader)) + { + Clear(); + return false; + } + if (files[i]->stringTable != NULL) + { + free(files[i]->stringTable); + files[i]->stringTable = NULL; + } + Free_AssetsReader(pTempReader); + } + if (databasesBuf) + { + free(databasesBuf); + Free_AssetsReader(pDatabasesReader); + } + + + this->stringTable = (char*)malloc(header.stringTableLenUncompressed); + if (!this->stringTable) + { + Clear(); + return false; + } + char *compressedBuf; + if (header.compressionType != 0) + compressedBuf = (char*)malloc(header.stringTableLenCompressed); + else + { + header.stringTableLenCompressed = header.stringTableLenUncompressed; + compressedBuf = this->stringTable; + } + if (!compressedBuf) + { + Clear(); + return false; + } + if (pReader->Read(header.stringTableOffset, header.stringTableLenCompressed, compressedBuf) != header.stringTableLenCompressed) + { + if (header.compressionType != 0) free(compressedBuf); + Clear(); + return false; + } + if (compressAlgo != 0 && !(header.compressionType & 0x40)) + { + uint32_t uncompressedSize = 0; + if (compressAlgo == 1) + { +#ifdef _WITHOUT_LZ4 + free(compressedBuf); + Clear(); + return false; +#else + uncompressedSize = (uint32_t)LZ4_decompress_safe( + compressedBuf, this->stringTable, + header.stringTableLenCompressed, header.stringTableLenUncompressed + ); +#endif + } + else if (compressAlgo == 2 && header.stringTableLenCompressed > LZMA_PROPS_SIZE) + { + size_t lz_uncompressedSize = header.stringTableLenUncompressed; + size_t compressedWithoutProps = header.stringTableLenCompressed - LZMA_PROPS_SIZE; + int result = LzmaUncompress((Byte*)this->stringTable, &lz_uncompressedSize, + (Byte*)&compressedBuf[LZMA_PROPS_SIZE], &compressedWithoutProps, + (Byte*)compressedBuf, LZMA_PROPS_SIZE); + if (result == SZ_OK) + uncompressedSize = (uint32_t)lz_uncompressedSize; + } + free(compressedBuf); + if (uncompressedSize != header.stringTableLenUncompressed) + { + Clear(); + return false; + } + } + for (uint32_t i = 0; i < header.fileCount; i++) + { + files[i]->dontFreeStringTable = true; + files[i]->stringTable = this->stringTable; + files[i]->header.stringTableLen = this->header.stringTableLenUncompressed; + } + return (valid = true); +} + +//success == false -> wrote uncompressed data +static bool CompressToWriter(QWORD filePos, IAssetsWriter *pWriter, + void *uncompressedBuf, uint32_t uncompressedLen, uint8_t compressAlgo, + uint32_t &compressedLen) +{ + bool success = true; + uint32_t maxSize; + if (compressAlgo == 2) + maxSize = (uint32_t)(uncompressedLen + uncompressedLen / 3 + 128 + LZMA_PROPS_SIZE); + else +#ifdef _WITHOUT_LZ4 + return false; +#else + maxSize = (uint32_t)LZ4_compressBound((int)uncompressedLen); +#endif + void *compressBuf = malloc(maxSize); + if (compressBuf) + { +#ifndef _WITHOUT_LZ4 + if (compressAlgo == 1) + compressedLen = (uint32_t)LZ4_compress_default((char*)uncompressedBuf, (char*)compressBuf, (int)uncompressedLen, maxSize); + else +#endif + if (compressAlgo == 2) + { + size_t destLen = maxSize - LZMA_PROPS_SIZE; size_t propsLen = LZMA_PROPS_SIZE; + int result = LzmaCompress( + &((uint8_t*)compressBuf)[LZMA_PROPS_SIZE], &destLen, + (uint8_t*)uncompressedBuf, uncompressedLen, + (uint8_t*)compressBuf, &propsLen, + -1, 0, -1, -1, -1, -1, -1); + if ((propsLen != LZMA_PROPS_SIZE) || (result != SZ_OK)) + compressedLen = 0; + else + compressedLen = (uint32_t)(destLen + propsLen); + } + else + compressedLen = 0; + } + if (!compressBuf || !compressedLen) + { + //out of memory or compression failure, but we can still write the uncompressed data + success = false; + compressedLen = uncompressedLen; + pWriter->Write(filePos, uncompressedLen, uncompressedBuf); + } + else + { + //compression succeeded + pWriter->Write(filePos, compressedLen, compressBuf); + } + if (compressBuf) + free(compressBuf); + return success; +} +ASSETSTOOLS_API QWORD ClassDatabasePackage::Write(IAssetsWriter *pWriter, QWORD filePos, int optimizeStringTable, uint32_t compress) +{ + compress = (((compress & 0x1F) > 2) ? 0 : (compress & 0x1F)) | (compress & 0xE0); + uint8_t compressAlgo = compress & 0x1F; + header.compressionType = compress; +#if ClassDatabasePackageVersion == 1 + header.fileVersion = ((compress & 0xE0) != 0) ? 1 : 0; +#else + header.fileVersion = ClassDatabasePackageVersion; +#endif + + StringTableWriter strTableWriter; + if (optimizeStringTable == 2) + { + size_t stringCount = 0; + for (uint32_t i = 0; i < header.fileCount; i++) + { + for (uint32_t k = 0; k < (uint32_t)this->files[i]->classes.size(); k++) + { + ClassDatabaseType *pType = &this->files[i]->classes[k]; + stringCount += 1 + ((uint32_t)pType->fields.size()) * 2; + } + } + const char **stringList = (const char**)malloc(sizeof(char*) * stringCount); + bool optResult = false; + if (stringList) + { + size_t curStringIndex = 0; + for (uint32_t i = 0; i < header.fileCount; i++) + { + ClassDatabaseFile *pCurFile = this->files[i]; + for (uint32_t k = 0; k < (uint32_t)pCurFile->classes.size(); k++) + { + ClassDatabaseType *pType = &pCurFile->classes[k]; + stringList[curStringIndex] = pType->name.GetString(pCurFile); + curStringIndex++; + for (uint32_t l = 0; l < (uint32_t)pType->fields.size(); l++) + { + stringList[curStringIndex] = pType->fields[l].fieldName.GetString(pCurFile); + curStringIndex++; + stringList[curStringIndex] = pType->fields[l].typeName.GetString(pCurFile); + curStringIndex++; + } + } + } + optResult = strTableWriter.AddStringsOptimized(stringList, curStringIndex); + free(stringList); + } + if (!optResult) + MessageBox(NULL, TEXT("Out of memory while optimizing the string table!"), TEXT("ERROR"), 0); + } + for (uint32_t i = 0; i < header.fileCount; i++) + { + for (uint32_t k = 0; k < (uint32_t)this->files[i]->classes.size(); k++) + { + ClassDatabaseType *pType = (ClassDatabaseType*)&this->files[i]->classes[k]; + pType->name.str.stringTableOffset = (uint32_t)strTableWriter.AddString(pType->name.GetString(this->files[i]), optimizeStringTable ? true : false); + pType->name.fromStringTable = true; + for (uint32_t l = 0; l < (uint32_t)pType->fields.size(); l++) + { + pType->fields[l].fieldName.str.stringTableOffset = + (uint32_t)strTableWriter.AddString(pType->fields[l].fieldName.GetString(this->files[i]), optimizeStringTable ? true : false); + pType->fields[l].fieldName.fromStringTable = true; + pType->fields[l].typeName.str.stringTableOffset = + (uint32_t)strTableWriter.AddString(pType->fields[l].typeName.GetString(this->files[i]), optimizeStringTable ? true : false); + pType->fields[l].typeName.fromStringTable = true; + } + } + } + if (this->stringTable) + { + free(this->stringTable); + this->stringTable = NULL; + } + this->stringTable = strTableWriter.GetStringTable(); + header.stringTableLenUncompressed = (uint32_t)strTableWriter.GetStringTableLen(); + QWORD headerPos = filePos; + filePos = header.Write(pWriter, filePos); + + { + IAssetsWriter *pFileWriter = pWriter; + IAssetsWriterToMemory *pBlockWriter = nullptr; + QWORD blockFilePos = filePos; + if ((compress & 0x80) && !(compress & 0x20)) + { + pBlockWriter = Create_AssetsWriterToMemory(); + pFileWriter = pBlockWriter; + blockFilePos = 0; + } + for (uint32_t i = 0; i < header.fileCount; i++) + { + if (!this->files[i]->dontFreeStringTable) + { + this->files[i]->dontFreeStringTable = true; + if (this->files[i]->stringTable) + free(this->files[i]->stringTable); + } + this->files[i]->stringTable = this->stringTable; + this->files[i]->header.stringTableLen = 0; //temporarily for writing + header.files[i].offset = (uint32_t)blockFilePos; + blockFilePos = this->files[i]->Write(pFileWriter, blockFilePos, false, (compress & 0x80) ? 0 : compressAlgo, false); + header.files[i].length = (uint32_t)blockFilePos - header.files[i].offset; + this->files[i]->header.stringTableLen = header.stringTableLenUncompressed; + } + if (compress & 0x80) + { + header.fileBlockSize = (uint32_t)blockFilePos; + + void *pBlockBuffer = nullptr; size_t blockBufferSize = 0; + if (!pBlockWriter->GetBuffer(pBlockBuffer, blockBufferSize)) + { + MessageBox(NULL, TEXT("Unable to retrieve the raw class databases!"), TEXT("ERROR"), 0); + header.fileBlockSize = 0; + } + + uint32_t compressedLen = 0; + if (!CompressToWriter(filePos, pWriter, pBlockBuffer, header.fileBlockSize, compressAlgo, compressedLen)) + header.compressionType |= 0x20; + filePos += compressedLen; + + Free_AssetsWriter(pBlockWriter); + } + } + + QWORD stringTablePos = filePos; + header.stringTableOffset = (uint32_t)stringTablePos; + header.stringTableLenCompressed = header.stringTableLenUncompressed; + memcpy(&header.magic[0], "CLPK", 4); + if (compressAlgo && (compress & 0x40) == 0) + { + if (!CompressToWriter(filePos, pWriter, this->stringTable, header.stringTableLenUncompressed, compressAlgo, header.stringTableLenCompressed)) + header.compressionType |= 0x40; + } + else + pWriter->Write(stringTablePos, header.stringTableLenUncompressed, this->stringTable); + header.Write(pWriter, headerPos); + return stringTablePos + header.stringTableLenCompressed; +} +ASSETSTOOLS_API bool ClassDatabasePackage::RemoveFile(uint32_t index) +{ + if (index >= header.fileCount) + return false; + if (files == NULL) + return false; + header.files.erase(header.files.begin() + index); + ClassDatabaseFile **newFiles = new PClassDatabaseFile[(header.fileCount - 1)]; + memcpy(newFiles, files, sizeof(ClassDatabaseFile*) * index); + memcpy(&newFiles[index], &files[index+1], sizeof(ClassDatabaseFile*) * (header.fileCount - index - 1)); + delete files[index]; + delete[] files; + files = newFiles; + header.fileCount--; + return true; +} +ASSETSTOOLS_API bool ClassDatabasePackage::ImportFile(IAssetsReader *pReader) +{ + ClassDatabaseFile **newFiles = new PClassDatabaseFile[(header.fileCount + 1)]; + memcpy(newFiles, files, sizeof(ClassDatabaseFile*) * header.fileCount); + ClassDatabaseFile *pFile = new ClassDatabaseFile(); + if (!pFile->Read(pReader)) + { + delete pFile; + delete[] newFiles; + return false; + } + newFiles[header.fileCount] = pFile; + delete[] files; + files = newFiles; + ClassDatabaseFileRef fileRef; + fileRef.length = fileRef.offset = 0; + memset(fileRef.name, 0, 16); + header.files.push_back(fileRef); + header.fileCount++; + return true; +} +ASSETSTOOLS_API bool ClassDatabasePackage::IsValid() +{ + return valid; +} + +ASSETSTOOLS_API ClassDatabasePackage::~ClassDatabasePackage() +{ + Clear(); +} \ No newline at end of file diff --git a/AssetsTools/ClassDatabaseFile.h b/AssetsTools/ClassDatabaseFile.h new file mode 100644 index 0000000..ad8b527 --- /dev/null +++ b/AssetsTools/ClassDatabaseFile.h @@ -0,0 +1,157 @@ +#pragma once +#include "AssetsFileFormat.h" +//#include "AssetTypeClass.h" +#include "defines.h" +#include + +//custom file type to store Unity type information +#define ClassDatabaseFileVersion 4 +#define ClassDatabaseCompressionType 1 //LZ4 compress by default +#define ClassDatabasePackageVersion 1 + +class ClassDatabaseFile; +struct ClassDatabaseFileString +{ + union { + //Don't trust this offset! GetString makes sure no out-of-bounds offset is used. + uint32_t stringTableOffset; + const char *string; + } str; + bool fromStringTable = false; + inline ClassDatabaseFileString() { str.string = nullptr; } + ASSETSTOOLS_API const char *GetString(ClassDatabaseFile *pFile); + ASSETSTOOLS_API QWORD Read(IAssetsReader *pReader, QWORD filePos); + ASSETSTOOLS_API QWORD Write(IAssetsWriter *pWriter, QWORD filePos); +}; +struct ClassDatabaseTypeField +{ + ClassDatabaseFileString typeName; + ClassDatabaseFileString fieldName; + uint8_t depth = 0; + uint8_t isArray = 0; + uint32_t size = 0; + //uint32_t index; + uint16_t version = 0; + uint32_t flags2 = 0; //Flag 0x4000 : align to 4 bytes after this field. + + ASSETSTOOLS_API QWORD Read(IAssetsReader *pReader, QWORD filePos, int version); //reads version 0,1,2,3 + ASSETSTOOLS_API QWORD Write(IAssetsWriter *pWriter, QWORD filePos, int version); //writes version 1,2,3 +}; +class AssetTypeTemplateField; +class ClassDatabaseType +{ +public: + int classId = 0; + int baseClass = 0; + ClassDatabaseFileString name; + ClassDatabaseFileString assemblyFileName; //set if (header.flags & 1) + std::vector fields; + //uint32_t fieldCount; + //ClassDatabaseTypeField *fields; + ASSETSTOOLS_API ClassDatabaseType(); + ASSETSTOOLS_API ClassDatabaseType(const ClassDatabaseType& other); + ASSETSTOOLS_API ~ClassDatabaseType(); + ASSETSTOOLS_API QWORD Read(IAssetsReader *pReader, QWORD filePos, int version, uint8_t flags); + ASSETSTOOLS_API QWORD Write(IAssetsWriter *pWriter, QWORD filePos, int version, uint8_t flags); + //Does NOT copy strings, so the ClassDatabaseType must not be used after the template field's strings are freed! + ASSETSTOOLS_API bool FromTemplateField(int classId, int baseClass, AssetTypeTemplateField *pTemplateBase); + + ASSETSTOOLS_API Hash128 MakeTypeHash(ClassDatabaseFile *pDatabaseFile); +}; +ASSETSTOOLS_API Hash128 MakeScriptID(const char *scriptName, const char *scriptNamespace, const char *scriptAssembly); +struct ClassDatabaseFileHeader +{ + char header[4] = {}; + uint8_t fileVersion = 0; + uint8_t flags = 0; //1 : Describes MonoBehaviour classes (contains assembly and full class names, base field is to be ignored) + + uint8_t compressionType = 0; //version 2; 0 = none, 1 = LZ4 + uint32_t compressedSize = 0, uncompressedSize = 0; //version 2 + //uint8_t assetsVersionCount; //version 0 only + //uint8_t *assetsVersions; //version 0 only + + uint8_t unityVersionCount = 0; + char **pUnityVersions = nullptr; + + + uint32_t stringTableLen = 0; + uint32_t stringTablePos = 0; + ASSETSTOOLS_API QWORD Read(IAssetsReader *pReader, QWORD filePos); + ASSETSTOOLS_API QWORD Write(IAssetsWriter *pWriter, QWORD filePos); + //uint32_t _tmp; //used only if assetsVersions == NULL; version 0 only +}; +//all classes that override Component : prepend PPtr m_GameObject +//Transform : add vector m_Children {Array Array {int size; PPtr data}} +class ClassDatabaseFile +{ + bool valid; +public: + //Only for internal use, otherwise this could create a memory leak! + bool dontFreeStringTable; + ClassDatabaseFileHeader header; + + std::vector classes; + //uint32_t classCount; + //ClassDatabaseType *classes; + + char *stringTable; + +public: + + ASSETSTOOLS_API QWORD Read(IAssetsReader *pReader, QWORD filePos); + ASSETSTOOLS_API bool Read(IAssetsReader *pReader); + ASSETSTOOLS_API QWORD Write(IAssetsWriter *pWriter, QWORD filePos, int optimizeStringTable=1, uint32_t compress=1, bool writeStringTable=true); + ASSETSTOOLS_API bool IsValid(); + + ASSETSTOOLS_API bool InsertFrom(ClassDatabaseFile *pOther, ClassDatabaseType *pType); + ASSETSTOOLS_API void Clear(); + + ASSETSTOOLS_API ClassDatabaseFile(); + ASSETSTOOLS_API ClassDatabaseFile &operator=(const ClassDatabaseFile& other); + ASSETSTOOLS_API ClassDatabaseFile(const ClassDatabaseFile& other); + ASSETSTOOLS_API ClassDatabaseFile(ClassDatabaseFile&& other); + ASSETSTOOLS_API ~ClassDatabaseFile(); +}; +typedef ClassDatabaseFile* PClassDatabaseFile; + +ASSETSTOOLS_API void FreeClassDatabase_Dummy(ClassDatabaseFile *pFile); + +struct ClassDatabaseFileRef +{ + uint32_t offset; + uint32_t length; + char name[16]; +}; +struct ClassDatabasePackageHeader +{ + char magic[4] = {}; //"CLPK" + uint8_t fileVersion = 0; //0 or 1 + uint8_t compressionType = 0; //Version 1 flags : 0x80 compressed all files in one block; 0x40 string table uncompressed; 0x20 file block uncompressed; + uint32_t stringTableOffset = 0, stringTableLenUncompressed = 0, stringTableLenCompressed = 0; + uint32_t fileBlockSize = 0; + uint32_t fileCount = 0; + std::vector files; + ASSETSTOOLS_API QWORD Read(IAssetsReader *pReader, QWORD filePos); + ASSETSTOOLS_API QWORD Write(IAssetsWriter *pWriter, QWORD filePos); +}; + +//contains multiple ClassDatabaseFiles and stores the string table of all in one block +class ClassDatabasePackage +{ + bool valid = false; +public: + ClassDatabasePackageHeader header; + ClassDatabaseFile **files = nullptr; + char *stringTable = nullptr; + +public: + + ASSETSTOOLS_API void Clear(); + ASSETSTOOLS_API bool Read(IAssetsReader *pReader); + ASSETSTOOLS_API QWORD Write(IAssetsWriter *pWriter, QWORD filePos, int optimizeStringTable=1, uint32_t compress=1); + ASSETSTOOLS_API bool RemoveFile(uint32_t index); + ASSETSTOOLS_API bool ImportFile(IAssetsReader *pReader); + ASSETSTOOLS_API bool IsValid(); + + ASSETSTOOLS_API ~ClassDatabasePackage(); +}; \ No newline at end of file diff --git a/AssetsTools/EngineVersion.cpp b/AssetsTools/EngineVersion.cpp new file mode 100644 index 0000000..2ee854f --- /dev/null +++ b/AssetsTools/EngineVersion.cpp @@ -0,0 +1,22 @@ +#include "EngineVersion.h" + +EngineVersion EngineVersion::parse(std::string versionString) +{ + unsigned int unityYear = 0, unityRelease = 0; + { + char toIntTemp[7] = {}; + if (versionString.size() > 6 && versionString[4] == '.' && versionString[6] == '.') + { + *(uint32_t*)&toIntTemp[0] = *(uint32_t*)&versionString[0]; + toIntTemp[5] = versionString[5]; + } + else if (versionString.size() > 3 && versionString[1] == '.' && versionString[3] == '.') + { + toIntTemp[0] = versionString[0]; + toIntTemp[5] = versionString[2]; + } + unityYear = (unsigned int)strtol(toIntTemp, NULL, 10); + unityRelease = (unsigned int)strtol(&toIntTemp[5], NULL, 10); + } + return EngineVersion{ unityYear, unityRelease }; +} diff --git a/AssetsTools/EngineVersion.h b/AssetsTools/EngineVersion.h new file mode 100644 index 0000000..daf5d33 --- /dev/null +++ b/AssetsTools/EngineVersion.h @@ -0,0 +1,13 @@ +#pragma once +#include "defines.h" +#include +#include +#include +//Represents the two 'major' Unity release numbers, +// as these are usually the relevant ones for version comparisons. +struct EngineVersion +{ + unsigned int year=0; //3,4,5,2017,... + unsigned int release=0; //Usually numbered 1,2,... + ASSETSTOOLS_API static EngineVersion parse(std::string versionString); +}; diff --git a/AssetsTools/InternalAssetsReplacer.h b/AssetsTools/InternalAssetsReplacer.h new file mode 100644 index 0000000..97752d1 --- /dev/null +++ b/AssetsTools/InternalAssetsReplacer.h @@ -0,0 +1,159 @@ +#pragma once +#include "../AssetsTools/AssetsReplacer.h" +#include "../AssetsTools/defines.h" + +enum EAssetsReplacers +{ + AssetsReplacer_AssetRemover, + AssetsReplacer_AssetModifierFromReader, + AssetsReplacer_AssetModifierFromMemory, + AssetsReplacer_AssetModifierFromFile, + AssetsReplacer_Dependencies +}; + +class AssetsEntryReplacerBase : public AssetsEntryReplacer +{ +public: + uint32_t fileID; + QWORD pathID; + int classID; + uint16_t monoScriptIndex; + std::vector preloadDependencies; + + AssetsEntryReplacerBase(); + AssetsEntryReplacerBase(uint32_t fileID, QWORD pathID, int classID, uint16_t monoScriptIndex); + + uint32_t GetFileID() const; + QWORD GetPathID() const; + int GetClassID() const; + uint16_t GetMonoScriptID() const; + void SetMonoScriptID(uint16_t scriptID); + + bool GetPropertiesHash(Hash128 &propertiesHash); + bool SetPropertiesHash(const Hash128 &propertiesHash); + bool GetScriptIDHash(Hash128 &scriptIDHash); + bool SetScriptIDHash(const Hash128 &scriptIDHash); + + bool GetTypeInfo(std::shared_ptr &pFile, ClassDatabaseType *&pType); + bool SetTypeInfo(std::shared_ptr pFile, ClassDatabaseType *pType); + + bool GetPreloadDependencies(const AssetPPtr *&pPreloadList, size_t &preloadListSize); + bool SetPreloadDependencies(const AssetPPtr *pPreloadList, size_t preloadListSize); + bool AddPreloadDependency(const AssetPPtr &dependency); + + //Writes the elements of AssetsReplacerBase with the version headers (always writes fileID 0). + QWORD WriteReplacer(QWORD pos, IAssetsWriter *pWriter); +}; + +class AssetRemover : public AssetsEntryReplacerBase +{ +public: + AssetRemover(uint32_t fileID, QWORD pathID, int classID, uint16_t monoScriptIndex = 0xFFFF); + ~AssetRemover(); + AssetsReplacementType GetType() const; + + QWORD GetSize() const; + QWORD Write(QWORD pos, IAssetsWriter *pWriter); + QWORD WriteReplacer(QWORD pos, IAssetsWriter *pWriter); +}; + +class AssetEntryModifierBase : public AssetsEntryReplacerBase +{ +public: + std::shared_ptr pClassFile; + ClassDatabaseType *pClassType; + + Hash128 propertiesHash; + Hash128 scriptIDHash; + bool hasPropertiesHash, hasScriptIDHash; +public: + AssetEntryModifierBase(); + AssetEntryModifierBase(uint32_t fileID, QWORD pathID, int classID, uint16_t monoScriptIndex); + ~AssetEntryModifierBase(); + + bool GetPropertiesHash(Hash128 &propertiesHash); + bool SetPropertiesHash(const Hash128 &propertiesHash); + bool GetScriptIDHash(Hash128 &scriptIDHash); + bool SetScriptIDHash(const Hash128 &scriptIDHash); + + bool GetTypeInfo(std::shared_ptr &pFile, ClassDatabaseType *&pType); + bool SetTypeInfo(std::shared_ptr pFile, ClassDatabaseType *pType); + + //Writes the elements of AssetModifierBase and AssetsReplacerBase with their version headers. + QWORD WriteReplacer(QWORD pos, IAssetsWriter *pWriter); +}; + +class AssetModifierFromReader : public AssetEntryModifierBase +{ + std::shared_ptr ref_pReader; +public: + IAssetsReader *pReader; + QWORD size; + QWORD readerPos; + size_t copyBufferLen; + bool freeReader; + + AssetModifierFromReader(); + AssetModifierFromReader(uint32_t fileID, QWORD pathID, int classID, uint16_t monoScriptIndex, + IAssetsReader *pReader, QWORD size, QWORD readerPos=0, + size_t copyBufferLen=0, bool freeReader=false, + std::shared_ptr ref_pReader = nullptr); + ~AssetModifierFromReader(); + AssetsReplacementType GetType() const; + + QWORD GetSize() const; + QWORD Write(QWORD pos, IAssetsWriter *pWriter); + QWORD WriteReplacer(QWORD pos, IAssetsWriter *pWriter); +}; + +class AssetModifierFromMemory : public AssetEntryModifierBase +{ +public: + void *buffer; + size_t size; + cbFreeMemoryResource freeMemCallback; + + AssetModifierFromMemory(uint32_t fileID, QWORD pathID, int classID, uint16_t monoScriptIndex, + void *buffer, size_t size, cbFreeMemoryResource freeMemCallback = NULL); + ~AssetModifierFromMemory(); + AssetsReplacementType GetType() const; + + QWORD GetSize() const; + QWORD Write(QWORD pos, IAssetsWriter *pWriter); + QWORD WriteReplacer(QWORD pos, IAssetsWriter *pWriter); +}; + +class AssetModifierFromFile : public AssetEntryModifierBase +{ +public: + FILE *pFile; + QWORD offs; + QWORD size; + size_t copyBufferLen; + bool freeFile; + + AssetModifierFromFile(uint32_t fileID, QWORD pathID, int classID, uint16_t monoScriptIndex, + FILE *pFile, QWORD offs, QWORD size, size_t copyBufferLen=0, bool freeFile=true); + ~AssetModifierFromFile(); + AssetsReplacementType GetType() const; + + QWORD GetSize() const; + QWORD Write(QWORD pos, IAssetsWriter *pWriter); + QWORD WriteReplacer(QWORD pos, IAssetsWriter *pWriter); +}; + +class AssetsDependenciesReplacerImpl : public AssetsDependenciesReplacer +{ +public: + uint32_t fileID; + std::vector dependencies; + + AssetsDependenciesReplacerImpl(uint32_t fileID, std::vector dependencies); + + AssetsReplacementType GetType() const; + uint32_t GetFileID() const; + + const std::vector& GetDependencies() const; + + QWORD WriteReplacer(QWORD pos, IAssetsWriter* pWriter); +}; diff --git a/AssetsTools/InternalBundleReplacer.h b/AssetsTools/InternalBundleReplacer.h new file mode 100644 index 0000000..6c0a075 --- /dev/null +++ b/AssetsTools/InternalBundleReplacer.h @@ -0,0 +1,209 @@ +#pragma once +#include "../AssetsTools/BundleReplacer.h" +#include "../AssetsTools/AssetsReplacer.h" +#include "../AssetsTools/ClassDatabaseFile.h" + +enum EBundleReplacers +{ + BundleReplacer_BundleEntryRemover, + BundleReplacer_BundleEntryRenamer, + BundleReplacer_BundleEntryModifier, + BundleReplacer_BundleEntryModifierFromMem, + BundleReplacer_BundleEntryModifierFromAssets, + BundleReplacer_BundleEntryModifierFromBundle, + BundleReplacer_BundleEntryModifierByResources, + BundleReplacer_MAX, +}; + +class BundleEntryRemover : public BundleReplacer +{ + protected: + unsigned int bundleListIndex; + char *originalEntryName; + + public: + BundleEntryRemover(const char *name, unsigned int bundleListIndex); + BundleReplacementType GetType(); + ~BundleEntryRemover(); + unsigned int GetBundleListIndex(); + const char *GetOriginalEntryName(); + const char *GetEntryName(); + QWORD GetSize(); + bool Init(AssetBundleFile *pBundleFile, + IAssetsReader *pEntryReader, + QWORD entryPos, QWORD entrySize, + ClassDatabaseFile *typeMeta = NULL); + void Uninit(); + QWORD Write(QWORD pos, IAssetsWriter *pWriter); + QWORD WriteReplacer(QWORD pos, IAssetsWriter *pWriter); + bool HasSerializedData(); + bool RequiresEntryReader(); +}; +class BundleEntryRenamer : public BundleReplacer +{ + protected: + unsigned int bundleListIndex; + char *originalEntryName; + char *newEntryName; + bool hasSerializedData; + + public: + BundleEntryRenamer(const char *oldName, const char *newName, unsigned int bundleListIndex, bool hasSerializedData); + BundleReplacementType GetType(); + ~BundleEntryRenamer(); + unsigned int GetBundleListIndex(); + const char *GetOriginalEntryName(); + const char *GetEntryName(); + QWORD GetSize(); + bool Init(AssetBundleFile *pBundleFile, + IAssetsReader *pEntryReader, + QWORD entryPos, QWORD entrySize, + ClassDatabaseFile *typeMeta = NULL); + void Uninit(); + QWORD Write(QWORD pos, IAssetsWriter *pWriter); + QWORD WriteReplacer(QWORD pos, IAssetsWriter *pWriter); + bool HasSerializedData(); + bool RequiresEntryReader(); +}; + +class BundleEntryModifier : public BundleEntryRenamer +{ + public: + std::shared_ptr pReader; + QWORD size; + QWORD readerPos; + size_t copyBufferLen; + public: + BundleEntryModifier(const char *oldName, const char *newName, unsigned int bundleListIndex, bool hasSerializedData, + std::shared_ptr pReader, QWORD size, QWORD readerPos, + size_t copyBufferLen); + BundleReplacementType GetType(); + ~BundleEntryModifier(); + QWORD GetSize(); + bool Init(AssetBundleFile *pBundleFile, + IAssetsReader *pEntryReader, + QWORD entryPos, QWORD entrySize, + ClassDatabaseFile *typeMeta = NULL); + void Uninit(); + QWORD Write(QWORD pos, IAssetsWriter *pWriter); + QWORD WriteReplacer(QWORD pos, IAssetsWriter *pWriter); +}; + +class BundleEntryModifierFromMem : public BundleEntryRenamer +{ + public: + void *pMem; + size_t size; + cbFreeMemoryResource freeResourceCallback; + + public: + //freeMemoryCallback is used for pMem and if (freeNames) for oldName/newName + BundleEntryModifierFromMem(const char *oldName, const char *newName, unsigned int bundleListIndex, bool hasSerializedData, + void *pMem, size_t size, cbFreeMemoryResource freeMemoryCallback); + BundleReplacementType GetType(); + ~BundleEntryModifierFromMem(); + QWORD GetSize(); + bool Init(AssetBundleFile *pBundleFile, + IAssetsReader *pEntryReader, + QWORD entryPos, QWORD entrySize, + ClassDatabaseFile *typeMeta = NULL); + void Uninit(); + QWORD Write(QWORD pos, IAssetsWriter *pWriter); + QWORD WriteReplacer(QWORD pos, IAssetsWriter *pWriter); +}; +class BundleEntryModifierFromAssets : public BundleEntryRenamer +{ + protected: + AssetsFile *pAssetsFile; + std::vector pReplacers; + ClassDatabaseFile *typeMeta; + uint32_t fileId; + bool freeAssetsFile; + + // Optional reference holders. + std::vector> pReplacers_shared; + std::shared_ptr typeMeta_shared; + + public: + BundleEntryModifierFromAssets(const char *oldName, const char *newName, unsigned int bundleListIndex, + AssetsFile *pAssetsFile, AssetsReplacer **pReplacers, size_t replacerCount, uint32_t fileId); + BundleEntryModifierFromAssets(const char *oldName, const char *newName, unsigned int bundleListIndex, + std::shared_ptr typeMeta, std::vector> pReplacers, uint32_t fileId); + BundleReplacementType GetType(); + ~BundleEntryModifierFromAssets(); + QWORD GetSize(); + bool Init(AssetBundleFile *pBundleFile, + IAssetsReader *pEntryReader, + QWORD entryPos, QWORD entrySize, + ClassDatabaseFile *typeMeta = NULL); + void Uninit(); + QWORD Write(QWORD pos, IAssetsWriter *pWriter); + QWORD WriteReplacer(QWORD pos, IAssetsWriter *pWriter); + ASSETSTOOLS_API AssetsReplacer **GetReplacers(size_t &count); + ASSETSTOOLS_API AssetsFile *GetAssignedAssetsFile(); + //Returns -1 if not set. + ASSETSTOOLS_API uint32_t GetFileID(); + bool RequiresEntryReader(); +}; +class BundleEntryModifierFromBundle : public BundleEntryRenamer +{ + protected: + std::vector pReplacers; + AssetBundleFile *pBundleFile; + IAssetsReader *pBundleReader; + ClassDatabaseFile *typeMeta; + bool freeBundleFile; + + // Optional reference holders. + std::vector> pReplacers_unique; + + public: + BundleEntryModifierFromBundle(const char *oldName, const char *newName, unsigned int bundleListIndex, + BundleReplacer **pReplacers, size_t replacerCount); + BundleEntryModifierFromBundle(const char *oldName, const char *newName, unsigned int bundleListIndex, + std::vector> pReplacers); + BundleReplacementType GetType(); + ~BundleEntryModifierFromBundle(); + QWORD GetSize(); + bool Init(AssetBundleFile *pBundleFile, + IAssetsReader *pEntryReader, + QWORD entryPos, QWORD entrySize, + ClassDatabaseFile *typeMeta = NULL); + void Uninit(); + QWORD Write(QWORD pos, IAssetsWriter *pWriter); + QWORD WriteReplacer(QWORD pos, IAssetsWriter *pWriter); + ASSETSTOOLS_API BundleReplacer **GetReplacers(size_t &count); + bool RequiresEntryReader(); +}; +class BundleEntryModifierByResources : public BundleEntryRenamer +{ + std::vector resources; + size_t copyBufferLen; + IAssetsReader* pEntryReader = nullptr; + QWORD entryPos = 0, entrySize = 0; +public: + inline const std::vector& getResources() + { + return resources; + } + inline QWORD getSize() + { + return resources.empty() ? 0 : (resources.back().outRangeBegin + resources.back().rangeSize); + } +public: + BundleEntryModifierByResources(const char* oldName, const char* newName, unsigned int bundleListIndex, + std::vector resources, + size_t copyBufferLen); + BundleReplacementType GetType(); + ~BundleEntryModifierByResources(); + QWORD GetSize(); + bool Init(AssetBundleFile* pBundleFile, + IAssetsReader* pEntryReader, + QWORD entryPos, QWORD entrySize, + ClassDatabaseFile* typeMeta = NULL); + void Uninit(); + QWORD Write(QWORD pos, IAssetsWriter* pWriter); + QWORD WriteReplacer(QWORD pos, IAssetsWriter* pWriter); + bool RequiresEntryReader(); + +}; diff --git a/AssetsTools/ResourceManagerFile.cpp b/AssetsTools/ResourceManagerFile.cpp new file mode 100644 index 0000000..e5fa23d --- /dev/null +++ b/AssetsTools/ResourceManagerFile.cpp @@ -0,0 +1,364 @@ +#include "stdafx.h" +#include "../AssetsTools/AssetsFileFormat.h" +#include "../AssetsTools/ResourceManagerFile.h" + +#define fwwrite(source,count) {if (pWriter->Write(count, source) != count) return false; filePos += count;} +#define fwalign() {uint32_t zero = 0; uint32_t nAlign = _fmalign(filePos) - filePos; if (nAlign != 0 && pWriter->Write(nAlign, &zero) != nAlign) return false; filePos += nAlign;} +#define _fmalign(fpos) (fpos + 3) & ~3 +#define fmalign() {*filePos = _fmalign(*filePos);} + +ASSETSTOOLS_API bool ResourceManagerFile::Write(IAssetsWriter *pWriter, size_t *size) +{ + if (!isRead) + return false; + size_t filePos = 0; + int iTmp; + + uint32_t containerArrayLen = (uint32_t)containers.size(); + fwwrite(&containerArrayLen, 4); + for (size_t i = 0; i < containerArrayLen; i++) + { + ResourceManager_ContainerData &cd = containers[i]; + + iTmp = cd.name.size(); + if (iTmp < 0) iTmp = 0; + fwwrite(&iTmp, 4); + fwwrite(cd.name.c_str(), iTmp); + fwalign(); + + fwwrite(&cd.ids.fileId, 4); + fwwrite(&cd.ids.pathId, ((unityVersion>=0x0E)?8:4)); + } + + uint32_t dependenciesArrayLen = (uint32_t)dependencyLists.size(); + fwwrite(&dependenciesArrayLen, 4); + for (size_t i = 0; i < dependenciesArrayLen; i++) + { + ResourceManager_AssetDependencies &dependencyList = dependencyLists[i]; + fwwrite(&dependencyList.asset.fileId, 4); + fwwrite(&dependencyList.asset.pathId, ((unityVersion>=0x0E)?8:4)); + + uint32_t dependencyCount = dependencyList.dependencies.size(); + fwwrite(&dependencyCount, 4); + for (int k = 0; k < dependencyCount; k++) + { + ResourceManager_PPtr &pptr = dependencyList.dependencies[k]; + fwwrite(&pptr.fileId, 4); + fwwrite(&pptr.pathId, ((unityVersion>=0x0E)?8:4)); + } + } + + if (size) + *size = filePos; + return true; +} + +ASSETSTOOLS_API size_t ResourceManagerFile::GetFileSize() +{ + if (!isRead) + return -1; + int ret = 0; + + ret += 4; //containerArrayLen + for (size_t i = 0; i < containers.size(); i++) + ret += _fmalign(containers[i].name.size()); //strlen(containerArray::name) + ret += (4 + ((unityVersion>=0x0E)?12:8)) * containers.size(); //sizeof(containerArray::ids) + + ret += 4; //dependenciesArrayLen + ret += dependencyLists.size() * (4 + ((unityVersion>=0x0E)?12:8)); //sizeof(assetDependency::asset) + for (size_t i = 0; i < dependencyLists.size(); i++) + ret += (dependencyLists[i].dependencies.size() * ((unityVersion>=0x0E)?12:8)); //assetDependency::dependencies + + return ret; +} + +ASSETSTOOLS_API ResourceManagerFile::ResourceManagerFile() +{ + isModified = false; + isRead = false; + unityVersion = 0; +} + +ASSETSTOOLS_API void ResourceManagerFile::Clear() +{ + isRead = false; + containers.clear(); + dependencyLists.clear(); +} + +ASSETSTOOLS_API ResourceManagerFile::~ResourceManagerFile() +{ + this->Clear(); +} + +//#define fmxread(target,count,onerr) {if ((*filePos + count) > dataLen) {memset(target, 0, count);onerr;} else {memcpy(target, &((uint8_t*)data)[*filePos], count); *filePos = *filePos + count;}} +//#define fmread(target,count) fmxread(target,count,0) +inline bool fmread(void *target, size_t count, void *data, size_t dataLen, size_t &dataPos) +{ + if ((dataPos + count) > dataLen) + { + memset(target, 0, count); + return false; + } + else + { + memcpy(target, &((uint8_t*)data)[dataPos], count); + dataPos += count; + return true; + } +} +#include "AssetTypeClass.h" +ASSETSTOOLS_API bool ResourceManagerFile::Read(AssetTypeValueField *pBase) +{ + /* + ResourceManager Base + map m_Container + Array Array (IsArray) + int size + pair data + string first + Array Array (IsArray) + int size + char data + PPtr second + int m_FileID + SInt64 m_PathID + vector m_DependentAssets + Array Array (IsArray) + int size + ResourceManager_Dependency data + PPtr m_Object + int m_FileID + SInt64 m_PathID + vector m_Dependencies + Array Array (IsArray) + int size + PPtr data + int m_FileID + SInt64 m_PathID + */ + Clear(); + unityVersion = -1; + isModified = false; + AssetTypeValueField *pContainers = pBase->Get("m_Container")->Get("Array"); + + uint32_t containerArrayLen; + if (pContainers->IsDummy()) + { + containerArrayLen = 0; + Clear(); + return false; + } + else + containerArrayLen = pContainers->GetChildrenCount(); + containers.resize(containerArrayLen); + for (uint32_t i = 0; i < containerArrayLen; i++) + { + AssetTypeValueField *pContainerItem = pContainers->Get(i); + AssetTypeValue *nameValue = pContainerItem->Get(0U)->GetValue(); //"first" + if (nameValue && nameValue->AsString()) + containers[i].name.assign(nameValue->AsString()); + else + { + Clear(); + return false; + } + AssetTypeValueField *pContainerPPtr = pContainerItem->Get(1U); //"second" + AssetTypeValueField *pContainerPPtr_FileID = pContainerPPtr->Get(0U); //"m_FileID" + AssetTypeValueField *pContainerPPtr_PathID = pContainerPPtr->Get(1U); //"m_PathID" + if (pContainerPPtr_FileID->GetValue() && pContainerPPtr_FileID->GetValue()->GetType() == ValueType_Int32 + && pContainerPPtr_PathID->GetValue() && (pContainerPPtr_PathID->GetValue()->GetType() == ValueType_Int32 || pContainerPPtr_PathID->GetValue()->GetType() == ValueType_Int64)) + { + containers[i].ids.fileId = pContainerPPtr_FileID->GetValue()->AsInt(); + containers[i].ids.pathId = pContainerPPtr_PathID->GetValue()->AsInt64(); + } + else + { + Clear(); + return false; + } + } + + AssetTypeValueField *pDependencyLists = pBase->Get("m_DependentAssets")->Get("Array"); + uint32_t dependencyListCount; + if (pDependencyLists->IsDummy() || !pDependencyLists->GetValue() || !pDependencyLists->GetValue()->AsArray()) + { + Clear(); + return false; + } + else + dependencyListCount = pDependencyLists->GetChildrenCount(); + + dependencyLists.resize(dependencyListCount); + for (uint32_t i = 0; i < dependencyListCount; i++) + { + AssetTypeValueField *pDependencyList = pDependencyLists->Get(i); + ResourceManager_AssetDependencies &dependencyList = dependencyLists[i]; + AssetTypeValueField *pObjectFileID = pDependencyList->Get(0U)->Get(0U); + AssetTypeValueField *pObjectPathID = pDependencyList->Get(0U)->Get(1U); + if (pObjectFileID->GetValue() && pObjectFileID->GetValue()->GetType() == ValueType_Int32 + && pObjectPathID->GetValue() && (pObjectPathID->GetValue()->GetType() == ValueType_Int32 || pObjectPathID->GetValue()->GetType() == ValueType_Int64)) + { + dependencyList.asset.fileId = pObjectFileID->GetValue()->AsInt(); + dependencyList.asset.pathId = pObjectPathID->GetValue()->AsInt64(); + } + else + { + Clear(); + return false; + } + AssetTypeValueField *pDependencies = pDependencyList->Get(1U)->Get(0U); + if (pDependencies->GetValue() && pDependencies->GetValue()->AsArray()) + { + uint32_t dependencyCount = pDependencies->GetChildrenCount(); + dependencyList.dependencies.resize(dependencyCount); + for (uint32_t k = 0; k < dependencyCount; k++) + { + AssetTypeValueField *pDependency = pDependencies->Get(k); + ResourceManager_PPtr &dependency = dependencyList.dependencies[k]; + + AssetTypeValueField *pDependencyFileID = pDependency->Get(0U); + AssetTypeValueField *pDependencyPathID = pDependency->Get(1U); + if (pDependencyFileID->GetValue() && pDependencyFileID->GetValue()->GetType() == ValueType_Int32 + && pDependencyPathID->GetValue() && (pDependencyPathID->GetValue()->GetType() == ValueType_Int32 || pDependencyPathID->GetValue()->GetType() == ValueType_Int64)) + { + dependency.fileId = pDependencyFileID->GetValue()->AsInt(); + dependency.pathId = pDependencyPathID->GetValue()->AsInt64(); + } + } + } + else + { + Clear(); + return false; + } + } + + isRead = true; + return true; + +} +ASSETSTOOLS_API void ResourceManagerFile::Read(void *data, size_t dataLen, size_t *filePos, int assetsVersion, bool bigEndian) +{ + Clear(); + unityVersion = assetsVersion; + isModified = false; + + bool readErr = false; + uint32_t containerArrayLen = 0; + readErr |= !fmread(&containerArrayLen, 4, data, dataLen, *filePos); + if (bigEndian) + SwapEndians_(containerArrayLen); + if (readErr) + { + Clear(); + return; + } + containers.resize(containerArrayLen); + for (size_t i = 0; i < containerArrayLen; i++) + { + size_t assetNameSize = 0; readErr |= !fmread(&assetNameSize, 4, data, dataLen, *filePos); + if (bigEndian) + SwapEndians_(assetNameSize); + if ((*filePos + assetNameSize) > dataLen) + { + Clear(); + return; + } + std::unique_ptr assetName(new char[assetNameSize+1]); + readErr |= !fmread(assetName.get(), assetNameSize, data, dataLen, *filePos); assetName[assetNameSize] = 0; + containers[i].name.assign(assetName.get()); + fmalign(); + + readErr |= !fmread(&containers[i].ids.fileId, 4, data, dataLen, *filePos); + if (bigEndian) + SwapEndians_(containers[i].ids.fileId); + containers[i].ids.pathId = 0; + if (assetsVersion>=0x0E) + { + readErr |= !fmread(&containers[i].ids.pathId, 8, data, dataLen, *filePos); + if (bigEndian) + SwapEndians_(containers[i].ids.pathId); + } + else + { + readErr |= !fmread(&containers[i].ids.pathId, 4, data, dataLen, *filePos); + if (bigEndian) + SwapEndians_(*(uint32_t*)&containers[i].ids.pathId); + } + if (readErr) + { + Clear(); + return; + } + } + + uint32_t dependenciesArrayLen = 0; + readErr |= !fmread(&dependenciesArrayLen, 4, data, dataLen, *filePos); + if (bigEndian) + SwapEndians_(dependenciesArrayLen); + if (readErr) + { + Clear(); + return; + } + dependencyLists.resize(dependenciesArrayLen); + for (size_t i = 0; i < dependenciesArrayLen; i++) + { + ResourceManager_AssetDependencies &dependencyList = dependencyLists[i]; + readErr |= !fmread(&dependencyList.asset.fileId, 4, data, dataLen, *filePos); + if (bigEndian) + SwapEndians_(dependencyList.asset.fileId); + dependencyList.asset.pathId = 0; + if (assetsVersion>=0x0E) + { + readErr |= !fmread(&dependencyList.asset.pathId, 8, data, dataLen, *filePos); + if (bigEndian) + SwapEndians_(dependencyList.asset.pathId); + } + else + { + readErr |= !fmread(&dependencyList.asset.pathId, 4, data, dataLen, *filePos); + if (bigEndian) + SwapEndians_(*(uint32_t*)&dependencyList.asset.pathId); + } + + uint32_t dependencyCount = 0; + readErr |= !fmread(&dependencyCount, 4, data, dataLen, *filePos); + if (bigEndian) + SwapEndians_(dependencyCount); + if (readErr || (*filePos + ((size_t)dependencyCount) * (assetsVersion>=0x0E ? 8 : 4)) > dataLen) + { + Clear(); + return; + } + dependencyList.dependencies.resize(dependencyCount); + for (size_t k = 0; k < dependencyCount; k++) + { + ResourceManager_PPtr &pptr = dependencyList.dependencies[k]; + readErr |= !fmread(&pptr.fileId, 4, data, dataLen, *filePos); + if (bigEndian) + SwapEndians_(pptr.fileId); + pptr.pathId = 0; + if (assetsVersion>=0x0E) + { + readErr |= !fmread(&pptr.pathId, 8, data, dataLen, *filePos); + if (bigEndian) + SwapEndians_(pptr.pathId); + } + else + { + readErr |= !fmread(&pptr.pathId, 4, data, dataLen, *filePos); + if (bigEndian) + SwapEndians_(*(uint32_t*)&pptr.pathId); + } + } + if (readErr) + { + Clear(); + return; + } + } + + isRead = true; +} \ No newline at end of file diff --git a/AssetsTools/ResourceManagerFile.h b/AssetsTools/ResourceManagerFile.h new file mode 100644 index 0000000..78709b6 --- /dev/null +++ b/AssetsTools/ResourceManagerFile.h @@ -0,0 +1,70 @@ +#pragma once +#include "defines.h" +#include "AssetTypeClass.h" +#include +#include + +#define ASSETTYPE_RESOURCEMANAGER 147 +struct ResourceManager_PPtr +{ + int fileId = 0; + __int64 pathId = 0; +}; +struct ResourceManager_ContainerData +{ + std::string name; + ResourceManager_PPtr ids; +}; +struct ResourceManager_AssetDependencies +{ + ResourceManager_PPtr asset; + std::vector dependencies; +}; +class ResourceManagerFile +{ + bool isModified; + bool isRead; + int unityVersion; +public: + std::vector containers; + + std::vector dependencyLists; +public: + ASSETSTOOLS_API ResourceManagerFile(); + ASSETSTOOLS_API ~ResourceManagerFile(); + + ASSETSTOOLS_API bool Read(AssetTypeValueField *pBase); + ASSETSTOOLS_API void Read(void *data, size_t dataLen, size_t *filePos, int assetsVersion, bool bigEndian); + ASSETSTOOLS_API size_t GetFileSize(); + ASSETSTOOLS_API bool Write(IAssetsWriter *pWriter, size_t *size = nullptr); + + + inline void SetModified() + { + isModified = true; + } + inline bool IsModified() const + { + return isModified; + } + inline bool IsRead() const + { + return isRead; + } + + inline void AddContainer(ResourceManager_ContainerData &&cd) + { + containers.emplace_back(cd); + } + inline void AddContainer(ResourceManager_ContainerData &cd) + { + containers.push_back(cd); + } + inline void RemoveContainer(size_t index) + { + containers.erase(containers.begin() + index); + } + +private: + ASSETSTOOLS_API void Clear(); +}; \ No newline at end of file diff --git a/AssetsTools/TextureFileFormat.cpp b/AssetsTools/TextureFileFormat.cpp new file mode 100644 index 0000000..b9bf766 --- /dev/null +++ b/AssetsTools/TextureFileFormat.cpp @@ -0,0 +1,2323 @@ +#include "stdafx.h" +#include "../inc/half.hpp" +#include "TextureFileFormat.h" +#include +#include +#include +#include +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#include +#include + +struct HalfFloat +{ + unsigned short half; + //http://stackoverflow.com/questions/6162651/half-precision-floating-point-in-java/6162687#6162687 + inline float toFloat() + { + return half_float::detail::half2float(half); + } + inline void toHalf(float f) + { + half = half_float::detail::float2half(f); + } +}; +struct RGB9e5Float +{ + unsigned __int32 value; + inline void toRGB9e5(float in[3]) + { + static const float highestFloat = 65408.0; + + float r = in[0]; + float g = in[1]; + float b = in[2]; + + if (r <= 0.0) r = 0.0; + else if (r >= highestFloat) r = highestFloat; + + if (g <= 0.0) g = 0.0; + else if (g >= highestFloat) g = highestFloat; + + if (b <= 0.0) b = 0.0; + else if (b >= highestFloat) b = highestFloat; + + float tempColor; + if (r > g) tempColor = r; + else tempColor = g; + if (tempColor <= b) tempColor = b; + + int tempExponent = (int)( ((*((unsigned int*)(&tempColor)) >> 23) & 0xFF) - 127 ); + if (tempExponent < -16) tempExponent = -16; + tempExponent += 16; + int curExponentVal = tempExponent - 24; + int exponentVal = curExponentVal; + if (curExponentVal < 0) curExponentVal = -curExponentVal; + if (curExponentVal < 0) curExponentVal = std::numeric_limits::max(); + + float factorA = 2.0F; + float factorB = 1.0F; + while (curExponentVal & 1) + { + if (curExponentVal & 1) + factorB *= factorA; + factorA *= factorA; + curExponentVal >>= 1; + } + + float factorC; + if (exponentVal < 0) + factorC = 1.0 / factorB; + else + factorC = factorB; + + tempColor /= factorC; + if ( ((int)floor((double)tempColor + 0.5)) == 512 ) + { + factorC *= 2.0; + tempExponent++; + } + + unsigned int newValue = tempExponent; + newValue <<= 9; + newValue |= ((unsigned int)floorf((b / factorC) + 0.5)) & 0x1FF; + newValue <<= 9; + newValue |= ((unsigned int)floorf((g / factorC) + 0.5)) & 0x1FF; + newValue <<= 9; + newValue |= ((unsigned int)floorf((r / factorC) + 0.5)) & 0x1FF; + + value = newValue; + } + inline void toFloat(float out[3]) + { + unsigned int tempFloat_Int = ((value >> 4) & 0x0F800000) + 0x33800000; + float exponentFloat = *((float*)&tempFloat_Int); + out[0] = (float)(value & 0x1FF) * exponentFloat; + out[1] = (float)((value >> 9) & 0x1FF) * exponentFloat; + out[2] = (float)((value >> 18) & 0x1FF) * exponentFloat; + } +}; + +enum TextureFileFields //enumeration for the array that stores the fields in ReadTextureFile +{ + //fields that aren't in all versions + TextureFileField_MipCount, + TextureFileField_MipMap, + TextureFileField_ReadAllowed, + //fields that always must be there + TextureFileField_Name, + TextureFileField_Width, + TextureFileField_Height, + TextureFileField_CompleteImageSize, + TextureFileField_TextureFormat, + TextureFileField_IsReadable, + TextureFileField_ImageCount, + TextureFileField_TextureDimension, + TextureFileField_FilterMode, + TextureFileField_Aniso, + TextureFileField_MipBias, + TextureFileField_WrapMode, //except me (since U2017.1), replaced by WrapU&V&W + TextureFileField_LightmapFormat, + TextureFileField_ColorSpace, //and me (before U3.5) + TextureFileField_ImageData, + //fields added in later versions + TextureFileField_StreamingInfo_offset, + TextureFileField_StreamingInfo_size, + TextureFileField_StreamingInfo_path, + TextureFileField_WrapU, + TextureFileField_WrapV, + TextureFileField_WrapW, + TextureFileField_ForcedFallbackFormat, + TextureFileField_DownscaleFallback, + TextureFileField_StreamingMipmaps, + TextureFileField_StreamingMipmapsPriority, + TextureFileField_IgnoreMasterTextureLimit, + TextureFileField_IsPreProcessed, + TextureFileField_MipsStripped, + TextureFileField_IsAlphaChannelOptional, + TextureFileField_PlatformBlob, + TextureFileField_Max +}; +ASSETSTOOLS_API void PreprocessTextureTemplate(AssetTypeTemplateField& templateBase) +{ + if (AssetTypeTemplateField* pPlatformBlobField = templateBase.SearchChild("m_PlatformBlob")) + { + if (AssetTypeTemplateField* pArrayField = pPlatformBlobField->SearchChild("Array")) + pArrayField->type = "TypelessData"; //Treat as byte array instead of generic array to lower the overhead. + } +} +ASSETSTOOLS_API bool ReadTextureFile(TextureFile *pOutTex, AssetTypeValueField *pBaseField) +{ + //Last checked: 2021.2.17f1 + AssetTypeValueField *fields[TextureFileField_Max] = + { + pBaseField->Get("m_MipCount"), //added in U5.2 + pBaseField->Get("m_MipMap"), //removed in U5.2 + pBaseField->Get("m_ReadAllowed"), //removed in U5.5 + + pBaseField->Get("m_Name"), + pBaseField->Get("m_Width"), + pBaseField->Get("m_Height"), + pBaseField->Get("m_CompleteImageSize"), //Since 2020.1: unsigned int, before: int + pBaseField->Get("m_TextureFormat"), + pBaseField->Get("m_IsReadable"), + pBaseField->Get("m_ImageCount"), + pBaseField->Get("m_TextureDimension"), + pBaseField->Get("m_TextureSettings")->Get("m_FilterMode"), + pBaseField->Get("m_TextureSettings")->Get("m_Aniso"), + pBaseField->Get("m_TextureSettings")->Get("m_MipBias"), + pBaseField->Get("m_TextureSettings")->Get("m_WrapMode"), //removed in U2017.1 + pBaseField->Get("m_LightmapFormat"), + pBaseField->Get("m_ColorSpace"), //added in U3.5 + pBaseField->Get("image data"), + + //added in U5.3 + pBaseField->Get("m_StreamData")->Get("offset"), //Since 2020.1: UInt64, before: unsigned int + pBaseField->Get("m_StreamData")->Get("size"), + pBaseField->Get("m_StreamData")->Get("path"), + //added in U2017.1 + pBaseField->Get("m_TextureSettings")->Get("m_WrapU"), + pBaseField->Get("m_TextureSettings")->Get("m_WrapV"), + pBaseField->Get("m_TextureSettings")->Get("m_WrapW"), + //added in U2017.3 + pBaseField->Get("m_ForcedFallbackFormat"), + pBaseField->Get("m_DownscaleFallback"), + //added in U2018.2 + pBaseField->Get("m_StreamingMipmaps"), + pBaseField->Get("m_StreamingMipmapsPriority"), + //added in U2019.3 + pBaseField->Get("m_IgnoreMasterTextureLimit"), //bool + //added in U2019.4 + pBaseField->Get("m_IsPreProcessed"), //bool + //added in U2020.1 + pBaseField->Get("m_MipsStripped"), //int + //added in U2020.2 + pBaseField->Get("m_IsAlphaChannelOptional"), //bool + pBaseField->Get("m_PlatformBlob")->Get("Array"), //Array(UInt8) or TypelessData(UInt8) after PreprocessTextureTemplate. + }; + for (int i = TextureFileField_Name; i < TextureFileField_StreamingInfo_offset; i++) + { + //color space added in U3.5, wrap mode removed in U2017.1 + if (fields[i]->IsDummy() && i != TextureFileField_ColorSpace && i != TextureFileField_WrapMode) + return false; + } + if (fields[TextureFileField_StreamingInfo_offset]->IsDummy() != fields[TextureFileField_StreamingInfo_size]->IsDummy() || + fields[TextureFileField_StreamingInfo_size]->IsDummy() != fields[TextureFileField_StreamingInfo_path]->IsDummy()) + return false; + if (fields[TextureFileField_WrapMode]->IsDummy() == (fields[TextureFileField_WrapU]->IsDummy() || + fields[TextureFileField_WrapV]->IsDummy() || fields[TextureFileField_WrapW]->IsDummy())) + return false; + if (!fields[TextureFileField_MipCount]->IsDummy()) + { + pOutTex->m_MipCount = fields[TextureFileField_MipCount]->GetValue()->AsInt(); + pOutTex->m_MipMap = pOutTex->m_MipCount > 1; + } + else if (!fields[TextureFileField_MipMap]->IsDummy()) + { + pOutTex->m_MipCount = -1; //unknown + pOutTex->m_MipMap = fields[TextureFileField_MipMap]->GetValue()->AsBool(); + } + else + return false; + pOutTex->m_Name = fields[TextureFileField_Name]->GetValue()->AsString(); + pOutTex->m_ForcedFallbackFormat = fields[TextureFileField_ForcedFallbackFormat]->IsDummy() + ? 0 : fields[TextureFileField_ForcedFallbackFormat]->GetValue()->AsInt(); + pOutTex->m_DownscaleFallback = fields[TextureFileField_DownscaleFallback]->IsDummy() + ? false : fields[TextureFileField_DownscaleFallback]->GetValue()->AsBool(); + pOutTex->m_IsAlphaChannelOptional = fields[TextureFileField_IsAlphaChannelOptional]->IsDummy() + ? false : fields[TextureFileField_IsAlphaChannelOptional]->GetValue()->AsBool(); + pOutTex->m_Width = fields[TextureFileField_Width]->GetValue()->AsUInt(); + pOutTex->m_Height = fields[TextureFileField_Height]->GetValue()->AsUInt(); + pOutTex->m_CompleteImageSize = fields[TextureFileField_CompleteImageSize]->GetValue()->AsUInt(); + pOutTex->m_MipsStripped = fields[TextureFileField_MipsStripped]->IsDummy() + ? 0 : fields[TextureFileField_MipsStripped]->GetValue()->AsInt(); + pOutTex->m_TextureFormat = fields[TextureFileField_TextureFormat]->GetValue()->AsUInt(); + pOutTex->m_IsReadable = fields[TextureFileField_IsReadable]->GetValue()->AsBool(); + pOutTex->m_IsPreProcessed = fields[TextureFileField_IsPreProcessed]->IsDummy() + ? 0 : fields[TextureFileField_IsPreProcessed]->GetValue()->AsBool(); + pOutTex->m_ReadAllowed = fields[TextureFileField_ReadAllowed]->IsDummy() + ? true : fields[TextureFileField_ReadAllowed]->GetValue()->AsBool(); + pOutTex->m_IgnoreMasterTextureLimit = fields[TextureFileField_IgnoreMasterTextureLimit]->IsDummy() + ? 0 : fields[TextureFileField_IgnoreMasterTextureLimit]->GetValue()->AsBool(); + pOutTex->m_StreamingMipmaps = fields[TextureFileField_StreamingMipmaps]->IsDummy() + ? true : fields[TextureFileField_StreamingMipmaps]->GetValue()->AsBool(); + pOutTex->m_StreamingMipmapsPriority = fields[TextureFileField_StreamingMipmapsPriority]->IsDummy() + ? 0 : fields[TextureFileField_StreamingMipmapsPriority]->GetValue()->AsInt(); + pOutTex->m_ImageCount = fields[TextureFileField_ImageCount]->GetValue()->AsInt(); + pOutTex->m_TextureDimension = fields[TextureFileField_TextureDimension]->GetValue()->AsInt(); + pOutTex->m_TextureSettings.m_FilterMode = fields[TextureFileField_FilterMode]->GetValue()->AsInt(); + pOutTex->m_TextureSettings.m_Aniso = fields[TextureFileField_Aniso]->GetValue()->AsInt(); + pOutTex->m_TextureSettings.m_MipBias = fields[TextureFileField_MipBias]->GetValue()->AsFloat(); + pOutTex->m_TextureSettings.m_WrapMode = fields[TextureFileField_WrapMode]->IsDummy() + ? 0 : fields[TextureFileField_WrapMode]->GetValue()->AsInt(); + pOutTex->m_TextureSettings.m_WrapU = fields[TextureFileField_WrapU]->IsDummy() + ? 0 : fields[TextureFileField_WrapU]->GetValue()->AsInt(); + pOutTex->m_TextureSettings.m_WrapV = fields[TextureFileField_WrapV]->IsDummy() + ? 0 : fields[TextureFileField_WrapV]->GetValue()->AsInt(); + pOutTex->m_TextureSettings.m_WrapW = fields[TextureFileField_WrapW]->IsDummy() + ? 0 : fields[TextureFileField_WrapW]->GetValue()->AsInt(); + pOutTex->m_LightmapFormat = fields[TextureFileField_LightmapFormat]->GetValue()->AsInt(); + if (!fields[TextureFileField_ColorSpace]->IsDummy()) + pOutTex->m_ColorSpace = fields[TextureFileField_ColorSpace]->GetValue()->AsInt(); + else + pOutTex->m_ColorSpace = 0; + pOutTex->m_PlatformBlob.clear(); + if (!fields[TextureFileField_PlatformBlob]->IsDummy()) + { + if (auto* pByteArray = fields[TextureFileField_PlatformBlob]->GetValue()->AsByteArray()) + pOutTex->m_PlatformBlob.assign(&pByteArray->data[0], &pByteArray->data[pByteArray->size]); + else if (auto* pArray = fields[TextureFileField_PlatformBlob]->GetValue()->AsArray()) + { + if (pArray->size == fields[TextureFileField_PlatformBlob]->GetChildrenCount()) + { + pOutTex->m_PlatformBlob.resize(pArray->size); + for (unsigned int i = 0; i < pArray->size; ++i) + { + auto *pCurByteField = fields[TextureFileField_PlatformBlob]->Get(i); + if (pCurByteField->GetValue() && pCurByteField->GetValue()->GetType() == ValueType_UInt8) + pOutTex->m_PlatformBlob[i] = (uint8_t)pCurByteField->GetValue()->AsUInt(); + } + } + else + assert(false); + } + } + pOutTex->_pictureDataSize = fields[TextureFileField_ImageData]->GetValue()->AsByteArray()->size; + pOutTex->pPictureData = fields[TextureFileField_ImageData]->GetValue()->AsByteArray()->data; + memcpy(pOutTex->pPictureData, pOutTex->pPictureData, pOutTex->_pictureDataSize); + if (!fields[TextureFileField_StreamingInfo_offset]->IsDummy()) + { + pOutTex->m_StreamData.offset = fields[TextureFileField_StreamingInfo_offset]->GetValue()->AsUInt64(); + pOutTex->m_StreamData.size = fields[TextureFileField_StreamingInfo_size]->GetValue()->AsUInt(); + pOutTex->m_StreamData.path = fields[TextureFileField_StreamingInfo_path]->GetValue()->AsString(); + } + else + { + pOutTex->m_StreamData.offset = pOutTex->m_StreamData.size = 0; + pOutTex->m_StreamData.path.clear(); + } + pOutTex->extra.textureFormatVersion = 0; + return true; +} +ASSETSTOOLS_API bool WriteTextureFile(TextureFile *pInTex, AssetTypeValueField *pBaseField, + std::vector>& allocatedMemory) +{ + AssetTypeValueField *fields[] = + { + pBaseField->Get("m_MipCount"), //added in U5.2 + pBaseField->Get("m_MipMap"), //not in U5.2 + pBaseField->Get("m_ReadAllowed"), //removed in U5.5 + + pBaseField->Get("m_Name"), + pBaseField->Get("m_Width"), + pBaseField->Get("m_Height"), + pBaseField->Get("m_CompleteImageSize"), + pBaseField->Get("m_TextureFormat"), + pBaseField->Get("m_IsReadable"), + pBaseField->Get("m_ImageCount"), + pBaseField->Get("m_TextureDimension"), + pBaseField->Get("m_TextureSettings")->Get("m_FilterMode"), + pBaseField->Get("m_TextureSettings")->Get("m_Aniso"), + pBaseField->Get("m_TextureSettings")->Get("m_MipBias"), + pBaseField->Get("m_TextureSettings")->Get("m_WrapMode"), //removed in U2017.1 + pBaseField->Get("m_LightmapFormat"), + pBaseField->Get("m_ColorSpace"), //added in U3.5 + pBaseField->Get("image data"), + + //added in U5.3 + pBaseField->Get("m_StreamData")->Get("offset"), + pBaseField->Get("m_StreamData")->Get("size"), + pBaseField->Get("m_StreamData")->Get("path"), + //added in U2017.1 + pBaseField->Get("m_TextureSettings")->Get("m_WrapU"), + pBaseField->Get("m_TextureSettings")->Get("m_WrapV"), + pBaseField->Get("m_TextureSettings")->Get("m_WrapW"), + //added in U2017.3 + pBaseField->Get("m_ForcedFallbackFormat"), + pBaseField->Get("m_DownscaleFallback"), + //added in U2018.2 + pBaseField->Get("m_StreamingMipmaps"), + pBaseField->Get("m_StreamingMipmapsPriority"), + //added in U2019.3 + pBaseField->Get("m_IgnoreMasterTextureLimit"), //bool + //added in U2019.4 + pBaseField->Get("m_IsPreProcessed"), //bool + //added in U2020.1 + pBaseField->Get("m_MipsStripped"), //int + //added in U2020.2 + pBaseField->Get("m_IsAlphaChannelOptional"), //bool + pBaseField->Get("m_PlatformBlob")->Get("Array"), //Array(UInt8) or TypelessData(UInt8) after PreprocessTextureTemplate. + }; + for (int i = TextureFileField_Name; i < TextureFileField_StreamingInfo_offset; i++) + { + //color space added in U3.5, wrap mode removed in U2017.1 + if (fields[i]->IsDummy() && i != TextureFileField_ColorSpace && i != TextureFileField_WrapMode) + return false; + } + if (fields[TextureFileField_StreamingInfo_offset]->IsDummy() != fields[TextureFileField_StreamingInfo_size]->IsDummy() || + fields[TextureFileField_StreamingInfo_size]->IsDummy() != fields[TextureFileField_StreamingInfo_path]->IsDummy()) + return false; + if (fields[TextureFileField_WrapMode]->IsDummy() == (fields[TextureFileField_WrapU]->IsDummy() || + fields[TextureFileField_WrapV]->IsDummy() || fields[TextureFileField_WrapW]->IsDummy())) + return false; + if (fields[TextureFileField_StreamingMipmaps]->IsDummy() != fields[TextureFileField_StreamingMipmapsPriority]->IsDummy()) + return false; + if (!fields[TextureFileField_MipCount]->IsDummy()) + { + fields[TextureFileField_MipCount]->GetValue()->Set(&pInTex->m_MipCount, ValueType_Int32); + } + else if (!fields[TextureFileField_MipMap]->IsDummy()) + { + fields[TextureFileField_MipMap]->GetValue()->Set(&pInTex->m_MipMap, ValueType_Bool); + } + else + return false; + + fields[TextureFileField_Name]->GetValue()->Set(const_cast(pInTex->m_Name.c_str())); + + if (!fields[TextureFileField_ForcedFallbackFormat]->IsDummy()) + fields[TextureFileField_ForcedFallbackFormat]->GetValue()->Set(&pInTex->m_ForcedFallbackFormat, ValueType_Int32); + if (!fields[TextureFileField_DownscaleFallback]->IsDummy()) + fields[TextureFileField_DownscaleFallback]->GetValue()->Set(&pInTex->m_DownscaleFallback, ValueType_Bool); + if (!fields[TextureFileField_IsAlphaChannelOptional]->IsDummy()) + fields[TextureFileField_IsAlphaChannelOptional]->GetValue()->Set(&pInTex->m_IsAlphaChannelOptional, ValueType_Bool); + + fields[TextureFileField_Width]->GetValue()->Set(&pInTex->m_Width, ValueType_UInt32); + fields[TextureFileField_Height]->GetValue()->Set(&pInTex->m_Height, ValueType_UInt32); + fields[TextureFileField_CompleteImageSize]->GetValue()->Set(&pInTex->m_CompleteImageSize, ValueType_UInt32); + if (!fields[TextureFileField_MipsStripped]->IsDummy()) + fields[TextureFileField_MipsStripped]->GetValue()->Set(&pInTex->m_MipsStripped, ValueType_Int32); + fields[TextureFileField_TextureFormat]->GetValue()->Set(&pInTex->m_TextureFormat, ValueType_UInt32); + fields[TextureFileField_IsReadable]->GetValue()->Set(&pInTex->m_IsReadable, ValueType_Bool); + if (!fields[TextureFileField_IsPreProcessed]->IsDummy()) + fields[TextureFileField_IsPreProcessed]->GetValue()->Set(&pInTex->m_IsPreProcessed, ValueType_Bool); + if (!fields[TextureFileField_ReadAllowed]->IsDummy()) + fields[TextureFileField_ReadAllowed]->GetValue()->Set(&pInTex->m_ReadAllowed, ValueType_Bool); + if (!fields[TextureFileField_IgnoreMasterTextureLimit]->IsDummy()) + fields[TextureFileField_IgnoreMasterTextureLimit]->GetValue()->Set(&pInTex->m_IgnoreMasterTextureLimit, ValueType_Bool); + if (!fields[TextureFileField_StreamingMipmaps]->IsDummy()) + { + fields[TextureFileField_StreamingMipmaps]->GetValue()->Set(&pInTex->m_StreamingMipmaps, ValueType_Bool); + fields[TextureFileField_StreamingMipmapsPriority]->GetValue()->Set(&pInTex->m_StreamingMipmapsPriority, ValueType_Int32); + } + fields[TextureFileField_ImageCount]->GetValue()->Set(&pInTex->m_ImageCount, ValueType_Int32); + fields[TextureFileField_TextureDimension]->GetValue()->Set(&pInTex->m_TextureDimension, ValueType_Int32); + fields[TextureFileField_FilterMode]->GetValue()->Set(&pInTex->m_TextureSettings.m_FilterMode, ValueType_Int32); + fields[TextureFileField_Aniso]->GetValue()->Set(&pInTex->m_TextureSettings.m_Aniso, ValueType_Int32); + fields[TextureFileField_MipBias]->GetValue()->Set(&pInTex->m_TextureSettings.m_MipBias, ValueType_Float); + if (!fields[TextureFileField_WrapMode]->IsDummy()) + fields[TextureFileField_WrapMode]->GetValue()->Set(&pInTex->m_TextureSettings.m_WrapMode, ValueType_Int32); + else + { + fields[TextureFileField_WrapU]->GetValue()->Set(&pInTex->m_TextureSettings.m_WrapU, ValueType_Int32); + fields[TextureFileField_WrapV]->GetValue()->Set(&pInTex->m_TextureSettings.m_WrapV, ValueType_Int32); + fields[TextureFileField_WrapW]->GetValue()->Set(&pInTex->m_TextureSettings.m_WrapW, ValueType_Int32); + } + fields[TextureFileField_LightmapFormat]->GetValue()->Set(&pInTex->m_LightmapFormat, ValueType_Int32); + if (!fields[TextureFileField_ColorSpace]->IsDummy()) //color space added in U3.5 + fields[TextureFileField_ColorSpace]->GetValue()->Set(&pInTex->m_ColorSpace, ValueType_Int32); + if (!fields[TextureFileField_PlatformBlob]->IsDummy()) + { + //Create an AssetTypeValue of type ByteArray. + uint8_t* valueMem = new uint8_t[sizeof(AssetTypeValue)]; + allocatedMemory.emplace_back(valueMem); + AssetTypeValue* pNewValue = (AssetTypeValue*)valueMem; + AssetTypeByteArray byteArray = {}; + byteArray.data = pInTex->m_PlatformBlob.data(); + byteArray.size = (uint32_t)std::min(pInTex->m_PlatformBlob.size(), std::numeric_limits::max()); + *pNewValue = AssetTypeValue(ValueType_ByteArray, &byteArray); + //Assign it to the field. + fields[TextureFileField_PlatformBlob]->Read(pNewValue, + fields[TextureFileField_PlatformBlob]->GetTemplateField(), + 0, nullptr); + } + AssetTypeByteArray byteArray; + byteArray.size = pInTex->_pictureDataSize; + byteArray.data = pInTex->pPictureData; + fields[TextureFileField_ImageData]->GetValue()->Set(&byteArray, ValueType_ByteArray); + if (!fields[TextureFileField_StreamingInfo_offset]->IsDummy()) + { + fields[TextureFileField_StreamingInfo_offset]->GetValue()->Set(&pInTex->m_StreamData.offset, ValueType_UInt64); + fields[TextureFileField_StreamingInfo_size]->GetValue()->Set(&pInTex->m_StreamData.size, ValueType_UInt32); + fields[TextureFileField_StreamingInfo_path]->GetValue()->Set(const_cast(pInTex->m_StreamData.path.c_str()), ValueType_String); + } + return true; +} +#include "EngineVersion.h" +bool SupportsTextureFormat(AssetsFile *pAssetsFile, TextureFormat texFmt, int &version) +{ + EngineVersion engineVersion = EngineVersion::parse(pAssetsFile->typeTree.unityVersion); + if (engineVersion.year >= 2019 + || pAssetsFile->header.format > 19) //>= 2019.1 + version = 2; + else if ((engineVersion.year == 2017 && engineVersion.release >= 3) || engineVersion.year > 2017 + || pAssetsFile->header.format > 17) //>= 2017.3 + version = 1; + else + version = 0; + + switch (texFmt) + { + case TexFmt_Alpha8: + case TexFmt_ARGB4444: + case TexFmt_RGB24: + case TexFmt_RGBA32: + case TexFmt_ARGB32: + case TexFmt_RGB565: + case TexFmt_DXT1: + return true; + case TexFmt_DXT5: + return engineVersion.year >= 2; //actually excludes Unity 2 (up to format 6) because Unity 3.0 (format 8) is the first one with a version name embedded + case TexFmt_PVRTC_RGB2: + case TexFmt_PVRTC_RGBA2: + case TexFmt_PVRTC_RGB4: + case TexFmt_PVRTC_RGBA4: + return engineVersion.year > 2 || (engineVersion.year == 2 && engineVersion.release >= 6); //same as above + case TexFmt_ETC_RGB4: + return engineVersion.year >= 3; + case TexFmt_ATC_RGB4: + case TexFmt_ATC_RGBA8: + return (engineVersion.year >= 4 && engineVersion.year <= 2017) || (engineVersion.year == 3 && engineVersion.release >= 4); + case TexFmt_BGRA32Old: + return (engineVersion.year == 4 && engineVersion.release < 5) || (engineVersion.year == 3 && engineVersion.release >= 4); + case TexFmt_UNUSED38: + case TexFmt_UNUSED39: + case TexFmt_UNUSED40: + return engineVersion.year == 4 || (engineVersion.year == 3 && engineVersion.release >= 5); + case TexFmt_RGBA4444: + return engineVersion.year > 4 || (engineVersion.year == 4 && engineVersion.release >= 1); + case TexFmt_BGRA32New: + case TexFmt_EAC_R: + case TexFmt_EAC_R_SIGNED: + case TexFmt_EAC_RG: + case TexFmt_EAC_RG_SIGNED: + case TexFmt_ETC2_RGB4: + case TexFmt_ETC2_RGBA1: + case TexFmt_ETC2_RGBA8: + case TexFmt_ASTC_RGB_4x4: //version >= 2: TexFmt_ASTC_4x4 + case TexFmt_ASTC_RGB_5x5: //version >= 2: TexFmt_ASTC_5x5 + case TexFmt_ASTC_RGB_6x6: //version >= 2: TexFmt_ASTC_6x6 + case TexFmt_ASTC_RGB_8x8: //version >= 2: TexFmt_ASTC_8x8 + case TexFmt_ASTC_RGB_10x10://version >= 2: TexFmt_ASTC_10x10 + case TexFmt_ASTC_RGB_12x12://version >= 2: TexFmt_ASTC_12x12 + return engineVersion.year > 4 || (engineVersion.year == 4 && engineVersion.release >= 5); + case TexFmt_ASTC_RGBA_4x4: + case TexFmt_ASTC_RGBA_5x5: + case TexFmt_ASTC_RGBA_6x6: + case TexFmt_ASTC_RGBA_8x8: + case TexFmt_ASTC_RGBA_10x10: + case TexFmt_ASTC_RGBA_12x12: + return (engineVersion.year > 4 || (engineVersion.year == 4 && engineVersion.release >= 5)) && engineVersion.year < 2019; + case TexFmt_R16: + case TexFmt_RHalf: + case TexFmt_RGHalf: + case TexFmt_RGBAHalf: + case TexFmt_RFloat: + case TexFmt_RGFloat: + case TexFmt_RGBAFloat: + case TexFmt_YUV2: + case TexFmt_DXT1Crunched: + case TexFmt_DXT5Crunched: + return engineVersion.year >= 5; + case TexFmt_ETC_RGB4_3DS: + case TexFmt_ETC_RGBA8_3DS: + return engineVersion.year >= 5 && (engineVersion.year < 2018 || (engineVersion.year == 2018 && engineVersion.release < 3)); + case TexFmt_BC6H: + case TexFmt_BC7: + case TexFmt_BC4: + case TexFmt_BC5: + return engineVersion.year > 5 || (engineVersion.year == 5 && engineVersion.release >= 5); + case TexFmt_RGB9e5Float: + return engineVersion.year > 5 || (engineVersion.year == 5 && engineVersion.release >= 6); + case TexFmt_RG16: + case TexFmt_R8: + return engineVersion.year > 2017 || (engineVersion.year == 2017 && engineVersion.release >= 1); + case TexFmt_ETC_RGB4Crunched: + case TexFmt_ETC2_RGBA8Crunched: + return engineVersion.year > 2017 || (engineVersion.year == 2017 && engineVersion.release >= 3); + case TexFmt_ASTC_HDR_4x4: + case TexFmt_ASTC_HDR_5x5: + case TexFmt_ASTC_HDR_6x6: + case TexFmt_ASTC_HDR_8x8: + case TexFmt_ASTC_HDR_10x10: + case TexFmt_ASTC_HDR_12x12: + return engineVersion.year >= 2019; + case TexFmt_RG32: + case TexFmt_RGB48: + case TexFmt_RGBA64: + return engineVersion.year > 2020 || (engineVersion.year == 2020 && engineVersion.release >= 2); + default: + return false; + } +} + +#include + +#define _24to32(in) (in + 0x000000FF) +void Write24BitTo(uint32_t in, void *buf) +{ + ((uint8_t*)buf)[2] = (uint8_t)((in & 0xFF000000) >> 24); + ((uint8_t*)buf)[1] = (uint8_t)((in & 0x00FF0000) >> 16); + ((uint8_t*)buf)[0] = (uint8_t)((in & 0x0000FF00) >> 8); +} +void Write24To32BitTo(uint32_t in, void *buf) +{ + ((uint32_t*)buf)[0] = (in & 0xFFFFFF00) + 0x000000FF; +} +void Write32BitTo(uint32_t in, void *buf) +{ + ((uint32_t*)buf)[0] = in; +} +typedef void(__cdecl *_WritePixelFormatTo)(uint32_t, void*); + +struct UncompressedColorChannel //not more than 8bit per channel +{ + uint32_t mask; //mask of the color channel in the byte + char shift; //amount of bits to lshift to compress = amount of bits to rshift to decompress +}; +struct UncompressedTextureFormat +{ + UncompressedColorChannel red, green, blue, alpha; +}; + +bool Uncompressed_ToRGBA32(TextureFile *pTex, void *pOutBuf) +{ + switch (pTex->m_TextureFormat) + { + case TexFmt_RGB24: + { + if (pTex->_pictureDataSize < (pTex->m_Width * pTex->m_Height * 3)) + return false; + for (unsigned int i = 0; i < (pTex->m_Width * pTex->m_Height); i++) + { + ((uint8_t*)pOutBuf)[i * 4 + 0] = pTex->pPictureData[i * 3 + 0]; //R + ((uint8_t*)pOutBuf)[i * 4 + 1] = pTex->pPictureData[i * 3 + 1]; //G + ((uint8_t*)pOutBuf)[i * 4 + 2] = pTex->pPictureData[i * 3 + 2]; //B + ((uint8_t*)pOutBuf)[i * 4 + 3] = 255; //A + } + } + break; + case TexFmt_RGBA32: + { + if (pTex->_pictureDataSize < (pTex->m_Width * pTex->m_Height * 4)) + return false; + memcpy(pOutBuf, pTex->pPictureData, (pTex->m_Width * pTex->m_Height) * 4); + } + break; + case TexFmt_BGRA32Old: + case TexFmt_BGRA32New: + { + if (pTex->_pictureDataSize < (pTex->m_Width * pTex->m_Height * 4)) + return false; + for (unsigned int i = 0; i < (pTex->m_Width * pTex->m_Height); i++) + { + ((uint8_t*)pOutBuf)[i * 4 + 0] = pTex->pPictureData[i * 4 + 2]; //R + ((uint8_t*)pOutBuf)[i * 4 + 1] = pTex->pPictureData[i * 4 + 1]; //G + ((uint8_t*)pOutBuf)[i * 4 + 2] = pTex->pPictureData[i * 4 + 0]; //B + ((uint8_t*)pOutBuf)[i * 4 + 3] = pTex->pPictureData[i * 4 + 3]; //A + } + } + break; + case TexFmt_ARGB32: + { + if (pTex->_pictureDataSize < (pTex->m_Width * pTex->m_Height * 4)) + return false; + for (unsigned int i = 0; i < (pTex->m_Width * pTex->m_Height); i++) + { + ((uint8_t*)pOutBuf)[i * 4 + 0] = pTex->pPictureData[i * 4 + 1]; //R + ((uint8_t*)pOutBuf)[i * 4 + 1] = pTex->pPictureData[i * 4 + 2]; //G + ((uint8_t*)pOutBuf)[i * 4 + 2] = pTex->pPictureData[i * 4 + 3]; //B + ((uint8_t*)pOutBuf)[i * 4 + 3] = pTex->pPictureData[i * 4 + 0]; //A + } + } + break; + case TexFmt_ARGB4444: + { + if (pTex->_pictureDataSize < (pTex->m_Width * pTex->m_Height * 2)) + return false; + //GBAR + for (unsigned int i = 0; i < (pTex->m_Width * pTex->m_Height); i++) + { + ((uint8_t*)pOutBuf)[i * 4 + 1] = (pTex->pPictureData[i * 2] & 0xF0) | (pTex->pPictureData[i * 2] >> 4); //| 0x0F; //G + ((uint8_t*)pOutBuf)[i * 4 + 2] = ((pTex->pPictureData[i * 2] & 0x0F) << 4) | (pTex->pPictureData[i * 2] & 0x0F); //| 0x0F; //B + ((uint8_t*)pOutBuf)[i * 4 + 3] = (pTex->pPictureData[i * 2 + 1] & 0xF0) | (pTex->pPictureData[i * 2 + 1] >> 4); //| 0x0F; //A + ((uint8_t*)pOutBuf)[i * 4 + 0] = ((pTex->pPictureData[i * 2 + 1] & 0x0F) << 4) | (pTex->pPictureData[i * 2 + 1] & 0x0F); //| 0x0F; //R + } + } + break; + case TexFmt_RGBA4444: + { + if (pTex->_pictureDataSize < (pTex->m_Width * pTex->m_Height * 2)) + return false; + //BARG + for (unsigned int i = 0; i < (pTex->m_Width * pTex->m_Height); i++) + { + ((uint8_t*)pOutBuf)[i * 4 + 2] = (pTex->pPictureData[i * 2] & 0xF0) | (pTex->pPictureData[i * 2] >> 4); //| 0x0F; //B + ((uint8_t*)pOutBuf)[i * 4 + 3] = ((pTex->pPictureData[i * 2] & 0x0F) << 4) | (pTex->pPictureData[i * 2] & 0x0F); //| 0x0F; //A + ((uint8_t*)pOutBuf)[i * 4 + 0] = (pTex->pPictureData[i * 2 + 1] & 0xF0) | (pTex->pPictureData[i * 2 + 1] >> 4); //| 0x0F; //R + ((uint8_t*)pOutBuf)[i * 4 + 1] = ((pTex->pPictureData[i * 2 + 1] & 0x0F) << 4) | (pTex->pPictureData[i * 2 + 1] & 0x0F); //| 0x0F; //G + } + } + break; + case TexFmt_RGB565: + //the R/G/B values won't be changed, so colors will look different in 8 bits per color + { + if (pTex->_pictureDataSize < (pTex->m_Width * pTex->m_Height * 2)) + return false; + //g3(low)b5g3(high)r5 + for (unsigned int i = 0; i < (pTex->m_Width * pTex->m_Height); i++) + { + uint16_t rgb565 = *(uint16_t*)(&pTex->pPictureData[i * 2]); + //rgb565 = ((rgb565 & 0xFF00) >> 8) | ((rgb565 & 0x00FF) << 8); + uint8_t r5 = (rgb565 >> 11) & 31; + uint8_t g6 = (rgb565 >> 5) & 63; + uint8_t b5 = (rgb565) & 31; + + //multiply by 17 -> for maximum 5bit red we get maximum 8bit red, for minimum 5bit red we get minimum 8bit red + ((uint8_t*)pOutBuf)[i * 4 + 0] = (r5 << 3) | (r5 & 7);// | 7; + ((uint8_t*)pOutBuf)[i * 4 + 1] = (g6 << 2) | (g6 & 3);// | 3; + ((uint8_t*)pOutBuf)[i * 4 + 2] = (b5 << 3) | (b5 & 7);// | 7; + ((uint8_t*)pOutBuf)[i * 4 + 3] = 255; //A + } + } + break; + case TexFmt_Alpha8: + { + if (pTex->_pictureDataSize < (pTex->m_Width * pTex->m_Height * 1)) + return false; + for (unsigned int i = 0; i < (pTex->m_Width * pTex->m_Height); i++) + { + ((uint8_t*)pOutBuf)[i * 4 + 0] = 255; + ((uint8_t*)pOutBuf)[i * 4 + 1] = 255; + ((uint8_t*)pOutBuf)[i * 4 + 2] = 255; + ((uint8_t*)pOutBuf)[i * 4 + 3] = pTex->pPictureData[i]; //A + } + } + break; + case TexFmt_RHalf: + { + if (pTex->_pictureDataSize < (pTex->m_Width * pTex->m_Height * 2)) + return false; + for (unsigned int i = 0; i < (pTex->m_Width * pTex->m_Height); i++) + { + float red = ((HalfFloat*)&pTex->pPictureData[i * 2])->toFloat(); + + //Cut off values outside the SDR range. + if (red >= 1.0F) red = 1.0F; + else if (red <= 0.0F) red = 0.0F; + + ((uint8_t*)pOutBuf)[i * 4 + 0] = (uint8_t)(red * 255.0F); //R + ((uint8_t*)pOutBuf)[i * 4 + 1] = 0; //G + ((uint8_t*)pOutBuf)[i * 4 + 2] = 0; //B + ((uint8_t*)pOutBuf)[i * 4 + 3] = 255; //A + } + } + break; + case TexFmt_RGHalf: + { + if (pTex->_pictureDataSize < (pTex->m_Width * pTex->m_Height * 4)) + return false; + for (unsigned int i = 0; i < (pTex->m_Width * pTex->m_Height); i++) + { + float colors[2]; + colors[0] = ((HalfFloat*)&pTex->pPictureData[i * 4])->toFloat(); + colors[1] = ((HalfFloat*)&pTex->pPictureData[i * 4 + 2])->toFloat(); + + //Cut off values outside the SDR range. + for (unsigned int j = 0; j < 2; j++) + { + if (colors[j] >= 1.0F) colors[j] = 1.0F; + else if (colors[j] <= 0.0F) colors[j] = 0.0F; + } + + ((uint8_t*)pOutBuf)[i * 4 + 0] = (uint8_t)(colors[0] * 255.0F); //R + ((uint8_t*)pOutBuf)[i * 4 + 1] = (uint8_t)(colors[1] * 255.0F); //G + ((uint8_t*)pOutBuf)[i * 4 + 2] = 0; //B + ((uint8_t*)pOutBuf)[i * 4 + 3] = 255; //A + } + } + break; + case TexFmt_RGBAHalf: + { + if (pTex->_pictureDataSize < (pTex->m_Width * pTex->m_Height * 8)) + return false; + for (unsigned int i = 0; i < (pTex->m_Width * pTex->m_Height); i++) + { + float colors[4]; + colors[0] = ((HalfFloat*)&pTex->pPictureData[i * 8])->toFloat(); + colors[1] = ((HalfFloat*)&pTex->pPictureData[i * 8 + 2])->toFloat(); + colors[2] = ((HalfFloat*)&pTex->pPictureData[i * 8 + 4])->toFloat(); + colors[3] = ((HalfFloat*)&pTex->pPictureData[i * 8 + 6])->toFloat(); + + //Cut off values outside the SDR range. + for (unsigned int j = 0; j < 4; j++) + { + if (colors[j] >= 1.0F) colors[j] = 1.0F; + else if (colors[j] <= 0.0F) colors[j] = 0.0F; + } + + ((uint8_t*)pOutBuf)[i * 4 + 0] = (uint8_t)(colors[0] * 255.0F); //R + ((uint8_t*)pOutBuf)[i * 4 + 1] = (uint8_t)(colors[1] * 255.0F); //G + ((uint8_t*)pOutBuf)[i * 4 + 2] = (uint8_t)(colors[2] * 255.0F); //B + ((uint8_t*)pOutBuf)[i * 4 + 3] = (uint8_t)(colors[3] * 255.0F); //A + } + } + break; + case TexFmt_RFloat: + { + if (pTex->_pictureDataSize < (pTex->m_Width * pTex->m_Height * 4)) + return false; + for (unsigned int i = 0; i < (pTex->m_Width * pTex->m_Height); i++) + { + float red = *(float*)&pTex->pPictureData[i * 4]; + + //Cut off values outside the SDR range. + if (red >= 1.0F) red = 1.0F; + else if (red <= 0.0F) red = 0.0F; + + ((uint8_t*)pOutBuf)[i * 4 + 0] = (uint8_t)(red * 255.0F); //R + ((uint8_t*)pOutBuf)[i * 4 + 1] = 0; //G + ((uint8_t*)pOutBuf)[i * 4 + 2] = 0; //B + ((uint8_t*)pOutBuf)[i * 4 + 3] = 255; //A + } + } + break; + case TexFmt_RGFloat: + { + if (pTex->_pictureDataSize < (pTex->m_Width * pTex->m_Height * 8)) + return false; + for (unsigned int i = 0; i < (pTex->m_Width * pTex->m_Height); i++) + { + float colors[2]; + colors[0] = *(float*)&pTex->pPictureData[i * 8]; //R + colors[1] = *(float*)&pTex->pPictureData[i * 8 + 4]; //G + + //Cut off values outside the SDR range. + for (unsigned int j = 0; j < 2; j++) + { + if (colors[j] >= 1.0F) colors[j] = 1.0F; + else if (colors[j] <= 0.0F) colors[j] = 0.0F; + } + + ((uint8_t*)pOutBuf)[i * 4 + 0] = (uint8_t)(colors[0] * 255.0F); //R + ((uint8_t*)pOutBuf)[i * 4 + 1] = (uint8_t)(colors[1] * 255.0F); //G + ((uint8_t*)pOutBuf)[i * 4 + 2] = 0; //B + ((uint8_t*)pOutBuf)[i * 4 + 3] = 255; //A + } + } + break; + case TexFmt_RGBAFloat: + { + if (pTex->_pictureDataSize < (pTex->m_Width * pTex->m_Height * 16)) + return false; + for (unsigned int i = 0; i < (pTex->m_Width * pTex->m_Height); i++) + { + float colors[4]; + colors[0] = *(float*)&pTex->pPictureData[i * 16]; //R + colors[1] = *(float*)&pTex->pPictureData[i * 16 + 4]; //G + colors[2] = *(float*)&pTex->pPictureData[i * 16 + 8]; //B + colors[3] = *(float*)&pTex->pPictureData[i * 16 + 12]; //A + + //Cut off values outside the SDR range. + for (unsigned int j = 0; j < 4; j++) + { + if (colors[j] >= 1.0F) colors[j] = 1.0F; + else if (colors[j] <= 0.0F) colors[j] = 0.0F; + } + + ((uint8_t*)pOutBuf)[i * 4 + 0] = (uint8_t)(colors[0] * 255.0F); //R + ((uint8_t*)pOutBuf)[i * 4 + 1] = (uint8_t)(colors[1] * 255.0F); //G + ((uint8_t*)pOutBuf)[i * 4 + 2] = (uint8_t)(colors[2] * 255.0F); //B + ((uint8_t*)pOutBuf)[i * 4 + 3] = (uint8_t)(colors[3] * 255.0F); //A + } + } + break; + case TexFmt_RGB9e5Float: + { + if (pTex->_pictureDataSize < (pTex->m_Width * pTex->m_Height * 4)) + return false; + for (unsigned int i = 0; i < (pTex->m_Width * pTex->m_Height); i++) + { + RGB9e5Float rgbFloat; + rgbFloat.value = *(unsigned int*)&pTex->pPictureData[i * 4]; + float colors[3] = {}; + rgbFloat.toFloat(colors); + + //Cut off values outside the SDR range. + for (unsigned int j = 0; j < 3; j++) + { + if (colors[j] >= 1.0F) colors[j] = 1.0F; + else if (colors[j] <= 0.0F) colors[j] = 0.0F; + } + + ((uint8_t*)pOutBuf)[i * 4 + 0] = (uint8_t)(colors[0] * 255.0F); //R + ((uint8_t*)pOutBuf)[i * 4 + 1] = (uint8_t)(colors[1] * 255.0F); //G + ((uint8_t*)pOutBuf)[i * 4 + 2] = (uint8_t)(colors[2] * 255.0F); //B + ((uint8_t*)pOutBuf)[i * 4 + 3] = 255; //A + } + } + break; + case TexFmt_R8: + { + if (pTex->_pictureDataSize < (pTex->m_Width * pTex->m_Height * 1)) + return false; + for (unsigned int i = 0; i < (pTex->m_Width * pTex->m_Height); i++) + { + ((uint8_t*)pOutBuf)[i * 4 + 0] = pTex->pPictureData[i]; //R + ((uint8_t*)pOutBuf)[i * 4 + 1] = 0; //G + ((uint8_t*)pOutBuf)[i * 4 + 2] = 0; //B + ((uint8_t*)pOutBuf)[i * 4 + 3] = 255; //A + } + } + break; + case TexFmt_R16: + { + if (pTex->_pictureDataSize < (pTex->m_Width * pTex->m_Height * 2)) + return false; + for (unsigned int i = 0; i < (pTex->m_Width * pTex->m_Height); i++) + { + ((uint8_t*)pOutBuf)[i * 4 + 0] = (*(uint16_t*)&pTex->pPictureData[i*2]) >> 8; //R + ((uint8_t*)pOutBuf)[i * 4 + 1] = 0; //G + ((uint8_t*)pOutBuf)[i * 4 + 2] = 0; //B + ((uint8_t*)pOutBuf)[i * 4 + 3] = 255; //A + } + } + break; + case TexFmt_RG16: + { + if (pTex->_pictureDataSize < (pTex->m_Width * pTex->m_Height * 2)) + return false; + for (unsigned int i = 0; i < (pTex->m_Width * pTex->m_Height); i++) + { + ((uint8_t*)pOutBuf)[i * 4 + 0] = pTex->pPictureData[i * 2]; //R + ((uint8_t*)pOutBuf)[i * 4 + 1] = pTex->pPictureData[i * 2 + 1]; //G + ((uint8_t*)pOutBuf)[i * 4 + 2] = 0; //B + ((uint8_t*)pOutBuf)[i * 4 + 3] = 255; //A + } + } + break; + case TexFmt_RG32: + { + if (pTex->_pictureDataSize < (pTex->m_Width * pTex->m_Height * 4)) + return false; + for (unsigned int i = 0; i < (pTex->m_Width * pTex->m_Height); i++) + { + ((uint8_t*)pOutBuf)[i * 4 + 0] = (*(uint16_t*)&pTex->pPictureData[i * 4 + 0]) >> 8; //R + ((uint8_t*)pOutBuf)[i * 4 + 1] = (*(uint16_t*)&pTex->pPictureData[i * 4 + 2]) >> 8; //G + ((uint8_t*)pOutBuf)[i * 4 + 2] = 0; //B + ((uint8_t*)pOutBuf)[i * 4 + 3] = 255; //A + } + } + break; + case TexFmt_RGB48: + { + if (pTex->_pictureDataSize < (pTex->m_Width * pTex->m_Height * 6)) + return false; + for (unsigned int i = 0; i < (pTex->m_Width * pTex->m_Height); i++) + { + ((uint8_t*)pOutBuf)[i * 4 + 0] = (*(uint16_t*)&pTex->pPictureData[i * 6 + 0]) >> 8; //R + ((uint8_t*)pOutBuf)[i * 4 + 1] = (*(uint16_t*)&pTex->pPictureData[i * 6 + 2]) >> 8; //G + ((uint8_t*)pOutBuf)[i * 4 + 2] = (*(uint16_t*)&pTex->pPictureData[i * 6 + 4]) >> 8; //B + ((uint8_t*)pOutBuf)[i * 4 + 3] = 255; //A + } + } + break; + case TexFmt_RGBA64: + { + if (pTex->_pictureDataSize < (pTex->m_Width * pTex->m_Height * 8)) + return false; + for (unsigned int i = 0; i < (pTex->m_Width * pTex->m_Height); i++) + { + ((uint8_t*)pOutBuf)[i * 4 + 0] = (*(uint16_t*)&pTex->pPictureData[i * 8 + 0]) >> 8; //R + ((uint8_t*)pOutBuf)[i * 4 + 1] = (*(uint16_t*)&pTex->pPictureData[i * 8 + 2]) >> 8; //G + ((uint8_t*)pOutBuf)[i * 4 + 2] = (*(uint16_t*)&pTex->pPictureData[i * 8 + 4]) >> 8; //B + ((uint8_t*)pOutBuf)[i * 4 + 3] = (*(uint16_t*)&pTex->pPictureData[i * 8 + 6]) >> 8; //A + } + } + break; + default: + return false; + } + return true; +} + +int highestAlpha = 0; +bool Compressed_ToRGBA32(TextureFile *pTex, void *pOutBuf) +{ + //pvrtexture::PixelType pvr_pxType; + switch (pTex->m_TextureFormat) + { + case TexFmt_ETC_RGB4Crunched: + case TexFmt_ETC2_RGBA8Crunched: + case TexFmt_DXT1Crunched: + case TexFmt_DXT5Crunched: + { + std::vector decrunchBuf; + TextureFormat decrunchFormat = (TextureFormat)0; + if (pTex->extra.textureFormatVersion >= 1) + { + if (!DecrunchTextureData_Unity(pTex, decrunchBuf, decrunchFormat)) + return false; + } + else + { + if (!DecrunchTextureData_Legacy(pTex, decrunchBuf, decrunchFormat)) + return false; + } + bool ret = false; + { + uint32_t origFormat = pTex->m_TextureFormat; + uint8_t* pOrigBuf = pTex->pPictureData; + uint32_t origSize = pTex->_pictureDataSize; + uint32_t origCompleteSize = pTex->m_CompleteImageSize; + + pTex->m_TextureFormat = (uint32_t)decrunchFormat; + pTex->pPictureData = decrunchBuf.data(); + pTex->_pictureDataSize = (uint32_t)decrunchBuf.size(); + pTex->m_CompleteImageSize = (uint32_t)decrunchBuf.size(); + + ret = Compressed_ToRGBA32(pTex, pOutBuf); + + pTex->m_TextureFormat = origFormat; + pTex->pPictureData = pOrigBuf; + pTex->_pictureDataSize = origSize; + pTex->m_CompleteImageSize = origCompleteSize; + } + return ret; + } + break; + case TexFmt_DXT1: + case TexFmt_DXT5: + { + pTex->m_MipCount = 1; + void *DXTBuf = pTex->pPictureData; size_t DXTBufLen = pTex->_pictureDataSize; + int squishDXTType; TextureFormat dxtTextureFormat; + if (pTex->m_TextureFormat == TexFmt_DXT1) + { + dxtTextureFormat = TexFmt_DXT1; + squishDXTType = squish::kDxt1; + } + else if (pTex->m_TextureFormat == TexFmt_DXT5) + { + dxtTextureFormat = TexFmt_DXT5; + squishDXTType = squish::kDxt5; + } + if (pTex->_pictureDataSize < (size_t)GetCompressedTextureDataSize(pTex->m_Width, pTex->m_Height, dxtTextureFormat)) + return false; + squish::DecompressImage((squish::u8*)pOutBuf, pTex->m_Width, pTex->m_Height, pTex->pPictureData, squishDXTType);//pTex->m_Width * pTex->m_Height * 4; + return true; + } + break; + case TexFmt_BC4: + case TexFmt_BC5: + case TexFmt_BC6H: + case TexFmt_BC7: + { + if (pTex->_pictureDataSize < (size_t)GetCompressedTextureDataSize(pTex->m_Width, pTex->m_Height, (TextureFormat)pTex->m_TextureFormat)) + { + break; + } + Image image = {}; + Texture texture = {}; + texture.pixels = (unsigned int*)pTex->pPictureData; + texture.width = pTex->m_Width; + texture.height = pTex->m_Height; + texture.extended_width = (texture.width + 3) & (~3); //4x4 block texture format + texture.extended_height = (texture.height + 3) & (~3); + switch (pTex->m_TextureFormat) + { + case TexFmt_BC4: + texture.type = TEXTURE_TYPE_RGTC1; + break; + case TexFmt_BC5: + texture.type = TEXTURE_TYPE_RGTC2; + break; + case TexFmt_BC6H: + texture.type = TEXTURE_TYPE_BPTC_FLOAT; + break; + case TexFmt_BC7: + texture.type = TEXTURE_TYPE_BPTC; + break; + } + texture.info = match_texture_type(texture.type); + texture.bits_per_block = texture.info->bits_per_block; + texture.block_width = texture.info->block_width; + texture.block_height = texture.info->block_height; + set_texture_decoding_function(&texture, NULL); + convert_texture_to_image(&texture, &image); + if (image.pixels == NULL) + return false; + memset(&texture, 0, sizeof(texture)); + if (pTex->m_TextureFormat == TexFmt_BC4 || pTex->m_TextureFormat == TexFmt_BC5) + { + convert_image_to_8_bit_format(&image, 4, 0); + if (image.pixels == NULL) + return false; + } + copy_image_to_uncompressed_texture(&image, TEXTURE_TYPE_UNCOMPRESSED_RGBA8, &texture); + destroy_image(&image); + if (texture.pixels == NULL) + return false; + memcpy(pOutBuf, texture.pixels, pTex->m_Width * pTex->m_Height * 4); + destroy_texture(&texture); + return true; + //option_texture_format + //option_mipmaps + } + break; + case TexFmt_YUV2: //not actually compressed but it's also handled + case TexFmt_EAC_R: + case TexFmt_EAC_R_SIGNED: + case TexFmt_EAC_RG: + case TexFmt_EAC_RG_SIGNED: + case TexFmt_ETC_RGB4: + case TexFmt_ETC_RGB4_3DS: + case TexFmt_ETC_RGBA8_3DS: + case TexFmt_ETC2_RGB4: + case TexFmt_ETC2_RGBA1: + case TexFmt_ETC2_RGBA8: + case TexFmt_PVRTC_RGB2: + case TexFmt_PVRTC_RGBA2: + case TexFmt_PVRTC_RGB4: + case TexFmt_PVRTC_RGBA4: + { + typedef size_t(_cdecl *Wrap_Decompress)(uint32_t texFmt, unsigned int height, unsigned int width, unsigned int mipCount, void *pInBuf, size_t inBufLen, void *pOutBuf, size_t outBufLen); + HMODULE hModule = LoadLibrary(TEXT("TexToolWrap.dll")); + if (hModule) + { + Wrap_Decompress Decompress = (Wrap_Decompress)GetProcAddress(hModule, "Decompress"); + size_t decompSize = 0; + if (Decompress) + decompSize = + Decompress(pTex->m_TextureFormat, + pTex->m_Height, pTex->m_Width, pTex->m_MipCount, + pTex->pPictureData, pTex->_pictureDataSize, + pOutBuf, 4 * pTex->m_Width * pTex->m_Height); + FreeLibrary(hModule); + return decompSize == (4 * pTex->m_Width * pTex->m_Height); + } + } + break; + case TexFmt_ASTC_RGB_4x4: + case TexFmt_ASTC_RGB_5x5: + case TexFmt_ASTC_RGB_6x6: + case TexFmt_ASTC_RGB_8x8: + case TexFmt_ASTC_RGB_10x10: + case TexFmt_ASTC_RGB_12x12: + case TexFmt_ASTC_RGBA_4x4: + case TexFmt_ASTC_RGBA_5x5: + case TexFmt_ASTC_RGBA_6x6: + case TexFmt_ASTC_RGBA_8x8: + case TexFmt_ASTC_RGBA_10x10: + case TexFmt_ASTC_RGBA_12x12: + case TexFmt_ASTC_HDR_4x4: + case TexFmt_ASTC_HDR_5x5: + case TexFmt_ASTC_HDR_6x6: + case TexFmt_ASTC_HDR_8x8: + case TexFmt_ASTC_HDR_10x10: + case TexFmt_ASTC_HDR_12x12: + { + int blockDim = 4; + DWORD textureFormat = pTex->m_TextureFormat; + static_assert(TexFmt_ASTC_4x4 == TexFmt_ASTC_RGB_4x4, "Outdated assumption for texture format enum values"); + if (pTex->extra.textureFormatVersion >= 2 + && textureFormat >= TexFmt_ASTC_4x4 && textureFormat <= TexFmt_ASTC_12x12) + { + textureFormat = (textureFormat - TexFmt_ASTC_4x4) + TexFmt_ASTC_RGBA_4x4; + } + switch (textureFormat) + { + case TexFmt_ASTC_HDR_4x4: case TexFmt_ASTC_4x4: blockDim = 4; break; + case TexFmt_ASTC_HDR_5x5: case TexFmt_ASTC_5x5: blockDim = 5; break; + case TexFmt_ASTC_HDR_6x6: case TexFmt_ASTC_6x6: blockDim = 6; break; + case TexFmt_ASTC_HDR_8x8: case TexFmt_ASTC_8x8: blockDim = 8; break; + case TexFmt_ASTC_HDR_10x10: case TexFmt_ASTC_10x10: blockDim = 10; break; + case TexFmt_ASTC_HDR_12x12: case TexFmt_ASTC_12x12: blockDim = 12; break; + default: assert(false); + } + bool isHDR = (textureFormat >= TexFmt_ASTC_HDR_4x4 && textureFormat <= TexFmt_ASTC_HDR_12x12); + astcenc_config astcenc_cfg; + if (astcenc_config_init(isHDR ? ASTCENC_PRF_HDR : ASTCENC_PRF_LDR, + blockDim, blockDim, 1, + ASTCENC_PRE_FAST, + ASTCENC_FLG_DECOMPRESS_ONLY, &astcenc_cfg) + != ASTCENC_SUCCESS) + return false; + //Note: May be noticably more efficient to reuse a context, as per the astcenc documentation. + astcenc_context* pAstcencContext = nullptr; + if (astcenc_context_alloc(&astcenc_cfg, 1, &pAstcencContext) + != ASTCENC_SUCCESS) + return false; + astcenc_decompress_reset(pAstcencContext); + astcenc_image image_out = {}; + void* dataSlices[1] = { pOutBuf }; //z size = 1 + image_out.data = &dataSlices[0]; + image_out.data_type = ASTCENC_TYPE_U8; + image_out.dim_x = pTex->m_Width; + image_out.dim_y = pTex->m_Height; + image_out.dim_z = 1; + astcenc_swizzle swizzle = {}; + swizzle.r = ASTCENC_SWZ_R; + swizzle.g = ASTCENC_SWZ_G; + swizzle.b = ASTCENC_SWZ_B; + swizzle.a = ASTCENC_SWZ_A; + auto result = astcenc_decompress_image(pAstcencContext, pTex->pPictureData, pTex->_pictureDataSize, &image_out, &swizzle, 0); + astcenc_context_free(pAstcencContext); + if (result != ASTCENC_SUCCESS) + return false; + return true; + } + break; + } + return false; +} + +static int astcTexFmt_GetBlockDim(TextureFormat texFmt) +{ + switch (texFmt) + { + case TexFmt_ASTC_RGB_4x4: + case TexFmt_ASTC_RGBA_4x4: + case TexFmt_ASTC_HDR_4x4: + return 4; + case TexFmt_ASTC_RGB_5x5: + case TexFmt_ASTC_RGBA_5x5: + case TexFmt_ASTC_HDR_5x5: + return 5; + case TexFmt_ASTC_RGB_6x6: + case TexFmt_ASTC_RGBA_6x6: + case TexFmt_ASTC_HDR_6x6: + return 6; + case TexFmt_ASTC_RGB_8x8: + case TexFmt_ASTC_RGBA_8x8: + case TexFmt_ASTC_HDR_8x8: + return 8; + case TexFmt_ASTC_RGB_10x10: + case TexFmt_ASTC_RGBA_10x10: + case TexFmt_ASTC_HDR_10x10: + return 10; + case TexFmt_ASTC_RGB_12x12: + case TexFmt_ASTC_RGBA_12x12: + case TexFmt_ASTC_HDR_12x12: + return 12; + default: + throw std::invalid_argument("astcTexFmt_GetBlockDim expects a ASTC texture format value."); + } +} +size_t GetCompressedTextureDataSize(int width, int height, TextureFormat texFmt) +{ + if (width < 0 || height < 0) + throw std::invalid_argument("negative width or height"); + //DXT1 : ARGB file size factor (at 4x4 pixel blocks) : 1/8 + //DXT5 : ARGB file size factor (at 4x4 pixel blocks) : 1/4 + //BC4 : ARGB file size factor (at 4x4 pixel blocks) : 1/8 + //BC5 : ARGB file size factor (at 4x4 pixel blocks) : 1/4 + //BC6H : ARGB file size factor (at 4x4 pixel blocks) : 1/4 + //BC7 : ARGB file size factor (at 4x4 pixel blocks) : 1/4 + + switch (texFmt) + { + case TexFmt_DXT1: + case TexFmt_BC4: //same size as DXT1 + return (unsigned int)squish::GetStorageRequirements(width, height, squish::kDxt1); + case TexFmt_DXT5: + case TexFmt_BC5: //same size as DXT5 + case TexFmt_BC6H: //same size as DXT5 + case TexFmt_BC7: //same size as DXT5 + return (unsigned int)squish::GetStorageRequirements(width, height, squish::kDxt5); + case TexFmt_YUV2: //not actually compressed but it's also handled + case TexFmt_EAC_R: + case TexFmt_EAC_R_SIGNED: + case TexFmt_EAC_RG: + case TexFmt_EAC_RG_SIGNED: + case TexFmt_ETC_RGB4: + case TexFmt_ETC_RGB4_3DS: + case TexFmt_ETC_RGBA8_3DS: + case TexFmt_ETC2_RGB4: + case TexFmt_ETC2_RGBA1: + case TexFmt_ETC2_RGBA8: + case TexFmt_PVRTC_RGB2: + case TexFmt_PVRTC_RGBA2: + case TexFmt_PVRTC_RGB4: + case TexFmt_PVRTC_RGBA4: + { + typedef unsigned int(_cdecl *Wrap_GetMaxCompressedSize)(int width, int height, uint32_t texFmt); + HMODULE hModule = LoadLibrary(TEXT("TexToolWrap.dll")); + if (hModule) + { + Wrap_GetMaxCompressedSize GetMaxCompressedSize = (Wrap_GetMaxCompressedSize)GetProcAddress(hModule, "GetMaxCompressedSize"); + size_t decompSize = 0; + if (GetMaxCompressedSize) + decompSize = GetMaxCompressedSize(width, height, (uint32_t)texFmt); + FreeLibrary(hModule); + return decompSize; + } + } + break; + case TexFmt_ASTC_RGB_4x4: + case TexFmt_ASTC_RGB_5x5: + case TexFmt_ASTC_RGB_6x6: + case TexFmt_ASTC_RGB_8x8: + case TexFmt_ASTC_RGB_10x10: + case TexFmt_ASTC_RGB_12x12: + case TexFmt_ASTC_RGBA_4x4: + case TexFmt_ASTC_RGBA_5x5: + case TexFmt_ASTC_RGBA_6x6: + case TexFmt_ASTC_RGBA_8x8: + case TexFmt_ASTC_RGBA_10x10: + case TexFmt_ASTC_RGBA_12x12: + case TexFmt_ASTC_HDR_4x4: + case TexFmt_ASTC_HDR_5x5: + case TexFmt_ASTC_HDR_6x6: + case TexFmt_ASTC_HDR_8x8: + case TexFmt_ASTC_HDR_10x10: + case TexFmt_ASTC_HDR_12x12: + { + int blockDim = astcTexFmt_GetBlockDim(texFmt); + int blocksHor = (width / blockDim) + ((width % blockDim) ? 1 : 0); + int blocksVert = (height / blockDim) + ((height % blockDim) ? 1 : 0); + //128 bit per block, apparently for both SDR and HDR. + return (size_t)blocksHor * (size_t)blocksVert * (128 / 8); + } + break; + default: + return 0; + } + return 0; +} + +static void texgenpack_compress_callback(BlockUserData *user_data) {} +typedef void(__cdecl* ISPCCompressBlocksFn)(const rgba_surface* src, uint8_t* dst, void* settings); +void CompressDXTBlockCallback(const rgba_surface* src, uint8_t* dst, void* settings) +{ + squish::CompressImage((squish::u8*)src->ptr, src->width, src->height, dst, reinterpret_cast(settings)); +} +struct ISPCCompressThreadPar +{ + rgba_surface surface; + uint8_t *outBuf; + void *encSettings; + ISPCCompressBlocksFn compressFn; + HANDLE threadHandle; +}; +DWORD _stdcall ISPCCompressThreadEntry(PVOID tpar) +{ + ISPCCompressThreadPar *pThreadPar = (ISPCCompressThreadPar*)tpar; + pThreadPar->compressFn(&pThreadPar->surface, pThreadPar->outBuf, pThreadPar->encSettings); + return 0; +} +inline uint32_t GetNumberOfProcessors() +{ + SYSTEM_INFO sysInfo; + GetSystemInfo(&sysInfo); + uint32_t numThreads = sysInfo.dwNumberOfProcessors; + if (!numThreads) numThreads = 1; + return numThreads; +} +//Input must fit in 4x4 blocks; bytesPerBlock is 8 for DXT1, 16 for DXT5/BC6H/BC7 +void ISPCTexCompressMt(rgba_surface *surface, uint8_t *outBuf, void *settings, ISPCCompressBlocksFn compressFn, unsigned int bytesPerBlock) +{ + if (surface->height <= 0) + return; + uint32_t numThreads = GetNumberOfProcessors(); + uint32_t verticalBlocks = (uint32_t)(surface->height >> 2); + if (numThreads > verticalBlocks) + numThreads = verticalBlocks; + uint32_t vBlocksPerThread = verticalBlocks / numThreads; + uint32_t vBlocksUnassigned = verticalBlocks % numThreads; + ISPCCompressThreadPar *pThreadPars = new ISPCCompressThreadPar[numThreads]; + uint32_t curPixelLine = 0; + for (uint32_t i = 0; i < numThreads; i++) + { + pThreadPars[i].surface.ptr = &surface->ptr[curPixelLine*surface->stride]; + pThreadPars[i].surface.width = surface->width; + pThreadPars[i].surface.stride = surface->stride; + pThreadPars[i].surface.height = vBlocksPerThread << 2; + if (vBlocksUnassigned) + { + pThreadPars[i].surface.height += 4; + vBlocksUnassigned--; + } + pThreadPars[i].outBuf = &outBuf[(curPixelLine >> 2) * (surface->width >> 2) * bytesPerBlock]; + //&outBuf[curPixelLine * surface->width]; //1 byte per pixel + pThreadPars[i].encSettings = settings; + pThreadPars[i].compressFn = compressFn; + curPixelLine += pThreadPars[i].surface.height; + pThreadPars[i].threadHandle = CreateThread(NULL, 0, ISPCCompressThreadEntry, &pThreadPars[i], 0, NULL); + } + for (uint32_t i = 0; i < numThreads; i++) + { + WaitForSingleObject(pThreadPars[i].threadHandle, INFINITE); + CloseHandle(pThreadPars[i].threadHandle); + } + delete[] pThreadPars; +} + +//Simple linear filter. +//Also allows in-place generation of the next mip map level (i.e. inBuf == outBuf). +//Updates width and height parameters for the width/height of outBuf. +//Returns false if there is no next mip map level, in which case outBuf is untouched. +bool MakeNextMipmapLevel_RGBA32(const void *_inBuf, void *_outBuf, unsigned int &width, unsigned int &height) +{ + const uint8_t *inBuf = (const uint8_t*)_inBuf; + uint32_t *outBuf = (uint32_t*)_outBuf; + if (width && height) + { + unsigned int newWidth; + unsigned int newHeight; + if ((width >> 1) >= 1 && (height >> 1) >= 1) + { + newWidth = width >> 1; + newHeight = height >> 1; + for (unsigned int y = 1; y < height; y += 2) + { + for (unsigned int x = 1; x < width; x += 2) + { + uint8_t r = ((int)(inBuf)[4*((y-1) * width + (x-1))] + + (int)(inBuf)[4*(y * width + (x-1))] + + (int)(inBuf)[4*((y-1) * width + x)] + + (int)(inBuf)[4*(y * width + x)]) + >> 2; + uint8_t g = ((int)inBuf[1+4*((y-1) * width + (x-1))] + + (int)inBuf[1+4*(y * width + (x-1))] + + (int)inBuf[1+4*((y-1) * width + x)] + + (int)inBuf[1+4*(y * width + x)]) + >> 2; + uint8_t b = ((int)inBuf[2+4*((y-1) * width + (x-1))] + + (int)inBuf[2+4*(y * width + (x-1))] + + (int)inBuf[2+4*((y-1) * width + x)] + + (int)inBuf[2+4*(y * width + x)]) + >> 2; + uint8_t a = ((int)inBuf[3+4*((y-1) * width + (x-1))] + + (int)inBuf[3+4*(y * width + (x-1))] + + (int)inBuf[3+4*((y-1) * width + x)] + + (int)inBuf[3+4*(y * width + x)]) + >> 2; + outBuf[((y-1)>>1) * newWidth + ((x-1)>>1)] = r | (g << 8) | (b << 16) | (a << 24); + } + } + } + else + { + unsigned int _len; + if ((width >> 1) >= 1) //=> curWidth >= 2, curHeight == 1 + { + //(curHeight / 2) : ]0;1[ + newWidth = width >> 1; + newHeight = 1; + _len = width; + } + else if ((height >> 1) >= 1) //=> curWidth == 1, curHeight >= 2 + { + //(curWidth / 2) : ]0;1[ + newWidth = 1; + newHeight = height >> 1; + _len = height; + } + else + return false; + for (unsigned int i = 1; i < _len; i += 2) + { + uint8_t r = ((int)inBuf[4*(i-1)] + + (int)inBuf[4*(i)]) + >> 1; + uint8_t g = ((int)inBuf[1+4*(i-1)] + + (int)inBuf[1+4*(i)]) + >> 1; + uint8_t b = ((int)inBuf[2+4*(i-1)] + + (int)inBuf[2+4*(i)]) + >> 1; + uint8_t a = ((int)inBuf[3+4*(i-1)] + + (int)inBuf[3+4*(i)]) + >> 1; + outBuf[(i-1)>>1] = r | (g << 8) | (b << 16) | (a << 24); + } + } + width = newWidth; + height = newHeight; + } + else + return false; + return true; +} + +static rgba_surface ispc_texcomp_prepare_surface_RGBA32(void* pRGBA32Buf, unsigned int curWidth, unsigned int curHeight, + std::unique_ptr &pExtendedBuffer, unsigned int numOutChannels=4, unsigned int channelBits=8) +{ + if (numOutChannels == 0 || numOutChannels >= 4) + throw std::invalid_argument("ispc_texcomp_prepare_surface_RGBA32: numOutChannels should be in [1,4]"); + if (channelBits != 8 && channelBits != 16) + throw std::invalid_argument("ispc_texcomp_prepare_surface_RGBA32: channelBits must be either 8 or 16"); + if (curWidth > (unsigned int)std::numeric_limits::max() + || curHeight > (unsigned int)std::numeric_limits::max() + || (curWidth * 4 * sizeof(uint8_t)) > (unsigned int)std::numeric_limits::max()) + throw std::invalid_argument("ispc_texcomp_prepare_surface_RGBA32: width or height out of range"); + rgba_surface surface = { (uint8_t*)pRGBA32Buf, (int)curWidth, (int)curHeight, (int)(curWidth * 4 * sizeof(uint8_t)) }; + rgba_surface surfaceOut = surface; + + unsigned int extendedWidth = (curWidth + 3) & (~3); + unsigned int extendedHeight = (curHeight + 3) & (~3); + if (curWidth != extendedWidth || curHeight != extendedHeight || numOutChannels != 4) + { + pExtendedBuffer.reset(new uint8_t[extendedWidth * extendedHeight * numOutChannels * (channelBits/8)]); + //Copy the pixels, but only the first bytes per input pixel. + for (unsigned int y = 0; y < curHeight; ++y) + for (unsigned int x = 0; x < curWidth; ++x) + { + uint8_t *pIn = &((uint8_t*)pRGBA32Buf)[(y * curWidth + x) * 4]; + uint8_t *pOut = &pExtendedBuffer[(y * extendedWidth + x) * numOutChannels * (channelBits/8)]; + //Copy the respective channel; if channelBits=16, + // replicate the channel value for each output byte (e.g. 0x1F -> 0x1F1F). + for (unsigned int iCh = 0; iCh < numOutChannels; ++iCh) + for (unsigned int iBy = 0; iBy < channelBits/8; ++iBy) + pOut[iCh * channelBits/8 + iBy] = pIn[iCh]; + } + surfaceOut.width = extendedWidth; + surfaceOut.height = extendedHeight; + surfaceOut.stride = extendedWidth * numOutChannels * (channelBits / 8) * sizeof(uint8_t); + surfaceOut.ptr = pExtendedBuffer.get(); + + surface.ptr = surfaceOut.ptr; + surface.stride = surfaceOut.stride; + //Padding for the extended image dimensions (replicate the border pixel). + //Helper function by ispc_texcomp. + ReplicateBorders(&surfaceOut, &surface, 0, 0, channelBits * numOutChannels); + } + return surfaceOut; +} + +void RGBA32_ToCompressed(TextureFile *pTex, void *pOutBuf, void *pRGBA32Buf, QWORD &outputSize, int compressQuality, unsigned int curWidth, unsigned int curHeight) +{ + if (pTex->m_TextureFormat == TexFmt_DXT1Crunched || pTex->m_TextureFormat == TexFmt_DXT5Crunched + || pTex->m_TextureFormat == TexFmt_ETC_RGB4Crunched || pTex->m_TextureFormat == TexFmt_ETC2_RGBA8Crunched) + { + (pTex->extra.textureFormatVersion >= 1 ? CrunchTextureData_RGBA32_Unity : CrunchTextureData_RGBA32_Legacy) + (pTex, pRGBA32Buf, pOutBuf, outputSize, compressQuality, curWidth, curHeight); + } + else + { + switch (pTex->m_TextureFormat) + { + case TexFmt_DXT1: + case TexFmt_DXT5: + { + //GetCompressedTextureDataSize takes care of DXT block size alignment (4x4). + size_t texLen = GetCompressedTextureDataSize(curWidth, curHeight, (TextureFormat)pTex->m_TextureFormat); + //int squishDXTType = ((pTex->m_TextureFormat == TexFmt_DXT1 || pTex->m_TextureFormat == TexFmt_DXT1Crunched) ? squish::kDxt1 : squish::kDxt5); + if (outputSize >= texLen) + { + int dxtFlags = ((pTex->m_TextureFormat == TexFmt_DXT1) ? squish::kDxt1 : squish::kDxt5); + bool mt = false; + switch (compressQuality) + { + case 1: mt = true; //normal mt + case 0: //normal + default: + dxtFlags |= squish::kColourClusterFit; + break; + case 3: mt = true; //very fast mt + case 2: //very fast + dxtFlags |= squish::kColourRangeFit; + break; + case 5: mt = true; //slow mt + case 4: //slow + dxtFlags |= squish::kColourIterativeClusterFit; + break; + } + if (mt) + { + unsigned int extendedWidth = (curWidth + 3) & (~3); + unsigned int extendedHeight = (curHeight + 3) & (~3); + uint8_t *pRGBAExt = new uint8_t[extendedWidth * extendedHeight * 4]; + for (unsigned int y = 0; y < curHeight; y++) + { + memcpy(&pRGBAExt[y*extendedWidth*4], &((uint8_t*)pRGBA32Buf)[y*curWidth*4], curWidth * 4); + for (unsigned int x = curWidth; x < extendedWidth; x++) + { + unsigned int outIndex = (y*extendedWidth+x)*4; + unsigned int inIndex = (y*extendedWidth+curWidth-1)*4; + pRGBAExt[outIndex] = pRGBAExt[inIndex]; + pRGBAExt[outIndex+1] = pRGBAExt[inIndex+1]; + pRGBAExt[outIndex+2] = pRGBAExt[inIndex+2]; + pRGBAExt[outIndex+3] = pRGBAExt[inIndex+3]; + } + } + for (unsigned int y = curHeight; y < extendedHeight; y++) + { + memcpy(&pRGBAExt[(y*extendedWidth)*4], &pRGBAExt[((curHeight-1)*extendedWidth)*4], extendedWidth*4 * sizeof(uint8_t)); + } + rgba_surface surface; + surface.width = extendedWidth; + surface.height = extendedHeight; + surface.stride = extendedWidth * 4; + surface.ptr = pRGBAExt; + ISPCTexCompressMt( + &surface, + (uint8_t*)pOutBuf, + (void*)dxtFlags, + CompressDXTBlockCallback, + (pTex->m_TextureFormat == TexFmt_DXT1) ? 8 : 16 + ); + delete[] pRGBAExt; + } + else + squish::CompressImage((squish::u8*)pRGBA32Buf, curWidth, curHeight, pOutBuf, dxtFlags); + outputSize = texLen; + } + else + outputSize = 0; + } + break; + case TexFmt_BC4: + case TexFmt_BC5: + { + bool mt = false; + switch (compressQuality) + { + case 5: + case 6: + case 7: + case 8: + case 9: + mt = true; + break; + default: + mt = false; + } + std::unique_ptr pExtendedBuffer; + //BC4 only uses the red channel, BC5 uses red and green. + //The compressor expects the input data to only have these channels. + rgba_surface surface = ispc_texcomp_prepare_surface_RGBA32(pRGBA32Buf, curWidth, curHeight, pExtendedBuffer, (pTex->m_TextureFormat == TexFmt_BC4) ? 1 : 2); + size_t texLen = GetCompressedTextureDataSize(surface.width, surface.height, (TextureFormat)pTex->m_TextureFormat); + if (outputSize >= texLen) + { + if (pTex->m_TextureFormat == TexFmt_BC4) + { + if (mt) + ISPCTexCompressMt(&surface, (uint8_t*)pOutBuf, nullptr, (ISPCCompressBlocksFn)CompressBlocksBC4, 8); + else + CompressBlocksBC4(&surface, (uint8_t*)pOutBuf); + } + else //if (pTex->m_TextureFormat == TexFmt_BC5) + { + if (mt) + ISPCTexCompressMt(&surface, (uint8_t*)pOutBuf, nullptr, (ISPCCompressBlocksFn)CompressBlocksBC5, 16); + else + CompressBlocksBC5(&surface, (uint8_t*)pOutBuf); + } + outputSize = texLen; + } + else + outputSize = 0; + } + break; + case TexFmt_BC6H: + { + bc6h_enc_settings settings; + bool mt = false; + switch (compressQuality) + { + case 5: mt = true; + case 0: + GetProfile_bc6h_veryfast(&settings); + break; + case 6: mt = true; + case 1: + GetProfile_bc6h_fast(&settings); + break; + case 7: mt = true; + case 2: + default: + GetProfile_bc6h_basic(&settings); + break; + case 8: mt = true; + case 3: + GetProfile_bc6h_slow(&settings); + break; + case 9: mt = true; + case 4: + GetProfile_bc6h_veryslow(&settings); + break; + } + std::unique_ptr pExtendedBuffer; + //BC6H uses r,g,b with 16bit each; the alpha channel is present in the inputs but ignored. + rgba_surface surface = ispc_texcomp_prepare_surface_RGBA32(pRGBA32Buf, curWidth, curHeight, pExtendedBuffer, 4, 16); + size_t texLen = GetCompressedTextureDataSize(surface.width, surface.height, (TextureFormat)pTex->m_TextureFormat); + if (outputSize >= texLen) + { + if (mt) + ISPCTexCompressMt(&surface, (uint8_t*)pOutBuf, &settings, (ISPCCompressBlocksFn)CompressBlocksBC6H, 16); + else + CompressBlocksBC6H(&surface, (uint8_t*)pOutBuf, &settings); + outputSize = texLen; + } + else + outputSize = 0; + } + break; + case TexFmt_BC7: + { + bc7_enc_settings settings; + bool mt = false; + switch (compressQuality) + { + case 5: mt = true; + case 0: + GetProfile_alpha_ultrafast(&settings); + break; + case 6: mt = true; + case 1: + GetProfile_alpha_veryfast(&settings); + break; + case 7: mt = true; + case 2: + GetProfile_alpha_fast(&settings); + break; + case 8: mt = true; + case 3: + default: + GetProfile_alpha_basic(&settings); + break; + case 9: mt = true; + case 4: + GetProfile_alpha_slow(&settings); + break; + } + std::unique_ptr pExtendedBuffer; + //Regular RGBA 32bpp input. + rgba_surface surface = ispc_texcomp_prepare_surface_RGBA32(pRGBA32Buf, curWidth, curHeight, pExtendedBuffer, 4, 8); + size_t texLen = GetCompressedTextureDataSize(surface.width, surface.height, (TextureFormat)pTex->m_TextureFormat); + if (outputSize >= texLen) + { + if (mt) + ISPCTexCompressMt(&surface, (uint8_t*)pOutBuf, &settings, (ISPCCompressBlocksFn)CompressBlocksBC7, 16); + else + CompressBlocksBC7(&surface, (uint8_t*)pOutBuf, &settings); + outputSize = texLen; + } + else + outputSize = 0; + } + break; + case TexFmt_YUV2: //not actually compressed but it's also handled + case TexFmt_EAC_R: + case TexFmt_EAC_R_SIGNED: + case TexFmt_EAC_RG: + case TexFmt_EAC_RG_SIGNED: + case TexFmt_ETC_RGB4: + case TexFmt_ETC_RGB4_3DS: + case TexFmt_ETC_RGBA8_3DS: + case TexFmt_ETC2_RGB4: + case TexFmt_ETC2_RGBA1: + case TexFmt_ETC2_RGBA8: + case TexFmt_PVRTC_RGB2: + case TexFmt_PVRTC_RGBA2: + case TexFmt_PVRTC_RGB4: + case TexFmt_PVRTC_RGBA4: + { + typedef size_t(_cdecl* Wrap_Compress)(uint32_t texFmt, unsigned int height, unsigned int width, unsigned int mipCount, void* pInBuf, size_t inBufLen, void* pOutBuf, size_t outBufLen, int compressQuality); + HMODULE hModule = LoadLibrary(TEXT("TexToolWrap.dll")); + if (hModule) + { + Wrap_Compress Compress = (Wrap_Compress)GetProcAddress(hModule, "Compress"); + if (Compress) + outputSize = + Compress(pTex->m_TextureFormat, + curHeight, curWidth, 1, + pRGBA32Buf, curWidth * curHeight * 4, + pOutBuf, outputSize, compressQuality); + else + outputSize = 0; + FreeLibrary(hModule); + } + } + break; + case TexFmt_ASTC_RGB_4x4: + case TexFmt_ASTC_RGB_5x5: + case TexFmt_ASTC_RGB_6x6: + case TexFmt_ASTC_RGB_8x8: + case TexFmt_ASTC_RGB_10x10: + case TexFmt_ASTC_RGB_12x12: + case TexFmt_ASTC_RGBA_4x4: + case TexFmt_ASTC_RGBA_5x5: + case TexFmt_ASTC_RGBA_6x6: + case TexFmt_ASTC_RGBA_8x8: + case TexFmt_ASTC_RGBA_10x10: + case TexFmt_ASTC_RGBA_12x12: + { + bool mt = false; + float astcenc_profile = ASTCENC_PRE_FAST; + switch (compressQuality) + { + case 5: mt = true; + case 0: + astcenc_profile = ASTCENC_PRE_FASTEST; + break; + case 6: mt = true; + case 1: + astcenc_profile = ASTCENC_PRE_FAST; + break; + case 7: mt = true; + case 2: + astcenc_profile = ASTCENC_PRE_MEDIUM; + break; + case 8: mt = true; + case 3: + default: + astcenc_profile = ASTCENC_PRE_THOROUGH; + break; + case 9: mt = true; + case 4: + astcenc_profile = ASTCENC_PRE_EXHAUSTIVE; + break; + } + + int blockDim = astcTexFmt_GetBlockDim((TextureFormat)pTex->m_TextureFormat); + bool isHDR = (pTex->m_TextureFormat >= TexFmt_ASTC_HDR_4x4 && pTex->m_TextureFormat <= TexFmt_ASTC_HDR_12x12); + + astcenc_config astcenc_cfg; + if (astcenc_config_init(isHDR ? ASTCENC_PRF_HDR : ASTCENC_PRF_LDR, + blockDim, blockDim, 1, + astcenc_profile, + 0, &astcenc_cfg) + != ASTCENC_SUCCESS) + return; + //Note: May be noticably more efficient to reuse a context, as per the astcenc documentation. + astcenc_context* pAstcencContext = nullptr; + unsigned int numThreads = 1; + if (mt) + { + numThreads = std::thread::hardware_concurrency(); + if (numThreads == 0) numThreads = 2; + } + if (astcenc_context_alloc(&astcenc_cfg, numThreads, &pAstcencContext) + != ASTCENC_SUCCESS) + return; + astcenc_compress_reset(pAstcencContext); + astcenc_image image_in = {}; + void* dataSlices[1] = { pRGBA32Buf }; //z size = 1 + image_in.data = &dataSlices[0]; + image_in.data_type = ASTCENC_TYPE_U8; + image_in.dim_x = pTex->m_Width; + image_in.dim_y = pTex->m_Height; + image_in.dim_z = 1; + astcenc_swizzle swizzle = {}; + swizzle.r = ASTCENC_SWZ_R; + swizzle.g = ASTCENC_SWZ_G; + swizzle.b = ASTCENC_SWZ_B; + swizzle.a = ASTCENC_SWZ_A; + std::vector threads(numThreads - 1); + for (unsigned int i = 1; i < numThreads; ++i) + { + threads[i - 1] = std::jthread([pAstcencContext, &image_in, &swizzle, pOutBuf, outputSize, i]() + { + astcenc_compress_image(pAstcencContext, &image_in, &swizzle, (uint8_t*)pOutBuf, outputSize, i); + }); + } + auto result = astcenc_compress_image(pAstcencContext, &image_in, &swizzle, (uint8_t*)pOutBuf, outputSize, 0); + threads.clear(); + astcenc_context_free(pAstcencContext); + if (result != ASTCENC_SUCCESS) + return; + outputSize = GetCompressedTextureDataSize((int)pTex->m_Width, (int)pTex->m_Height, (TextureFormat)pTex->m_TextureFormat); + } + break; + } + } +} + +ASSETSTOOLS_API bool MakeTextureData(TextureFile *pTex, void *pRGBA32Buf, int compressQuality) +{ + uint8_t *outPictureData = pTex->pPictureData; + if (pTex->m_TextureFormat == TexFmt_DXT1Crunched || pTex->m_TextureFormat == TexFmt_DXT5Crunched + || pTex->m_TextureFormat == TexFmt_ETC_RGB4Crunched || pTex->m_TextureFormat == TexFmt_ETC2_RGBA8Crunched) + { + QWORD outputSize = pTex->_pictureDataSize; + + if ((pTex->extra.textureFormatVersion >= 1 ? CrunchTextureData_RGBA32_Unity : CrunchTextureData_RGBA32_Legacy) + (pTex, pRGBA32Buf, outPictureData, outputSize, compressQuality, pTex->m_Width, pTex->m_Height)) + { + pTex->_pictureDataSize = pTex->m_CompleteImageSize = (uint32_t)outputSize; + return true; + } + else + return false; + } + int curMipCount = pTex->m_MipCount; + int totalMipCount = 0; + unsigned int curWidth = pTex->m_Width; + unsigned int curHeight = pTex->m_Height; + QWORD curOutIndex = 0; + do { + switch (pTex->m_TextureFormat) { + case TexFmt_ARGB32: + { + if ((curOutIndex > pTex->_pictureDataSize) || (pTex->_pictureDataSize - curOutIndex) < (curWidth * curHeight * 4)) + return false; + uint32_t pixelCount = curWidth * curHeight; + for (uint32_t i = 0; i < pixelCount; i++) + { + uint8_t rgba[4]; + *((uint32_t*)rgba) = ((uint32_t*)pRGBA32Buf)[i]; + outPictureData[curOutIndex+i*4] = rgba[3]; //A + outPictureData[curOutIndex+i*4+1] = rgba[0]; //R + outPictureData[curOutIndex+i*4+2] = rgba[1]; //B + outPictureData[curOutIndex+i*4+3] = rgba[2]; //G + } + curOutIndex += (curWidth * curHeight * 4); + } + break; + case TexFmt_BGRA32Old: + case TexFmt_BGRA32New: + { + if ((curOutIndex > pTex->_pictureDataSize) || (pTex->_pictureDataSize - curOutIndex) < (curWidth * curHeight * 4)) + return false; + uint32_t pixelCount = curWidth * curHeight; + for (uint32_t i = 0; i < pixelCount; i++) + { + uint8_t rgba[4]; + *((uint32_t*)rgba) = ((uint32_t*)pRGBA32Buf)[i]; + outPictureData[curOutIndex+i*4] = rgba[2]; //B + outPictureData[curOutIndex+i*4+1] = rgba[1]; //G + outPictureData[curOutIndex+i*4+2] = rgba[0]; //R + outPictureData[curOutIndex+i*4+3] = rgba[3]; //A + } + curOutIndex += (curWidth * curHeight * 4); + } + break; + case TexFmt_RGBA32: + if ((curOutIndex > pTex->_pictureDataSize) || (pTex->_pictureDataSize - curOutIndex) < (curWidth * curHeight * 4)) + return false; + memcpy(&outPictureData[curOutIndex], pRGBA32Buf, curWidth * curHeight * 4); + curOutIndex += (curWidth * curHeight * 4); + break; + case TexFmt_RGB24: + { + if ((curOutIndex > pTex->_pictureDataSize) || (pTex->_pictureDataSize - curOutIndex) < (curWidth * curHeight * 3)) + return false; + uint32_t pixelCount = curWidth * curHeight; + for (uint32_t i = 0; i < pixelCount; i++) + { + uint8_t rgba[4]; + *((uint32_t*)rgba) = ((uint32_t*)pRGBA32Buf)[i]; + outPictureData[curOutIndex+i*3] = rgba[0]; //R + outPictureData[curOutIndex+i*3+1] = rgba[1]; //G + outPictureData[curOutIndex+i*3+2] = rgba[2]; //B + } + curOutIndex += (curWidth * curHeight * 3); + } + break; + case TexFmt_ARGB4444: + { + if ((curOutIndex > pTex->_pictureDataSize) || (pTex->_pictureDataSize - curOutIndex) < (curWidth * curHeight * 2)) + return false; + //G,B,A,R + uint32_t pixelCount = curWidth * curHeight; + for (uint32_t i = 0; i < pixelCount; i++) + { + uint8_t rgba[4]; + *((uint32_t*)rgba) = ((uint32_t*)pRGBA32Buf)[i]; + // >> 4 is equal to / 16 (to reduce the 8bit data to 4bit) + outPictureData[curOutIndex+i*2] = (((rgba[1] >> 4) & 15) << 4) | ((rgba[2] >> 4) & 15); //G,B + outPictureData[curOutIndex+i*2+1] = (((rgba[3] >> 4) & 15) << 4) | ((rgba[0] >> 4) & 15); //A,R + } + curOutIndex += (curWidth * curHeight * 2); + } + break; + case TexFmt_RGBA4444: + { + if ((curOutIndex > pTex->_pictureDataSize) || (pTex->_pictureDataSize - curOutIndex) < (curWidth * curHeight * 2)) + return false; + //BARG + uint32_t pixelCount = curWidth * curHeight; + for (uint32_t i = 0; i < pixelCount; i++) + { + uint8_t rgba[4]; + *((uint32_t*)rgba) = ((uint32_t*)pRGBA32Buf)[i]; + // >> 4 is equal to / 16 (to reduce the 8bit data to 4bit) + outPictureData[curOutIndex+i*2+1] = (((rgba[0] >> 4) & 15) << 4) | ((rgba[1] >> 4) & 15); //R,G + outPictureData[curOutIndex+i*2] = (((rgba[2] >> 4) & 15) << 4) | ((rgba[3] >> 4) & 15); //B,A + } + curOutIndex += (curWidth * curHeight * 2); + } + break; + case TexFmt_RGB565: + { + if ((curOutIndex > pTex->_pictureDataSize) || (pTex->_pictureDataSize - curOutIndex) < (curWidth * curHeight * 2)) + return false; + //g3(low)b5g3(high)r5 + uint32_t pixelCount = curWidth * curHeight; + for (uint32_t i = 0; i < pixelCount; i++) + { + uint8_t rgba[4]; + *((uint32_t*)rgba) = ((uint32_t*)pRGBA32Buf)[i]; + // >> 3 is equal to / 8 (to reduce the 8bit data to 5bit) + uint8_t r5 = (rgba[0] >> 3) & 31; + // >> 2 is equal to / 4 (to reduce the 8bit data to 6bit) + uint8_t g6 = (rgba[1] >> 2) & 63; + // >> 3 is equal to / 8 (to reduce the 8bit data to 5bit) + uint8_t b5 = (rgba[2] >> 3) & 31; + uint16_t rgb565 = ((uint16_t)r5 << 11) | ((uint16_t)g6 << 5) | ((uint16_t)b5); + *(uint16_t*)(&outPictureData[curOutIndex+i*2]) = rgb565;//((rgb565 & 0xFF00) >> 8) | ((rgb565 & 0x00FF) << 8); + } + curOutIndex += (curWidth * curHeight * 2); + } + break; + case TexFmt_Alpha8: + { + if ((curOutIndex > pTex->_pictureDataSize) || (pTex->_pictureDataSize - curOutIndex) < (curWidth * curHeight)) + return false; + uint32_t pixelCount = curWidth * curHeight; + for (uint32_t i = 0; i < pixelCount; i++) + { + outPictureData[curOutIndex+i] = ((uint8_t*)pRGBA32Buf)[i*4+3]; + } + curOutIndex += (curWidth * curHeight); + } + break; + case TexFmt_RHalf: + { + if ((curOutIndex > pTex->_pictureDataSize) || (pTex->_pictureDataSize - curOutIndex) < (curWidth * curHeight * 2)) + return false; + uint32_t pixelCount = curWidth * curHeight; + HalfFloat hf; + for (uint32_t i = 0; i < pixelCount; i++) + { + hf.toHalf((float)((uint8_t*)pRGBA32Buf)[i*4] / 255.0F); + *(uint16_t*)(&outPictureData[curOutIndex+i*2]) = hf.half; + } + curOutIndex += (curWidth * curHeight * 2); + } + break; + case TexFmt_RGHalf: + { + if ((curOutIndex > pTex->_pictureDataSize) || (pTex->_pictureDataSize - curOutIndex) < (curWidth * curHeight * 4)) + return false; + uint32_t pixelCount = curWidth * curHeight; + HalfFloat hf; + for (uint32_t i = 0; i < pixelCount; i++) + { + hf.toHalf((float)((uint8_t*)pRGBA32Buf)[i*4] / 255.0F); + *(uint16_t*)(&outPictureData[curOutIndex+i*4]) = hf.half; + hf.toHalf((float)((uint8_t*)pRGBA32Buf)[i*4+1] / 255.0F); + *(uint16_t*)(&outPictureData[curOutIndex+i*4+2]) = hf.half; + } + curOutIndex += (curWidth * curHeight * 4); + } + break; + case TexFmt_RGBAHalf: + { + if ((curOutIndex > pTex->_pictureDataSize) || (pTex->_pictureDataSize - curOutIndex) < (curWidth * curHeight * 8)) + return false; + uint32_t pixelCount = curWidth * curHeight; + HalfFloat hf; + for (uint32_t i = 0; i < pixelCount; i++) + { + hf.toHalf((float)((uint8_t*)pRGBA32Buf)[i*4] / 255.0F); + *(uint16_t*)(&outPictureData[curOutIndex+i*8]) = hf.half; + hf.toHalf((float)((uint8_t*)pRGBA32Buf)[i*4+1] / 255.0F); + *(uint16_t*)(&outPictureData[curOutIndex+i*8+2]) = hf.half; + hf.toHalf((float)((uint8_t*)pRGBA32Buf)[i*4+2] / 255.0F); + *(uint16_t*)(&outPictureData[curOutIndex+i*8+4]) = hf.half; + hf.toHalf((float)((uint8_t*)pRGBA32Buf)[i*4+3] / 255.0F); + *(uint16_t*)(&outPictureData[curOutIndex+i*8+6]) = hf.half; + } + curOutIndex += (curWidth * curHeight * 8); + } + break; + case TexFmt_RFloat: + { + if ((curOutIndex > pTex->_pictureDataSize) || (pTex->_pictureDataSize - curOutIndex) < (curWidth * curHeight * 4)) + return false; + uint32_t pixelCount = curWidth * curHeight; + for (uint32_t i = 0; i < pixelCount; i++) + { + *(float*)(&outPictureData[curOutIndex+i*4]) = (float)((uint8_t*)pRGBA32Buf)[i*4] / 255.0F; + } + curOutIndex += (curWidth * curHeight * 4); + } + break; + case TexFmt_RGFloat: + { + if ((curOutIndex > pTex->_pictureDataSize) || (pTex->_pictureDataSize - curOutIndex) < (curWidth * curHeight * 8)) + return false; + uint32_t pixelCount = curWidth * curHeight; + for (uint32_t i = 0; i < pixelCount; i++) + { + *(float*)(&outPictureData[curOutIndex+i*8]) = (float)((uint8_t*)pRGBA32Buf)[i*4] / 255.0F; + *(float*)(&outPictureData[curOutIndex+i*8+4]) = (float)((uint8_t*)pRGBA32Buf)[i*4+1] / 255.0F; + } + curOutIndex += (curWidth * curHeight * 8); + } + break; + case TexFmt_RGBAFloat: + { + if ((curOutIndex > pTex->_pictureDataSize) || (pTex->_pictureDataSize - curOutIndex) < (curWidth * curHeight * 16)) + return false; + uint32_t pixelCount = curWidth * curHeight; + for (uint32_t i = 0; i < pixelCount; i++) + { + *(float*)(&outPictureData[curOutIndex+i*16]) = (float)((uint8_t*)pRGBA32Buf)[i*4] / 255.0F; + *(float*)(&outPictureData[curOutIndex+i*16+4]) = (float)((uint8_t*)pRGBA32Buf)[i*4+1] / 255.0F; + *(float*)(&outPictureData[curOutIndex+i*16+8]) = (float)((uint8_t*)pRGBA32Buf)[i*4+2] / 255.0F; + *(float*)(&outPictureData[curOutIndex+i*16+12]) = (float)((uint8_t*)pRGBA32Buf)[i*4+3] / 255.0F; + } + curOutIndex += (curWidth * curHeight * 16); + } + break; + case TexFmt_RGB9e5Float: + { + if ((curOutIndex > pTex->_pictureDataSize) || (pTex->_pictureDataSize - curOutIndex) < (curWidth * curHeight * 4)) + return false; + uint32_t pixelCount = curWidth * curHeight; + for (uint32_t i = 0; i < pixelCount; i++) + { + float color[3] = { + (float)((uint8_t*)pRGBA32Buf)[i*4] / 255.0F, + (float)((uint8_t*)pRGBA32Buf)[i*4+1] / 255.0F, + (float)((uint8_t*)pRGBA32Buf)[i*4+2] / 255.0F + }; + RGB9e5Float rgbFloat; rgbFloat.toRGB9e5(color); + *(unsigned int*)(&outPictureData[curOutIndex+i*4]) = rgbFloat.value; + } + curOutIndex += (curWidth * curHeight * 4); + } + break; + case TexFmt_RG16: + { + if ((curOutIndex > pTex->_pictureDataSize) || (pTex->_pictureDataSize - curOutIndex) < (curWidth * curHeight * 2)) + return false; + uint32_t pixelCount = curWidth * curHeight; + for (uint32_t i = 0; i < pixelCount; i++) + { + outPictureData[curOutIndex+2*i] = ((uint8_t*)pRGBA32Buf)[i*4]; + outPictureData[curOutIndex+2*i+1] = ((uint8_t*)pRGBA32Buf)[i*4+1]; + } + curOutIndex += (curWidth * curHeight * 2); + } + break; + case TexFmt_R8: + { + if ((curOutIndex > pTex->_pictureDataSize) || (pTex->_pictureDataSize - curOutIndex) < (curWidth * curHeight)) + return false; + uint32_t pixelCount = curWidth * curHeight; + for (uint32_t i = 0; i < pixelCount; i++) + { + outPictureData[curOutIndex+i] = ((uint8_t*)pRGBA32Buf)[i*4]; + } + curOutIndex += (curWidth * curHeight); + } + break; + case TexFmt_R16: + { + if ((curOutIndex > pTex->_pictureDataSize/2) || (pTex->_pictureDataSize/2 - curOutIndex) < (curWidth * curHeight)) + return false; + uint32_t pixelCount = curWidth * curHeight; + for (uint32_t i = 0; i < pixelCount; i++) + { + uint8_t r = ((uint8_t*)pRGBA32Buf)[i * 4]; + ((uint16_t*)outPictureData)[curOutIndex + i] = ((uint16_t)r << 8) | r; + } + curOutIndex += (curWidth * curHeight); + } + break; + case TexFmt_RG32: + { + if ((curOutIndex > pTex->_pictureDataSize / 4) || (pTex->_pictureDataSize / 4 - curOutIndex) < (curWidth * curHeight)) + return false; + uint32_t pixelCount = curWidth * curHeight; + for (uint32_t i = 0; i < pixelCount; i++) + { + uint8_t r = ((uint8_t*)pRGBA32Buf)[i * 4]; + uint8_t g = ((uint8_t*)pRGBA32Buf)[i * 4 + 1]; + ((uint16_t*)outPictureData)[(curOutIndex + i) * 2] = ((uint16_t)r << 8) | r; + ((uint16_t*)outPictureData)[(curOutIndex + i) * 2 + 1] = ((uint16_t)g << 8) | g; + } + curOutIndex += (curWidth * curHeight); + } + break; + case TexFmt_RGB48: + { + if ((curOutIndex > pTex->_pictureDataSize / 6) || (pTex->_pictureDataSize / 6 - curOutIndex) < (curWidth * curHeight)) + return false; + uint32_t pixelCount = curWidth * curHeight; + for (uint32_t i = 0; i < pixelCount; i++) + { + uint8_t r = ((uint8_t*)pRGBA32Buf)[i * 4]; + uint8_t g = ((uint8_t*)pRGBA32Buf)[i * 4 + 1]; + uint8_t b = ((uint8_t*)pRGBA32Buf)[i * 4 + 2]; + ((uint16_t*)outPictureData)[(curOutIndex + i) * 3] = ((uint16_t)r << 8) | r; + ((uint16_t*)outPictureData)[(curOutIndex + i) * 3 + 1] = ((uint16_t)g << 8) | g; + ((uint16_t*)outPictureData)[(curOutIndex + i) * 3 + 2] = ((uint16_t)b << 8) | b; + } + curOutIndex += (curWidth * curHeight); + } + break; + case TexFmt_RGBA64: + { + if ((curOutIndex > pTex->_pictureDataSize / 8) || (pTex->_pictureDataSize / 8 - curOutIndex) < (curWidth * curHeight)) + return false; + uint32_t pixelCount = curWidth * curHeight; + for (uint32_t i = 0; i < pixelCount; i++) + { + uint8_t r = ((uint8_t*)pRGBA32Buf)[i * 4]; + uint8_t g = ((uint8_t*)pRGBA32Buf)[i * 4 + 1]; + uint8_t b = ((uint8_t*)pRGBA32Buf)[i * 4 + 2]; + uint8_t a = ((uint8_t*)pRGBA32Buf)[i * 4 + 3]; + ((uint16_t*)outPictureData)[(curOutIndex + i) * 4] = ((uint16_t)r << 8) | r; + ((uint16_t*)outPictureData)[(curOutIndex + i) * 4 + 1] = ((uint16_t)g << 8) | g; + ((uint16_t*)outPictureData)[(curOutIndex + i) * 4 + 2] = ((uint16_t)b << 8) | b; + ((uint16_t*)outPictureData)[(curOutIndex + i) * 4 + 3] = ((uint16_t)a << 8) | a; + } + curOutIndex += (curWidth * curHeight); + } + break; + case TexFmt_DXT1: + case TexFmt_DXT1Crunched: + case TexFmt_DXT5: + case TexFmt_DXT5Crunched: + case TexFmt_BC4: + case TexFmt_BC5: + case TexFmt_BC6H: + case TexFmt_BC7: + case TexFmt_YUV2: //not actually compressed but it's also handled + case TexFmt_EAC_R: + case TexFmt_EAC_R_SIGNED: + case TexFmt_EAC_RG: + case TexFmt_EAC_RG_SIGNED: + case TexFmt_ETC_RGB4: + case TexFmt_ETC_RGB4Crunched: + case TexFmt_ETC_RGB4_3DS: + case TexFmt_ETC_RGBA8_3DS: + case TexFmt_ETC2_RGB4: + case TexFmt_ETC2_RGBA1: + case TexFmt_ETC2_RGBA8: + case TexFmt_ETC2_RGBA8Crunched: + case TexFmt_PVRTC_RGB2: + case TexFmt_PVRTC_RGBA2: + case TexFmt_PVRTC_RGB4: + case TexFmt_PVRTC_RGBA4: + case TexFmt_ASTC_RGB_4x4: + case TexFmt_ASTC_RGB_5x5: + case TexFmt_ASTC_RGB_6x6: + case TexFmt_ASTC_RGB_8x8: + case TexFmt_ASTC_RGB_10x10: + case TexFmt_ASTC_RGB_12x12: + case TexFmt_ASTC_RGBA_4x4: + case TexFmt_ASTC_RGBA_5x5: + case TexFmt_ASTC_RGBA_6x6: + case TexFmt_ASTC_RGBA_8x8: + case TexFmt_ASTC_RGBA_10x10: + case TexFmt_ASTC_RGBA_12x12: + case TexFmt_ASTC_HDR_4x4: + case TexFmt_ASTC_HDR_5x5: + case TexFmt_ASTC_HDR_6x6: + case TexFmt_ASTC_HDR_8x8: + case TexFmt_ASTC_HDR_10x10: + case TexFmt_ASTC_HDR_12x12: + { + QWORD compressedSize = pTex->_pictureDataSize - curOutIndex; + RGBA32_ToCompressed(pTex, &pTex->pPictureData[curOutIndex], pRGBA32Buf, compressedSize, compressQuality, curWidth, curHeight); + /*pTex->_pictureDataSize = pTex->m_CompleteImageSize = (uint32_t)*/curOutIndex += compressedSize; + if (compressedSize == 0 && (curWidth * curHeight) != 0) + { + printf("Failed converting texture format RGBA32 to %i!", pTex->m_TextureFormat); + return false; + } + } + break; + default: + printf("Unsupported texture format %i!", pTex->m_TextureFormat); + return false; + } + totalMipCount++; + if ((curMipCount > 1) && MakeNextMipmapLevel_RGBA32(pRGBA32Buf, pRGBA32Buf, curWidth, curHeight)) + curMipCount--; + else + break; + } while (true); + pTex->_pictureDataSize = pTex->m_CompleteImageSize = (uint32_t)curOutIndex; + pTex->m_MipCount = totalMipCount; + //printf("Successfully converted texture format RGBA32 to %i!", pTex->m_TextureFormat); + return true; +} + +bool GetTextureData(TextureFile *pTex, void *pOutBuf) +{ + switch (pTex->m_TextureFormat) { + case TexFmt_Alpha8: + case TexFmt_RGB24: + case TexFmt_RGBA32: + case TexFmt_BGRA32Old: + case TexFmt_BGRA32New: + case TexFmt_ARGB32: + case TexFmt_ARGB4444: + case TexFmt_RGBA4444: + case TexFmt_RGB565: + case TexFmt_R16: + case TexFmt_RHalf: + case TexFmt_RGHalf: + case TexFmt_RGBAHalf: + case TexFmt_RFloat: + case TexFmt_RGFloat: + case TexFmt_RGBAFloat: + case TexFmt_RGB9e5Float: + case TexFmt_RG16: + case TexFmt_R8: + if (!Uncompressed_ToRGBA32(pTex, pOutBuf)) + { + return false; + } + break; + case TexFmt_DXT1: + case TexFmt_DXT1Crunched: + case TexFmt_DXT5: + case TexFmt_DXT5Crunched: + case TexFmt_BC4: + case TexFmt_BC5: + case TexFmt_BC6H: + case TexFmt_BC7: + case TexFmt_YUV2: //not actually compressed but it's also handled + case TexFmt_EAC_R: + case TexFmt_EAC_R_SIGNED: + case TexFmt_EAC_RG: + case TexFmt_EAC_RG_SIGNED: + case TexFmt_ETC_RGB4: + case TexFmt_ETC_RGB4Crunched: + case TexFmt_ETC_RGB4_3DS: + case TexFmt_ETC_RGBA8_3DS: + case TexFmt_ETC2_RGB4: + case TexFmt_ETC2_RGBA1: + case TexFmt_ETC2_RGBA8: + case TexFmt_ETC2_RGBA8Crunched: + case TexFmt_PVRTC_RGB2: + case TexFmt_PVRTC_RGBA2: + case TexFmt_PVRTC_RGB4: + case TexFmt_PVRTC_RGBA4: + case TexFmt_ASTC_RGB_4x4: + case TexFmt_ASTC_RGB_5x5: + case TexFmt_ASTC_RGB_6x6: + case TexFmt_ASTC_RGB_8x8: + case TexFmt_ASTC_RGB_10x10: + case TexFmt_ASTC_RGB_12x12: + case TexFmt_ASTC_RGBA_4x4: + case TexFmt_ASTC_RGBA_5x5: + case TexFmt_ASTC_RGBA_6x6: + case TexFmt_ASTC_RGBA_8x8: + case TexFmt_ASTC_RGBA_10x10: + case TexFmt_ASTC_RGBA_12x12: + if (!Compressed_ToRGBA32(pTex, pOutBuf)) + { + printf("Failed decompressing from texture format %i!", pTex->m_TextureFormat); + return false; + } + break; + default: + printf("Unsupported texture format %i!", pTex->m_TextureFormat); + return false; + } + //printf("Successfully converted texture format %i to RGBA32!", pTex->m_TextureFormat); + return true; +} diff --git a/AssetsTools/TextureFileFormat.h b/AssetsTools/TextureFileFormat.h new file mode 100644 index 0000000..e5de826 --- /dev/null +++ b/AssetsTools/TextureFileFormat.h @@ -0,0 +1,155 @@ +#pragma once +#include "defines.h" +#include "AssetTypeClass.h" + +#define MinTextureFileSize 0x38 +struct TextureFile +{ + struct { + int textureFormatVersion = 0; //version code returned by SupportsTextureFormat + } extra; + + std::string m_Name; + int m_ForcedFallbackFormat = 0; //added with Unity 2017.3 + bool m_DownscaleFallback = false; //added with Unity 2017.3 + bool m_IsAlphaChannelOptional = false; //added with Unity 2020.2 + unsigned int m_Width = 0; + unsigned int m_Height = 0; + uint32_t m_CompleteImageSize = 0; + int m_MipsStripped = 0; //added with Unity 2020.1 + uint32_t m_TextureFormat = 0; //Values from enum TextureFormat + int m_MipCount = 0; //added with Unity 5.2 + bool m_MipMap = false; //removed with Unity 5.2 + bool m_IsReadable = false; + bool m_IsPreProcessed = false; //added with Unity 2019.4 + bool m_ReadAllowed = false; //removed with Unity 5.5 + bool m_IgnoreMasterTextureLimit = false;//added with Unity 2019.3 + bool m_StreamingMipmaps = false; //added with Unity 2018.2 + int m_StreamingMipmapsPriority = 0; //added with Unity 2018.2 + int m_ImageCount = 0; + int m_TextureDimension = 0; + struct GLTextureSettings + { + int m_FilterMode; //FilterMode : Point, Bilinear, Trilinear + int m_Aniso; //Anisotropic filtering level. + float m_MipBias; + int m_WrapMode; //removed with Unity 2017.1 //0x28 or 0x2C; TextureWrapMode : Repeat, Clamp + int m_WrapU; //added with Unity 2017.1 + int m_WrapV; //added with Unity 2017.1 + int m_WrapW; //added with Unity 2017.1 + } m_TextureSettings = {}; + int m_LightmapFormat = 0; //LightmapsMode(?) : NonDirectional, CombinedDirectional, SeparateDirectional + int m_ColorSpace = 0; //added with Unity 3.5 //ColorSpace : Gamma, Linear + std::vector m_PlatformBlob; //added with Unity 2020.2 + uint32_t _pictureDataSize = 0; //The same as m_CompleteImageSize if m_StreamData is empty. + uint8_t *pPictureData = nullptr; + struct StreamingInfo //added with Unity 5.3 + { + uint64_t offset; + unsigned int size; + std::string path; + } m_StreamData; +}; +enum TextureFormat { //by disunity and UnityEngine.dll + TexFmt_Alpha8=1, //Unity 1.5 or earlier (already in 1.2.2 according to documentation) + TexFmt_ARGB4444, //Unity 3.0 (already in 1.2.2) + TexFmt_RGB24, //Unity 1.5 or earlier (already in 1.2.2) + TexFmt_RGBA32, //Unity 3.2 (not sure about 1.2.2) + TexFmt_ARGB32, //Unity 1.5 or earlier (already in 1.2.2) + TexFmt_UNUSED06, + TexFmt_RGB565, //Unity 3.0 (already in 1.2.2) + TexFmt_UNUSED08, + TexFmt_R16, //Unity 5.0 + TexFmt_DXT1, //Unity 2.0 (already in 1.2.2) + TexFmt_UNUSED11, //(DXT3 in 1.2.2?) + TexFmt_DXT5, //Unity 2.0 + TexFmt_RGBA4444, //Unity 4.1 + TexFmt_BGRA32New, //Unity 4.5 + TexFmt_RHalf, //Unity 5.0 + TexFmt_RGHalf, //Unity 5.0 + TexFmt_RGBAHalf, //Unity 5.0 + TexFmt_RFloat, //Unity 5.0 + TexFmt_RGFloat, //Unity 5.0 + TexFmt_RGBAFloat, //Unity 5.0 + TexFmt_YUV2, //Unity 5.0 + TexFmt_RGB9e5Float, //Unity 5.6 + TexFmt_UNUSED23, + TexFmt_BC6H, //Unity 5.5 + TexFmt_BC7, //Unity 5.5 + TexFmt_BC4, //Unity 5.5 + TexFmt_BC5, //Unity 5.5 + TexFmt_DXT1Crunched, //Unity 5.0 //SupportsTextureFormat version codes 0 (original) and 1 (Unity 2017.3) + TexFmt_DXT5Crunched, //Unity 5.0 //SupportsTextureFormat version codes 0 (original) and 1 (Unity 2017.3) + TexFmt_PVRTC_RGB2, //Unity 2.6 + TexFmt_PVRTC_RGBA2, //Unity 2.6 + TexFmt_PVRTC_RGB4, //Unity 2.6 + TexFmt_PVRTC_RGBA4, //Unity 2.6 + TexFmt_ETC_RGB4, //Unity 3.0 + TexFmt_ATC_RGB4, //Unity 3.4, removed in 2018.1 + TexFmt_ATC_RGBA8, //Unity 3.4, removed in 2018.1 + TexFmt_BGRA32Old, //Unity 3.4, removed in Unity 4.5 + TexFmt_UNUSED38, //TexFmt_ATF_RGB_DXT1, added in Unity 3.5, removed in Unity 5.0 + TexFmt_UNUSED39, //TexFmt_ATF_RGBA_JPG, added in Unity 3.5, removed in Unity 5.0 + TexFmt_UNUSED40, //TexFmt_ATF_RGB_JPG, added in Unity 3.5, removed in Unity 5.0 + TexFmt_EAC_R, //Unity 4.5 + TexFmt_EAC_R_SIGNED, //Unity 4.5 + TexFmt_EAC_RG, //Unity 4.5 + TexFmt_EAC_RG_SIGNED, //Unity 4.5 + TexFmt_ETC2_RGB4, //Unity 4.5 + TexFmt_ETC2_RGBA1, //Unity 4.5 //R4G4B4A1 + TexFmt_ETC2_RGBA8, //Unity 4.5 //R8G8B8A8 + TexFmt_ASTC_RGB_4x4=48, //Unity 4.5, removed (2019.1: replaced by ASTC_4x4) exists for version code < 2 + TexFmt_ASTC_RGB_5x5, //Unity 4.5, removed (2019.1: replaced by ASTC_5x5) exists for version code < 2 + TexFmt_ASTC_RGB_6x6, //Unity 4.5, removed (2019.1: replaced by ASTC_6x6) exists for version code < 2 + TexFmt_ASTC_RGB_8x8, //Unity 4.5, removed (2019.1: replaced by ASTC_8x8) exists for version code < 2 + TexFmt_ASTC_RGB_10x10, //Unity 4.5, removed (2019.1: replaced by ASTC_10x10) exists for version code < 2 + TexFmt_ASTC_RGB_12x12, //Unity 4.5, removed (2019.1: replaced by ASTC_12x12) exists for version code < 2 + TexFmt_ASTC_RGBA_4x4, //Unity 4.5, removed (obsoleted in 2019.1) + TexFmt_ASTC_RGBA_5x5, //Unity 4.5, removed (obsoleted in 2019.1) + TexFmt_ASTC_RGBA_6x6, //Unity 4.5, removed (obsoleted in 2019.1) + TexFmt_ASTC_RGBA_8x8, //Unity 4.5, removed (obsoleted in 2019.1) + TexFmt_ASTC_RGBA_10x10, //Unity 4.5, removed (obsoleted in 2019.1) + TexFmt_ASTC_RGBA_12x12, //Unity 4.5, removed (obsoleted in 2019.1) + TexFmt_ETC_RGB4_3DS, //Unity 5.0, removed (obsoleted in 2018.3, "Nintendo 3DS no longer supported") + TexFmt_ETC_RGBA8_3DS, //Unity 5.0, removed (obsoleted in 2018.3, "Nintendo 3DS no longer supported") + TexFmt_RG16, //Unity 2017.1 + TexFmt_R8, //Unity 2017.1 + TexFmt_ETC_RGB4Crunched, //Unity 2017.3 //SupportsTextureFormat version code 1 + TexFmt_ETC2_RGBA8Crunched, //Unity 2017.3 //SupportsTextureFormat version code 1 + TexFmt_ASTC_HDR_4x4, //Unity 2019.1 + TexFmt_ASTC_HDR_5x5, //Unity 2019.1 + TexFmt_ASTC_HDR_6x6, //Unity 2019.1 + TexFmt_ASTC_HDR_8x8, //Unity 2019.1 + TexFmt_ASTC_HDR_10x10, //Unity 2019.1 + TexFmt_ASTC_HDR_12x12, //Unity 2019.1 + TexFmt_RG32, //Unity 2020.2 + TexFmt_RGB48, //Unity 2020.2 + TexFmt_RGBA64, //Unity 2020.2 + + TexFmt_ASTC_4x4 = 48, //Unity 2019.1 exists for version code >= 2; Format equivalent to old ASTC_RGBA_* + TexFmt_ASTC_5x5, //Unity 2019.1 exists for version code >= 2; Format equivalent to old ASTC_RGBA_* + TexFmt_ASTC_6x6, //Unity 2019.1 exists for version code >= 2; Format equivalent to old ASTC_RGBA_* + TexFmt_ASTC_8x8, //Unity 2019.1 exists for version code >= 2; Format equivalent to old ASTC_RGBA_* + TexFmt_ASTC_10x10, //Unity 2019.1 exists for version code >= 2; Format equivalent to old ASTC_RGBA_* + TexFmt_ASTC_12x12, //Unity 2019.1 exists for version code >= 2; Format equivalent to old ASTC_RGBA_* +}; + +//Fills in a TextureFile structure using the values from the value field. +ASSETSTOOLS_API bool ReadTextureFile(TextureFile *pOutTex, AssetTypeValueField *pBaseField); +//Writes a TextureFile structure into a Texture2D AssetTypeValueField. +// Any memory allocated by WriteTextureFile is pushed back to allocatedMemory. +// Note: Directly uses buffer pointers from the texture in the values (e.g. pInTex->m_Name)! +// -> pInTex should not be touched until pBaseField is freed. +//Can throw a AssetTypeValue_ConfusionError. +ASSETSTOOLS_API bool WriteTextureFile(TextureFile* pInTex, AssetTypeValueField* pBaseField, + std::vector> &allocatedMemory); +//Determines whether an AssetsFile supports a specific texture format. +// version is set to 2 for 2019.1+, else to 1 for 2017.3+ and else to 0 to mark breaking format changes. +ASSETSTOOLS_API bool SupportsTextureFormat(AssetsFile *pAssetsFile, TextureFormat texFmt, int &version); +//Retrieves RGBA32 (byte order) texture data from a previously initialized TextureFile structure. pOutBuf is expected to have at least width*height*4 bytes of space. +ASSETSTOOLS_API bool GetTextureData(TextureFile *pTex, void *pOutBuf); +//Retrieves the size of compressed data of a specific texture format. +ASSETSTOOLS_API size_t GetCompressedTextureDataSize(int width, int height, TextureFormat texFmt); +//Converts the RGBA32 (byte order) input buffer for the texture format specified in the TextureFile structure. The output buffer is pPictureData (_pictureDataSize). +ASSETSTOOLS_API bool MakeTextureData(TextureFile *pTex, void *pRGBA32Buf, int compressQuality = 0); +//ASSETSTOOLS_API unsigned int GetCompressedTextureDataSizeCrunch(TextureFile *pTex); \ No newline at end of file diff --git a/AssetsTools/defines.h b/AssetsTools/defines.h new file mode 100644 index 0000000..d8c9b8a --- /dev/null +++ b/AssetsTools/defines.h @@ -0,0 +1,40 @@ +#pragma once +#include +#include + +typedef uint64_t QWORD; + +#ifdef ASSETSTOOLS_EXPORTS +#if (ASSETSTOOLS_EXPORTS == 1) +#define ASSETSTOOLS_API __declspec(dllexport) +#else +#define ASSETSTOOLS_API +#endif +#elif defined(ASSETSTOOLS_IMPORTSTATIC) +#define ASSETSTOOLS_API +#else +#define ASSETSTOOLS_API __declspec(dllimport) +#endif + +#ifndef __AssetsTools_AssetsFileFunctions_Read +#define __AssetsTools_AssetsFileFunctions_Read +typedef void(_cdecl *AssetsFileVerifyLogger)(const char *message); +#endif + +#ifndef __AssetsTools_AssetsReplacerFunctions_FreeCallback +#define __AssetsTools_AssetsReplacerFunctions_FreeCallback +typedef void(_cdecl *cbFreeMemoryResource)(void *pResource); +typedef void(_cdecl *cbFreeReaderResource)(class IAssetsReader *pReader); +#endif +#ifndef __AssetsTools_Hash128 +#define __AssetsTools_Hash128 +union Hash128 +{ + uint8_t bValue[16]; + uint16_t wValue[8]; + uint32_t dValue[4]; + QWORD qValue[2]; +}; +#endif + +#include "AssetsFileReader.h" \ No newline at end of file diff --git a/AssetsTools/dllmain.cpp b/AssetsTools/dllmain.cpp new file mode 100644 index 0000000..56cc249 --- /dev/null +++ b/AssetsTools/dllmain.cpp @@ -0,0 +1,24 @@ +// dllmain.cpp : Definiert den Einstiegspunkt für die DLL-Anwendung. +#include "stdafx.h" +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include + +BOOL APIENTRY DllMain( HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + break; + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + diff --git a/AssetsTools/stdafx.cpp b/AssetsTools/stdafx.cpp new file mode 100644 index 0000000..fd4f341 --- /dev/null +++ b/AssetsTools/stdafx.cpp @@ -0,0 +1 @@ +#include "stdafx.h" diff --git a/AssetsTools/stdafx.h b/AssetsTools/stdafx.h new file mode 100644 index 0000000..d64c581 --- /dev/null +++ b/AssetsTools/stdafx.h @@ -0,0 +1,18 @@ +#pragma once + +//#define WIN32_LEAN_AND_MEAN +//#ifndef _UNICODE +//#define _UNICODE +//#endif +//#define NOMINMAX +// +//#include + +#include +#include +#include +#include + +typedef uint64_t QWORD; +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..fa89d32 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required (VERSION 3.18) +project (AssetBundleExtractor) +set(UABE_ROOT ${CMAKE_CURRENT_SOURCE_DIR}) + +#set(THREADS_PREFER_PTHREAD_FLAG ON) +#find_package(Threads REQUIRED) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/CMakeModules") +#find_package(ispc_texcomp REQUIRED) +#find_package(texgenpack REQUIRED) +#find_package(mCtrl REQUIRED) +#find_package(jsmn REQUIRED) + +#Enable C++20 support +set(CMAKE_CXX_STANDARD 23) #23, since MSVC++ no longer appears to enable std::format with standard 20. +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_DEBUG") + +add_compile_options(/W3) +add_definitions(-DUNICODE) +add_definitions(-D_UNICODE) + +include(depend.cmake) + +add_subdirectory (Launcher) +add_subdirectory (libCompression) +add_subdirectory (libStringConverter) +add_subdirectory (CrnlibWrap) +add_subdirectory (AssetsTools) +add_subdirectory (ModInstaller) +add_subdirectory (UABE_Generic) +add_subdirectory (UABE_Win32) +add_subdirectory (Plugins) +add_subdirectory (TexToolWrap) diff --git a/CMakeModules/FindPVRTexTool.cmake b/CMakeModules/FindPVRTexTool.cmake new file mode 100644 index 0000000..7e966a3 --- /dev/null +++ b/CMakeModules/FindPVRTexTool.cmake @@ -0,0 +1,17 @@ +set(PVRTexTool_ROOT CACHE PATH "Root directory of PVRTexTool") + +if (CMAKE_SIZEOF_VOID_P EQUAL 8) + set(PVRTexTool_NAME_ARCH "x86_64") +elseif (CMAKE_SIZEOF_VOID_P EQUAL 4) + set(PVRTexTool_NAME_ARCH "x86_32") +endif (CMAKE_SIZEOF_VOID_P EQUAL 8) + +find_path(PVRTexTool_INCLUDE_DIR NAMES PVRTexLib.h PVRTexLib.hpp PATHS ${PVRTexTool_ROOT}/Library/Include) +find_library(PVRTexTool_LIBRARIES NAMES PVRTexLib.lib PATHS ${PVRTexTool_ROOT}/Library/Windows_${PVRTexTool_NAME_ARCH}) +find_path(PVRTexTool_MODULE_DIR NAMES PVRTexLib.dll PATHS ${PVRTexTool_ROOT}/Library/Windows_${PVRTexTool_NAME_ARCH}) +set(PVRTexTool_MODULES "${PVRTexTool_MODULE_DIR}/PVRTexLib.dll") + +if (PVRTexTool_INCLUDE_DIR AND PVRTexTool_LIBRARIES AND PVRTexTool_MODULES) + set(PVRTexTool_FOUND TRUE) + message(STATUS "Found PVRTexTool: ${PVRTexTool_ROOT}") +endif (PVRTexTool_INCLUDE_DIR AND PVRTexTool_LIBRARIES AND PVRTexTool_MODULES) diff --git a/CMakeModules/Findfmod.cmake b/CMakeModules/Findfmod.cmake new file mode 100644 index 0000000..599f9aa --- /dev/null +++ b/CMakeModules/Findfmod.cmake @@ -0,0 +1,24 @@ +set(FMOD_ROOT CACHE PATH "Root directory of FMOD") + +if (CMAKE_SIZEOF_VOID_P EQUAL 8) + set(FMOD_NAME_ARCH "64") +elseif (CMAKE_SIZEOF_VOID_P EQUAL 4) + set(FMOD_NAME_ARCH "") +endif (CMAKE_SIZEOF_VOID_P EQUAL 8) + +find_path(FMODSTUDIO_INCLUDE_DIR NAMES fmod_studio.h fmod_studio.hpp PATHS ${FMOD_ROOT}/api/studio/inc) +find_library(FMODSTUDIO_LIBRARIES NAMES fmodstudio${FMOD_NAME_ARCH}_vc.lib PATHS ${FMOD_ROOT}/api/studio/lib) +find_path(FMODSTUDIO_MODULE_DIR NAMES fmodstudio${FMOD_NAME_ARCH}.dll PATHS ${FMOD_ROOT}/api/studio/lib) + +find_path(FMOD_INCLUDE_DIR NAMES fmod.h fmod.hpp PATHS ${FMOD_ROOT}/api/lowlevel/inc) +find_library(FMOD_LIBRARIES NAMES fmod${FMOD_NAME_ARCH}_vc.lib PATHS ${FMOD_ROOT}/api/lowlevel/lib) +find_path(FMOD_MODULE_DIR NAMES fmod${FMOD_NAME_ARCH}.dll PATHS ${FMOD_ROOT}/api/lowlevel/lib) +set(FMOD_MODULES "${FMOD_MODULE_DIR}/fmod${FMOD_NAME_ARCH}.dll") +set(FMODSTUDIO_MODULES ${FMOD_MODULES} "${FMODSTUDIO_MODULE_DIR}/fmodstudio${FMOD_NAME_ARCH}.dll") + +if (FMODSTUDIO_INCLUDE_DIR AND FMODSTUDIO_LIBRARY AND FMODSTUDIO_MODULE_DIR AND + FMOD_INCLUDE_DIR AND FMOD_LIBRARY AND FMOD_MODULE_DIR) + set(FMOD_FOUND TRUE) + message(STATUS "Found FMOD: ${FMOD_ROOT}") +endif (FMODSTUDIO_INCLUDE_DIR AND FMODSTUDIO_LIBRARY AND FMODSTUDIO_MODULE_DIR AND + FMOD_INCLUDE_DIR AND FMOD_LIBRARY AND FMOD_MODULE_DIR) diff --git a/CMakeSettings.Example.json b/CMakeSettings.Example.json new file mode 100644 index 0000000..3e8b371 --- /dev/null +++ b/CMakeSettings.Example.json @@ -0,0 +1,73 @@ +{ + "configurations": [ + { + "name": "x64-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "inheritEnvironments": [ "msvc_x64_x64" ], + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "variables": [ + { + "name": "PVRTexTool_ROOT", + "value": "X:/Path/To/PowerVR/PVRTexTool", + "type": "PATH" + }, + { + "name": "FMOD_ROOT", + "value": "X:/Path/To/FMOD", + "type": "PATH" + } + ] + }, + { + "name": "x64-Release", + "generator": "Ninja", + "configurationType": "Release", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "msvc_x64_x64" ], + "variables": [ + { + "name": "PVRTexTool_ROOT", + "value": "X:/Path/To/PowerVR/PVRTexTool", + "type": "PATH" + }, + { + "name": "FMOD_ROOT", + "value": "X:/Path/To/FMOD", + "type": "PATH" + } + ] + }, + { + "name": "x86-Release", + "generator": "Ninja", + "configurationType": "Release", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "msvc_x86" ], + "variables": [ + { + "name": "PVRTexTool_ROOT", + "value": "X:/Path/To/PowerVR/PVRTexTool", + "type": "PATH" + }, + { + "name": "FMOD_ROOT", + "value": "X:/Path/To/FMOD", + "type": "PATH" + } + ] + } + ] +} \ No newline at end of file diff --git a/Changelog.md b/Changelog.md deleted file mode 100644 index e51cd7e..0000000 --- a/Changelog.md +++ /dev/null @@ -1,272 +0,0 @@ -Additions in 1.1 : -- Added batch processing, see Usage.txt for more information. -- Now the original bundle file gets closed properly when saving the modified one. -- Fixed the detection of a running file operation so the program can't longer crash when fastly opening another bundle file while a modified bundle file gets saved. - -Additions in 1.2 : -- Added Unity 5 support (until 5.0.0f4; 5.0.0p4 doesn't work but I'll get a new version out soon) -- Added an Info button to view all assets in a bundle file. - -1.2b fixes the TypeTree remover for some bundle files. - -Additions in 1.3 : -- Added file format 0x0F support (so it works with all current Unity 5 versions). -- Added class databases for all Unity asset file formats (one for Unity 4 and one for Unity 5). -- Added a View Data button to view the data of the selected asset in a tree view (if the asset file format is known). Depending on the asset, it might take some time to load. -- Added an option to view the asset file list of .assets files and their dependencies (open mainData to get more exact file names). It might take some time to load. - - -Additions in 1.3b : -- Made the AssetBundleExtractor work without having a mainData file in a specific directory (some debugging stuff which I forgot to remove). -- Added asset dump export and raw export functionality. - - -Additions in 1.4 : -- Added asset importing functionality (from raw data or from asset dumps). -- Added asset adding and removing functionality. To add a new asset to the file table, add it to the ResourceManager (in mainData) or AssetBundle (in any bundle file) asset. -- Fixed many types in the Unity 5 type database. -- Added a type database editor. - - -Additions in 1.5a : -- Fixed a bug that broke the type tree when writing assets files -- Added a Plugin API and a Texture plugin -- Fixed a bug when reading aligned arrays that are no strings (only affects Dump Export) -- Cleaned up some of the code (so the functions that read an asset from bundle or .assets files use the same code for both) -- Added the file id inside the asset list to PPtrs in the asset Tree View -- Changed the AssetsBundle saving function to use the actual .assets file writing functions to prevent tree view remover issues - - -Additions in 1.6 : -- Cleaned up the asset tree view code. -- Added a view asset button to PPtrs so you can directly view referred assets in the same tree view. -- Fixed some asset writing bugs (caused by writing to the wrong file position). -- Added a TextAsset plugin. - - -Additions in 1.6b : -- Fixed raw asset exporting (the output file was previously opened in text mode) -- Updated classdata_0E.dat for Unity 5.1.1p3 (for previous Unity 5 versions, use the file from an earlier release) - - -Additions in 1.6c : -- Added a fallback to the file dialogs for Windows XP (even though I don't recommend using XP). - - -Additions in 1.7 : -- Fixed the TextAsset plugin. -- Added an AudioClip plugin which can export Unity 5 sounds to uncompressed 16-bit WAV files using FMOD. See https://7daystodie.com/forums/showthread.php?32600-Overriding-vanilla-files-(assets-)&p=314195&viewfull=1#post314195 if you want to import sounds. - - -Additions in 1.8 : -- Added the -keepnames switch for batch extracting, which is useful for making a webplayer game a standalone game -- Fixed the crash when trying to save a .assets if the target file is not writable -- Fixed the color channels of some raw texture formats -- Added Texture2D support for Unity 5.1 and newer, including support for reading/writing crunched textures -- Fixed Texture2D editing if the encoding and the texture itself wasn't changed (before it always reencoded the texture which only took time) -- Fixed exported texture's direction (converting Unity's 'bottom to top' to 'top to bottom') -- Fixed the metadata size field when saving .assets (at least I think it is correct now) -- Added colums to the asset list and sorting by these columns -- Added an asset search by name and a goto asset dialog -- Added support for asset batch exporting for all plugins (not batch importing) -- Added a Mesh to .obj plugin (export) and a MovieTexture plugin (import/export) -- Added support for Unity 4 AudioClip assets -- Fixed the asset dump for MonoBehaviours (which still doesn't include the script-specific data) -- Added a Unity 5.2.0f3 type database (which HAS to be used for new Texture2D assets) -- Fixed the Unity 4 type database -- Added .unity3d unpacking functionality (but not packing; if you want to use such a webplayer game, you can export it to a standalone one; I can give you instructions on how to do that) -- Fixed the Info button for bundles (it now shows the asset list from the selected assets' point of view) -- Improved the TreeView performance (quadratic vs. linear creation time) - - -Additions in 1.8b : -- Greatly improved the asset list creation performance -- Fixed a minor memory leak in Texture2D (when converting the texture data fails, it now also frees the memory) - - -Addition in 1.8c : -- Fixed the UV channels for some meshes and fixed the V orientation. - -Additions in 1.8.1 : -- Added Unity 5.3 bundle support (LZ4 and uncompressed tested, LZMA not tested) -- Added a Unity 5.3.1p3 type database -- Added LZMA (default) or LZ4 compression for type databases -- Fixed bundle file operations after closing an info dialog -- Fixed negative signed integer values in the dump (int8, int16, int32) -- Fixed RGBA4444 Texture2D exporting - -Additions in 1.9 : -- Created a type database package file format in order to automatically choose the right type database and to reduce the file sizes -- Added support for many new texture formats (mainly mobile compressed ones and floating point) and fixed the 16bit formats -- Added a compression quality selection when editing a texture (not for DXT) -- Added a type database package editor -- Added support for streamed Texture2D (starting with Unity 5.3) -- Fixed bundle reading for Unity 5.3 bundles with LZMA -- Fixed modifying bundles -- Fixed batch exporting through plugins when there are multiple assets with the same name -- Increased the opened file limit to 2048 to improve support for some mobile games with many .assets and split files -- Added support for split .assets (only as a dependency of a non-split file) - -Additions in 2.0 : -- Added a mod installer and mod installer editor, so any changes can directly be saved to a installer or a package file -- Added the feature to reload a previous state from a package file -- Allowed directly doing changes to assets in bundles to remove the need of exporting/importing the .assets file -- Added support for opening multiple independent .assets files -- Recreated the bundle file writer (it's now in AssetsTools along with BundleReplacer); I tested it a lot but it might still have some new issues -- Fixed some issues when adding new assets -- Added format 8 support (Unity 3.1+/also 3.0?) -- Added type databases for Unity 3.4 - 5.4 -- Added a "modified" indicator in the asset list, and made Export options directly see the changes -- Added a plugin to export/import TerrainData to/from .raw, as supported by Unity -- Added a plugin for 7dtd's umaplayer UMAMesh to .obj -- Improved the Mesh plugin, with better support for U4 and U5 (hopefully it fixes most of the export issues) -- Improved the AudioClip plugin's FMOD sound bank export, removing the sample granularity and the inflexible sample rate. -- Added big endian readonly support; Writing would corrupt the file -- Allowed the AudioClip plugin to export .m4a files as used in WebGL games -- Improved asset search (direction, start from selection) -- Added support for streamed Unity 4 AudioClips -- Added more validity checks for .assets files, reducing freezes for invalid files -- Added "Would you like to save" warnings -- Fixed writing uncompressed type databases without huge null blocks -- Added batch export for raw or dump files -- Changed import raw to load the asset into memory if possible, not blocking the file anymore -- Fixed some charset issues, UTF16 is now used instead of UTF8 on ANSI functions in some instances -- Fixed some small memory leaks -- Improved the type database and package editors and fixed some string to int related bugs -- Fixed exporting v6 bundles with an uncompressed file table but with compressed blocks -- Fixed exporting v6 bundles with the file table at the end -- Fixed the MovieTexture plugin (not sure if it was already bugged in 1.9) -- Fixed creating dumps of some assets, such as TerrainData -- Added support for split .resource / .resS files -- Fixed some cases where AudioClip/Texture2D can't export from .resource/.resS - -Addition in 2.0b : -- Use the type package even for asset bundle file tables, seemingly the type information isn't always stored. - -Additions in 2.1 : -- Add Unity 5.5 (format 16/17) .assets file support. -- Add Unity 5.5.0f3 type database. -- Fix hidden assets added with File->Add when closing the asset list and pressing Info again (for bundles) or when loading an installer package. -- Add a dependency view dialog (View->Dependencies) that lists all listed .assets files with their absolute File IDs and their direct dependencies with relative File IDs. -- Improve support for split .assets files, .split0 files can now be opened directly. -- Add support for BC4,BC5,BC6H and BC7 texture formats (added in U5.5) with different compression settings and decompression. -- Add file name suggestions for asset export options. -- Fix writing some format 6 AssetBundles with UnityWeb header and flag 0x100 set. -- Add a SubstanceArchive plugin that can export .sbsasm files but not the .xml file. -- Calculate type hashes and fill out type tree when adding new asset types to format 16/17 files. -- Small TextAsset plugin fixes. -- Fix a possible crash when resolving a conflict caused by an installer package import in the installer maker. -- API: Remove two console outputs in MakeTextureData and GetTextureData. -- Fix exporting dumps of MonoBehaviours with no type tree stored in the .assets file. -- Fix some potential issues when trying to locate streamed resources. - -Addition in 2.1b : -- Fix a bug in writing the type information of Unity 5.5 .assets files. - -Addition in 2.1c : -- Fix another bug in reading and writing the type information of Unity 5.5 .assets files. - -Additions in 2.1d : -- Fix another bug in writing the type information of Unity 5.5 .assets files. -- Fix a bug causing the last character of strings in asset dumps before \n or \r to be cut off. -- Fixed the suggested file path name for the XP dialog fallback. -- Improved performance of batch dump export. -- Fix a bug causing ResourceManager names to be applied to File ID 0's assets if the .assets files that contain the targeted assets aren't loaded. - -Additions in 2.2 beta1 : -- Add batch import support for raw assets, dumps, Texture2D, TextAsset, TerrainData and MovieTexture. -- Add an option to retrieve MonoBehaviour type information from assemblies to create full dumps. -- Add UABE JSON dump export/import and Unity JsonUtility compatible export. -- Update plugins for 2017.1.* and 2017.2.*.- Add type databases for 2017.1.0f3 and 2017.2.0f3. -- Add a new dialog View->Containers. -- Add a new column "Container" to the asset list to show assets grouped together properly. Base container assets can be searched for. -- Add progress indicators for opening .assets files and batch export/import. -- Improve performance massively for very large .assets files (opening files, selecting assets, removing assets, adding assets, plugin options). -- Support RGB9e5Float HDR, RG16 and R8 texture formats (will still be converted from/to normal SDR RGBA32). -- Add big endian write support (experimental). -- Redo AssetsFileReader / AssetsFileWriter interfaces and add experimental support for more than 2045 files. -- Improve streamed data file lookup.- Add mip map support to the Texture2D plugin. -- Add compressed mesh support (experimental). -- Add bundle compression support (LZMA only, experimental). -- Add multithread DXT1/5 compression support. -- Use type information to add valid assets with zeroed fields instead of empty ones. -- Allow entering a type name instead of a type id for the Add Asset dialog. -- Fix a bug writing .assets files that caused a missing field which in some cases caused further trouble. -- Improve bundle decompression for LZ4 files by creating a streamed decompression interface. -- Make AssetTypeInstance more strict on invalid assets or outdated type information (prevent out of memory crashes when trying to view or export dumps of some assets). -- Rework the plugin interface. -- Fix a bug importing .txt dumps with long lines. -- Update the Texture2D edit dialog to and fix a couple of options to represent the asset format properly. -- Change the default asset list window size. -- Change the default name format for exports to allow UABE to find the files for batch import. -- Make the plugin option list double-clickable. -- Support longer engine version strings. -- Hide removed assets from unsaved bundles. -- Use comctrl32 6.0 for the progress indicators, which also changes the look of dialog controls. -- Fix a couple of small memory leaks. -- Fix a bug allowing UABE to close before saving bundles. - -Additions in 2.2 beta2 : -- Add 2017.3.0f3 support (new class database, updated Texture and Mesh plugins). -- Fix a memory issue for .txt dumps with many lines larger than 255 bytes. -- Rework the dependency resolver to support external .assets or streamed data file references from inside a bundle. -- Fix a crash when trying to compress some texture file formats with mip map support. -- Fix mip maps of crunched textures. -- Fix the container name to asset assignment with ResourceManager file tables that have names for multiple File IDs. -- Fix a crash when an invalid File ID is entered in the Add dialog. - -Additions in 2.2 beta3 : -- Add export support for rigged uncompressed meshes to Collada (.dae). -- Add a type database for 2018.1.1f1. -- Add support for DXT and ETC crunch formats (2017.3 and newer). -- Fix issues importing .txt and .json dumps, and enforce exact float and double representations. -- Fix exporting compressed meshes to .obj. -- Fix issues when reading split files. -- Only show texture formats supported by the file's Unity version. -- Mod Installers : Automatically remove AssetsTools.dll, and (hopefully) fix the racing condition that caused the .exe to stay open sometimes. -- Allow the MonoBehaviour type tree stealer to fail for some classes if others succeed. -- Leave bundled .resource files uncompressed (File->Compress) to fix sound issues (untested!) -- Add an export button for the View->Dependencies dialog. - -Additions in 2.2 beta4 : -- Add Unity 2017.4 and 2018.2 support. -- Add LZ4 compression support (experimental), and add a compression dialog for per-file settings. -- Mesh plugin : Correct the orientation of bind poses and skeleton node transformations. - -Additions in 2.2 stable : -- Add a new 'Add' option for MonoBehaviours. -- Bundle replacer type information and hashes inside installer packages. -- Add support for 2018.3; streamed mesh export is not tested. -- Add a new console command 'applyemip' to apply installer packages. -- Fix the file order in written bundles to stay the same. -- Fix the serialized flag for bundles so it is not set on .resS files. -- Write assets listed in the preload table before others. -- Fix reading Mesh assets. -- Fix the window focus after progress dialogs. -- Improve the errors logged to progress dialogs and make them stay open if an error occured. -- Improve the MonoBehaviour type retrieve tool for array and List fields. -- Look for newly added MonoScripts when retrieving MonoBehaviour script info. -- Fix writing bundles with an installer package loaded. -- Fix writing multiple .assets files with an installer package loaded. -- Fix a crash that may occur when adding a new asset in a bundle and reopening the 'Asset info' dialog. -- Fix a crash when using assets that were added, removed and added again. -- Fix locating script assemblies in 2018.2 and 3. Also added m_AssemblyName in MonoScript manually to 2018.2 and 3 type databases, since it was missing. -- Allow setting the field version in the type database editor. -- Fix removing version targets in the type database editor. -- Fix version names in classdata.tpk. -- Fix the file name extension for exported .obj meshes. -- Improve classdata.tpk compression. - -Additions in 2.2 stable b : -- Fix Mesh export (again). -- Fix mip map generation for RGBA32 textures. - -Additions in 2.2 stable c : -- Add class databases for 2018.4.5f1 and 2019.1.0f3 -- Support the slightly changed Unity 2019 .assets file format. -- The mod installer now uses a statically linked AssetsTools.dll to reduce file size and hopefully reduce the amount of false-positives. - -Additions in 2.2 stable d : -- Add a class database for 2019.2.0f1. -- Support the updated Unity 2019.2 .assets file format. -- Make the mod installer close properly after a file dialog was opened. diff --git a/CrnlibWrap/CMakeLists.txt b/CrnlibWrap/CMakeLists.txt new file mode 100644 index 0000000..71579d5 --- /dev/null +++ b/CrnlibWrap/CMakeLists.txt @@ -0,0 +1,11 @@ +add_library (CrnlibWrapUnity SHARED CrnlibWrap.cpp) +target_include_directories (CrnlibWrapUnity PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(CrnlibWrapUnity PRIVATE crnlib-unity) +target_compile_definitions(CrnlibWrapUnity PRIVATE HAS_ETC2 WRAPSUFFIX=Unity CRNVERSION=1) +set_target_properties(CrnlibWrapUnity PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + +add_library (CrnlibWrapLegacy SHARED CrnlibWrap.cpp) +target_include_directories (CrnlibWrapLegacy PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(CrnlibWrapLegacy PRIVATE crnlib-legacy) +target_compile_definitions(CrnlibWrapLegacy PRIVATE WRAPSUFFIX=Legacy CRNVERSION=0) +set_target_properties(CrnlibWrapLegacy PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") diff --git a/CrnlibWrap/CrnlibWrap.cpp b/CrnlibWrap/CrnlibWrap.cpp new file mode 100644 index 0000000..0001262 --- /dev/null +++ b/CrnlibWrap/CrnlibWrap.cpp @@ -0,0 +1,165 @@ +#include "../AssetsTools/TextureFileFormat.h" +#include +#include +#include +#include + +//Recursive ## macro evaluation (required for WRAPSUFFIX) needs extra layers +//https://stackoverflow.com/a/1597129 +#define _CONCAT(a,b) a ## b +#define _WN2(a, b) _CONCAT(a, b) +#define _WN(NAME) _WN2(NAME,WRAPSUFFIX) +#define API __declspec(dllexport) + +static crn_bool CrunchProcessCallback(crn_uint32 phase_index, crn_uint32 total_phases, crn_uint32 subphase_index, crn_uint32 total_subphases, void* pUser_data_ptr) +{ + //int percentage_complete = (int)(.5f + (phase_index + float(subphase_index) / total_subphases) * 100.0f) / total_phases; + //printf("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\bProcessing: %u%%", std::min(100, std::max(0, percentage_complete))); + return true; +} +API bool _WN(CrunchTextureData_RGBA32_)(TextureFile* pTex, const void* pRGBA32Buf, + void* pOutBuf, QWORD& outputSize, int compressQuality, unsigned int curWidth, unsigned int curHeight) +{ + crn_comp_params compPars; compPars.clear(); + compPars.m_width = curWidth; + compPars.m_height = curHeight; + compPars.m_levels = 1;//(mipMapOffsets.size() > cCRNMaxLevels) ? cCRNMaxLevels : (crn_uint32)mipMapOffsets.size(); + compPars.m_file_type = cCRNFileTypeCRN; + switch (pTex->m_TextureFormat) + { + case TexFmt_DXT1Crunched: + compPars.m_format = cCRNFmtDXT1; + break; + case TexFmt_DXT5Crunched: + compPars.m_format = cCRNFmtDXT5; + break; + case TexFmt_ETC_RGB4Crunched: + compPars.m_format = cCRNFmtETC1; + pTex->extra.textureFormatVersion = 1; + break; +#ifdef HAS_ETC2 + case TexFmt_ETC2_RGBA8Crunched: + compPars.m_format = cCRNFmtETC2A; + pTex->extra.textureFormatVersion = 1; + break; +#endif + default: + return false; + } + compPars.m_pImages[0][0] = (crn_uint32*)pRGBA32Buf; + //compPars.m_quality_level = 255; + compPars.m_num_helper_threads = 0;//(compressQuality >= 1) ? (compressQuality - 1) : 1; + compPars.m_pProgress_func = CrunchProcessCallback; + + uint32_t maxNumThreads = std::thread::hardware_concurrency(); + if (maxNumThreads == 0) maxNumThreads = 2; + //16==cCRNMaxHelperThreads+1 for Unity, cCRNMaxHelperThreads for Legacy + // (the legacy version constant was too high by one, + // since 'helper threads' mean all threads but the main compression thread). + //Assuming we can have 16 threads total. + if (maxNumThreads > 16) maxNumThreads = 16; + bool mt = false; + switch (compressQuality) + { + case 1: compPars.m_num_helper_threads = maxNumThreads - 1; //normal mt + case 0: //normal + default: + compPars.m_quality_level = 128; + break; + case 3: compPars.m_num_helper_threads = maxNumThreads - 1; //very fast mt + case 2: //very fast + compPars.m_quality_level = 0; //cCRNMinQualityLevel + break; + case 5: compPars.m_num_helper_threads = maxNumThreads - 1; //slow mt + case 4: //slow + compPars.m_quality_level = 255; //cCRNMaxQualityLevel + break; + } + compPars.m_userdata0 = CRNVERSION; + + crn_mipmap_params mipPars; mipPars.clear(); + mipPars.m_gamma_filtering = false; + mipPars.m_max_levels = (pTex->m_MipCount > cCRNMaxLevels) ? cCRNMaxLevels : pTex->m_MipCount; + if (mipPars.m_max_levels < 1) mipPars.m_max_levels = 1; + mipPars.m_mode = (pTex->m_MipCount > 1) ? cCRNMipModeGenerateMips : cCRNMipModeNoMips; + + crn_uint32 crunchedQuality; + float crunchedBitrate; + crn_uint32 crunchedSize; + + void* crnCompressed = crn_compress(compPars, mipPars, crunchedSize, &crunchedQuality, &crunchedBitrate); + if (crnCompressed) + { + if (outputSize < crunchedSize) + { + crn_free_block(crnCompressed); + outputSize = 0; + return false; + } + + outputSize = crunchedSize; + memcpy(pOutBuf, crnCompressed, crunchedSize); + + crnd::crn_texture_info info; + if (crnd::crnd_get_texture_info(crnCompressed, crunchedSize, &info)) + { + pTex->m_MipCount = info.m_levels; + } + + crn_free_block(crnCompressed); + return true; + } + else + { + outputSize = 0; + return false; + } +} +API bool _WN(DecrunchTextureData_)(TextureFile* pTex, std::vector& decrunchBuf, TextureFormat &decrunchFormat) +{ + pTex->m_MipCount = 1; + crnd::crn_texture_info tex_info; + if (!crnd::crnd_get_texture_info(pTex->pPictureData, pTex->_pictureDataSize, &tex_info)) + return false; + switch (pTex->m_TextureFormat) + { + case TexFmt_DXT1Crunched: + if (tex_info.m_format != cCRNFmtDXT1) + return false; + decrunchFormat = TexFmt_DXT1; + break; + case TexFmt_DXT5Crunched: + if (tex_info.m_format != cCRNFmtDXT5) + return false; + decrunchFormat = TexFmt_DXT5; + break; + case TexFmt_ETC_RGB4Crunched: + if (tex_info.m_format != cCRNFmtETC1) + return false; + decrunchFormat = TexFmt_ETC_RGB4; + break; +#ifdef HAS_ETC2 + case TexFmt_ETC2_RGBA8Crunched: + if (tex_info.m_format != cCRNFmtETC2A) + return false; + decrunchFormat = TexFmt_ETC2_RGBA8; + break; +#endif + default: + return false; + } + crn_uint32 blockCountX = (tex_info.m_width + 3) >> 2; if (!blockCountX) blockCountX = 1; + crn_uint32 blockCountY = (tex_info.m_height + 3) >> 2; if (!blockCountY) blockCountY = 1; + crn_uint32 row_pitch = blockCountX * crnd::crnd_get_bytes_per_dxt_block(tex_info.m_format); + size_t dataSize = (size_t)row_pitch * blockCountY; + decrunchBuf.resize(dataSize); + + crnd::crnd_unpack_context ctx = crnd::crnd_unpack_begin(pTex->pPictureData, pTex->_pictureDataSize); + void* bufptr = decrunchBuf.data(); + if (!crnd::crnd_unpack_level(ctx, &bufptr, (unsigned int)decrunchBuf.size(), row_pitch, 0)) + { + decrunchBuf.clear(); + return false; + } + return true; +} diff --git a/CrnlibWrap/CrnlibWrap.h b/CrnlibWrap/CrnlibWrap.h new file mode 100644 index 0000000..e5aeee0 --- /dev/null +++ b/CrnlibWrap/CrnlibWrap.h @@ -0,0 +1,14 @@ +#pragma once +#include "../AssetsTools/TextureFileFormat.h" +#include +#ifndef CRNLIBWRAP_API +#define CRNLIBWRAP_API __declspec(dllimport) +#endif + +CRNLIBWRAP_API bool CrunchTextureData_RGBA32_Unity(TextureFile* pTex, const void* pRGBA32Buf, + void* pOutBuf, QWORD& outputSize, int compressQuality, unsigned int curWidth, unsigned int curHeight); +CRNLIBWRAP_API bool CrunchTextureData_RGBA32_Legacy(TextureFile* pTex, const void* pRGBA32Buf, + void* pOutBuf, QWORD& outputSize, int compressQuality, unsigned int curWidth, unsigned int curHeight); + +CRNLIBWRAP_API bool DecrunchTextureData_Unity(TextureFile* pTex, std::vector& decrunchBuf, TextureFormat& decrunchFormat); +CRNLIBWRAP_API bool DecrunchTextureData_Legacy(TextureFile* pTex, std::vector& decrunchBuf, TextureFormat& decrunchFormat); \ No newline at end of file diff --git a/CrnlibWrap/CrnlibWrapLegacy.def b/CrnlibWrap/CrnlibWrapLegacy.def new file mode 100644 index 0000000..18a2024 --- /dev/null +++ b/CrnlibWrap/CrnlibWrapLegacy.def @@ -0,0 +1,3 @@ +LIBRARY CrnlibWrapLegacy +EXPORTS + GetUABEPluginDesc1 diff --git a/CrnlibWrap/CrnlibWrapUnity.def b/CrnlibWrap/CrnlibWrapUnity.def new file mode 100644 index 0000000..03ddd96 --- /dev/null +++ b/CrnlibWrap/CrnlibWrapUnity.def @@ -0,0 +1,3 @@ +LIBRARY CrnlibWrapUnity +EXPORTS + GetUABEPluginDesc1 diff --git a/Launcher/AssetBundleExtractor.cpp b/Launcher/AssetBundleExtractor.cpp new file mode 100644 index 0000000..4473c63 --- /dev/null +++ b/Launcher/AssetBundleExtractor.cpp @@ -0,0 +1,84 @@ +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#include +#include "../libStringConverter/convert.h" + +#include +#include +#include + +static std::string getModuleBaseDir(HINSTANCE hInstance) +{ + std::string baseDir; + std::vector baseDirT; + size_t ownPathLen = MAX_PATH; + while (true) + { + baseDirT.resize(ownPathLen + 1, 0); + SetLastError(0); + DWORD result = GetModuleFileName(hInstance, baseDirT.data(), (DWORD)ownPathLen); + if (result == 0) + { + baseDirT.clear(); + break; + } + else if (result == ownPathLen && GetLastError() == ERROR_INSUFFICIENT_BUFFER) + ownPathLen += MAX_PATH; + else + break; + } + size_t ownPathStrlen = _tcslen(baseDirT.data()); + for (size_t i = ownPathStrlen-1; i > 0; i--) + { + if (baseDirT[i] == TEXT('\\')) + { + baseDirT.resize(i + 1); + baseDirT[i] = 0; + ownPathStrlen = i; + break; + } + } + + size_t outLen = 0; + char *baseDirA = _TCHARToMultiByte(baseDirT.data(), outLen); + baseDir.assign(baseDirA); + _FreeCHAR(baseDirA); + return baseDir; +} + +#include "../UABE_Win32/Win32AppContext.h" +int APIENTRY _tWinMain(HINSTANCE hInstance, + HINSTANCE hPrevInstance, + LPTSTR lpCmdLine, + int nCmdShow) +{ + std::string baseDir = getModuleBaseDir(hInstance); + std::vector argvBuf8; + size_t totalArgvLen = 0; + char **argv8 = new char*[__argc+1]; + for (int i = 0; i < __argc; i++) + { + size_t len16 = wcslen(__wargv[i]); + if (len16 > INT_MAX) len16 = INT_MAX; + size_t len8 = (size_t)WideCharToMultiByte(CP_UTF8, 0, __wargv[i], (int)len16, NULL, 0, NULL, NULL); + size_t argvBufOffset = argvBuf8.size(); + argvBuf8.resize(argvBuf8.size() + len8 + 1); + WideCharToMultiByte(CP_UTF8, 0, __wargv[i], (int)len16, &argvBuf8[argvBufOffset], (int)len8, NULL, NULL); + argvBuf8[argvBufOffset + len8] = 0; + argv8[i] = (char*)argvBufOffset; + } + for (int i = 0; i < __argc; i++) + { + argv8[i] = argvBuf8.data() + (size_t)argv8[i]; + } + argv8[__argc] = nullptr; + int ret; + if (HMODULE hUABEWin32 = GetModuleHandle(TEXT("UABE_Win32.dll"))) + { + Win32AppContext appContext(hUABEWin32, baseDir); + ret = appContext.Run(__argc, argv8); + } + delete[] argv8; + return ret; +} diff --git a/Launcher/AssetBundleExtractor.ico b/Launcher/AssetBundleExtractor.ico new file mode 100644 index 0000000000000000000000000000000000000000..d551aa3aaf80adf9b7760e2eb8de95a5c3e53df6 GIT binary patch literal 23558 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 + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + diff --git a/Launcher/CMakeLists.txt b/Launcher/CMakeLists.txt new file mode 100644 index 0000000..4480423 --- /dev/null +++ b/Launcher/CMakeLists.txt @@ -0,0 +1,23 @@ +set(WIN32_EXECUTABLE TRUE) + +add_executable (AssetBundleExtractor WIN32 "AssetBundleExtractor.cpp" AssetBundleExtractor.manifest Launcher.rc "resource.h") + +target_link_libraries (AssetBundleExtractor LINK_PUBLIC UABE_Win32) + +set_target_properties(AssetBundleExtractor PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + +set(AssetBundleExtractor_MODULE_DEPENDENCY_PATHS ${MCTRL_MODULES} ${ISPC_TEXCOMP_MODULES} ${TEXGENPACK_MODULES}) + +file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/bin") +foreach(depfile ${AssetBundleExtractor_MODULE_DEPENDENCY_PATHS}) + configure_file("${depfile}" "${CMAKE_BINARY_DIR}/bin" COPYONLY) +endforeach() +configure_file("${UABE_ROOT}/classdata.tpk" "${CMAKE_BINARY_DIR}/bin" COPYONLY) + +configure_file("${UABE_ROOT}/Readme.License.txt" "${CMAKE_BINARY_DIR}/bin" COPYONLY) +file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/bin/Licenses") +add_custom_command(TARGET AssetBundleExtractor PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory "${UABE_ROOT}/Licenses" "${CMAKE_BINARY_DIR}/bin/Licenses") +file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/bin/Tools") +add_custom_command(TARGET AssetBundleExtractor PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory "${UABE_ROOT}/Tools" "${CMAKE_BINARY_DIR}/bin/Tools") diff --git a/Launcher/Launcher.rc b/Launcher/Launcher.rc new file mode 100644 index 0000000..b2b2e3e --- /dev/null +++ b/Launcher/Launcher.rc @@ -0,0 +1,50 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// Neutral resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_NEU) +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_ASSETBUNDLEEXTRACTOR ICON "AssetBundleExtractor.ico" +#endif // Neutral resources +///////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////// +// English resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_NEUTRAL +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN +IDS_APP_TITLE "Unity Asset Bundle Extractor" +IDC_ASSETBUNDLEEXTRACTOR "ASSETBUNDLEEXTRACTOR" +END + +#endif // English resources +///////////////////////////////////////////////////////////////////////////// diff --git a/Launcher/resource.h b/Launcher/resource.h new file mode 100644 index 0000000..99e0c47 --- /dev/null +++ b/Launcher/resource.h @@ -0,0 +1,8 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by AssetBundleExtractor.rc +// + +#define IDS_APP_TITLE 103 +#define IDI_ASSETBUNDLEEXTRACTOR 107 +#define IDC_ASSETBUNDLEEXTRACTOR 109 diff --git a/Legacy.md b/Legacy.md deleted file mode 100644 index c02b7ef..0000000 --- a/Legacy.md +++ /dev/null @@ -1,198 +0,0 @@ -You can find new versions in the GitHub releases and here: https://7daystodie.com/forums/showthread.php?22675-Unity-Assets-Bundle-Extractor - -## Application releases - -1.0 -- 32/64: https://mega.co.nz/#!aNhz2ZAJ!PmhuWkO2Od7ek2yDLervxOrBM1BFmcxb039TLYItFfk -- 32/64: https://www.dropbox.com/s/x356s4pp1o9iied/AssetsBundleExtractor_1.0.zip?dl=0 - -1.1 -- 32/64: https://mega.co.nz/#!aABW1J7T!2YKvs2kQyiXXoGAxZol0V-bfbiGuPzyr1Iq_wXPUW3U -- 32/64: https://www.dropbox.com/s/3fx52kkkeohzj4a/AssetsBundleExtractor_1.1.zip?dl=0 - -1.2 -- 32/64: https://www.dropbox.com/s/89hoqgaynvslv2v/AssetsBundleExtractor_1.2.zip?dl=0 - -1.2b -- 32/64: https://mega.co.nz/#!GBR2xb4a!93I4k0HBkyK3CaAZ24r9FWCOHB-BpHmiEd8iatouUmI -- 32/64: https://www.dropbox.com/s/l1xg61oq840wxly/AssetsBundleExtractor_1.2b.zip?dl=0 - -1.3 (broken) -- 32: https://mega.co.nz/#!jc4VRZyb!jX34RZqiBixuAIVfjnHpQ3ENIcEviaePk2tOrvyeZfs -- 32: https://www.dropbox.com/s/g65f180hej7ybft/AssetsBundleExtractor_1.3_32bit.zip?dl=0 -- 64: https://mega.co.nz/#!jMQB2JZb!5hBxXjaJBxkNwl0CEpoRMCUnggDnTQY5fMHh1faqb8w -- 64: https://www.dropbox.com/s/6d91g3wxe2wlbay/AssetsBundleExtractor_1.3_64bit.zip?dl=0 - -1.3b -- 32: https://mega.co.nz/#!2ZBCmTzA!gP_rbSEsS0GHso_EjpsLnraoxwK6kxy3zBlJeCzq_zM -- 32: https://www.dropbox.com/s/x8mrht86v8r58hv/AssetsBundleExtractor_1.3b_32bit.zip?dl=0 -- 64: https://mega.co.nz/#!3dh3FRoI!M25WNXfF_oNrYLs3tYxvalbKaGJPJ423IyySHYz5DJU -- 64: https://www.dropbox.com/s/ml9pjht9dssfh8t/AssetsBundleExtractor_1.3b_64bit.zip?dl=0 - -1.4 -- 32: https://mega.co.nz/#!eNRW0C5K!1zA-_76P-r27TC8CtZKWWWr7fRWWp06MFaCPS8_fVcI -- 32: https://www.dropbox.com/s/hqeguzcmuhn8ppa/AssetsBundleExtractor_1.4_32bit.zip?dl=0 -- 64: https://mega.co.nz/#!SAxxxLAJ!pFiyg4P75JRQAVpKH4-tkuBGF5711TQlbjZrJs-Xu5U -- 64: https://www.dropbox.com/s/0azbp58f1f55j63/AssetsBundleExtractor_1.4_64bit.zip?dl=0 - -1.5a -- 32: https://mega.co.nz/#!zZxiAAZK!b9MP1Ev_7G_ZnNErSedMfrFLyqz1BuFabzNJK_Dnzek -- 32: https://www.dropbox.com/s/n23z3srizcsdhn9/AssetsBundleExtractor_1.5a_32bit.zip?dl=0 -- 64: https://mega.co.nz/#!fUpBSKTK!r1YnB-rFkAc854WPHp9ZnxCP50qVyylykWj3W6Ahk_I -- 64: https://www.dropbox.com/s/xj9zu428khrfp2z/AssetsBundleExtractor_1.5a_64bit.zip?dl=0 - -1.6 -- 32: https://mega.co.nz/#!PIoS0JgA!d8CdM23TyOhBGjjm1gtRyD5i3z3bDeZS8q2PponyBnU -- 32: https://www.dropbox.com/s/ak5lvhg9yl14qrt/AssetsBundleExtractor_1.6_32bit.zip?dl=0 -- 64: https://mega.co.nz/#!Scpx3B7R!YAN7hVRuMv7jFPk6lDokFVmhNUbui6eDKBKmayiQw6E -- 64: https://www.dropbox.com/s/wk3mym4r46q9ayv/AssetsBundleExtractor_1.6_64bit.zip?dl=0 - -1.6b -- 32: https://mega.co.nz/#!2MYTwKxY!wycljselPFOSzEj40FdeCgARnyTKt_G8YInRhPv4Yfk -- 32: https://www.dropbox.com/s/pvvy85ems84yey7/AssetsBundleExtractor_1.6b_32bit.zip?dl=0 -- 64: https://mega.co.nz/#!jNpW1Qqa!z8IcudsVXfi7JvhAmPbynHkqAFnGOIv_tt6K2mGoOQI -- 64: https://www.dropbox.com/s/0b9a103lwh271nf/AssetsBundleExtractor_1.6b_64bit.zip?dl=0 - -1.6c -- 32: https://mega.nz/#!HMBDkQ4D!-qXbyPmVLIHSPjsdu43EOxKf1Mrr7ghp0YZdDBKUTKg -- 32: https://www.dropbox.com/s/29nq71hmf7n3wg5/AssetsBundleExtractor_1.6c_32bit.zip?dl=0 -- 64: https://mega.nz/#!TdojESbB!4SK5z_lBlVWUHdwZ7QERJd7kV4cFMjCLXQBZexDCmeQ -- 64: https://www.dropbox.com/s/ze7jsdop0mnzygg/AssetsBundleExtractor_1.6c_64bit.zip?dl=0 - -1.7 -- 32: https://mega.nz/#!jBIxjKBR!a-zWwkDxJNIiqF_cBiny1ebJ8Y23swrmJhI_h_S81kg -- 32: https://www.dropbox.com/s/jpdvjd3tehpn2nm/AssetsBundleExtractor_1.7_32bit.zip?dl=0 -- 64: https://mega.nz/#!vQoBkApZ!Ar_vlrN3TfWhz4rMxvE4Ws3pC_5iJDgJeWkCUGp_ODU -- 64: https://www.dropbox.com/s/749p3n7k5ym9af9/AssetsBundleExtractor_1.7_64bit.zip?dl=0 - -1.8 -- 32: https://mega.nz/#!uBRTCJKY!MTFUgksl_AbFuFimMPkT9m9buAfF1ifMLgqJynDI-Og -- 32: https://www.dropbox.com/s/vnanevizp3qk52d/AssetsBundleExtractor_1.8_32bit.zip?dl=0 -- 64: https://mega.nz/#!3BJkhKzK!gM7wxPjNrWIZ1DHLRCCfPIeO-DOZbONNwSx_10MWPbU -- 64: https://www.dropbox.com/s/ofs4j41zsfvi0ix/AssetsBundleExtractor_1.8_64bit.zip?dl=0 - -1.8b -- 32: https://mega.nz/#!uZhQ2T5D!Tb_990VhdLRn8OhFgR6SZUqExgxP3Hdp9AIPDfeMAa8 -- 32: https://www.dropbox.com/s/ojk6sun9c8prh1c/AssetsBundleExtractor_1.8b_32bit.zip?dl=0 -- 64: https://mega.nz/#!TZBBkAgD!mG8hPDxOFGpQZCKZsqODvl5uDdDyfo2h1aXfDZnBPMM -- 64: https://www.dropbox.com/s/3g03amuxm1ciqyq/AssetsBundleExtractor_1.8b_64bit.zip?dl=0 - -1.8c -- 32: https://mega.nz/#!3NY0WJpa!xZEieRf-dDYuOedOdOw5UobQP8RquDQJL6Y9ZHsggzM -- 32: https://www.dropbox.com/s/gulkc8jyrld3eun/AssetsBundleExtractor_1.8c_32bit.zip?dl=0 -- 64: https://mega.nz/#!qBhXgRgb!eR99KF3WXd1SvT5BlIwBSIrYNJ6viNMf2jTRTQy8ggk -- 64: https://www.dropbox.com/s/kese2fhu4q8hsx5/AssetsBundleExtractor_1.8c_64bit.zip?dl=0 - -1.8.1 -- 32: https://mega.nz/#!mRwWmZiS!6R1-iXbtGnkgVgF3Dzr_OBsZ77dovS972ex7SiLZMOE -- 32: https://www.dropbox.com/s/8ol6ape3x30iz7t/AssetsBundleExtractor_1.8.1_32bit.zip?dl=0 -- 64: https://mega.nz/#!qFJD0YAB!rn-jNYYMJbMIhThC7xW5h735-8J6TcI8sn2IsOSgQ7Y -- 64: https://www.dropbox.com/s/fohectguzmvyxna/AssetsBundleExtractor_1.8.1_64bit.zip?dl=0 - -5.1.1p3 type database (for 7dtd A12.5) -- dl: https://mega.nz/#!vNgyXaZD!LqYiN18KENXa-bmaWtwD0p1b51B25WblCJiMMMc-fEc -- dl: https://www.dropbox.com/s/4n06erqsytfipu5/classdata_5.1.1p3.zip?dl=0 - -Note that you have to replace classdata_0E with classData_0E_5.0.1f1 if you use the Texture plugin for any Unity version before 5.2 (only applies to older UABE versions without classdata.tpk) - -1.9 -- 32: https://mega.nz/#!iBpWwLyC!w88lKqRBA0n9AMa8BSokIRSRXQh51bPjTBDQCU8cQAI -- 32: https://www.dropbox.com/s/x1jvso1f97vfsyl/AssetsBundleExtractor_1.9_32bit.zip?dl=0 -- 64: https://mega.nz/#!XRRRURJR!dessOauxznx_nnOtDCSOC0UfXDO1EataDG6WmpkroCc -- 64: https://www.dropbox.com/s/o00uf6u8sovocib/AssetsBundleExtractor_1.9_64bit.zip?dl=0 - -2.0 -- 32: https://mega.nz/#!rB5Rnb5K!b2KpAInDbri16hrQYnOMljPoChrebOVvfyjxOb9MKaI -- 32: https://www.dropbox.com/s/2vjenfn0c2ihbm7/AssetsBundleExtractor_2.0_32bit.zip?dl=0 -- 64: https://mega.nz/#!rBARWQzR!uqvS21k45XHAeWOFxtI-trg4wWT335M3FV3FTLG8THI -- 64: https://www.dropbox.com/s/39z7zyctsdaekyk/AssetsBundleExtractor_2.0_64bit.zip?dl=0 - -7dtd UMAMesh plugin for 2.0 -- 32/64: https://mega.nz/#!eVYDUYLS!POqaBKQKWevZRmJBrWYcglqXDho5UDIkIrdfvI748ps -- 32/64: https://www.dropbox.com/s/53pa70rcdlzrb6j/UMAMesh%20Plugin.zip?dl=0 - -2.0b bugfix, download 2.0 first -- 32/64 : https://mega.nz/#!fBgQCKKI!47kkOVbe7vRnM8yGt5I4tj4se-zLCpVDeKkPMa43bds -- 32/64 : https://www.dropbox.com/s/emdbuthaiv5kkvl/AssetsBundleExtractor_2.0b.zip?dl=0 - -2.1 -- 32 : [URL=https://mega.nz/#!TRZFHIyK!vG82BIaG0FRLYfzOnJhIEYMCyL9GDENKAUrTDieYAvU]here[/URL] -- 32 : [URL=https://www.dropbox.com/s/ulb9uqlk0pd77bm/AssetsBundleExtractor_2.1_32bit.zip?dl=0]here[/URL] -- 64 : [URL=https://mega.nz/#!mVQFDTpY!nydtrVtVI3pij6B9JIouukZVrrAW4OBjesLYQGzA4vQ]here[/URL] -- 64 :[URL=https://www.dropbox.com/s/9iu59lpj76m0m4m/AssetsBundleExtractor_2.1_64bit.zip?dl=0]here[/URL]) - -2.1b bugfix, download 2.1 first -- 32/64 : https://mega.nz/#!XNRgnKIa!8nqkrNOAzFl-PpgWRP7XzUGPuDiC-5ihyjEMV2x9ZtM -- 32/64 : https://www.dropbox.com/s/ebwvutc9t82l1g8/AssetsBundleExtractor_2.1b.zip?dl=0 - -2.1c bugfix, download 2.1 first -- 32/64 : https://mega.nz/#!idI21a4J!LdrOt6h_24tTrepV4Gj2vbx_IBFqdRRzQ1qa0UCiXoc -- 32/64 : https://www.dropbox.com/s/azpmvh9aqs1hy4l/AssetsBundleExtractor_2.1c.zip?dl=0 - -2.1d bugfix, download 2.1 first -- 32/64 : https://mega.nz/#!7A4XCJqS!qkP6NiyHC6v1Q54-cksLqRg6FDnm0OIfdfY0gcFs9EU -- 32/64 : https://www.dropbox.com/s/haousqg0a3n6x1t/AssetsBundleExtractor_2.1d.zip?dl=0 - -5.6.0f3 type package (for 2.1d): -- https://mega.nz/#!uAZmFQoI!pGmIUQyvP5i1bpPOd9eTYDRGafXL0FCrIAPVSMZjPCg -- https://www.dropbox.com/s/7lb77s8z6ckfq01/classdata%205.6.0f3.zip?dl=0 - -2.2 beta1 -- 32 : https://mega.nz/#!GFoQSLIb!8zT_GXn4cyOn-kfvOi5ZQu5LarQwlHvMPviBEeRGdN4 -- 32 : https://www.dropbox.com/s/0kk2qnkz3d7wo2n/AssetsBundleExtractor_2.2beta1_32bit.zip?dl=0 -- 64 : https://mega.nz/#!7F4ikYhQ!Gp7Cav270Yi4tp451ZcnboS4KJ5eE6qIqjpKXsC2TXM -- 64 : https://www.dropbox.com/s/ic3fpb8x9idd3cp/AssetsBundleExtractor_2.2beta1_64bit.zip?dl=0 - -2.2 beta2 -- 32 : https://mega.nz/#!bBxWxS5K!bcxdQK9eWeOmkrdOpE7Wc8TECmS-c_Obl4Tnkwqor4g -- 32 : https://www.dropbox.com/s/qug30txp80tabai/AssetsBundleExtractor_2.2beta2_32bit.zip?dl=0 -- 64 : https://mega.nz/#!vc40jaDD!YGToxSR1_50CxSImzSYtlW4EKB7gXPvkZt5VERLydT8 -- 64 : https://www.dropbox.com/s/48lendp3r8woo9c/AssetsBundleExtractor_2.2beta2_64bit.zip?dl=0 - -2.2 beta3 -- 32 : https://mega.nz/#!uIJliSJR!puDDDVhjm2aRkmpmD8N0e4digNCZWX5aU0xvBgV3uKE -- 32 : https://github.com/DerPopo/UABE/releases/download/2.2beta3/AssetsBundleExtractor_2.2beta3_32bit.zip -- 64 : https://mega.nz/#!eRY3gAAI!wEB5cTEAxtEEbe7jIKroatUxwYtwmcUnCjAzoMBEyCs -- 64 : https://github.com/DerPopo/UABE/releases/download/2.2beta3/AssetsBundleExtractor_2.2beta3_64bit.zip - -2.2 beta4 -- 32 : https://mega.nz/#!qYonmY6S!TJ_W-GIWjkc_qzYhiP1MgqI1flbDsRGi5nkQ97jvXKc -- 32 : https://github.com/DerPopo/UABE/releases/download/2.2beta4/AssetsBundleExtractor_2.2beta4_32bit.zip -- 64 : https://mega.nz/#!uR4ViAob!c5Xgt8DY08xDKb1ggyubKhY6utLOQZ3TmMe6aHnn6Bk -- 64 : https://github.com/DerPopo/UABE/releases/download/2.2beta4/AssetsBundleExtractor_2.2beta4_64bit.zip - -2.2 stable -- 32 : https://mega.nz/#!bBoDWAKR!LnpINrRmzxj07h-8l-iJoc-nU5VVFJBhnHj-q26GlPQ -- 32 : https://github.com/DerPopo/UABE/releases/download/2.2stable/AssetsBundleExtractor_2.2stable_32bit.zip -- 64 : https://mega.nz/#!fEpRGQIC!grmdKuiFKX1DqXtrvlnDCjScUpGgp526d4Aur6Kw-b4 -- 64 : https://github.com/DerPopo/UABE/releases/download/2.2stable/AssetsBundleExtractor_2.2stable_64bit.zip - -## API releases - -1.9 API -- https://mega.nz/#!yEIFzYRa!zW482wlJqaMCC3bt2sw5ua_By4ywrA5WpJTnlfWzK_k -- https://www.dropbox.com/s/btlfd5cbquffna0/AssetsToolsAPI_1.9.zip?dl=0 - -2.0 API -- https://mega.nz/#!LcxFUDgY!LbR4wZ6mYuv8k0PWMnFcTaFh4AVNr0Z6ylJZJka-_ww -- https://www.dropbox.com/s/mhq0ergi91bw1xu/AssetsToolsAPI_2.0.zip?dl=0 - -2.1b API -- https://mega.nz/#!nRw1nZIL!ndMf5hd1_A8qVztRGvsFxTwA8H2zSPZ3TMepjaS5zkE -- https://www.dropbox.com/s/wdhte4ayj9vw93c/UABE_API_2.1.zip?dl=0 - -2.1c API -- https://mega.nz/#!XMYkgIJK!yEGXF46ntLO3-9O3rt2AMIF3dJp0A5Yz1xWCcrT6rpk -- https://www.dropbox.com/s/vdiirsmo3ce9wqr/UABE_API_2.1c.zip?dl=0 - -Sample Plugin -- https://mega.nz/#!aVZkVDAC!G7_KC0KrP0d-ZLiroZTuUKQMEHNEtEds3c65i9cq924 -- https://www.dropbox.com/s/q38ond8ke3p3ozi/UABE%20Sample%20Plugin.zip?dl=0 - -2.1d API -- https://mega.nz/#!KV4kiSBT!XKkpc4h9in4iM-eTrzNx1_whzcltU_M_sk5gYkDowlQ -- https://www.dropbox.com/s/qmwym6bx2py8j4p/UABE_API_2.1d.zip?dl=0 - -2.2 beta 3 API -- https://mega.nz/#!fdJkRaAa!Ry_AL3SmrcVVb1375MTuU7v4D9HfhVN91laseWHs8xA -- https://github.com/DerPopo/UABE/releases/download/2.2beta3/AssetsToolsAPI_2.2beta3.zip diff --git a/Licenses/LodePNG_license.txt b/Licenses/LodePNG_license.txt new file mode 100644 index 0000000..d0467fd --- /dev/null +++ b/Licenses/LodePNG_license.txt @@ -0,0 +1,22 @@ +LodePNG version 20150418 + +Copyright (c) 2005-2015 Lode Vandevenne + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. \ No newline at end of file diff --git a/Licenses/assimp_license.txt b/Licenses/assimp_license.txt new file mode 100644 index 0000000..ec6ff8e --- /dev/null +++ b/Licenses/assimp_license.txt @@ -0,0 +1,63 @@ +Open Asset Import Library (assimp) + +Copyright (c) 2006-2016, assimp team +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors +http://code.google.com/p/poly2tri/ + +All rights reserved. +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +* Neither the name of Poly2Tri nor the names of its contributors may be + used to endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Licenses/astcenc_license.txt b/Licenses/astcenc_license.txt new file mode 100644 index 0000000..b82735a --- /dev/null +++ b/Licenses/astcenc_license.txt @@ -0,0 +1,175 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/Licenses/cecil_license.txt b/Licenses/cecil_license.txt new file mode 100644 index 0000000..afd0ae6 --- /dev/null +++ b/Licenses/cecil_license.txt @@ -0,0 +1,21 @@ +Copyright (c) 2008 - 2015 Jb Evain +Copyright (c) 2008 - 2011 Novell, Inc. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Licenses/crunch-unity-license.txt b/Licenses/crunch-unity-license.txt new file mode 100644 index 0000000..1b30d78 --- /dev/null +++ b/Licenses/crunch-unity-license.txt @@ -0,0 +1,22 @@ +crunch/crnlib uses the ZLIB license: +http://opensource.org/licenses/Zlib + +Copyright (c) 2010-2016 Richard Geldreich, Jr. and Binomial LLC + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not +claim that you wrote the original software. If you use this software +in a product, an acknowledgment in the product documentation would be +appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not be +misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. diff --git a/Licenses/half_license.txt b/Licenses/half_license.txt new file mode 100644 index 0000000..a09a348 --- /dev/null +++ b/Licenses/half_license.txt @@ -0,0 +1,15 @@ +half - IEEE 754-based half-precision floating point library. + +Copyright (c) 2012-2013 Christian Rau + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, +modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/Licenses/ispc_texcomp_license.txt b/Licenses/ispc_texcomp_license.txt new file mode 100644 index 0000000..6c2d710 --- /dev/null +++ b/Licenses/ispc_texcomp_license.txt @@ -0,0 +1,19 @@ +Copyright 2017 Intel Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Licenses/jsmn_license.txt b/Licenses/jsmn_license.txt new file mode 100644 index 0000000..e38f663 --- /dev/null +++ b/Licenses/jsmn_license.txt @@ -0,0 +1,19 @@ +Copyright (c) 2010 Serge A. Zaitsev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/Licenses/libfgen_lgpl.txt b/Licenses/libfgen_lgpl.txt new file mode 100644 index 0000000..65c5ca8 --- /dev/null +++ b/Licenses/libfgen_lgpl.txt @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/Licenses/libfgen_license.txt b/Licenses/libfgen_license.txt new file mode 100644 index 0000000..ed0034a --- /dev/null +++ b/Licenses/libfgen_license.txt @@ -0,0 +1,15 @@ +fgen -- Library for optimization using a genetic algorithm or particle swarm optimization. +Copyright 2012, Harm Hanemaaijer + +fgen is free software: you can redistribute it and/or modify it +under the terms of the GNU Lesser General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +fgen is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with fgen. If not, see . \ No newline at end of file diff --git a/Licenses/libsquish_license.txt b/Licenses/libsquish_license.txt new file mode 100644 index 0000000..ed1c78d --- /dev/null +++ b/Licenses/libsquish_license.txt @@ -0,0 +1,20 @@ + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Licenses/license.txt b/Licenses/license.txt new file mode 100644 index 0000000..e23ece2 --- /dev/null +++ b/Licenses/license.txt @@ -0,0 +1,277 @@ +Eclipse Public License - v 2.0 + + THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE + PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION + OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + + a) in the case of the initial Contributor, the initial content + Distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + where such changes and/or additions to the Program originate from + and are Distributed by that particular Contributor. A Contribution + "originates" from a Contributor if it was added to the Program by + such Contributor itself or anyone acting on such Contributor's behalf. + Contributions do not include changes or additions to the Program that + are not Modified Works. + +"Contributor" means any person or entity that Distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which +are necessarily infringed by the use or sale of its Contribution alone +or when combined with the Program. + +"Program" means the Contributions Distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement +or any Secondary License (as applicable), including Contributors. + +"Derivative Works" shall mean any work, whether in Source Code or other +form, that is based on (or derived from) the Program and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. + +"Modified Works" shall mean any work in Source Code or other form that +results from an addition to, deletion from, or modification of the +contents of the Program, including, for purposes of clarity any new file +in Source Code form that contains any contents of the Program. Modified +Works shall not include works that contain only declarations, +interfaces, types, classes, structures, or files of the Program solely +in each case in order to link to, bind by name, or subclass the Program +or Modified Works thereof. + +"Distribute" means the acts of a) distributing or b) making available +in any manner that enables the transfer of a copy. + +"Source Code" means the form of a Program preferred for making +modifications, including but not limited to software source code, +documentation source, and configuration files. + +"Secondary License" means either the GNU General Public License, +Version 2.0, or any later versions of that license, including any +exceptions or additional permissions as identified by the initial +Contributor. + +2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare Derivative Works of, publicly display, + publicly perform, Distribute and sublicense the Contribution of such + Contributor, if any, and such Derivative Works. + + b) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, + if any, in Source Code or other form. This patent license shall + apply to the combination of the Contribution and the Program if, at + the time the Contribution is added by the Contributor, such addition + of the Contribution causes such combination to be covered by the + Licensed Patents. The patent license shall not apply to any other + combinations which include the Contribution. No hardware per se is + licensed hereunder. + + c) Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. + Each Contributor disclaims any liability to Recipient for claims + brought by any other entity based on infringement of intellectual + property rights or otherwise. As a condition to exercising the + rights and licenses granted hereunder, each Recipient hereby + assumes sole responsibility to secure any other intellectual + property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to Distribute the + Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + + d) Each Contributor represents that to its knowledge it has + sufficient copyright rights in its Contribution, if any, to grant + the copyright license set forth in this Agreement. + + e) Notwithstanding the terms of any Secondary License, no + Contributor makes additional grants to any Recipient (other than + those set forth in this Agreement) as a result of such Recipient's + receipt of the Program under the terms of a Secondary License + (if permitted under the terms of Section 3). + +3. REQUIREMENTS + +3.1 If a Contributor Distributes the Program in any form, then: + + a) the Program must also be made available as Source Code, in + accordance with section 3.2, and the Contributor must accompany + the Program with a statement that the Source Code for the Program + is available under this Agreement, and informs Recipients how to + obtain it in a reasonable manner on or through a medium customarily + used for software exchange; and + + b) the Contributor may Distribute the Program under a license + different than this Agreement, provided that such license: + i) effectively disclaims on behalf of all other Contributors all + warranties and conditions, express and implied, including + warranties or conditions of title and non-infringement, and + implied warranties or conditions of merchantability and fitness + for a particular purpose; + + ii) effectively excludes on behalf of all other Contributors all + liability for damages, including direct, indirect, special, + incidental and consequential damages, such as lost profits; + + iii) does not attempt to limit or alter the recipients' rights + in the Source Code under section 3.2; and + + iv) requires any subsequent distribution of the Program by any + party to be under a license that satisfies the requirements + of this section 3. + +3.2 When the Program is Distributed as Source Code: + + a) it must be made available under this Agreement, or if the + Program (i) is combined with other material in a separate file or + files made available under a Secondary License, and (ii) the initial + Contributor attached to the Source Code the notice described in + Exhibit A of this Agreement, then the Program may be made available + under the terms of such Secondary Licenses, and + + b) a copy of this Agreement must be included with each copy of + the Program. + +3.3 Contributors may not remove or alter any copyright, patent, +trademark, attribution notices, disclaimers of warranty, or limitations +of liability ("notices") contained within the Program from any copy of +the Program which they Distribute, provided that Contributors may add +their own appropriate notices. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities +with respect to end users, business partners and the like. While this +license is intended to facilitate the commercial use of the Program, +the Contributor who includes the Program in a commercial product +offering should do so in a manner which does not create potential +liability for other Contributors. Therefore, if a Contributor includes +the Program in a commercial product offering, such Contributor +("Commercial Contributor") hereby agrees to defend and indemnify every +other Contributor ("Indemnified Contributor") against any losses, +damages and costs (collectively "Losses") arising from claims, lawsuits +and other legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such +Commercial Contributor in connection with its distribution of the Program +in a commercial product offering. The obligations in this section do not +apply to any claims or Losses relating to any actual or alleged +intellectual property infringement. In order to qualify, an Indemnified +Contributor must: a) promptly notify the Commercial Contributor in +writing of such claim, and b) allow the Commercial Contributor to control, +and cooperate with the Commercial Contributor in, the defense and any +related settlement negotiations. The Indemnified Contributor may +participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial +product offering, Product X. That Contributor is then a Commercial +Contributor. If that Commercial Contributor then makes performance +claims, or offers warranties related to Product X, those performance +claims and warranties are such Commercial Contributor's responsibility +alone. Under this section, the Commercial Contributor would have to +defend claims against the other Contributors related to those performance +claims and warranties, and if a court requires any other Contributor to +pay any damages as a result, the Commercial Contributor must pay +those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" +BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF +TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR +PURPOSE. Each Recipient is solely responsible for determining the +appropriateness of using and distributing the Program and assumes all +risks associated with its exercise of rights under this Agreement, +including but not limited to the risks and costs of program errors, +compliance with applicable laws, damage to or loss of data, programs +or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS +SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE +EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of +the remainder of the terms of this Agreement, and without further +action by the parties hereto, such provision shall be reformed to the +minimum extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity +(including a cross-claim or counterclaim in a lawsuit) alleging that the +Program itself (excluding combinations of the Program with other software +or hardware) infringes such Recipient's patent(s), then such Recipient's +rights granted under Section 2(b) shall terminate as of the date such +litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it +fails to comply with any of the material terms or conditions of this +Agreement and does not cure such failure in a reasonable period of +time after becoming aware of such noncompliance. If all Recipient's +rights under this Agreement terminate, Recipient agrees to cease use +and distribution of the Program as soon as reasonably practicable. +However, Recipient's obligations under this Agreement and any licenses +granted by Recipient relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, +but in order to avoid inconsistency the Agreement is copyrighted and +may only be modified in the following manner. The Agreement Steward +reserves the right to publish new versions (including revisions) of +this Agreement from time to time. No one other than the Agreement +Steward has the right to modify this Agreement. The Eclipse Foundation +is the initial Agreement Steward. The Eclipse Foundation may assign the +responsibility to serve as the Agreement Steward to a suitable separate +entity. Each new version of the Agreement will be given a distinguishing +version number. The Program (including Contributions) may always be +Distributed subject to the version of the Agreement under which it was +received. In addition, after a new version of the Agreement is published, +Contributor may elect to Distribute the Program (including its +Contributions) under the new version. + +Except as expressly stated in Sections 2(a) and 2(b) above, Recipient +receives no rights or licenses to the intellectual property of any +Contributor under this Agreement, whether expressly, by implication, +estoppel or otherwise. All rights in the Program not expressly granted +under this Agreement are reserved. Nothing in this Agreement is intended +to be enforceable by any entity that is not a Contributor or Recipient. +No third-party beneficiary rights are created under this Agreement. + +Exhibit A - Form of Secondary Licenses Notice + +"This Source Code may also be made available under the following +Secondary Licenses when the conditions for such availability set forth +in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), +version(s), and exceptions or additional permissions here}." + + Simply including a copy of this Agreement, including this Exhibit A + is not sufficient to license the Source Code under Secondary Licenses. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a LICENSE + file in a relevant directory) where a recipient would be likely to + look for such a notice. + + You may add additional accurate notices of copyright ownership. \ No newline at end of file diff --git a/Licenses/lz4_license.txt b/Licenses/lz4_license.txt new file mode 100644 index 0000000..b566df3 --- /dev/null +++ b/Licenses/lz4_license.txt @@ -0,0 +1,24 @@ +LZ4 Library +Copyright (c) 2011-2014, Yann Collet +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/Licenses/mctrl_license.txt b/Licenses/mctrl_license.txt new file mode 100644 index 0000000..c32c2f9 --- /dev/null +++ b/Licenses/mctrl_license.txt @@ -0,0 +1,503 @@ +# GNU LESSER GENERAL PUBLIC LICENSE + +Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + [This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + +## Preamble + +The licenses for most software are designed to take away your freedom +to share and change it. By contrast, the GNU General Public Licenses +are intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. + +This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations +below. + +When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + +To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + +For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + +We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + +To protect each distributor, we want to make it very clear that there +is no warranty for the free library. Also, if the library is modified +by someone else and passed on, the recipients should know that what +they have is not the original version, so that the original author's +reputation will not be affected by problems that might be introduced +by others. + +Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + +Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + +When a program is linked with a library, whether statically or using a +shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + +We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + +For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it +becomes a de-facto standard. To achieve this, non-free programs must +be allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + +In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + +Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + +The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + +## TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +**0.** This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). Each +licensee is addressed as "you". + +A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control +compilation and installation of the library. + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does and +what the program that uses the Library does. + +**1.** You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a +fee. + +**2.** You may modify your copy or copies of the Library or any +portion of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + +- **a)** The modified work must itself be a software library. +- **b)** You must cause the files modified to carry prominent + notices stating that you changed the files and the date of + any change. +- **c)** You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. +- **d)** If a facility in the modified Library refers to a function + or a table of data to be supplied by an application program that + uses the facility, other than as an argument passed when the + facility is invoked, then you must make a good faith effort to + ensure that, in the event an application does not supply such + function or table, the facility still operates, and performs + whatever part of its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of + the application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + +**3.** You may opt to apply the terms of the ordinary GNU General +Public License instead of this License to a given copy of the Library. +To do this, you must alter all the notices that refer to this License, +so that they refer to the ordinary GNU General Public License, version +2, instead of to this License. (If a newer version than version 2 of +the ordinary GNU General Public License has appeared, then you can +specify that version instead if you wish.) Do not make any other +change in these notices. + +Once this change is made in a given copy, it is irreversible for that +copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the +Library into a program that is not a library. + +**4.** You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + +If distribution of object code is made by offering access to copy from +a designated place, then offering equivalent access to copy the source +code from the same place satisfies the requirement to distribute the +source code, even though third parties are not compelled to copy the +source along with the object code. + +**5.** A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a work, +in isolation, is not a derivative work of the Library, and therefore +falls outside the scope of this License. + +However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. Section +6 states terms for distribution of such executables. + +When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + +If such an object file uses only numerical parameters, data structure +layouts and accessors, and small macros and small inline functions +(ten lines or less in length), then the use of the object file is +unrestricted, regardless of whether it is legally a derivative work. +(Executables containing this object code plus portions of the Library +will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + +**6.** As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a work +containing portions of the Library, and distribute that work under +terms of your choice, provided that the terms permit modification of +the work for the customer's own use and reverse engineering for +debugging such modifications. + +You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + +- **a)** Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood that + the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) +- **b)** Use a suitable shared library mechanism for linking with + the Library. A suitable mechanism is one that (1) uses at run time + a copy of the library already present on the user's computer + system, rather than copying library functions into the executable, + and (2) will operate properly with a modified version of the + library, if the user installs one, as long as the modified version + is interface-compatible with the version that the work was + made with. +- **c)** Accompany the work with a written offer, valid for at least + three years, to give the same user the materials specified in + Subsection 6a, above, for a charge no more than the cost of + performing this distribution. +- **d)** If distribution of the work is made by offering access to + copy from a designated place, offer equivalent access to copy the + above specified materials from the same place. +- **e)** Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + +It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + +**7.** You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + +- **a)** Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other + library facilities. This must be distributed under the terms of + the Sections above. +- **b)** Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + +**8.** You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + +**9.** You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + +**10.** Each time you redistribute the Library (or any work based on +the Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + +**11.** If, as a consequence of a court judgment or allegation of +patent infringement or for any other reason (not limited to patent +issues), conditions are imposed on you (whether by court order, +agreement or otherwise) that contradict the conditions of this +License, they do not excuse you from the conditions of this License. +If you cannot distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, +then as a consequence you may not distribute the Library at all. For +example, if a patent license would not permit royalty-free +redistribution of the Library by all those who receive copies directly +or indirectly through you, then the only way you could satisfy both it +and this License would be to refrain entirely from distribution of the +Library. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply, and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + +**12.** If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + +**13.** The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. Such +new versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + +**14.** If you wish to incorporate parts of the Library into other +free programs whose distribution conditions are incompatible with +these, write to the author to ask for permission. For software which +is copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + +**NO WARRANTY** + +**15.** BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +**16.** IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + +## END OF TERMS AND CONDITIONS + +## How to Apply These Terms to Your New Libraries + +If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms +of the ordinary General Public License). + +To apply these terms, attach the following notices to the library. It +is safest to attach them to the start of each source file to most +effectively convey the exclusion of warranty; and each file should +have at least the "copyright" line and a pointer to where the full +notice is found. + + one line to give the library's name and an idea of what it does. + Copyright (C) year name of author + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper +mail. + +You should also get your employer (if you work as a programmer) or +your school, if any, to sign a "copyright disclaimer" for the library, +if necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in + the library `Frob' (a library for tweaking knobs) written + by James Random Hacker. + + signature of Ty Coon, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/Licenses/pthreads_license.txt b/Licenses/pthreads_license.txt new file mode 100644 index 0000000..b1e3f5a --- /dev/null +++ b/Licenses/pthreads_license.txt @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/Licenses/texgenpack_license.txt b/Licenses/texgenpack_license.txt new file mode 100644 index 0000000..08d2fed --- /dev/null +++ b/Licenses/texgenpack_license.txt @@ -0,0 +1,13 @@ +Copyright (c) 2015 Harm Hanemaaijer + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. \ No newline at end of file diff --git a/Licenses/vgmstream_license.txt b/Licenses/vgmstream_license.txt new file mode 100644 index 0000000..4c1775d --- /dev/null +++ b/Licenses/vgmstream_license.txt @@ -0,0 +1,22 @@ +Copyright (c) 2008-2019 Adam Gashlin, Fastelbja, Ronny Elfert, bnnm, + Christopher Snowhill, NicknineTheEagle, bxaimc, + Thealexbarney, CyberBotX, et al + +Portions Copyright (c) 2004-2008, Marko Kreen +Portions Copyright 2001-2007 jagarl / Kazunori Ueno +Portions Copyright (c) 1998, Justin Frankel/Nullsoft Inc. +Portions Copyright (C) 2006 Nullsoft, Inc. +Portions Copyright (c) 2005-2007 Paul Hsieh +Portions Public Domain originating with Sun Microsystems + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. \ No newline at end of file diff --git a/ModInstaller/CMakeLists.txt b/ModInstaller/CMakeLists.txt new file mode 100644 index 0000000..79d5bd8 --- /dev/null +++ b/ModInstaller/CMakeLists.txt @@ -0,0 +1,7 @@ +add_library (ModInstaller SHARED dllmain.cpp InstallDialog.cpp InstallerDataFormat.cpp MakeIconResource.cpp MakeInstaller.cpp ModInstaller.cpp stdafx.cpp ../UABE_Win32/FileDialog.cpp Dialogs.rc ModInstaller.manifest) +target_include_directories (ModInstaller PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +target_compile_definitions(ModInstaller PRIVATE MODINSTALLER_EXPORTS) +set_target_properties(ModInstaller PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") +target_link_libraries(ModInstaller PRIVATE AssetsTools_Static libCompression libStringConverter Comctl32.lib Shlwapi.lib) + diff --git a/ModInstaller/Dialogs.rc b/ModInstaller/Dialogs.rc new file mode 100644 index 0000000000000000000000000000000000000000..9859904cafa38dac576b643568cf614cca3b8f61 GIT binary patch literal 13426 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 literal 0 HcmV?d00001 diff --git a/ModInstaller/InstallDialog.cpp b/ModInstaller/InstallDialog.cpp new file mode 100644 index 0000000..05c42e9 --- /dev/null +++ b/ModInstaller/InstallDialog.cpp @@ -0,0 +1,1215 @@ +#include "stdafx.h" +#include "InstallDialog.h" +#include "resource.h" +#include "../libStringConverter/convert.h" +#include "../AssetsTools/InternalBundleReplacer.h" +#include "../UABE_Win32/FileDialog.h" +#include + +LRESULT CALLBACK InstallWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); +INT_PTR CALLBACK DescriptionDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); +INT_PTR CALLBACK PrepareDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); + +ATOM RegisterInstallWindowClass(HINSTANCE hInstance) +{ + WNDCLASSEX wcex; + + wcex.cbSize = sizeof(WNDCLASSEX); + + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = InstallWndProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = hInstance; + wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MODINSTALLER)); + wcex.hCursor = LoadCursor(NULL, IDC_ARROW); + wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+0); + wcex.lpszMenuName = NULL; + wcex.lpszClassName = L"UABE_ModInstaller"; + wcex.hIconSm = NULL; + + return RegisterClassEx(&wcex); +} +LRESULT CALLBACK InstallWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + int wmId, wmEvent; + + switch (message) + { + case WM_COMMAND: + wmId = LOWORD(wParam); + wmEvent = HIWORD(wParam); + return DefWindowProc(hWnd, message, wParam, lParam); + case WM_SIZE: + { + uint16_t width = LOWORD(lParam); uint16_t height = HIWORD(lParam); + InstallDialogsData *pData = (InstallDialogsData*)GetWindowLongPtr(hWnd, GWLP_USERDATA); + for (int i = 0; i < InstallDialog_COUNT; i++) + { + MoveWindow(pData->hDialogs[i], 0, 0, width, height, TRUE); + } + } + return (LRESULT)1; + case WM_DESTROY: + { + InstallDialogsData *pData = (InstallDialogsData*)GetWindowLongPtr(hWnd, GWLP_USERDATA); + pData->closeWindows = true; + //PostQuitMessage(0); + } + break; + default: + return DefWindowProc(hWnd, message, wParam, lParam); + } + return 0; +} +void SetTitleFont(HWND hStatic) +{ + HFONT origFont = (HFONT)SendMessage(hStatic, WM_GETFONT, 0, 0); + LOGFONT logfont = {}; + GetObject(origFont, sizeof(LOGFONT), &logfont); + logfont.lfWeight = FW_BOLD; + logfont.lfHeight = -16; + HFONT newFont = CreateFontIndirect(&logfont); + SendMessage(hStatic, WM_SETFONT, (WPARAM)newFont, (LPARAM)FALSE); +} +INT_PTR CALLBACK PrepareDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + UNREFERENCED_PARAMETER(lParam); + switch (message) + { + case WM_INITDIALOG: + return (INT_PTR)TRUE; + } + return (INT_PTR)FALSE; +} +#include "Licences.h" +INT_PTR CALLBACK LicenceDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + UNREFERENCED_PARAMETER(lParam); + switch (message) + { + case WM_INITDIALOG: + { + HWND hLicencesEdit = GetDlgItem(hDlg, IDC_LICENCES); + Edit_SetText(hLicencesEdit, LicencesText); + } + return (INT_PTR)TRUE; + case WM_COMMAND: + if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) + { + EndDialog(hDlg, LOWORD(wParam)); + return (INT_PTR)TRUE; + } + break; + } + return (INT_PTR)FALSE; +} +INT_PTR CALLBACK IntroductionDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + UNREFERENCED_PARAMETER(lParam); + switch (message) + { + case WM_INITDIALOG: + { + HWND hSetupTitle = GetDlgItem(hDlg, IDC_SETUPTITLE); + SetTitleFont(hSetupTitle); + } + return (INT_PTR)TRUE; + case WM_DESTROY: + { + HWND hSetupTitle = GetDlgItem(hDlg, IDC_SETUPTITLE); + HFONT modifiedFont = (HFONT)SendMessage(hSetupTitle, WM_GETFONT, 0, 0); + DeleteObject((HGDIOBJ)modifiedFont); + } + return (INT_PTR)TRUE; + case WM_SIZE: + { + int width = LOWORD(lParam); int height = HIWORD(lParam); + HWND hSetupTitle = GetDlgItem(hDlg, IDC_SETUPTITLE); + HWND hIntroduction = GetDlgItem(hDlg, IDC_SINTRODUCTION); + HWND hLicencelink = GetDlgItem(hDlg, IDC_LICENCELINK); + HWND hBack = GetDlgItem(hDlg, IDC_BACK); + HWND hNext = GetDlgItem(hDlg, IDC_NEXT); + HWND hCancel = GetDlgItem(hDlg, IDC_CANCEL); + SetWindowPos(hSetupTitle, NULL, 0, 0, width - 2, 18, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(hIntroduction, NULL, 0, 0, width - 8, height - 56, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(hLicencelink, NULL, 5, height - 26, width / 5 - 10, 23, SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(hBack, NULL, width / 5, height - 26, (width - 10) / 4, 23, SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(hNext, NULL, 9 * width / 20 + 4, height - 26, (width - 10) / 4, 23, SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(hCancel, NULL, width - width / 4 - 4, height - 26, (width - 10) / 4, 23, SWP_NOZORDER | SWP_NOACTIVATE); + } + return (INT_PTR)TRUE; + case WM_COMMAND: + if (LOWORD(wParam) == IDC_NEXT) + { + DialogController_Introduction *pController = (DialogController_Introduction*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + ShowInstallDialog(pController->GetDialogsData(), (EInstallDialogs)(pController->GetOwnDialogType() + 1)); + } + else if (LOWORD(wParam) == IDC_CANCEL) + { + DialogController_Introduction *pController = (DialogController_Introduction*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + OnCancelPressed(pController->GetDialogsData()); + } + else + break; + return (INT_PTR)TRUE; + case WM_NOTIFY: + if (((NMHDR*)lParam)->idFrom == IDC_LICENCELINK) + { + switch (((NMHDR*)lParam)->code) + { + case NM_CLICK: + case NM_RETURN: + DialogBox((HINSTANCE)GetWindowLongPtr(hDlg, GWLP_HINSTANCE), MAKEINTRESOURCE(IDD_LICENCES), hDlg, LicenceDlgProc); + break; + } + } + } + return (INT_PTR)FALSE; +} +INT_PTR CALLBACK DescriptionDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + UNREFERENCED_PARAMETER(lParam); + switch (message) + { + case WM_INITDIALOG: + { + HWND hSetupTitle = GetDlgItem(hDlg, IDC_SETUPTITLE); + SetTitleFont(hSetupTitle); + } + return (INT_PTR)TRUE; + case WM_DESTROY: + { + HWND hSetupTitle = GetDlgItem(hDlg, IDC_SETUPTITLE); + HFONT modifiedFont = (HFONT)SendMessage(hSetupTitle, WM_GETFONT, 0, 0); + DeleteObject((HGDIOBJ)modifiedFont); + } + return (INT_PTR)TRUE; + case WM_SIZE: + { + int width = LOWORD(lParam); int height = HIWORD(lParam); + HWND hSetupTitle = GetDlgItem(hDlg, IDC_SETUPTITLE); + HWND hCancel = GetDlgItem(hDlg, IDC_CANCEL); + HWND hAuthors = GetDlgItem(hDlg, IDC_AUTHORS); + HWND hDescription = GetDlgItem(hDlg, IDC_DESCRIPTION); + HWND hBack = GetDlgItem(hDlg, IDC_BACK); + HWND hNext = GetDlgItem(hDlg, IDC_NEXT); + SetWindowPos(hSetupTitle, NULL, 0, 0, width - 2, 18, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(hAuthors, NULL, 0, 0, width - 4, 32, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(hDescription, NULL, 0, 0, width - 8, height - 88, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(hCancel, NULL, width - width / 4 - 4, height - 26, (width - 10) / 4, 23, SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(hBack, NULL, width / 5, height - 26, (width - 10) / 4, 23, SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(hNext, NULL, 9 * width / 20 + 4, height - 26, (width - 10) / 4, 23, SWP_NOZORDER | SWP_NOACTIVATE); + } + return (INT_PTR)TRUE; + case WM_COMMAND: + { + DialogController_Description *pController = (DialogController_Description*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + switch (LOWORD(wParam)) + { + case IDC_BACK: + ShowInstallDialog(pController->GetDialogsData(), (EInstallDialogs)(pController->GetOwnDialogType() - 1)); + break; + case IDC_NEXT: + ShowInstallDialog(pController->GetDialogsData(), (EInstallDialogs)(pController->GetOwnDialogType() + 1)); + break; + case IDC_CANCEL: + OnCancelPressed(pController->GetDialogsData()); + break; + default: + return (INT_PTR)FALSE; + } + return (INT_PTR)TRUE; + } + } + return (INT_PTR)FALSE; +} +INT_PTR CALLBACK PathSelectDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + UNREFERENCED_PARAMETER(lParam); + switch (message) + { + case WM_INITDIALOG: + { + HWND hSetupTitle = GetDlgItem(hDlg, IDC_SETUPTITLE); + SetTitleFont(hSetupTitle); + } + return (INT_PTR)TRUE; + case WM_DESTROY: + { + HWND hSetupTitle = GetDlgItem(hDlg, IDC_SETUPTITLE); + HFONT modifiedFont = (HFONT)SendMessage(hSetupTitle, WM_GETFONT, 0, 0); + DeleteObject((HGDIOBJ)modifiedFont); + } + return (INT_PTR)TRUE; + case WM_SIZE: + { + int width = LOWORD(lParam); int height = HIWORD(lParam); + HWND hNext = GetDlgItem(hDlg, IDC_NEXT); + HWND hBack = GetDlgItem(hDlg, IDC_BACK); + HWND hCancel = GetDlgItem(hDlg, IDC_CANCEL); + HWND hSetupTitle = GetDlgItem(hDlg, IDC_SETUPTITLE); + HWND hEditPath = GetDlgItem(hDlg, IDC_EDITPATH); + HWND hBtnPathSelect = GetDlgItem(hDlg, IDC_BTNPATHSELECT); + HWND hTreeMods = GetDlgItem(hDlg, IDC_TREEMODS); + SetWindowPos(hNext, NULL, 9 * width / 20 + 4, height - 26, (width - 10) / 4, 23, SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(hBack, NULL, width / 5, height - 26, (width - 10) / 4, 23, SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(hCancel, NULL, width - width / 4 - 4, height - 26, (width - 10) / 4, 23, SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(hSetupTitle, NULL, 0, 0, width - 2, 18, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(hEditPath, NULL, 5, 56, (int)((float)width / 10.0F * 8.0F) - 12, 26, SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(hBtnPathSelect, NULL, width - width / 5 - 5, 56, (width - 10) / 5 - 2, 24, SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(hTreeMods, NULL, 6, 86, width - 11, height - 116, SWP_NOZORDER | SWP_NOACTIVATE); + } + return (INT_PTR)TRUE; + case WM_COMMAND: + { + DialogController_PathSelect *pController = (DialogController_PathSelect*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + switch (LOWORD(wParam)) + { + case IDC_BACK: + ShowInstallDialog(pController->GetDialogsData(), (EInstallDialogs)(pController->GetOwnDialogType() - 1)); + break; + case IDC_NEXT: + ShowInstallDialog(pController->GetDialogsData(), (EInstallDialogs)(pController->GetOwnDialogType() + 1)); + break; + case IDC_CANCEL: + OnCancelPressed(pController->GetDialogsData()); + break; + case IDC_BTNPATHSELECT: + { + HWND hEditPath = GetDlgItem(hDlg, IDC_EDITPATH); + WCHAR *pFolder = NULL; + if (hEditPath && ShowFolderSelectDialog(hDlg, &pFolder)) + { + SetWindowTextW(hEditPath, pFolder); + } + if (pFolder) + FreeCOMFilePathBuf(&pFolder); + } + break; + default: + return (INT_PTR)FALSE; + } + return (INT_PTR)TRUE; + } + } + return (INT_PTR)FALSE; +} +INT_PTR CALLBACK ProgressDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + UNREFERENCED_PARAMETER(lParam); + switch (message) + { + case WM_INITDIALOG: + { + HWND hSetupTitle = GetDlgItem(hDlg, IDC_SETUPTITLE); + SetTitleFont(hSetupTitle); + } + return (INT_PTR)TRUE; + case WM_DESTROY: + { + HWND hSetupTitle = GetDlgItem(hDlg, IDC_SETUPTITLE); + HFONT modifiedFont = (HFONT)SendMessage(hSetupTitle, WM_GETFONT, 0, 0); + DeleteObject((HGDIOBJ)modifiedFont); + } + return (INT_PTR)TRUE; + case WM_SIZE: + { + int width = LOWORD(lParam); int height = HIWORD(lParam); + HWND hNext = GetDlgItem(hDlg, IDC_NEXT); + HWND hCancel = GetDlgItem(hDlg, IDC_CANCEL); + HWND hSetupTitle = GetDlgItem(hDlg, IDC_SETUPTITLE); + HWND hProgInstall = GetDlgItem(hDlg, IDC_PROGINSTALL); + HWND hSCurFile = GetDlgItem(hDlg, IDC_SCURFILE); + HWND hEditStatus = GetDlgItem(hDlg, IDC_EDITSTATUS); + SetWindowPos(hNext, NULL, 9 * width / 20 + 4, height - 26, (width - 10) / 4, 23, SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(hCancel, NULL, width - width / 4 - 4, height - 26, (width - 10) / 4, 23, SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(hSetupTitle, NULL, 0, 0, width - 2, 18, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(hProgInstall, NULL, 5, 70, width - 10, 20, SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(hSCurFile, NULL, 5, 90, width - 10, 18, SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(hEditStatus, NULL, 6, 108, width - 11, height - 134, SWP_NOZORDER | SWP_NOACTIVATE); + } + return (INT_PTR)TRUE; + case WM_COMMAND: + { + DialogController_Progress *pController = (DialogController_Progress*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + switch (LOWORD(wParam)) + { + case IDC_NEXT: + ShowInstallDialog(pController->GetDialogsData(), (EInstallDialogs)(pController->GetOwnDialogType() + 1)); + break; + case IDC_CANCEL: + { + DWORD result = MessageBox(hDlg, + TEXT("Do you really want to cancel the mod installation?\r\n")\ + TEXT("The installer will finish the current operation and then revert the changes."), + TEXT("UABE Mod Installer"), MB_YESNO | MB_ICONINFORMATION); + if (result == IDYES) + { + pController->SetCancelled(true); + } + } + break; + default: + return (INT_PTR)FALSE; + } + return (INT_PTR)TRUE; + } + } + return (INT_PTR)FALSE; +} +INT_PTR CALLBACK CompleteDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + UNREFERENCED_PARAMETER(lParam); + switch (message) + { + case WM_INITDIALOG: + { + HWND hSetupTitle = GetDlgItem(hDlg, IDC_SETUPTITLE); + SetTitleFont(hSetupTitle); + } + return (INT_PTR)TRUE; + case WM_SIZE: + { + int width = LOWORD(lParam); int height = HIWORD(lParam); + HWND hSetupTitle = GetDlgItem(hDlg, IDC_SETUPTITLE); + HWND hClose = GetDlgItem(hDlg, IDC_CLOSE); + HWND hCompleteText = GetDlgItem(hDlg, IDC_COMPLETETEXT); + HWND hModAuthors = GetDlgItem(hDlg, IDC_MODAUTHORS); + HWND hInstAuthor = GetDlgItem(hDlg, IDC_INSTAUTHOR); + HWND hBack = GetDlgItem(hDlg, IDC_BACK); + SetWindowPos(hSetupTitle, NULL, 0, 0, width - 2, 18, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(hCompleteText, NULL, 2, 20, width - 4, height - 120, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(hModAuthors, NULL, 2, height - 100, width - 4, 40, SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(hInstAuthor, NULL, 2, height - 60, width - 4, 20, SWP_NOACTIVATE); + SetWindowPos(hClose, NULL, width - width / 4 - 4, height - 26, (width - 10) / 4, 23, SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(hBack, NULL, width / 5, height - 26, (width - 10) / 4, 23, SWP_NOZORDER | SWP_NOACTIVATE); + } + return (INT_PTR)TRUE; + case WM_DESTROY: + { + HWND hSetupTitle = GetDlgItem(hDlg, IDC_SETUPTITLE); + HFONT modifiedFont = (HFONT)SendMessage(hSetupTitle, WM_GETFONT, 0, 0); + DeleteObject((HGDIOBJ)modifiedFont); + } + return (INT_PTR)TRUE; + case WM_COMMAND: + { + DialogController_Complete *pController = (DialogController_Complete*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + switch (LOWORD(wParam)) + { + case IDC_BACK: + ShowInstallDialog(pController->GetDialogsData(), (EInstallDialogs)(pController->GetOwnDialogType() - 1)); + break; + case IDC_CLOSE: + pController->GetDialogsData()->closeWindows = true; + break; + default: + return (INT_PTR)FALSE; + } + return (INT_PTR)TRUE; + } + } + return (INT_PTR)FALSE; +} + +typedef DialogController*(__cdecl *DialogControllerConstructor)(HWND hDialog, HWND hParentWindow, InstallDialogsData *dialogsData); +DialogController *ConstructPrepareDialogController(HWND hDialog, HWND hParentWindow, InstallDialogsData *dialogsData) +{ + return new DialogController_Prepare(hDialog, hParentWindow, dialogsData); +} +DialogController *ConstructIntroductionDialogController(HWND hDialog, HWND hParentWindow, InstallDialogsData *dialogsData) +{ + return new DialogController_Introduction(hDialog, hParentWindow, dialogsData); +} +DialogController *ConstructDescriptionDialogController(HWND hDialog, HWND hParentWindow, InstallDialogsData *dialogsData) +{ + return new DialogController_Description(hDialog, hParentWindow, dialogsData); +} +DialogController *ConstructPathSelectDialogController(HWND hDialog, HWND hParentWindow, InstallDialogsData *dialogsData) +{ + return new DialogController_PathSelect(hDialog, hParentWindow, dialogsData); +} +DialogController *ConstructProgressDialogController(HWND hDialog, HWND hParentWindow, InstallDialogsData *dialogsData) +{ + return new DialogController_Progress(hDialog, hParentWindow, dialogsData); +} +DialogController *ConstructCompleteDialogController(HWND hDialog, HWND hParentWindow, InstallDialogsData *dialogsData) +{ + return new DialogController_Complete(hDialog, hParentWindow, dialogsData); +} +const struct {uint16_t id; DLGPROC proc; DialogControllerConstructor constr;} InstallDialogCreateInfo[] = { + { IDD_PREPARE, PrepareDlgProc, ConstructPrepareDialogController}, + { IDD_INTRODUCTION, IntroductionDlgProc, ConstructIntroductionDialogController}, + { IDD_DESCRIPTION, DescriptionDlgProc, ConstructDescriptionDialogController}, + { IDD_PATHSELECT, PathSelectDlgProc, ConstructPathSelectDialogController}, + { IDD_PROGRESS, ProgressDlgProc, ConstructProgressDialogController}, + { IDD_COMPLETE, CompleteDlgProc, ConstructCompleteDialogController}, +}; + +struct DialogMessageThreadParam +{ + InstallDialogsData *ret; + HINSTANCE hInstance; + HANDLE readyEvent; +}; +DWORD WINAPI InstallDialogsMessageThread(PVOID _param) +{ + DialogMessageThreadParam *param = (DialogMessageThreadParam*)_param; + InstallDialogsData *dialogsData = param->ret; + HINSTANCE hInstance = param->hInstance; + { + InstallDialogsData *ret = param->ret; + HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | + COINIT_DISABLE_OLE1DDE); + if (!FAILED(hr)) + { + INITCOMMONCONTROLSEX init; + init.dwSize = sizeof(init); + init.dwICC = ICC_PROGRESS_CLASS | ICC_LINK_CLASS; + if (!InitCommonControlsEx(&init)) + hr = E_FAIL; + } + if (FAILED(hr)) + { + TCHAR sprntTmp[100]; + _stprintf_s(sprntTmp, TEXT("Fatal error : Unable to initialize the COM (HRESULT %X)!"), hr); + MessageBox(NULL, sprntTmp, TEXT("ERROR"), 16); + CoUninitialize(); + delete ret; + param->ret = NULL; + SetEvent(param->readyEvent); + return NULL; + } + ret->dialogChangedEvent = CreateEvent(NULL, FALSE, TRUE, NULL); + ret->freeDialogResourcesEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + ret->dialogThreadClosedEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + RegisterInstallWindowClass(hInstance); + ret->hWindow = CreateWindow(L"UABE_ModInstaller", L"UABE Mod Installer", WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, 0, 100, 100, NULL, NULL, hInstance, NULL); + if (ret->hWindow == NULL) + { + CloseHandle(ret->dialogThreadClosedEvent); + CloseHandle(ret->freeDialogResourcesEvent); + CloseHandle(ret->dialogChangedEvent); + CoUninitialize(); + delete ret; + param->ret = NULL; + SetEvent(param->readyEvent); + return NULL; + } + SetWindowLongPtr(ret->hWindow, GWLP_USERDATA, (LONG_PTR)dialogsData); + for (unsigned int i = 0; i < InstallDialog_COUNT; i++) + { + ret->hDialogs[i] = + CreateDialog(hInstance, MAKEINTRESOURCE(InstallDialogCreateInfo[i].id), ret->hWindow, InstallDialogCreateInfo[i].proc); + if (!ret->hDialogs[i]) + { + DWORD test = GetLastError(); + DestroyWindow(ret->hWindow); + CloseHandle(ret->dialogThreadClosedEvent); + CloseHandle(ret->freeDialogResourcesEvent); + CloseHandle(ret->dialogChangedEvent); + CoUninitialize(); + delete ret; + param->ret = NULL; + SetEvent(param->readyEvent); + return 0; + } + } + for (unsigned int i = 0; i < InstallDialog_COUNT; i++) + { + ret->pControllers[i] = InstallDialogCreateInfo[i].constr(ret->hDialogs[i], ret->hWindow, ret); + } + int borderWidth, borderHeight; + { + RECT oldWinRect; + GetWindowRect(ret->hWindow, &oldWinRect); + RECT oldClnRect; + GetClientRect(ret->hWindow, &oldClnRect); + borderWidth = (oldWinRect.right - oldWinRect.left) - (oldClnRect.right - oldClnRect.left); + borderHeight = (oldWinRect.bottom - oldWinRect.top) - (oldClnRect.bottom - oldClnRect.top); + } + ret->activeDialogIndex = InstallDialog_Prepare; + /*RECT prepareRect; + GetClientRect(ret->hDialogs[InstallDialog_Prepare], &prepareRect); + SetWindowPos(ret->hWindow, NULL, 0, 0, + prepareRect.right - prepareRect.left + borderWidth, + prepareRect.bottom - prepareRect.top + borderHeight, + SWP_NOMOVE | SWP_NOREPOSITION | SWP_NOZORDER);*/ + SetWindowPos(ret->hWindow, NULL, 0, 0, + 500 + borderWidth, + 360 + borderHeight, + SWP_NOMOVE | SWP_NOREPOSITION | SWP_NOZORDER); + ShowWindow(ret->hWindow, SW_SHOWDEFAULT); + UpdateWindow(ret->hWindow); + + SetEvent(param->readyEvent); + } + + { + int noMessageCounter = 0; + while (!dialogsData->closeWindows) + { + MSG msg; + if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + { + noMessageCounter = 0; + TranslateMessage(&msg); + DispatchMessage(&msg); + } + else + { + if (!IsWindow(dialogsData->hWindow)) + break; + if (noMessageCounter < 100) + { + noMessageCounter++; + Sleep(1); + } + else + Sleep(10); + } + } + while (!dialogsData->closeWindows) + Sleep(10); + DestroyWindow(dialogsData->hWindow); + dialogsData->hWindow = NULL; + dialogsData->closeWindows = false; + SetEvent(dialogsData->dialogChangedEvent); + WaitForSingleObject(dialogsData->freeDialogResourcesEvent, INFINITE); + CloseHandle(dialogsData->freeDialogResourcesEvent); dialogsData->freeDialogResourcesEvent = NULL; + CloseHandle(dialogsData->dialogChangedEvent); dialogsData->dialogChangedEvent = NULL; + for (unsigned int i = 0; i < InstallDialog_COUNT; i++) + { + delete dialogsData->pControllers[i]; + } + dialogsData->dialogChangedEvent = NULL; + dialogsData->isClosed = true; + CoUninitialize(); + SetEvent(dialogsData->dialogThreadClosedEvent); + } + return 0; +} +InstallDialogsData *InitInstallDialogs(HINSTANCE hInstance) +{ + InstallDialogsData *ret = new InstallDialogsData(); + memset(ret, 0, sizeof(InstallDialogsData)); + DialogMessageThreadParam threadParam; + threadParam.ret = ret; + threadParam.hInstance = hInstance; + threadParam.readyEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + HANDLE hThread = CreateThread(NULL, 0, InstallDialogsMessageThread, &threadParam, 0, NULL); + if (!hThread) + { + CloseHandle(threadParam.readyEvent); + delete ret; + return NULL; + } + CloseHandle(hThread); + while (WaitForSingleObject(threadParam.readyEvent, INFINITE) != WAIT_OBJECT_0){} + CloseHandle(threadParam.readyEvent); + return threadParam.ret; +} + +bool ShowInstallDialog(InstallDialogsData *data, EInstallDialogs dialog) +{ + if (!IsWindow(data->hWindow)) + return false; + ShowWindow(data->hDialogs[data->activeDialogIndex], SW_HIDE); + data->activeDialogIndex = (int)dialog; + ShowWindow(data->hDialogs[dialog], SW_SHOW); + SetEvent(data->dialogChangedEvent); + return true; +} +EInstallDialogs WaitForDialogChanged(InstallDialogsData *data, DWORD timeout) +{ + if (data->hWindow != NULL && WaitForSingleObject(data->dialogChangedEvent, timeout) == WAIT_OBJECT_0) + return data->pControllers[data->activeDialogIndex]->GetOwnDialogType(); + else + return (EInstallDialogs)-1; +} +void OnCancelPressed(InstallDialogsData *data) +{ + DWORD result = MessageBox(data->hWindow, + TEXT("Do you really want to cancel the mod installation?"), + TEXT("UABE Mod Installer"), MB_YESNO | MB_ICONINFORMATION); + if (result == IDYES) + { + data->closeWindows = true; + } +} +void CloseDialogThread(InstallDialogsData *data) +{ + //if (WaitForSingleObject(data->dialogThreadClosedEvent, 0) != WAIT_OBJECT_0) + { + data->closeWindows = true; + SetEvent(data->freeDialogResourcesEvent); + WaitForSingleObject(data->dialogThreadClosedEvent, INFINITE); + CloseHandle(data->dialogThreadClosedEvent); data->dialogThreadClosedEvent = NULL; + } +} + +InstallDialogsData::~InstallDialogsData() +{ + //if (IsWindow(this->hWindow)) + if (this->hWindow != NULL) + { + this->closeWindows = true; + while (!this->isClosed) Sleep(0); + } +} + +DialogController::~DialogController(){} +DialogControllerBase::DialogControllerBase(HWND hDialog, HWND hParentWindow, InstallDialogsData *dialogsData) +{ + this->hDialog = hDialog; + this->hParentWindow = hParentWindow; + this->dialogsData = dialogsData; + SetWindowLongPtr(hDialog, GWLP_USERDATA, (LONG_PTR)this); +} +DialogControllerBase::~DialogControllerBase(){} +HWND DialogControllerBase::GetDialogHandle() { return hDialog; } +HWND DialogControllerBase::GetParentWindow() { return hParentWindow; } +InstallDialogsData *DialogControllerBase::GetDialogsData() { return dialogsData; } + +DialogController_Prepare::DialogController_Prepare(HWND hDialog, HWND hParentWindow, InstallDialogsData *dialogsData) + : DialogControllerBase(hDialog, hParentWindow, dialogsData) +{ + hProcessCheckboxes[0] = GetDlgItem(hDialog, IDC_CKLOADDATA); +} +DialogController_Prepare::~DialogController_Prepare(){} +EInstallDialogs DialogController_Prepare::GetOwnDialogType() { return InstallDialog_Prepare; } +void DialogController_Prepare::SetStatus(EInstallDialogPrepareStatus stat, EInstallDialogPrepareStatusValue value) +{ + if (stat < InstallDialogPrepareStatus_SIZE) + { + switch (value) + { + case InstallPrepareStatus_Inactive: + Button_SetCheck(hProcessCheckboxes[stat], BST_UNCHECKED); + break; + case InstallPrepareStatus_Active: + Button_SetCheck(hProcessCheckboxes[stat], BST_INDETERMINATE); + break; + case InstallPrepareStatus_Completed: + Button_SetCheck(hProcessCheckboxes[stat], BST_CHECKED); + break; + case InstallPrepareStatus_Error: + { + Button_SetCheck(hProcessCheckboxes[stat], BST_INDETERMINATE); + int textLen = Button_GetTextLength(hProcessCheckboxes[stat]); + TCHAR *newButtonText = new TCHAR[textLen + 10]; + int copied = Button_GetText(hProcessCheckboxes[stat], newButtonText, textLen+1); + if (copied > textLen) copied = textLen; + _tcscpy(&newButtonText[copied], TEXT(" (failed)")); + Button_SetText(hProcessCheckboxes[stat], newButtonText); + delete[] newButtonText; + } + break; + } + } +} + +DialogControllerTitled::DialogControllerTitled(HWND hDialog, HWND hParentWindow, InstallDialogsData *dialogsData, bool appendSetup) + : DialogControllerBase(hDialog, hParentWindow, dialogsData) +{ + hStaticTitle = GetDlgItem(hDialog, IDC_SETUPTITLE); + bAppendTitle = appendSetup; +} +DialogControllerTitled::~DialogControllerTitled(){} +void DialogControllerTitled::SetModName(const char *modName) +{ + if (hStaticTitle) + { +#ifdef _UNICODE + int modNameLenA = (int)(strlen(modName) & 0x7FFFFFFF); + int modNameLenW = MultiByteToWideChar(CP_UTF8, 0, modName, modNameLenA, NULL, 0); + wchar_t *title = new wchar_t[modNameLenW + 7]; + MultiByteToWideChar(CP_UTF8, 0, modName, modNameLenA, title, modNameLenW); + wcsncpy(&title[modNameLenW], bAppendTitle ? L" Setup" : L"", 7); +#else + size_t modNameLen = strlen(modName); + char *title = new char[modNameLen + 7]; + strncpy(title, modName, modNameLen); + strncpy(&title[modNameLen], bAppendTitle ? " Setup" : "", 7); +#endif + SetWindowText(hStaticTitle, title); + delete[] title; + } +} + + +DialogController_Introduction::DialogController_Introduction(HWND hDialog, HWND hParentWindow, InstallDialogsData *dialogsData) + : DialogControllerTitled(hDialog, hParentWindow, dialogsData, true) +{ +} +DialogController_Introduction::~DialogController_Introduction() +{ +} +EInstallDialogs DialogController_Introduction::GetOwnDialogType() +{ + return InstallDialog_Introduction; +} + +DialogController_Description::DialogController_Description(HWND hDialog, HWND hParentWindow, InstallDialogsData *dialogsData) + : DialogControllerTitled(hDialog, hParentWindow, dialogsData, false) +{ + hStaticAuthors = GetDlgItem(hDialog, IDC_AUTHORS); + hStaticDescription = GetDlgItem(hDialog, IDC_DESCRIPTION); +} +DialogController_Description::~DialogController_Description(){} +EInstallDialogs DialogController_Description::GetOwnDialogType() { return InstallDialog_Description; } +void DialogController_Description::SetAuthors(const char *authors) +{ + if (hStaticAuthors) + { + uint16_t nullAuthor = 0; + TCHAR *displayText; + if (!authors) + { + displayText = (TCHAR*)&nullAuthor; + } + else + { +#ifdef _UNICODE + int authorsLenA = (int)(strlen(authors) & 0x7FFFFFFF); + int authorsLenW = MultiByteToWideChar(CP_UTF8, 0, authors, authorsLenA, NULL, 0); + displayText = new wchar_t[authorsLenW + 4]; + wcsncpy(displayText, L"By ", 3); + MultiByteToWideChar(CP_UTF8, 0, authors, authorsLenA, &displayText[3], authorsLenW); + displayText[authorsLenW + 3] = 0; +#else + size_t authorsLen = strlen(authors); + displayText = new char[authorsLen + 4]; + strncpy(displayText, "By ", 3); + strncpy(&displayText[3], authors, authorsLen); + displayText[authorsLen + 3] = 0; +#endif + } + SetWindowText(hStaticAuthors, displayText); + if (displayText != (TCHAR*)&nullAuthor) + delete[] displayText; + } +} +void DialogController_Description::SetDescription(const char *description) +{ + if (hStaticDescription) + { +#ifdef _UNICODE + int descriptionLenA = (int)(strlen(description) & 0x7FFFFFFF); + int descriptionLenW = MultiByteToWideChar(CP_UTF8, 0, description, descriptionLenA, NULL, 0); + wchar_t *displayText = new wchar_t[descriptionLenW + 1]; + MultiByteToWideChar(CP_UTF8, 0, description, descriptionLenA, displayText, descriptionLenW); + displayText[descriptionLenW] = 0; + SetWindowText(hStaticDescription, displayText); + delete[] displayText; +#else + SetWindowText(hStaticDescription, description); +#endif + } +} + +DialogController_PathSelect::DialogController_PathSelect(HWND hDialog, HWND hParentWindow, InstallDialogsData *dialogsData) + : DialogControllerTitled(hDialog, hParentWindow, dialogsData, true) +{ + hEditPath = GetDlgItem(hDialog, IDC_EDITPATH); + hTreeModifications = GetDlgItem(hDialog, IDC_TREEMODS); +} +DialogController_PathSelect::~DialogController_PathSelect() +{ +} +EInstallDialogs DialogController_PathSelect::GetOwnDialogType() +{ + return InstallDialog_PathSelect; +} + +TCHAR *DialogController_PathSelect::GetPath(size_t &pathLen) +{ + if (hEditPath) + { + size_t textLen = (size_t)Edit_GetTextLength(hEditPath); + TCHAR *ret = new TCHAR[textLen + 1]; + Edit_GetText(hEditPath, ret, textLen + 1); + ret[textLen] = 0; + pathLen = textLen; + return ret; + } + pathLen = 0; + return NULL; +} +void DialogController_PathSelect::SetPath(const TCHAR *path) +{ + if (hEditPath) + Edit_SetText(hEditPath, path); +} + +void FillModsTree_AssetsReplacers(HWND hTree, HTREEITEM base, std::vector& replacers) +{ + TCHAR sprntTmp[256]; + + TVINSERTSTRUCT insert = {}; + insert.hParent = base; + insert.hInsertAfter = TVI_FIRST;//hInsertAfter?hInsertAfter:TVI_ROOT; + insert.itemex.hItem = NULL; + insert.itemex.state = 0; + insert.itemex.stateMask = 0xFF; + insert.itemex.pszText = sprntTmp; + insert.itemex.cchTextMax = 256; + insert.itemex.hwnd = NULL; + insert.itemex.mask = TVIF_CHILDREN | TVIF_STATE | TVIF_TEXT; + insert.itemex.cChildren = 0; + for (size_t i = replacers.size(); i > 0; i--) + { + AssetsReplacer *pReplacer = (AssetsReplacer*)replacers[i - 1]; + if (!pReplacer) + continue; + switch (pReplacer->GetType()) + { + case AssetsReplacement_AddOrModify: + case AssetsReplacement_Remove: + { + AssetsEntryReplacer *pEntryReplacer = reinterpret_cast(pReplacer); + const TCHAR* opType = + (pReplacer->GetType() == AssetsReplacement_AddOrModify) ? TEXT("Replace") : ( + (pReplacer->GetType() == AssetsReplacement_Remove) ? TEXT("Remove") : TEXT("")); + _stprintf_s(sprntTmp, TEXT("%s PathId %lld"), opType, (long long int)pEntryReplacer->GetPathID()); + TreeView_InsertItem(hTree, &insert); + } + break; + case AssetsReplacement_Dependencies: + { + _stprintf_s(sprntTmp, TEXT("Modify dependencies")); + TreeView_InsertItem(hTree, &insert); + } + break; + } + } +} +void DialogController_PathSelect::FillModsTree(InstallerPackageFile *pInstallFile) +{ + if (!hTreeModifications) + return; + size_t numBundle = 0, numAssets = 0, numResources = 0; + for (size_t i = 0; i < pInstallFile->affectedAssets.size(); i++) + { + switch (pInstallFile->affectedAssets[i].type) + { + case InstallerPackageAssetsType::Assets: + numAssets++; + break; + case InstallerPackageAssetsType::Bundle: + numBundle++; + break; + case InstallerPackageAssetsType::Resources: + numResources++; + break; + } + } + + TVINSERTSTRUCT insert = {}; + insert.hParent = NULL; + insert.hInsertAfter = TVI_FIRST;//hInsertAfter?hInsertAfter:TVI_ROOT; + insert.itemex.hItem = NULL; + insert.itemex.state = 0; + insert.itemex.stateMask = 0xFF; + insert.itemex.pszText = const_cast(TEXT("Affected bundles")); + insert.itemex.hwnd = NULL; + insert.itemex.mask = TVIF_CHILDREN | TVIF_STATE | TVIF_TEXT; + insert.itemex.cChildren = numBundle ? 1 : 0; + HTREEITEM hBundleBaseItem = TreeView_InsertItem(hTreeModifications, &insert); + + insert.itemex.pszText = const_cast(TEXT("Affected assets files")); + insert.hInsertAfter = TVI_LAST; + insert.itemex.cChildren = numAssets ? 1 : 0; + HTREEITEM hAssetsBaseItem = TreeView_InsertItem(hTreeModifications, &insert); + + insert.itemex.pszText = const_cast(TEXT("Affected resource files")); + insert.hInsertAfter = TVI_LAST; + insert.itemex.cChildren = numResources ? 1 : 0; + HTREEITEM hResourcesBaseItem = TreeView_InsertItem(hTreeModifications, &insert); + + for (size_t _i = pInstallFile->affectedAssets.size(); _i > 0; _i--) + { + size_t i = _i - 1; + InstallerPackageAssetsDesc &desc = pInstallFile->affectedAssets[i]; + size_t pathLen; + TCHAR *tcPath = _MultiByteToTCHAR(desc.path.c_str(), pathLen); + insert.itemex.pszText = tcPath; + insert.hInsertAfter = TVI_FIRST; + switch (desc.type) + { + case InstallerPackageAssetsType::Assets: + insert.hParent = hAssetsBaseItem; + break; + case InstallerPackageAssetsType::Bundle: + insert.hParent = hBundleBaseItem; + break; + case InstallerPackageAssetsType::Resources: + insert.hParent = hResourcesBaseItem; + break; + default: insert.hParent = NULL; + } + insert.itemex.cChildren = desc.replacers.size() ? 1 : 0; + HTREEITEM hCurBaseDesc = TreeView_InsertItem(hTreeModifications, &insert); + _FreeTCHAR(tcPath); + switch (desc.type) + { + case InstallerPackageAssetsType::Assets: + { + std::vector replacerVector(desc.replacers.size()); + for (size_t i = 0; i < desc.replacers.size(); ++i) + replacerVector[i] = reinterpret_cast(desc.replacers[i].get()); + FillModsTree_AssetsReplacers(hTreeModifications, hCurBaseDesc, replacerVector); + } + break; + case InstallerPackageAssetsType::Bundle: + { + insert.hParent = hCurBaseDesc; + for (size_t _i = desc.replacers.size(); _i > 0; _i--) + { + size_t i = _i - 1; + TCHAR sprntTmp[1024]; + insert.itemex.pszText = sprntTmp; + insert.itemex.cChildren = 0; + + BundleReplacer* pReplacer = (BundleReplacer*)desc.replacers[i].get(); + BundleEntryModifierFromAssets* pModifierFromAssets = + dynamic_cast(pReplacer); + size_t origEntryNameLen; size_t newEntryNameLen; + TCHAR* tcOrigEntry = _MultiByteToTCHAR(pReplacer->GetOriginalEntryName(), origEntryNameLen); + TCHAR* tcNewEntry = _MultiByteToTCHAR(pReplacer->GetEntryName(), newEntryNameLen); + if (pReplacer->GetType() == BundleReplacement_Rename) + { + _stprintf_s(sprntTmp, TEXT("Rename %s to %s"), tcOrigEntry, tcNewEntry); + } + else if (pReplacer->GetType() == BundleReplacement_AddOrModify) + { + if (pReplacer->GetOriginalEntryName() == NULL && pReplacer->GetEntryName() != NULL) + { + _stprintf_s(sprntTmp, TEXT("Modify/add %s"), tcNewEntry); + } + else if (pReplacer->GetOriginalEntryName() != NULL && pReplacer->GetEntryName() != NULL && + strcmp(pReplacer->GetOriginalEntryName(), pReplacer->GetEntryName())) + { + _stprintf_s(sprntTmp, TEXT("Modify and rename %s to %s"), tcOrigEntry, tcNewEntry); + } + else + { + _stprintf_s(sprntTmp, TEXT("Modify %s"), tcOrigEntry); + } + if (pModifierFromAssets) + { + insert.itemex.cChildren = 1; + } + } + else if (pReplacer->GetType() == BundleReplacement_Remove) + { + _stprintf_s(sprntTmp, TEXT("Remove %s"), tcOrigEntry); + } + else + { + _stprintf_s(sprntTmp, TEXT(" %s"), tcOrigEntry); + } + HTREEITEM curTreeItem = TreeView_InsertItem(hTreeModifications, &insert); + _FreeTCHAR(tcOrigEntry); + _FreeTCHAR(tcNewEntry); + if (pModifierFromAssets) + { + size_t assetReplacerCount; + AssetsReplacer** pAssetReplacers = pModifierFromAssets->GetReplacers(assetReplacerCount); + std::vector replacerVector(pAssetReplacers, &pAssetReplacers[assetReplacerCount]); + FillModsTree_AssetsReplacers(hTreeModifications, curTreeItem, replacerVector); + } + } + } + break; + case InstallerPackageAssetsType::Resources: + if (desc.replacers.size() == 1) + { + BundleReplacer* pReplacer = reinterpret_cast(desc.replacers[0].get()); + auto* pResourcesReplacer = dynamic_cast(pReplacer); + if (pResourcesReplacer != nullptr) + { + insert.hParent = hCurBaseDesc; + insert.itemex.pszText = const_cast( + pResourcesReplacer->RequiresEntryReader() ? TEXT("Modify") : TEXT("Add or replace") + ); + insert.itemex.cChildren = 0; + TreeView_InsertItem(hTreeModifications, &insert); + } + } + break; + } + } +} + + +DialogController_Progress::DialogController_Progress(HWND hDialog, HWND hParentWindow, InstallDialogsData *dialogsData) + : DialogControllerTitled(hDialog, hParentWindow, dialogsData, true) +{ + hInstallingText = GetDlgItem(hDialog, IDC_SINSTALLING); + hProgressBar = GetDlgItem(hDialog, IDC_PROGINSTALL); + hCurFileText = GetDlgItem(hDialog, IDC_SCURFILE); + hLogEdit = GetDlgItem(hDialog, IDC_EDITSTATUS); + hBtnNext = GetDlgItem(hDialog, IDC_NEXT); + hBtnCancel = GetDlgItem(hDialog, IDC_CANCEL); + cancelled = false; + + if (hProgressBar) + { + SendMessage(hProgressBar, PBM_SETRANGE, NULL, MAKELPARAM(0,10000)); + SendMessage(hProgressBar, PBM_SETPOS, NULL, NULL); + } +} +DialogController_Progress::~DialogController_Progress() +{ +} +EInstallDialogs DialogController_Progress::GetOwnDialogType() +{ + return InstallDialog_Progress; +} + +bool DialogController_Progress::GetCancelled() +{ + return cancelled; +} +void DialogController_Progress::SetCancelled(bool cancelled) +{ + this->cancelled = cancelled; +} +void DialogController_Progress::SetPaused(bool paused) +{ + if (hProgressBar) + { + if (paused) + SendMessage(hProgressBar, PBM_SETSTATE, PBST_PAUSED, NULL); + else + SendMessage(hProgressBar, PBM_SETSTATE, PBST_NORMAL, NULL); + } +} +void DialogController_Progress::SetProgress(float percent, const char *curFileName) +{ + if (hProgressBar) + { + SendMessage(hProgressBar, PBM_SETPOS, (WPARAM)((int)(abs(percent) * 100.0F)), NULL); + if (percent < 0) + SendMessage(hProgressBar, PBM_SETSTATE, PBST_ERROR, NULL); + else + SendMessage(hProgressBar, PBM_SETSTATE, PBST_NORMAL, NULL); + } + if (hCurFileText) + { + size_t fileNameLen; + TCHAR *tcFileName = _MultiByteToTCHAR(curFileName, fileNameLen); + SetWindowText(hCurFileText, tcFileName); + _FreeTCHAR(tcFileName); + } +} +void DialogController_Progress::AddToLog(const wchar_t *logText) +{ + if (hLogEdit) + { + size_t logTextLen; + TCHAR *tcLogText = _WideToTCHAR(logText, logTextLen); + int editLen = Edit_GetTextLength(hLogEdit); + int oldSelStart = editLen, oldSelEnd = editLen; + SendMessage(hLogEdit, EM_GETSEL, (WPARAM)&oldSelStart, (LPARAM)&oldSelEnd); + Edit_SetSel(hLogEdit, editLen, editLen); + Edit_ReplaceSel(hLogEdit, logText); + if ((oldSelEnd != editLen) || (oldSelStart != oldSelEnd)) + Edit_SetSel(hLogEdit, oldSelStart, oldSelEnd); + else + Edit_SetSel(hLogEdit, editLen + logTextLen, editLen + logTextLen); + _FreeTCHAR(tcLogText); + } +} +void DialogController_Progress::AddToLog(const char *logText) +{ + if (hLogEdit) + { + size_t logTextLen; + TCHAR *tcLogText = _MultiByteToTCHAR(logText, logTextLen); + int editLen = Edit_GetTextLength(hLogEdit); + int oldSelStart = editLen, oldSelEnd = editLen; + SendMessage(hLogEdit, EM_GETSEL, (WPARAM)&oldSelStart, (LPARAM)&oldSelEnd); + Edit_SetSel(hLogEdit, editLen, editLen); + Edit_ReplaceSel(hLogEdit, tcLogText); + if ((oldSelEnd != editLen) || (oldSelStart != oldSelEnd)) + Edit_SetSel(hLogEdit, oldSelStart, oldSelEnd); + else + Edit_SetSel(hLogEdit, editLen + logTextLen, editLen + logTextLen); + _FreeTCHAR(tcLogText); + } +} +void DialogController_Progress::EnableContinue() +{ + if (hBtnNext) + { + Button_Enable(hBtnNext, TRUE); + if (hInstallingText) + { + Edit_SetText(hInstallingText, TEXT("")); + } + } +} +void DialogController_Progress::DisableCancel() +{ + if (hBtnCancel) + { + Button_Enable(hBtnCancel, FALSE); + } +} + +DialogController_Complete::DialogController_Complete(HWND hDialog, HWND hParentWindow, InstallDialogsData *dialogsData) + : DialogControllerTitled(hDialog, hParentWindow, dialogsData, true) +{ + hStaticCompleteText = GetDlgItem(hDialog, IDC_COMPLETETEXT); + hStaticAuthors = GetDlgItem(hDialog, IDC_MODAUTHORS); +} +DialogController_Complete::~DialogController_Complete() +{ +} +EInstallDialogs DialogController_Complete::GetOwnDialogType() +{ + return InstallDialog_Complete; +} +void DialogController_Complete::SetCompleteText(const TCHAR *text) +{ + if (hStaticCompleteText) + { + SetWindowText(hStaticCompleteText, text); + } +} +void DialogController_Complete::SetAuthors(const char *authors) +{ + if (hStaticAuthors) + { + uint16_t nullAuthor = 0; + TCHAR *displayText; + if (!authors) + { + displayText = (TCHAR*)&nullAuthor; + } + else + { +#ifdef _UNICODE + int authorsLenA = (int)(strlen(authors) & 0x7FFFFFFF); + int authorsLenW = MultiByteToWideChar(CP_UTF8, 0, authors, authorsLenA, NULL, 0); + displayText = new wchar_t[authorsLenW + 4]; + wcsncpy(displayText, L"By ", 3); + MultiByteToWideChar(CP_UTF8, 0, authors, authorsLenA, &displayText[3], authorsLenW); + displayText[authorsLenW + 3] = 0; +#else + size_t authorsLen = strlen(authors); + displayText = new char[authorsLen + 4]; + strncpy(displayText, "By ", 3); + strncpy(&displayText[3], authors, authorsLen); + displayText[authorsLen + 3] = 0; +#endif + } + SetWindowText(hStaticAuthors, displayText); + if (displayText != (TCHAR*)&nullAuthor) + delete[] displayText; + } +} \ No newline at end of file diff --git a/ModInstaller/InstallDialog.h b/ModInstaller/InstallDialog.h new file mode 100644 index 0000000..00d88fc --- /dev/null +++ b/ModInstaller/InstallDialog.h @@ -0,0 +1,163 @@ +#pragma once +#include "InstallerDataFormat.h" + +enum EInstallDialogs +{ + InstallDialog_Prepare, + InstallDialog_Introduction, + InstallDialog_Description, + InstallDialog_PathSelect, + InstallDialog_Progress, + InstallDialog_Complete, + InstallDialog_COUNT +}; + +class InstallDialogsData +{ +public: + HWND hWindow; //parent window with borders that contains the dialogs + HWND hDialogs[InstallDialog_COUNT]; + class DialogController *pControllers[InstallDialog_COUNT]; + int activeDialogIndex; + + HANDLE dialogChangedEvent; + HANDLE freeDialogResourcesEvent; + HANDLE dialogThreadClosedEvent; + bool closeWindows; bool isClosed; + ~InstallDialogsData(); +}; +InstallDialogsData *InitInstallDialogs(HINSTANCE hInstance); +bool ShowInstallDialog(InstallDialogsData *data, EInstallDialogs dialog); +void OnCancelPressed(InstallDialogsData *data); +EInstallDialogs WaitForDialogChanged(InstallDialogsData *data, DWORD timeout = INFINITE); +void CloseDialogThread(InstallDialogsData *data); + +class DialogController +{ +public: + virtual ~DialogController(); + virtual EInstallDialogs GetOwnDialogType() = 0; + virtual HWND GetDialogHandle() = 0; + virtual HWND GetParentWindow() = 0; + virtual InstallDialogsData *GetDialogsData() = 0; +}; +enum EInstallDialogPrepareStatus +{ + InstallDialogPrepareStatus_LoadInstData, + InstallDialogPrepareStatus_SIZE +}; +enum EInstallDialogPrepareStatusValue +{ + InstallPrepareStatus_Inactive, + InstallPrepareStatus_Active, + InstallPrepareStatus_Completed, + InstallPrepareStatus_Error +}; +class DialogControllerBase : public DialogController +{ +protected: + HWND hDialog; + HWND hParentWindow; + InstallDialogsData *dialogsData; +public: + DialogControllerBase(HWND hDialog, HWND hParentWindow, InstallDialogsData *dialogsData); + ~DialogControllerBase(); + HWND GetDialogHandle(); + HWND GetParentWindow(); + InstallDialogsData *GetDialogsData(); +}; +class DialogControllerTitled : public DialogControllerBase +{ +protected: + HWND hStaticTitle; bool bAppendTitle; +public: + DialogControllerTitled(HWND hDialog, HWND hParentWindow, InstallDialogsData *dialogsData, bool appendSetup); + ~DialogControllerTitled(); + + void SetModName(const char *modName); +}; + +class DialogController_Prepare : public DialogControllerBase +{ + HWND hProcessCheckboxes[InstallDialogPrepareStatus_SIZE]; +public: + DialogController_Prepare(HWND hDialog, HWND hParentWindow, InstallDialogsData *dialogsData); + ~DialogController_Prepare(); + EInstallDialogs GetOwnDialogType(); + + void SetStatus(EInstallDialogPrepareStatus stat, EInstallDialogPrepareStatusValue value); +}; +class DialogController_Introduction : public DialogControllerTitled +{ +protected: +public: + DialogController_Introduction(HWND hDialog, HWND hParentWindow, InstallDialogsData *dialogsData); + ~DialogController_Introduction(); + EInstallDialogs GetOwnDialogType(); +}; +class DialogController_Description : public DialogControllerTitled +{ +protected: + HWND hStaticAuthors; + HWND hStaticDescription; +public: + DialogController_Description(HWND hDialog, HWND hParentWindow, InstallDialogsData *dialogsData); + ~DialogController_Description(); + EInstallDialogs GetOwnDialogType(); + + void SetAuthors(const char *authors); + void SetDescription(const char *description); +}; +class DialogController_PathSelect : public DialogControllerTitled +{ +protected: + HWND hEditPath; + HWND hTreeModifications; +public: + DialogController_PathSelect(HWND hDialog, HWND hParentWindow, InstallDialogsData *dialogsData); + ~DialogController_PathSelect(); + EInstallDialogs GetOwnDialogType(); + + TCHAR *GetPath(size_t &pathLen); + void SetPath(const TCHAR *path); + void FillModsTree(InstallerPackageFile *pInstallFile); +}; +class DialogController_Progress : public DialogControllerTitled +{ +protected: + HWND hInstallingText; + HWND hProgressBar; + HWND hCurFileText; + HWND hLogEdit; + HWND hBtnNext; + HWND hBtnCancel; + bool cancelled; +public: + DialogController_Progress(HWND hDialog, HWND hParentWindow, InstallDialogsData *dialogsData); + ~DialogController_Progress(); + EInstallDialogs GetOwnDialogType(); + + bool GetCancelled(); + void SetCancelled(bool cancelled); + + void SetPaused(bool paused); + //negative percent -> error + void SetProgress(float percent, const char *curFileName); + void AddToLog(const char *logText); + void AddToLog(const wchar_t *logText); + void EnableContinue(); + void DisableCancel(); +}; +class DialogController_Complete : public DialogControllerTitled +{ +protected: + HWND hStaticCompleteText; + HWND hStaticAuthors; +public: + DialogController_Complete(HWND hDialog, HWND hParentWindow, InstallDialogsData *dialogsData); + ~DialogController_Complete(); + EInstallDialogs GetOwnDialogType(); + + void SetAuthors(const char *authors); + void SetCompleteText(const TCHAR *text); +}; \ No newline at end of file diff --git a/ModInstaller/InstallerDataFormat.cpp b/ModInstaller/InstallerDataFormat.cpp new file mode 100644 index 0000000..aaa4457 --- /dev/null +++ b/ModInstaller/InstallerDataFormat.cpp @@ -0,0 +1,195 @@ +#include "stdafx.h" +#include "InstallerDataFormat.h" +#include +#include + +MODINSTALLER_API InstallerPackageFile::InstallerPackageFile() +{ +} +MODINSTALLER_API InstallerPackageFile::~InstallerPackageFile() +{ +} + +bool InstallerPackageFile::Read(QWORD& pos, IAssetsReader *pReader, std::shared_ptr ref_pReader, bool prefReplacersInMemory = false) +{ + DWORD assetsCount = 0; + uint8_t version = 1; + //opCount = 0; + *(DWORD*)(&magic[0]) = 0; + affectedAssets.clear(); + modName.clear(); + modCreators.clear(); + modDescription.clear(); + + pos += pReader->Read(pos, 4, &magic[0]); + if (*(DWORD*)(&magic[0]) != 0x50494D45) + return false; + + pos += pReader->Read(pos, 1, &version); + if (version > 2) + return false; + + uint16_t nameLen = 0; + pos += pReader->Read(pos, 2, &nameLen); + std::vector modNameBuf(nameLen); + pos += pReader->Read(pos, nameLen, modNameBuf.data()); + modName.assign(modNameBuf.begin(), modNameBuf.end()); + + uint16_t creatorsLen = 0; + pos += pReader->Read(pos, 2, &creatorsLen); + std::vector modCreatorsBuf(creatorsLen); + pos += pReader->Read(pos, creatorsLen, modCreatorsBuf.data()); + modCreators.assign(modCreatorsBuf.begin(), modCreatorsBuf.end()); + + uint16_t descriptionLen = 0; + pos += pReader->Read(pos, 2, &descriptionLen); + std::vector modDescriptionBuf(descriptionLen); + pos += pReader->Read(pos, descriptionLen, modDescriptionBuf.data()); + modDescription.assign(modDescriptionBuf.begin(), modDescriptionBuf.end()); + + if (version >= 1) + { + pos = addedTypes.Read(pReader, pos); + } + + pos += pReader->Read(pos, 4, &assetsCount); + affectedAssets.reserve(assetsCount); + for (DWORD i = 0; i < assetsCount; i++) + { + affectedAssets.push_back(InstallerPackageAssetsDesc()); + InstallerPackageAssetsDesc *pDesc = &affectedAssets[i]; + uint8_t typeVal = 0; + pos += pReader->Read(pos, 1, &typeVal); + if (typeVal > 2) + return false; + pDesc->type = static_cast((int)typeVal); + uint16_t pathLen = 0; + pos += pReader->Read(pos, 2, &pathLen); + std::vector pathBuf(pathLen); + pos += pReader->Read(pos, pathLen, pathBuf.data()); + pDesc->path.assign(pathBuf.begin(), pathBuf.end()); + DWORD replacerCount = 0; + pos += pReader->Read(pos, 4, &replacerCount); + switch (pDesc->type) + { + case InstallerPackageAssetsType::Assets: + for (DWORD k = 0; k < replacerCount; k++) + { + pDesc->replacers.emplace_back(ref_pReader + ? ReadAssetsReplacer(pos, ref_pReader, prefReplacersInMemory) + : ReadAssetsReplacer(pos, pReader, prefReplacersInMemory)); + if (pDesc->replacers.back().get() == nullptr) + { + pDesc->replacers.clear(); + return false; + } + } + break; + case InstallerPackageAssetsType::Bundle: + for (DWORD k = 0; k < replacerCount; k++) + { + pDesc->replacers.emplace_back(ref_pReader + ? ReadBundleReplacer(pos, ref_pReader, prefReplacersInMemory) + : ReadBundleReplacer(pos, pReader, prefReplacersInMemory)); + if (pDesc->replacers.back().get() == nullptr) + { + pDesc->replacers.clear(); + return false; + } + } + break; + case InstallerPackageAssetsType::Resources: + if (replacerCount != 1) + return false; + pDesc->replacers.emplace_back(ref_pReader + ? ReadBundleReplacer(pos, ref_pReader, prefReplacersInMemory) + : ReadBundleReplacer(pos, pReader, prefReplacersInMemory)); + if (dynamic_cast(pDesc->replacers.back().get()) == nullptr) + { + pDesc->replacers.clear(); + return false; + } + break; + } + } + return true; +} +MODINSTALLER_API bool InstallerPackageFile::Read(QWORD& pos, IAssetsReader *pReader, bool prefReplacersInMemory) +{ + return Read(pos, pReader, nullptr, prefReplacersInMemory); +} +MODINSTALLER_API bool InstallerPackageFile::Read(QWORD& pos, std::shared_ptr pReader, bool prefReplacersInMemory) +{ + IAssetsReader *_pReader = pReader.get(); + return Read(pos, _pReader, std::move(pReader), prefReplacersInMemory); +} +MODINSTALLER_API InstallerPackageAssetsDesc::InstallerPackageAssetsDesc() : type(InstallerPackageAssetsType::Assets){} +MODINSTALLER_API InstallerPackageAssetsDesc::InstallerPackageAssetsDesc(const InstallerPackageAssetsDesc &src) +{ + type = src.type; + replacers = src.replacers; + path = src.path; +} +MODINSTALLER_API InstallerPackageAssetsDesc::~InstallerPackageAssetsDesc(){} +MODINSTALLER_API bool InstallerPackageFile::Write(QWORD& pos, IAssetsWriter *pWriter) +{ + *(DWORD*)(&magic[0]) = 0x50494D45; + pos += pWriter->Write(pos, 4, &magic[0]); + + uint8_t version = 2; + pos += pWriter->Write(pos, 1, &version); + + uint16_t nameLen = (uint16_t)modName.size(); + pos += pWriter->Write(pos, 2, &nameLen); + pos += pWriter->Write(pos, nameLen, modName.c_str()); + + uint16_t creatorsLen = (uint16_t)modCreators.size(); + pos += pWriter->Write(pos, 2, &creatorsLen); + pos += pWriter->Write(pos, creatorsLen, modCreators.c_str()); + + uint16_t descriptionLen = (uint16_t)modDescription.size(); + pos += pWriter->Write(pos, 2, &descriptionLen); + pos += pWriter->Write(pos, descriptionLen, modDescription.c_str()); + + if (version >= 1) + { + pos = addedTypes.Write(pWriter, pos); + } + + DWORD assetsCount = (DWORD)affectedAssets.size(); + pos += pWriter->Write(pos, 4, &assetsCount); + for (DWORD i = 0; i < assetsCount; i++) + { + InstallerPackageAssetsDesc *pDesc = &affectedAssets[i]; + uint8_t typeVal = (uint8_t)(uint32_t)pDesc->type; + pos += pWriter->Write(pos, 1, &typeVal); + + uint16_t pathLen = (uint16_t)pDesc->path.size(); + pos += pWriter->Write(pos, 2, &pathLen); + pos += pWriter->Write(pos, pathLen, pDesc->path.c_str()); + + DWORD replacerCount = (DWORD)pDesc->replacers.size(); + pos += pWriter->Write(pos, 4, &replacerCount); + switch (pDesc->type) + { + case InstallerPackageAssetsType::Assets: + for (DWORD k = 0; k < replacerCount; k++) + pos = ((AssetsReplacer*)pDesc->replacers[k].get())->WriteReplacer(pos, pWriter); + break; + case InstallerPackageAssetsType::Bundle: + for (DWORD k = 0; k < replacerCount; k++) + pos = ((BundleReplacer*)pDesc->replacers[k].get())->WriteReplacer(pos, pWriter); + break; + case InstallerPackageAssetsType::Resources: + assert(replacerCount == 1); + if (replacerCount != 1 || dynamic_cast(pDesc->replacers[0].get()) == nullptr) + return false; + pos = ((BundleReplacer*)pDesc->replacers[0].get())->WriteReplacer(pos, pWriter); + break; + default: + assert(false); + return false; + } + } + return true; +} diff --git a/ModInstaller/InstallerDataFormat.h b/ModInstaller/InstallerDataFormat.h new file mode 100644 index 0000000..ae6a7f6 --- /dev/null +++ b/ModInstaller/InstallerDataFormat.h @@ -0,0 +1,62 @@ +#pragma once +typedef unsigned __int64 QWORD; +#ifndef MODINSTALLER_API +#ifdef MODINSTALLER_EXPORTS +#define MODINSTALLER_API __declspec(dllexport) +#else +#define MODINSTALLER_API __declspec(dllimport) +#endif +#endif +#include +#include +#include +#include +#include +#include "../AssetsTools/AssetsFileFormat.h" +#include "../AssetsTools/AssetsFileReader.h" +#include "../AssetsTools/AssetBundleFileFormat.h" +#include "../AssetsTools/BundleReplacer.h" +#include "../AssetsTools/AssetsReplacer.h" +#include "../AssetsTools/ClassDatabaseFile.h" + +//Installer packages contain all instructions (add, modify, remove) +class InstallerPackageFile +{ + bool Read(QWORD& pos, IAssetsReader *pReader, std::shared_ptr ref_pReader, bool prefReplacersInMemory); +public: + MODINSTALLER_API InstallerPackageFile(); + MODINSTALLER_API ~InstallerPackageFile(); + char magic[4] = {}; //EMIP = Extractor Mod Installer Package + std::string modName; + std::string modCreators; //comma-separated names list + std::string modDescription; + ClassDatabaseFile addedTypes; + std::vector affectedAssets; + //DWORD opCount; //the amount of operations (on any of the .assets files) + + //prefReplacersInMemory indicates whether it should try to read all replacer data into memory to remove the reader dependency after Read + MODINSTALLER_API bool Read(QWORD& pos, IAssetsReader *pReader, bool prefReplacersInMemory = false); + MODINSTALLER_API bool Read(QWORD& pos, std::shared_ptr pReader, bool prefReplacersInMemory = false); + MODINSTALLER_API bool Write(QWORD& pos, IAssetsWriter *pWriter); +}; + +enum class InstallerPackageAssetsType +{ + Assets=0, + Bundle=1, + Resources=2 +}; +class InstallerPackageAssetsDesc +{ +public: + MODINSTALLER_API InstallerPackageAssetsDesc(); + MODINSTALLER_API InstallerPackageAssetsDesc(const InstallerPackageAssetsDesc &src); + MODINSTALLER_API ~InstallerPackageAssetsDesc(); + InstallerPackageAssetsType type; + //relative to the main path + std::string path; + //Assets: 0..N AssetsReplacer entries + //Bundle: 0..N BundleReplacer entries + //Resources: 1 BundleEntryModifierByResources entry + std::vector> replacers; +}; diff --git a/ModInstaller/Licences.h b/ModInstaller/Licences.h new file mode 100644 index 0000000..7d12b04 --- /dev/null +++ b/ModInstaller/Licences.h @@ -0,0 +1,27 @@ +#pragma once + +const TCHAR *LicencesText = +TEXT("LZ4 Library\r\n") +TEXT("Copyright (c) 2011-2020, Yann Collet\r\n") +TEXT("All rights reserved.\r\n") +TEXT("\r\n") +TEXT("Redistribution and use in source and binary forms, with or without modification,\r\n") +TEXT("are permitted provided that the following conditions are met:\r\n") +TEXT("\r\n") +TEXT("* Redistributions of source code must retain the above copyright notice, this\r\n") +TEXT(" list of conditions and the following disclaimer.\r\n") +TEXT("\r\n") +TEXT("* Redistributions in binary form must reproduce the above copyright notice, this\r\n") +TEXT(" list of conditions and the following disclaimer in the documentation and/or\r\n") +TEXT(" other materials provided with the distribution.\r\n") +TEXT("\r\n") +TEXT("THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\r\n") +TEXT("ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\r\n") +TEXT("WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\r\n") +TEXT("DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR\r\n") +TEXT("ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\r\n") +TEXT("(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\r\n") +TEXT("LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\r\n") +TEXT("ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\r\n") +TEXT("(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r\n") +TEXT("SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."); \ No newline at end of file diff --git a/ModInstaller/MakeIconResource.cpp b/ModInstaller/MakeIconResource.cpp new file mode 100644 index 0000000..592e0e9 --- /dev/null +++ b/ModInstaller/MakeIconResource.cpp @@ -0,0 +1,206 @@ +#include "stdafx.h" +#include "MakeIconResource.h" + +typedef struct +{ + uint16_t idReserved; // Reserved (must be 0) + uint16_t idType; // Resource type (1 for icons) + uint16_t idCount; // How many images? +} GRPICONDIR, *LPGRPICONDIR; +#define SIZEOF_GRPICONDIR 6 +typedef struct +{ + uint8_t bWidth; // Width, in pixels, of the image + uint8_t bHeight; // Height, in pixels, of the image + uint8_t bColorCount; // Number of colors in image (0 if >=8bpp) + uint8_t bReserved; // Reserved + uint16_t wPlanes; // Color Planes + uint16_t wBitCount; // Bits per pixel + DWORD dwBytesInRes; // how many bytes in this resource? + uint16_t nID; // the ID +} GRPICONDIRENTRY, *LPGRPICONDIRENTRY; +#define SIZEOF_GRPICONDIRENTRY 14 +typedef struct +{ + uint8_t bWidth; // Width, in pixels, of the image + uint8_t bHeight; // Height, in pixels, of the image + uint8_t bColorCount; // Number of colors in image (0 if >=8bpp) + uint8_t bReserved; // Reserved + uint16_t wPlanes; // Color Planes + uint16_t wBitCount; // Bits per pixel + DWORD dwBytesInRes; // how many bytes in this resource? + DWORD dwOffset; //byte offset from .ico file base +} GRPICONENTRY, *LPGRPICONENTRY; +#define SIZEOF_GRPICONENTRY 16 +static bool MakeIconResource(const std::vector &icoData, std::vector &iconGroup, std::vector> &iconData) +{ + const LPGRPICONDIR pIconHeader = (const LPGRPICONDIR)icoData.data(); + if ((icoData.size() < SIZEOF_GRPICONDIR) || (icoData.size() < (SIZEOF_GRPICONDIR + pIconHeader->idCount * SIZEOF_GRPICONENTRY))) + return false; + for (uint16_t i = 0; i < pIconHeader->idCount; i++) + { + LPGRPICONENTRY pCurEntry = (LPGRPICONENTRY)&(icoData[SIZEOF_GRPICONDIR + i * SIZEOF_GRPICONENTRY]); + if (icoData.size() < (pCurEntry->dwOffset + pCurEntry->dwBytesInRes)) + return false; + } + iconGroup.resize(SIZEOF_GRPICONDIR + pIconHeader->idCount * SIZEOF_GRPICONDIRENTRY); + memcpy(iconGroup.data(), pIconHeader, SIZEOF_GRPICONDIR); + iconData.resize(pIconHeader->idCount); + for (uint16_t i = 0; i < pIconHeader->idCount; i++) + { + LPGRPICONENTRY pCurEntry = (LPGRPICONENTRY)&(icoData[SIZEOF_GRPICONDIR + i * SIZEOF_GRPICONENTRY]); + LPGRPICONDIRENTRY pCurDirEntry = (LPGRPICONDIRENTRY)&(iconGroup[SIZEOF_GRPICONDIR + i * SIZEOF_GRPICONDIRENTRY]); + memcpy(pCurDirEntry, pCurEntry, SIZEOF_GRPICONDIRENTRY - 2); + pCurDirEntry->nID = i + 1; + iconData[i].resize(pCurDirEntry->dwBytesInRes); + memcpy(iconData[i].data(), &(icoData[pCurEntry->dwOffset]), pCurEntry->dwBytesInRes); + } + return true; +} +static bool MakeIconResource(std::vector hIcons, std::vector &iconGroup, std::vector>&iconData) +{ + //http://www.codeproject.com/Articles/4945/UpdateResource?msg=2766314#xx2766314xx + + GRPICONDIR dirBase = {}; + dirBase.idReserved = 0; dirBase.idType = 1; + dirBase.idCount = std::min((uint16_t)hIcons.size(),0xFFFE); + std::vector dirEntries; + dirEntries.resize(std::min((uint16_t)hIcons.size(),0xFFFE)); + iconData.resize(std::min((uint16_t)hIcons.size(),0xFFFE)); + for (size_t i = 0; i < hIcons.size() && i < 0xFFFF; i++) + { + ICONINFO info = {}; + GetIconInfo(hIcons[i], &info); + BITMAP bitmapColorInfo = {}; + BITMAP bitmapMaskInfo = {}; + if (!GetObject(info.hbmColor, sizeof(bitmapColorInfo), &bitmapColorInfo)) + { + DeleteObject(info.hbmColor); + DeleteObject(info.hbmMask); + return false; + } + else + { + bool hasAlpha = false; + bool hasMask = GetObject(info.hbmMask, sizeof(bitmapMaskInfo), &bitmapMaskInfo) != 0 && !hasAlpha; + if (hasMask && bitmapMaskInfo.bmBitsPixel != 1) + return false; + + GRPICONDIRENTRY &entry = dirEntries[i]; + entry.bWidth = (uint8_t)bitmapColorInfo.bmWidth; + entry.bHeight = (uint8_t)bitmapColorInfo.bmHeight; + entry.bColorCount = 0;//(bitmapColorInfo.bmBitsPixel >= 8) ? 0 : (1 << bitmapColorInfo.bmBitsPixel); Size of palette + entry.bReserved = 0; + entry.wPlanes = 1;//bitmapColorInfo.bmPlanes; + entry.wBitCount = 32;//(hasMask || hasAlpha) ? 32 : 24;//bitmapColorInfo.bmBitsPixel; + entry.nID = 1 + (uint16_t)i; + DWORD xorStride = (((entry.bWidth * entry.wBitCount) + 31) & (~31)) >> 3; + DWORD andStride = (((entry.bWidth * 1) + 31) & (~31)) >> 3; //There MUST be a mask in the icon, if hasMask == false, we fill this with 1 + entry.dwBytesInRes = sizeof(BITMAPINFOHEADER) + entry.bHeight * xorStride + entry.bHeight * andStride;//(DWORD)iconDataSize; + //only width, height and color format info is stored in bitmapInfo + std::vector colorBitsTmp(xorStride * entry.bHeight); + + HDC hDC = CreateCompatibleDC(NULL); + BITMAPINFO bmpInfo = {}; + bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmpInfo.bmiHeader.biBitCount = 0; + bmpInfo.bmiHeader.biWidth = entry.bWidth; + bmpInfo.bmiHeader.biHeight = entry.bHeight; + bmpInfo.bmiHeader.biPlanes = 1; + bmpInfo.bmiHeader.biBitCount = 32;//bitmapColorInfo.bmBitsPixel; + bmpInfo.bmiHeader.biCompression = BI_RGB; + bmpInfo.bmiHeader.biSizeImage = 0; + SelectObject(hDC, info.hbmColor); + int ret = GetDIBits(hDC, info.hbmColor, 0, entry.bHeight, colorBitsTmp.data(), &bmpInfo, DIB_RGB_COLORS); + if (ret != entry.bHeight) + { + DeleteObject(info.hbmColor); + if (hasMask) + DeleteObject(info.hbmMask); + return false; + } + std::vector outIconData(entry.dwBytesInRes); + BITMAPINFOHEADER *rawIconData_Header = (BITMAPINFOHEADER*)outIconData.data(); + void *rawIconData_XOR = (void*)&(outIconData[sizeof(BITMAPINFOHEADER)]); + void *rawIconData_AND = (void*)&(outIconData[sizeof(BITMAPINFOHEADER) + entry.bHeight * xorStride]); + if (hasMask) + { + memcpy(rawIconData_XOR, colorBitsTmp.data(), (entry.bWidth * entry.bHeight) * 4); //always 32bit per pixel + std::vector bmpInfoBuf(sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD)); + BITMAPINFO *_bmpInfo = reinterpret_cast(bmpInfoBuf.data()); + _bmpInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + _bmpInfo->bmiHeader.biWidth = entry.bWidth; + _bmpInfo->bmiHeader.biHeight = entry.bHeight; + _bmpInfo->bmiHeader.biPlanes = 1; + _bmpInfo->bmiHeader.biBitCount = 1;//bitmapColorInfo.bmBitsPixel; + _bmpInfo->bmiHeader.biCompression = BI_RGB; + _bmpInfo->bmiHeader.biSizeImage = 0; + SelectObject(hDC, info.hbmMask); + if (GetDIBits(hDC, info.hbmMask, 0, entry.bHeight, colorBitsTmp.data(), _bmpInfo, DIB_RGB_COLORS) != entry.bHeight) + { + DeleteObject(info.hbmColor); + if (hasMask) + DeleteObject(info.hbmMask); + return false; + } + memcpy(rawIconData_AND, colorBitsTmp.data(), entry.bHeight * andStride); + } + else + { + memcpy(rawIconData_XOR, colorBitsTmp.data(), (entry.bWidth * entry.bHeight) * 4); //always 32bit per pixel + memset(rawIconData_AND, 1, andStride * entry.bHeight); //always 32bit per pixel + } + rawIconData_Header->biSize = sizeof(BITMAPINFOHEADER); + rawIconData_Header->biWidth = entry.bWidth; + rawIconData_Header->biHeight = entry.bHeight * 2; //once intended (1 bit per color times) to easily determine the size + rawIconData_Header->biPlanes = entry.wPlanes; + rawIconData_Header->biBitCount = entry.wBitCount; + rawIconData_Header->biCompression = 0; + rawIconData_Header->biSizeImage = 0; + rawIconData_Header->biXPelsPerMeter = 0; + rawIconData_Header->biYPelsPerMeter = 0; + rawIconData_Header->biClrUsed = 0; + rawIconData_Header->biClrImportant = 0; + + DeleteObject(info.hbmColor); + if (hasMask) + DeleteObject(info.hbmMask); + + iconData[i] = std::move(outIconData); + } + } + iconGroup.resize(SIZEOF_GRPICONDIR + dirEntries.size() * SIZEOF_GRPICONDIRENTRY); + memcpy(iconGroup.data(), &dirBase, SIZEOF_GRPICONDIR); + for (size_t i = 0; i < dirEntries.size(); i++) + memcpy(&(iconGroup.data()[SIZEOF_GRPICONDIR + i * SIZEOF_GRPICONDIRENTRY]), &dirEntries[i], SIZEOF_GRPICONDIRENTRY); + return true; +} + +bool SetProgramIconResource(const TCHAR *filePath, const std::vector &iconFileData) +{ + std::vector iconGroup; + std::vector> iconDataList; + if (MakeIconResource(iconFileData, iconGroup, iconDataList)) + { + HANDLE hUpdate = BeginUpdateResource(filePath, FALSE); + if (hUpdate) + { + #define IDI_MODINSTALLER 102 + //UpdateResource(hUpdate, RT_ICON, MAKEINTRESOURCE(IDI_MODINSTALLER), + // MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), userData->iconData, userData->iconDataSize); + + UpdateResource(hUpdate, RT_GROUP_ICON, MAKEINTRESOURCE(IDI_MODINSTALLER), + MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), iconGroup.data(), (DWORD)iconGroup.size()); + for (size_t i = 0; i < iconDataList.size(); i++) + { + UpdateResource(hUpdate, RT_ICON, MAKEINTRESOURCE(1+i), + MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), iconDataList[i].data(), (DWORD)iconDataList[i].size()); + } + //UpdateResource(hUpdate, RT_ICON, MAKEINTRESOURCE(1), + // MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), pIconData, iconDataLen); + EndUpdateResource(hUpdate, FALSE); + return true; + } + } + return false; +} diff --git a/ModInstaller/MakeIconResource.h b/ModInstaller/MakeIconResource.h new file mode 100644 index 0000000..1c0bba1 --- /dev/null +++ b/ModInstaller/MakeIconResource.h @@ -0,0 +1,6 @@ +#pragma once +#include +#include +#include + +bool SetProgramIconResource(const TCHAR *filePath, const std::vector &iconFileData); diff --git a/ModInstaller/MakeInstaller.cpp b/ModInstaller/MakeInstaller.cpp new file mode 100644 index 0000000..876e5c8 --- /dev/null +++ b/ModInstaller/MakeInstaller.cpp @@ -0,0 +1,324 @@ +#include "stdafx.h" +#include +#include +#include +#include +#include "InstallerDataFormat.h" +#include "ModInstaller.h" +#include "MakeIconResource.h" +#include + +DWORD GetFileOffsetFromRVA(IMAGE_DOS_HEADER *pDosHeader, DWORD rva) +{ + IMAGE_NT_HEADERS *pNtHeaders = (IMAGE_NT_HEADERS*)((uintptr_t)pDosHeader + pDosHeader->e_lfanew); + IMAGE_SECTION_HEADER *pSectionHeaders = (IMAGE_SECTION_HEADER*)((uintptr_t)pNtHeaders + + sizeof(IMAGE_NT_SIGNATURE) + sizeof(IMAGE_FILE_HEADER) + pNtHeaders->FileHeader.SizeOfOptionalHeader); + for (unsigned int i = 0; i < pNtHeaders->FileHeader.NumberOfSections; i++) + { + if (rva >= pSectionHeaders[i].VirtualAddress && rva < (pSectionHeaders[i].VirtualAddress + pSectionHeaders[i].SizeOfRawData)) + { + return rva - pSectionHeaders[i].VirtualAddress + pSectionHeaders[i].PointerToRawData; + } + } + return 0; +} +//searches an export in a not mapped module (directly loaded as a file) +//Assumes that all section headers are in bounds. +DWORD SearchExportByName_RawModule(PIMAGE_DOS_HEADER pModule, size_t fileSize, const char *name) +{ + PIMAGE_NT_HEADERS32 pNTHeaders32 = (PIMAGE_NT_HEADERS32)&((uint8_t*)pModule)[pModule->e_lfanew]; + PIMAGE_EXPORT_DIRECTORY pExportsDirectory; + if (pNTHeaders32->FileHeader.Machine == 0x014C) + { + DWORD offset = GetFileOffsetFromRVA(pModule, pNTHeaders32->OptionalHeader.DataDirectory[0].VirtualAddress); + if (!offset) + return 0; + pExportsDirectory = (PIMAGE_EXPORT_DIRECTORY)&((uint8_t*)pModule)[offset]; + } + else if (pNTHeaders32->FileHeader.Machine == 0x8664) //is AMD64 / 64bit + { + PIMAGE_NT_HEADERS64 pNTHeaders64 = (PIMAGE_NT_HEADERS64)pNTHeaders32; + DWORD offset = GetFileOffsetFromRVA(pModule, pNTHeaders64->OptionalHeader.DataDirectory[0].VirtualAddress); + if (!offset) + return 0; + pExportsDirectory = (PIMAGE_EXPORT_DIRECTORY)&((uint8_t*)pModule)[offset]; + } + else + return 0; + if ((uintptr_t)((uint8_t*)pExportsDirectory - (uint8_t*)pModule) >= fileSize + || (uintptr_t)((uint8_t*)&pExportsDirectory[1] - (uint8_t*)pModule) > fileSize) + return 0; + DWORD nameTableOffset = GetFileOffsetFromRVA(pModule, pExportsDirectory->AddressOfNames); + DWORD nameOrdinalTableOffset = GetFileOffsetFromRVA(pModule, pExportsDirectory->AddressOfNameOrdinals); + DWORD funcTableOffset = GetFileOffsetFromRVA(pModule, pExportsDirectory->AddressOfFunctions); + if ((!nameTableOffset) || (!nameOrdinalTableOffset) || (!funcTableOffset)) + return 0; + DWORD *nameTable = (DWORD*)&((uint8_t*)pModule)[nameTableOffset]; + if (nameTableOffset >= fileSize || (uint64_t)(nameTableOffset + pExportsDirectory->NumberOfNames * sizeof(DWORD)) > (uint64_t)fileSize) + return 0; + uint16_t *nameOrdinalTable = (uint16_t*)&((uint8_t*)pModule)[nameOrdinalTableOffset]; + if (nameOrdinalTableOffset >= fileSize || (uint64_t)(nameOrdinalTableOffset + pExportsDirectory->NumberOfNames * sizeof(uint16_t)) > (uint64_t)fileSize) + return 0; + DWORD *funcTable = (DWORD*)&((uint8_t*)pModule)[funcTableOffset]; + if (funcTableOffset >= fileSize || (uint64_t)(funcTableOffset + pExportsDirectory->NumberOfFunctions * sizeof(DWORD)) > (uint64_t)fileSize) + return 0; + for (DWORD i = 0; i < pExportsDirectory->NumberOfNames; i++) + { + if (nameTable[i] != 0) + { + DWORD nameOffset = GetFileOffsetFromRVA(pModule, nameTable[i]); + if ((nameOffset != 0) && !strcmp((char*)&((uint8_t*)pModule)[nameOffset], name)) + { + //The name ordinal table actually contains indices into the function table (name index -> function ordinal) + DWORD functionIndex = nameOrdinalTable[i]; + if (functionIndex < pExportsDirectory->NumberOfFunctions) + { + return funcTable[functionIndex]; + } + } + } + } + return 0; +} + +static IMAGE_RESOURCE_DIRECTORY_ENTRY *FindResourceDirEntry(IMAGE_DOS_HEADER *pModule, size_t fileSize, DWORD resourceBaseOffset, std::vector idPath) +{ + IMAGE_RESOURCE_DIRECTORY *pDir = (PIMAGE_RESOURCE_DIRECTORY)&((uint8_t*)pModule)[resourceBaseOffset]; + while (pDir != nullptr && !idPath.empty()) + { + if ((uintptr_t)pDir - (uintptr_t)pModule >= fileSize || ((uintptr_t)&pDir[1] - (uintptr_t)pModule) > fileSize) + return nullptr; + IMAGE_RESOURCE_DIRECTORY_ENTRY *pBeginEntry = &((IMAGE_RESOURCE_DIRECTORY_ENTRY*)&pDir[1])[pDir->NumberOfNamedEntries]; + IMAGE_RESOURCE_DIRECTORY_ENTRY *pEndEntry = &pBeginEntry[pDir->NumberOfIdEntries]; + if ((uintptr_t)pBeginEntry - (uintptr_t)pModule >= fileSize || ((uintptr_t)pEndEntry - (uintptr_t)pModule) > fileSize) + return nullptr; + //Note: The resource IDs are actually sorted, so a binary search would be possible. + // We have only ~4 entries to search through for the UABE installer, so a linear search is good enough. + for (IMAGE_RESOURCE_DIRECTORY_ENTRY *pCurEntry = pBeginEntry; pCurEntry != pEndEntry; ++pCurEntry) + { + if (!pCurEntry->NameIsString && pCurEntry->Id == idPath.front()) + { + idPath.erase(idPath.begin()); + if (idPath.empty()) + return pCurEntry; + if (!pCurEntry->DataIsDirectory) + return nullptr; + pDir = (PIMAGE_RESOURCE_DIRECTORY)&((uint8_t*)pModule)[resourceBaseOffset + pCurEntry->OffsetToDirectory]; + break; + } + } + } + return nullptr; +} + +//Also checks that all section headers are in bounds. +bool ConvertToEXE(IMAGE_DOS_HEADER *pDLL, size_t fileSize) +{ + IMAGE_NT_HEADERS32 *pNtHeaders = (IMAGE_NT_HEADERS32*)((uintptr_t)pDLL + pDLL->e_lfanew); + if (pDLL->e_lfanew > fileSize || (pDLL->e_lfanew + sizeof(IMAGE_NT_SIGNATURE) + sizeof(IMAGE_FILE_HEADER)) > fileSize + || (pDLL->e_lfanew + sizeof(IMAGE_NT_SIGNATURE) + sizeof(IMAGE_FILE_HEADER) + pNtHeaders->FileHeader.SizeOfOptionalHeader) > fileSize) + return false; + IMAGE_SECTION_HEADER *pSectionHeaders = (IMAGE_SECTION_HEADER*)((uintptr_t)pNtHeaders + + sizeof(IMAGE_NT_SIGNATURE) + sizeof(IMAGE_FILE_HEADER) + pNtHeaders->FileHeader.SizeOfOptionalHeader); + if (fileSize < (DWORD)pNtHeaders->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER) + || (uintptr_t)((uint8_t*)pSectionHeaders - (uint8_t*)pDLL) > (fileSize - (DWORD)pNtHeaders->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER))) + return false; + + pNtHeaders->FileHeader.Characteristics &= ~0x2000; //disable dll flag + //DWORD winMainOffs = (DWORD)((uintptr_t)GetProcAddress(hModule, "_tWinMain") - (uintptr_t)hModule); + DWORD winMainRVA = SearchExportByName_RawModule(pDLL, fileSize, "_WinMain"); + if (!winMainRVA) + return false; + //patch entry point + if (pNtHeaders->FileHeader.Machine == 0x014C) //is i386 / 32bit + pNtHeaders->OptionalHeader.AddressOfEntryPoint = winMainRVA; + else if (pNtHeaders->FileHeader.Machine == 0x8664) //is AMD64 / 64bit + ((IMAGE_NT_HEADERS64*)pNtHeaders)->OptionalHeader.AddressOfEntryPoint = winMainRVA; + + //Find the resources directory. + DWORD resourceDirOffset = 0; + if (pNtHeaders->FileHeader.Machine == 0x014C) //is i386 / 32bit + { + if (((uintptr_t)&pNtHeaders->OptionalHeader.DataDirectory[3] - (uintptr_t)pDLL) >= fileSize) + return false; + resourceDirOffset = GetFileOffsetFromRVA(pDLL, pNtHeaders->OptionalHeader.DataDirectory[2].VirtualAddress); + } + else if (pNtHeaders->FileHeader.Machine == 0x8664) //is AMD64 / 64bit + { + PIMAGE_NT_HEADERS64 pNTHeaders64 = (PIMAGE_NT_HEADERS64)pNtHeaders; + if (((uintptr_t)&pNTHeaders64->OptionalHeader.DataDirectory[3] - (uintptr_t)pDLL) >= fileSize) + return false; + resourceDirOffset = GetFileOffsetFromRVA(pDLL, pNTHeaders64->OptionalHeader.DataDirectory[2].VirtualAddress); + } + else + return false; + if (resourceDirOffset == 0) + return false; + + //Fix the manifest resource ID: + //ISOLATIONAWARE_MANIFEST_RESOURCE_ID is the manifest resource ID for dlls, + //CREATEPROCESS_MANIFEST_RESOURCE_ID is the manifest resource ID for exes. + //-> Change the manifest resource ID, since Windows otherwise would not find the manifest upon loading the process. + //The manifest is required to make Windows load the 6.0+ comctrl.dll instead of the pre-XP one that lacks features and looks outdated. + std::vector idPath(2); idPath[0] = (uint16_t)RT_MANIFEST; idPath[1] = (uint16_t)ISOLATIONAWARE_MANIFEST_RESOURCE_ID; + IMAGE_RESOURCE_DIRECTORY_ENTRY *pSearchedEntry = FindResourceDirEntry(pDLL, fileSize, resourceDirOffset, std::move(idPath)); + if (pSearchedEntry == nullptr) + return false; + pSearchedEntry->Id = (uint16_t)CREATEPROCESS_MANIFEST_RESOURCE_ID; + + return true; +} + +//Shifts the imports from MSVC++ redist after the import directories' null terminator. +//-> The PE loader will not resolve these imports by itself so they can be resolved by the .exe later. +//Also gives the new .exe pointers to the new import descriptors. +//Assumes that all section headers are in bounds. +int CreateLateResolveImports(IMAGE_DOS_HEADER *pModule, size_t fileSize) +{ + PIMAGE_NT_HEADERS32 pNTHeaders32 = (PIMAGE_NT_HEADERS32)&((uint8_t*)pModule)[pModule->e_lfanew]; + IMAGE_IMPORT_DESCRIPTOR *importDir; DWORD importDirRVA; + bool isAMD64 = false; + if (pNTHeaders32->FileHeader.Machine == 0x014C) + { + DWORD offset = GetFileOffsetFromRVA(pModule, pNTHeaders32->OptionalHeader.DataDirectory[1].VirtualAddress); + if (!offset) + return -1; + importDirRVA = pNTHeaders32->OptionalHeader.DataDirectory[1].VirtualAddress; + importDir = (IMAGE_IMPORT_DESCRIPTOR*)&((uint8_t*)pModule)[offset]; + } + else if (pNTHeaders32->FileHeader.Machine == 0x8664) //is AMD64 / 64bit + { + isAMD64 = true; + PIMAGE_NT_HEADERS64 pNTHeaders64 = (PIMAGE_NT_HEADERS64)pNTHeaders32; + DWORD offset = GetFileOffsetFromRVA(pModule, pNTHeaders64->OptionalHeader.DataDirectory[1].VirtualAddress); + if (!offset) + return -1; + importDirRVA = pNTHeaders64->OptionalHeader.DataDirectory[1].VirtualAddress; + importDir = (IMAGE_IMPORT_DESCRIPTOR*)&((uint8_t*)pModule)[offset]; + } + else + return -1; + if ((uintptr_t)((uint8_t*)importDir - (uint8_t*)pModule) >= fileSize || (uintptr_t)((uint8_t*)&importDir[1] - (uint8_t*)pModule) > fileSize + || fileSize < sizeof(IMAGE_IMPORT_DESCRIPTOR)) + return -1; + + IMAGE_IMPORT_DESCRIPTOR *curImportDir = importDir; + bool patchedMSVCR = false, patchedAssetsTools = false; + + static constexpr size_t maxNumDelayedImports = 32; + std::vector patchImportDirOffsets; + while (curImportDir && curImportDir->Name != 0) + { + DWORD offset = GetFileOffsetFromRVA(pModule, curImportDir->Name); + if (!offset || offset >= fileSize) + continue; + const char *moduleName = (const char*)&((uint8_t*)pModule)[offset]; + if (!strncmp(moduleName, "MSVCR", 5) || !strncmp(moduleName, "MSVCP", 5) || !strncmp(moduleName, "api-ms", 5) + || !strncmp(moduleName, "VCRUNTIME", 9) || !strncmp(moduleName, "ucrt", 4)) + { + patchImportDirOffsets.push_back((DWORD)((uintptr_t)curImportDir - (uintptr_t)pModule)); + } + { + /*DWORD offset = GetFileOffsetFromRVA(pModule, curImportDir->OriginalFirstThunk); + if (!offset) + continue; + IMAGE_THUNK_DATA *pThunkData = (IMAGE_THUNK_DATA*)&((uint8_t*)pModule)[offset];*/ + } + ++curImportDir; + } + //Check bounds for null-terminating import descriptor. + if (curImportDir && (uintptr_t)((uint8_t*)curImportDir - (uint8_t*)pModule) > fileSize - sizeof(IMAGE_IMPORT_DESCRIPTOR)) + return -1; + if (patchImportDirOffsets.size() > maxNumDelayedImports) + patchImportDirOffsets.resize(maxNumDelayedImports); + + for (size_t i = 0; i < patchImportDirOffsets.size(); i++) + { + //Move the current import descriptor past the null terminator. + IMAGE_IMPORT_DESCRIPTOR *curImportDir = ((IMAGE_IMPORT_DESCRIPTOR*)&((uint8_t*)pModule)[patchImportDirOffsets[i]]); + IMAGE_IMPORT_DESCRIPTOR tempCopy = *curImportDir; + IMAGE_IMPORT_DESCRIPTOR *lastImportDir; + do + { + lastImportDir = curImportDir; + curImportDir = (IMAGE_IMPORT_DESCRIPTOR*)&((uint8_t*)curImportDir)[sizeof(IMAGE_IMPORT_DESCRIPTOR)]; + memcpy(lastImportDir, curImportDir, sizeof(IMAGE_IMPORT_DESCRIPTOR)); + } while (curImportDir && curImportDir->Name); + memcpy(curImportDir, &tempCopy, sizeof(IMAGE_IMPORT_DESCRIPTOR)); + //Correct the offsets for the other descriptors that were just shifted in the opposite direction. + //-> Note: patchImportDirOffsets[0..i-1] are already moved past the null terminator, and hence are not touched again. + for (size_t k = i+1; k < patchImportDirOffsets.size(); k++) + { + if (patchImportDirOffsets[k] > patchImportDirOffsets[i]) + patchImportDirOffsets[k] -= sizeof(IMAGE_IMPORT_DESCRIPTOR); + } + //Correct the offset for the current descriptor. + patchImportDirOffsets[i] = (DWORD)( ((uintptr_t)curImportDir - (uintptr_t)importDir) + (DWORD)importDirRVA); + } + DWORD importDirRVABack_RVA = SearchExportByName_RawModule(pModule, fileSize, "delayResolveImportRVAs"); + if (!importDirRVABack_RVA) + return -2; + DWORD importDirRVABack_Offset = GetFileOffsetFromRVA(pModule, importDirRVABack_RVA); + if (!importDirRVABack_Offset || importDirRVABack_Offset >= fileSize || importDirRVABack_Offset + patchImportDirOffsets.size() > fileSize) + return -2; + memcpy(&((uint8_t*)pModule)[importDirRVABack_Offset], patchImportDirOffsets.data(), patchImportDirOffsets.size() * sizeof(DWORD)); + return 0; +} + +__declspec(dllexport) bool MakeInstaller(const TCHAR *installerDllPath, InstallerPackageFile *installerData, const TCHAR *outPath, const std::vector &iconData) +{ + std::unique_ptr pDllReader(Create_AssetsReaderFromFile(installerDllPath, true, RWOpenFlags_Immediately)); + if (!pDllReader) + return false; + + pDllReader->Seek(AssetsSeek_End, 0); + QWORD fileSize = 0; + pDllReader->Tell(fileSize); + fileSize = (size_t)fileSize; + pDllReader->Seek(AssetsSeek_Begin, 0); + std::vector fileBuf(fileSize+2); //Two extra bytes as a 'security' null terminator for strings. + pDllReader->Read(fileSize, fileBuf.data()); + pDllReader.reset(); + + if (!ConvertToEXE((IMAGE_DOS_HEADER*)fileBuf.data(), fileSize)) + return false; + if (CreateLateResolveImports((IMAGE_DOS_HEADER*)fileBuf.data(), fileSize) < 0) + return false; + + std::unique_ptr pExeWriter(Create_AssetsWriterToFile(outPath, true, true, RWOpenFlags_Immediately)); + if (!pExeWriter) + return false; + pExeWriter->Write(fileSize, fileBuf.data()); + pExeWriter.reset(); + fileBuf = std::vector(); assert(fileBuf.empty()); + + //Set the icon resource, if requested. + //-> Do it before appending the resource data, as it can change the PE file size. + if (!iconData.empty()) + SetProgramIconResource(outPath, iconData); + + //Reopen the exe file for reading, potentially with the icon resource attached. + std::unique_ptr pExeReader(Create_AssetsReaderFromFile(outPath, true, RWOpenFlags_Immediately)); + if (!pExeReader) + return false; + //Retrieve the PE overlay offset. + size_t overlayOffset = GetPEOverlayOffset(pExeReader.get()); + if (overlayOffset == 0) + return false; + pExeReader.reset(); + + //Now append the installer package as the overlay. + pExeWriter.reset(Create_AssetsWriterToFile(outPath, false, true, RWOpenFlags_Immediately)); + if (!pExeWriter) + return false; + pExeWriter->Seek(AssetsSeek_End, 0); + QWORD endPos = 0; + pExeWriter->Tell(endPos); + //Test the assumption: End of file == overlay start. + if (endPos != overlayOffset) + return false; + bool ret = installerData->Write(endPos, pExeWriter.get()); + pExeWriter.reset(); + + return ret; +} \ No newline at end of file diff --git a/ModInstaller/ModInstaller.cpp b/ModInstaller/ModInstaller.cpp new file mode 100644 index 0000000..47ea262 --- /dev/null +++ b/ModInstaller/ModInstaller.cpp @@ -0,0 +1,739 @@ +// ModInstaller.cpp : Definiert die exportierten Funktionen für die DLL-Anwendung. +// + +#include "stdafx.h" +#include +#include +#include "../libStringConverter/convert.h" +#include "../AssetsTools/AssetsFileReader.h" +#include "../AssetsTools/InternalBundleReplacer.h" +#include "InstallerDataFormat.h" +#include "ModInstaller.h" +#include "InstallDialog.h" +#include +#include + +HMODULE DelayResolveImport(HMODULE hModule, PIMAGE_IMPORT_DESCRIPTOR pDescriptor) +{ + if (!pDescriptor->Name || !pDescriptor->FirstThunk) + return NULL; + HMODULE hNewLibrary = LoadLibraryA((LPCSTR) &((uint8_t*)hModule)[pDescriptor->Name]); + if (!hNewLibrary) + return NULL; + PIMAGE_THUNK_DATA pThunks = (PIMAGE_THUNK_DATA) &((uint8_t*)hModule)[pDescriptor->FirstThunk]; + for (size_t i = 0; pThunks[i].u1.Function != 0; i++) + { + PVOID proc = NULL; + if (IMAGE_SNAP_BY_ORDINAL(pThunks[i].u1.Ordinal)) + { + proc = GetProcAddress(hNewLibrary, (LPCSTR) IMAGE_ORDINAL(pThunks[i].u1.Ordinal)); + if (!proc) + { + FreeLibrary(hNewLibrary); + return NULL; + } + } + else + { + PIMAGE_IMPORT_BY_NAME pImport = (PIMAGE_IMPORT_BY_NAME) &((uint8_t*)hModule)[pThunks[i].u1.AddressOfData]; + proc = GetProcAddress(hNewLibrary, (LPCSTR)&pImport->Name[0]); + if (!proc) + { + FreeLibrary(hNewLibrary); + return NULL; + } + } + DWORD dwOldProt; + VirtualProtect(&pThunks[i].u1.Function, sizeof(ULONG_PTR), PAGE_EXECUTE_READWRITE, &dwOldProt); + pThunks[i].u1.Function = (ULONG_PTR)proc; + VirtualProtect(&pThunks[i].u1.Function, sizeof(ULONG_PTR), dwOldProt, &dwOldProt); + } + return hNewLibrary; +} + +static std::string getModulePath(HINSTANCE hInstance) +{ + std::vector modulePathT; + size_t ownPathLen = MAX_PATH; + while (true) + { + modulePathT.resize(ownPathLen + 1, 0); + SetLastError(0); + DWORD result = GetModuleFileName(hInstance, modulePathT.data(), (DWORD)ownPathLen); + if (result == 0) + { + modulePathT.clear(); + break; + } + else if (result == ownPathLen && GetLastError() == ERROR_INSUFFICIENT_BUFFER) + ownPathLen += MAX_PATH; + else + break; + } + + size_t outLen = 0; + char *modulePath8 = _TCHARToMultiByte(modulePathT.data(), outLen); + std::string modulePath(modulePath8); + _FreeCHAR(modulePath8); + return modulePath; +} + +//Also checks that all section headers are in bounds. +size_t GetPEOverlayOffset(IAssetsReader *pReader) +{ + IMAGE_DOS_HEADER dosHeader = {}; + if (pReader->Read(0, sizeof(IMAGE_DOS_HEADER), &dosHeader) != sizeof(IMAGE_DOS_HEADER)) + return 0; + QWORD fileHeaderPos = dosHeader.e_lfanew + sizeof(IMAGE_NT_SIGNATURE); + IMAGE_FILE_HEADER fileHeader = {}; + if (pReader->Read(fileHeaderPos, sizeof(IMAGE_FILE_HEADER), &fileHeader) != sizeof(IMAGE_FILE_HEADER)) + return 0; + std::vector optHeaderBuf(fileHeader.SizeOfOptionalHeader); + if (pReader->Read(fileHeaderPos + sizeof(IMAGE_FILE_HEADER), fileHeader.SizeOfOptionalHeader, optHeaderBuf.data()) != fileHeader.SizeOfOptionalHeader) + return 0; + DWORD fileAlignment = 1; + if (fileHeader.Machine == 0x014C) //is i386 / 32bit + fileAlignment = ((IMAGE_OPTIONAL_HEADER32*)optHeaderBuf.data())->FileAlignment; + else if (fileHeader.Machine == 0x8664) //is AMD64 / 64bit + fileAlignment = ((IMAGE_OPTIONAL_HEADER64*)optHeaderBuf.data())->FileAlignment; + else + return 0; + if (fileAlignment == 0 || fileAlignment >= 0x7FFFFFFF + || ((fileAlignment + (fileAlignment - 1)) & ~(fileAlignment - 1)) != fileAlignment + || ((2*fileAlignment) & ~(fileAlignment - 1)) != 2*fileAlignment) + return 0; //Rudimentary check that fileAlignment is a power of two (may not be fully precise), just in case. + QWORD sectionHeadersPos = fileHeaderPos + sizeof(IMAGE_FILE_HEADER) + fileHeader.SizeOfOptionalHeader; + std::vector sectionHeaders(fileHeader.NumberOfSections); + if (pReader->Read(sectionHeadersPos, sizeof(IMAGE_SECTION_HEADER) * sectionHeaders.size(), sectionHeaders.data()) + != sizeof(IMAGE_SECTION_HEADER) * sectionHeaders.size()) + return 0; + + DWORD rawEndAddressMax = 0; + for (size_t _i = sectionHeaders.size(); _i > 0; --_i) + { + size_t i = _i - 1; + if (sectionHeaders[i].SizeOfRawData > 0) + { + DWORD downAlignedRawPtr = sectionHeaders[i].PointerToRawData & ~511; + DWORD endOfRaw = sectionHeaders[i].PointerToRawData + sectionHeaders[i].SizeOfRawData; + endOfRaw = (endOfRaw + (fileAlignment - 1)) & ~(fileAlignment - 1); + + DWORD actualSize = (sectionHeaders[i].Misc.VirtualSize != 0) + ? std::min(sectionHeaders[i].Misc.VirtualSize, endOfRaw - downAlignedRawPtr) + : (endOfRaw - downAlignedRawPtr); + actualSize = (actualSize + 511) & ~511; + + DWORD selfEndAddress = downAlignedRawPtr + actualSize; + if (selfEndAddress < sectionHeaders[i].PointerToRawData) + assert(false); + rawEndAddressMax = std::max(rawEndAddressMax, selfEndAddress); + } + } + return rawEndAddressMax; +} + +void RunModInstaller(HINSTANCE hInstance); +extern "C" BOOL WINAPI _CRT_INIT(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved); +extern "C" +{ + //Initialize to 1 instead of 0 to make sure this field is not stripped from the .dll file as uninitialized data + __declspec(dllexport) DWORD delayResolveImportRVAs[32] = {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}; //if one is null or 1, it's not imported + __declspec(dllexport) int APIENTRY _WinMain(); +} +int APIENTRY _WinMain() +{ +#ifdef _DEBUG + MessageBox(NULL, + TEXT("Installer says hello!"), + TEXT("UABE Mod installer"), 0); +#endif + HINSTANCE hInstance = GetModuleHandle(NULL); + for (DWORD i = 0; i < sizeof(delayResolveImportRVAs) / sizeof(DWORD); i++) + { + if (delayResolveImportRVAs[i] > 1) + { + if (!DelayResolveImport(hInstance, (PIMAGE_IMPORT_DESCRIPTOR)( &((uint8_t*)hInstance)[delayResolveImportRVAs[i]] ))) + { + MessageBox(NULL, + TEXT("Unable to find the MSVC++ 2022 redistributable!\nMake sure the 32bit and 64bit versions are installed."), + TEXT("UABE Mod installer"), 16); + return 0; + } + } + } + _CRT_INIT(hInstance, DLL_PROCESS_ATTACH, NULL); + HeapSetInformation(GetProcessHeap(), HeapEnableTerminationOnCorruption, NULL, 0); + + RunModInstaller(hInstance); + + exit(0); + _CRT_INIT(hInstance, DLL_PROCESS_DETACH, NULL); + return 0; +} + +static void RunModInstaller(HINSTANCE hInstance) +{ + std::string selfPath = getModulePath(hInstance); + std::unique_ptr pSelfReader(Create_AssetsReaderFromFile(selfPath.c_str(), true, RWOpenFlags_Immediately)); + QWORD selfFileEnd = 0; + size_t overlayOffset = 0; + if (pSelfReader != nullptr) + { + pSelfReader->Seek(AssetsSeek_End, 0); + pSelfReader->Tell(selfFileEnd); + } + if (pSelfReader == nullptr || (overlayOffset = GetPEOverlayOffset(pSelfReader.get())) == 0 || selfFileEnd <= overlayOffset) + { + MessageBox(NULL, + TEXT("Unable to locate the installer data. Make sure the installer executable is readable, or try moving the installer to another directory."), + TEXT("UABE Mod installer"), 0); + return; + } + std::unique_ptr pInstallDialogs(InitInstallDialogs(hInstance)); + if (pInstallDialogs == nullptr) + { + MessageBox(NULL, + TEXT("Unable to create the installer window. I have no idea why this happened."), + TEXT("UABE Mod installer"), 0); + return; + } + DialogController_Prepare *pPrepDlgController = dynamic_cast(pInstallDialogs->pControllers[InstallDialog_Prepare]); + + pPrepDlgController->SetStatus(InstallDialogPrepareStatus_LoadInstData, InstallPrepareStatus_Active); + + std::unique_ptr pPackageReader( + Create_AssetsReaderFromReaderRange(pSelfReader.get(), overlayOffset, selfFileEnd - overlayOffset)); + //AssetsReaderFromSplitResourceWrapper readerWrapper(readerPar); + + InstallerPackageFile packageFile; + QWORD filePos = 0; + bool readPackage = packageFile.Read(filePos, pPackageReader.get()); + if (!readPackage) + { + pPrepDlgController->SetStatus(InstallDialogPrepareStatus_LoadInstData, InstallPrepareStatus_Error); + MessageBox(NULL, + TEXT("Unable to load the installer data. Either the installer is invalid, or the system is out of memory."), + TEXT("UABE Mod installer"), 0); + CloseDialogThread(pInstallDialogs.get()); + return; + } + SanitizePackageFile(packageFile); + + pPrepDlgController->SetStatus(InstallDialogPrepareStatus_LoadInstData, InstallPrepareStatus_Completed); + if (!ShowInstallDialog(pInstallDialogs.get(), InstallDialog_Introduction)) + { + CloseDialogThread(pInstallDialogs.get()); + return; + } + for (int i = 0; i < InstallDialog_COUNT; i++) + { + DialogControllerTitled *titledController = + dynamic_cast(pInstallDialogs->pControllers[i]); + if (titledController) + titledController->SetModName(packageFile.modName.c_str()); + } + DialogController_Description *pDescController = + dynamic_cast(pInstallDialogs->pControllers[InstallDialog_Description]); + pDescController->SetAuthors(packageFile.modCreators.c_str()); + pDescController->SetDescription(packageFile.modDescription.c_str()); + DialogController_PathSelect *pPathController = + dynamic_cast(pInstallDialogs->pControllers[InstallDialog_PathSelect]); + DialogController_Progress *pProgressController = + dynamic_cast(pInstallDialogs->pControllers[InstallDialog_Progress]); + DialogController_Complete *pCompleteController = + dynamic_cast(pInstallDialogs->pControllers[InstallDialog_Complete]); + { + DWORD curDirLen = GetCurrentDirectory(0, NULL); + //curDirLen includes the null-char but it could also be 0 if there's an error + TCHAR *curDir = new TCHAR[curDirLen + 1]; + GetCurrentDirectory(curDirLen, curDir); + curDir[curDirLen] = 0; + pPathController->SetPath(curDir); + delete[] curDir; + } + pPathController->FillModsTree(&packageFile); + size_t selPathLen = 0; TCHAR *selPath = NULL; + while (true) + { + WaitInstall_Loop: + EInstallDialogs newDialogType = WaitForDialogChanged(pInstallDialogs.get()); + if (newDialogType == (EInstallDialogs)-1) + { + CloseDialogThread(pInstallDialogs.get()); + return; + } + if (newDialogType == InstallDialog_Progress) + { + selPath = pPathController->GetPath(selPathLen); + if (!SetCurrentDirectory(selPath)) + { + DWORD msgResult = MessageBox(pInstallDialogs->hWindow, + TEXT("Unable to switch to the path entered, most likely it's invalid.\n")\ + TEXT("Press OK to ignore and continue, press Cancel to retry."), + TEXT("UABE Mod Installer"), + MB_OKCANCEL | MB_ICONWARNING); + if (msgResult == IDCANCEL) + { + ShowInstallDialog(pInstallDialogs.get(), InstallDialog_PathSelect); + goto WaitInstall_Loop; + } + } + for (size_t i = 0; i < packageFile.affectedAssets.size(); i++) + { + TCHAR *_MultiByteToTCHAR(const char *mb, size_t &len); + void _FreeTCHAR(TCHAR *tc); + size_t pathLenT; + TCHAR *pathT = _MultiByteToTCHAR(packageFile.affectedAssets[i].path.c_str(), pathLenT); + FILE *testFile = _tfopen(pathT, TEXT("rb")); + if (!testFile) + { + TCHAR *sprntTmp = new TCHAR[pathLenT + 164]; + _stprintf_s(sprntTmp, pathLenT + 164, + TEXT("Unable to open %s! Likely it's not in the path entered or it's locked.")\ + TEXT("Press abort to select another path, retry to retry or ignore to continue."), pathT); + DWORD msgResult = MessageBox(pInstallDialogs->hWindow, + sprntTmp, + TEXT("UABE Mod Installer"), + MB_ABORTRETRYIGNORE | MB_ICONWARNING); + delete[] sprntTmp; + if (msgResult == IDABORT) + { + _FreeTCHAR(pathT); + ShowInstallDialog(pInstallDialogs.get(), InstallDialog_PathSelect); + goto WaitInstall_Loop; + } + if (msgResult == IDRETRY) + i--; + } + else + fclose(testFile); + _FreeTCHAR(pathT); + } + break; + } + } + + Install(packageFile, NULL, pProgressController, pCompleteController); + + //FreeAssetsReaderFromSplitResource(readerPar); + while (true) + { + EInstallDialogs newDialogType = WaitForDialogChanged(pInstallDialogs.get()); + if (newDialogType == (EInstallDialogs)-1) + { + CloseDialogThread(pInstallDialogs.get()); + return; + } + } +} + + + +void SanitizePackageFile(InstallerPackageFile &packageFile) +{ + for (size_t i = 0; i < packageFile.affectedAssets.size(); i++) + { + InstallerPackageAssetsDesc &assetsDesc = packageFile.affectedAssets[i]; + for (size_t k = 0; k < assetsDesc.replacers.size(); k++) + { + if (assetsDesc.replacers[k] == NULL) + { + assetsDesc.replacers.erase(assetsDesc.replacers.begin() + k); + k--; + continue; + } + } + } +} + +typedef void(_cdecl *LogCallback)(const char *message); +static void LogToBoth(const char *message, DialogController_Progress *pProgressController, LogCallback log) +{ + if (pProgressController) + pProgressController->AddToLog(message); + if (log) + log(message); +} +static void LogToBoth(const wchar_t *message, DialogController_Progress *pProgressController, LogCallback log) +{ + if (pProgressController) + pProgressController->AddToLog(message); + if (log) + { + size_t mbLen = 0; + char *mbMessage = _WideToMultiByte(message, mbLen); + log(mbMessage); + _FreeCHAR(mbMessage); + } +} +int Install(InstallerPackageFile &packageFile, LogCallback log, + DialogController_Progress *pProgressController, DialogController_Complete *pCompleteController) +{ + bool errorsOccured = false; + double curFileLenProgress = (double)0.99F / (double)packageFile.affectedAssets.size(); + struct InstallFileDesc + { + std::basic_string tOrigFilePath; + bool originalFileExists = false; + }; + std::vector fileDescs(packageFile.affectedAssets.size()); + for (size_t i = 0; i < packageFile.affectedAssets.size(); i++) + { + if (pProgressController && pProgressController->GetCancelled()) + break; + InstallerPackageAssetsDesc &affectedDesc = packageFile.affectedAssets[i]; + + double curFileBeginProgress = (double)i * (double)0.99F / (double)packageFile.affectedAssets.size(); + if (pProgressController) + pProgressController->SetProgress((float)(curFileBeginProgress * 100.0), affectedDesc.path.c_str()); + + bool needsOriginalFile = true; + if (affectedDesc.type == InstallerPackageAssetsType::Resources) + { + if (affectedDesc.replacers.size() == 1 && + dynamic_cast( + reinterpret_cast(affectedDesc.replacers[0].get()) + ) != nullptr) + { + needsOriginalFile = + reinterpret_cast(affectedDesc.replacers[0].get()) + ->RequiresEntryReader(); + } + else + { + errorsOccured = true; + LogToBoth("ERROR: Unexpected resource file replacer!\r\n", pProgressController, log); + continue; + } + } + + size_t pathLenT; + auto pathT = unique_MultiByteToTCHAR(affectedDesc.path.c_str(), pathLenT); + std::unique_ptr pCurReader; + if (needsOriginalFile) + { + LogToBoth("Opening file ", pProgressController, log); + LogToBoth(pathT.get(), pProgressController, log); + pCurReader.reset(Create_AssetsReaderFromFile(pathT.get(), true, RWOpenFlags_Immediately)); + //FILE *curFile = _tfopen(pathT, TEXT("rb")); + if (!pCurReader) + { + errorsOccured = true; + LogToBoth(" [FAILURE]\r\n", pProgressController, log); + continue; + } + LogToBoth(" [SUCCESS]\r\n", pProgressController, log); + fileDescs[i].originalFileExists = true; + } + else + { + std::unique_ptr tmpReader(Create_AssetsReaderFromFile(pathT.get(), true, RWOpenFlags_Immediately)); + fileDescs[i].originalFileExists = (tmpReader != nullptr); + } + bool curFileModded = false; + std::basic_string decompFileName; + switch (affectedDesc.type) + { + case InstallerPackageAssetsType::Assets: + { + LogToBoth("Opening assets", pProgressController, log); + AssetsFile assetsFile = AssetsFile(pCurReader.get()); + if (!assetsFile.VerifyAssetsFile()) + { + errorsOccured = true; + LogToBoth(" [FAILURE]\r\n", pProgressController, log); + continue; + } + LogToBoth(" [SUCCESS]\r\n", pProgressController, log); + if (pProgressController) + pProgressController->SetProgress((float)((curFileBeginProgress + (curFileLenProgress * 0.05F)) * 100.0), affectedDesc.path.c_str()); + std::basic_string modFileName = pathT.get(); + modFileName += TEXT(".mod"); + std::unique_ptr pModWriter(Create_AssetsWriterToFile(modFileName.c_str(), true, true, RWOpenFlags_Immediately)); + //FILE *pModFile = _tfopen(modFileName, TEXT("wb")); + LogToBoth("Modifying and writing assets to ", pProgressController, log); + LogToBoth(modFileName.c_str(), pProgressController, log); + if (!pModWriter) + { + errorsOccured = true; + LogToBoth(" [FAILURE]\r\n", pProgressController, log); + continue; + } + if (pProgressController) + pProgressController->SetProgress((float)((curFileBeginProgress + (curFileLenProgress * 0.1F)) * 100.0), affectedDesc.path.c_str()); + std::vector pReplacers(affectedDesc.replacers.size()); + for (size_t i = 0; i < affectedDesc.replacers.size(); ++i) + pReplacers[i] = reinterpret_cast(affectedDesc.replacers[i].get()); + if (!assetsFile.Write(pModWriter.get(), 0, pReplacers.data(), pReplacers.size(), 0, &packageFile.addedTypes)) + { + errorsOccured = true; + LogToBoth(" [FAILURE]\r\n", pProgressController, log); + continue; + } + pModWriter.reset(); + LogToBoth(" [SUCCESS]\r\n", pProgressController, log); + curFileModded = true; + } + break; + case InstallerPackageAssetsType::Bundle: + { + LogToBoth("Opening bundle", pProgressController, log); + AssetBundleFile bundleFile; + if (!bundleFile.Read(pCurReader.get(), NULL, true)) + { + errorsOccured = true; + LogToBoth(" [FAILURE]\r\n", pProgressController, log); + continue; + } + LogToBoth(" [SUCCESS]\r\n", pProgressController, log); + if (pProgressController) + pProgressController->SetProgress((float)((curFileBeginProgress + (curFileLenProgress * 0.1F)) * 100.0), affectedDesc.path.c_str()); + bool iscompressed = (bundleFile.IsCompressed() || bundleFile.bundleHeader6.fileVersion >= 6 && (bundleFile.bundleHeader6.flags & 0x3F) != 0); + if (iscompressed) + { + decompFileName.assign(pathT.get()); + decompFileName += TEXT(".decomp"); + std::unique_ptr pDecompWriter(Create_AssetsWriterToFile(decompFileName.c_str(), true, true, RWOpenFlags_Immediately)); + //FILE *pDecompFile = _tfopen(decompFileName, TEXT("wb")); + LogToBoth("Decompressing bundle to ", pProgressController, log); + LogToBoth(decompFileName.c_str(), pProgressController, log); + if (!pDecompWriter) + { + errorsOccured = true; + LogToBoth(" [FAILURE]\r\n", pProgressController, log); + bundleFile.Close(); + continue; + } + if (!bundleFile.Unpack(pCurReader.get(), pDecompWriter.get())) + { + errorsOccured = true; + LogToBoth(" [FAILURE]\r\n", pProgressController, log); + bundleFile.Close(); + continue; + } + bundleFile.Close(); + pDecompWriter.reset(); + pCurReader.reset(Create_AssetsReaderFromFile(decompFileName.c_str(), true, RWOpenFlags_Immediately)); + //curFile = _tfopen(decompFileName, TEXT("rb")); + if (!pCurReader || !bundleFile.Read(pCurReader.get(), NULL, false)) + { + errorsOccured = true; + LogToBoth(" [FAILURE]\r\n", pProgressController, log); + continue; + } + LogToBoth(" [SUCCESS]\r\n", pProgressController, log); + if (pProgressController) + pProgressController->SetProgress((float)((curFileBeginProgress + (curFileLenProgress * 0.5F)) * 100.0), affectedDesc.path.c_str()); + } + std::basic_string modFileName = pathT.get(); + modFileName += TEXT(".mod"); + std::unique_ptr pModWriter(Create_AssetsWriterToFile(modFileName.c_str(), true, true, RWOpenFlags_Immediately)); + //FILE *pModFile = _tfopen(modFileName, TEXT("wb")); + LogToBoth("Modifying and writing bundle to ", pProgressController, log); + LogToBoth(modFileName.c_str(), pProgressController, log); + if (!pModWriter) + { + errorsOccured = true; + LogToBoth(" [FAILURE]\r\n", pProgressController, log); + bundleFile.Close(); + continue; + } + std::vector pReplacers(affectedDesc.replacers.size()); + for (size_t i = 0; i < affectedDesc.replacers.size(); ++i) + pReplacers[i] = reinterpret_cast(affectedDesc.replacers[i].get()); + if (!bundleFile.Write(pCurReader.get(), pModWriter.get(), pReplacers.data(), pReplacers.size(), + log, &packageFile.addedTypes)) + { + errorsOccured = true; + LogToBoth(" [FAILURE]\r\n", pProgressController, log); + bundleFile.Close(); + continue; + } + bundleFile.Close(); + pModWriter.reset(); + LogToBoth(" [SUCCESS]\r\n", pProgressController, log); + curFileModded = true; + } + break; + case InstallerPackageAssetsType::Resources: + { + std::basic_string modFileName = pathT.get(); + modFileName += TEXT(".mod"); + std::unique_ptr pModWriter(Create_AssetsWriterToFile(modFileName.c_str(), true, true, RWOpenFlags_Immediately)); + LogToBoth(needsOriginalFile ? "Writing resources to " : "Modifying and writing resources to ", pProgressController, log); + LogToBoth(modFileName.c_str(), pProgressController, log); + if (!pModWriter) + { + errorsOccured = true; + LogToBoth(" [FAILURE]\r\n", pProgressController, log); + continue; + } + auto* pEntryModifier = reinterpret_cast(affectedDesc.replacers[0].get()); + if (needsOriginalFile) + pEntryModifier->Init(nullptr, pCurReader.get(), 0, std::numeric_limits::max()); + QWORD newSize = pEntryModifier->Write(0, pModWriter.get()); + if (pEntryModifier->getSize() != newSize) + { + errorsOccured = true; + LogToBoth(" [INCOMPLETE]\r\n", pProgressController, log); + } + else + { + LogToBoth(" [SUCCESS]\r\n", pProgressController, log); + } + pEntryModifier->Uninit(); + } + break; + } + + if (curFileModded) + fileDescs[i].tOrigFilePath = pathT.get(); + pCurReader.reset(); + if (!decompFileName.empty()) + { + LogToBoth("Deleting decompressed unmodded file", pProgressController, log); + if (DeleteFile(decompFileName.c_str())) + LogToBoth(" [SUCCESS]\r\n", pProgressController, log); + else + LogToBoth(" [FAILURE]\r\n", pProgressController, log); + } + } + int ret = 0; + if (pProgressController) + pProgressController->DisableCancel(); + if (pProgressController && pProgressController->GetCancelled()) + { + for (size_t _i = packageFile.affectedAssets.size(); _i > 0; _i--) + { + size_t i = _i - 1; + double curFileBeginProgress = (double)(i + 1) * (double)0.99F / (double)packageFile.affectedAssets.size(); + pProgressController->SetProgress((float)(curFileBeginProgress * 100.0), ""); + if (fileDescs[i].tOrigFilePath.empty()) + continue; + LogToBoth("Removing modded file of ", pProgressController, log); + LogToBoth(fileDescs[i].tOrigFilePath.c_str(), pProgressController, log); + std::basic_string modFileName = fileDescs[i].tOrigFilePath + TEXT(".mod"); + if (DeleteFile(modFileName.c_str())) + LogToBoth(" [SUCCESS]\r\n", pProgressController, log); + else + LogToBoth(" [FAILURE]\r\n", pProgressController, log); + } + pProgressController->SetProgress(0.0F, ""); + if (pCompleteController) + pCompleteController->SetCompleteText( + TEXT("The mod installation is cancelled. ")\ + TEXT("No changes have been applied to the files. All temporary files have been removed.\r\n")\ + TEXT("")); + ret = MI_CANCEL; + } + else + { + if (pProgressController) + pProgressController->SetProgress(99.0F, ""); + if (errorsOccured) + { + if (pCompleteController) + pCompleteController->SetCompleteText( + TEXT("At least one error occured while installing the mod.\r\n")\ + TEXT("Successfully modified files (if there are any) are saved as .mod files.\r\n")\ + TEXT("Press back and see the log for more details.\r\n")); + ret = MI_INCOMPLETE; + } + else + { + bool allFilesMoved = true; + for (size_t i = 0; i < packageFile.affectedAssets.size(); i++) + { + double curFileProgress = (double)0.99F + + ((double)i * (double)0.01F) / (double)packageFile.affectedAssets.size(); + if (pProgressController) + pProgressController->SetProgress((float)(curFileProgress * 100.0), packageFile.affectedAssets[i].path.c_str()); + if (fileDescs[i].tOrigFilePath.empty()) + continue; + bool doRenameModdedFile = true; + const std::basic_string& filePath = fileDescs[i].tOrigFilePath; + std::vector backupFilePath; + if (fileDescs[i].originalFileExists) + { + LogToBoth("Swapping original and mod of ", pProgressController, log); + LogToBoth(fileDescs[i].tOrigFilePath.c_str(), pProgressController, log); + backupFilePath.assign(filePath.begin(), filePath.end()); + //http://stackoverflow.com/a/6218957 + int backupIndex = -1; + for (int k = 0; k < 10000; k++) + { + backupFilePath.resize(filePath.size()); + TCHAR sprntTmp[32]; + _stprintf_s(sprntTmp, TEXT(".bak%04d"), k); + backupFilePath.insert(backupFilePath.end(), sprntTmp, sprntTmp + _tcslen(sprntTmp) + 1); + if (GetFileAttributes(backupFilePath.data()) == INVALID_FILE_ATTRIBUTES) + { + backupIndex = k; + break; + } + } + backupFilePath.push_back(0); //Should not be necessary. + if (backupIndex == -1) + { + allFilesMoved = false; + doRenameModdedFile = false; + LogToBoth(" [FAILURE]\r\nIt seems like you already have 10000 backups?!?\r\n", pProgressController, log); + } + else + { + if (!MoveFile(filePath.c_str(), backupFilePath.data())) + { + allFilesMoved = false; + doRenameModdedFile = false; + LogToBoth(" [FAILURE]\r\nUnable to rename the original file.\r\n", pProgressController, log); + } + } + } + if (doRenameModdedFile) + { + std::basic_string modFilePath = filePath + TEXT(".mod"); + if (!MoveFile(modFilePath.c_str(), filePath.c_str())) + { + allFilesMoved = false; + LogToBoth(" [FAILURE]\r\nUnable to rename the modded file.\r\n", pProgressController, log); + if (!backupFilePath.empty()) + MoveFile(backupFilePath.data(), filePath.c_str()); + } + else + LogToBoth(" [SUCCESS]\r\n", pProgressController, log); + } + } + if (allFilesMoved) + { + if (pCompleteController) + pCompleteController->SetCompleteText( + TEXT("The mod has been installed successfully and should now be usable. ")\ + TEXT("The old files are preserved with .bak + number file names.\r\n")\ + TEXT("")); + ret = 0; + } + else + { + if (pCompleteController) + pCompleteController->SetCompleteText( + TEXT("The mod is not completely installed. ")\ + TEXT("Not all original files were moved to backup files (.bak + number) and swapped with modded ones. ")\ + TEXT("Some modified files still have a .mod file name.\r\n")\ + TEXT("See the log for more details.\r\n")\ + TEXT("")); + ret = MI_MOVEFILEFAIL; + } + } + if (pProgressController) + pProgressController->SetProgress(100.0F, ""); + } + if (pCompleteController) + pCompleteController->SetAuthors(packageFile.modCreators.c_str()); + if (pProgressController) + pProgressController->EnableContinue(); + return ret; +} +int Install(InstallerPackageFile &packageFile, LogCallback log) +{ + return Install(packageFile, log, NULL, NULL); +} \ No newline at end of file diff --git a/ModInstaller/ModInstaller.h b/ModInstaller/ModInstaller.h new file mode 100644 index 0000000..12e5ecf --- /dev/null +++ b/ModInstaller/ModInstaller.h @@ -0,0 +1,25 @@ +#pragma once +#include "InstallerDataFormat.h" + +#ifndef MODINSTALLER_API +#ifdef MODINSTALLER_EXPORTS +#define MODINSTALLER_API __declspec(dllexport) +#else +#define MODINSTALLER_API __declspec(dllimport) +#endif +#endif + +#define MI_CANCEL -1 +#define MI_INCOMPLETE -2 +#define MI_MOVEFILEFAIL -3 +typedef void(_cdecl *LogCallback)(const char *message); + +MODINSTALLER_API void SanitizePackageFile(InstallerPackageFile &packageFile); +#ifdef MODINSTALLER_EXPORTS +#include "InstallDialog.h" +int Install(InstallerPackageFile &packageFile, LogCallback log = NULL, + DialogController_Progress *pProgressController = NULL, DialogController_Complete *pCompleteController = NULL); +#endif +MODINSTALLER_API int Install(InstallerPackageFile &packageFile, LogCallback log = NULL); + +MODINSTALLER_API size_t GetPEOverlayOffset(IAssetsReader *pReader); diff --git a/ModInstaller/ModInstaller.ico b/ModInstaller/ModInstaller.ico new file mode 100644 index 0000000000000000000000000000000000000000..9cb2ab3ac6097c60951b0f9b38634d7d6ee65838 GIT binary patch literal 15086 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 literal 0 HcmV?d00001 diff --git a/ModInstaller/ModInstaller.manifest b/ModInstaller/ModInstaller.manifest new file mode 100644 index 0000000..b06db18 --- /dev/null +++ b/ModInstaller/ModInstaller.manifest @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + diff --git a/ModInstaller/dllmain.cpp b/ModInstaller/dllmain.cpp new file mode 100644 index 0000000..0535a1a --- /dev/null +++ b/ModInstaller/dllmain.cpp @@ -0,0 +1,20 @@ +// dllmain.cpp : Definiert den Einstiegspunkt für die DLL-Anwendung. +#include "stdafx.h" + +BOOL APIENTRY DllMain( HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + break; + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + diff --git a/ModInstaller/resource.h b/ModInstaller/resource.h new file mode 100644 index 0000000000000000000000000000000000000000..5d671e87f46d4b79f453887bc2b35c3f112877c3 GIT binary patch literal 4470 zcmcJTTW{Jx5QXQtQvZXcK2|E1-X9%9NmOGHn5cO|4G9%h6eI|}sOn#Dd(IA)DR{?g zRh4CVy)54zW@lz+`TeUUE%|{flab7&Cw&>?&Ri>#L@w|)m7lmC%Oi|!NhCvA$wD@= zmM!k3JfQCqeOuYdN4djWB2RHe@=0#x9`^?Q4|v|7Z-t%(M(yx+i}xjZ?$CO6FvAQi z9epj|F{XzxB(Fi-V&n#dLJyx$9E&8*7D06n_Z)YE-<3%ReNC>8w5982@R5h$a*hnv z$t9K)S6?Qzv)JUGTZ0S78_A%NZjzsSHIqzR46(jcn&S?&2+1!@a_THize$|GG$Fq< z$z#}*J{h}bC^v%T+VaXIbM10axsEgF&hZ=QD|K^_{MsZlil!h=KpY2iRr4E@Olyq5 zq&)_hWsddiJ3pMEsZI1lg&(M=c*Xmx$~xtv@6@rRoo%_xHN`Bv!MD( z$EX@;6Wz@QE?1RR)UgFl-&J>ATqf}FiM#`y_0B1!x<}dm%7MBI|5nY6{xG{J+xG9o z?F`H+)%twB%C`Ocxc*}e7~=2Bw*7m0w2It+1_8f;>@IH0zm?s*R#UbeJL=cXv!Sx> z*eOp3+}kwQ`quBTU)lCPKb0$Z9qaB4)kJ;GQ}C5-@5N;`uhpQ=Q4svkW^Bz@|BoFO zll(QRTk{G1P*1rTPLki;*_P+jCC;BaoIrWj{o>A|JM?bxw#2D)gC|d(U-4YawR}c< zfw5cX*UD25UtzrRBJ5#ftReFUPm(-;(;Am(vG(lkV}jqfH@?IC%u14#QwpzOO>%{N zeTJSC?W)M){?EC~J#W6R&qpq6a&6{N!gFm^6-kR#=g2Cu=A9vE{9nQ1M%Xb}oBOJB lAH6uqr8RV2>2T$0Nv?&hy6~bR*YIDD8~huw+*|+e_dgb``BDG? literal 0 HcmV?d00001 diff --git a/ModInstaller/stdafx.cpp b/ModInstaller/stdafx.cpp new file mode 100644 index 0000000..fd4f341 --- /dev/null +++ b/ModInstaller/stdafx.cpp @@ -0,0 +1 @@ +#include "stdafx.h" diff --git a/ModInstaller/stdafx.h b/ModInstaller/stdafx.h new file mode 100644 index 0000000..d258c0a --- /dev/null +++ b/ModInstaller/stdafx.h @@ -0,0 +1,12 @@ +#pragma once + +#include "targetver.h" + +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#include +#include +#include +#include + diff --git a/ModInstaller/targetver.h b/ModInstaller/targetver.h new file mode 100644 index 0000000..05ba7d4 --- /dev/null +++ b/ModInstaller/targetver.h @@ -0,0 +1,3 @@ +#pragma once + +#include diff --git a/Plugins/AudioClip/AudioClip.cpp b/Plugins/AudioClip/AudioClip.cpp new file mode 100644 index 0000000..70a13e4 --- /dev/null +++ b/Plugins/AudioClip/AudioClip.cpp @@ -0,0 +1,494 @@ +#include "../UABE_Generic/PluginManager.h" +#include "../UABE_Generic/FileContextInfo.h" +#include "../UABE_Generic/AppContext.h" +//#include "../AssetsTools/TextureFileFormat.h" +#include "../AssetsTools/AssetsReplacer.h" +#include "../AssetsTools/AssetsFileTable.h" +#include "../AssetsTools/ResourceManagerFile.h" + +#include + +#include +#include + +#include "wavfile.h" + +enum AudioType_U4 +{ + AudioType_UNKNOWN=0, + AudioType_ACC=1, + AudioType_AIFF=2, + AudioType_IT=10, + AudioType_MOD=12, + AudioType_MPEG=13, //.mp3 + AudioType_OGGVORBIS=14, //.ogg + AudioType_S3M=0x11, + AudioType_WAV=0x14, + AudioType_XM=0x15, + AudioType_XMA=0x16, + AudioType_VAG=0x17, + AudioType_AUDIOQUEUE=0x18, + AudioType_MAX +}; +uint8_t AudioTypeIndexTable[AudioType_MAX] = { + 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 3, 0, 4, 5, 6, 0, 0, 7, 0, 0, 8, 9, 10, 11, 12 +}; +const char *AudioTypeExtensions[13] = { + "", //0 + ".acc", + ".aiff", + "", + ".mod", + ".mp3", + ".ogg", //6 + ".s3m", + ".wav", + ".xm", + ".xma", + ".vag", + "" +}; +const wchar_t *AudioTypeExtensionsW[13] = { + L"", //0 + L".acc", + L".aiff", + L"", + L".mod", + L".mp3", + L".ogg", //6 + L".s3m", + L".wav", + L".xm", + L".xma", + L".vag", + L"" +}; +const wchar_t *AudioTypeFileFilters[13] = { + L"*.*|Unknown file format", //0 + L"*.acc|Advanced Audio Coding file", + L"*.aiff|Audio Interchange File", + L"*.*|Impulse tracker audio file", + L"*.mod|Fasttracker .MOD file", + L"*.mp3:*.mp2|MPEG audio file", + L"*.ogg|Ogg vorbis container file", //6 + L"*.s3m|ScreamTracker 3 file", + L"*.wav|Wave file", + L"*.xm|FastTracker 2 XM file", + L"*.xma|Xbox360 XMA file", + L"*.vag|PlayStation ADPCM file", + L"*.*|AudioQueue file" +}; +enum AudioCompressionFormat_U4 +{ + AudioCompressionFormat_PCM, //header-less + AudioCompressionFormat_Vorbis, //.ogg + AudioCompressionFormat_ADPCM, //headerless + AudioCompressionFormat_MP3, //.mp3 + AudioCompressionFormat_VAG, //.vag + AudioCompressionFormat_HEVAG, //no idea + AudioCompressionFormat_XMA, //no idea + AudioCompressionFormat_AAC //.3gp or .aac +}; +FMOD_RESULT _stdcall SoundEndCallback(FMOD_CHANNELCONTROL *channelcontrol, FMOD_CHANNELCONTROL_TYPE controltype, FMOD_CHANNELCONTROL_CALLBACK_TYPE callbacktype, void *commanddata1, void *commanddata2) +{ + if (callbacktype == FMOD_CHANNELCONTROL_CALLBACK_END) + { + return FMOD_ERR_FILE_EOF; + } + return FMOD_OK; +} + + +class AudioClipExportTask : public AssetExportTask +{ + AppContext& appContext; + TypeTemplateCache templateCache; + + static void closeFMODSystem(FMOD::System* pSystem) + { + pSystem->release(); + } + std::unique_ptr pFMODSystem; +public: + AudioClipExportTask(AppContext& appContext, + std::vector _assets, std::string _baseDir, + bool stopOnError = false) + + : AssetExportTask(std::move(_assets), "Export AudioClip data", "", std::move(_baseDir), stopOnError), + appContext(appContext), + pFMODSystem(nullptr, &closeFMODSystem) + { + FMOD::System* pFMODSystem = NULL; + //createFunc((FMOD_SYSTEM**)&pFMODSystem) + if (FMOD::System_Create(&pFMODSystem) == FMOD_OK) + { + this->pFMODSystem.reset(pFMODSystem); + pFMODSystem->setOutput(FMOD_OUTPUTTYPE_NOSOUND_NRT); + pFMODSystem->init(16, FMOD_INIT_NORMAL, NULL); + } + } + + bool exportAsset(AssetUtilDesc& desc, std::string path, std::optional> progressManager) + { + if (desc.asset.pFile == nullptr) + throw AssetUtilError("Unable to find the target .assets file."); + + IAssetsReader_ptr pAssetReader = desc.asset.makeReader(); + if (pAssetReader == nullptr) + throw AssetUtilError("Unable to read the asset."); + QWORD assetSize = 0; + if (!pAssetReader->Seek(AssetsSeek_End, 0) || !pAssetReader->Tell(assetSize) || !pAssetReader->Seek(AssetsSeek_Begin, 0)) + throw AssetUtilError("Unable to read the asset."); + + AssetTypeTemplateField& templateBase = templateCache.getTemplateField(appContext, desc.asset, + [](AssetTypeTemplateField& templateBase) { + for (size_t i = 0; i < templateBase.children.size(); i++) + { + if (templateBase.children[i].children.size() > 0 && templateBase.children[i].name == "m_AudioData") + { + templateBase.children[i].children[0].type = "TypelessData"; //Improve deserialization performance for U4 audio clips + break; + } + } + } + ); + AssetTypeTemplateField* pTemplateBase = &templateBase; + + + AssetTypeInstance assetInstance(1, &pTemplateBase, assetSize, pAssetReader.get(), desc.asset.isBigEndian()); + AssetTypeValueField* pBaseField = assetInstance.GetBaseField(); + if (pBaseField == nullptr || pBaseField->IsDummy()) + throw AssetUtilError("Unable to deserialize the asset."); + + + AssetTypeValueField* formatField = pBaseField->Get("m_Format"); + AssetTypeValueField* typeField = pBaseField->Get("m_Type"); + AssetTypeValueField* nameField = pBaseField->Get("m_Name"); + AssetTypeValueField* streamField = pBaseField->Get("m_Stream"); + AssetTypeValueField* dataField = pBaseField->Get("m_AudioData")->Get(0U); + + if (!nameField->IsDummy() && !formatField->IsDummy() && !nameField->IsDummy() && !dataField->IsDummy()) //Unity 3/4 + { + int streamType = 1; + if (!streamField->IsDummy()) + streamType = streamField->GetValue()->AsInt(); + std::shared_ptr pStreamReader = nullptr; + QWORD streamSize = 0; + if (streamType == 2 && !desc.assetsFileName.empty()) + { + //TODO: Make sure this actually works (is based on legacy plugin code, API has been revamped in the meantime). + // It appears like the data Array size is set to the resource size, but the in-asset Array just contains the 4 bytes size? + // If so, this would most certainly cause trouble with deserialization. + std::string streamDataFileName = desc.assetsFileName + ".resS"; + + if (dataField->GetValue()->AsByteArray()->size < 4) + throw AssetUtilError("The streamed data file is invalid."); + + QWORD streamFileOffset = *(uint32_t*)dataField->GetValue()->AsByteArray()->data; + QWORD streamSize = dataField->GetValue()->AsByteArray()->size; + + std::shared_ptr streamResourcesContextInfo; + streamResourcesContextInfo = FindResourcesFile(appContext, streamDataFileName, desc.asset, progressManager); + //Non-null guaranteed by FindResourcesFile (AssetUtilError thrown otherwise). + + pStreamReader = streamResourcesContextInfo->getResource(streamResourcesContextInfo, + streamFileOffset, + streamSize); + if (pStreamReader == nullptr) + throw AssetUtilError("Unable to locate the audio resource."); + } + + char* audioClipName = nameField->GetValue()->AsString(); + unsigned int audioClipFormat = (unsigned int)formatField->GetValue()->AsInt(); + unsigned int audioClipType = (unsigned int)typeField->GetValue()->AsInt(); + if (audioClipType >= AudioType_MAX) + audioClipType = 0; + const char* audioClipExtension = AudioTypeExtensions[AudioTypeIndexTable[audioClipType]]; + std::string fullOutputPath = path + audioClipExtension; + + std::unique_ptr pWriter(Create_AssetsWriterToFile(fullOutputPath.c_str(), true, true, RWOpenFlags_Immediately)); + if (pWriter == nullptr) + throw AssetUtilError("Unable to create the output file."); + if (pStreamReader) + { + std::unique_ptr copier(MakeAssetModifierFromReader(0, 0, -1, 0xFFFF, pStreamReader.get(), streamSize)); + if (copier->Write(0, pWriter.get()) != streamSize) + throw AssetUtilError("Unable to copy data from the audio resource to the output file."); + } + else + { + QWORD size = dataField->GetValue()->AsByteArray()->size; + if (pWriter->Write(size, dataField->GetValue()->AsByteArray()->data) != size) + throw AssetUtilError("Unable to write data to the output file."); + } + return true; + } + else //Unity 5 and newer + { + AssetTypeValueField* bitsPerSampleField = pBaseField->Get("m_BitsPerSample"); + AssetTypeValueField* frequencyField = pBaseField->Get("m_Frequency"); + AssetTypeValueField* channelsField = pBaseField->Get("m_Channels"); + AssetTypeValueField* resource = pBaseField->Get("m_Resource"); + if (bitsPerSampleField->IsDummy() || nameField->IsDummy() || resource->IsDummy() + || frequencyField->IsDummy() || channelsField->IsDummy()) + { + throw AssetUtilError("Unexpected AudioClip asset format."); + } + AssetTypeValueField* sourceField = resource->Get("m_Source"); + AssetTypeValueField* offsetField = resource->Get("m_Offset"); + AssetTypeValueField* sizeField = resource->Get("m_Size"); + if ((sourceField->IsDummy() || offsetField->IsDummy() || sizeField->IsDummy()) + || stricmp(sourceField->GetType().c_str(), "string") + || (stricmp(offsetField->GetType().c_str(), "UInt64") && stricmp(offsetField->GetType().c_str(), "FileSize")) + || stricmp(sizeField->GetType().c_str(), "UInt64") + || stricmp(frequencyField->GetType().c_str(), "int") || stricmp(channelsField->GetType().c_str(), "int")) + { + throw AssetUtilError("Unexpected AudioClip asset format."); + } + + char* sourceFileName = sourceField->GetValue()->AsString(); + QWORD soundOffset = offsetField->GetValue()->AsUInt64(); + QWORD soundSize = sizeField->GetValue()->AsUInt64(); + if (soundSize > std::numeric_limits::max()) + throw AssetUtilError("Resource size out of range."); + + std::shared_ptr streamResourcesContextInfo; + streamResourcesContextInfo = FindResourcesFile(appContext, sourceFileName, desc.asset, progressManager); + //Non-null guaranteed by FindResourcesFile (AssetUtilError thrown otherwise). + + std::shared_ptr pStreamReader = streamResourcesContextInfo->getResource(streamResourcesContextInfo, + soundOffset, + soundSize); + if (pStreamReader == nullptr) + throw AssetUtilError("Unable to locate the audio resource."); + + //May consider alternatives to the proprietary FMOD API, e.g. vgmstream's parser + //-> https://github.com/vgmstream/vgmstream/blob/master/src/meta/fsb5.c + + std::unique_ptr _pFMODSystem_raii(nullptr, closeFMODSystem); + + FMOD::System* pFMODSystem = this->pFMODSystem.get(); + if (!pFMODSystem) + { + if (FMOD::System_Create(&pFMODSystem) != FMOD_OK) + throw AssetUtilError("Unable to initialize FMOD."); + _pFMODSystem_raii.reset(pFMODSystem); + //pFMODSystem->setOutput(FMOD_OUTPUTTYPE_WAVWRITER_NRT); + //pFMODSystem->init(16, FMOD_INIT_NORMAL, cOutFilePath); + pFMODSystem->setOutput(FMOD_OUTPUTTYPE_NOSOUND_NRT); + pFMODSystem->init(16, FMOD_INIT_NORMAL, NULL); + } + void* rawBuffer = NULL; unsigned int rawBufferLen = 0; + std::vector soundBuffer(soundSize); + if (pStreamReader->Read(0, (QWORD)soundSize, soundBuffer.data()) != soundSize) + throw AssetUtilError("Unable to read data from the audio resource."); + + FMOD::Sound* pSound = NULL; + FMOD_CREATESOUNDEXINFO soundLenInfo; + memset(&soundLenInfo, 0, sizeof(FMOD_CREATESOUNDEXINFO)); + soundLenInfo.cbsize = sizeof(FMOD_CREATESOUNDEXINFO); + soundLenInfo.length = (unsigned int)soundSize; + soundLenInfo.format = (FMOD_SOUND_FORMAT)0; + soundLenInfo.suggestedsoundtype = (FMOD_SOUND_TYPE)8; + if (pFMODSystem->createSound((char*)soundBuffer.data(), FMOD_OPENMEMORY, &soundLenInfo, &pSound) == FMOD_OK) + { + auto closeFMODSound = [](FMOD::Sound* pSound) + { + pSound->release(); + }; + std::unique_ptr _pSound_raii(pSound, closeFMODSound); + + pSound->setMode(FMOD_LOOP_OFF); + pSound->setLoopCount(-1); + FMOD::Sound* pSubSound = NULL; + if (pSound->getSubSound(0, &pSubSound) != FMOD_OK) + throw AssetUtilError("Unable to get the sub sound."); + + pSubSound->setMode(FMOD_LOOP_OFF); + pSubSound->setLoopCount(-1); + //uint32_t bitsPerSample = (uint32_t)bitsPerSampleField->GetValue()->AsInt(); + //FMOD_SOUND_FORMAT rawFormat; + //switch (bitsPerSample) + //{ + //... rawFormat = ... + //} + unsigned int sampleByteCount = 0; + pSubSound->getLength(&sampleByteCount, FMOD_TIMEUNIT_PCMBYTES); + + std::string fullOutputPath = path + ".wav"; + std::unique_ptr pFileWriter(Create_AssetsWriterToFile(fullOutputPath.c_str(), true, true, RWOpenFlags_Immediately)); + if (pFileWriter == nullptr) + throw AssetUtilError("Unable to open the output file."); + if (!wavfile_open(pFileWriter.get(), WAVFILE_SOUND_FORMAT::PCM_16bit, + (uint32_t)frequencyField->GetValue()->AsInt(), + (uint32_t)channelsField->GetValue()->AsInt())) + throw AssetUtilError("Unable to open the output file."); + + void* pData = NULL; unsigned int dataLen = 0; + void* pData2; unsigned int dataLen2; + pSubSound->lock(0, sampleByteCount, &pData, &pData2, &dataLen, &dataLen2); + if (pData && dataLen) + { + wavfile_write(pFileWriter.get(), pData, dataLen); + } + pSubSound->unlock(pData, pData2, dataLen, dataLen2); + wavfile_close(pFileWriter.get()); + } + else if ((soundSize > 12) && !memcmp(&soundBuffer.data()[4], "ftypmp42", 8)) + { + std::string fullOutputPath = path + ".m4a"; + std::unique_ptr pFileWriter(Create_AssetsWriterToFile(fullOutputPath.c_str(), true, true, RWOpenFlags_Immediately)); + if (pFileWriter == nullptr) + throw AssetUtilError("Unable to open the output file."); + if (pFileWriter->Write(soundSize, soundBuffer.data()) != soundSize) + throw AssetUtilError("Unable to write data to the output file."); + } + else + { + throw AssetUtilError("Unable to recognize the audio data format."); + } + return true; + } + throw AssetUtilError("Unrecognized AudioClip asset format."); + } +}; + +static bool SupportsElements(AppContext& appContext, std::vector& elements) +{ + auto checkAudioClass = [&appContext](AssetsFileContextInfo* pFile, int32_t classID) + { + AssetTypeTemplateField templateBase; + if (!pFile->MakeTemplateField(&templateBase, appContext, classID)) + return false; + if (templateBase.SearchChild("m_Name") + && templateBase.SearchChild("m_Format") + && templateBase.SearchChild("m_Type") + && templateBase.SearchChild("m_Stream") + && templateBase.SearchChild("m_Type") + && templateBase.SearchChild("m_AudioData") + && templateBase.SearchChild("m_AudioData")->SearchChild("Array")) + return true; //Unity 3/4 + if (templateBase.SearchChild("m_Name") + && templateBase.SearchChild("m_BitsPerSample") + && templateBase.SearchChild("m_Frequency") + && templateBase.SearchChild("m_Channels") + && templateBase.SearchChild("m_Resource")) + { + AssetTypeTemplateField& frequencyField = *templateBase.SearchChild("m_Frequency"); + AssetTypeTemplateField& channelsField = *templateBase.SearchChild("m_Channels"); + AssetTypeTemplateField& resourceField = *templateBase.SearchChild("m_Resource"); + AssetTypeTemplateField* pSourceField = resourceField.SearchChild("m_Source"); + AssetTypeTemplateField* pOffsetField = resourceField.SearchChild("m_Offset"); + AssetTypeTemplateField* pSizeField = resourceField.SearchChild("m_Size"); + if (pSourceField + && !stricmp(pSourceField->type.c_str(), "string") + && pOffsetField + && (!stricmp(pOffsetField->type.c_str(), "UInt64") || !stricmp(pOffsetField->type.c_str(), "FileSize")) + && pSizeField + && !stricmp(pSizeField->type.c_str(), "UInt64") + && !stricmp(frequencyField.type.c_str(), "int") + && !stricmp(channelsField.type.c_str(), "int")) + return true; //Unity 5..2021+ + } + return false; + }; + + std::unordered_map audioClassIDs; + for (size_t i = 0; i < elements.size(); i++) + { + if (elements[i].asset.pFile == nullptr) + return false; + AssetsFileContextInfo* pFile = elements[i].asset.pFile.get(); + auto classIDsit = audioClassIDs.find(pFile); + int32_t audioClipClassID = -1; + if (classIDsit == audioClassIDs.end()) + { + audioClipClassID = pFile->GetClassByName("AudioClip"); + audioClassIDs[pFile] = audioClipClassID; + if (!checkAudioClass(pFile, audioClipClassID)) + return false; + } + else + audioClipClassID = classIDsit->second; + if (audioClipClassID == -1) + return false; + int32_t classId = elements[i].asset.getClassID(); + if (classId != audioClipClassID) + return false; + } + return true; +} +class AudioClipExportProvider : public IAssetOptionProviderGeneric +{ +public: + class Runner : public IOptionRunner + { + AppContext& appContext; + std::vector selection; + public: + Runner(AppContext& appContext, std::vector _selection) + : appContext(appContext), selection(std::move(_selection)) + {} + void operator()() + { + std::string exportLocation = appContext.QueryAssetExportLocation(selection, "", "*|Varying audio format:"); + if (!exportLocation.empty()) + { + auto pTask = std::make_shared(appContext, std::move(selection), std::move(exportLocation)); + appContext.taskManager.enqueue(pTask); + } + } + }; + EAssetOptionType getType() + { + return EAssetOptionType::Export; + } + std::unique_ptr prepareForSelection( + class AppContext& appContext, + std::vector selection, + std::string& optionName) + { + if (!SupportsElements(appContext, selection)) + return nullptr; + optionName = "Export audio"; + return std::make_unique(appContext, std::move(selection)); + } +}; + +class AudioClipPluginDesc : public IPluginDesc +{ + std::vector> pProviders; +public: + AudioClipPluginDesc() + { + pProviders = { std::make_shared() }; + } + std::string getName() + { + return "AudioClip"; + } + std::string getAuthor() + { + return ""; + } + std::string getDescriptionText() + { + return "Export AudioClip assets."; + } + //The IPluginDesc object should keep a reference to the returned options, as the caller may keep only std::weak_ptrs. + //Note: May be called early, e.g. before program UI initialization. + std::vector> getPluginOptions(class AppContext& appContext) + { + return pProviders; + } +}; + +IPluginDesc* GetUABEPluginDesc1(size_t sizeof_AppContext, size_t sizeof_BundleFileContextInfo) +{ + if (sizeof_AppContext != sizeof(AppContext) || sizeof_BundleFileContextInfo != sizeof(BundleFileContextInfo)) + { + assert(false); + return nullptr; + } + return new AudioClipPluginDesc(); +} diff --git a/Plugins/AudioClip/AudioClip.def b/Plugins/AudioClip/AudioClip.def new file mode 100644 index 0000000..610ef43 --- /dev/null +++ b/Plugins/AudioClip/AudioClip.def @@ -0,0 +1,3 @@ +LIBRARY AudioClip +EXPORTS + GetUABEPluginDesc1 diff --git a/Plugins/AudioClip/CMakeLists.txt b/Plugins/AudioClip/CMakeLists.txt new file mode 100644 index 0000000..08c77ae --- /dev/null +++ b/Plugins/AudioClip/CMakeLists.txt @@ -0,0 +1,17 @@ +find_package(fmod REQUIRED) + +add_library (AudioClip SHARED "AudioClip.cpp" "wavfile.cpp" AudioClip.def) +target_include_directories (AudioClip PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_include_directories (AudioClip PRIVATE ${FMOD_INCLUDE_DIR}) + +set_target_properties(AudioClip PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/Plugins" + SUFFIX ".bep") +target_link_libraries(AudioClip PRIVATE UABE_Generic AssetsTools libStringConverter ${FMOD_LIBRARIES}) + +set(AudioClip_MODULE_DEPENDENCY_PATHS ${FMOD_MODULES}) + +file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/bin") +foreach(depfile ${AudioClip_MODULE_DEPENDENCY_PATHS}) + configure_file("${depfile}" "${CMAKE_BINARY_DIR}/bin" COPYONLY) +endforeach() diff --git a/Plugins/AudioClip/wavfile.cpp b/Plugins/AudioClip/wavfile.cpp new file mode 100644 index 0000000..1c8e2fb --- /dev/null +++ b/Plugins/AudioClip/wavfile.cpp @@ -0,0 +1,115 @@ +/* +A simple sound library for CSE 20211 by Douglas Thain (dthain@nd.edu). +This work is made available under the Creative Commons Attribution license. +https://creativecommons.org/licenses/by/4.0/ + +For course assignments, you should not change this file. +For complete documentation, see: +http://www.nd.edu/~dthain/courses/cse20211/fall2013/wavfile +*/ +//Changes for integration in the UABE AudioClip plugin: +// - Use IAssetsWriter instead of FILE* as an abstraction. +// - Allow different formats based on WAVFILE_SOUND_FORMAT. + +#include "wavfile.h" + +#include +#include +#include + +#pragma pack(push, 1) +struct wavfile_header { + char riff_tag[4]; + uint32_t riff_length; + char wave_tag[4]; + char fmt_tag[4]; + uint32_t fmt_length; + uint16_t audio_format; + uint16_t num_channels; + uint32_t sample_rate; + uint32_t byte_rate; + uint16_t block_align; + uint16_t bits_per_sample; + char data_tag[4]; + uint32_t data_length; +}; +#pragma pack(pop) +enum EnumWaveFormats +{ + WaveFmt_PCM=1, + WaveFmt_FLOAT=3, + WaveFmt_IMAADPCM=17, + WaveFMT_MP1_2=80, + WaveFMT_MP3=85, +}; + +IAssetsWriter *wavfile_open( IAssetsWriter *file, WAVFILE_SOUND_FORMAT format, uint32_t sampleRate, uint32_t channelCount ) +{ + struct wavfile_header header; + int bits_per_sample; + switch (format) + { + case WAVFILE_SOUND_FORMAT::PCM_8bit: + bits_per_sample = 8; + header.audio_format = WaveFmt_PCM; + break; + case WAVFILE_SOUND_FORMAT::PCM_16bit: + bits_per_sample = 16; + header.audio_format = WaveFmt_PCM; + break; + case WAVFILE_SOUND_FORMAT::PCM_24bit: + bits_per_sample = 24; + header.audio_format = WaveFmt_PCM; + break; + case WAVFILE_SOUND_FORMAT::PCM_32bit: + bits_per_sample = 32; + header.audio_format = WaveFmt_PCM; + break; + default: + return NULL; + } + + + strncpy(header.riff_tag,"RIFF",4); + strncpy(header.wave_tag,"WAVE",4); + strncpy(header.fmt_tag,"fmt ",4); + strncpy(header.data_tag,"data",4); + + header.riff_length = 0; + header.fmt_length = 16; + header.num_channels = (uint16_t)channelCount; + header.sample_rate = sampleRate; + header.block_align = channelCount*((bits_per_sample+7)/8); + header.byte_rate = sampleRate*header.block_align; + header.bits_per_sample = bits_per_sample; + header.data_length = 0; + + file->Write(0x2C, &header); + file->Flush(); + + return file; +} + +void wavfile_write( IAssetsWriter *file, void *data, uint32_t byteLen ) +{ + file->Write(byteLen, data); +} + +void wavfile_close( IAssetsWriter *file ) +{ + QWORD file_length = 0; + file->Tell(file_length); + if (file_length < 0x7FFFFFFF) + { + uint32_t data_length = file_length - sizeof(struct wavfile_header); + if (data_length < file_length) + { + file->Seek(AssetsSeek_Begin, sizeof(struct wavfile_header) - sizeof(int)); + file->Write(sizeof(data_length), &data_length); + + uint32_t riff_length = file_length - 8; + file->Seek(AssetsSeek_Begin, 4); + file->Write(sizeof(riff_length), &riff_length); + } + } +} diff --git a/Plugins/AudioClip/wavfile.h b/Plugins/AudioClip/wavfile.h new file mode 100644 index 0000000..35e9ee6 --- /dev/null +++ b/Plugins/AudioClip/wavfile.h @@ -0,0 +1,35 @@ +/* +A simple sound library for CSE 20211 by Douglas Thain. +This work is made available under the Creative Commons Attribution license. +https://creativecommons.org/licenses/by/4.0/ + +For course assignments, you should not change this file. +For complete documentation, see: +http://www.nd.edu/~dthain/courses/cse20211/fall2013/wavfile +*/ +//Changes for integration in the UABE AudioClip plugin: +// - Use IAssetsWriter instead of FILE* as an abstraction. +// - Allow different formats based on FMOD_SOUND_FORMAT. + +#ifndef WAVFILE_H +#define WAVFILE_H + +#include +#include +#include + +enum class WAVFILE_SOUND_FORMAT +{ + PCM_8bit, + PCM_16bit, + PCM_24bit, + PCM_32bit +}; + +IAssetsWriter *wavfile_open( IAssetsWriter *file, WAVFILE_SOUND_FORMAT format, uint32_t sampleRate, uint32_t channelCount ); +void wavfile_write( IAssetsWriter *file, void *data, uint32_t byteLen ); +void wavfile_close( IAssetsWriter *file ); + +#define WAVFILE_SAMPLES_PER_SECOND 44100 + +#endif \ No newline at end of file diff --git a/Plugins/CMakeLists.txt b/Plugins/CMakeLists.txt new file mode 100644 index 0000000..2c9df98 --- /dev/null +++ b/Plugins/CMakeLists.txt @@ -0,0 +1,5 @@ +add_subdirectory (Utility) +add_subdirectory (TextAsset) +add_subdirectory (AudioClip) +add_subdirectory (Texture) +add_subdirectory (Mesh) diff --git a/Plugins/Mesh/AssimpMesh.cpp b/Plugins/Mesh/AssimpMesh.cpp new file mode 100644 index 0000000..c451d76 --- /dev/null +++ b/Plugins/Mesh/AssimpMesh.cpp @@ -0,0 +1,438 @@ +#pragma once +#include "AssimpMesh.h" +#include //assimp + +bool AddMeshToScene(aiScene &scene, Mesh &mesh, std::vector &boneNames, bool unity5OrNewer) +{ + if (!mesh.wasAbleToRead) return false; + if (mesh.m_VertexData.m_Streams.size() == 0) return false; + if (mesh.m_VertexData.m_Channels.size() < 6) return false; + if (mesh.m_VertexData.m_Channels[0].dimension != 3) return false; + + //Unity 4.x sees a ColorRGBA as one DWORD, while Unity 5.x+ sees it as four UNORM8 (equivalent to raw color channel value). + if (unity5OrNewer && mesh.m_VertexData.m_Channels[2].format == 2 && mesh.m_VertexData.m_Channels[2].dimension == 4) + { + mesh.m_VertexData.m_Channels[2].dimension = 1; + mesh.m_VertexData.m_Channels[2].format = 11; //UINT32 + } + + if (scene.mRootNode == nullptr) + scene.mRootNode = new aiNode("Root"); + + void* pVertexDataEnd = &((uint8_t*)mesh.m_VertexData.m_DataSize)[mesh.m_VertexData.dataByteCount]; + + aiMesh **pNewMeshList = new aiMesh*[scene.mNumMeshes + mesh.m_SubMeshes.size()](); + memcpy(pNewMeshList, scene.mMeshes, scene.mNumMeshes * sizeof(aiMesh*)); + delete[] scene.mMeshes; + scene.mMeshes = pNewMeshList; + + aiMaterial **pNewMatList = new aiMaterial*[scene.mNumMeshes + mesh.m_SubMeshes.size()](); + memcpy(pNewMatList, scene.mMaterials, scene.mNumMaterials * sizeof(aiMaterial*)); + delete[] scene.mMaterials; + scene.mMaterials = pNewMatList; + + bool blendShapeValid = mesh.m_Shapes.shapes.size() > 0 + && mesh.m_Shapes.shapes.size() == mesh.m_Shapes.channels.size() + && mesh.m_Shapes.shapes.size() == mesh.m_Shapes.fullWeights.size() + && mesh.m_Shapes.vertices.size() == (mesh.m_Shapes.shapes.size() * mesh.m_Shapes.shapes[0].vertexCount); + for (size_t i = 1; i < mesh.m_Shapes.shapes.size(); i++) + { + if (mesh.m_Shapes.shapes[i].vertexCount != mesh.m_Shapes.shapes[0].vertexCount) + { + blendShapeValid = false; + break; + } + } + + for (size_t i = 0; i < mesh.m_SubMeshes.size(); i++) + { + //LOOP NOT DESIGNED TO continue/break! + SubMesh &subMesh = mesh.m_SubMeshes[i]; + aiMesh *pAiMesh = new aiMesh(); + pAiMesh->mName = mesh.m_Name; + if (mesh.m_SubMeshes.size() > 0) + { + char subTemp[16]; + sprintf_s(subTemp, "_sub%u", (unsigned int)i); + pAiMesh->mName.Append(subTemp); + } + pAiMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + pAiMesh->mNumVertices = subMesh.vertexCount; + for (int j = 0; j < mesh.m_VertexData.m_Channels.size() && j < 8; j++) + { + ChannelInfo &channel = mesh.m_VertexData.m_Channels[j]; + if (channel.dimension > 0 && channel.dimension <= 4 && channel.stream < mesh.m_VertexData.m_Streams.size()) + { + StreamInfo &stream = mesh.m_VertexData.m_Streams[channel.stream]; + uint8_t channelElementSize = mesh.m_VertexData.ChannelElementSize(channel.format, unity5OrNewer); + if ((channel.offset + channel.dimension * channelElementSize) <= stream.stride + && ((QWORD)stream.offset + (QWORD)stream.stride * (QWORD)subMesh.vertexCount) <= mesh.m_VertexData.dataByteCount) + { + void *channelDataBuffer = nullptr; + int channelType = j; + size_t outElements = 0; + switch (j) + { + case 0: //pos + if (channel.dimension == 3 && mesh.m_VertexData.IsFloatFormat(channel.format, unity5OrNewer)) + { + pAiMesh->mVertices = new aiVector3D[subMesh.vertexCount]; + channelDataBuffer = pAiMesh->mVertices; + outElements = 3; + } + break; + case 1: //normal + if (channel.dimension == 3 && mesh.m_VertexData.IsFloatFormat(channel.format, unity5OrNewer)) + { + pAiMesh->mNormals = new aiVector3D[subMesh.vertexCount]; + channelDataBuffer = pAiMesh->mNormals; + outElements = 3; + } + break; + case 2: //color + if (channel.dimension == 1 && mesh.m_VertexData.IsUIntFormat(channel.format, unity5OrNewer)) + { + pAiMesh->mColors[0] = new aiColor4D[subMesh.vertexCount]; + channelDataBuffer = pAiMesh->mColors[0]; + outElements = 4; + } + break; + case 3: //uv1 + if (channel.dimension <= 3 && mesh.m_VertexData.IsFloatFormat(channel.format, unity5OrNewer)) + { + pAiMesh->mNumUVComponents[0] = channel.dimension; + pAiMesh->mTextureCoords[0] = new aiVector3D[subMesh.vertexCount]; + channelDataBuffer = pAiMesh->mTextureCoords[0]; + outElements = 3; + } + break; + case 4: //uv2 + if (channel.dimension <= 3 && mesh.m_VertexData.IsFloatFormat(channel.format, unity5OrNewer)) + { + pAiMesh->mNumUVComponents[1] = channel.dimension; + pAiMesh->mTextureCoords[1] = new aiVector3D[subMesh.vertexCount]; + channelDataBuffer = pAiMesh->mTextureCoords[1]; + outElements = 3; + } + break; + case 5: //uv3 or tangent + if (mesh.m_VertexData.m_Channels.size() > 6) + { + if (channel.dimension <= 3 && mesh.m_VertexData.IsFloatFormat(channel.format, unity5OrNewer)) + { + pAiMesh->mNumUVComponents[2] = channel.dimension; + pAiMesh->mTextureCoords[2] = new aiVector3D[subMesh.vertexCount]; + channelDataBuffer = pAiMesh->mTextureCoords[2]; + outElements = 3; + } + } + else + { + channelType = 7; //tangent + if (channel.dimension == 3 && mesh.m_VertexData.IsFloatFormat(channel.format, unity5OrNewer)) + { + pAiMesh->mTangents = new aiVector3D[subMesh.vertexCount]; + channelDataBuffer = pAiMesh->mTangents; + outElements = 3; + } + } + break; + case 6: //uv4 + if (channel.dimension <= 3 && mesh.m_VertexData.IsFloatFormat(channel.format, unity5OrNewer)) + { + pAiMesh->mNumUVComponents[3] = channel.dimension; + pAiMesh->mTextureCoords[3] = new aiVector3D[subMesh.vertexCount]; + channelDataBuffer = pAiMesh->mTextureCoords[3]; + outElements = 3; + } + break; + case 7: //tangent + if (channel.dimension == 3 && mesh.m_VertexData.IsFloatFormat(channel.format, unity5OrNewer)) + { + pAiMesh->mTangents = new aiVector3D[subMesh.vertexCount]; + channelDataBuffer = pAiMesh->mTangents; + outElements = 3; + } + break; + } + + if (channelDataBuffer != NULL) + { + for (unsigned int k = subMesh.firstVertex; k < (subMesh.firstVertex+subMesh.vertexCount); k++) + { + uint8_t*pCurVertex = &((uint8_t*)mesh.m_VertexData.m_DataSize)[stream.offset + k * stream.stride + channel.offset]; + float inData[4] = {}; + if (channelType == 2) //color + { + uint8_t color[4] = {}; + mesh.m_VertexData.ConvertChannelUInt32(pCurVertex, channel.format, 1, (unsigned int*)&color[0], unity5OrNewer); + for (unsigned int l = 0; l < 4; l++) + { + inData[l] = ((float)color[l]) / 255.0f; + } + } + else + mesh.m_VertexData.ConvertChannelFloat(pCurVertex, channel.format, channel.dimension, inData, unity5OrNewer); + memcpy(&((uint8_t*)channelDataBuffer)[outElements * sizeof(float) * (k - subMesh.firstVertex)], inData, outElements * sizeof(float)); + } + } + } + } + } + + if (pAiMesh->mVertices != NULL) + { + for (unsigned int j = 0; j < subMesh.vertexCount; j++) + { + /*pAiMesh->mVertices[j].x *= subMesh.localAABB.m_Extent.x; + pAiMesh->mVertices[j].y *= subMesh.localAABB.m_Extent.y; + pAiMesh->mVertices[j].z *= subMesh.localAABB.m_Extent.z; + pAiMesh->mVertices[j].x += subMesh.localAABB.m_Center.x; + pAiMesh->mVertices[j].y += subMesh.localAABB.m_Center.y; + pAiMesh->mVertices[j].z += subMesh.localAABB.m_Center.z;*/ + //TODO: Fix orientation + pAiMesh->mVertices[j].x = -pAiMesh->mVertices[j].x; + } + } + if (pAiMesh->mNormals != NULL) + { + for (unsigned int j = 0; j < subMesh.vertexCount; j++) + { + pAiMesh->mNormals[j].x = -pAiMesh->mNormals[j].x; + } + } + if (pAiMesh->mTangents != NULL) + { + for (unsigned int j = 0; j < subMesh.vertexCount; j++) + { + pAiMesh->mTangents[j].x = -pAiMesh->mTangents[j].x; + } + pAiMesh->mBitangents = new aiVector3D[subMesh.vertexCount](); + if (pAiMesh->mNormals != NULL) + { + for (unsigned int j = 0; j < subMesh.vertexCount; j++) + { + aiVector3D &tan = pAiMesh->mTangents[j]; + aiVector3D &norm = pAiMesh->mNormals[j]; + //Cross product of tangent and normal. + //TODO: Check if this needs a normalization! + pAiMesh->mBitangents[j] = aiVector3D(tan.y * norm.z - tan.z * norm.y, tan.z * norm.x - tan.x * norm.z, tan.x * norm.y - tan.y * norm.x); + } + } + } + + pAiMesh->mNumFaces = subMesh.indexCount / 3; + pAiMesh->mFaces = new aiFace[pAiMesh->mNumFaces](); + size_t curFaceIndex = 0; + + unsigned int firstIndex = subMesh.firstByte / ((mesh.m_IndexFormat == 1) ? 4 : 2); + for (unsigned int i = firstIndex; i < (firstIndex+subMesh.indexCount) && i < mesh.m_IndexBuffer.size(); i++) + { + if (curFaceIndex == pAiMesh->mNumFaces) break; + aiFace &curFace = pAiMesh->mFaces[curFaceIndex]; + if (!curFace.mIndices) + curFace.mIndices = new unsigned int[3]; + curFace.mIndices[curFace.mNumIndices++] = mesh.m_IndexBuffer[i] + subMesh.baseVertex - subMesh.firstVertex; + + if (curFace.mNumIndices == 3) + { + if (subMesh.topology && (i&1)) + { + //always switch the winding + //curFace.mIndices is {idx0, idx1, idx2}. + } + else + { + unsigned int newIndices[3] = {curFace.mIndices[2], curFace.mIndices[1], curFace.mIndices[0]}; + memcpy(curFace.mIndices, newIndices, 3 * sizeof(unsigned int)); + } + curFaceIndex++; + } + } + + if (blendShapeValid && mesh.m_Shapes.vertices.size() >= ((size_t)subMesh.firstVertex + subMesh.vertexCount)) + { + pAiMesh->mNumAnimMeshes = (unsigned int)mesh.m_Shapes.shapes.size(); + pAiMesh->mAnimMeshes = new aiAnimMesh*[pAiMesh->mNumAnimMeshes]; + for (unsigned int j = 0; j < pAiMesh->mNumAnimMeshes; j++) + { + aiAnimMesh *pAiAnimMesh = new aiAnimMesh(); + + pAiAnimMesh->mNumVertices = subMesh.vertexCount; + pAiAnimMesh->mVertices = new aiVector3D[subMesh.vertexCount]; + if (mesh.m_Shapes.shapes[j].hasNormals) + pAiAnimMesh->mNormals = new aiVector3D[subMesh.vertexCount]; + if (mesh.m_Shapes.shapes[j].hasTangents) + { + pAiAnimMesh->mTangents = new aiVector3D[subMesh.vertexCount]; + pAiAnimMesh->mBitangents = new aiVector3D[subMesh.vertexCount]; + } + unsigned int firstVertex = mesh.m_Shapes.shapes[j].firstVertex + subMesh.firstVertex; + for (unsigned int k = 0; k < subMesh.vertexCount; k++) + { + BlendShapeVertex &curInVertex = mesh.m_Shapes.vertices[firstVertex + k]; + //TODO: Check if the SubMesh's localAABB has to be applied! + pAiAnimMesh->mVertices[k] = aiVector3D(-curInVertex.vertex.x, curInVertex.vertex.y, curInVertex.vertex.z); + if (mesh.m_Shapes.shapes[j].hasNormals) + pAiAnimMesh->mNormals[k] = aiVector3D(-curInVertex.normal.x, curInVertex.normal.y, curInVertex.normal.z); + if (mesh.m_Shapes.shapes[j].hasTangents) + { + pAiAnimMesh->mTangents[k] = aiVector3D(-curInVertex.tangent.x, curInVertex.tangent.y, curInVertex.tangent.z); + aiVector3D &tan = pAiAnimMesh->mTangents[k]; + aiVector3D norm; + if (mesh.m_Shapes.shapes[j].hasNormals) + norm = pAiAnimMesh->mNormals[k]; + else if (pAiMesh->mNormals) + norm = pAiMesh->mNormals[k]; + pAiAnimMesh->mBitangents[k] = aiVector3D(tan.y * norm.z - tan.z * norm.y, tan.z * norm.x - tan.x * norm.z, tan.x * norm.y - tan.y * norm.x); + } + } + + pAiAnimMesh->mWeight = mesh.m_Shapes.fullWeights[j] / 100.0f; //Seems to be a percentage for some reason + + char nameHashStr[24]; + sprintf_s(nameHashStr, "%u_", mesh.m_Shapes.channels[j].nameHash); + pAiAnimMesh->mName = nameHashStr; + pAiAnimMesh->mName.Append(mesh.m_Shapes.channels[j].name); + + pAiMesh->mAnimMeshes[j] = pAiAnimMesh; + } + } + + if (mesh.m_BindPose.size() > 0 + //&& (mesh.m_BindPose.size() == mesh.m_BoneNameHashes.size()) + && mesh.m_Skin.size() >= ((size_t)subMesh.firstVertex + subMesh.vertexCount)) + { + std::vector boneWeightCount = std::vector(mesh.m_BindPose.size()); + bool hasAnyBones = false; + for (size_t i = subMesh.firstVertex; i < (subMesh.firstVertex + subMesh.vertexCount); i++) + { + BoneInfluence &curVertexSkin = mesh.m_Skin[i]; + for (int j = 0; j < 4; j++) + { + if (curVertexSkin.weight[j] > 0.0f && curVertexSkin.boneIndex[j] < mesh.m_BindPose.size()) + { + boneWeightCount[curVertexSkin.boneIndex[j]]++; + hasAnyBones = true; + } + } + } + if (hasAnyBones) + { + unsigned int curBoneCount = 0; + std::vector boneIndexMap = std::vector(mesh.m_BindPose.size(), (unsigned int)-1); + for (size_t i = 0; i < mesh.m_BindPose.size(); i++) + if (boneWeightCount[i]) + boneIndexMap[i] = curBoneCount++; + + pAiMesh->mNumBones = curBoneCount; + pAiMesh->mBones = new aiBone*[curBoneCount]; + unsigned int curBoneIndex = 0; + for (size_t i = 0; i < mesh.m_BindPose.size(); i++) + { + if (boneWeightCount[i]) + { + aiBone *pAiBone = new aiBone(); + pAiBone->mWeights = new aiVertexWeight[boneWeightCount[i]](); + Matrix4x4f &m = mesh.m_BindPose[i]; + //Let the transformation matrix be ((a1 a2 a3 a4) (b1 ...) ... (d1 d2 d3 d4)). + //Since the vertex positions are flipped around the y-z-plane (x *= -1), + //we need to 1) unflip it + // (i.e. negate transform.a1, b1, c1, which will be multiplied with the negated x coordinate => transformed as if x was not flipped) + //and 2) flip it again after the transformation + // (i.e. multiply the transformation matrix with ((-1 0 0 0) (0 1 0 0) (0 0 1 0) (0 0 0 1)), which causes the resulting x coordinate to be flipped). + //Fused together : a2, a3, a4, b1, c1 have to be negated in order to account for the negated x vertex coordinate. + pAiBone->mOffsetMatrix = aiMatrix4x4 + (m.e[0][0], -m.e[0][1], -m.e[0][2], -m.e[0][3], + -m.e[1][0], m.e[1][1], m.e[1][2], m.e[1][3], + -m.e[2][0], m.e[2][1], m.e[2][2], m.e[2][3], + m.e[3][0], m.e[3][1], m.e[3][2], m.e[3][3]); + if (boneNames.size() > i) + pAiBone->mName = boneNames[i]; + else + { + char boneNameTemp[16]; + if (mesh.m_BoneNameHashes.size() > i) + sprintf_s(boneNameTemp, "%u", mesh.m_BoneNameHashes[i]); + else + sprintf_s(boneNameTemp, "i%u", (unsigned int)i); + pAiBone->mName = boneNameTemp; + } + pAiMesh->mBones[boneIndexMap[i]] = pAiBone; + } + } + for (size_t i = subMesh.firstVertex; i < ((size_t)subMesh.firstVertex + subMesh.vertexCount); i++) + { + BoneInfluence &curVertexSkin = mesh.m_Skin[i]; + for (int j = 0; j < 4; j++) + { + unsigned int targetIndex; + if (curVertexSkin.weight[j] > 0 && curVertexSkin.boneIndex[j] < mesh.m_BindPose.size() + && (targetIndex = boneIndexMap[curVertexSkin.boneIndex[j]]) != (unsigned int)-1) + { + aiBone *pAiTargetBone = pAiMesh->mBones[targetIndex]; + aiVertexWeight &vertexWeight = pAiTargetBone->mWeights[pAiTargetBone->mNumWeights++]; + vertexWeight.mVertexId = (unsigned int)(i - subMesh.firstVertex); + vertexWeight.mWeight = curVertexSkin.weight[j]; + } + } + } + } + } + + aiMaterial *pAiMat = new aiMaterial(); + pAiMesh->mMaterialIndex = scene.mNumMaterials; + scene.mMaterials[scene.mNumMaterials++] = pAiMat; + + scene.mMeshes[scene.mNumMeshes++] = pAiMesh; + } + + aiNode *pMeshNode = new aiNode(mesh.m_Name); + /*aiMatrix4x4 translation; aiMatrix4x4 scaling; + aiMatrix4x4::Translation(aiVector3D(mesh.m_LocalAABB.m_Center.x, mesh.m_LocalAABB.m_Center.y, mesh.m_LocalAABB.m_Center.z), translation); + aiMatrix4x4::Scaling(aiVector3D(mesh.m_LocalAABB.m_Extent.x, mesh.m_LocalAABB.m_Extent.y, mesh.m_LocalAABB.m_Extent.z), scaling); + pMeshNode->mTransformation = translation * scaling;*/ + + pMeshNode->mNumMeshes = (unsigned int)mesh.m_SubMeshes.size(); + pMeshNode->mMeshes = new unsigned int[pMeshNode->mNumMeshes]; + for (unsigned int i = 0; i < pMeshNode->mNumMeshes; i++) + pMeshNode->mMeshes[i] = scene.mNumMeshes - pMeshNode->mNumMeshes + i; + + scene.mRootNode->addChildren(1, &pMeshNode); + + return true; +} + +bool WriteScene(aiScene &scene, IAssetsWriter *pWriter) +{ + Assimp::ColladaExporter exporter(&scene, NULL, std::string(), std::string()); + char *outputBuffer = new char[4096](); + + exporter.mOutput.seekp(0, std::ios::end); + size_t fileSize = exporter.mOutput.tellp(); + exporter.mOutput.seekg(0, std::ios::beg); + while (fileSize >= 4096) + { + exporter.mOutput.read(outputBuffer, 4096); + if (pWriter->Write(4096, outputBuffer) != 4096) + { + delete[] outputBuffer; + return false; + } + fileSize -= 4096; + } + bool ret = true; + if (fileSize > 0) + { + exporter.mOutput.read(outputBuffer, fileSize); + ret = pWriter->Write(fileSize, outputBuffer) == fileSize; + } + delete[] outputBuffer; + return ret; +} + + +void ComposeMatrix(Quaternionf &rotation, Vector3f &position, Vector3f &scale, aiMatrix4x4 &out) +{ + out = aiMatrix4x4(aiVector3D(scale.x, scale.y, scale.z), aiQuaternion(rotation.w, rotation.x, rotation.y, rotation.z), aiVector3D(position.x, position.y, position.z)); +} \ No newline at end of file diff --git a/Plugins/Mesh/AssimpMesh.h b/Plugins/Mesh/AssimpMesh.h new file mode 100644 index 0000000..daeaade --- /dev/null +++ b/Plugins/Mesh/AssimpMesh.h @@ -0,0 +1,14 @@ +#pragma once +#include "Mesh.h" +#include +#include +#include + +//Adds a mesh to an aiScene. boneNames (optional) overrides the bone names (default: name hashes). +//Bone names can be retrieved through a SkinnedMeshRenderer, which has a bone list of Transforms (=> name of GameObject) +bool AddMeshToScene(aiScene &scene, Mesh &mesh, std::vector &boneNames, bool unity5OrNewer); + +//Writes an aiScene. +bool WriteScene(aiScene &scene, IAssetsWriter *pWriter); + +void ComposeMatrix(Quaternionf &rotation, Vector3f &position, Vector3f &scale, aiMatrix4x4 &out); \ No newline at end of file diff --git a/Plugins/Mesh/CMakeLists.txt b/Plugins/Mesh/CMakeLists.txt new file mode 100644 index 0000000..c1370b9 --- /dev/null +++ b/Plugins/Mesh/CMakeLists.txt @@ -0,0 +1,8 @@ +add_library (Mesh SHARED "Mesh.cpp" "MeshStructures.cpp" "AssimpMesh.cpp" Mesh.def) +target_include_directories (Mesh PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_include_directories (Mesh PRIVATE ${ASSIMP_INCLUDE_DIR}) + +set_target_properties(Mesh PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/Plugins" + SUFFIX ".bep") +target_link_libraries(Mesh PRIVATE UABE_Generic AssetsTools libStringConverter assimp) diff --git a/Plugins/Mesh/Mesh.cpp b/Plugins/Mesh/Mesh.cpp new file mode 100644 index 0000000..19f342a --- /dev/null +++ b/Plugins/Mesh/Mesh.cpp @@ -0,0 +1,1170 @@ +#include "Mesh.h" +#include "AssimpMesh.h" +#include "../libStringConverter/convert.h" +#include "../UABE_Generic/AssetPluginUtil.h" +#include "../UABE_Generic/FileContextInfo.h" +#include "../UABE_Generic/AppContext.h" +#include "../AssetsTools/AssetsReplacer.h" +#include "../AssetsTools/AssetsFileTable.h" +#include "../AssetsTools/ResourceManagerFile.h" +#include +#include +#include +#include +#include + +//void OpenExportFile(HWND hParentWnd, IPluginInterface *pInterface, +// char *outFolderPath, const char *fileName, IAssetInterface *pAsset, const char *extension, const wchar_t *extensionFilters, +// char **prevAssetNames, size_t i, size_t assetCount, +// IAssetsWriter *&pWriter); + +//Throws an AssetUtilError if the GameObject type cannot be resolved. +bool GetTransformName(AppContext &appContext, AssetIdentifier &asset, TypeTemplateCache &typeCache, + AssetTypeValueField *pTransformValue, std::string &name) +{ + name.clear(); + AssetTypeValueField *pGameObjectField = pTransformValue->Get("m_GameObject"); + AssetTypeValueField *pGameObjectFileIDField = pGameObjectField->Get("m_FileID"); + AssetTypeValueField *pGameObjectPathIDField = pGameObjectField->Get("m_PathID"); + if (!pGameObjectFileIDField->GetValue() || !pGameObjectPathIDField->GetValue()) + return false; + if (!asset.resolve(appContext)) + return false; + unsigned int targetFileID = asset.pFile->resolveRelativeFileID(pGameObjectFileIDField->GetValue()->AsUInt()); + AssetIdentifier gameObjectAsset(targetFileID, (pathid_t)pGameObjectPathIDField->GetValue()->AsUInt64()); + if (!gameObjectAsset.resolve(appContext)) + return false; + IAssetsReader_ptr pAssetReader = gameObjectAsset.makeReader(); + if (pAssetReader == nullptr) + return false; + QWORD assetSize = 0; + if (!pAssetReader->Seek(AssetsSeek_End, 0) || !pAssetReader->Tell(assetSize) || !pAssetReader->Seek(AssetsSeek_Begin, 0)) + return false; + AssetTypeTemplateField &gameObjectBase = typeCache.getTemplateField(appContext, gameObjectAsset); + AssetTypeTemplateField *pGameObjectBase = &gameObjectBase; + AssetTypeInstance gameObjectInstance(1, &pGameObjectBase, assetSize, pAssetReader.get(), gameObjectAsset.isBigEndian()); + + AssetTypeValueField *pBaseField = gameObjectInstance.GetBaseField(); + AssetTypeValueField *pNameField = nullptr; + if (pBaseField && (pNameField = pBaseField->Get("m_Name"))->GetValue() && pNameField->GetValue()->AsString()) + { + name.assign(pNameField->GetValue()->AsString()); + return true; + } + + return false; +} +struct BoneLocation +{ + BoneLocation(){hierarchyIndex = (unsigned int)-1; fileID = 0; pathID = 0;} + unsigned int hierarchyIndex; + unsigned int fileID; pathid_t pathID; +}; +class BoneHierarchy +{ +public: + BoneHierarchy(){level = -1; fileID = 0; pathID = 0;} + std::string name; + int level; + unsigned int fileID; pathid_t pathID; + aiMatrix4x4 transform; //relative to the parent transform +}; +void RestoreBoneHierarchy(AppContext &appContext, TypeTemplateCache& typeCache, + std::vector &output, std::vector &bones, BoneLocation &baseBoneTransform) +{ + class HierarchyStackEntry + { + public: + AssetIdentifier transformAsset; //not necessarily a bone + aiMatrix4x4 transform; //updated (i.e. multiplied with the current transform) if processedBase==true + int level; //level of the next children fields (increased by 1 if !processedBase and transformDesc is a bone). + bool processedBase = false; //says if transformDesc was already processed (i.e. checked if it is a bone and added to output in case it is). + bool isBone = false; //assigned if processedBase==true + std::unique_ptr pInstance; + AssetTypeValueField *pChildrenField = nullptr; //pInstance->GetBaseField()->Get("m_Children")->Get("Array") + unsigned int curChildIndex = 0; //next transform child index to check in pChildrenField + public: + HierarchyStackEntry(unsigned int fileID, pathid_t pathID, const aiMatrix4x4 &transform, int level) + : transformAsset(fileID, pathID), transform(transform), level(level) + {} + }; + std::vector stack; + stack.push_back(HierarchyStackEntry(baseBoneTransform.fileID, baseBoneTransform.pathID, aiMatrix4x4(), 0)); + while (stack.size()) + { + HierarchyStackEntry *pCurEntry = &stack[stack.size()-1]; + if ((stack.size() > 512) + || (output.size() > 0x7FFFFFFE) + || (!pCurEntry->transformAsset.resolve(appContext))) + { + stack.pop_back(); + continue; + } + bool doFree = false; + AssetTypeTemplateField& transformTemplate = typeCache.getTemplateField(appContext, pCurEntry->transformAsset); + + //If necessary, generate a type instance. + if (!pCurEntry->pInstance) + { + IAssetsReader_ptr pAssetReader = pCurEntry->transformAsset.makeReader(); + if (pAssetReader == nullptr) + { + stack.pop_back(); + continue; + } + QWORD transformAssetSize = 0; + if (!pAssetReader->Seek(AssetsSeek_End, 0) || !pAssetReader->Tell(transformAssetSize) || !pAssetReader->Seek(AssetsSeek_Begin, 0)) + { + stack.pop_back(); + continue; + } + AssetTypeTemplateField* pTransformTemplate = &transformTemplate; + pCurEntry->pInstance.reset( + new AssetTypeInstance(1, &pTransformTemplate, transformAssetSize, pAssetReader.get(), pCurEntry->transformAsset.isBigEndian())); + } + AssetTypeValueField *pBaseField; + if ((pBaseField = pCurEntry->pInstance->GetBaseField()) == nullptr || pBaseField->IsDummy()) + { + stack.pop_back(); + continue; + } + if (!pCurEntry->processedBase) + { + //Multiply this node's base transform (i.e. transform of the parent) with the local transform. + AssetTypeValueField *pRotationField = pBaseField->Get("m_LocalRotation"); + AssetTypeValueField *pPositionField = pBaseField->Get("m_LocalPosition"); + AssetTypeValueField *pScaleField = pBaseField->Get("m_LocalScale"); + if (!pRotationField->IsDummy() && !pPositionField->IsDummy() && !pScaleField->IsDummy()) + { + Quaternionf rot; + Vector3f pos; + Vector3f scale; + if (rot.Read(pRotationField) && pos.Read(pPositionField) && scale.Read(pScaleField)) + { + aiMatrix4x4 transform; + ComposeMatrix(rot, pos, scale, transform); + //Account for the negated x vertex coordinates (see AssimpMesh.cpp in the bind pose loop for a detailed explanation comment). + //Even though the root node has no flipped transformation, we're not getting into trouble since it starts at (0 0 0). + //The child nodes will have flipped x coordinates to match the flipped vertices. + transform.a2 *= -1; + transform.a3 *= -1; + transform.a4 *= -1; + transform.b1 *= -1; + transform.c1 *= -1; + pCurEntry->transform *= transform; + } + } + + //Find the matching bone entry and add it to the bone hierarchy (if it exists). + BoneLocation *pBaseBoneEntry = nullptr; + for (size_t i = 0; i < bones.size(); i++) + { + if (bones[i].hierarchyIndex == (unsigned int)-1 + && bones[i].fileID == pCurEntry->transformAsset.fileID + && bones[i].pathID == pCurEntry->transformAsset.pathID) + { + pBaseBoneEntry = &bones[i]; + break; + } + } + if (pBaseBoneEntry) + { + BoneHierarchy newOutEntry; + newOutEntry.level = pCurEntry->level++; //Increase the level to assign to child transforms. + newOutEntry.fileID = pCurEntry->transformAsset.fileID; + newOutEntry.pathID = pCurEntry->transformAsset.pathID; + newOutEntry.transform = pCurEntry->transform; + pBaseBoneEntry->hierarchyIndex = (unsigned int)output.size(); + output.push_back(newOutEntry); + //Set the bone name. + GetTransformName(appContext, pCurEntry->transformAsset, typeCache, pBaseField, output[pBaseBoneEntry->hierarchyIndex].name); + //Escape the bone name (should not inject XML tags / be multiple elements in a Collada Name_array or IDREF_array). + for (size_t i = 0; i < output[pBaseBoneEntry->hierarchyIndex].name.size(); i++) + { + switch (output[pBaseBoneEntry->hierarchyIndex].name[i]) + { + case ' ': + case '<': + case '>': + output[pBaseBoneEntry->hierarchyIndex].name[i] = '_'; + break; + } + } + + pCurEntry->isBone = true; //Do not pass on the transformation matrix since it's always relative to the parent bone in the hierarchy. + } + pCurEntry->processedBase = true; + } + //Locate the child array of the transform. + if (!pCurEntry->pChildrenField) + { + pCurEntry->pChildrenField = pBaseField->Get("m_Children")->Get("Array"); + if (pCurEntry->pChildrenField->GetValue() == nullptr || pCurEntry->pChildrenField->GetValue()->GetType() != ValueType_Array) + pCurEntry->pChildrenField = nullptr; + } + if (pCurEntry->pChildrenField) + { + //Add a stack entry for the next valid child transform, or remove the current entry. + bool addedNewEntry = false; + for (; pCurEntry->curChildIndex < pCurEntry->pChildrenField->GetChildrenCount(); pCurEntry->curChildIndex++) + { + AssetTypeValueField *pCurChild = pCurEntry->pChildrenField->Get(pCurEntry->curChildIndex); + AssetTypeValueField *pFileIDField = pCurChild->Get("m_FileID"); + AssetTypeValueField *pPathIDField = pCurChild->Get("m_PathID"); + if (pFileIDField->GetValue() && pPathIDField->GetValue()) + { + unsigned int targetFileID = pCurEntry->transformAsset.pFile->resolveRelativeFileID(pFileIDField->GetValue()->AsUInt()); + if (targetFileID != 0) + { + pCurEntry->curChildIndex++; + stack.push_back(HierarchyStackEntry(targetFileID, + pPathIDField->GetValue()->AsInt64(), + pCurEntry->isBone ? aiMatrix4x4() : pCurEntry->transform, pCurEntry->level) + ); + pCurEntry = &stack[stack.size()-2]; //Precaution + addedNewEntry = true; + break; + } + } + } + if (!addedNewEntry && pCurEntry->curChildIndex >= pCurEntry->pChildrenField->GetChildrenCount()) + doFree = true; + } + else + doFree = true; + if (doFree) + { + stack.pop_back(); + continue; + } + } + +} + +//skeletonName should be unique and will be a prefix for all bone names! +void AddBoneHierarchyToScene(aiScene &scene, std::vector &bones, const char *skeletonName) +{ + if (!scene.mRootNode) + scene.mRootNode = new aiNode("Root"); + aiNode *baseNode = new aiNode(skeletonName ? skeletonName : ""); + baseNode->mParent = scene.mRootNode; + aiNode **newChildNodes = new aiNode*[scene.mRootNode->mNumChildren + 1]; + memcpy(newChildNodes, scene.mRootNode->mChildren, scene.mRootNode->mNumChildren * sizeof(aiNode*)); + newChildNodes[scene.mRootNode->mNumChildren++] = baseNode; + delete[] scene.mRootNode->mChildren; + scene.mRootNode->mChildren = newChildNodes; + + class NodeLevelStackEntry + { + public: + aiNode *pNode; + unsigned int childBufferSize; + NodeLevelStackEntry(aiNode *pNode, unsigned int childBufferSize) + : pNode(pNode), childBufferSize(childBufferSize) + {} + }; + std::vector nodeLevelStack; + nodeLevelStack.push_back(NodeLevelStackEntry(baseNode, 0)); + for (size_t i = 0; i < bones.size(); i++) + { + if (nodeLevelStack.size() == 0) + break; + NodeLevelStackEntry *pCurEntry = &nodeLevelStack[nodeLevelStack.size()-1]; + if (i > 0 && bones[i].level > bones[i-1].level) + { + if (bones[i].level != (bones[i-1].level + 1)) + break; + if (pCurEntry->pNode->mNumChildren == 0) + break; + nodeLevelStack.push_back(NodeLevelStackEntry(pCurEntry->pNode->mChildren[pCurEntry->pNode->mNumChildren-1], 0)); + pCurEntry = &nodeLevelStack[nodeLevelStack.size()-1]; + } + else if (i > 0 && bones[i].level < bones[i-1].level) + { + if (bones[i].level < 0) + break; + nodeLevelStack.erase(nodeLevelStack.end() - (bones[i-1].level - bones[i].level), nodeLevelStack.end()); + if (nodeLevelStack.size() == 0) + break; + pCurEntry = &nodeLevelStack[nodeLevelStack.size()-1]; + } + if (pCurEntry->childBufferSize == 0) + { + size_t sameLevelCount = 1; + for (size_t j = i+1; j < bones.size(); j++) + { + if (bones[j].level == bones[i].level) + sameLevelCount++; + else if (bones[j].level < bones[i].level) + break; + } + if (sameLevelCount > 0x7FFFFFFE) + break; + pCurEntry->pNode->mNumChildren = 0; + pCurEntry->pNode->mChildren = new aiNode*[sameLevelCount](); + pCurEntry->childBufferSize = (unsigned int)sameLevelCount; + } + if ((pCurEntry->pNode->mNumChildren + 1) > pCurEntry->childBufferSize) + break; + aiNode *pNewNode = new aiNode(skeletonName ? (std::string(skeletonName) + "_" + bones[i].name) : bones[i].name); + pNewNode->mParent = pCurEntry->pNode; + pNewNode->mTransformation = bones[i].transform; + pCurEntry->pNode->mChildren[pCurEntry->pNode->mNumChildren++] = pNewNode; + } +} + +class MeshDAEExportTask : public AssetExportTask +{ + AppContext& appContext; + TypeTemplateCache templateCache; + bool combineToSingleFile; + std::mutex combinedSceneMutex; + aiScene combinedScene; + std::atomic_uint assetCounter; +public: + MeshDAEExportTask(AppContext& appContext, + std::vector _assets, std::string _baseDir, bool combineToSingleFile, + bool stopOnError = false) + + : AssetExportTask(std::move(_assets), "Export Mesh", ".dae", std::move(_baseDir), stopOnError, combineToSingleFile), + appContext(appContext), combineToSingleFile(combineToSingleFile) + {} + void onCompletion(const std::string& outputPath, std::optional> progressManager) + { + if (combineToSingleFile && combinedScene.mRootNode != nullptr && combinedScene.mNumMeshes > 0) + { + std::unique_ptr pWriter(Create_AssetsWriterToFile(outputPath.c_str(), true, true, RWOpenFlags_Immediately)); + if (pWriter == nullptr) + throw AssetUtilError("Unable to create the output file."); + if (!WriteScene(combinedScene, pWriter.get())) + throw AssetUtilError("Unable to write the data."); + } + } + + bool exportAsset(AssetUtilDesc& desc, std::string path, std::optional> progressManager) + { + if (desc.asset.pFile == nullptr) + throw AssetUtilError("Unable to find the target .assets file."); + + IAssetsReader_ptr pAssetReader = desc.asset.makeReader(); + if (pAssetReader == nullptr) + throw AssetUtilError("Unable to read the asset."); + QWORD assetSize = 0; + if (!pAssetReader->Seek(AssetsSeek_End, 0) || !pAssetReader->Tell(assetSize) || !pAssetReader->Seek(AssetsSeek_Begin, 0)) + throw AssetUtilError("Unable to read the asset."); + + AssetTypeTemplateField& templateBaseInitial = templateCache.getTemplateField(appContext, desc.asset); + AssetTypeTemplateField* pTemplateBase = &templateBaseInitial; + + AssetTypeInstance assetInstance(1, &pTemplateBase, assetSize, pAssetReader.get(), desc.asset.isBigEndian()); + AssetTypeValueField* pBaseField = assetInstance.GetBaseField(); + if (pBaseField == nullptr || pBaseField->IsDummy()) + throw AssetUtilError("Unable to deserialize the asset."); + + AssetIdentifier actualMeshAsset = desc.asset; + + std::vector boneNames; + std::vector bones; + + if (desc.asset.getClassID(appContext) != 0x2B //'heuristic' comparison with the original Mesh ID to save GetClassName_ calls. + && desc.asset.pFile->GetClassName_(appContext, desc.asset.getClassID(), desc.asset.getMonoScriptID(), &desc.asset) + == "SkinnedMeshRenderer") + { + AssetTypeValueField *pMeshField = pBaseField->Get("m_Mesh"); //PPtr + AssetTypeValueField *pMeshFileIDField = pMeshField->Get("m_FileID"); //int + AssetTypeValueField *pMeshPathIDField = pMeshField->Get("m_PathID"); //int or SInt64 + + AssetTypeValueField *pBonesField = pBaseField->Get("m_Bones")->Get("Array"); //Array> + + AssetTypeValueField *pRootBoneField = pBaseField->Get("m_RootBone"); //PPtr + AssetTypeValueField *pRootBoneFileIDField = pRootBoneField->Get("m_FileID"); //int + AssetTypeValueField *pRootBonePathIDField = pRootBoneField->Get("m_PathID"); //int or SInt64 + + if (pBonesField->GetValue() && pBonesField->GetValue()->GetType() == ValueType_Array + && pRootBoneFileIDField->GetValue() && pRootBoneFileIDField->GetValue()) + { + //Reconstruct the bone hierarchy. + AssetTypeTemplateField transformTemplateBase, gameObjectTemplateBase; + bones.reserve(pBonesField->GetChildrenCount()); + std::vector boneLocations(pBonesField->GetChildrenCount()); + for (unsigned int i = 0; i < pBonesField->GetChildrenCount(); i++) + { + AssetTypeValueField *pCurEntry = pBonesField->Get(i); + AssetTypeValueField *pFileIDField = pCurEntry->Get("m_FileID"); + AssetTypeValueField *pPathIDField = pCurEntry->Get("m_PathID"); + if (pFileIDField->GetValue() && pPathIDField->GetValue()) + { + boneLocations[i].fileID = desc.asset.pFile->resolveRelativeFileID(pFileIDField->GetValue()->AsUInt()); + boneLocations[i].pathID = pPathIDField->GetValue()->AsUInt64(); + } + else + { + boneLocations[i].fileID = 0; + boneLocations[i].pathID = 0; + } + boneLocations[i].hierarchyIndex = (unsigned int)-1; + } + BoneLocation rootLocation; + rootLocation.fileID = desc.asset.pFile->resolveRelativeFileID(pRootBoneFileIDField->GetValue()->AsUInt()); + rootLocation.pathID = pRootBonePathIDField->GetValue()->AsUInt64(); + rootLocation.hierarchyIndex = (unsigned int)-1; + RestoreBoneHierarchy(appContext, templateCache, bones, boneLocations, rootLocation); + for (size_t i = 0; i < boneLocations.size(); i++) + { + if (boneLocations[i].hierarchyIndex == (unsigned int)-1 && boneLocations[i].fileID != 0 && boneLocations[i].pathID != 0) + { + std::vector tempLocations; tempLocations.push_back(boneLocations[i]); + RestoreBoneHierarchy(appContext, templateCache, bones, tempLocations, boneLocations[i]); + } + } + boneNames.resize(boneLocations.size()); + for (size_t i = 0; i < boneLocations.size(); i++) + { + if (boneLocations[i].hierarchyIndex < bones.size()) + boneNames[i] = bones[boneLocations[i].hierarchyIndex].name; + else + boneNames[i] = ""; + } + } + else if (progressManager) + progressManager->get().logMessage(std::format( + "WARNING: Unable to locate the bone list (SkinnedMeshRenderer File ID {}, Path ID {}).", + desc.asset.fileID, desc.asset.pathID) + ); + + if (pMeshFileIDField->GetValue() && pMeshPathIDField->GetValue()) + { + unsigned int targetFileID = desc.asset.pFile->resolveRelativeFileID(pMeshFileIDField->GetValue()->AsUInt()); + pathid_t targetPathID = pMeshPathIDField->GetValue()->AsUInt64(); + if (targetPathID == 0) //can happen + { + if (progressManager) + progressManager->get().logMessage(std::format( + "WARNING: Unable to locate the actual mesh (SkinnedMeshRenderer File ID {}, Path ID {}).", + desc.asset.fileID, desc.asset.pathID) + ); + return true; + } + actualMeshAsset = AssetIdentifier(targetFileID, targetPathID); + if (!actualMeshAsset.resolve(appContext)) + { + throw AssetUtilError(std::format( + "Unable to find the referenced mesh asset (File ID {}, Path ID {}).", + targetFileID, targetPathID) + ); + } + pTemplateBase = &templateCache.getTemplateField(appContext, actualMeshAsset); + } + else + { + throw AssetUtilError(std::format( + "Unable to find the referenced mesh asset (SkinnedMeshRenderer File ID {}, Path ID {}).", + desc.asset.fileID, desc.asset.pathID) + ); + } + pAssetReader = actualMeshAsset.makeReader(); + if (pAssetReader == nullptr) + throw AssetUtilError("Unable to read the asset."); + assetSize = 0; + if (!pAssetReader->Seek(AssetsSeek_End, 0) || !pAssetReader->Tell(assetSize) || !pAssetReader->Seek(AssetsSeek_Begin, 0)) + throw AssetUtilError("Unable to read the asset."); + assetInstance = AssetTypeInstance(1, &pTemplateBase, assetSize, pAssetReader.get(), actualMeshAsset.isBigEndian()); + pBaseField = assetInstance.GetBaseField(); + if (pBaseField == nullptr || pBaseField->IsDummy()) + throw AssetUtilError("Unable to deserialize the asset."); + } + + bool isU5 = actualMeshAsset.pFile->getAssetsFileContext()->getAssetsFile()->header.format >= 0x0D; + Mesh mesh(pBaseField, isU5, appContext, actualMeshAsset); + const char *errorMessage = NULL; + if (!mesh.wasAbleToRead) + throw AssetUtilError("Unable to read the mesh asset! (unknown asset format)"); + else if (mesh.m_MeshCompression != 0) + throw AssetUtilError("Compressed meshes are not supported! Use .obj export instead."); + else if (mesh.m_VertexData.m_Streams.size() == 0) + throw AssetUtilError("Invalid vertex data (no streams available)!"); + else if (mesh.m_VertexData.m_Channels.size() < 6) + throw AssetUtilError("Invalid shader channels (less than 6 channels)!"); + else if (mesh.m_VertexData.m_Channels[0].dimension != 3) + throw AssetUtilError("Invalid shader channels (vertex position doesn't have 3 floats)!"); + + if (!combineToSingleFile) //Max. one mesh and skeleton per file. + { + aiScene curScene; + if (!AddMeshToScene(curScene, mesh, boneNames, isU5)) + throw AssetUtilError("Unable to process the mesh."); + AddBoneHierarchyToScene(curScene, bones, nullptr); + + std::unique_ptr pWriter(Create_AssetsWriterToFile(path.c_str(), true, true, RWOpenFlags_Immediately)); + if (pWriter == nullptr) + throw AssetUtilError("Unable to create the output file."); + + if (!WriteScene(curScene, pWriter.get())) + throw AssetUtilError("Unable to write the data."); + } + else + { + unsigned int exportIndex = this->assetCounter++; + std::string meshIndexStr = std::format("skeleton{}_", exportIndex); + + for (size_t i = 0; i < boneNames.size(); i++) + { + boneNames[i].insert(0, meshIndexStr); + } + + //For proper parallelism with combined meshes, + // working on per-thread sub scenes and reducing them in onCompletion + // may be a viable approach. + std::scoped_lock combinedSceneLock(combinedSceneMutex); + if (!AddMeshToScene(combinedScene, mesh, boneNames, isU5)) + throw AssetUtilError("Unable to process the mesh."); + meshIndexStr.pop_back(); //Remove the "_" character. + AddBoneHierarchyToScene(combinedScene, bones, meshIndexStr.c_str()); + } + + + return true; + } +}; + +class MeshOBJExportTask : public AssetExportTask +{ + AppContext& appContext; + TypeTemplateCache templateCache; +public: + MeshOBJExportTask(AppContext& appContext, + std::vector _assets, std::string _baseDir, + bool stopOnError = false) + + : AssetExportTask(std::move(_assets), "Export Mesh", ".obj", std::move(_baseDir), stopOnError), + appContext(appContext) + {} + + bool exportAsset(AssetUtilDesc& desc, std::string path, std::optional> progressManager) + { + if (desc.asset.pFile == nullptr) + throw AssetUtilError("Unable to find the target .assets file."); + + IAssetsReader_ptr pAssetReader = desc.asset.makeReader(); + if (pAssetReader == nullptr) + throw AssetUtilError("Unable to read the asset."); + QWORD assetSize = 0; + if (!pAssetReader->Seek(AssetsSeek_End, 0) || !pAssetReader->Tell(assetSize) || !pAssetReader->Seek(AssetsSeek_Begin, 0)) + throw AssetUtilError("Unable to read the asset."); + + AssetTypeTemplateField& templateBase = templateCache.getTemplateField(appContext, desc.asset); + AssetTypeTemplateField* pTemplateBase = &templateBase; + + AssetTypeInstance assetInstance(1, &pTemplateBase, assetSize, pAssetReader.get(), desc.asset.isBigEndian()); + AssetTypeValueField* pBaseField = assetInstance.GetBaseField(); + if (pBaseField == nullptr || pBaseField->IsDummy()) + throw AssetUtilError("Unable to deserialize the asset."); + + bool isU5 = desc.asset.pFile->getAssetsFileContext()->getAssetsFile()->header.format >= 0x0D; + Mesh mesh(pBaseField, isU5, appContext, desc.asset); + + const char* errorMessage = NULL; + if (!mesh.wasAbleToRead) + throw AssetUtilError("Unable to read the mesh asset. (unknown or unsupported asset format)"); + else if (mesh.m_MeshCompression != 0 && !mesh.m_CompressedMesh.wasAbleToRead) + throw AssetUtilError("The compressed mesh has an unknown format."); + else if (mesh.m_VertexData.m_Streams.size() == 0) + throw AssetUtilError("Invalid vertex data (no streams available)."); + else if (mesh.m_MeshCompression == 0) + { + if (mesh.m_VertexData.m_Channels.size() < 6) + throw AssetUtilError("Invalid shader channels (less than 6 channels)."); + else if (mesh.m_VertexData.m_Channels[0].dimension != 3) + throw AssetUtilError("Invalid shader channels (vertex position doesn't have 3 floats)."); + else if (!mesh.m_VertexData.IsFloatFormat(mesh.m_VertexData.m_Channels[0].format, isU5)) + throw AssetUtilError("Invalid shader channels (vertex position is not a float vector)."); + } + + std::unique_ptr pWriter(Create_AssetsWriterToFile(path.c_str(), true, true, RWOpenFlags_Immediately)); + if (pWriter == nullptr) + throw AssetUtilError("Unable to create the output file."); + + auto _writerputs = [&pWriter](const std::string& msg) {pWriter->Write(msg.size(), msg.data()); }; + std::string formatTmp; + unsigned int vertexCount = 0, indexCount = 0; + for (size_t i = 0; i < mesh.m_SubMeshes.size(); i++) + { + SubMesh& subMesh = mesh.m_SubMeshes[i]; + vertexCount = subMesh.firstVertex + subMesh.vertexCount; + indexCount += subMesh.indexCount; + } + //if (!vertexCount && mesh.m_MeshCompression != 0) + // vertexCount = mesh.m_CompressedMesh.m_Vertices.m_NumItems / 3; + unsigned int bitsPerVertex = 0; + if (!mesh.m_MeshCompression) + { + unsigned int bytesPerVertex = 0; + for (size_t i = 0; i < mesh.m_VertexData.m_Streams.size(); i++) + bytesPerVertex += mesh.m_VertexData.m_Streams[i].stride; + unsigned int expectedVertexBytes = vertexCount * bytesPerVertex; + if (expectedVertexBytes > (unsigned int)mesh.m_VertexData.dataByteCount) + { + throw AssetUtilError(std::format("Expected {} vertices ({} bytes) but got only {} bytes.", + vertexCount, expectedVertexBytes, mesh.m_VertexData.dataByteCount)); + + vertexCount = mesh.m_VertexData.dataByteCount / expectedVertexBytes; + } + bitsPerVertex = bytesPerVertex * 8; + } + else// if (mesh.m_VertexData.m_VertexCount > 0) + { + unsigned int totalVertexCount = mesh.m_CompressedMesh.m_Vertices.m_NumItems / 3; + if (totalVertexCount > 0) + { + if (mesh.m_CompressedMesh.m_Vertices.m_NumItems) + bitsPerVertex += mesh.m_CompressedMesh.m_Vertices.m_BitSize * + (mesh.m_CompressedMesh.m_Vertices.m_NumItems / totalVertexCount); //should be m_BitSize*3 + if (mesh.m_CompressedMesh.m_UV.m_NumItems) + bitsPerVertex += mesh.m_CompressedMesh.m_UV.m_BitSize * + (mesh.m_CompressedMesh.m_UV.m_NumItems / totalVertexCount); //should be m_BitSize*2,4,6 or 8 (uv1-4) + //Bind poses don't belong to the vertex data; 4x4 float matrices; + + //Contains the normals data without sign bit (the bit vectors are unsigned); + //The m_NormalSigns value has the sign bit of the third component, z. + //The length of the normal vector |v| = sqrt(x²+y²+z²) = 1²; z is not stored, so z = sqrt(1²-x²-y²). + if (mesh.m_CompressedMesh.m_Normals.m_NumItems) + bitsPerVertex += mesh.m_CompressedMesh.m_Normals.m_BitSize * + (mesh.m_CompressedMesh.m_Normals.m_NumItems / totalVertexCount); //shuold be m_BitSize*2 + //Tangents are stored like normals, so the description above applies to tangents, too. + if (mesh.m_CompressedMesh.m_Tangents.m_NumItems) + bitsPerVertex += mesh.m_CompressedMesh.m_Tangents.m_BitSize * + (mesh.m_CompressedMesh.m_Tangents.m_NumItems / totalVertexCount); //should be m_BitSize * 2 + //Weights don't belong to the vertex data; Assumed single floats; + if (mesh.m_CompressedMesh.m_NormalSigns.m_NumItems) + bitsPerVertex += mesh.m_CompressedMesh.m_NormalSigns.m_BitSize * + (mesh.m_CompressedMesh.m_NormalSigns.m_NumItems / totalVertexCount); //should be m_BitSize + if (mesh.m_CompressedMesh.m_TangentSigns.m_NumItems) + bitsPerVertex += mesh.m_CompressedMesh.m_TangentSigns.m_BitSize * + (mesh.m_CompressedMesh.m_TangentSigns.m_NumItems / totalVertexCount); //should be m_BitSize + if (mesh.m_CompressedMesh.m_FloatColors.wasAbleToRead && mesh.m_CompressedMesh.m_FloatColors.m_NumItems) + bitsPerVertex += mesh.m_CompressedMesh.m_FloatColors.m_BitSize * + (mesh.m_CompressedMesh.m_FloatColors.m_NumItems / totalVertexCount); //should be m_BitSize*3 + else if (mesh.m_CompressedMesh.m_Colors.wasAbleToRead && mesh.m_CompressedMesh.m_Colors.m_NumItems) + bitsPerVertex += mesh.m_CompressedMesh.m_Colors.m_BitSize * + (mesh.m_CompressedMesh.m_Colors.m_NumItems / totalVertexCount); //should be m_BitSize + } + } + std::format_to(std::back_inserter(formatTmp), "# Mesh \"{}\" exported with UABE; {} vertices, {} indices, {} bits per vertex\n", + mesh.m_Name, vertexCount, indexCount, bitsPerVertex); + _writerputs(formatTmp); + formatTmp.clear(); + std::format_to(std::back_inserter(formatTmp), "g {}\n", mesh.m_Name); + _writerputs(formatTmp); + formatTmp.clear(); + if (!mesh.m_MeshCompression) + { + ChannelInfo& posChannel = mesh.m_VertexData.m_Channels[0]; + ChannelInfo& normalChannel = mesh.m_VertexData.m_Channels[1]; + ChannelInfo& colorChannel = mesh.m_VertexData.m_Channels[2]; + ChannelInfo& uv1Channel = mesh.m_VertexData.m_Channels[3]; + ChannelInfo& uv2Channel = mesh.m_VertexData.m_Channels[4]; + //m_Channels[5] : UV3 for Unity >= 5.0, else tangents + //m_Channels[6] : UV4 + //m_Channels[7] : Tangents + ChannelInfo* pUVChannel = &uv1Channel; + + StreamInfo& posStream = mesh.m_VertexData.m_Streams[posChannel.stream]; + StreamInfo& normalStream = mesh.m_VertexData.m_Streams[normalChannel.stream]; + StreamInfo& colorStream = mesh.m_VertexData.m_Streams[colorChannel.stream]; + StreamInfo& uv1Stream = mesh.m_VertexData.m_Streams[uv1Channel.stream]; + StreamInfo& uv2Stream = mesh.m_VertexData.m_Streams[uv2Channel.stream]; + StreamInfo* pUVStream = &uv1Stream; + + bool hasNormals = (mesh.m_VertexData.m_Channels[1].dimension == 3 || mesh.m_VertexData.m_Channels[1].dimension == 4) + && mesh.m_VertexData.IsFloatFormat(mesh.m_VertexData.m_Channels[1].format, isU5); + bool hasColors = mesh.m_VertexData.m_Channels[2].dimension > 0; + bool hasUV1 = mesh.m_VertexData.m_Channels[3].dimension == 2 && mesh.m_VertexData.IsFloatFormat(mesh.m_VertexData.m_Channels[3].format, isU5); + bool hasUV2 = mesh.m_VertexData.m_Channels[4].dimension == 2 && mesh.m_VertexData.IsFloatFormat(mesh.m_VertexData.m_Channels[4].format, isU5); + if (!hasUV1) + { + pUVChannel = (hasUV2 ? &uv2Channel : NULL); + pUVStream = (hasUV2 ? &uv2Stream : NULL); + } + uint8_t vertexPosElementSize = mesh.m_VertexData.ChannelElementSize(posChannel.format, isU5); + uint8_t vertexNormalElementSize = mesh.m_VertexData.ChannelElementSize(normalChannel.format, isU5); + uint8_t vertexUVElementSize = pUVChannel ? mesh.m_VertexData.ChannelElementSize(pUVChannel->format, isU5) : 0; + + std::string_view faceFormat; + { + faceFormat = " {}"; + if (hasUV1 || hasUV2) + { + if (hasNormals) + faceFormat = " {}/{}/{}"; + else + faceFormat = " {}/{}"; + } + else if (hasNormals) + faceFormat = " {}//{}"; + } + for (size_t i = 0; i < mesh.m_SubMeshes.size(); i++) + { + std::format_to(std::back_inserter(formatTmp), "# SubMesh {}\n# Vertices\n", i); + _writerputs(formatTmp); + formatTmp.clear(); + /*#define MeshChannel_Pos 0 + #define MeshChannel_Normal 1 + #define MeshChannel_Color 2 + #define MeshChannel_UV1 3 + #define MeshChannel_UV2 4*/ + SubMesh& subMesh = mesh.m_SubMeshes[i]; + for (unsigned int j = subMesh.firstVertex; j < (subMesh.firstVertex + subMesh.vertexCount) && j < vertexCount; j++) + { + void *pCurPosVertex = &((uint8_t*)mesh.m_VertexData.m_DataSize)[posStream.offset + j * posStream.stride]; + void *pCurColorVertex = &((uint8_t*)mesh.m_VertexData.m_DataSize)[colorStream.offset + j * colorStream.stride]; + void *pCurUVVertex = NULL; + if (pUVStream) + pCurUVVertex = &((uint8_t*)mesh.m_VertexData.m_DataSize)[pUVStream->offset + j * pUVStream->stride]; + void *pCurNormalVertex = &((uint8_t*)mesh.m_VertexData.m_DataSize)[normalStream.offset + j * normalStream.stride]; + //PVOID pCurStreamVertex = &((BYTE*)mesh.m_VertexData.m_DataSize)[j * mesh.m_VertexData.m_Streams[pos]; + Vector3f vertexPos; + if ((posStream.offset + j * posStream.stride + posChannel.offset + 3 * vertexPosElementSize) <= mesh.m_VertexData.dataByteCount) + mesh.m_VertexData.ConvertChannelFloat( + &((uint8_t*)pCurPosVertex)[posChannel.offset], + posChannel.format, 3, (float*)&vertexPos, + isU5 + ); + else + { + throw AssetUtilError("Invalid vertex data (out of bounds)."); + } + _writerputs(std::format("v {} {} {}\n", + -vertexPos.x/* * subMesh.localAABB.m_Extent.x*//* + subMesh.localAABB.m_Center.x*/, + vertexPos.y/* * subMesh.localAABB.m_Extent.y*//* + subMesh.localAABB.m_Center.y*/, + vertexPos.z/* * subMesh.localAABB.m_Extent.z*//* + subMesh.localAABB.m_Center.z*/)); + if (hasColors) + { + uint32_t* colors = (uint32_t*)&((uint8_t*)pCurColorVertex)[colorChannel.offset]; + //TODO : add an additional .mtl file; usemtl, mtllib + } + if (pUVChannel) + { + Vector2f vertexUV1; + if ((pUVStream->offset + j * pUVStream->stride + pUVChannel->offset + 2 * vertexUVElementSize) <= mesh.m_VertexData.dataByteCount) + mesh.m_VertexData.ConvertChannelFloat( + &((uint8_t*)pCurUVVertex)[pUVChannel->offset], + pUVChannel->format, 2, (float*)&vertexUV1, + isU5 + ); + else + { + throw AssetUtilError("Invalid vertex data (out of bounds)."); + } + //Vector2f *vertexUV1 = (Vector2f*)&((BYTE*)pCurVertex)[mesh.m_VertexData.m_Channels[3].offset]; + _writerputs(std::format("vt {} {}\n", vertexUV1.x, vertexUV1.y)); + } + if (hasNormals) + { + Vector3f vertexNormals; + if ((normalStream.offset + j * normalStream.stride + normalChannel.offset + 3 * vertexNormalElementSize) <= mesh.m_VertexData.dataByteCount) + mesh.m_VertexData.ConvertChannelFloat( + &((uint8_t*)pCurNormalVertex)[normalChannel.offset], + normalChannel.format, 3, (float*)&vertexNormals, + isU5 + ); + else + { + throw AssetUtilError("Invalid vertex data (out of bounds)."); + } + //Vector3f *vertexNormals = (Vector3f*)&((BYTE*)pCurVertex)[mesh.m_VertexData.m_Channels[1].offset]; + _writerputs(std::format("vn {} {} {}\n", -vertexNormals.x, vertexNormals.y, vertexNormals.z)); + } + } + std::format_to(std::back_inserter(formatTmp), "# Faces (%u to %u)\n", + subMesh.firstVertex + 1, (subMesh.firstVertex + subMesh.vertexCount)); + _writerputs(formatTmp); + formatTmp.clear(); + std::format_to(std::back_inserter(formatTmp), "g %s_%u\n", mesh.m_Name, i); + _writerputs(formatTmp); + formatTmp.clear(); + //sprintf_s(sprntTmp, "usemtl %s_%u\n", mesh.m_Name, i); + //fputs(sprntTmp, pFile); + //sprintf_s(sprntTmp, "usemap %s_%u\n", mesh.m_Name, i); + //fputs(sprntTmp, pFile); + unsigned int curIndexCount = 0; + int triIndices[3]; + unsigned int definedVertices = (subMesh.firstVertex + subMesh.vertexCount); + unsigned int firstIndex = subMesh.firstByte / ((mesh.m_IndexFormat == 1) ? 4 : 2); + for (unsigned int i = firstIndex; i < (firstIndex + subMesh.indexCount) && i < mesh.m_IndexBuffer.size(); i++) + { + if (!curIndexCount) + _writerputs("f"); + + triIndices[curIndexCount] = (int)(mesh.m_IndexBuffer[i] + subMesh.baseVertex - (subMesh.firstVertex + subMesh.vertexCount)); + curIndexCount++; + + if (curIndexCount == 3) + { + if (subMesh.topology && (i & 1)) + { + //always switch the winding + for (int i = 0; i < 3; i++) + { + std::format_to(std::back_inserter(formatTmp), faceFormat, triIndices[i], triIndices[i], triIndices[i]); + _writerputs(formatTmp); + formatTmp.clear(); + } + } + else + { + for (int i = 2; i >= 0; i--) + { + std::format_to(std::back_inserter(formatTmp), faceFormat, triIndices[i], triIndices[i], triIndices[i]); + _writerputs(formatTmp); + formatTmp.clear(); + } + } + + _writerputs("\n"); + curIndexCount = 0; + } + /*int relIndex = mesh.m_IndexBuffer[i] - (subMesh.firstVertex+subMesh.vertexCount); + sprintf_s(sprntTmp, faceFormat, relIndex, relIndex, relIndex); + //sprintf_s(sprntTmp, faceFormat, mesh.m_IndexBuffer[i]+1, mesh.m_IndexBuffer[i]+1, mesh.m_IndexBuffer[i]+1); + //sprintf_s(sprntTmp, " %d", mesh.m_IndexBuffer[i]); + fputs(sprntTmp, pFile);*/ + } + _writerputs("\n"); + } + } + else + { + unsigned int vertexDim = 3; + unsigned int uvCount = 0; + bool hasNormals = false; + bool hasNormalSigns = false; + /*bool hasFloatColors = false; + bool has32BitColors = false;*/ + if (mesh.m_VertexData.m_VertexCount > 0) + { + vertexDim = (mesh.m_CompressedMesh.m_Vertices.m_NumItems / mesh.m_VertexData.m_VertexCount); + uvCount = (mesh.m_CompressedMesh.m_UV.m_NumItems / mesh.m_VertexData.m_VertexCount) / 2; + hasNormals = mesh.m_CompressedMesh.m_Normals.m_NumItems != 0 && + (mesh.m_CompressedMesh.m_Normals.m_NumItems / mesh.m_VertexData.m_VertexCount) == 2; + hasNormalSigns = (mesh.m_CompressedMesh.m_NormalSigns.m_NumItems / mesh.m_CompressedMesh.m_Normals.m_NumItems) == 2; + /*hasFloatColors = mesh.m_CompressedMesh.m_FloatColors.m_NumItems != 0 && + (mesh.m_CompressedMesh.m_FloatColors.m_NumItems / mesh.m_VertexData.m_VertexCount) == 4; + has32BitColors = mesh.m_CompressedMesh.m_Colors.m_NumItems != 0 && + (mesh.m_CompressedMesh.m_Colors.m_NumItems / mesh.m_VertexData.m_VertexCount) == 4;*/ + } + if (vertexDim < 2 || vertexDim > 3) + { + throw AssetUtilError(std::format("Expected vertex dimension 2 or 3 but got {}.", vertexDim)); + } + std::string_view faceFormat; + { + faceFormat = " {}"; + if (uvCount > 0) + { + if (hasNormals) + faceFormat = " {}/{}/{}"; + else + faceFormat = " {}/{}"; + } + else if (hasNormals) + faceFormat = " {}//{}"; + } + for (size_t i = 0; i < mesh.m_SubMeshes.size(); i++) + { + std::format_to(std::back_inserter(formatTmp), "# SubMesh {}\n# Vertices\n", i); + _writerputs(formatTmp); + formatTmp.clear(); + + SubMesh& subMesh = mesh.m_SubMeshes[i]; + for (unsigned int j = subMesh.firstVertex; j < (subMesh.firstVertex + subMesh.vertexCount) && j < mesh.m_CompressedMesh.m_Vertices.m_NumItems; j++) + { + float position[3] = { 0,0,0 }; + for (unsigned int k = 0; k < vertexDim; k++) + { + unsigned int index = j * vertexDim + k; + if (!mesh.m_CompressedMesh.m_Vertices.ReadValueFloat(index, position[k])) + { + throw AssetUtilError(std::format("Unable to read vertex position {} (m_Vertices item {}).", j, index)); + } + } + float uv[2] = { 0,0 }; + if (uvCount > 0) + { + for (unsigned int k = 0; k < 2; k++) + { + unsigned int index = j * (2 * uvCount) + k; + if (!mesh.m_CompressedMesh.m_UV.ReadValueFloat(index, uv[k])) + { + throw AssetUtilError(std::format("Unable to read vertex uv {} (m_UV item {}).", j, index)); + } + } + } + float normals[3] = { 0,0,0 }; + if (hasNormals) + { + for (unsigned int k = 0; k < 2; k++) + { + unsigned int index = j * 2 + k; + if (!mesh.m_CompressedMesh.m_Normals.ReadValueFloat(index, normals[k])) + { + throw AssetUtilError(std::format("Unable to read vertex normal {} (m_Normals item {}).", j, index)); + } + } + normals[2] = sqrt(1 - normals[0] * normals[0] - normals[1] * normals[1]); + if (hasNormalSigns) + { + unsigned int sign = 0; + if (!mesh.m_CompressedMesh.m_NormalSigns.ReadValue(j, sign)) + { + throw AssetUtilError(std::format("Unable to read vertex normal sign {}.", j)); + } + if (!sign) + normals[2] = -normals[2]; + } + } + + if (vertexDim == 2) + std::format_to(std::back_inserter(formatTmp), "v {} {}\n", -position[0], position[1]); + else + std::format_to(std::back_inserter(formatTmp), "v {} {} {}\n", -position[0], position[1], position[2]); + _writerputs(formatTmp); + formatTmp.clear(); + if (uvCount > 0) + { + std::format_to(std::back_inserter(formatTmp), "vt {} {}\n", uv[0], uv[1]); + _writerputs(formatTmp); + formatTmp.clear(); + } + if (hasNormals) + { + std::format_to(std::back_inserter(formatTmp), "vn {} {} {}\n", -normals[0], normals[1], normals[2]); + _writerputs(formatTmp); + formatTmp.clear(); + } + } + std::format_to(std::back_inserter(formatTmp), "# Faces (%u to %u)\n", + subMesh.firstVertex + 1, (subMesh.firstVertex + subMesh.vertexCount)); + _writerputs(formatTmp); + formatTmp.clear(); + + std::format_to(std::back_inserter(formatTmp), "g %s_%u\n", mesh.m_Name, i); + _writerputs(formatTmp); + formatTmp.clear(); + + //sprintf_s(sprntTmp, "usemtl %s_%u\n", mesh.m_Name, i); + //fputs(sprntTmp, pFile); + //sprintf_s(sprntTmp, "usemap %s_%u\n", mesh.m_Name, i); + //fputs(sprntTmp, pFile); + + int curIndexCount = 0; + int triIndices[3]; + unsigned int definedVertices = (subMesh.firstVertex + subMesh.vertexCount); + unsigned int firstIndex = subMesh.firstByte / 2; + for (unsigned int j = firstIndex; j < (firstIndex + subMesh.indexCount) + && i < mesh.m_CompressedMesh.m_Triangles.m_NumItems; j++) + { + if (!curIndexCount) + _writerputs("f"); + unsigned int curIndex = 0; + if (!mesh.m_CompressedMesh.m_Triangles.ReadValue(j, curIndex)) + { + throw AssetUtilError(std::format("Unable to read triangle index {}.", j)); + } + + triIndices[curIndexCount] = ((int)curIndex) - (subMesh.firstVertex + subMesh.vertexCount); + curIndexCount++; + + if (curIndexCount == 3) + { + if (subMesh.topology && (j & 1)) + { + //always switch the winding + for (int k = 0; k < 3; k++) + { + std::format_to(std::back_inserter(formatTmp), faceFormat, triIndices[k], triIndices[k], triIndices[k]); + _writerputs(formatTmp); + formatTmp.clear(); + } + } + else + { + for (int k = 2; k >= 0; k--) + { + std::format_to(std::back_inserter(formatTmp), faceFormat, triIndices[k], triIndices[k], triIndices[k]); + _writerputs(formatTmp); + formatTmp.clear(); + } + } + + _writerputs("\n"); + curIndexCount = 0; + } + } + _writerputs("\n"); + } + } + + + return true; + } +}; + +enum class MeshExportMode +{ + OBJ, + DAESeparate, + DAECombined +}; +static bool SupportsElements(AppContext& appContext, std::vector& elements, MeshExportMode mode) +{ + bool allowSkinnedMeshRenderer = (mode != MeshExportMode::OBJ); + if (mode == MeshExportMode::DAECombined && elements.size() < 2) + return false; + std::unordered_map> meshClassIDs; + for (size_t i = 0; i < elements.size(); i++) + { + if (elements[i].asset.pFile == nullptr) + return false; + AssetsFileContextInfo* pFile = elements[i].asset.pFile.get(); + auto classIDsit = meshClassIDs.find(pFile); + std::array ids = { -1, -1 }; + if (classIDsit == meshClassIDs.end()) + { + ids[0] = pFile->GetClassByName("Mesh"); + ids[1] = pFile->GetClassByName("SkinnedMeshRenderer"); + meshClassIDs[pFile] = ids; + } + else + ids = classIDsit->second; + int32_t classId = elements[i].asset.getClassID(); + if (classId == -1 || (classId != ids[0] && (!allowSkinnedMeshRenderer || classId != ids[1]))) + return false; + } + return true; +} +class MeshExportProvider : public IAssetOptionProviderGeneric +{ + MeshExportMode mode; +public: + inline MeshExportProvider(MeshExportMode mode) + : mode(mode) + {} + class Runner : public IOptionRunner + { + AppContext& appContext; + std::vector selection; + MeshExportMode mode; + public: + Runner(AppContext& appContext, std::vector _selection, MeshExportMode mode) + : appContext(appContext), selection(std::move(_selection)), mode(mode) + {} + void operator()() + { + std::string exportLocation; + switch (mode) + { + case MeshExportMode::OBJ: + exportLocation = appContext.QueryAssetExportLocation(selection, ".obj", "*.obj|OBJ file:"); + break; + case MeshExportMode::DAESeparate: + exportLocation = appContext.QueryAssetExportLocation(selection, ".dae", "*.dae|Collada file:"); + break; + case MeshExportMode::DAECombined: + if (!selection.empty()) + exportLocation = appContext.QueryAssetExportLocation({ selection[0] }, ".dae", "*.dae|Collada file:"); + break; + } + if (!exportLocation.empty()) + { + std::shared_ptr pTask = nullptr; + switch (mode) + { + case MeshExportMode::OBJ: + pTask = std::make_shared(appContext, std::move(selection), std::move(exportLocation)); + break; + case MeshExportMode::DAESeparate: + pTask = std::make_shared(appContext, std::move(selection), std::move(exportLocation), false); + break; + case MeshExportMode::DAECombined: + pTask = std::make_shared(appContext, std::move(selection), std::move(exportLocation), true); + break; + default: + return; + } + appContext.taskManager.enqueue(pTask); + } + } + }; + EAssetOptionType getType() + { + return EAssetOptionType::Export; + } + std::unique_ptr prepareForSelection( + class AppContext& appContext, + std::vector selection, + std::string& optionName) + { + if (!SupportsElements(appContext, selection, mode)) + return nullptr; + const char* modeDesc = ""; + switch (mode) + { + case MeshExportMode::OBJ: + modeDesc = ".obj"; + break; + case MeshExportMode::DAESeparate: + modeDesc = ".dae"; + break; + case MeshExportMode::DAECombined: + modeDesc = ".dae (combined)"; + break; + default: + return nullptr; + } + optionName = std::string("Export mesh to ") + modeDesc; + return std::make_unique(appContext, std::move(selection), mode); + } +}; + +class MeshPluginDesc : public IPluginDesc +{ + std::vector> pProviders; +public: + MeshPluginDesc() + { + pProviders = { + std::make_shared(MeshExportMode::OBJ), + std::make_shared(MeshExportMode::DAESeparate), + std::make_shared(MeshExportMode::DAECombined) + }; + } + std::string getName() + { + return "Mesh"; + } + std::string getAuthor() + { + return ""; + } + std::string getDescriptionText() + { + return "Export Mesh and SkinnedMeshRenderer assets."; + } + //The IPluginDesc object should keep a reference to the returned options, as the caller may keep only std::weak_ptrs. + //Note: May be called early, e.g. before program UI initialization. + std::vector> getPluginOptions(class AppContext& appContext) + { + return pProviders; + } +}; + +IPluginDesc* GetUABEPluginDesc1(size_t sizeof_AppContext, size_t sizeof_BundleFileContextInfo) +{ + if (sizeof_AppContext != sizeof(AppContext) || sizeof_BundleFileContextInfo != sizeof(BundleFileContextInfo)) + { + assert(false); + return nullptr; + } + return new MeshPluginDesc(); +} + diff --git a/Plugins/Mesh/Mesh.def b/Plugins/Mesh/Mesh.def new file mode 100644 index 0000000..dff1bc3 --- /dev/null +++ b/Plugins/Mesh/Mesh.def @@ -0,0 +1,3 @@ +LIBRARY Mesh +EXPORTS + GetUABEPluginDesc1 diff --git a/Plugins/Mesh/Mesh.h b/Plugins/Mesh/Mesh.h new file mode 100644 index 0000000..86ae868 --- /dev/null +++ b/Plugins/Mesh/Mesh.h @@ -0,0 +1,287 @@ +#pragma once + +#include "../UABE_Generic/AssetPluginUtil.h" +#include "../AssetsTools/AssetsReplacer.h" + +#include +#include +struct HalfFloat +{ + unsigned short half; + //http://stackoverflow.com/questions/6162651/half-precision-floating-point-in-java/6162687#6162687 + float toFloat(); +}; +struct Vector2f +{ + float x; + float y; +}; +struct Vector2hf +{ + HalfFloat x; + HalfFloat y; + void ToVector2f(Vector2f &out); +}; + +struct Vector3f +{ + bool Read(AssetTypeValueField *pField); + float x; + float y; + float z; +}; +struct Vector3hf +{ + HalfFloat x; + HalfFloat y; + HalfFloat z; + void ToVector3f(Vector3f &out); +}; +struct AABB +{ + bool Read(AssetTypeValueField *pField); + Vector3f m_Center; + Vector3f m_Extent; +}; +struct MinMaxAABB +{ + bool Read(AssetTypeValueField *pField); + Vector3f m_Min; + Vector3f m_Max; +}; + +struct Quaternionf +{ + bool Read(AssetTypeValueField *pField); + float x; + float y; + float z; + float w; +}; + +struct Matrix4x4f +{ + bool Read(AssetTypeValueField *pField); + float e[4][4]; //e[line][column] +}; + +struct SubMesh +{ + bool Read(AssetTypeValueField *pField); + unsigned int firstByte; //index buffer offset + unsigned int indexCount; + int topology; //unknown + unsigned int baseVertex; //since 2017.3; offset for indices, used especially for 16bit index buffers with >65535 vertices. + unsigned int firstVertex; //vertex index + unsigned int vertexCount; + AABB localAABB; +}; +struct ChannelInfo +{ + bool Read(AssetTypeValueField *pField); + unsigned char stream; + unsigned char offset; //byte offset + unsigned char format; //0 : float32, 1 : half, 2 : byte(?), 11 : int32 + unsigned char dimension; //amount of values; if 0 : channel doesn't exist + unsigned char dimension_flags; +}; +struct StreamInfo +{ + bool Read(AssetTypeValueField *pField); + unsigned int channelMask; + unsigned int offset; + unsigned char stride; + unsigned char dividerOp; + unsigned short frequency; +}; +class VertexData +{ +public: + bool wasAbleToRead; + VertexData(); + inline VertexData(const VertexData &src) { (*this) = src; } + inline VertexData(VertexData &&src) { (*this) = std::move(src); } + VertexData &operator=(const VertexData &src); + VertexData &operator=(VertexData &&src); + //pMeshContextInfo: File that contains the streamInfo reference, used for path resolving. + VertexData(AssetTypeValueField *pField, + AppContext &appContext, class StreamingInfo &streamInfo, AssetIdentifier& meshAsset, + std::vector &boneWeights, bool unity5OrNewer); + ~VertexData(); + + //Allows float32/float16/unorm8/snorm8/unorm16/snorm16 + static bool ConvertChannelFloat(const void *inData, uint8_t inFormat, uint8_t dimension, float *outData, bool unity5OrNewer); + //Only allows int8/int16/int32. + static bool ConvertChannelInt32(const void *inData, uint8_t inFormat, uint8_t dimension, int *outData, bool unity5OrNewer); + //Interprets int8/int16/int32 as uint8/uint16/uint32. + static bool ConvertChannelUInt32(const void *inData, uint8_t inFormat, uint8_t dimension, unsigned int *outData, bool unity5OrNewer); + static inline uint8_t ChannelElementSize(uint8_t format, bool unity5OrNewer) + { + //Unity 2018.2 + static const uint8_t channelFormatSizeNew[16] = { + 4, 2, 1, 1, 1, 2, 2, 1, 1, 2, 2, 4, 4, 0, 0, 0 + }; + /* + 0 : Float32; 1 : Float16; 2 : UNorm8; 3 : UNorm8; 4 : SNorm8; 5 : UNorm16; 6 : SNorm16; + 7 : UInt8; 8 : SInt8; 9 : UInt16; 10 : SInt16; 11 : UInt32; 12 : SInt32; + */ + + //Unity 4 : kVertexChannelFormatSizes; + static const uint8_t channelFormatSizeOld[4] = { + 4, 2, 4, 1 + }; + //0 : Float32; 1 : Float16; 2 : UInt32; 3 : SNorm8; + + if (format > (unity5OrNewer ? 12 : 3)) + return 0; + return unity5OrNewer ? channelFormatSizeNew[format] : channelFormatSizeOld[format]; + } + static inline bool IsFloatFormat(uint8_t format, bool unity5OrNewer) + { + return unity5OrNewer ? (format < 7) : (format != 2); + } + static inline bool IsIntFormat(uint8_t format, bool unity5OrNewer) + { + return unity5OrNewer ? (format >= 8 && format <= 12 && ((format - 7) & 1)) : false; + } + //Int or UInt + static inline bool IsUIntFormat(uint8_t format, bool unity5OrNewer) + { + return unity5OrNewer ? (format >= 7 && format <= 12) : (format == 2); + } + + int m_CurrentChannels; + unsigned int m_VertexCount; //amount of vertices (= dataByteCount / (lastChannel.offset + 4*lastChannel.dimension)) + std::vector m_Channels; + std::vector m_Streams; + int dataByteCount; + void* m_DataSize; //actual data of the mesh +protected: + bool _hasOwnData; //if m_DataSize should be freed in the destructor. +}; +class PackedBitVector +{ +public: + bool wasAbleToRead; + bool hasRangeAndStart; + PackedBitVector(); + bool Read(AssetTypeValueField *pField); + unsigned char MakeByteMask(unsigned int bitOffset); + bool ReadValue(unsigned int index, unsigned int &out); + bool ReadValueFloat(unsigned int index, float &out); + unsigned int m_NumItems; + float m_Range; + float m_Start; + std::vector m_Data; + unsigned char m_BitSize; + unsigned char bitSize; //m_BitSize may be null (according to https://github.com/Perfare/UnityStudio/blob/master/Unity%20Studio/Unity%20Classes/Mesh.cs) +}; +class CompressedMesh +{ +public: + bool wasAbleToRead; + CompressedMesh(); + bool Read(AssetTypeValueField *pField); + PackedBitVector m_Vertices; + PackedBitVector m_UV; + PackedBitVector m_BindPoses; + PackedBitVector m_Normals; + PackedBitVector m_Tangents; + PackedBitVector m_Weights; + PackedBitVector m_NormalSigns; + PackedBitVector m_TangentSigns; + PackedBitVector m_FloatColors; + PackedBitVector m_BoneIndices; + PackedBitVector m_Triangles; + PackedBitVector m_Colors; + unsigned int m_UVInfo; +}; +struct BlendShapeVertex +{ +public: + bool Read(AssetTypeValueField *pField); + Vector3f vertex; + Vector3f normal; + Vector3f tangent; + unsigned int index; +}; +struct MeshBlendShape +{ +public: + bool Read(AssetTypeValueField *pField); + unsigned int firstVertex; + unsigned int vertexCount; + bool hasNormals; + bool hasTangents; +}; +struct MeshBlendShapeChannel +{ +public: + bool Read(AssetTypeValueField *pField); + const char *name; + unsigned int nameHash; + int frameIndex; + int frameCount; +}; +class BlendShapeData +{ +public: + bool wasAbleToRead; + BlendShapeData(); + bool Read(AssetTypeValueField *pField); + + std::vector vertices; + std::vector shapes; + std::vector channels; + std::vector fullWeights; +}; +struct BoneInfluence +{ + bool Read(AssetTypeValueField *pField); + float weight[4]; + unsigned int boneIndex[4]; +}; +class StreamingInfo +{ +public: + bool wasAbleToRead; + StreamingInfo(); + bool Read(AssetTypeValueField *pField); + uint64_t offset; + unsigned int size; + std::string path; +}; +class VariableBoneCountWeights +{ +public: + VariableBoneCountWeights(); + bool Read(AssetTypeValueField *pField); + std::vector m_Data; +}; +class Mesh +{ +public: + bool wasAbleToRead; + + Mesh(AssetTypeValueField *pField, bool unity5OrNewer, AppContext &appContext, AssetIdentifier &asset); + const char *m_Name; + std::vector m_SubMeshes; + BlendShapeData m_Shapes; + std::vector m_BindPose; + std::vector m_BoneNameHashes; + unsigned int m_RootBoneNameHash; + std::vector m_BonesAABB; + VariableBoneCountWeights m_VariableBoneCountWeights; + unsigned char m_MeshCompression; + int m_IndexFormat; //0 : UInt16, 1 : UInt32 (=> UnityEngine.CoreModule.dll UnityEngine.Rendering.IndexFormat) + std::vector m_IndexBuffer; + std::vector m_Skin; //TODO: removed with Unity 2018.2, replaced by vertex data channels 12/13 + VertexData m_VertexData; + CompressedMesh m_CompressedMesh; + AABB m_LocalAABB; + //int m_MeshUsageFlags; + //std::vector m_BakedConvexCollisionMesh; + //std::vector m_BakedTriangleCollisionMesh; + //float m_MeshMetrics[2]; + StreamingInfo m_StreamData; +}; \ No newline at end of file diff --git a/Plugins/Mesh/MeshStructures.cpp b/Plugins/Mesh/MeshStructures.cpp new file mode 100644 index 0000000..655c6e5 --- /dev/null +++ b/Plugins/Mesh/MeshStructures.cpp @@ -0,0 +1,997 @@ +#include "Mesh.h" +#undef max +#undef min +#include "../inc/half.hpp" + +float HalfFloat::toFloat() +{ + return half_float::detail::half2float(half); +} +void Vector2hf::ToVector2f(Vector2f &out) +{ + out.x = x.toFloat(); + out.y = y.toFloat(); +} +void Vector3hf::ToVector3f(Vector3f &out) +{ + out.x = x.toFloat(); + out.y = y.toFloat(); + out.z = z.toFloat(); +} +bool Vector3f::Read(AssetTypeValueField *pField) +{ + AssetTypeValueField *pXField = (*pField)["x"]; + AssetTypeValueField *pYField = (*pField)["y"]; + AssetTypeValueField *pZField = (*pField)["z"]; + if (!pXField->IsDummy() && !pYField->IsDummy() && !pZField->IsDummy()) + { + x = pXField->GetValue()->AsFloat(); + y = pYField->GetValue()->AsFloat(); + z = pZField->GetValue()->AsFloat(); + return true; + } + return false; +} +bool AABB::Read(AssetTypeValueField *pField) +{ + AssetTypeValueField *pCenterField = (*pField)["m_Center"]; + AssetTypeValueField *pExtentField = (*pField)["m_Extent"]; + if (!pCenterField->IsDummy() && !pExtentField->IsDummy()) + { + return m_Center.Read(pCenterField) && m_Extent.Read(pExtentField); + } + return false; +} +bool MinMaxAABB::Read(AssetTypeValueField *pField) +{ + AssetTypeValueField *pMinField = (*pField)["m_Min"]; + AssetTypeValueField *pMaxField = (*pField)["m_Max"]; + if (!pMinField->IsDummy() && !pMaxField->IsDummy()) + { + return m_Min.Read(pMinField) && m_Max.Read(pMaxField); + } + return false; +} +bool Quaternionf::Read(AssetTypeValueField *pField) +{ + AssetTypeValueField *pXField = (*pField)["x"]; + AssetTypeValueField *pYField = (*pField)["y"]; + AssetTypeValueField *pZField = (*pField)["z"]; + AssetTypeValueField *pWField = (*pField)["w"]; + if (!pXField->IsDummy() && !pYField->IsDummy() && !pZField->IsDummy() && !pWField->IsDummy()) + { + x = pXField->GetValue()->AsFloat(); + y = pYField->GetValue()->AsFloat(); + z = pZField->GetValue()->AsFloat(); + w = pWField->GetValue()->AsFloat(); + return true; + } + return false; +} +bool Matrix4x4f::Read(AssetTypeValueField *pField) +{ + if (pField->GetChildrenCount() == 4*4) + { + for (unsigned int i = 0; i < 16; i++) + { + AssetTypeValueField *pNumField = pField->Get(i); + AssetTypeValue *pValue = pNumField->GetValue(); + if (pValue == NULL || pValue->GetType() != ValueType_Float) + return false; + e[i >> 2][i & 3] = pValue->AsFloat(); + } + return true; + } + return false; +} +bool SubMesh::Read(AssetTypeValueField *pField) +{ + AssetTypeValueField *pFirstByteField = (*pField)["firstByte"]; + AssetTypeValueField *pIndexCountField = (*pField)["indexCount"]; + AssetTypeValueField *pTopologyField = (*pField)["topology"]; + AssetTypeValueField *pBaseVertexField = (*pField)["baseVertex"]; + AssetTypeValueField *pFirstVertexField = (*pField)["firstVertex"]; + AssetTypeValueField *pVertexCountField = (*pField)["vertexCount"]; + AssetTypeValueField *pAABBField = (*pField)["localAABB"]; + if (!pFirstByteField->IsDummy() && !pIndexCountField->IsDummy() && !pTopologyField->IsDummy() && !pFirstVertexField->IsDummy() + && !pVertexCountField->IsDummy() && !pAABBField->IsDummy()) + { + firstByte = pFirstByteField->GetValue()->AsUInt(); + indexCount = pIndexCountField->GetValue()->AsUInt(); + topology = pTopologyField->GetValue()->AsInt(); + if (!pBaseVertexField->IsDummy()) + baseVertex = pBaseVertexField->GetValue()->AsUInt(); + else + baseVertex = 0; + firstVertex = pFirstVertexField->GetValue()->AsUInt(); + vertexCount = pVertexCountField->GetValue()->AsUInt(); + return localAABB.Read(pAABBField); + } + return false; +} +bool ChannelInfo::Read(AssetTypeValueField *pField) +{ + AssetTypeValueField *pStreamField = (*pField)["stream"]; + AssetTypeValueField *pOffsetField = (*pField)["offset"]; + AssetTypeValueField *pFormatField = (*pField)["format"]; + AssetTypeValueField *pDimensionField = (*pField)["dimension"]; + if (!pStreamField->IsDummy() && !pOffsetField->IsDummy() && !pFormatField->IsDummy() && !pDimensionField->IsDummy()) + { + stream = (unsigned char)pStreamField->GetValue()->AsUInt(); + offset = (unsigned char)pOffsetField->GetValue()->AsUInt(); + format = (unsigned char)pFormatField->GetValue()->AsUInt(); + dimension = (unsigned char)pDimensionField->GetValue()->AsUInt(); + dimension_flags = (dimension & 0xF0) >> 4; + dimension = dimension & 0x0F; + return true; + } + return false; +} +bool StreamInfo::Read(AssetTypeValueField *pField) +{ + AssetTypeValueField *pChannelMaskField = (*pField)["channelMask"]; + AssetTypeValueField *pOffsetField = (*pField)["offset"]; + AssetTypeValueField *pStrideField = (*pField)["stride"]; + AssetTypeValueField *pDividerOpField = (*pField)["dividerOp"]; + AssetTypeValueField *pFrequencyField = (*pField)["frequency"]; + if (!pChannelMaskField->IsDummy() && !pOffsetField->IsDummy() && !pStrideField->IsDummy() + && !pDividerOpField->IsDummy() && !pFrequencyField->IsDummy()) + { + channelMask = pChannelMaskField->GetValue()->AsUInt(); + offset = pOffsetField->GetValue()->AsUInt(); + stride = (unsigned char)pStrideField->GetValue()->AsUInt(); + dividerOp = (unsigned char)pDividerOpField->GetValue()->AsUInt(); + frequency = (unsigned short)pFrequencyField->GetValue()->AsUInt(); + return true; + } + return false; +} +VertexData::VertexData() +{ + wasAbleToRead = false; + _hasOwnData = false; + m_CurrentChannels = -1; + m_VertexCount = 0; + m_Channels = std::vector(); + m_Streams = std::vector(); + dataByteCount = 0; + m_DataSize = NULL; +} +VertexData::~VertexData() +{ + if (_hasOwnData && m_DataSize != nullptr) + { + delete[] ((uint8_t*)m_DataSize); + dataByteCount = 0; + m_DataSize = nullptr; + _hasOwnData = false; + } +} +VertexData& VertexData::operator=(const VertexData& src) +{ + wasAbleToRead = src.wasAbleToRead; + m_CurrentChannels = src.m_CurrentChannels; + m_VertexCount = src.m_VertexCount; + m_Channels.assign(src.m_Channels.begin(), src.m_Channels.end()); + m_Streams.assign(src.m_Streams.begin(), src.m_Streams.end()); + dataByteCount = src.dataByteCount; + m_DataSize = src.m_DataSize; + if (src._hasOwnData && m_DataSize != nullptr) + { + m_DataSize = new uint8_t[dataByteCount]; + memcpy(m_DataSize, src.m_DataSize, dataByteCount); + _hasOwnData = true; + } + else + _hasOwnData = false; + return *this; +} +VertexData& VertexData::operator=(VertexData&& src) +{ + wasAbleToRead = src.wasAbleToRead; + m_CurrentChannels = src.m_CurrentChannels; + m_VertexCount = src.m_VertexCount; + m_Channels = std::move(src.m_Channels); + m_Streams = std::move(src.m_Streams); + dataByteCount = src.dataByteCount; + m_DataSize = src.m_DataSize; + _hasOwnData = src._hasOwnData; + src._hasOwnData = false; + src.m_DataSize = nullptr; + return *this; +} +VertexData::VertexData(AssetTypeValueField* pField, + AppContext& appContext, class StreamingInfo& streamInfo, AssetIdentifier &meshAsset, + std::vector& boneWeights, bool unity5OrNewer) +{ + const uint8_t formatCount = unity5OrNewer ? 12 : 4; + m_Channels = std::vector(); + wasAbleToRead = false; + _hasOwnData = false; + AssetTypeValueField *pCurrentChannelsField = (*pField)["m_CurrentChannels"]; + AssetTypeValueField *pVertexCountField = (*pField)["m_VertexCount"]; + AssetTypeValueField *pChannelsField = (*(*pField)["m_Channels"])["Array"]; + AssetTypeValueField *pDataField = (*pField)["m_DataSize"]; + if (!pVertexCountField->IsDummy() && !pChannelsField->IsDummy() && !pDataField->IsDummy()) + { + wasAbleToRead = true; + dataByteCount = pDataField->GetValue()->AsByteArray()->size; + m_DataSize = pDataField->GetValue()->AsByteArray()->data; + if (!pCurrentChannelsField->IsDummy()) + m_CurrentChannels = pCurrentChannelsField->GetValue()->AsInt(); + else + m_CurrentChannels = -1; + m_VertexCount = pVertexCountField->GetValue()->AsUInt(); + int channelCount = pChannelsField->GetValue()->AsArray()->size; + for (int i = 0; i < channelCount; i++) + { + ChannelInfo channel; + if (!channel.Read((*pChannelsField)[i])) + wasAbleToRead = false; + else + m_Channels.push_back(channel); + } + AssetTypeValueField *pStreamsField = (*(*pField)["m_Streams"])["Array"]; + if (!pStreamsField->IsDummy() && pStreamsField->GetValue()) + { + uint32_t streamCount = pStreamsField->GetValue()->AsArray()->size; + for (uint32_t i = 0; i < streamCount; i++) + { + StreamInfo curStream; + if (!curStream.Read(pStreamsField->Get(i))) + wasAbleToRead = false; + else + m_Streams.push_back(curStream); + } + { + uint32_t maxStream = streamCount - 1; + for (size_t i = 0; i < m_Channels.size(); i++) + if (m_Channels[i].stream > maxStream) + maxStream = m_Channels[i].stream; + StreamInfo tmpStream = {}; + for (uint32_t i = streamCount; i <= maxStream; i++) + m_Streams.push_back(tmpStream); + } + } + else + { + uint32_t maxStream = 0; + for (size_t i = 0; i < m_Channels.size(); i++) + if (m_Channels[i].stream > maxStream) + maxStream = m_Channels[i].stream; + StreamInfo tmpStream = {}; + for (uint32_t i = 0; i <= maxStream; i++) + m_Streams.push_back(tmpStream); + for (size_t i = 0; i < m_Channels.size(); i++) + { + ChannelInfo &channel = m_Channels[i]; + m_Streams[channel.stream].channelMask |= (1 << (int)(uint8_t)i); + unsigned int curSize = channel.offset + channel.dimension * ChannelElementSize(channel.format, unity5OrNewer); + if (curSize > m_Streams[channel.stream].stride) + m_Streams[channel.stream].stride = curSize; + + if (channel.stream >= m_Streams.size() && m_VertexCount > 0) + wasAbleToRead = false; + if (channel.format >= formatCount) + wasAbleToRead = false; + } + unsigned int curOffset = 0; + for (size_t i = 0; i < m_Streams.size(); i++) + { + //no idea if there is any alignment + //m_Streams[i].stride = (m_Streams[i].stride + 3) & (~3) + m_Streams[i].offset = curOffset; + //m_Streams[i].offset = curOffset; + curOffset += m_Streams[i].stride * m_VertexCount; + } + if (curOffset > dataByteCount && curOffset > streamInfo.size) + wasAbleToRead = false; + if (m_Streams.size() == 2) //sometimes there are additional 8 bytes between both streams + m_Streams[1].offset = (dataByteCount ? dataByteCount : streamInfo.size) - (m_Streams[1].stride * m_VertexCount); + } + + if (wasAbleToRead && (streamInfo.size > 0)) + { + std::shared_ptr pResourcesFile = nullptr; + std::shared_ptr pStreamReader = nullptr; + try { + pResourcesFile = FindResourcesFile(appContext, streamInfo.path, meshAsset, {}); + pStreamReader = pResourcesFile->getResource(pResourcesFile, + streamInfo.offset, + streamInfo.size); + if (pStreamReader == nullptr) + throw AssetUtilError("Unable to locate the texture resource."); + } + catch (AssetUtilError e) + { + //TODO: Proper error reporting + // Have to make all allocations RAII to prevent leaks with thrown exceptions. + wasAbleToRead = false; + } + if (pStreamReader != nullptr) + { + m_DataSize = new uint32_t[streamInfo.size]; + dataByteCount = pStreamReader->Read(streamInfo.size, m_DataSize); + + if (dataByteCount != streamInfo.size) + { + wasAbleToRead = false; + delete[] ((uint32_t*)m_DataSize); + m_DataSize = nullptr; + dataByteCount = 0; + } + _hasOwnData = true; + } + else + wasAbleToRead = false; + } + + if (wasAbleToRead && m_Channels.size() >= 14 + && m_Channels[12].dimension == 4 && IsFloatFormat(m_Channels[12].format, unity5OrNewer) + && m_Channels[13].dimension == 4 && IsUIntFormat(m_Channels[13].format, unity5OrNewer)) + { + ChannelInfo &weightChannel = m_Channels[12]; + ChannelInfo &indexChannel = m_Channels[13]; + StreamInfo &weightStream = m_Streams[weightChannel.stream]; + StreamInfo &indexStream = m_Streams[indexChannel.stream]; + uint32_t weightVertexSize = 4 * ChannelElementSize(weightChannel.format, unity5OrNewer); + uint32_t indexVertexSize = 4 * ChannelElementSize(indexChannel.format, unity5OrNewer); + boneWeights.resize(m_VertexCount); + for (unsigned int i = 0; i < m_VertexCount; i++) + { + void* pCurWeightVertex = &((uint8_t*)m_DataSize)[weightStream.offset + i * weightStream.stride + weightChannel.offset]; + void* pCurIndexVertex = &((uint8_t*)m_DataSize)[indexStream.offset + i * indexStream.stride + indexChannel.offset]; + if ((weightStream.offset + i * weightStream.stride + weightChannel.offset + weightVertexSize) <= dataByteCount + && (indexStream.offset + i * indexStream.stride + indexChannel.offset + indexVertexSize) <= dataByteCount) + { + ConvertChannelFloat(pCurWeightVertex, weightChannel.format, 4, boneWeights[i].weight, unity5OrNewer); + ConvertChannelUInt32(pCurIndexVertex, indexChannel.format, 4, boneWeights[i].boneIndex, unity5OrNewer); + } + else + { + boneWeights.clear(); + wasAbleToRead = false; + break; + } + } + } + } +} + +bool VertexData::ConvertChannelFloat(const void *inData, uint8_t inFormat, uint8_t dimension, float *outData, bool unity5OrNewer) +{ + if (!unity5OrNewer) + { + if (inFormat == 2) //uint32 + return false; + if (inFormat == 3) //snorm8 + inFormat = 4; + } + switch (inFormat) + { + case 0: //float32 + { + const float *fInData = (const float*)inData; + memcpy(outData, fInData, 4*dimension); + } + return true; + case 1: //float16 + { + const HalfFloat *hfInData = (const HalfFloat*)inData; + for (uint8_t i = 0; i < dimension; i++) + { + HalfFloat curHF = {hfInData[i].half}; + outData[i] = curHF.toFloat(); + } + } + return true; + case 2: //unorm8 + case 3: //unorm8 + { + const unsigned __int8 *bInData = (const unsigned __int8*)inData; + for (uint8_t i = 0; i < dimension; i++) + outData[i] = (((float)bInData[i]) / 255.0); + } + return true; + case 4: //snorm8 + { + const __int8 *bInData = (const __int8*)inData; + for (uint8_t i = 0; i < dimension; i++) + outData[i] = (bInData[i] == -128) ? -1.0f : (((float)bInData[i]) / 127.0); + } + return true; + case 5: //unorm16 + { + const unsigned __int16 *bInData = (const unsigned __int16*)inData; + for (uint8_t i = 0; i < dimension; i++) + outData[i] = (((float)bInData[i]) / 65535.0); + } + return true; + case 6: //snorm16 + { + const __int16 *bInData = (const __int16*)inData; + for (uint8_t i = 0; i < dimension; i++) + outData[i] = (bInData[i] == -32768) ? -1.0f : (((float)bInData[i]) / 32767.0); + } + return true; + default: + return false; + } +} +bool VertexData::ConvertChannelInt32(const void *inData, uint8_t inFormat, uint8_t dimension, int *outData, bool unity5OrNewer) +{ + if (!unity5OrNewer) + { + return false; + } + switch (inFormat) + { + case 8: //int8 + { + const __int8 *bInData = (const __int8*)inData; + for (uint8_t i = 0; i < dimension; i++) + outData[i] = (int)bInData[i]; + } + return true; + case 10: //int16 + { + const __int16 *sInData = (const __int16*)inData; + for (uint8_t i = 0; i < dimension; i++) + outData[i] = (int)sInData[i]; + } + return true; + case 12: //int32 + { + const __int32 *iInData = (const __int32*)inData; + memcpy(outData, iInData, 4 * dimension); + } + return true; + default: + return false; + } +} +bool VertexData::ConvertChannelUInt32(const void *inData, uint8_t inFormat, uint8_t dimension, unsigned int *outData, bool unity5OrNewer) +{ + if (!unity5OrNewer) + { + if (inFormat == 2) + inFormat = 11; + else + return false; + } + switch (inFormat) + { + case 7: //uint8 + case 8: //int8 + { + const unsigned __int8 *bInData = (const unsigned __int8*)inData; + for (uint8_t i = 0; i < dimension; i++) + outData[i] = (unsigned int)bInData[i]; + } + return true; + case 9: //uint16 + case 10: //int16 + { + const unsigned __int16 *sInData = (const unsigned __int16*)inData; + for (uint8_t i = 0; i < dimension; i++) + outData[i] = (unsigned int)sInData[i]; + } + return true; + case 11: //uint32 + case 12: //int32 + { + const unsigned __int32 *iInData = (const unsigned __int32*)inData; + memcpy(outData, iInData, 4 * dimension); + } + return true; + default: + return false; + } +} + +PackedBitVector::PackedBitVector() +{ + wasAbleToRead = false; + hasRangeAndStart = false; +} +bool PackedBitVector::Read(AssetTypeValueField *pField) +{ + AssetTypeValueField *pNumItems = (*pField)["m_NumItems"]; + AssetTypeValueField *pRange = (*pField)["m_Range"]; + AssetTypeValueField *pStart = (*pField)["m_Start"]; + AssetTypeValueField *pData = ((*pField)["m_Data"])->Get("Array"); + AssetTypeValueField *pBitSize = (*pField)["m_BitSize"]; + if ((!pNumItems->IsDummy() && pNumItems->GetValue() && pNumItems->GetValue()->GetType() == ValueType_UInt32) && + (!pData->IsDummy() && pData->GetValue() && pData->GetValue()->GetType() == ValueType_Array && + pData->GetTemplateField()->children.size() == 2 && pData->GetTemplateField()->children[1].valueType == ValueType_UInt8) && + (!pBitSize->IsDummy() && pBitSize->GetValue() && pBitSize->GetValue()->GetType() == ValueType_UInt8)) + { + wasAbleToRead = true; + m_NumItems = pNumItems->GetValue()->AsUInt(); + m_Data.clear(); + m_Data.reserve(pData->GetChildrenCount()); + for (unsigned int i = 0; i < pData->GetChildrenCount(); i++) + { + AssetTypeValueField *pCur = pData->Get(i); + if (!pCur->GetValue()) + { + wasAbleToRead = false; + break; + } + m_Data.push_back((unsigned char)pCur->GetValue()->AsUInt()); + } + m_BitSize = (unsigned char)pBitSize->GetValue()->AsUInt(); + if (!m_BitSize && m_NumItems) + bitSize = (unsigned char)((m_Data.size() * 8) / m_NumItems); + else + bitSize = m_BitSize; + if (bitSize > 32) + wasAbleToRead = false; + if ((!pRange->IsDummy() && pRange->GetValue() && pRange->GetValue()->GetType() == ValueType_Float) && + (!pStart->IsDummy() && pStart->GetValue() && pStart->GetValue()->GetType() == ValueType_Float)) + { + hasRangeAndStart = true; + m_Range = pRange->GetValue()->AsFloat(); + m_Start = pStart->GetValue()->AsFloat(); + } + else + hasRangeAndStart = false; + } + else + wasAbleToRead = false; + return wasAbleToRead; +} +unsigned char PackedBitVector::MakeByteMask(unsigned int bitOffset) +{ + return (unsigned char)(0xFF << bitOffset); +} +bool PackedBitVector::ReadValue(unsigned int index, unsigned int &out) +{ + out = 0; + if (bitSize == 0) + return true; + if (index >= m_NumItems) + return false; + unsigned int bitIndex = index * bitSize; //let's just assume that one compressed buffer doesn't use more than 512 MB + if (bitIndex >= (0UL - bitSize)) + return false; + unsigned int curByteIndex = bitIndex >> 3; + unsigned int curBitOffset = bitIndex & 7; + //Bounds checks; The start check is just there to prevent integer overflows. + if ((curByteIndex >= m_Data.size()) || (((bitIndex + bitSize) >> 3) > m_Data.size())) + return false; + //Copy the potentially not byte-aligned data first. + out = ((m_Data[curByteIndex] & MakeByteMask(curBitOffset)) >> curBitOffset); + unsigned int curOutBitOffset = 8 - curBitOffset; + curBitOffset = 0; + curByteIndex++; + //Copy the byte-aligned data. + while (curOutBitOffset < bitSize) + { + out |= m_Data[curByteIndex] << curOutBitOffset; + curOutBitOffset += 8; + curByteIndex++; + } + //Limit the data bits as the previous copies always read until the end of the current byte, while the actual value might end a couple of bits before. + out &= (1 << bitSize) - 1; //Even works when bitSize == 32. + return true; +} +bool PackedBitVector::ReadValueFloat(unsigned int index, float &out) +{ + if (!hasRangeAndStart) + return false; + unsigned int raw; + if (!ReadValue(index, raw)) + return false; + //Use double for the first step to reduce the overall error (there could be up to 32 bits representing a number between 0.0 and 1.0). + out = (float)((((double)raw) / ((1 << bitSize) - 1)) * m_Range + m_Start); + return true; +} +CompressedMesh::CompressedMesh() +{ + wasAbleToRead = false; +} +bool CompressedMesh::Read(AssetTypeValueField *pField) +{ + //If not stated otherwise, the following fields are present in U3.4 until (at least) U5.6. + AssetTypeValueField *pVertices = (*pField)["m_Vertices"]; + AssetTypeValueField *pUV = (*pField)["m_UV"]; + AssetTypeValueField *pBindPoses = (*pField)["m_BindPoses"]; //Present starting with U3.5 until U4.7 + AssetTypeValueField *pNormals = (*pField)["m_Normals"]; + AssetTypeValueField *pTangents = (*pField)["m_Tangents"]; + AssetTypeValueField *pWeights = (*pField)["m_Weights"]; + AssetTypeValueField *pNormalSigns = (*pField)["m_NormalSigns"]; + AssetTypeValueField *pTangentSigns = (*pField)["m_TangentSigns"]; + AssetTypeValueField *pFloatColors = (*pField)["m_FloatColors"]; //Present starting with U5.0 + AssetTypeValueField *pBoneIndices = (*pField)["m_BoneIndices"]; + AssetTypeValueField *pTriangles = (*pField)["m_Triangles"]; + AssetTypeValueField *pColors = (*pField)["m_Colors"]; //Present starting with U3.5 until U4.7 + AssetTypeValueField *pUVInfo = (*pField)["m_UVInfo"]; //Present starting with U5.0 + if (!pVertices->IsDummy() && !pUV->IsDummy()/* && !pBindPoses->IsDummy()*/ && + !pNormals->IsDummy() && !pTangents->IsDummy() && !pWeights->IsDummy() && + !pNormalSigns->IsDummy() && !pTangentSigns->IsDummy() && !pBoneIndices->IsDummy() && + !pTriangles->IsDummy()) + { + wasAbleToRead = true; + wasAbleToRead &= m_Vertices.Read(pVertices); + wasAbleToRead &= m_UV.Read(pUV); + if (!pBindPoses->IsDummy()) + wasAbleToRead &= m_BindPoses.Read(pBindPoses); + wasAbleToRead &= m_Normals.Read(pNormals); + wasAbleToRead &= m_Tangents.Read(pTangents); + wasAbleToRead &= m_Weights.Read(pWeights); + wasAbleToRead &= m_NormalSigns.Read(pNormalSigns); + wasAbleToRead &= m_TangentSigns.Read(pTangentSigns); + if (!pFloatColors->IsDummy()) + wasAbleToRead &= m_FloatColors.Read(pFloatColors); + wasAbleToRead &= m_BoneIndices.Read(pBoneIndices); + wasAbleToRead &= m_Triangles.Read(pTriangles); + if (!pColors->IsDummy()) + wasAbleToRead &= m_Colors.Read(pColors); + if (!pUVInfo->IsDummy() && pUVInfo->GetValue() && pUVInfo->GetValue()->GetType() == ValueType_UInt32) + m_UVInfo = pUVInfo->GetValue()->AsUInt(); + else + m_UVInfo = 0; + } + else + wasAbleToRead = false; + return wasAbleToRead; +} +bool BlendShapeVertex::Read(AssetTypeValueField *pField) +{ + AssetTypeValueField *pVertex = (*pField)["vertex"]; + AssetTypeValueField *pNormal = (*pField)["normal"]; + AssetTypeValueField *pTangent = (*pField)["tangent"]; + AssetTypeValueField *pIndex = (*pField)["index"]; + if (!pVertex->IsDummy() && !pNormal->IsDummy() && + !pTangent->IsDummy() && !pIndex->IsDummy()) + { + if (!vertex.Read(pVertex)) + return false; + if (!normal.Read(pNormal)) + return false; + if (!tangent.Read(pTangent)) + return false; + if (!pIndex->GetValue()) + return false; + index = pIndex->GetValue()->AsUInt(); + return true; + } + return false; +} +bool MeshBlendShape::Read(AssetTypeValueField *pField) +{ + AssetTypeValueField *pFirstVertex = (*pField)["firstVertex"]; + AssetTypeValueField *pVertexCount = (*pField)["vertexCount"]; + AssetTypeValueField *pHasNormals = (*pField)["hasNormals"]; + AssetTypeValueField *pHasTangents = (*pField)["hasTangents"]; + if (!pFirstVertex->IsDummy() && !pVertexCount->IsDummy() && + !pHasNormals->IsDummy() && !pHasTangents->IsDummy()) + { + if (!pFirstVertex->GetValue()) + return false; + firstVertex = pFirstVertex->GetValue()->AsUInt(); + if (!pVertexCount->GetValue()) + return false; + vertexCount = pVertexCount->GetValue()->AsUInt(); + if (!pHasNormals->GetValue()) + return false; + hasNormals = pHasNormals->GetValue()->AsBool(); + if (!pHasTangents->GetValue()) + return false; + hasTangents = pHasTangents->GetValue()->AsBool(); + return true; + } + return false; +} +bool MeshBlendShapeChannel::Read(AssetTypeValueField *pField) +{ + AssetTypeValueField *pName = (*pField)["name"]; + AssetTypeValueField *pNameHash = (*pField)["nameHash"]; + AssetTypeValueField *pFrameIndex = (*pField)["frameIndex"]; + AssetTypeValueField *pFrameCount = (*pField)["frameCount"]; + if (!pName->IsDummy() && !pNameHash->IsDummy() && + !pFrameIndex->IsDummy() && !pFrameCount->IsDummy()) + { + if (!pName->GetValue()) + return false; + name = pName->GetValue()->AsString(); + if (!pNameHash->GetValue()) + return false; + nameHash = pNameHash->GetValue()->AsUInt(); + if (!pFrameIndex->GetValue()) + return false; + frameIndex = pFrameIndex->GetValue()->AsInt(); + if (!pFrameCount->GetValue()) + return false; + frameCount = pFrameCount->GetValue()->AsInt(); + return true; + } + return false; +} +BlendShapeData::BlendShapeData(){} +bool BlendShapeData::Read(AssetTypeValueField *pField) +{ + wasAbleToRead = false; + AssetTypeValueField *pVertices = (*pField)["vertices"]->Get("Array"); + AssetTypeValueField *pShapes = (*pField)["shapes"]->Get("Array"); + AssetTypeValueField *pChannels = (*pField)["channels"]->Get("Array"); + AssetTypeValueField *pFullWeights = (*pField)["fullWeights"]->Get("Array"); + if (!pVertices->IsDummy() && !pShapes->IsDummy() && + !pChannels->IsDummy() && !pFullWeights->IsDummy()) + { + vertices.resize(pVertices->GetChildrenCount()); + for (unsigned int i = 0; i < pVertices->GetChildrenCount(); i++) + { + if (!vertices[i].Read(pVertices->Get(i))) + { + vertices.clear(); + return false; + } + } + + shapes.resize(pShapes->GetChildrenCount()); + for (unsigned int i = 0; i < pShapes->GetChildrenCount(); i++) + { + if (!shapes[i].Read(pShapes->Get(i))) + { + vertices.clear(); + shapes.clear(); + return false; + } + } + + channels.resize(pChannels->GetChildrenCount()); + for (unsigned int i = 0; i < pChannels->GetChildrenCount(); i++) + { + if (!channels[i].Read(pChannels->Get(i))) + { + vertices.clear(); + shapes.clear(); + channels.clear(); + return false; + } + } + + fullWeights.resize(pFullWeights->GetChildrenCount()); + for (unsigned int i = 0; i < pFullWeights->GetChildrenCount(); i++) + { + AssetTypeValueField *pEntry = pFullWeights->Get(i); + if (!pEntry->GetValue() || pEntry->GetValue()->GetType() != ValueType_Float) + { + vertices.clear(); + shapes.clear(); + channels.clear(); + fullWeights.clear(); + return false; + } + fullWeights[i] = pEntry->GetValue()->AsFloat(); + } + wasAbleToRead = true; + return true; + } + return false; +} + +bool BoneInfluence::Read(AssetTypeValueField *pField) +{ + AssetTypeValueField *pWeight[4] = { + (*pField)["weight[0]"], (*pField)["weight[1]"], (*pField)["weight[2]"], (*pField)["weight[3]"] + }; + AssetTypeValueField *pBoneIndex[4] = { + (*pField)["boneIndex[0]"], (*pField)["boneIndex[1]"], (*pField)["boneIndex[2]"], (*pField)["boneIndex[3]"] + }; + for (int i = 0; i < 4; i++) + { + if (pWeight[i]->GetValue() == NULL || pBoneIndex[i]->GetValue() == NULL || pWeight[i]->GetValue()->GetType() != ValueType_Float) + return false; + weight[i] = pWeight[i]->GetValue()->AsFloat(); + boneIndex[i] = pBoneIndex[i]->GetValue()->AsUInt(); + } + return true; +} + +VariableBoneCountWeights::VariableBoneCountWeights() {} +bool VariableBoneCountWeights::Read(AssetTypeValueField *pField) +{ + AssetTypeValueField *pDataArray = (*(*pField)["m_Data"])["Array"]; + if (!pDataArray->IsDummy()) + { + m_Data.resize(pDataArray->GetChildrenCount()); + for (unsigned int i = 0; i < pDataArray->GetChildrenCount(); i++) + { + AssetTypeValue *pValue = pDataArray->Get(i)->GetValue(); + if (!pValue || pValue->GetType() != ValueType_UInt32) + return false; + m_Data[i] = pValue->AsUInt(); + } + return true; + } + return false; +} + +StreamingInfo::StreamingInfo() +{ + this->wasAbleToRead = false; + this->size = 0; + this->offset = 0; +} +bool StreamingInfo::Read(AssetTypeValueField *pField) +{ + this->wasAbleToRead = false; + AssetTypeValueField *pOffset = (*pField)["offset"]; + AssetTypeValueField *pSize = (*pField)["size"]; + AssetTypeValueField *pPath = (*pField)["path"]; + if (pOffset->GetValue() && pOffset->GetValue()->GetType() == ValueType_UInt32 && + pSize->GetValue() && pSize->GetValue()->GetType() == ValueType_UInt32 && + pPath->GetValue() && pPath->GetValue()->GetType() == ValueType_String && pPath->GetValue()->AsString()) + { + this->offset = pOffset->GetValue()->AsUInt64(); + this->size = pSize->GetValue()->AsUInt(); + this->path = std::string(pPath->GetValue()->AsString()); + this->wasAbleToRead = true; + } + return this->wasAbleToRead; +} + +Mesh::Mesh(AssetTypeValueField *pField, bool unity5OrNewer, AppContext &appContext, AssetIdentifier &asset) +{ + m_SubMeshes = std::vector(); + m_IndexBuffer = std::vector(); + m_VertexData = VertexData(); + wasAbleToRead = false; + AssetTypeValueField *pNameField = (*pField)["m_Name"]; + AssetTypeValueField *pSubMeshesField = (*(*pField)["m_SubMeshes"])["Array"]; + AssetTypeValueField *pShapesField = (*pField)["m_Shapes"]; + AssetTypeValueField *pBindPoseField = (*(*pField)["m_BindPose"])["Array"]; + AssetTypeValueField *pBoneNameHashesField = (*(*pField)["m_BoneNameHashes"])["Array"]; + AssetTypeValueField *pRootBoneNameHashField = (*pField)["m_RootBoneNameHash"]; + AssetTypeValueField *pBonesAABBField = (*(*pField)["m_BonesAABB"])["Array"]; + AssetTypeValueField *pVariableBoneCountWeightsField = (*pField)["m_VariableBoneCountWeights"]; + AssetTypeValueField *pMeshCompressionField = (*pField)["m_MeshCompression"]; + AssetTypeValueField *pIndexFormatField = (*pField)["m_IndexFormat"]; + AssetTypeValueField *pIndexBufferField = (*(*pField)["m_IndexBuffer"])["Array"]; + AssetTypeValueField *pSkinField = (*(*pField)["m_Skin"])["Array"]; + AssetTypeValueField *pVertexDataField = (*pField)["m_VertexData"]; + AssetTypeValueField *pCompressedMeshField = (*pField)["m_CompressedMesh"]; + AssetTypeValueField *pLocalAABBField = (*pField)["m_LocalAABB"]; + AssetTypeValueField *pStreamDataField = (*pField)["m_StreamData"]; + if (!pNameField->IsDummy() && !pSubMeshesField->IsDummy() && !pMeshCompressionField->IsDummy() && !pIndexBufferField->IsDummy() + && !pVertexDataField->IsDummy() && !pLocalAABBField->IsDummy()) + { + wasAbleToRead = true; + m_Name = pNameField->GetValue()->AsString(); + if (!m_Name) + wasAbleToRead = false; //to prevent access violations + uint32_t subMeshCount = pSubMeshesField->GetValue()->AsArray()->size; + m_SubMeshes.reserve(subMeshCount); + for (uint32_t i = 0; i < subMeshCount; i++) + { + SubMesh subMesh; + if (!subMesh.Read((*pSubMeshesField)[i])) + wasAbleToRead = false; + else + m_SubMeshes.push_back(subMesh); + } + + if (!pShapesField->IsDummy()) + m_Shapes.Read(pShapesField); + else + m_Shapes.wasAbleToRead = false; + if (!pBindPoseField->IsDummy()) + { + m_BindPose.resize(pBindPoseField->GetChildrenCount()); + for (unsigned int i = 0; i < pBindPoseField->GetChildrenCount(); i++) + { + if (!m_BindPose[i].Read(pBindPoseField->Get(i))) + { + m_BindPose.clear(); + break; + } + } + } + if (!pBoneNameHashesField->IsDummy()) + { + m_BoneNameHashes.resize(pBoneNameHashesField->GetChildrenCount()); + for (unsigned int i = 0; i < pBindPoseField->GetChildrenCount(); i++) + { + AssetTypeValue *pValue = pBoneNameHashesField->Get(i)->GetValue(); + if (!pValue) + { + m_BoneNameHashes.clear(); + break; + } + m_BoneNameHashes[i] = pValue->AsUInt(); + } + } + if (pRootBoneNameHashField->GetValue()) + m_RootBoneNameHash = pRootBoneNameHashField->GetValue()->AsUInt(); + else + m_RootBoneNameHash = 0; + + if (!pBonesAABBField->IsDummy()) + { + m_BonesAABB.resize(pBonesAABBField->GetChildrenCount()); + for (unsigned int i = 0; i < pBonesAABBField->GetChildrenCount(); i++) + { + if (!m_BonesAABB[i].Read(pBonesAABBField->Get(i))) + { + m_BonesAABB.clear(); + break; + } + } + } + if (!pVariableBoneCountWeightsField->IsDummy()) + { + m_VariableBoneCountWeights.Read(pVariableBoneCountWeightsField); + } + + m_MeshCompression = (unsigned char)pMeshCompressionField->GetValue()->AsUInt(); + + if (!pIndexFormatField->IsDummy()) + m_IndexFormat = pIndexFormatField->GetValue()->AsInt(); + else + m_IndexFormat = 0; //16 bit is default. + + int indexCount = pIndexBufferField->GetValue()->AsArray()->size; + switch (m_IndexFormat) + { + case 0: //UInt16 + if (indexCount & 1) + wasAbleToRead = false; //the indices obviously are no shorts + else + { + m_IndexBuffer.reserve(indexCount/2); //index array is a byte array; the indices always(?) are shorts + for (int i = 0; i < (indexCount-1); i+=2) + { + m_IndexBuffer.push_back((unsigned short)(*pIndexBufferField)[i]->GetValue()->AsInt() | ((unsigned short)(*pIndexBufferField)[i+1]->GetValue()->AsInt() << 8)); + } + } + break; + case 1: //UInt32 + if (indexCount & 3) + wasAbleToRead = false; //the indices obviously are no ints + else + { + m_IndexBuffer.reserve(indexCount/4); //index array is a byte array; the indices always are ints + for (int i = 0; i < (indexCount-3); i+=4) + { + m_IndexBuffer.push_back( + (unsigned int)(*pIndexBufferField)[i]->GetValue()->AsInt() + | ((unsigned int)(*pIndexBufferField)[i+1]->GetValue()->AsInt() << 8) + | ((unsigned int)(*pIndexBufferField)[i+2]->GetValue()->AsInt() << 16) + | ((unsigned int)(*pIndexBufferField)[i+3]->GetValue()->AsInt() << 24) + ); + } + } + break; + default: + wasAbleToRead = false; + break; + } + + if (!pSkinField->IsDummy()) + { + m_Skin.resize(pSkinField->GetChildrenCount()); + for (unsigned int i = 0; i < pSkinField->GetChildrenCount(); i++) + { + if (!m_Skin[i].Read(pSkinField->Get(i))) + { + m_Skin.clear(); + break; + } + } + } + + if (!pStreamDataField->IsDummy()) + m_StreamData.Read(pStreamDataField); + + m_VertexData = VertexData(pVertexDataField, appContext, m_StreamData, asset, m_Skin, unity5OrNewer); + if (!m_VertexData.wasAbleToRead) + wasAbleToRead = false; + if (!pCompressedMeshField->IsDummy()) + m_CompressedMesh.Read(pCompressedMeshField); + if (!m_LocalAABB.Read(pLocalAABBField)) + wasAbleToRead = false; + } +} \ No newline at end of file diff --git a/Plugins/TextAsset/CMakeLists.txt b/Plugins/TextAsset/CMakeLists.txt new file mode 100644 index 0000000..13bb423 --- /dev/null +++ b/Plugins/TextAsset/CMakeLists.txt @@ -0,0 +1,7 @@ +add_library (TextAsset SHARED "TextAsset.cpp" TextAsset.def) +target_include_directories (TextAsset PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +set_target_properties(TextAsset PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/Plugins" + SUFFIX ".bep") +target_link_libraries(TextAsset PUBLIC UABE_Generic AssetsTools libStringConverter) diff --git a/Plugins/TextAsset/TextAsset.cpp b/Plugins/TextAsset/TextAsset.cpp new file mode 100644 index 0000000..3203db7 --- /dev/null +++ b/Plugins/TextAsset/TextAsset.cpp @@ -0,0 +1,294 @@ +#include "../UABE_Generic/PluginManager.h" +#include "../UABE_Generic/FileContextInfo.h" +#include "../UABE_Generic/AppContext.h" +#include +#include + +static bool SupportsElements(std::vector& elements) +{ + std::unordered_map textClassIDs; + for (size_t i = 0; i < elements.size(); i++) + { + if (elements[i].asset.pFile == nullptr) + return false; + AssetsFileContextInfo* pFile = elements[i].asset.pFile.get(); + auto classIDsit = textClassIDs.find(pFile); + int32_t textAssetClassID = -1; + if (classIDsit == textClassIDs.end()) + { + textAssetClassID = pFile->GetClassByName("TextAsset"); + textClassIDs[pFile] = textAssetClassID; + } + else + textAssetClassID = classIDsit->second; + if (textAssetClassID == -1) + return false; + int32_t classId = elements[i].asset.getClassID(); + if (classId != textAssetClassID) + return false; + } + return true; +} + +static void SubstituteTextAssetStringType(AssetTypeTemplateField& templateBase) +{ + for (uint32_t i = 0; i < templateBase.children.size(); i++) + { + if (templateBase.children[i].name == "m_Script") + { + templateBase.children[i].type = "_string"; + templateBase.children[i].valueType = ValueType_None; + for (uint32_t k = 0; k < templateBase.children[i].children.size(); k++) + { + if (templateBase.children[i].children[k].name == "Array") + { + templateBase.children[i].children[k].type = "TypelessData"; + break; + } + } + break; + } + } +} + +static void FreeByteBufCallback(void* buffer) +{ + if (buffer) + delete[](uint8_t*)buffer; +} + +class TextAssetImportTask : public AssetImportTask +{ + AppContext& appContext; + TypeTemplateCache templateCache; +public: + TextAssetImportTask(AppContext& appContext, + std::vector _assets, std::vector _importFilePaths, + bool stopOnError = false) + + : AssetImportTask(std::move(_assets), std::move(_importFilePaths), "Import TextAssets", stopOnError), + appContext(appContext) + {} + + bool importAsset(AssetUtilDesc& desc, std::string path, std::optional> progressManager) + { + if (desc.asset.pFile == nullptr) + throw AssetUtilError("Unable to find the target .assets file."); + + IAssetsReader_ptr pAssetReader = desc.asset.makeReader(); + if (pAssetReader == nullptr) + throw AssetUtilError("Unable to read the asset."); + QWORD assetSize = 0; + if (!pAssetReader->Seek(AssetsSeek_End, 0) || !pAssetReader->Tell(assetSize) || !pAssetReader->Seek(AssetsSeek_Begin, 0)) + throw AssetUtilError("Unable to read the asset."); + + AssetTypeTemplateField& templateBase = templateCache.getTemplateField(appContext, desc.asset, &SubstituteTextAssetStringType); + AssetTypeTemplateField* pTemplateBase = &templateBase; + + AssetTypeInstance assetInstance(1, &pTemplateBase, assetSize, pAssetReader.get(), desc.asset.isBigEndian()); + AssetTypeValueField* pBaseField = assetInstance.GetBaseField(); + if (pBaseField == nullptr || pBaseField->IsDummy()) + throw AssetUtilError("Unable to deserialize the asset."); + + AssetTypeValueField* scriptField = pBaseField->Get("m_Script"); + AssetTypeValueField* dataArrayField = scriptField->Get("Array"); + if (scriptField->IsDummy() || dataArrayField->GetValue() == nullptr || dataArrayField->GetValue()->GetType() != ValueType_ByteArray) + throw AssetUtilError("Unexpected TextAsset format."); + + std::unique_ptr pTextReader(Create_AssetsReaderFromFile(path.c_str(), true, RWOpenFlags_Immediately));// desc.asset.makeReader(); + if (pTextReader == nullptr) + throw AssetUtilError("Unable to read the text file."); + QWORD textSize = 0; + if (!pTextReader->Seek(AssetsSeek_End, 0) || !pTextReader->Tell(textSize) || !pTextReader->Seek(AssetsSeek_Begin, 0)) + throw AssetUtilError("Unable to read the text file."); + if (textSize >= INT32_MAX) + throw AssetUtilError("The text file is too large (should be below 2 GiB)."); + std::unique_ptr textBuf(new uint8_t[(size_t)textSize]); + if (pTextReader->Read(textSize, textBuf.get()) != textSize) + throw AssetUtilError("Unable to read the text file."); + + AssetTypeByteArray byteArrayValue = {}; + byteArrayValue.data = textBuf.get(); + byteArrayValue.size = (uint32_t)textSize; + dataArrayField->GetValue()->Set(&byteArrayValue); + + QWORD outSize = pBaseField->GetByteSize(0); + if (outSize >= SIZE_MAX) + throw AssetUtilError("Import size out of range."); + std::unique_ptr newDataBuf(new uint8_t[outSize]); + std::unique_ptr pTempWriter(Create_AssetsWriterToMemory(newDataBuf.get(), outSize)); + if (pTempWriter == nullptr) + throw AssetUtilError("Unexpected runtime error."); + QWORD newByteSize = pBaseField->Write(pTempWriter.get(), 0, desc.asset.isBigEndian()); + + std::shared_ptr pReplacer(MakeAssetModifierFromMemory(0, desc.asset.pathID, + desc.asset.getClassID(), desc.asset.getMonoScriptID(), + newDataBuf.release(), (size_t)newByteSize, FreeByteBufCallback)); + if (pReplacer == nullptr) + throw AssetUtilError("Unexpected runtime error."); + desc.asset.pFile->addReplacer(pReplacer, appContext); + return true; + } +}; +class TextAssetImportProvider : public IAssetOptionProviderGeneric +{ +public: + class Runner : public IOptionRunner + { + AppContext& appContext; + std::vector selection; + public: + Runner(AppContext& appContext, std::vector _selection) + : appContext(appContext), selection(std::move(_selection)) + {} + void operator()() + { + std::vector importLocations = appContext.QueryAssetImportLocation(selection, ".txt", "\\.txt", "*.txt|Text file:"); + if (!importLocations.empty()) + { + auto pTask = std::make_shared(appContext, std::move(selection), std::move(importLocations)); + appContext.taskManager.enqueue(pTask); + } + } + }; + EAssetOptionType getType() + { + return EAssetOptionType::Import; + } + std::unique_ptr prepareForSelection( + class AppContext& appContext, + std::vector selection, + std::string& optionName) + { + if (!SupportsElements(selection)) + return nullptr; + optionName = "Import from .txt"; + return std::make_unique(appContext, std::move(selection)); + } +}; + + +class TextAssetExportTask : public AssetExportTask +{ + AppContext& appContext; + TypeTemplateCache templateCache; +public: + TextAssetExportTask(AppContext& appContext, + std::vector _assets, std::string _baseDir, + bool stopOnError = false) + + : AssetExportTask(std::move(_assets), "Export TextAssets", ".txt", std::move(_baseDir), stopOnError), + appContext(appContext) + {} + + bool exportAsset(AssetUtilDesc& desc, std::string path, std::optional> progressManager) + { + if (desc.asset.pFile == nullptr) + throw AssetUtilError("Unable to find the target .assets file."); + + IAssetsReader_ptr pAssetReader = desc.asset.makeReader(); + if (pAssetReader == nullptr) + throw AssetUtilError("Unable to read the asset."); + QWORD assetSize = 0; + if (!pAssetReader->Seek(AssetsSeek_End, 0) || !pAssetReader->Tell(assetSize) || !pAssetReader->Seek(AssetsSeek_Begin, 0)) + throw AssetUtilError("Unable to read the asset."); + + AssetTypeTemplateField& templateBase = templateCache.getTemplateField(appContext, desc.asset, &SubstituteTextAssetStringType); + AssetTypeTemplateField* pTemplateBase = &templateBase; + + AssetTypeInstance assetInstance(1, &pTemplateBase, assetSize, pAssetReader.get(), desc.asset.isBigEndian()); + AssetTypeValueField* pBaseField = assetInstance.GetBaseField(); + if (pBaseField == nullptr || pBaseField->IsDummy()) + throw AssetUtilError("Unable to deserialize the asset."); + + AssetTypeValueField* scriptField = pBaseField->Get("m_Script"); + AssetTypeValueField* dataArrayField = scriptField->Get("Array"); + if (scriptField->IsDummy() || dataArrayField->GetValue() == nullptr || dataArrayField->GetValue()->GetType() != ValueType_ByteArray) + throw AssetUtilError("Unexpected TextAsset format."); + + AssetTypeByteArray* pByteArray = dataArrayField->GetValue()->AsByteArray(); + + std::unique_ptr pWriter(Create_AssetsWriterToFile(path.c_str(), true, true, RWOpenFlags_Immediately)); + if (pWriter == nullptr) + throw AssetUtilError("Unable to create the output file."); + + if (pWriter->Write(pByteArray->size, pByteArray->data) != pByteArray->size) + throw AssetUtilError("Unable to write the data."); + + return true; + } +}; +class TextAssetExportProvider : public IAssetOptionProviderGeneric +{ +public: + class Runner : public IOptionRunner + { + AppContext& appContext; + std::vector selection; + public: + Runner(AppContext& appContext, std::vector _selection) + : appContext(appContext), selection(std::move(_selection)) + {} + void operator()() + { + std::string exportLocation = appContext.QueryAssetExportLocation(selection, ".txt", "*.txt|Text file:"); + if (!exportLocation.empty()) + { + auto pTask = std::make_shared(appContext, std::move(selection), std::move(exportLocation)); + appContext.taskManager.enqueue(pTask); + } + } + }; + EAssetOptionType getType() + { + return EAssetOptionType::Export; + } + std::unique_ptr prepareForSelection( + class AppContext& appContext, + std::vector selection, + std::string& optionName) + { + if (!SupportsElements(selection)) + return nullptr; + optionName = "Export to .txt"; + return std::make_unique(appContext, std::move(selection)); + } +}; + +class TextAssetPluginDesc : public IPluginDesc +{ + std::vector> pProviders; +public: + TextAssetPluginDesc() + { + pProviders = { std::make_shared(), std::make_shared() }; + } + std::string getName() + { + return "TextAsset"; + } + std::string getAuthor() + { + return ""; + } + std::string getDescriptionText() + { + return "Export and import the content of TextAsset assets."; + } + //The IPluginDesc object should keep a reference to the returned options, as the caller may keep only std::weak_ptrs. + //Note: May be called early, e.g. before program UI initialization. + std::vector> getPluginOptions(class AppContext& appContext) + { + return pProviders; + } +}; + +IPluginDesc* GetUABEPluginDesc1(size_t sizeof_AppContext, size_t sizeof_BundleFileContextInfo) +{ + if (sizeof_AppContext != sizeof(AppContext) || sizeof_BundleFileContextInfo != sizeof(BundleFileContextInfo)) + { + assert(false); + return nullptr; + } + return new TextAssetPluginDesc(); +} diff --git a/Plugins/TextAsset/TextAsset.def b/Plugins/TextAsset/TextAsset.def new file mode 100644 index 0000000..fe4f66b --- /dev/null +++ b/Plugins/TextAsset/TextAsset.def @@ -0,0 +1,3 @@ +LIBRARY TextAsset +EXPORTS + GetUABEPluginDesc1 diff --git a/Plugins/Texture/CMakeLists.txt b/Plugins/Texture/CMakeLists.txt new file mode 100644 index 0000000..88a13c5 --- /dev/null +++ b/Plugins/Texture/CMakeLists.txt @@ -0,0 +1,7 @@ +add_library (Texture SHARED "Texture.cpp" "lodepng.cpp" Texture.rc Texture.def "TextureWin32.cpp" "Texture.h") +target_include_directories (Texture PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +set_target_properties(Texture PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/Plugins" + SUFFIX ".bep") +target_link_libraries(Texture PRIVATE UABE_Generic UABE_Win32 AssetsTools libStringConverter) diff --git a/Plugins/Texture/Texture.cpp b/Plugins/Texture/Texture.cpp new file mode 100644 index 0000000..c5788e8 --- /dev/null +++ b/Plugins/Texture/Texture.cpp @@ -0,0 +1,282 @@ +#include +#include +#include "../libStringConverter/convert.h" +#include "../UABE_Generic/PluginManager.h" +#include "../UABE_Generic/FileContextInfo.h" +#include "../UABE_Generic/AppContext.h" +#include "../AssetsTools/TextureFileFormat.h" + +#include "Texture.h" + +#ifdef _WIN32 +#define STBI_WINDOWS_UTF8 +#endif +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" + +#define LODEPNG_NO_COMPILE_DECODER +#define LODEPNG_NO_COMPILE_ANCILLARY_CHUNKS +#include "lodepng.h" + +#pragma region Import +static int stbi__AssetsTools_read(void* user, char* data, int size) +{ + return (int)((IAssetsReader*)user)->Read((QWORD)size, data); +} +static void stbi__AssetsTools_skip(void* user, int n) +{ + ((IAssetsReader*)user)->Seek(AssetsSeek_Cur, n); +} +static int stbi__AssetsTools_eof(void* user) +{ + if (((IAssetsReader*)user)->Seek(AssetsSeek_Cur, 1)) + { + ((IAssetsReader*)user)->Seek(AssetsSeek_Cur, -1); + return 0; + } + return 1; +} +static stbi_io_callbacks stbi__AssetsTools_callbacks = +{ + stbi__AssetsTools_read, + stbi__AssetsTools_skip, + stbi__AssetsTools_eof, +}; + +bool LoadTextureFromFile(const char *filePath, std::vector &newTextureData, unsigned int &newWidth, unsigned int &newHeight) +{ + std::unique_ptr pImageFileReader(Create_AssetsReaderFromFile(filePath, true, RWOpenFlags_Immediately)); + if (pImageFileReader == nullptr) + return false; + + int width, height, n; + stbi__vertically_flip_on_load = 1; //added because the image is flipped on save + uint8_t *pImg = stbi_load_from_callbacks(&stbi__AssetsTools_callbacks, pImageFileReader.get(), &width, &height, &n, 4); + if (pImg == NULL) + return false; + newWidth = width; + newHeight = height; + newTextureData.assign(&pImg[0], &pImg[(size_t)width * height * 4]); + free(pImg); + return true; +} +#pragma endregion + +bool PluginSupportsElements(std::vector& elements) +{ + std::unordered_map texture2DClassIDs; + for (size_t i = 0; i < elements.size(); i++) + { + if (elements[i].asset.pFile == nullptr) + return false; + AssetsFileContextInfo* pFile = elements[i].asset.pFile.get(); + auto classIDsit = texture2DClassIDs.find(pFile); + int32_t texture2DClassID = -1; + if (classIDsit == texture2DClassIDs.end()) + { + texture2DClassID = pFile->GetClassByName("Texture2D"); + texture2DClassIDs[pFile] = texture2DClassID; + } + else + texture2DClassID = classIDsit->second; + if (texture2DClassID == -1) + return false; + int32_t classId = elements[i].asset.getClassID(); + if (classId != texture2DClassID) + return false; + } + return true; +} + +class TextureExportTask : public AssetExportTask +{ + AppContext& appContext; + EExportFormat exportFormat; + TypeTemplateCache templateCache; +public: + TextureExportTask(AppContext& appContext, + std::vector _assets, std::string _baseDir, std::string _extension, EExportFormat exportFormat, + bool stopOnError = false) + + : AssetExportTask(std::move(_assets), "Export Texture2D", std::move(_extension), std::move(_baseDir), stopOnError), + appContext(appContext), exportFormat(exportFormat) + {} + + bool exportAsset(AssetUtilDesc& desc, std::string path, std::optional> progressManager) + { + if (desc.asset.pFile == nullptr) + throw AssetUtilError("Unable to find the target .assets file."); + + IAssetsReader_ptr pAssetReader = desc.asset.makeReader(); + if (pAssetReader == nullptr) + throw AssetUtilError("Unable to read the asset."); + QWORD assetSize = 0; + if (!pAssetReader->Seek(AssetsSeek_End, 0) || !pAssetReader->Tell(assetSize) || !pAssetReader->Seek(AssetsSeek_Begin, 0)) + throw AssetUtilError("Unable to read the asset."); + + AssetTypeTemplateField& templateBase = templateCache.getTemplateField(appContext, desc.asset); + AssetTypeTemplateField* pTemplateBase = &templateBase; + + AssetTypeInstance assetInstance(1, &pTemplateBase, assetSize, pAssetReader.get(), desc.asset.isBigEndian()); + AssetTypeValueField* pBaseField = assetInstance.GetBaseField(); + if (pBaseField == nullptr || pBaseField->IsDummy()) + throw AssetUtilError("Unable to deserialize the asset."); + + TextureFile textureFile; memset(&textureFile, 0, sizeof(TextureFile)); + if (!ReadTextureFile(&textureFile, pBaseField)) + throw AssetUtilError("Unknown Texture2D asset format."); + //Retrieve the texture format version. + SupportsTextureFormat(desc.asset.pFile->getAssetsFileContext()->getAssetsFile(), + (TextureFormat)0, + textureFile.extra.textureFormatVersion); + + if (textureFile._pictureDataSize == 0) + textureFile.pPictureData = NULL; + std::vector texDataResourceBuf; + std::vector texDataDecompressed((size_t)textureFile.m_Width * textureFile.m_Height * 4); + if (textureFile._pictureDataSize == 0 && (textureFile.m_Width * textureFile.m_Height) > 0 && textureFile.m_StreamData.size) + { + std::shared_ptr streamResourcesContextInfo; + streamResourcesContextInfo = FindResourcesFile(appContext, textureFile.m_StreamData.path, desc.asset, progressManager); + //Non-null guaranteed by FindResourcesFile (AssetUtilError thrown otherwise). + + std::shared_ptr pStreamReader = streamResourcesContextInfo->getResource(streamResourcesContextInfo, + textureFile.m_StreamData.offset, + textureFile.m_StreamData.size); + if (pStreamReader == nullptr) + throw AssetUtilError("Unable to locate the texture resource."); + + texDataResourceBuf.resize(textureFile.m_StreamData.size); + textureFile.pPictureData = texDataResourceBuf.data(); + if (pStreamReader->Read(0, textureFile.m_StreamData.size, textureFile.pPictureData) + < textureFile.m_StreamData.size) + throw AssetUtilError("Unable to read data from the texture resource."); + + textureFile._pictureDataSize = textureFile.m_StreamData.size; + } + if (!GetTextureData(&textureFile, texDataDecompressed.data())) + throw AssetUtilError("Unable to convert the texture data."); + + switch (exportFormat) + { + case EExportFormat::PNG: + { + if (textureFile.m_Height > 1) + { + std::vector flipDataTmp(textureFile.m_Width * 4); + unsigned int stride = textureFile.m_Width * 4; + unsigned int halfHeight = textureFile.m_Height / 2; + unsigned int curLinePos = 0; + unsigned int curEndLinePos = stride * (textureFile.m_Height - 1); + for (unsigned int y = 0; y < halfHeight; y++) + { + memcpy(flipDataTmp.data(), &texDataDecompressed.data()[curLinePos], stride); + memcpy(&texDataDecompressed.data()[curLinePos], &texDataDecompressed.data()[curEndLinePos], stride); + memcpy(&texDataDecompressed.data()[curEndLinePos], flipDataTmp.data(), stride); + + curLinePos += stride; + curEndLinePos -= stride; + } + } + //stbi_write_pngW(targetFile, g_TextureWidth, g_TextureHeight, 4, textureData, g_TextureWidth*4); + auto pathW = unique_MultiByteToWide(path.c_str()); + lodepng_encode32_fileW(pathW.get(), texDataDecompressed.data(), textureFile.m_Width, textureFile.m_Height); + } + break; + case EExportFormat::TGA: + stbi_write_tga(path.c_str(), textureFile.m_Width, textureFile.m_Height, 4, true, texDataDecompressed.data()); + break; + default: + assert(false); + throw AssetUtilError("Unexpected export format."); + break; + } + + return true; + } +}; +class TextureExportProvider : public IAssetOptionProviderGeneric +{ +public: + std::string optionName; + std::string extension; + std::string extensionFilter; + EExportFormat exportFormat; + class Runner : public IOptionRunner + { + AppContext& appContext; + std::vector selection; + std::string extension; + std::string extensionFilter; + EExportFormat exportFormat; + public: + Runner(AppContext& appContext, std::vector _selection, + const std::string& extension, const std::string& extensionFilter, + EExportFormat exportFormat) + : appContext(appContext), selection(std::move(_selection)), + extension(extension), extensionFilter(extensionFilter), exportFormat(exportFormat) + {} + void operator()() + { + std::string exportLocation = appContext.QueryAssetExportLocation(selection, extension, extensionFilter); + if (!exportLocation.empty()) + { + auto pTask = std::make_shared(appContext, std::move(selection), std::move(exportLocation), std::move(extension), exportFormat); + appContext.taskManager.enqueue(pTask); + } + } + }; + + inline TextureExportProvider(const std::string &optionName, + const std::string &extension, const std::string &extensionFilter, + EExportFormat exportFormat) + + : optionName(optionName), extension(extension), extensionFilter(extensionFilter), exportFormat(exportFormat) + {} + EAssetOptionType getType() + { + return EAssetOptionType::Export; + } + std::unique_ptr prepareForSelection( + class AppContext& appContext, + std::vector selection, + std::string& optionName) + { + if (!PluginSupportsElements(selection)) + return nullptr; + optionName = this->optionName; + return std::make_unique(appContext, std::move(selection), extension, extensionFilter, exportFormat); + } +}; + +//class GenericTexturePluginDesc : public IPluginDesc +//{ + GenericTexturePluginDesc::GenericTexturePluginDesc() + { + pProviders = { + std::make_shared("Export to .png", ".png", "*.png|PNG file:", EExportFormat::PNG), + std::make_shared("Export to .tga", ".tga", "*.tga|TGA file:", EExportFormat::TGA), + //std::make_shared() + }; + } + std::string GenericTexturePluginDesc::getName() + { + return "Texture"; + } + std::string GenericTexturePluginDesc::getAuthor() + { + return ""; + } + std::string GenericTexturePluginDesc::getDescriptionText() + { + return "Export and import the content of Texture2D assets."; + } + //The IPluginDesc object should keep a reference to the returned options, as the caller may keep only std::weak_ptrs. + //Note: May be called early, e.g. before program UI initialization. + std::vector> GenericTexturePluginDesc::getPluginOptions(class AppContext& appContext) + { + return pProviders; + } +//}; diff --git a/Plugins/Texture/Texture.def b/Plugins/Texture/Texture.def new file mode 100644 index 0000000..e30195a --- /dev/null +++ b/Plugins/Texture/Texture.def @@ -0,0 +1,3 @@ +LIBRARY Texture +EXPORTS + GetUABEPluginDesc1 diff --git a/Plugins/Texture/Texture.h b/Plugins/Texture/Texture.h new file mode 100644 index 0000000..1bec983 --- /dev/null +++ b/Plugins/Texture/Texture.h @@ -0,0 +1,36 @@ +#pragma once +#include +#include +#include +#include +#include "../UABE_Generic/PluginManager.h" +#include "../UABE_Generic/FileContextInfo.h" +#include "../UABE_Generic/AppContext.h" +#include "../UABE_Generic/AssetIterator.h" + +enum class EExportFormat +{ + PNG, + TGA +}; + +bool LoadTextureFromFile(const char* filePath, std::vector &newTextureData, unsigned int& newWidth, unsigned int& newHeight); +inline void FreeTextureFromFile(uint8_t* textureData) +{ + if (textureData) free(textureData); +} + +bool PluginSupportsElements(std::vector &elements); +class GenericTexturePluginDesc : public IPluginDesc +{ +protected: + std::vector> pProviders; +public: + GenericTexturePluginDesc(); + std::string getName(); + std::string getAuthor(); + std::string getDescriptionText(); + //The IPluginDesc object should keep a reference to the returned options, as the caller may keep only std::weak_ptrs. + //Note: May be called early, e.g. before program UI initialization. + std::vector> getPluginOptions(class AppContext& appContext); +}; diff --git a/Plugins/Texture/Texture.rc b/Plugins/Texture/Texture.rc new file mode 100644 index 0000000000000000000000000000000000000000..d62e5bb5544830ab4236151f82a985e0c040df7d GIT binary patch literal 11474 zcmd^FTTkOw5T55s{SR(<>8jB}0t9#rxd0+0DG9JhNGJ=0MdDTnT~_LEZ~J{SIoQY8 zxslMI%1Ue>+h^|I%o+QizZYdu0@;%*IguAZ6K+hMda2R4`h`ab0S1jFA@p_hb_zH5@zop$)0O;C}~FmmnwgQNef~zqmzS+n@^y)PEOuzt$+{JcRak zF=IK^Zaw(GgL*_y{={4lu(h+?g353Xt9AR&8#P)#*^1kcq^vU36D^qJIp5VQOxh^z zr-9N9qvR|xf4)MF$lU^>XOR6yiHP}^lF|n7hzm&DQ%w1W^Iu~l*YHB>;!}d(KdQD& zWAwy8e(})c9J3Xria0V9X~E|YfF~vR{VC|wuyNkMum!14wlT*(qLxoo@cbvGTEl1^ zqX*b;X=J*QQ$61YLh+6)W^UoGP0+F@{OjaClwh2golpODJ{;Yrp_%hhr-K}Hm4it# zXIVD9`3Gxm{U0;K6>|4A)?AmW{b)vT*b~KOKc0w60dS1|WB)|XH9aT7 z8SZiwdkie+s*N$qbt~hPS*VUZ>rQuN4`XFT{wff>2;AgT;q@v!_6x>1&oO>>?MP=O zJ~rXkO>eY>S(>22HFVv(Vl$9J9ZxYvy|LG{7uQKgfexR&(9>bhkm_5gNOJ z1@3jo@uv}cTZq-@TwM?Z?brn)( zL3t9Y`+a%=B&EcRNXF!MjGxEk4XJb0){rBO2Ge#ysjouo<4axz?Q2++{SE5;PHRE> zZ0J*rKGPvrb&OOe{W^S@(ibM6T*MlIa^HbEyFd3hI|C0u*Ppm|mTx@gqe+`gc^@IcVB^ zoYD1mn(Q`kJ?~pPPW#|RV%DydetOgohf+U$p>AG28TA_MdI@CiDSlD!>1f94C|n;j z;g2D%G0)M@*3|RHwLl48&iH1eJ;M4O?I-)%XRosbWGiZy#(XUgy)(8alikO0pPH+n z9UaRQ%;9nWV$4^;*8#ZO1_B-c>sg~Qw{pwic=yGVcNxZ`iq36OC{*30`7M)Zn|K_WmT2WmDBo)`e9D-p0&l>xD9g}yMxRr)8}|qy(6#x z)8yFC!qexwpgPVg-Y4;G?|+=#eBX%raB}W;F+?OcUoOnllShwp^WZSo8mBo#IT0|7 zyV*p1Joh=jXXa(3zlW~IL8v&t%+*d~D-n#Gt##kRG4s3Q*lM`<(DCU0m{tuqo~;6& zwO_`o0!MuV9{E0AnV$I#don=qJ5)6Ox`nuIJvwLQJR+H^g-~PB95S3r2{ibI{Q{L# znEkjW;9IR4W_Gbk>?EbPRvyvyhUwjS4<#6mMPaXr(h{G`s28H$-QgV;tIgO+u+HDd z?yo$f{$(Z19!m?>WR+s8J+8i}rCE1aCa-8&btZe&H2k-qna=9WWGckl!F8c&)H(|H zSod_GWmbXweu5o~Fmg_{{;Q&HW<|qyFHKa1MMyZ!nugUne+9)WSI;Czg+q*ZSAUcU zp=B5$%j!WNIYJzo#ygN)Iex5YVcnMO$HSwgh>pxxpu`0bpWg-9D#q&DDCV!ChG5q* zo!0P}VK?p`S;bc6j2HH3Zu={B^1!_F<5lc%71Pw%VFu!Ajht}}Kbsg@*Q_=;hO8os zzwa3yL2JmK7D4t_<|9WCF_2x{Ggy*H7|0)34YS9}&i)Ua`;j!iS!Y#I)7FB?ku**P z9)Abuu$lc#9@DdC(be$LKK-5B+rNmK?{73%+qj>7eKF`;H@?&Mzg3)X>D-${?%(>Y g=Q3s>?nb}9^}qFWdYo^ar2G0UYBHkJs9zxc3)MMVD*ylh literal 0 HcmV?d00001 diff --git a/Plugins/Texture/TextureWin32.cpp b/Plugins/Texture/TextureWin32.cpp new file mode 100644 index 0000000..a17c9cb --- /dev/null +++ b/Plugins/Texture/TextureWin32.cpp @@ -0,0 +1,953 @@ +#include "resource.h" +#include "defines.h" + +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#include +#include +#include +#include +#include +#include +#include "../libStringConverter/convert.h" +#include "../UABE_Generic/FileContextInfo.h" +#include "../UABE_Generic/CreateEmptyValueField.h" +#include "../UABE_Win32/Win32PluginManager.h" +#include "../UABE_Win32/Win32AppContext.h" +#include "../UABE_Win32/Win32BatchImportDesc.h" +#include "../UABE_Win32/BatchImportDialog.h" +#include "../UABE_Win32/FileDialog.h" +#include "Texture.h" + +#define IsPowerOfTwo(n) ((n!=0)&&!(n&(n-1))) + +HMODULE g_hModule; + +INT_PTR CALLBACK CompressQualityDlg(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + UNREFERENCED_PARAMETER(lParam); + switch (message) + { + case WM_INITDIALOG: + { + SetWindowLongPtr(hDlg, GWLP_USERDATA, lParam); + TextureImportParam* pParam = (TextureImportParam*)lParam; + + HWND hCbQuality = GetDlgItem(hDlg, IDC_CBQUALITY); + std::vector presetNames; + size_t defaultPresetIdx = GetQualityPresetList(pParam->batchInfo.newTextureFormat, presetNames); + if (defaultPresetIdx >= presetNames.size() || presetNames.size() >= 0x7FFFFFFF) + { + EndDialog(hDlg, 0); + } + else + { + pParam->qualitySelection = GetQualityFromPresetListIdx(pParam->batchInfo.newTextureFormat, defaultPresetIdx); + for (size_t i = 0; i < presetNames.size(); i++) + { + ComboBox_AddString(hCbQuality, presetNames[i]); + } + ComboBox_SetCurSel(hCbQuality, (int)defaultPresetIdx); + } + return (INT_PTR)TRUE; + } + case WM_CLOSE: + case WM_DESTROY: + { + TextureImportParam* pParam = (TextureImportParam*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + + int selection = ComboBox_GetCurSel(GetDlgItem(hDlg, IDC_CBQUALITY)); + if (selection < 0) selection = 0; + + ECompressorQuality newQuality = GetQualityFromPresetListIdx(pParam->batchInfo.newTextureFormat, (size_t)selection); + if (pParam->qualitySelection != newQuality) + { + pParam->qualitySelected = true; + pParam->qualitySelection = newQuality; + } + } + break; + case WM_COMMAND: + { + WORD wmId = LOWORD(wParam); + WORD wmEvent = HIWORD(wParam); + switch (wmId) + { + case IDOK: + case IDCANCEL: + { + EndDialog(hDlg, (INT_PTR)lParam); + return (INT_PTR)TRUE; + } + break; + } + } + } + return (INT_PTR)FALSE; +} +INT_PTR CALLBACK ImportDlg(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + TextureImportParam* pParam = (TextureImportParam*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + TCHAR tPrint[128]; + int wmId, wmEvent; + UNREFERENCED_PARAMETER(lParam); + switch (message) + { + case WM_DESTROY: + break; + case WM_INITDIALOG: + { + SetWindowLongPtr(hDlg, GWLP_USERDATA, lParam); + TextureImportParam* pParam = (TextureImportParam*)lParam; + if (pParam->importTextureName.size() > 0) + { + auto tc_importTextureName = unique_MultiByteToTCHAR(pParam->importTextureName.c_str()); + Edit_SetText(GetDlgItem(hDlg, IDC_ENAME), tc_importTextureName.get()); + } + HWND hCBTexFmt = GetDlgItem(hDlg, IDC_CBTEXFMT); + + HWND hCKMipMap = GetDlgItem(hDlg, IDC_CKMIPMAP); + + HWND hCKReadable = GetDlgItem(hDlg, IDC_CKREADABLE); + + HWND hCKReadAllowed = GetDlgItem(hDlg, IDC_CKREADALLOWED); + + HWND hCBFilterMode = GetDlgItem(hDlg, IDC_CBFILTERMODE); + ComboBox_AddString(hCBFilterMode, TEXT("Point")); + ComboBox_AddString(hCBFilterMode, TEXT("Bilinear")); + ComboBox_AddString(hCBFilterMode, TEXT("Trilinear")); + + HWND hEAniso = GetDlgItem(hDlg, IDC_EANISO); + + HWND hEMipBias = GetDlgItem(hDlg, IDC_EMIPBIAS); + + HWND hCBWrapMode = GetDlgItem(hDlg, IDC_CBWRAPMODE); + ComboBox_AddString(hCBWrapMode, TEXT("Repeat")); + ComboBox_AddString(hCBWrapMode, TEXT("Clamp")); + + HWND hCBWrapModeU = GetDlgItem(hDlg, IDC_CBWRAPMODEU); + ComboBox_AddString(hCBWrapModeU, TEXT("Repeat")); + ComboBox_AddString(hCBWrapModeU, TEXT("Clamp")); + ComboBox_AddString(hCBWrapModeU, TEXT("Mirror")); //2017.1 + ComboBox_AddString(hCBWrapModeU, TEXT("MirrorOnce")); //2017.1 + + HWND hCBWrapModeV = GetDlgItem(hDlg, IDC_CBWRAPMODEV); + ComboBox_AddString(hCBWrapModeV, TEXT("Repeat")); + ComboBox_AddString(hCBWrapModeV, TEXT("Clamp")); + ComboBox_AddString(hCBWrapModeV, TEXT("Mirror")); + ComboBox_AddString(hCBWrapModeV, TEXT("MirrorOnce")); + + HWND hELightMapFmt = GetDlgItem(hDlg, IDC_ELIGHTMAPFMT); + + HWND hCBClSpace = GetDlgItem(hDlg, IDC_CBCLSPACE); + ComboBox_AddString(hCBClSpace, TEXT("Gamma")); + ComboBox_AddString(hCBClSpace, TEXT("Linear")); + + if (!pParam->assetWasTextureFile) + { + pParam->importTextureInfo.m_ForcedFallbackFormat = 0; + pParam->importTextureInfo.m_DownscaleFallback = false; + pParam->importTextureInfo.m_TextureFormat = TexFmt_DXT5; + pParam->importTextureInfo.m_MipMap = true; + pParam->importTextureInfo.m_IsReadable = false; + pParam->importTextureInfo.m_ReadAllowed = true; + pParam->importTextureInfo.m_StreamingMipmaps = false; + pParam->importTextureInfo.m_StreamingMipmapsPriority = 0; + pParam->importTextureInfo.m_TextureSettings.m_FilterMode = 2; + pParam->importTextureInfo.m_TextureSettings.m_Aniso = 2; + pParam->importTextureInfo.m_TextureSettings.m_MipBias = 0.0F; + pParam->importTextureInfo.m_TextureSettings.m_WrapMode = 1; + pParam->importTextureInfo.m_TextureSettings.m_WrapU = 1; + pParam->importTextureInfo.m_TextureSettings.m_WrapV = 1; + pParam->importTextureInfo.m_TextureSettings.m_WrapW = 1; + pParam->importTextureInfo.m_LightmapFormat = 0; + pParam->importTextureInfo.m_ColorSpace = 1; + + pParam->importTextureInfo.m_MipCount = 1; + pParam->importTextureInfo.m_StreamData.offset = 0; + pParam->importTextureInfo.m_StreamData.size = 0; + pParam->importTextureInfo.m_StreamData.path.clear(); + + //Retrieve texture format version. + SupportsTextureFormat(pParam->asset.pFile->getAssetsFileContext()->getAssetsFile(), + TexFmt_ARGB32, + pParam->importTextureInfo.extra.textureFormatVersion); + } + pParam->importTextureInfo.m_TextureDimension = 2; + pParam->importTextureInfo.m_ImageCount = 1; + + { + size_t textureTypeIndex; + if (pParam->batchInfo.newTextureFormat == TexFmt_BGRA32Old) + textureTypeIndex = GetTextureNameIDPair(TexFmt_BGRA32New, pParam->importTextureInfo.extra.textureFormatVersion); + else + textureTypeIndex = GetTextureNameIDPair(pParam->batchInfo.newTextureFormat, pParam->importTextureInfo.extra.textureFormatVersion); + if (textureTypeIndex == (size_t)-1) textureTypeIndex = 0; + int cbTargetSel = 0; + for (size_t i = 0, cbIdx = 0; i < SupportedTextureNames_size; i++) + { + int texVersion = -1; + if (SupportsTextureFormat(pParam->asset.pFile->getAssetsFileContext()->getAssetsFile(), + SupportedTextureNames[i].textureType, + texVersion) + && IsTextureNameIDPairInRange(&SupportedTextureNames[i], texVersion)) + { + ComboBox_AddString(hCBTexFmt, SupportedTextureNames[i].name); + ComboBox_SetItemData(hCBTexFmt, cbIdx, i); + if (textureTypeIndex == i) + cbTargetSel = (int)cbIdx; + cbIdx++; + } + } + ComboBox_SetCurSel(hCBTexFmt, (int)cbTargetSel); + + if (pParam->assetWasTextureFile && IsPowerOfTwo(pParam->importTextureInfo.m_Width) && IsPowerOfTwo(pParam->importTextureInfo.m_Height)) + { + Button_SetCheck(hCKMipMap, pParam->batchInfo.newMipMap); + } + else + { + Button_SetCheck(hCKMipMap, FALSE); + Button_Enable(hCKMipMap, FALSE); + } + Button_SetCheck(hCKReadable, pParam->batchInfo.newReadable); + Button_SetCheck(hCKReadAllowed, pParam->batchInfo.newReadAllowed); + + if (pParam->importTextureInfo.m_TextureSettings.m_FilterMode > 2) + ComboBox_SetCurSel(hCBFilterMode, 2); + else + ComboBox_SetCurSel(hCBFilterMode, pParam->batchInfo.newFilterMode); + + _stprintf_s(tPrint, TEXT("%u"), pParam->batchInfo.newAnisoLevel); + Edit_SetText(hEAniso, tPrint); + + //Print an exact representation, using scientific notation if it's shorter. + _stprintf_s(tPrint, TEXT("%.9g"), pParam->batchInfo.newMipBias); + Edit_SetText(hEMipBias, tPrint); + + if (pParam->batchInfo.newWrapMode > 1) + ComboBox_SetCurSel(hCBWrapMode, 1); + else + ComboBox_SetCurSel(hCBWrapMode, pParam->batchInfo.newWrapMode); + + if (pParam->batchInfo.newWrapModeU > 3) + ComboBox_SetCurSel(hCBWrapModeU, 3); + else + ComboBox_SetCurSel(hCBWrapModeU, pParam->batchInfo.newWrapModeU); + + if (pParam->batchInfo.newWrapModeV > 3) + ComboBox_SetCurSel(hCBWrapModeV, 3); + else + ComboBox_SetCurSel(hCBWrapModeV, pParam->batchInfo.newWrapModeV); + + _stprintf_s(tPrint, TEXT("0x%02X"), pParam->batchInfo.newLightmapFmt); + Edit_SetText(hELightMapFmt, tPrint); + + if (pParam->batchInfo.newColorSpace > 1) + ComboBox_SetCurSel(hCBClSpace, 1); + else + ComboBox_SetCurSel(hCBClSpace, pParam->batchInfo.newColorSpace); + } + + InactivateDialogPairsByIdx(hDlg, pParam->hideDialogElementsList); + return (INT_PTR)TRUE; + } + case WM_COMMAND: + wmId = LOWORD(wParam); + wmEvent = HIWORD(wParam); + switch (wmId) + { + case IDC_BLOAD: + { + TextureImportParam* pParam = (TextureImportParam*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + wchar_t* newTextureFilePathW = nullptr; + if (SUCCEEDED(ShowFileOpenDialog(hDlg, &newTextureFilePathW, L"*.tga;*.png;*.bmp|Image file:*.*|All types:", + nullptr, nullptr, nullptr, UABE_FILEDIALOG_EXPIMPASSET_GUID))) + { + auto newTextureFilePath = unique_WideToMultiByte(newTextureFilePathW); + FreeCOMFilePathBuf(&newTextureFilePathW); + if (pParam->batchInfo.isBatchEntry) + { + IAssetsReader* pImageFileReader = Create_AssetsReaderFromFile(newTextureFilePath.get(), true, RWOpenFlags_Immediately); + if (pImageFileReader != NULL) + { + pParam->batchInfo.batchFilenameOverride.assign(newTextureFilePath.get()); + Free_AssetsReader(pImageFileReader); + } + else + MessageBox(hDlg, TEXT("Unable to open the file!"), TEXT("ERROR"), 16); + } + else + { + std::vector newTextureData; + if (LoadTextureFromFile(newTextureFilePath.get(), + newTextureData, + pParam->importTextureInfo.m_Width, + pParam->importTextureInfo.m_Height)) + { + pParam->importTextureData = std::move(newTextureData); + pParam->textureDataModified = true; + HWND hCKMipMap = GetDlgItem(hDlg, IDC_CKMIPMAP); + if (IsPowerOfTwo(pParam->importTextureInfo.m_Width) && + IsPowerOfTwo(pParam->importTextureInfo.m_Height)) + Button_Enable(hCKMipMap, TRUE); + else + { + Button_SetCheck(hCKMipMap, FALSE); + Button_Enable(hCKMipMap, FALSE); + } + } + else + MessageBox(hDlg, TEXT("Unable to open or process the file!"), TEXT("ERROR"), 16); + } + } + } + break; + case IDOK: + { + TextureImportParam* pParam = (TextureImportParam*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + HWND hEName = GetDlgItem(hDlg, IDC_ENAME); + HWND hCBTexFmt = GetDlgItem(hDlg, IDC_CBTEXFMT); + HWND hCKMipMap = GetDlgItem(hDlg, IDC_CKMIPMAP); + HWND hCKReadable = GetDlgItem(hDlg, IDC_CKREADABLE); + HWND hCKReadAllowed = GetDlgItem(hDlg, IDC_CKREADALLOWED); + HWND hCBFilterMode = GetDlgItem(hDlg, IDC_CBFILTERMODE); + HWND hEAniso = GetDlgItem(hDlg, IDC_EANISO); + HWND hEMipBias = GetDlgItem(hDlg, IDC_EMIPBIAS); + HWND hCBWrapMode = GetDlgItem(hDlg, IDC_CBWRAPMODE); + HWND hCBWrapModeU = GetDlgItem(hDlg, IDC_CBWRAPMODEU); + HWND hCBWrapModeV = GetDlgItem(hDlg, IDC_CBWRAPMODEV); + HWND hELightMapFmt = GetDlgItem(hDlg, IDC_ELIGHTMAPFMT); + HWND hCBClSpace = GetDlgItem(hDlg, IDC_CBCLSPACE); + + + Edit_GetText(hEName, tPrint, 128); + { + auto cNameBuffer = unique_TCHARToMultiByte(tPrint); + pParam->batchInfo.newName.assign(cNameBuffer.get()); + } + + size_t selection = (unsigned int)ComboBox_GetCurSel(hCBTexFmt); + if (selection >= SupportedTextureNames_size || selection >= ComboBox_GetCount(hCBTexFmt)) selection = 0; + else selection = (size_t)ComboBox_GetItemData(hCBTexFmt, selection); + const TextureNameIDPair* pTexFormatInfo = &SupportedTextureNames[selection]; + + pParam->batchInfo.newTextureFormat = pTexFormatInfo->textureType; + //Special case : TexFmt_BGRA32Old and TexFmt_BGRA32New. + if ((pTexFormatInfo->textureType == TexFmt_BGRA32New + && pParam->asset.pFile->getAssetsFileContext()->getAssetsFile()->header.format < 0x0E)) + pParam->batchInfo.newTextureFormat = TexFmt_BGRA32Old; + + pParam->batchInfo.newMipMap = Button_GetCheck(hCKMipMap) ? true : false; + pParam->batchInfo.newReadable = Button_GetCheck(hCKReadable) ? true : false; + pParam->batchInfo.newReadAllowed = Button_GetCheck(hCKReadAllowed) ? true : false; + + TCHAR* endPtr = NULL; + + pParam->batchInfo.newFilterMode = ComboBox_GetCurSel(hCBFilterMode); + + Edit_GetText(hEAniso, tPrint, 128); + int anisoLevel = (int)_tcstol(tPrint, &endPtr, 0); + if (endPtr != tPrint) + pParam->batchInfo.newAnisoLevel = anisoLevel; + + Edit_GetText(hEMipBias, tPrint, 128); + float mipBias = (float)_tcstod(tPrint, &endPtr); + if (endPtr != tPrint) + pParam->batchInfo.newMipBias = mipBias; + + pParam->batchInfo.newWrapMode = ComboBox_GetCurSel(hCBWrapMode); + pParam->batchInfo.newWrapModeU = ComboBox_GetCurSel(hCBWrapModeU); + pParam->batchInfo.newWrapModeV = ComboBox_GetCurSel(hCBWrapModeV); + + Edit_GetText(hELightMapFmt, tPrint, 128); + int lightMapFormat = (int)_tcstol(tPrint, &endPtr, 0); + if (endPtr != tPrint) + pParam->batchInfo.newLightmapFmt = lightMapFormat; + + pParam->batchInfo.newColorSpace = ComboBox_GetCurSel(hCBClSpace); + + if (pParam->batchInfo.isBatchEntry) + { + if (pParam->batchInfo.newName.compare(pParam->importTextureName) + || pParam->batchInfo.newTextureFormat != pParam->importTextureInfo.m_TextureFormat + || pParam->batchInfo.newMipMap != (pParam->importTextureInfo.m_MipMap || (pParam->importTextureInfo.m_MipCount > 1)) + || pParam->batchInfo.newReadable != pParam->importTextureInfo.m_IsReadable + || pParam->batchInfo.newReadAllowed != pParam->importTextureInfo.m_ReadAllowed + || pParam->batchInfo.newFilterMode != pParam->importTextureInfo.m_TextureSettings.m_FilterMode + || pParam->batchInfo.newAnisoLevel != pParam->importTextureInfo.m_TextureSettings.m_Aniso + || pParam->batchInfo.newMipBias != pParam->importTextureInfo.m_TextureSettings.m_MipBias + || pParam->batchInfo.newWrapMode != pParam->importTextureInfo.m_TextureSettings.m_WrapMode + || pParam->batchInfo.newWrapModeU != pParam->importTextureInfo.m_TextureSettings.m_WrapU + || pParam->batchInfo.newWrapModeV != pParam->importTextureInfo.m_TextureSettings.m_WrapV + || pParam->batchInfo.newLightmapFmt != pParam->importTextureInfo.m_LightmapFormat + || pParam->batchInfo.newColorSpace != pParam->importTextureInfo.m_ColorSpace) + { + pParam->batchInfo.hasNewSettings = true; + } + } + /*if (pParam->batchInfo.newTextureFormat != pParam->importTextureInfo.m_TextureFormat + || pParam->batchInfo.batchFilenameOverride.size() > 0)*/ + { + DialogBoxParam(g_hModule, MAKEINTRESOURCE(IDD_COMPQUALITY), hDlg, CompressQualityDlg, (LPARAM)pParam); + } + } + case IDCANCEL: + EndDialog(hDlg, LOWORD(wParam)); + return (INT_PTR)TRUE; + } + break; + } + return (INT_PTR)FALSE; +} + +void GetHideDialogElementsList(AssetTypeTemplateField* pTemplateBase, std::vector& elementIndices) +{ + if (!pTemplateBase->SearchChild("m_ReadAllowed")) + elementIndices.push_back(GetImportDialogPairByID(IDC_SREADALLOWED)); + if (!pTemplateBase->SearchChild("m_ColorSpace")) + elementIndices.push_back(GetImportDialogPairByID(IDC_SCLSPACE)); + AssetTypeTemplateField* pTextureSettings; + if ((pTextureSettings = pTemplateBase->SearchChild("m_TextureSettings"))) + { + if (!pTextureSettings->SearchChild("m_WrapMode")) + elementIndices.push_back(GetImportDialogPairByID(IDC_SWRAPMODE)); + if (!pTextureSettings->SearchChild("m_WrapU")) + elementIndices.push_back(GetImportDialogPairByID(IDC_SWRAPMODEU)); + if (!pTextureSettings->SearchChild("m_WrapV")) + elementIndices.push_back(GetImportDialogPairByID(IDC_SWRAPMODEV)); + } +} +void InitializeImportParam(TextureImportParam* pParam, AppContext& appContext) +{ + pParam->batchInfo.hasNewSettings = false; + AssetIdentifier& asset = pParam->asset; + + IAssetsReader_ptr pAssetReader = asset.makeReader(appContext); + if (pAssetReader == nullptr) + return; + QWORD assetSize = 0; + if (!pAssetReader->Seek(AssetsSeek_End, 0) || !pAssetReader->Tell(assetSize) || !pAssetReader->Seek(AssetsSeek_Begin, 0)) + return; + + AssetTypeTemplateField templateBase; + if (!asset.pFile->MakeTemplateField(&templateBase, appContext, asset.getClassID(), asset.getMonoScriptID(), &asset)) + return; + AssetTypeTemplateField* pTemplateBase = &templateBase; + + AssetTypeInstance assetInstance(1, &pTemplateBase, assetSize, pAssetReader.get(), asset.isBigEndian()); + AssetTypeValueField* pBaseField = assetInstance.GetBaseField(); + if (pBaseField == nullptr || pBaseField->IsDummy()) + return; + pAssetReader.reset(); + + GetHideDialogElementsList(pTemplateBase, pParam->hideDialogElementsList); + + TextureFile textureFile; + if (!ReadTextureFile(&textureFile, pBaseField)) + return; + + //Retrieve texture format version. + SupportsTextureFormat(asset.pFile->getAssetsFileContext()->getAssetsFile(), + (TextureFormat)textureFile.m_TextureFormat, + textureFile.extra.textureFormatVersion); + + pParam->importTextureName.assign(textureFile.m_Name); + pParam->importTextureInfo = textureFile; + //pParam->importTextureInfo.m_Name.clear(); + + pParam->assignCompressedTextureData(std::vector(pParam->importTextureInfo._pictureDataSize)); + memcpy(pParam->importTextureInfo.pPictureData, textureFile.pPictureData, pParam->importTextureInfo._pictureDataSize); + + pParam->assetWasTextureFile = true; + pParam->batchInfo.newName.assign(pParam->importTextureName); + pParam->batchInfo.newTextureFormat = pParam->importTextureInfo.m_TextureFormat; + pParam->batchInfo.newMipMap = pParam->importTextureInfo.m_MipMap || (pParam->importTextureInfo.m_MipCount > 1); + pParam->batchInfo.newReadable = pParam->importTextureInfo.m_IsReadable; + pParam->batchInfo.newReadAllowed = pParam->importTextureInfo.m_ReadAllowed; + pParam->batchInfo.newFilterMode = pParam->importTextureInfo.m_TextureSettings.m_FilterMode; + pParam->batchInfo.newAnisoLevel = pParam->importTextureInfo.m_TextureSettings.m_Aniso; + pParam->batchInfo.newMipBias = pParam->importTextureInfo.m_TextureSettings.m_MipBias; + pParam->batchInfo.newWrapMode = pParam->importTextureInfo.m_TextureSettings.m_WrapMode; + pParam->batchInfo.newWrapModeU = pParam->importTextureInfo.m_TextureSettings.m_WrapU; + pParam->batchInfo.newWrapModeV = pParam->importTextureInfo.m_TextureSettings.m_WrapV; + pParam->batchInfo.newLightmapFmt = pParam->importTextureInfo.m_LightmapFormat; + pParam->batchInfo.newColorSpace = pParam->importTextureInfo.m_ColorSpace; +} +void OpenImportDialog(HWND hParentWnd, TextureImportParam* pParam) +{ + DialogBoxParam(g_hModule, MAKEINTRESOURCE(IDD_IMPORT), hParentWnd, ImportDlg, (LPARAM)pParam); +} + +class TextureBatchImportDialogDesc : public CGenericBatchImportDialogDesc, public IWin32AssetBatchImportDesc +{ + Win32AppContext& appContext; +public: + std::vector> importParameters; +public: + inline TextureBatchImportDialogDesc(Win32AppContext& appContext, std::vector _elements, const std::string& extensionRegex) + : CGenericBatchImportDialogDesc(std::move(_elements), extensionRegex), + appContext(appContext) + { + importParameters.resize(getElements().size()); + } + bool hasAnyChanges() + { + return std::any_of(importParameters.begin(), importParameters.end(), [](const auto& x) {return x != nullptr; }) + || std::any_of(importFilePathOverrides.begin(), importFilePathOverrides.end(), [](const auto& x) {return !x.empty(); }); + } + + bool ShowAssetSettings(IN size_t matchIndex, IN HWND hParentWindow) + { + if (matchIndex == (size_t)-1) + return true; + if (matchIndex >= importParameters.size() || matchIndex >= getElements().size() || matchIndex >= importFilePathOverrides.size()) + return false; + std::unique_ptr& pImportParam = importParameters[matchIndex]; + if (pImportParam == nullptr) + { + pImportParam.reset(new TextureImportParam(getElements()[matchIndex].asset, false, true)); + InitializeImportParam(pImportParam.get(), appContext); + } + + pImportParam->batchInfo.batchFilenameOverride.clear(); + + OpenImportDialog(hParentWindow, pImportParam.get()); + + importFilePathOverrides[matchIndex].assign(pImportParam->batchInfo.batchFilenameOverride); + if (!pImportParam->batchInfo.hasNewSettings && !pImportParam->textureDataModified && !pImportParam->qualitySelected) + pImportParam.reset(); + return true; + return false; //TODO + } +}; + +class TextureEditTask : public ITask +{ + AppContext& appContext; + std::unique_ptr pDialogDesc; + std::string taskName; + TypeTemplateCache templateCache; +public: + TextureEditTask(AppContext& appContext, std::unique_ptr _pDialogDesc) + : appContext(appContext), pDialogDesc(std::move(_pDialogDesc)), taskName("Edit Textures") + {} + const std::string& getName() + { + return taskName; + } + TaskResult execute(TaskProgressManager& progressManager) + { + unsigned int progressRange = static_cast(std::min(pDialogDesc->importParameters.size(), 10000)); + size_t assetsPerProgressStep = pDialogDesc->importParameters.size() / progressRange; + constexpr size_t assetsPerDescUpdate = 8; + progressManager.setCancelable(); + progressManager.setProgress(0, progressRange); + auto lastDescTime = std::chrono::high_resolution_clock::now(); + bool encounteredErrors = false; + for (size_t i = 0; i < pDialogDesc->importParameters.size(); ++i) + { + if (progressManager.isCanceled()) + return TaskResult_Canceled; + if ((i % assetsPerProgressStep) == 0) + progressManager.setProgress((unsigned int)(i / assetsPerProgressStep), progressRange); + auto curTime = std::chrono::high_resolution_clock::now(); + if (i == 0 || std::chrono::duration_cast(curTime - lastDescTime).count() >= 500) + { + progressManager.setProgressDesc(std::format("Importing {}/{}", (i + 1), pDialogDesc->importParameters.size())); + lastDescTime = curTime; + } + const AssetIdentifier& asset = pDialogDesc->getElements()[i].asset; + std::unique_ptr &pImportParam = pDialogDesc->importParameters[i]; + if (pImportParam == nullptr) + { + pImportParam.reset(new TextureImportParam(asset, false, true)); + InitializeImportParam(pImportParam.get(), appContext); + } + + pImportParam->batchInfo.batchFilenameOverride = pDialogDesc->getImportFilePath(i); + try { + FinalizeTextureEdit(pImportParam.get(), progressManager); + } + catch (AssetUtilError err) { + progressManager.logMessage(std::format( + "Error importing an asset (File ID {0}, Path ID {1}): {2}", + asset.fileID, (int64_t)asset.pathID, err.what())); + encounteredErrors = true; + } + pImportParam.reset(); + } + return (TaskResult)(encounteredErrors ? -2 : 0); + + } + + //Set pParam->batchInfo.batchFilenameOverride to the import file name. + void FinalizeTextureEdit(TextureImportParam* pParam, TaskProgressManager &progressManager) + { + size_t textureTypeIndex; + if (pParam->batchInfo.newTextureFormat == TexFmt_BGRA32Old) + textureTypeIndex = GetTextureNameIDPair(TexFmt_BGRA32New, pParam->importTextureInfo.extra.textureFormatVersion); + else + textureTypeIndex = GetTextureNameIDPair(pParam->batchInfo.newTextureFormat, pParam->importTextureInfo.extra.textureFormatVersion); + if (textureTypeIndex == (size_t)-1) + throw AssetUtilError("Unable to determine the texture format."); + + const TextureNameIDPair* pTexFormatInfo = &SupportedTextureNames[textureTypeIndex]; + unsigned int newWidth = pParam->importTextureInfo.m_Width; + unsigned int newHeight = pParam->importTextureInfo.m_Height; + std::vector &newTextureData = pParam->importTextureData; + if (!pParam->batchInfo.batchFilenameOverride.empty()) + { + //Load the given texture file. + LoadTextureFromFile(pParam->batchInfo.batchFilenameOverride.c_str(), newTextureData, newWidth, newHeight); + pParam->importTextureInfo.pPictureData = NULL; + pParam->textureDataModified = true; + } + + bool oldMipMap = pParam->importTextureInfo.m_MipMap || (pParam->importTextureInfo.m_MipCount > 1); + bool newMipMap = pParam->batchInfo.newMipMap && + IsPowerOfTwo(newWidth) && + IsPowerOfTwo(newHeight); + + bool generateTextureData = pParam->textureDataModified + || (pParam->importTextureInfo.m_TextureFormat != pParam->batchInfo.newTextureFormat); + //|| (newMipMap != oldMipMap)); + if (newMipMap && !oldMipMap) + generateTextureData = true; + + size_t newTextureDataSize = 0; + unsigned int mipCount = pParam->importTextureInfo.m_MipCount; + if (generateTextureData) + { + unsigned int curWidth = newWidth; + unsigned int curHeight = newHeight; + + mipCount = 0; + do + { + size_t curTextureDataSize = GetTextureDataSize(pTexFormatInfo, curWidth, curHeight); + + newTextureDataSize += curTextureDataSize; + mipCount++; + if ((curWidth >>= 1) >= 1 || (curHeight >>= 1) >= 1) + { + if (curWidth == 0) + curWidth = 1; + else if (curHeight == 0) + curHeight = 1; + } + else + break; + } while (newMipMap); + + //Decode the current texture data if necessary. + if (newTextureDataSize > 0 && newTextureDataSize <= 0xFFFFFFFFULL + && (pParam->importTextureInfo.pPictureData != NULL + || (pParam->importTextureData.empty() + && pParam->importTextureInfo._pictureDataSize == 0 + && pParam->importTextureInfo.m_StreamData.size > 0))) + { + if (pParam->importTextureInfo.pPictureData != pParam->importTextureData.data() + || pParam->importTextureInfo._pictureDataSize != pParam->importTextureData.size()) + throw std::runtime_error("FinalizeTextureEdit: Unexpected texture data reference."); + std::vector compressedTextureData; + if (pParam->importTextureInfo._pictureDataSize == 0 + && (pParam->importTextureInfo.m_Width * pParam->importTextureInfo.m_Height) > 0 && + pParam->importTextureInfo.m_StreamData.size > 0) + { + //Load the texture from the referred resource. + pParam->importTextureInfo.pPictureData = NULL; + auto pResourcesFile = FindResourcesFile(appContext, pParam->importTextureInfo.m_StreamData.path, pParam->asset, progressManager); + //Non-null guaranteed by FindResourcesFile (AssetUtilError thrown otherwise). + + std::shared_ptr pStreamReader = pResourcesFile->getResource(pResourcesFile, + pParam->importTextureInfo.m_StreamData.offset, + pParam->importTextureInfo.m_StreamData.size); + if (pStreamReader == nullptr) + throw AssetUtilError("Unable to locate the texture resource."); + + compressedTextureData.resize(pParam->importTextureInfo.m_StreamData.size); + if (pStreamReader->Read(0, pParam->importTextureInfo.m_StreamData.size, compressedTextureData.data()) + != pParam->importTextureInfo.m_StreamData.size) + throw AssetUtilError("Unable to read data from the texture resource."); + } + else + compressedTextureData = pParam->importTextureData; + if (!compressedTextureData.empty()) + { + //Decode the texture data. + newTextureData.resize((size_t)pParam->importTextureInfo.m_Width * pParam->importTextureInfo.m_Height * 4); + + pParam->importTextureInfo.pPictureData = compressedTextureData.data(); + pParam->importTextureInfo._pictureDataSize = pParam->importTextureInfo.m_StreamData.size; + + //Retrieve the texture format version. + SupportsTextureFormat(pParam->asset.pFile->getAssetsFileContext()->getAssetsFile(), + (TextureFormat)0, + pParam->importTextureInfo.extra.textureFormatVersion); + if (!GetTextureData(&pParam->importTextureInfo, newTextureData.data())) + throw AssetUtilError("Unable to decode the texture."); + } + else + throw AssetUtilError("Unable to find the compressed texture data."); + pParam->importTextureInfo.pPictureData = NULL; + pParam->importTextureInfo.m_StreamData.offset = pParam->importTextureInfo.m_StreamData.size = 0; + pParam->importTextureInfo.m_StreamData.path.clear(); + } + } + + + std::unique_ptr pWriter(Create_AssetsWriterToMemory()); + AssetTypeTemplateField &templateBase = templateCache.getTemplateField(appContext, pParam->asset); + + pParam->importTextureName = pParam->batchInfo.newName; + pParam->importTextureInfo.m_TextureFormat = pParam->batchInfo.newTextureFormat; + pParam->importTextureInfo.m_MipMap = pParam->batchInfo.newMipMap; + pParam->importTextureInfo.m_MipCount = mipCount; + pParam->importTextureInfo.m_IsReadable = pParam->batchInfo.newReadable; + pParam->importTextureInfo.m_ReadAllowed = pParam->batchInfo.newReadAllowed; + pParam->importTextureInfo.m_TextureSettings.m_FilterMode = pParam->batchInfo.newFilterMode; + pParam->importTextureInfo.m_TextureSettings.m_Aniso = pParam->batchInfo.newAnisoLevel; + pParam->importTextureInfo.m_TextureSettings.m_MipBias = pParam->batchInfo.newMipBias; + pParam->importTextureInfo.m_TextureSettings.m_WrapMode = pParam->batchInfo.newWrapMode; + pParam->importTextureInfo.m_TextureSettings.m_WrapU = pParam->batchInfo.newWrapModeU; + pParam->importTextureInfo.m_TextureSettings.m_WrapV = pParam->batchInfo.newWrapModeV; + pParam->importTextureInfo.m_LightmapFormat = pParam->batchInfo.newLightmapFmt; + pParam->importTextureInfo.m_ColorSpace = pParam->batchInfo.newColorSpace; + + pParam->importTextureInfo.m_Width = newWidth; + pParam->importTextureInfo.m_Height = newHeight; + + std::vector recompressedTextureDataBuf; + bool dataFormatValid = true; + if (generateTextureData && !newTextureData.empty()) + { + //Compress the texture data. + recompressedTextureDataBuf.resize((newTextureDataSize + 3) & (~3), 0); + pParam->importTextureInfo.pPictureData = recompressedTextureDataBuf.data(); + pParam->importTextureInfo._pictureDataSize = (newTextureDataSize + 3) & (~3); + pParam->importTextureInfo.m_StreamData.path.clear(); + pParam->importTextureInfo.m_StreamData.offset = 0; + pParam->importTextureInfo.m_StreamData.size = 0; + if (!pParam->qualitySelected) + { + pParam->qualitySelection = ePVRTCFastest; + std::vector presetNames; + size_t defaultPresetIdx = GetQualityPresetList(pParam->importTextureInfo.m_TextureFormat, presetNames); + if (defaultPresetIdx < presetNames.size() && presetNames.size() < 0x7FFFFFFF) + { + pParam->qualitySelection = GetQualityFromPresetListIdx(pParam->importTextureInfo.m_TextureFormat, defaultPresetIdx); + } + } + //update the texture format version + SupportsTextureFormat(pParam->asset.pFile->getAssetsFileContext()->getAssetsFile(), + (TextureFormat)0, + pParam->importTextureInfo.extra.textureFormatVersion); + dataFormatValid = MakeTextureData(&pParam->importTextureInfo, newTextureData.data(), pParam->qualitySelection); + } + + if (!dataFormatValid + || (pParam->importTextureInfo.pPictureData == NULL && pParam->importTextureInfo._pictureDataSize > 0)) + throw AssetUtilError("Unable to convert the texture data."); + //Replace the texture. + pParam->importTextureInfo.m_Name = pParam->importTextureName; + + { + std::vector> textureValueFieldMemory; + AssetTypeValueField* pTextureBase = CreateEmptyValueFieldFromTemplate(&templateBase, textureValueFieldMemory); + if (!WriteTextureFile(&pParam->importTextureInfo, pTextureBase, textureValueFieldMemory)) + throw AssetUtilError("Unable to serialize the texture file. Is the class database invalid?"); + + QWORD newTextureFileSize = pTextureBase->Write(pWriter.get(), 0, pParam->asset.isBigEndian()); + if (newTextureFileSize == 0) + throw AssetUtilError("Unable to write the texture file. Is the class database invalid?"); + } + + QWORD writerPos = 0; size_t writerSize = 0; + void* pWriterBuf = NULL; + pWriter->GetBuffer(pWriterBuf, writerSize); + pWriter->Tell(writerPos); + + std::shared_ptr pReplacer(MakeAssetModifierFromMemory(0, pParam->asset.pathID, + pParam->asset.getClassID(), pParam->asset.getMonoScriptID(), + pWriterBuf, writerSize, Free_AssetsWriterToMemory_DynBuf)); + if (pReplacer == nullptr) + throw AssetUtilError("Unexpected runtime error."); + pWriter->SetFreeBuffer(false); + pParam->asset.pFile->addReplacer(pReplacer, appContext); + } +}; + +class TextureEditModifyDialog : public AssetModifyDialog +{ + CBatchImportDialog batchImportDialog; + std::unique_ptr pDialogDesc; + Win32AppContext &appContext; + AssetListDialog &listDialog; +public: + TextureEditModifyDialog(std::unique_ptr _pDialogDesc, std::string _basePath, + Win32AppContext &appContext, AssetListDialog& listDialog) + : batchImportDialog(appContext.getMainWindow().getHInstance(), _pDialogDesc.get(), _pDialogDesc.get(), std::move(_basePath)), + pDialogDesc(std::move(_pDialogDesc)), + appContext(appContext), listDialog(listDialog) + { + batchImportDialog.SetCloseCallback([this,&listDialog,&appContext](bool apply) + { + if (apply && pDialogDesc != nullptr) + { + auto pTask = std::make_shared(appContext, std::move(pDialogDesc)); + appContext.taskManager.enqueue(pTask); + } + batchImportDialog.SetCloseCallback(nullptr); + listDialog.removeModifyDialog(this); + } + ); + } + virtual ~TextureEditModifyDialog() {} + //Called when the user requests to close the tab. + //Returns true if there are unsaved changes, false otherwise. + //If the function will return true and applyable is not null, + // *applyable will be set to true iff applyNow() is assumed to succeed without further interaction + // (e.g. all fields in the dialog have a valid value, ...). + //The caller uses this info to decide whether and how it should display a confirmation dialog before proceeding. + virtual bool hasUnappliedChanges(bool* applyable) + { + if (applyable) *applyable = false; + return true; + } + //Called when the user requests to apply the changes (e.g. selecting Apply, Save or Save All in the menu). + //Returns whether the changes have been applied; + // if true, the caller may continue closing the AssetModifyDialog. + // if false, the caller shall stop closing the AssetModifyDialog. + //Note: applyChanges() is expected to notify the user about errors (e.g. via MessageBox). + virtual bool applyChanges() + { + return false; + } + virtual std::string getTabName() + { + return "Edit textures"; + } + virtual HWND getWindowHandle() + { + return batchImportDialog.getWindowHandle(); + } + //Called for unhandled WM_COMMAND messages. Returns true if this dialog has handled the request, false otherwise. + virtual bool onCommand(WPARAM wParam, LPARAM lParam) + { + return false; + } + //message : currently only WM_KEYDOWN; keyCode : VK_F3 for instance + virtual void onHotkey(ULONG message, DWORD keyCode) + { + return; + } + //Called when the dialog is to be shown. The parent window will not change before the next onHide call. + virtual void onShow(HWND hParentWnd) + { + batchImportDialog.ShowModeless(hParentWnd); + } + //Called when the dialog is to be hidden, either because of a tab switch or while closing the tab. + virtual void onHide() + { + batchImportDialog.Hide(); + } + //Called when the tab is about to be destroyed. + //Once this function is called, AssetListDialog::removeModifyDialog must not be used for this dialog. + virtual void onDestroy() + { + batchImportDialog.SetCloseCallback(nullptr); + } +}; +class Win32TextureEditProvider : public IAssetListTabOptionProvider +{ +public: + class Runner : public IOptionRunner + { + Win32AppContext& appContext; + AssetListDialog& listDialog; + std::vector selection; + public: + Runner(Win32AppContext& appContext, AssetListDialog& listDialog, std::vector _selection) + : appContext(appContext), listDialog(listDialog), selection(std::move(_selection)) + {} + void operator()() + { + auto pDialogDesc = std::make_unique(appContext, std::move(selection), "\\.(?:tga|png)"); + if (pDialogDesc->getElements().size() > 1) + { + WCHAR* folderPathW = nullptr; + if (!ShowFolderSelectDialog(appContext.getMainWindow().getWindow(), &folderPathW, L"Select an input directory", UABE_FILEDIALOG_EXPIMPASSET_GUID)) + return; + auto folderPath8 = unique_WideToMultiByte(folderPathW); + FreeCOMFilePathBuf(&folderPathW); + + auto pModifyDialog = std::make_shared(std::move(pDialogDesc), folderPath8.get(), appContext, listDialog); + listDialog.addModifyDialog(pModifyDialog); + + } + else + { + if (pDialogDesc->ShowAssetSettings(0, appContext.getMainWindow().getWindow()) + && pDialogDesc->hasAnyChanges()) + { + auto pTask = std::make_shared(appContext, std::move(pDialogDesc)); + appContext.taskManager.enqueue(pTask); + } + } + } + }; + EAssetOptionType getType() + { + return EAssetOptionType::Import; + } + std::unique_ptr prepareForSelection( + Win32AppContext& appContext, AssetListDialog& listDialog, + std::vector selection, + std::string& optionName) + { + if (!PluginSupportsElements(selection)) + return nullptr; + optionName = "Edit"; + return std::make_unique(appContext, listDialog, std::move(selection)); + } +}; + +class Win32TexturePluginDesc : public GenericTexturePluginDesc +{ +public: + Win32TexturePluginDesc() + : GenericTexturePluginDesc() + { + pProviders.push_back(std::make_shared()); + } +}; + +IPluginDesc* GetUABEPluginDesc1(size_t sizeof_AppContext, size_t sizeof_BundleFileContextInfo) +{ + if (sizeof_AppContext != sizeof(AppContext) || sizeof_BundleFileContextInfo != sizeof(BundleFileContextInfo)) + { + assert(false); + return nullptr; + } + return new Win32TexturePluginDesc(); +} + +BOOL APIENTRY DllMain(HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved +) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + g_hModule = hModule; + break; + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} diff --git a/Plugins/Texture/defines.h b/Plugins/Texture/defines.h new file mode 100644 index 0000000..ff94509 --- /dev/null +++ b/Plugins/Texture/defines.h @@ -0,0 +1,515 @@ +#pragma once + +#include +#include +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#include "resource.h" + +#include "../UABE_Generic/PluginManager.h" +#include +#include +#include + +//following enum : Partially Copyright (c) Imagination Technologies Limited. +enum ECompressorQuality +{ + eDXTnNormal=0, + eDXTnNormalMt, + eDXTnVeryFast, + eDXTnVeryFastMt, + eDXTnSlow, + eDXTnSlowMt, + + ePVRTCFastest=0, //!< PVRTC fastest + ePVRTCFast, //!< PVRTC fast + ePVRTCNormal, //!< PVRTC normal + ePVRTCHigh, //!< PVRTC high + ePVRTCBest, //!< PVRTC best + eNumPVRTCModes, //!< Number of PVRTC modes + + eETCFast=0, //!< ETC fast + eETCFastPerceptual, //!< ETC fast perceptual + eETCSlow, //!< ETC slow + eETCSlowPerceptual, //!< ETC slow perceptual + eNumETCModes, //!< Number of ETC modes + + eASTCVeryFast=0, //!< ASTC very fast + eASTCFast, //!< ASTC fast + eASTCMedium, //!< ASTC medium + eASTCThorough, //!< ASTC thorough + eASTCExhaustive, //!< ASTC exhaustive + eNumASTCModes, //!< Number of ASTC modes + //following entries : custom + eBCnVeryFast=0, //!< Texgenpack Ultra preset + eBCnFast=8, //!< Texgenpack Fast preset + eBCnMedium=16, //!< Texgenpack Medium preset + eBCnSlow=32, //!< Texgenpack Slow preset + eBCnVerySlow=40, //!< Texgenpack Very Slow (COMPRESSION_LEVEL_CLASS2+7) + eBCnPlacebo=50, //!< Texgenpack Placebo (COMPRESSION_LEVEL_CLASS2+17) + + eBC6HVeryFast=0, //!< ISPC Compressor Very Fast preset + eBC6HFast, //!< ISPC Compressor Fast preset + eBC6HBasic, //!< ISPC Compressor Basic preset + eBC6HSlow, //!< ISPC Compressor Slow preset + eBC6HVerySlow, //!< ISPC Compressor Very Slow preset + eBC6HVeryFastMT, //!< ISPC Compressor Very Fast preset, multithread + eBC6HFastMT, //!< ISPC Compressor Fast preset, multithread + eBC6HBasicMT, //!< ISPC Compressor Basic preset, multithread + eBC6HSlowMT, //!< ISPC Compressor Slow preset, multithread + eBC6HVerySlowMT, //!< ISPC Compressor Very Slow preset, multithread + + eBC7UltraFast=0, //!< ISPC Compressor Ultra Fast preset, multithread + eBC7VeryFast, //!< ISPC Compressor Very Fast preset, multithread + eBC7Fast, //!< ISPC Compressor Fast preset, multithread + eBC7Basic, //!< ISPC Compressor Basic preset, multithread + eBC7Slow, //!< ISPC Compressor Slow preset, multithread + eBC7UltraFastMT, //!< ISPC Compressor Ultra Fast preset, multithread + eBC7VeryFastMT, //!< ISPC Compressor Very Fast preset, multithread + eBC7FastMT, //!< ISPC Compressor Fast preset, multithread + eBC7BasicMT, //!< ISPC Compressor Basic preset, multithread + eBC7SlowMT, //!< ISPC Compressor Slow preset, multithread +}; + + +class TextureImportParam +{ +public: + AssetIdentifier asset; + std::vector hideDialogElementsList; //Indices to ImportDialogPairs_size + + std::string importTextureName; + bool assetWasTextureFile; + //If importTextureInfo.pPictureData == NULL but importTextureData is not empty, + // importTextureData contains the RGBA32 data to override the original texture with, + // and does not contain any mip maps. + //Otherwise, importTextureInfo.pPictureData is set to importTextureData.data(). + TextureFile importTextureInfo; + std::vector importTextureData; + inline void assignCompressedTextureData(std::vector _data) + { + this->importTextureData = std::move(_data); + importTextureInfo.pPictureData = this->importTextureData.data(); + } + bool textureDataModified; + + class + { + public: + bool isBatchEntry; + std::string batchFilenameOverride; + + bool hasNewSettings; + + std::string newName; + unsigned int newTextureFormat; + bool newMipMap; + bool newReadable; + bool newReadAllowed; + unsigned int newFilterMode; + unsigned int newAnisoLevel; + float newMipBias; + unsigned int newWrapMode; + unsigned int newWrapModeU; + unsigned int newWrapModeV; + unsigned int newLightmapFmt; + unsigned int newColorSpace; + } batchInfo; + + ECompressorQuality qualitySelection; + bool qualitySelected; +public: + inline TextureImportParam(AssetIdentifier asset, + bool assetWasTextureFile, bool isBatchImportEntry, + ECompressorQuality qualitySelection = ePVRTCFastest) + { + this->asset = asset; + this->assetWasTextureFile = assetWasTextureFile; + this->batchInfo.isBatchEntry = isBatchImportEntry; + this->batchInfo.hasNewSettings = false; + this->qualitySelection = qualitySelection; + this->qualitySelected = false; + this->textureDataModified = false; + } +}; + + +//Retrieves a list of compression preset names for a specified texture format and returns the index of the default preset. +static size_t GetQualityPresetList(unsigned int textureFormat, std::vector &names) +{ + switch (textureFormat) + { + case TexFmt_DXT1: + case TexFmt_DXT5: + case TexFmt_DXT1Crunched: + case TexFmt_DXT5Crunched: + case TexFmt_ETC_RGB4Crunched: + case TexFmt_ETC2_RGBA8Crunched: + names.push_back(TEXT("Very Fast")); + names.push_back(TEXT("Very Fast (multithread)")); + names.push_back(TEXT("Normal")); + names.push_back(TEXT("Normal (multithread)")); + names.push_back(TEXT("Slow")); + names.push_back(TEXT("Slow (multithread)")); + return 3; + case TexFmt_ASTC_RGB_4x4: + case TexFmt_ASTC_RGB_5x5: + case TexFmt_ASTC_RGB_6x6: + case TexFmt_ASTC_RGB_8x8: + case TexFmt_ASTC_RGB_10x10: + case TexFmt_ASTC_RGB_12x12: + case TexFmt_ASTC_RGBA_4x4: + case TexFmt_ASTC_RGBA_5x5: + case TexFmt_ASTC_RGBA_6x6: + case TexFmt_ASTC_RGBA_8x8: + case TexFmt_ASTC_RGBA_10x10: + case TexFmt_ASTC_RGBA_12x12: + names.push_back(TEXT("Very Fast")); + names.push_back(TEXT("Fast")); + names.push_back(TEXT("Medium")); + names.push_back(TEXT("Thorough")); + names.push_back(TEXT("Exhaustive")); + return 2; + case TexFmt_EAC_R: + case TexFmt_EAC_R_SIGNED: + case TexFmt_EAC_RG: + case TexFmt_EAC_RG_SIGNED: + case TexFmt_ETC_RGB4: + case TexFmt_ETC_RGB4_3DS: + case TexFmt_ETC_RGBA8_3DS: + case TexFmt_ETC2_RGB4: + case TexFmt_ETC2_RGBA1: + case TexFmt_ETC2_RGBA8: + names.push_back(TEXT("Fast")); + names.push_back(TEXT("Fast Perceptual")); + names.push_back(TEXT("Slow")); + names.push_back(TEXT("Slow Perceptual")); + return 2; + case TexFmt_PVRTC_RGB2: + case TexFmt_PVRTC_RGB4: + case TexFmt_PVRTC_RGBA2: + case TexFmt_PVRTC_RGBA4: + names.push_back(TEXT("Fastest")); + names.push_back(TEXT("Fast")); + names.push_back(TEXT("Normal")); + names.push_back(TEXT("High")); + names.push_back(TEXT("Best")); + return 2; + case TexFmt_BC4: + case TexFmt_BC5: + names.push_back(TEXT("Very Fast")); + names.push_back(TEXT("Fast")); + names.push_back(TEXT("Medium")); + names.push_back(TEXT("Slow")); + names.push_back(TEXT("Very Slow")); + names.push_back(TEXT("Placebo")); + return 2; + case TexFmt_BC6H: + names.push_back(TEXT("Very Fast")); //0 + names.push_back(TEXT("Very Fast (multithread)")); //5 + names.push_back(TEXT("Fast")); //1 + names.push_back(TEXT("Fast (multithread)")); //6 + names.push_back(TEXT("Basic")); //2 + names.push_back(TEXT("Basic (multithread)")); //7 + names.push_back(TEXT("Slow")); //3 + names.push_back(TEXT("Slow (multithread)")); //8 + names.push_back(TEXT("Very Slow")); //4 + names.push_back(TEXT("Very Slow (multithread)")); //9 + return 7; + case TexFmt_BC7: + names.push_back(TEXT("Ultra Fast")); + names.push_back(TEXT("Ultra Fast (multithread)")); + names.push_back(TEXT("Very Fast")); + names.push_back(TEXT("Very Fast (multithread)")); + names.push_back(TEXT("Fast")); + names.push_back(TEXT("Fast (multithread)")); + names.push_back(TEXT("Basic")); + names.push_back(TEXT("Basic (multithread)")); + names.push_back(TEXT("Slow")); + names.push_back(TEXT("Slow (multithread)")); + return 7; + default: + return (size_t)-1; + } +} +static ECompressorQuality GetQualityFromPresetListIdx(unsigned int textureFormat, size_t index) +{ + ECompressorQuality qualitySelection = (ECompressorQuality)index; + switch (textureFormat) + { + case TexFmt_DXT1: + case TexFmt_DXT5: + case TexFmt_DXT1Crunched: + case TexFmt_DXT5Crunched: + case TexFmt_ETC_RGB4Crunched: + case TexFmt_ETC2_RGBA8Crunched: + switch (qualitySelection) + { + case 0: + qualitySelection = eDXTnVeryFast; + break; + case 1: + qualitySelection = eDXTnVeryFastMt; + break; + case 2: + qualitySelection = eDXTnNormal; + break; + default: + case 3: + qualitySelection = eDXTnNormalMt; + break; + case 4: + qualitySelection = eDXTnSlow; + break; + case 5: + qualitySelection = eDXTnSlowMt; + break; + } + break; + case TexFmt_BC4: + case TexFmt_BC5: + switch (qualitySelection) + { + default: + case 0: + qualitySelection = eBCnVeryFast; + break; + case 1: + qualitySelection = eBCnFast; + break; + case 2: + qualitySelection = eBCnMedium; + break; + case 3: + qualitySelection = eBCnSlow; + break; + case 4: + qualitySelection = eBCnVerySlow; + break; + case 5: + qualitySelection = eBCnPlacebo; + break; + } + break; + case TexFmt_BC6H: + case TexFmt_BC7: + if (qualitySelection < 0 || qualitySelection > eBC6HVerySlowMT) + qualitySelection = eBC6HBasic; + else if (qualitySelection & 1) //qualitySelection % 2, multithread + qualitySelection = (ECompressorQuality)(eBC6HVeryFastMT + (qualitySelection >> 1)); + else + qualitySelection = (ECompressorQuality)(qualitySelection >> 1); + break; + default: + if (qualitySelection < 0 || qualitySelection >= eNumPVRTCModes) + qualitySelection = ePVRTCFastest; + break; + } + return qualitySelection; +} + +struct TextureNameIDPair +{ + const TCHAR *name; + TextureFormat textureType; + int versionRangeMin; //-1 for no minimum (see SupportsTextureFormat) + int versionRangeMax; //-1 for no maximum + char sizeMul; + bool showQualityDialog; +}; + +static const TextureNameIDPair SupportedTextureNames[] = { + {TEXT("ARGB32"), TexFmt_ARGB32, -1, -1, 4, false}, + {TEXT("BGRA32"), TexFmt_BGRA32New, -1, -1, 4, false}, //TexFmt_BGRA32Old or TexFmt_BGRA32New + {TEXT("RGBA32"), TexFmt_RGBA32, -1, -1, 4, false}, + {TEXT("RGB24"), TexFmt_RGB24, -1, -1, 3, false}, + {TEXT("ARGB4444"), TexFmt_ARGB4444, -1, -1, 2, false}, + {TEXT("RGBA4444"), TexFmt_RGBA4444, -1, -1, 2, false}, + {TEXT("RGB565"), TexFmt_RGB565, -1, -1, 2, false}, + {TEXT("Alpha8"), TexFmt_Alpha8, -1, -1, 1, false}, + {TEXT("R8"), TexFmt_R8, -1, -1, 1, false}, + {TEXT("R16"), TexFmt_R16, -1, -1, 2, false}, + {TEXT("RG16"), TexFmt_RG16, -1, -1, 2, false}, + {TEXT("RHalf"), TexFmt_RHalf, -1, -1, 2, false}, + {TEXT("RGHalf"), TexFmt_RGHalf, -1, -1, 4, false}, + {TEXT("RGBAHalf"), TexFmt_RGBAHalf, -1, -1, 8, false}, + {TEXT("RFloat"), TexFmt_RFloat, -1, -1, 4, false}, + {TEXT("RGFloat"), TexFmt_RGFloat, -1, -1, 8, false}, + {TEXT("RGBAFloat"), TexFmt_RGBAFloat, -1, -1, 16, false}, + {TEXT("RGB9e5Float"), TexFmt_RGB9e5Float, -1, -1, 4, false}, + {TEXT("RG32"), TexFmt_RG32, -1, -1, 4, false}, + {TEXT("RGB48"), TexFmt_RGB48, -1, -1, 6, false}, + {TEXT("RGBA64"), TexFmt_RGBA64, -1, -1, 8, false}, + {TEXT("YUV2"), TexFmt_YUV2, -1, -1, 2, false}, + {TEXT("DXT1"), TexFmt_DXT1, -1, -1, -1, true}, + {TEXT("DXT1Crunched (slow!)"), TexFmt_DXT1Crunched, -1, 0, -1, true}, + {TEXT("DXT1Crunched"), TexFmt_DXT1Crunched, 1, -1, -1, true}, + {TEXT("DXT5"), TexFmt_DXT5, -1, -1, -1, true}, + {TEXT("DXT5Crunched (slow!)"), TexFmt_DXT5Crunched, -1, 0, -1, true}, + {TEXT("DXT5Crunched"), TexFmt_DXT5Crunched, 1, -1, -1, true}, + {TEXT("EAC_R"), TexFmt_EAC_R, -1, -1, -1, true}, + {TEXT("EAC_R_SIGNED"), TexFmt_EAC_R_SIGNED, -1, -1, -1, true}, + {TEXT("EAC_RG"), TexFmt_EAC_RG, -1, -1, -1, true}, + {TEXT("EAC_RG_SIGNED"), TexFmt_EAC_RG_SIGNED, -1, -1, -1, true}, + {TEXT("ETC_RGB4"), TexFmt_ETC_RGB4, -1, -1, -1, true}, + {TEXT("ETC_RGB4Crunched"), TexFmt_ETC_RGB4Crunched, -1, -1, -1, true}, + {TEXT("ETC2_RGBA8Crunched"), TexFmt_ETC2_RGBA8Crunched, -1, -1, -1, true}, + {TEXT("ETC_RGB4_3DS"), TexFmt_ETC_RGB4_3DS, -1, -1, -1, true}, + {TEXT("ETC_RGBA8_3DS"), TexFmt_ETC_RGBA8_3DS, -1, -1, -1, true}, + {TEXT("ETC2_RGB4"), TexFmt_ETC2_RGB4, -1, -1, -1, true}, + {TEXT("ETC2_RGB_A1"), TexFmt_ETC2_RGBA1, -1, -1, -1, true}, + {TEXT("ETC2_RGBA8"), TexFmt_ETC2_RGBA8, -1, -1, -1, true}, + {TEXT("PVRTC_RGB2"), TexFmt_PVRTC_RGB2, -1, -1, -1, true}, + {TEXT("PVRTC_RGBA2"), TexFmt_PVRTC_RGBA2, -1, -1, -1, true}, + {TEXT("PVRTC_RGB4"), TexFmt_PVRTC_RGB4, -1, -1, -1, true}, + {TEXT("PVRTC_RGBA4"), TexFmt_PVRTC_RGBA4, -1, -1, -1, true}, + {TEXT("ASTC_4x4"), TexFmt_ASTC_4x4, 2, -1, -1, true}, + {TEXT("ASTC_5x5"), TexFmt_ASTC_5x5, 2, -1, -1, true}, + {TEXT("ASTC_6x6"), TexFmt_ASTC_6x6, 2, -1, -1, true}, + {TEXT("ASTC_8x8"), TexFmt_ASTC_8x8, 2, -1, -1, true}, + {TEXT("ASTC_10x10"), TexFmt_ASTC_10x10, 2, -1, -1, true}, + {TEXT("ASTC_12x12"), TexFmt_ASTC_12x12, 2, -1, -1, true}, + {TEXT("ASTC_RGB_4x4"), TexFmt_ASTC_RGB_4x4, -1, 1, -1, true}, //Same ID as ASTC_*x* + {TEXT("ASTC_RGB_5x5"), TexFmt_ASTC_RGB_5x5, -1, 1, -1, true}, //Same ID as ASTC_*x* + {TEXT("ASTC_RGB_6x6"), TexFmt_ASTC_RGB_6x6, -1, 1, -1, true}, //Same ID as ASTC_*x* + {TEXT("ASTC_RGB_8x8"), TexFmt_ASTC_RGB_8x8, -1, 1, -1, true}, //Same ID as ASTC_*x* + {TEXT("ASTC_RGB_10x10"), TexFmt_ASTC_RGB_10x10, -1, 1, -1, true}, //Same ID as ASTC_*x* + {TEXT("ASTC_RGB_12x12"), TexFmt_ASTC_RGB_12x12, -1, 1, -1, true}, //Same ID as ASTC_*x* + {TEXT("ASTC_RGBA_4x4"), TexFmt_ASTC_RGBA_4x4, -1, 1, -1, true}, + {TEXT("ASTC_RGBA_5x5"), TexFmt_ASTC_RGBA_5x5, -1, 1, -1, true}, + {TEXT("ASTC_RGBA_6x6"), TexFmt_ASTC_RGBA_6x6, -1, 1, -1, true}, + {TEXT("ASTC_RGBA_8x8"), TexFmt_ASTC_RGBA_8x8, -1, 1, -1, true}, + {TEXT("ASTC_RGBA_10x10"), TexFmt_ASTC_RGBA_10x10, -1, 1, -1, true}, + {TEXT("ASTC_RGBA_12x12"), TexFmt_ASTC_RGBA_12x12, -1, 1, -1, true}, + {TEXT("ASTC_HDR_4x4"), TexFmt_ASTC_HDR_4x4, -1, -1, -1, true}, + {TEXT("ASTC_HDR_5x5"), TexFmt_ASTC_HDR_5x5, -1, -1, -1, true}, + {TEXT("ASTC_HDR_6x6"), TexFmt_ASTC_HDR_6x6, -1, -1, -1, true}, + {TEXT("ASTC_HDR_8x8"), TexFmt_ASTC_HDR_8x8, -1, -1, -1, true}, + {TEXT("ASTC_HDR_10x10"), TexFmt_ASTC_HDR_10x10, -1, -1, -1, true}, + {TEXT("ASTC_HDR_12x12"), TexFmt_ASTC_HDR_12x12, -1, -1, -1, true}, + {TEXT("BC4"), TexFmt_BC4, -1, -1, -1, false}, + {TEXT("BC5"), TexFmt_BC5, -1, -1, -1, false}, + {TEXT("BC6H"), TexFmt_BC6H, -1, -1, -1, true}, + {TEXT("BC7"), TexFmt_BC7, -1, -1, -1, true}, +}; +#define SupportedTextureNames_size (sizeof(SupportedTextureNames) / sizeof(TextureNameIDPair)) +static bool IsTextureNameIDPairInRange(const TextureNameIDPair *pPair, int version) +{ + return (pPair->versionRangeMin == -1 ? true : pPair->versionRangeMin <= version) + && (pPair->versionRangeMax == -1 ? true : pPair->versionRangeMax >= version); +} +static size_t GetTextureNameIDPair(unsigned int textureFormat, int version) +{ + if (textureFormat == TexFmt_BGRA32Old) textureFormat = TexFmt_BGRA32New; + for (size_t i = 0; i < SupportedTextureNames_size; i++) + { + if (SupportedTextureNames[i].textureType == textureFormat + && IsTextureNameIDPairInRange(&SupportedTextureNames[i], version)) + return i; + } + return (size_t)-1; +} +static size_t GetTextureDataSize(const TextureNameIDPair *pPair, unsigned int width, unsigned int height) +{ + size_t ret = 0; + if (pPair->sizeMul <= 0) + { + TextureFormat textureType = pPair->textureType; + if (textureType == TexFmt_DXT1Crunched) + { + textureType = TexFmt_DXT1; + ret = 1024; + } + else if (textureType == TexFmt_DXT5Crunched) + { + textureType = TexFmt_DXT5; + ret = 1024; + } + else if (textureType == TexFmt_ETC_RGB4Crunched) + { + textureType = TexFmt_ETC_RGB4; + ret = 1024; + } + else if (textureType == TexFmt_ETC2_RGBA8Crunched) + { + textureType = TexFmt_ETC2_RGBA8; + ret = 1024; + } + ret += GetCompressedTextureDataSize(width, height, textureType); + } + else + ret = width * height * pPair->sizeMul; + return ret; +} + + +struct DialogElementPair { int left; int right; short height; short leftOffset; short leftX; short rightX; }; +static const DialogElementPair ImportDialogPairs[] = { + {-1, -1, 13, 0, 0, 0}, + {IDC_SNAME, IDC_ENAME, 30, 3, 15, 124}, + {IDC_STEXFMT, IDC_CBTEXFMT, 27, 3, 15, 124}, + {IDC_SMIPMAP, IDC_CKMIPMAP, 20, 1, 15, 124}, + {IDC_SREADABLE, IDC_CKREADABLE, 20, 1, 15, 124}, + {IDC_SREADALLOWED, IDC_CKREADALLOWED, 21, 1, 15, 124}, + {IDC_SFILTERMODE, IDC_CBFILTERMODE, 28, 3, 15, 124}, + {IDC_SANISO, IDC_EANISO, 30, 3, 15, 124}, + {IDC_SMIPBIAS, IDC_EMIPBIAS, 30, 3, 15, 124}, + {IDC_SWRAPMODE, IDC_CBWRAPMODE, 28, 3, 15, 124}, + {IDC_SWRAPMODEU, IDC_CBWRAPMODEU, 27, 3, 15, 124}, + {IDC_SWRAPMODEV, IDC_CBWRAPMODEV, 28, 3, 15, 124}, + {IDC_SLIGHTMAPFMT, IDC_ELIGHTMAPFMT, 30, 3, 15, 124}, + {IDC_SCLSPACE, IDC_CBCLSPACE, 27, 3, 15, 124}, + {IDC_SLOAD, IDC_BLOAD, 30, 3, 15, 124}, + {IDOK, IDCANCEL, 35, 0, 15, 192} +}; +#define ImportDialogPairs_size (sizeof(ImportDialogPairs) / sizeof(DialogElementPair)) +static size_t GetImportDialogPairByID(int id) +{ + for (size_t i = 0; i < ImportDialogPairs_size; i++) + { + if (ImportDialogPairs[i].left == id || ImportDialogPairs[i].right == id) return i; + } + return (size_t)-1; +} +static void InactivateDialogPairsByIdx(HWND hDlg, std::vector &indices) +{ + bool inactivate[ImportDialogPairs_size] = {}; + for (size_t i = 0; i < indices.size(); i++) + if (indices[i] < ImportDialogPairs_size) inactivate[indices[i]] = true; + + int curY = 0; + for (size_t i = 0; i < ImportDialogPairs_size; i++) + { + HWND hLeft = GetDlgItem(hDlg, ImportDialogPairs[i].left); + HWND hRight = GetDlgItem(hDlg, ImportDialogPairs[i].right); + + if (hLeft) + SetWindowPos(hLeft, NULL, ImportDialogPairs[i].leftX, curY + ImportDialogPairs[i].leftOffset, 0, 0, + SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOZORDER); + if (hRight) + SetWindowPos(hRight, NULL, ImportDialogPairs[i].rightX, curY, 0, 0, + SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOZORDER); + + if (inactivate[i]) + { + if (hLeft) + ShowWindow(hLeft, SW_HIDE); + if (hRight) + ShowWindow(hRight, SW_HIDE); + } + else + { + curY += ImportDialogPairs[i].height; + } + } + + RECT dialogRect = {}; + GetWindowRect(hDlg, &dialogRect); + RECT dialogClientRect = {}; + GetClientRect(hDlg, &dialogClientRect); + SetWindowPos(hDlg, NULL, 0, 0, + dialogRect.right - dialogRect.left, + ((dialogRect.bottom - dialogRect.top) - (dialogClientRect.bottom - dialogClientRect.top)) + curY, + SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOZORDER); +} \ No newline at end of file diff --git a/Plugins/Texture/lodepng.cpp b/Plugins/Texture/lodepng.cpp new file mode 100644 index 0000000..f8f63eb --- /dev/null +++ b/Plugins/Texture/lodepng.cpp @@ -0,0 +1,6174 @@ +/* +LodePNG version 20150418 + +Copyright (c) 2005-2015 Lode Vandevenne + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +/* +The manual and changelog are in the header file "lodepng.h" +Rename this file to lodepng.cpp to use it for C++, or to lodepng.c to use it for C. +*/ + +#include "lodepng.h" + +#include +#include + +#ifdef LODEPNG_COMPILE_CPP +#include +#endif /*LODEPNG_COMPILE_CPP*/ + +#if defined(_MSC_VER) && (_MSC_VER >= 1310) /*Visual Studio: A few warning types are not desired here.*/ +#pragma warning( disable : 4244 ) /*implicit conversions: not warned by gcc -Wall -Wextra and requires too much casts*/ +#pragma warning( disable : 4996 ) /*VS does not like fopen, but fopen_s is not standard C so unusable here*/ +#endif /*_MSC_VER */ + +const char* LODEPNG_VERSION_STRING = "20150418"; + +/* +This source file is built up in the following large parts. The code sections +with the "LODEPNG_COMPILE_" #defines divide this up further in an intermixed way. +-Tools for C and common code for PNG and Zlib +-C Code for Zlib (huffman, deflate, ...) +-C Code for PNG (file format chunks, adam7, PNG filters, color conversions, ...) +-The C++ wrapper around all of the above +*/ + +/*The malloc, realloc and free functions defined here with "lodepng_" in front +of the name, so that you can easily change them to others related to your +platform if needed. Everything else in the code calls these. Pass +-DLODEPNG_NO_COMPILE_ALLOCATORS to the compiler, or comment out +#define LODEPNG_COMPILE_ALLOCATORS in the header, to disable the ones here and +define them in your own project's source files without needing to change +lodepng source code. Don't forget to remove "static" if you copypaste them +from here.*/ + +#ifdef LODEPNG_COMPILE_ALLOCATORS +static void* lodepng_malloc(size_t size) +{ + return malloc(size); +} + +static void* lodepng_realloc(void* ptr, size_t new_size) +{ + return realloc(ptr, new_size); +} + +static void lodepng_free(void* ptr) +{ + free(ptr); +} +#else /*LODEPNG_COMPILE_ALLOCATORS*/ +void* lodepng_malloc(size_t size); +void* lodepng_realloc(void* ptr, size_t new_size); +void lodepng_free(void* ptr); +#endif /*LODEPNG_COMPILE_ALLOCATORS*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // Tools for C, and common code for PNG and Zlib. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/* +Often in case of an error a value is assigned to a variable and then it breaks +out of a loop (to go to the cleanup phase of a function). This macro does that. +It makes the error handling code shorter and more readable. + +Example: if(!uivector_resizev(&frequencies_ll, 286, 0)) ERROR_BREAK(83); +*/ +#define CERROR_BREAK(errorvar, code)\ +{\ + errorvar = code;\ + break;\ +} + +/*version of CERROR_BREAK that assumes the common case where the error variable is named "error"*/ +#define ERROR_BREAK(code) CERROR_BREAK(error, code) + +/*Set error var to the error code, and return it.*/ +#define CERROR_RETURN_ERROR(errorvar, code)\ +{\ + errorvar = code;\ + return code;\ +} + +/*Try the code, if it returns error, also return the error.*/ +#define CERROR_TRY_RETURN(call)\ +{\ + unsigned error = call;\ + if(error) return error;\ +} + +/*Set error var to the error code, and return from the void function.*/ +#define CERROR_RETURN(errorvar, code)\ +{\ + errorvar = code;\ + return;\ +} + +/* +About uivector, ucvector and string: +-All of them wrap dynamic arrays or text strings in a similar way. +-LodePNG was originally written in C++. The vectors replace the std::vectors that were used in the C++ version. +-The string tools are made to avoid problems with compilers that declare things like strncat as deprecated. +-They're not used in the interface, only internally in this file as static functions. +-As with many other structs in this file, the init and cleanup functions serve as ctor and dtor. +*/ + +#ifdef LODEPNG_COMPILE_ZLIB +/*dynamic vector of unsigned ints*/ +typedef struct uivector +{ + unsigned* data; + size_t size; /*size in number of unsigned longs*/ + size_t allocsize; /*allocated size in bytes*/ +} uivector; + +static void uivector_cleanup(void* p) +{ + ((uivector*)p)->size = ((uivector*)p)->allocsize = 0; + lodepng_free(((uivector*)p)->data); + ((uivector*)p)->data = NULL; +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned uivector_reserve(uivector* p, size_t allocsize) +{ + if(allocsize > p->allocsize) + { + size_t newsize = (allocsize > p->allocsize * 2) ? allocsize : (allocsize * 3 / 2); + void* data = lodepng_realloc(p->data, newsize); + if(data) + { + p->allocsize = newsize; + p->data = (unsigned*)data; + } + else return 0; /*error: not enough memory*/ + } + return 1; +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned uivector_resize(uivector* p, size_t size) +{ + if(!uivector_reserve(p, size * sizeof(unsigned))) return 0; + p->size = size; + return 1; /*success*/ +} + +/*resize and give all new elements the value*/ +static unsigned uivector_resizev(uivector* p, size_t size, unsigned value) +{ + size_t oldsize = p->size, i; + if(!uivector_resize(p, size)) return 0; + for(i = oldsize; i < size; ++i) p->data[i] = value; + return 1; +} + +static void uivector_init(uivector* p) +{ + p->data = NULL; + p->size = p->allocsize = 0; +} + +#ifdef LODEPNG_COMPILE_ENCODER +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned uivector_push_back(uivector* p, unsigned c) +{ + if(!uivector_resize(p, p->size + 1)) return 0; + p->data[p->size - 1] = c; + return 1; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_ZLIB*/ + +/* /////////////////////////////////////////////////////////////////////////// */ + +/*dynamic vector of unsigned chars*/ +typedef struct ucvector +{ + unsigned char* data; + size_t size; /*used size*/ + size_t allocsize; /*allocated size*/ +} ucvector; + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned ucvector_reserve(ucvector* p, size_t allocsize) +{ + if(allocsize > p->allocsize) + { + size_t newsize = (allocsize > p->allocsize * 2) ? allocsize : (allocsize * 3 / 2); + void* data = lodepng_realloc(p->data, newsize); + if(data) + { + p->allocsize = newsize; + p->data = (unsigned char*)data; + } + else return 0; /*error: not enough memory*/ + } + return 1; +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned ucvector_resize(ucvector* p, size_t size) +{ + if(!ucvector_reserve(p, size * sizeof(unsigned char))) return 0; + p->size = size; + return 1; /*success*/ +} + +#ifdef LODEPNG_COMPILE_PNG + +static void ucvector_cleanup(void* p) +{ + ((ucvector*)p)->size = ((ucvector*)p)->allocsize = 0; + lodepng_free(((ucvector*)p)->data); + ((ucvector*)p)->data = NULL; +} + +static void ucvector_init(ucvector* p) +{ + p->data = NULL; + p->size = p->allocsize = 0; +} + +#ifdef LODEPNG_COMPILE_DECODER +/*resize and give all new elements the value*/ +static unsigned ucvector_resizev(ucvector* p, size_t size, unsigned char value) +{ + size_t oldsize = p->size, i; + if(!ucvector_resize(p, size)) return 0; + for(i = oldsize; i < size; ++i) p->data[i] = value; + return 1; +} +#endif /*LODEPNG_COMPILE_DECODER*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ZLIB +/*you can both convert from vector to buffer&size and vica versa. If you use +init_buffer to take over a buffer and size, it is not needed to use cleanup*/ +static void ucvector_init_buffer(ucvector* p, unsigned char* buffer, size_t size) +{ + p->data = buffer; + p->allocsize = p->size = size; +} +#endif /*LODEPNG_COMPILE_ZLIB*/ + +#if (defined(LODEPNG_COMPILE_PNG) && defined(LODEPNG_COMPILE_ANCILLARY_CHUNKS)) || defined(LODEPNG_COMPILE_ENCODER) +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned ucvector_push_back(ucvector* p, unsigned char c) +{ + if(!ucvector_resize(p, p->size + 1)) return 0; + p->data[p->size - 1] = c; + return 1; +} +#endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER)*/ + + +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_PNG +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned string_resize(char** out, size_t size) +{ + char* data = (char*)lodepng_realloc(*out, size + 1); + if(data) + { + data[size] = 0; /*null termination char*/ + *out = data; + } + return data != 0; +} + +/*init a {char*, size_t} pair for use as string*/ +static void string_init(char** out) +{ + *out = NULL; + string_resize(out, 0); +} + +/*free the above pair again*/ +static void string_cleanup(char** out) +{ + lodepng_free(*out); + *out = NULL; +} + +static void string_set(char** out, const char* in) +{ + size_t insize = strlen(in), i; + if(string_resize(out, insize)) + { + for(i = 0; i != insize; ++i) + { + (*out)[i] = in[i]; + } + } +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +/* ////////////////////////////////////////////////////////////////////////// */ + +unsigned lodepng_read32bitInt(const unsigned char* buffer) +{ + return (unsigned)((buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]); +} + +#if defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER) +/*buffer must have at least 4 allocated bytes available*/ +static void lodepng_set32bitInt(unsigned char* buffer, unsigned value) +{ + buffer[0] = (unsigned char)((value >> 24) & 0xff); + buffer[1] = (unsigned char)((value >> 16) & 0xff); + buffer[2] = (unsigned char)((value >> 8) & 0xff); + buffer[3] = (unsigned char)((value ) & 0xff); +} +#endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER)*/ + +#ifdef LODEPNG_COMPILE_ENCODER +static void lodepng_add32bitInt(ucvector* buffer, unsigned value) +{ + ucvector_resize(buffer, buffer->size + 4); /*todo: give error if resize failed*/ + lodepng_set32bitInt(&buffer->data[buffer->size - 4], value); +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / File IO / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DISK + +unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename) +{ + FILE* file; + long size; + + /*provide some proper output values if error will happen*/ + *out = 0; + *outsize = 0; + + file = fopen(filename, "rb"); + if(!file) return 78; + + /*get filesize:*/ + fseek(file , 0 , SEEK_END); + size = ftell(file); + rewind(file); + + /*read contents of the file into the vector*/ + *outsize = 0; + *out = (unsigned char*)lodepng_malloc((size_t)size); + if(size && (*out)) (*outsize) = fread(*out, 1, (size_t)size, file); + + fclose(file); + if(!(*out) && size) return 83; /*the above malloc failed*/ + return 0; +} + +/*write given buffer to the file, overwriting the file, it doesn't append to it.*/ +unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename) +{ + FILE* file; + file = fopen(filename, "wb" ); + if(!file) return 79; + fwrite((char*)buffer , 1 , buffersize, file); + fclose(file); + return 0; +} +/*write given buffer to the file, overwriting the file, it doesn't append to it.*/ +unsigned lodepng_save_fileW(const unsigned char* buffer, size_t buffersize, const wchar_t* filename) +{ + FILE* file; + file = _wfopen(filename, L"wb" ); + if(!file) return 79; + fwrite((char*)buffer , 1 , buffersize, file); + fclose(file); + return 0; +} + +#endif /*LODEPNG_COMPILE_DISK*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // End of common code and tools. Begin of Zlib related code. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_ENCODER +/*TODO: this ignores potential out of memory errors*/ +#define addBitToStream(/*size_t**/ bitpointer, /*ucvector**/ bitstream, /*unsigned char*/ bit)\ +{\ + /*add a new byte at the end*/\ + if(((*bitpointer) & 7) == 0) ucvector_push_back(bitstream, (unsigned char)0);\ + /*earlier bit of huffman code is in a lesser significant bit of an earlier byte*/\ + (bitstream->data[bitstream->size - 1]) |= (bit << ((*bitpointer) & 0x7));\ + ++(*bitpointer);\ +} + +static void addBitsToStream(size_t* bitpointer, ucvector* bitstream, unsigned value, size_t nbits) +{ + size_t i; + for(i = 0; i != nbits; ++i) addBitToStream(bitpointer, bitstream, (unsigned char)((value >> i) & 1)); +} + +static void addBitsToStreamReversed(size_t* bitpointer, ucvector* bitstream, unsigned value, size_t nbits) +{ + size_t i; + for(i = 0; i != nbits; ++i) addBitToStream(bitpointer, bitstream, (unsigned char)((value >> (nbits - 1 - i)) & 1)); +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +#define READBIT(bitpointer, bitstream) ((bitstream[bitpointer >> 3] >> (bitpointer & 0x7)) & (unsigned char)1) + +static unsigned char readBitFromStream(size_t* bitpointer, const unsigned char* bitstream) +{ + unsigned char result = (unsigned char)(READBIT(*bitpointer, bitstream)); + ++(*bitpointer); + return result; +} + +static unsigned readBitsFromStream(size_t* bitpointer, const unsigned char* bitstream, size_t nbits) +{ + unsigned result = 0, i; + for(i = 0; i != nbits; ++i) + { + result += ((unsigned)READBIT(*bitpointer, bitstream)) << i; + ++(*bitpointer); + } + return result; +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Deflate - Huffman / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#define FIRST_LENGTH_CODE_INDEX 257 +#define LAST_LENGTH_CODE_INDEX 285 +/*256 literals, the end code, some length codes, and 2 unused codes*/ +#define NUM_DEFLATE_CODE_SYMBOLS 288 +/*the distance codes have their own symbols, 30 used, 2 unused*/ +#define NUM_DISTANCE_SYMBOLS 32 +/*the code length codes. 0-15: code lengths, 16: copy previous 3-6 times, 17: 3-10 zeros, 18: 11-138 zeros*/ +#define NUM_CODE_LENGTH_CODES 19 + +/*the base lengths represented by codes 257-285*/ +static const unsigned LENGTHBASE[29] + = {3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, + 67, 83, 99, 115, 131, 163, 195, 227, 258}; + +/*the extra bits used by codes 257-285 (added to base length)*/ +static const unsigned LENGTHEXTRA[29] + = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, + 4, 4, 4, 4, 5, 5, 5, 5, 0}; + +/*the base backwards distances (the bits of distance codes appear after length codes and use their own huffman tree)*/ +static const unsigned DISTANCEBASE[30] + = {1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, + 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577}; + +/*the extra bits of backwards distances (added to base)*/ +static const unsigned DISTANCEEXTRA[30] + = {0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, + 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13}; + +/*the order in which "code length alphabet code lengths" are stored, out of this +the huffman tree of the dynamic huffman tree lengths is generated*/ +static const unsigned CLCL_ORDER[NUM_CODE_LENGTH_CODES] + = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + +/* ////////////////////////////////////////////////////////////////////////// */ + +/* +Huffman tree struct, containing multiple representations of the tree +*/ +typedef struct HuffmanTree +{ + unsigned* tree2d; + unsigned* tree1d; + unsigned* lengths; /*the lengths of the codes of the 1d-tree*/ + unsigned maxbitlen; /*maximum number of bits a single code can get*/ + unsigned numcodes; /*number of symbols in the alphabet = number of codes*/ +} HuffmanTree; + +/*function used for debug purposes to draw the tree in ascii art with C++*/ +/* +static void HuffmanTree_draw(HuffmanTree* tree) +{ + std::cout << "tree. length: " << tree->numcodes << " maxbitlen: " << tree->maxbitlen << std::endl; + for(size_t i = 0; i != tree->tree1d.size; ++i) + { + if(tree->lengths.data[i]) + std::cout << i << " " << tree->tree1d.data[i] << " " << tree->lengths.data[i] << std::endl; + } + std::cout << std::endl; +}*/ + +static void HuffmanTree_init(HuffmanTree* tree) +{ + tree->tree2d = 0; + tree->tree1d = 0; + tree->lengths = 0; +} + +static void HuffmanTree_cleanup(HuffmanTree* tree) +{ + lodepng_free(tree->tree2d); + lodepng_free(tree->tree1d); + lodepng_free(tree->lengths); +} + +/*the tree representation used by the decoder. return value is error*/ +static unsigned HuffmanTree_make2DTree(HuffmanTree* tree) +{ + unsigned nodefilled = 0; /*up to which node it is filled*/ + unsigned treepos = 0; /*position in the tree (1 of the numcodes columns)*/ + unsigned n, i; + + tree->tree2d = (unsigned*)lodepng_malloc(tree->numcodes * 2 * sizeof(unsigned)); + if(!tree->tree2d) return 83; /*alloc fail*/ + + /* + convert tree1d[] to tree2d[][]. In the 2D array, a value of 32767 means + uninited, a value >= numcodes is an address to another bit, a value < numcodes + is a code. The 2 rows are the 2 possible bit values (0 or 1), there are as + many columns as codes - 1. + A good huffmann tree has N * 2 - 1 nodes, of which N - 1 are internal nodes. + Here, the internal nodes are stored (what their 0 and 1 option point to). + There is only memory for such good tree currently, if there are more nodes + (due to too long length codes), error 55 will happen + */ + for(n = 0; n < tree->numcodes * 2; ++n) + { + tree->tree2d[n] = 32767; /*32767 here means the tree2d isn't filled there yet*/ + } + + for(n = 0; n < tree->numcodes; ++n) /*the codes*/ + { + for(i = 0; i != tree->lengths[n]; ++i) /*the bits for this code*/ + { + unsigned char bit = (unsigned char)((tree->tree1d[n] >> (tree->lengths[n] - i - 1)) & 1); + /*oversubscribed, see comment in lodepng_error_text*/ + if(treepos > 2147483647 || treepos + 2 > tree->numcodes) return 55; + if(tree->tree2d[2 * treepos + bit] == 32767) /*not yet filled in*/ + { + if(i + 1 == tree->lengths[n]) /*last bit*/ + { + tree->tree2d[2 * treepos + bit] = n; /*put the current code in it*/ + treepos = 0; + } + else + { + /*put address of the next step in here, first that address has to be found of course + (it's just nodefilled + 1)...*/ + ++nodefilled; + /*addresses encoded with numcodes added to it*/ + tree->tree2d[2 * treepos + bit] = nodefilled + tree->numcodes; + treepos = nodefilled; + } + } + else treepos = tree->tree2d[2 * treepos + bit] - tree->numcodes; + } + } + + for(n = 0; n < tree->numcodes * 2; ++n) + { + if(tree->tree2d[n] == 32767) tree->tree2d[n] = 0; /*remove possible remaining 32767's*/ + } + + return 0; +} + +/* +Second step for the ...makeFromLengths and ...makeFromFrequencies functions. +numcodes, lengths and maxbitlen must already be filled in correctly. return +value is error. +*/ +static unsigned HuffmanTree_makeFromLengths2(HuffmanTree* tree) +{ + uivector blcount; + uivector nextcode; + unsigned error = 0; + unsigned bits, n; + + uivector_init(&blcount); + uivector_init(&nextcode); + + tree->tree1d = (unsigned*)lodepng_malloc(tree->numcodes * sizeof(unsigned)); + if(!tree->tree1d) error = 83; /*alloc fail*/ + + if(!uivector_resizev(&blcount, tree->maxbitlen + 1, 0) + || !uivector_resizev(&nextcode, tree->maxbitlen + 1, 0)) + error = 83; /*alloc fail*/ + + if(!error) + { + /*step 1: count number of instances of each code length*/ + for(bits = 0; bits != tree->numcodes; ++bits) ++blcount.data[tree->lengths[bits]]; + /*step 2: generate the nextcode values*/ + for(bits = 1; bits <= tree->maxbitlen; ++bits) + { + nextcode.data[bits] = (nextcode.data[bits - 1] + blcount.data[bits - 1]) << 1; + } + /*step 3: generate all the codes*/ + for(n = 0; n != tree->numcodes; ++n) + { + if(tree->lengths[n] != 0) tree->tree1d[n] = nextcode.data[tree->lengths[n]]++; + } + } + + uivector_cleanup(&blcount); + uivector_cleanup(&nextcode); + + if(!error) return HuffmanTree_make2DTree(tree); + else return error; +} + +/* +given the code lengths (as stored in the PNG file), generate the tree as defined +by Deflate. maxbitlen is the maximum bits that a code in the tree can have. +return value is error. +*/ +static unsigned HuffmanTree_makeFromLengths(HuffmanTree* tree, const unsigned* bitlen, + size_t numcodes, unsigned maxbitlen) +{ + unsigned i; + tree->lengths = (unsigned*)lodepng_malloc(numcodes * sizeof(unsigned)); + if(!tree->lengths) return 83; /*alloc fail*/ + for(i = 0; i != numcodes; ++i) tree->lengths[i] = bitlen[i]; + tree->numcodes = (unsigned)numcodes; /*number of symbols*/ + tree->maxbitlen = maxbitlen; + return HuffmanTree_makeFromLengths2(tree); +} + +#ifdef LODEPNG_COMPILE_ENCODER + +/*BPM: Boundary Package Merge, see "A Fast and Space-Economical Algorithm for Length-Limited Coding", +Jyrki Katajainen, Alistair Moffat, Andrew Turpin, 1995.*/ + +/*chain node for boundary package merge*/ +typedef struct BPMNode +{ + int weight; /*the sum of all weights in this chain*/ + unsigned index; /*index of this leaf node (called "count" in the paper)*/ + struct BPMNode* tail; /*the next nodes in this chain (null if last)*/ + int in_use; +} BPMNode; + +/*lists of chains*/ +typedef struct BPMLists +{ + /*memory pool*/ + unsigned memsize; + BPMNode* memory; + unsigned numfree; + unsigned nextfree; + BPMNode** freelist; + /*two heads of lookahead chains per list*/ + unsigned listsize; + BPMNode** chains0; + BPMNode** chains1; +} BPMLists; + +/*creates a new chain node with the given parameters, from the memory in the lists */ +static BPMNode* bpmnode_create(BPMLists* lists, int weight, unsigned index, BPMNode* tail) +{ + unsigned i; + BPMNode* result; + + /*memory full, so garbage collect*/ + if(lists->nextfree >= lists->numfree) + { + /*mark only those that are in use*/ + for(i = 0; i != lists->memsize; ++i) lists->memory[i].in_use = 0; + for(i = 0; i != lists->listsize; ++i) + { + BPMNode* node; + for(node = lists->chains0[i]; node != 0; node = node->tail) node->in_use = 1; + for(node = lists->chains1[i]; node != 0; node = node->tail) node->in_use = 1; + } + /*collect those that are free*/ + lists->numfree = 0; + for(i = 0; i != lists->memsize; ++i) + { + if(!lists->memory[i].in_use) lists->freelist[lists->numfree++] = &lists->memory[i]; + } + lists->nextfree = 0; + } + + result = lists->freelist[lists->nextfree++]; + result->weight = weight; + result->index = index; + result->tail = tail; + return result; +} + +static int bpmnode_compare(const void* a, const void* b) +{ + int wa = ((const BPMNode*)a)->weight; + int wb = ((const BPMNode*)b)->weight; + if(wa < wb) return -1; + if(wa > wb) return 1; + /*make the qsort a stable sort*/ + return ((const BPMNode*)a)->index < ((const BPMNode*)b)->index ? 1 : -1; +} + +/*Boundary Package Merge step, numpresent is the amount of leaves, and c is the current chain.*/ +static void boundaryPM(BPMLists* lists, BPMNode* leaves, size_t numpresent, int c, int num) +{ + unsigned lastindex = lists->chains1[c]->index; + + if(c == 0) + { + if(lastindex >= numpresent) return; + lists->chains0[c] = lists->chains1[c]; + lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, 0); + } + else + { + /*sum of the weights of the head nodes of the previous lookahead chains.*/ + int sum = lists->chains0[c - 1]->weight + lists->chains1[c - 1]->weight; + lists->chains0[c] = lists->chains1[c]; + if(lastindex < numpresent && sum > leaves[lastindex].weight) + { + lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, lists->chains1[c]->tail); + return; + } + lists->chains1[c] = bpmnode_create(lists, sum, lastindex, lists->chains1[c - 1]); + /*in the end we are only interested in the chain of the last list, so no + need to recurse if we're at the last one (this gives measurable speedup)*/ + if(num + 1 < (int)(2 * numpresent - 2)) + { + boundaryPM(lists, leaves, numpresent, c - 1, num); + boundaryPM(lists, leaves, numpresent, c - 1, num); + } + } +} + +unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies, + size_t numcodes, unsigned maxbitlen) +{ + unsigned error = 0; + unsigned i; + size_t numpresent = 0; /*number of symbols with non-zero frequency*/ + BPMNode* leaves; /*the symbols, only those with > 0 frequency*/ + + if(numcodes == 0) return 80; /*error: a tree of 0 symbols is not supposed to be made*/ + if((1u << maxbitlen) < numcodes) return 80; /*error: represent all symbols*/ + + leaves = (BPMNode*)lodepng_malloc(numcodes * sizeof(*leaves)); + if(!leaves) return 83; /*alloc fail*/ + + for(i = 0; i != numcodes; ++i) + { + if(frequencies[i] > 0) + { + leaves[numpresent].weight = frequencies[i]; + leaves[numpresent].index = i; + ++numpresent; + } + } + + for(i = 0; i != numcodes; ++i) lengths[i] = 0; + + /*ensure at least two present symbols. There should be at least one symbol + according to RFC 1951 section 3.2.7. Some decoders incorrectly require two. To + make these work as well ensure there are at least two symbols. The + Package-Merge code below also doesn't work correctly if there's only one + symbol, it'd give it the theoritical 0 bits but in practice zlib wants 1 bit*/ + if(numpresent == 0) + { + lengths[0] = lengths[1] = 1; /*note that for RFC 1951 section 3.2.7, only lengths[0] = 1 is needed*/ + } + else if(numpresent == 1) + { + lengths[leaves[0].index] = 1; + lengths[leaves[0].index == 0 ? 1 : 0] = 1; + } + else + { + BPMLists lists; + BPMNode* node; + + qsort(leaves, numpresent, sizeof(BPMNode), bpmnode_compare); + + lists.listsize = maxbitlen; + lists.memsize = 2 * maxbitlen * (maxbitlen + 1); + lists.nextfree = 0; + lists.numfree = lists.memsize; + lists.memory = (BPMNode*)lodepng_malloc(lists.memsize * sizeof(*lists.memory)); + lists.freelist = (BPMNode**)lodepng_malloc(lists.memsize * sizeof(BPMNode*)); + lists.chains0 = (BPMNode**)lodepng_malloc(lists.listsize * sizeof(BPMNode*)); + lists.chains1 = (BPMNode**)lodepng_malloc(lists.listsize * sizeof(BPMNode*)); + if(!lists.memory || !lists.freelist || !lists.chains0 || !lists.chains1) error = 83; /*alloc fail*/ + + if(!error) + { + for(i = 0; i != lists.memsize; ++i) lists.freelist[i] = &lists.memory[i]; + + bpmnode_create(&lists, leaves[0].weight, 1, 0); + bpmnode_create(&lists, leaves[1].weight, 2, 0); + + for(i = 0; i != lists.listsize; ++i) + { + lists.chains0[i] = &lists.memory[0]; + lists.chains1[i] = &lists.memory[1]; + } + + /*each boundaryPM call adds one chain to the last list, and we need 2 * numpresent - 2 chains.*/ + for(i = 2; i != 2 * numpresent - 2; ++i) boundaryPM(&lists, leaves, numpresent, maxbitlen - 1, i); + + for(node = lists.chains1[maxbitlen - 1]; node; node = node->tail) + { + for(i = 0; i != node->index; ++i) ++lengths[leaves[i].index]; + } + } + + lodepng_free(lists.memory); + lodepng_free(lists.freelist); + lodepng_free(lists.chains0); + lodepng_free(lists.chains1); + } + + lodepng_free(leaves); + return error; +} + +/*Create the Huffman tree given the symbol frequencies*/ +static unsigned HuffmanTree_makeFromFrequencies(HuffmanTree* tree, const unsigned* frequencies, + size_t mincodes, size_t numcodes, unsigned maxbitlen) +{ + unsigned error = 0; + while(!frequencies[numcodes - 1] && numcodes > mincodes) --numcodes; /*trim zeroes*/ + tree->maxbitlen = maxbitlen; + tree->numcodes = (unsigned)numcodes; /*number of symbols*/ + tree->lengths = (unsigned*)lodepng_realloc(tree->lengths, numcodes * sizeof(unsigned)); + if(!tree->lengths) return 83; /*alloc fail*/ + /*initialize all lengths to 0*/ + memset(tree->lengths, 0, numcodes * sizeof(unsigned)); + + error = lodepng_huffman_code_lengths(tree->lengths, frequencies, numcodes, maxbitlen); + if(!error) error = HuffmanTree_makeFromLengths2(tree); + return error; +} + +static unsigned HuffmanTree_getCode(const HuffmanTree* tree, unsigned index) +{ + return tree->tree1d[index]; +} + +static unsigned HuffmanTree_getLength(const HuffmanTree* tree, unsigned index) +{ + return tree->lengths[index]; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/*get the literal and length code tree of a deflated block with fixed tree, as per the deflate specification*/ +static unsigned generateFixedLitLenTree(HuffmanTree* tree) +{ + unsigned i, error = 0; + unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned)); + if(!bitlen) return 83; /*alloc fail*/ + + /*288 possible codes: 0-255=literals, 256=endcode, 257-285=lengthcodes, 286-287=unused*/ + for(i = 0; i <= 143; ++i) bitlen[i] = 8; + for(i = 144; i <= 255; ++i) bitlen[i] = 9; + for(i = 256; i <= 279; ++i) bitlen[i] = 7; + for(i = 280; i <= 287; ++i) bitlen[i] = 8; + + error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DEFLATE_CODE_SYMBOLS, 15); + + lodepng_free(bitlen); + return error; +} + +/*get the distance code tree of a deflated block with fixed tree, as specified in the deflate specification*/ +static unsigned generateFixedDistanceTree(HuffmanTree* tree) +{ + unsigned i, error = 0; + unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned)); + if(!bitlen) return 83; /*alloc fail*/ + + /*there are 32 distance codes, but 30-31 are unused*/ + for(i = 0; i != NUM_DISTANCE_SYMBOLS; ++i) bitlen[i] = 5; + error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DISTANCE_SYMBOLS, 15); + + lodepng_free(bitlen); + return error; +} + +#ifdef LODEPNG_COMPILE_DECODER + +/* +returns the code, or (unsigned)(-1) if error happened +inbitlength is the length of the complete buffer, in bits (so its byte length times 8) +*/ +static unsigned huffmanDecodeSymbol(const unsigned char* in, size_t* bp, + const HuffmanTree* codetree, size_t inbitlength) +{ + unsigned treepos = 0, ct; + for(;;) + { + if(*bp >= inbitlength) return (unsigned)(-1); /*error: end of input memory reached without endcode*/ + /* + decode the symbol from the tree. The "readBitFromStream" code is inlined in + the expression below because this is the biggest bottleneck while decoding + */ + ct = codetree->tree2d[(treepos << 1) + READBIT(*bp, in)]; + ++(*bp); + if(ct < codetree->numcodes) return ct; /*the symbol is decoded, return it*/ + else treepos = ct - codetree->numcodes; /*symbol not yet decoded, instead move tree position*/ + + if(treepos >= codetree->numcodes) return (unsigned)(-1); /*error: it appeared outside the codetree*/ + } +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Inflator (Decompressor) / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*get the tree of a deflated block with fixed tree, as specified in the deflate specification*/ +static void getTreeInflateFixed(HuffmanTree* tree_ll, HuffmanTree* tree_d) +{ + /*TODO: check for out of memory errors*/ + generateFixedLitLenTree(tree_ll); + generateFixedDistanceTree(tree_d); +} + +/*get the tree of a deflated block with dynamic tree, the tree itself is also Huffman compressed with a known tree*/ +static unsigned getTreeInflateDynamic(HuffmanTree* tree_ll, HuffmanTree* tree_d, + const unsigned char* in, size_t* bp, size_t inlength) +{ + /*make sure that length values that aren't filled in will be 0, or a wrong tree will be generated*/ + unsigned error = 0; + unsigned n, HLIT, HDIST, HCLEN, i; + size_t inbitlength = inlength * 8; + + /*see comments in deflateDynamic for explanation of the context and these variables, it is analogous*/ + unsigned* bitlen_ll = 0; /*lit,len code lengths*/ + unsigned* bitlen_d = 0; /*dist code lengths*/ + /*code length code lengths ("clcl"), the bit lengths of the huffman tree used to compress bitlen_ll and bitlen_d*/ + unsigned* bitlen_cl = 0; + HuffmanTree tree_cl; /*the code tree for code length codes (the huffman tree for compressed huffman trees)*/ + + if((*bp) + 14 > (inlength << 3)) return 49; /*error: the bit pointer is or will go past the memory*/ + + /*number of literal/length codes + 257. Unlike the spec, the value 257 is added to it here already*/ + HLIT = readBitsFromStream(bp, in, 5) + 257; + /*number of distance codes. Unlike the spec, the value 1 is added to it here already*/ + HDIST = readBitsFromStream(bp, in, 5) + 1; + /*number of code length codes. Unlike the spec, the value 4 is added to it here already*/ + HCLEN = readBitsFromStream(bp, in, 4) + 4; + + if((*bp) + HCLEN * 3 > (inlength << 3)) return 50; /*error: the bit pointer is or will go past the memory*/ + + HuffmanTree_init(&tree_cl); + + while(!error) + { + /*read the code length codes out of 3 * (amount of code length codes) bits*/ + + bitlen_cl = (unsigned*)lodepng_malloc(NUM_CODE_LENGTH_CODES * sizeof(unsigned)); + if(!bitlen_cl) ERROR_BREAK(83 /*alloc fail*/); + + for(i = 0; i != NUM_CODE_LENGTH_CODES; ++i) + { + if(i < HCLEN) bitlen_cl[CLCL_ORDER[i]] = readBitsFromStream(bp, in, 3); + else bitlen_cl[CLCL_ORDER[i]] = 0; /*if not, it must stay 0*/ + } + + error = HuffmanTree_makeFromLengths(&tree_cl, bitlen_cl, NUM_CODE_LENGTH_CODES, 7); + if(error) break; + + /*now we can use this tree to read the lengths for the tree that this function will return*/ + bitlen_ll = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned)); + bitlen_d = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned)); + if(!bitlen_ll || !bitlen_d) ERROR_BREAK(83 /*alloc fail*/); + for(i = 0; i != NUM_DEFLATE_CODE_SYMBOLS; ++i) bitlen_ll[i] = 0; + for(i = 0; i != NUM_DISTANCE_SYMBOLS; ++i) bitlen_d[i] = 0; + + /*i is the current symbol we're reading in the part that contains the code lengths of lit/len and dist codes*/ + i = 0; + while(i < HLIT + HDIST) + { + unsigned code = huffmanDecodeSymbol(in, bp, &tree_cl, inbitlength); + if(code <= 15) /*a length code*/ + { + if(i < HLIT) bitlen_ll[i] = code; + else bitlen_d[i - HLIT] = code; + ++i; + } + else if(code == 16) /*repeat previous*/ + { + unsigned replength = 3; /*read in the 2 bits that indicate repeat length (3-6)*/ + unsigned value; /*set value to the previous code*/ + + if(i == 0) ERROR_BREAK(54); /*can't repeat previous if i is 0*/ + + if((*bp + 2) > inbitlength) ERROR_BREAK(50); /*error, bit pointer jumps past memory*/ + replength += readBitsFromStream(bp, in, 2); + + if(i < HLIT + 1) value = bitlen_ll[i - 1]; + else value = bitlen_d[i - HLIT - 1]; + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) + { + if(i >= HLIT + HDIST) ERROR_BREAK(13); /*error: i is larger than the amount of codes*/ + if(i < HLIT) bitlen_ll[i] = value; + else bitlen_d[i - HLIT] = value; + ++i; + } + } + else if(code == 17) /*repeat "0" 3-10 times*/ + { + unsigned replength = 3; /*read in the bits that indicate repeat length*/ + if((*bp + 3) > inbitlength) ERROR_BREAK(50); /*error, bit pointer jumps past memory*/ + replength += readBitsFromStream(bp, in, 3); + + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) + { + if(i >= HLIT + HDIST) ERROR_BREAK(14); /*error: i is larger than the amount of codes*/ + + if(i < HLIT) bitlen_ll[i] = 0; + else bitlen_d[i - HLIT] = 0; + ++i; + } + } + else if(code == 18) /*repeat "0" 11-138 times*/ + { + unsigned replength = 11; /*read in the bits that indicate repeat length*/ + if((*bp + 7) > inbitlength) ERROR_BREAK(50); /*error, bit pointer jumps past memory*/ + replength += readBitsFromStream(bp, in, 7); + + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) + { + if(i >= HLIT + HDIST) ERROR_BREAK(15); /*error: i is larger than the amount of codes*/ + + if(i < HLIT) bitlen_ll[i] = 0; + else bitlen_d[i - HLIT] = 0; + ++i; + } + } + else /*if(code == (unsigned)(-1))*/ /*huffmanDecodeSymbol returns (unsigned)(-1) in case of error*/ + { + if(code == (unsigned)(-1)) + { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + error = (*bp) > inbitlength ? 10 : 11; + } + else error = 16; /*unexisting code, this can never happen*/ + break; + } + } + if(error) break; + + if(bitlen_ll[256] == 0) ERROR_BREAK(64); /*the length of the end code 256 must be larger than 0*/ + + /*now we've finally got HLIT and HDIST, so generate the code trees, and the function is done*/ + error = HuffmanTree_makeFromLengths(tree_ll, bitlen_ll, NUM_DEFLATE_CODE_SYMBOLS, 15); + if(error) break; + error = HuffmanTree_makeFromLengths(tree_d, bitlen_d, NUM_DISTANCE_SYMBOLS, 15); + + break; /*end of error-while*/ + } + + lodepng_free(bitlen_cl); + lodepng_free(bitlen_ll); + lodepng_free(bitlen_d); + HuffmanTree_cleanup(&tree_cl); + + return error; +} + +/*inflate a block with dynamic of fixed Huffman tree*/ +static unsigned inflateHuffmanBlock(ucvector* out, const unsigned char* in, size_t* bp, + size_t* pos, size_t inlength, unsigned btype) +{ + unsigned error = 0; + HuffmanTree tree_ll; /*the huffman tree for literal and length codes*/ + HuffmanTree tree_d; /*the huffman tree for distance codes*/ + size_t inbitlength = inlength * 8; + + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + + if(btype == 1) getTreeInflateFixed(&tree_ll, &tree_d); + else if(btype == 2) error = getTreeInflateDynamic(&tree_ll, &tree_d, in, bp, inlength); + + while(!error) /*decode all symbols until end reached, breaks at end code*/ + { + /*code_ll is literal, length or end code*/ + unsigned code_ll = huffmanDecodeSymbol(in, bp, &tree_ll, inbitlength); + if(code_ll <= 255) /*literal symbol*/ + { + /*ucvector_push_back would do the same, but for some reason the two lines below run 10% faster*/ + if(!ucvector_resize(out, (*pos) + 1)) ERROR_BREAK(83 /*alloc fail*/); + out->data[*pos] = (unsigned char)code_ll; + ++(*pos); + } + else if(code_ll >= FIRST_LENGTH_CODE_INDEX && code_ll <= LAST_LENGTH_CODE_INDEX) /*length code*/ + { + unsigned code_d, distance; + unsigned numextrabits_l, numextrabits_d; /*extra bits for length and distance*/ + size_t start, forward, backward, length; + + /*part 1: get length base*/ + length = LENGTHBASE[code_ll - FIRST_LENGTH_CODE_INDEX]; + + /*part 2: get extra bits and add the value of that to length*/ + numextrabits_l = LENGTHEXTRA[code_ll - FIRST_LENGTH_CODE_INDEX]; + if((*bp + numextrabits_l) > inbitlength) ERROR_BREAK(51); /*error, bit pointer will jump past memory*/ + length += readBitsFromStream(bp, in, numextrabits_l); + + /*part 3: get distance code*/ + code_d = huffmanDecodeSymbol(in, bp, &tree_d, inbitlength); + if(code_d > 29) + { + if(code_ll == (unsigned)(-1)) /*huffmanDecodeSymbol returns (unsigned)(-1) in case of error*/ + { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + error = (*bp) > inlength * 8 ? 10 : 11; + } + else error = 18; /*error: invalid distance code (30-31 are never used)*/ + break; + } + distance = DISTANCEBASE[code_d]; + + /*part 4: get extra bits from distance*/ + numextrabits_d = DISTANCEEXTRA[code_d]; + if((*bp + numextrabits_d) > inbitlength) ERROR_BREAK(51); /*error, bit pointer will jump past memory*/ + distance += readBitsFromStream(bp, in, numextrabits_d); + + /*part 5: fill in all the out[n] values based on the length and dist*/ + start = (*pos); + if(distance > start) ERROR_BREAK(52); /*too long backward distance*/ + backward = start - distance; + + if(!ucvector_resize(out, (*pos) + length)) ERROR_BREAK(83 /*alloc fail*/); + if (distance < length) { + for(forward = 0; forward < length; ++forward) + { + out->data[(*pos)++] = out->data[backward++]; + } + } else { + memcpy(out->data + *pos, out->data + backward, length); + *pos += length; + } + } + else if(code_ll == 256) + { + break; /*end code, break the loop*/ + } + else /*if(code == (unsigned)(-1))*/ /*huffmanDecodeSymbol returns (unsigned)(-1) in case of error*/ + { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + error = ((*bp) > inlength * 8) ? 10 : 11; + break; + } + } + + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + + return error; +} + +static unsigned inflateNoCompression(ucvector* out, const unsigned char* in, size_t* bp, size_t* pos, size_t inlength) +{ + size_t p; + unsigned LEN, NLEN, n, error = 0; + + /*go to first boundary of byte*/ + while(((*bp) & 0x7) != 0) ++(*bp); + p = (*bp) / 8; /*byte position*/ + + /*read LEN (2 bytes) and NLEN (2 bytes)*/ + if(p + 4 >= inlength) return 52; /*error, bit pointer will jump past memory*/ + LEN = in[p] + 256u * in[p + 1]; p += 2; + NLEN = in[p] + 256u * in[p + 1]; p += 2; + + /*check if 16-bit NLEN is really the one's complement of LEN*/ + if(LEN + NLEN != 65535) return 21; /*error: NLEN is not one's complement of LEN*/ + + if(!ucvector_resize(out, (*pos) + LEN)) return 83; /*alloc fail*/ + + /*read the literal data: LEN bytes are now stored in the out buffer*/ + if(p + LEN > inlength) return 23; /*error: reading outside of in buffer*/ + for(n = 0; n < LEN; ++n) out->data[(*pos)++] = in[p++]; + + (*bp) = p * 8; + + return error; +} + +static unsigned lodepng_inflatev(ucvector* out, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) +{ + /*bit pointer in the "in" data, current byte is bp >> 3, current bit is bp & 0x7 (from lsb to msb of the byte)*/ + size_t bp = 0; + unsigned BFINAL = 0; + size_t pos = 0; /*byte position in the out buffer*/ + unsigned error = 0; + + (void)settings; + + while(!BFINAL) + { + unsigned BTYPE; + if(bp + 2 >= insize * 8) return 52; /*error, bit pointer will jump past memory*/ + BFINAL = readBitFromStream(&bp, in); + BTYPE = 1u * readBitFromStream(&bp, in); + BTYPE += 2u * readBitFromStream(&bp, in); + + if(BTYPE == 3) return 20; /*error: invalid BTYPE*/ + else if(BTYPE == 0) error = inflateNoCompression(out, in, &bp, &pos, insize); /*no compression*/ + else error = inflateHuffmanBlock(out, in, &bp, &pos, insize, BTYPE); /*compression, BTYPE 01 or 10*/ + + if(error) return error; + } + + return error; +} + +unsigned lodepng_inflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) +{ + unsigned error; + ucvector v; + ucvector_init_buffer(&v, *out, *outsize); + error = lodepng_inflatev(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +static unsigned inflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) +{ + if(settings->custom_inflate) + { + return settings->custom_inflate(out, outsize, in, insize, settings); + } + else + { + return lodepng_inflate(out, outsize, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Deflator (Compressor) / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static const size_t MAX_SUPPORTED_DEFLATE_LENGTH = 258; + +/*bitlen is the size in bits of the code*/ +static void addHuffmanSymbol(size_t* bp, ucvector* compressed, unsigned code, unsigned bitlen) +{ + addBitsToStreamReversed(bp, compressed, code, bitlen); +} + +/*search the index in the array, that has the largest value smaller than or equal to the given value, +given array must be sorted (if no value is smaller, it returns the size of the given array)*/ +static size_t searchCodeIndex(const unsigned* array, size_t array_size, size_t value) +{ + /*linear search implementation*/ + /*for(size_t i = 1; i < array_size; ++i) if(array[i] > value) return i - 1; + return array_size - 1;*/ + + /*binary search implementation (not that much faster) (precondition: array_size > 0)*/ + size_t left = 1; + size_t right = array_size - 1; + while(left <= right) + { + size_t mid = (left + right) / 2; + if(array[mid] <= value) left = mid + 1; /*the value to find is more to the right*/ + else if(array[mid - 1] > value) right = mid - 1; /*the value to find is more to the left*/ + else return mid - 1; + } + return array_size - 1; +} + +static void addLengthDistance(uivector* values, size_t length, size_t distance) +{ + /*values in encoded vector are those used by deflate: + 0-255: literal bytes + 256: end + 257-285: length/distance pair (length code, followed by extra length bits, distance code, extra distance bits) + 286-287: invalid*/ + + unsigned length_code = (unsigned)searchCodeIndex(LENGTHBASE, 29, length); + unsigned extra_length = (unsigned)(length - LENGTHBASE[length_code]); + unsigned dist_code = (unsigned)searchCodeIndex(DISTANCEBASE, 30, distance); + unsigned extra_distance = (unsigned)(distance - DISTANCEBASE[dist_code]); + + uivector_push_back(values, length_code + FIRST_LENGTH_CODE_INDEX); + uivector_push_back(values, extra_length); + uivector_push_back(values, dist_code); + uivector_push_back(values, extra_distance); +} + +/*3 bytes of data get encoded into two bytes. The hash cannot use more than 3 +bytes as input because 3 is the minimum match length for deflate*/ +static const unsigned HASH_NUM_VALUES = 65536; +static const unsigned HASH_BIT_MASK = 65535; /*HASH_NUM_VALUES - 1, but C90 does not like that as initializer*/ + +typedef struct Hash +{ + int* head; /*hash value to head circular pos - can be outdated if went around window*/ + /*circular pos to prev circular pos*/ + unsigned short* chain; + int* val; /*circular pos to hash value*/ + + /*TODO: do this not only for zeros but for any repeated byte. However for PNG + it's always going to be the zeros that dominate, so not important for PNG*/ + int* headz; /*similar to head, but for chainz*/ + unsigned short* chainz; /*those with same amount of zeros*/ + unsigned short* zeros; /*length of zeros streak, used as a second hash chain*/ +} Hash; + +static unsigned hash_init(Hash* hash, unsigned windowsize) +{ + unsigned i; + hash->head = (int*)lodepng_malloc(sizeof(int) * HASH_NUM_VALUES); + hash->val = (int*)lodepng_malloc(sizeof(int) * windowsize); + hash->chain = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + + hash->zeros = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + hash->headz = (int*)lodepng_malloc(sizeof(int) * (MAX_SUPPORTED_DEFLATE_LENGTH + 1)); + hash->chainz = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + + if(!hash->head || !hash->chain || !hash->val || !hash->headz|| !hash->chainz || !hash->zeros) + { + return 83; /*alloc fail*/ + } + + /*initialize hash table*/ + for(i = 0; i != HASH_NUM_VALUES; ++i) hash->head[i] = -1; + for(i = 0; i != windowsize; ++i) hash->val[i] = -1; + for(i = 0; i != windowsize; ++i) hash->chain[i] = i; /*same value as index indicates uninitialized*/ + + for(i = 0; i <= MAX_SUPPORTED_DEFLATE_LENGTH; ++i) hash->headz[i] = -1; + for(i = 0; i != windowsize; ++i) hash->chainz[i] = i; /*same value as index indicates uninitialized*/ + + return 0; +} + +static void hash_cleanup(Hash* hash) +{ + lodepng_free(hash->head); + lodepng_free(hash->val); + lodepng_free(hash->chain); + + lodepng_free(hash->zeros); + lodepng_free(hash->headz); + lodepng_free(hash->chainz); +} + + + +static unsigned getHash(const unsigned char* data, size_t size, size_t pos) +{ + unsigned result = 0; + if(pos + 2 < size) + { + /*A simple shift and xor hash is used. Since the data of PNGs is dominated + by zeroes due to the filters, a better hash does not have a significant + effect on speed in traversing the chain, and causes more time spend on + calculating the hash.*/ + result ^= (unsigned)(data[pos + 0] << 0u); + result ^= (unsigned)(data[pos + 1] << 4u); + result ^= (unsigned)(data[pos + 2] << 8u); + } else { + size_t amount, i; + if(pos >= size) return 0; + amount = size - pos; + for(i = 0; i != amount; ++i) result ^= (unsigned)(data[pos + i] << (i * 8u)); + } + return result & HASH_BIT_MASK; +} + +static unsigned countZeros(const unsigned char* data, size_t size, size_t pos) +{ + const unsigned char* start = data + pos; + const unsigned char* end = start + MAX_SUPPORTED_DEFLATE_LENGTH; + if(end > data + size) end = data + size; + data = start; + while(data != end && *data == 0) ++data; + /*subtracting two addresses returned as 32-bit number (max value is MAX_SUPPORTED_DEFLATE_LENGTH)*/ + return (unsigned)(data - start); +} + +/*wpos = pos & (windowsize - 1)*/ +static void updateHashChain(Hash* hash, size_t wpos, unsigned hashval, unsigned short numzeros) +{ + hash->val[wpos] = (int)hashval; + if(hash->head[hashval] != -1) hash->chain[wpos] = hash->head[hashval]; + hash->head[hashval] = wpos; + + hash->zeros[wpos] = numzeros; + if(hash->headz[numzeros] != -1) hash->chainz[wpos] = hash->headz[numzeros]; + hash->headz[numzeros] = wpos; +} + +/* +LZ77-encode the data. Return value is error code. The input are raw bytes, the output +is in the form of unsigned integers with codes representing for example literal bytes, or +length/distance pairs. +It uses a hash table technique to let it encode faster. When doing LZ77 encoding, a +sliding window (of windowsize) is used, and all past bytes in that window can be used as +the "dictionary". A brute force search through all possible distances would be slow, and +this hash technique is one out of several ways to speed this up. +*/ +static unsigned encodeLZ77(uivector* out, Hash* hash, + const unsigned char* in, size_t inpos, size_t insize, unsigned windowsize, + unsigned minmatch, unsigned nicematch, unsigned lazymatching) +{ + size_t pos; + unsigned i, error = 0; + /*for large window lengths, assume the user wants no compression loss. Otherwise, max hash chain length speedup.*/ + unsigned maxchainlength = windowsize >= 8192 ? windowsize : windowsize / 8; + unsigned maxlazymatch = windowsize >= 8192 ? MAX_SUPPORTED_DEFLATE_LENGTH : 64; + + unsigned usezeros = 1; /*not sure if setting it to false for windowsize < 8192 is better or worse*/ + unsigned numzeros = 0; + + unsigned offset; /*the offset represents the distance in LZ77 terminology*/ + unsigned length; + unsigned lazy = 0; + unsigned lazylength = 0, lazyoffset = 0; + unsigned hashval; + unsigned current_offset, current_length; + unsigned prev_offset; + const unsigned char *lastptr, *foreptr, *backptr; + unsigned hashpos; + + if(windowsize == 0 || windowsize > 32768) return 60; /*error: windowsize smaller/larger than allowed*/ + if((windowsize & (windowsize - 1)) != 0) return 90; /*error: must be power of two*/ + + if(nicematch > MAX_SUPPORTED_DEFLATE_LENGTH) nicematch = MAX_SUPPORTED_DEFLATE_LENGTH; + + for(pos = inpos; pos < insize; ++pos) + { + size_t wpos = pos & (windowsize - 1); /*position for in 'circular' hash buffers*/ + unsigned chainlength = 0; + + hashval = getHash(in, insize, pos); + + if(usezeros && hashval == 0) + { + if(numzeros == 0) numzeros = countZeros(in, insize, pos); + else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros; + } + else + { + numzeros = 0; + } + + updateHashChain(hash, wpos, hashval, numzeros); + + /*the length and offset found for the current position*/ + length = 0; + offset = 0; + + hashpos = hash->chain[wpos]; + + lastptr = &in[insize < pos + MAX_SUPPORTED_DEFLATE_LENGTH ? insize : pos + MAX_SUPPORTED_DEFLATE_LENGTH]; + + /*search for the longest string*/ + prev_offset = 0; + for(;;) + { + if(chainlength++ >= maxchainlength) break; + current_offset = hashpos <= wpos ? wpos - hashpos : wpos - hashpos + windowsize; + + if(current_offset < prev_offset) break; /*stop when went completely around the circular buffer*/ + prev_offset = current_offset; + if(current_offset > 0) + { + /*test the next characters*/ + foreptr = &in[pos]; + backptr = &in[pos - current_offset]; + + /*common case in PNGs is lots of zeros. Quickly skip over them as a speedup*/ + if(numzeros >= 3) + { + unsigned skip = hash->zeros[hashpos]; + if(skip > numzeros) skip = numzeros; + backptr += skip; + foreptr += skip; + } + + while(foreptr != lastptr && *backptr == *foreptr) /*maximum supported length by deflate is max length*/ + { + ++backptr; + ++foreptr; + } + current_length = (unsigned)(foreptr - &in[pos]); + + if(current_length > length) + { + length = current_length; /*the longest length*/ + offset = current_offset; /*the offset that is related to this longest length*/ + /*jump out once a length of max length is found (speed gain). This also jumps + out if length is MAX_SUPPORTED_DEFLATE_LENGTH*/ + if(current_length >= nicematch) break; + } + } + + if(hashpos == hash->chain[hashpos]) break; + + if(numzeros >= 3 && length > numzeros) + { + hashpos = hash->chainz[hashpos]; + if(hash->zeros[hashpos] != numzeros) break; + } + else + { + hashpos = hash->chain[hashpos]; + /*outdated hash value, happens if particular value was not encountered in whole last window*/ + if(hash->val[hashpos] != (int)hashval) break; + } + } + + if(lazymatching) + { + if(!lazy && length >= 3 && length <= maxlazymatch && length < MAX_SUPPORTED_DEFLATE_LENGTH) + { + lazy = 1; + lazylength = length; + lazyoffset = offset; + continue; /*try the next byte*/ + } + if(lazy) + { + lazy = 0; + if(pos == 0) ERROR_BREAK(81); + if(length > lazylength + 1) + { + /*push the previous character as literal*/ + if(!uivector_push_back(out, in[pos - 1])) ERROR_BREAK(83 /*alloc fail*/); + } + else + { + length = lazylength; + offset = lazyoffset; + hash->head[hashval] = -1; /*the same hashchain update will be done, this ensures no wrong alteration*/ + hash->headz[numzeros] = -1; /*idem*/ + --pos; + } + } + } + if(length >= 3 && offset > windowsize) ERROR_BREAK(86 /*too big (or overflown negative) offset*/); + + /*encode it as length/distance pair or literal value*/ + if(length < 3) /*only lengths of 3 or higher are supported as length/distance pair*/ + { + if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/); + } + else if(length < minmatch || (length == 3 && offset > 4096)) + { + /*compensate for the fact that longer offsets have more extra bits, a + length of only 3 may be not worth it then*/ + if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/); + } + else + { + addLengthDistance(out, length, offset); + for(i = 1; i < length; ++i) + { + ++pos; + wpos = pos & (windowsize - 1); + hashval = getHash(in, insize, pos); + if(usezeros && hashval == 0) + { + if(numzeros == 0) numzeros = countZeros(in, insize, pos); + else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros; + } + else + { + numzeros = 0; + } + updateHashChain(hash, wpos, hashval, numzeros); + } + } + } /*end of the loop through each character of input*/ + + return error; +} + +/* /////////////////////////////////////////////////////////////////////////// */ + +static unsigned deflateNoCompression(ucvector* out, const unsigned char* data, size_t datasize) +{ + /*non compressed deflate block data: 1 bit BFINAL,2 bits BTYPE,(5 bits): it jumps to start of next byte, + 2 bytes LEN, 2 bytes NLEN, LEN bytes literal DATA*/ + + size_t i, j, numdeflateblocks = (datasize + 65534) / 65535; + unsigned datapos = 0; + for(i = 0; i != numdeflateblocks; ++i) + { + unsigned BFINAL, BTYPE, LEN, NLEN; + unsigned char firstbyte; + + BFINAL = (i == numdeflateblocks - 1); + BTYPE = 0; + + firstbyte = (unsigned char)(BFINAL + ((BTYPE & 1) << 1) + ((BTYPE & 2) << 1)); + ucvector_push_back(out, firstbyte); + + LEN = 65535; + if(datasize - datapos < 65535) LEN = (unsigned)datasize - datapos; + NLEN = 65535 - LEN; + + ucvector_push_back(out, (unsigned char)(LEN % 256)); + ucvector_push_back(out, (unsigned char)(LEN / 256)); + ucvector_push_back(out, (unsigned char)(NLEN % 256)); + ucvector_push_back(out, (unsigned char)(NLEN / 256)); + + /*Decompressed data*/ + for(j = 0; j < 65535 && datapos < datasize; ++j) + { + ucvector_push_back(out, data[datapos++]); + } + } + + return 0; +} + +/* +write the lz77-encoded data, which has lit, len and dist codes, to compressed stream using huffman trees. +tree_ll: the tree for lit and len codes. +tree_d: the tree for distance codes. +*/ +static void writeLZ77data(size_t* bp, ucvector* out, const uivector* lz77_encoded, + const HuffmanTree* tree_ll, const HuffmanTree* tree_d) +{ + size_t i = 0; + for(i = 0; i != lz77_encoded->size; ++i) + { + unsigned val = lz77_encoded->data[i]; + addHuffmanSymbol(bp, out, HuffmanTree_getCode(tree_ll, val), HuffmanTree_getLength(tree_ll, val)); + if(val > 256) /*for a length code, 3 more things have to be added*/ + { + unsigned length_index = val - FIRST_LENGTH_CODE_INDEX; + unsigned n_length_extra_bits = LENGTHEXTRA[length_index]; + unsigned length_extra_bits = lz77_encoded->data[++i]; + + unsigned distance_code = lz77_encoded->data[++i]; + + unsigned distance_index = distance_code; + unsigned n_distance_extra_bits = DISTANCEEXTRA[distance_index]; + unsigned distance_extra_bits = lz77_encoded->data[++i]; + + addBitsToStream(bp, out, length_extra_bits, n_length_extra_bits); + addHuffmanSymbol(bp, out, HuffmanTree_getCode(tree_d, distance_code), + HuffmanTree_getLength(tree_d, distance_code)); + addBitsToStream(bp, out, distance_extra_bits, n_distance_extra_bits); + } + } +} + +/*Deflate for a block of type "dynamic", that is, with freely, optimally, created huffman trees*/ +static unsigned deflateDynamic(ucvector* out, size_t* bp, Hash* hash, + const unsigned char* data, size_t datapos, size_t dataend, + const LodePNGCompressSettings* settings, unsigned final) +{ + unsigned error = 0; + + /* + A block is compressed as follows: The PNG data is lz77 encoded, resulting in + literal bytes and length/distance pairs. This is then huffman compressed with + two huffman trees. One huffman tree is used for the lit and len values ("ll"), + another huffman tree is used for the dist values ("d"). These two trees are + stored using their code lengths, and to compress even more these code lengths + are also run-length encoded and huffman compressed. This gives a huffman tree + of code lengths "cl". The code lenghts used to describe this third tree are + the code length code lengths ("clcl"). + */ + + /*The lz77 encoded data, represented with integers since there will also be length and distance codes in it*/ + uivector lz77_encoded; + HuffmanTree tree_ll; /*tree for lit,len values*/ + HuffmanTree tree_d; /*tree for distance codes*/ + HuffmanTree tree_cl; /*tree for encoding the code lengths representing tree_ll and tree_d*/ + uivector frequencies_ll; /*frequency of lit,len codes*/ + uivector frequencies_d; /*frequency of dist codes*/ + uivector frequencies_cl; /*frequency of code length codes*/ + uivector bitlen_lld; /*lit,len,dist code lenghts (int bits), literally (without repeat codes).*/ + uivector bitlen_lld_e; /*bitlen_lld encoded with repeat codes (this is a rudemtary run length compression)*/ + /*bitlen_cl is the code length code lengths ("clcl"). The bit lengths of codes to represent tree_cl + (these are written as is in the file, it would be crazy to compress these using yet another huffman + tree that needs to be represented by yet another set of code lengths)*/ + uivector bitlen_cl; + size_t datasize = dataend - datapos; + + /* + Due to the huffman compression of huffman tree representations ("two levels"), there are some anologies: + bitlen_lld is to tree_cl what data is to tree_ll and tree_d. + bitlen_lld_e is to bitlen_lld what lz77_encoded is to data. + bitlen_cl is to bitlen_lld_e what bitlen_lld is to lz77_encoded. + */ + + unsigned BFINAL = final; + size_t numcodes_ll, numcodes_d, i; + unsigned HLIT, HDIST, HCLEN; + + uivector_init(&lz77_encoded); + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + HuffmanTree_init(&tree_cl); + uivector_init(&frequencies_ll); + uivector_init(&frequencies_d); + uivector_init(&frequencies_cl); + uivector_init(&bitlen_lld); + uivector_init(&bitlen_lld_e); + uivector_init(&bitlen_cl); + + /*This while loop never loops due to a break at the end, it is here to + allow breaking out of it to the cleanup phase on error conditions.*/ + while(!error) + { + if(settings->use_lz77) + { + error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize, + settings->minmatch, settings->nicematch, settings->lazymatching); + if(error) break; + } + else + { + if(!uivector_resize(&lz77_encoded, datasize)) ERROR_BREAK(83 /*alloc fail*/); + for(i = datapos; i < dataend; ++i) lz77_encoded.data[i] = data[i]; /*no LZ77, but still will be Huffman compressed*/ + } + + if(!uivector_resizev(&frequencies_ll, 286, 0)) ERROR_BREAK(83 /*alloc fail*/); + if(!uivector_resizev(&frequencies_d, 30, 0)) ERROR_BREAK(83 /*alloc fail*/); + + /*Count the frequencies of lit, len and dist codes*/ + for(i = 0; i != lz77_encoded.size; ++i) + { + unsigned symbol = lz77_encoded.data[i]; + ++frequencies_ll.data[symbol]; + if(symbol > 256) + { + unsigned dist = lz77_encoded.data[i + 2]; + ++frequencies_d.data[dist]; + i += 3; + } + } + frequencies_ll.data[256] = 1; /*there will be exactly 1 end code, at the end of the block*/ + + /*Make both huffman trees, one for the lit and len codes, one for the dist codes*/ + error = HuffmanTree_makeFromFrequencies(&tree_ll, frequencies_ll.data, 257, frequencies_ll.size, 15); + if(error) break; + /*2, not 1, is chosen for mincodes: some buggy PNG decoders require at least 2 symbols in the dist tree*/ + error = HuffmanTree_makeFromFrequencies(&tree_d, frequencies_d.data, 2, frequencies_d.size, 15); + if(error) break; + + numcodes_ll = tree_ll.numcodes; if(numcodes_ll > 286) numcodes_ll = 286; + numcodes_d = tree_d.numcodes; if(numcodes_d > 30) numcodes_d = 30; + /*store the code lengths of both generated trees in bitlen_lld*/ + for(i = 0; i != numcodes_ll; ++i) uivector_push_back(&bitlen_lld, HuffmanTree_getLength(&tree_ll, (unsigned)i)); + for(i = 0; i != numcodes_d; ++i) uivector_push_back(&bitlen_lld, HuffmanTree_getLength(&tree_d, (unsigned)i)); + + /*run-length compress bitlen_ldd into bitlen_lld_e by using repeat codes 16 (copy length 3-6 times), + 17 (3-10 zeroes), 18 (11-138 zeroes)*/ + for(i = 0; i != (unsigned)bitlen_lld.size; ++i) + { + unsigned j = 0; /*amount of repititions*/ + while(i + j + 1 < (unsigned)bitlen_lld.size && bitlen_lld.data[i + j + 1] == bitlen_lld.data[i]) ++j; + + if(bitlen_lld.data[i] == 0 && j >= 2) /*repeat code for zeroes*/ + { + ++j; /*include the first zero*/ + if(j <= 10) /*repeat code 17 supports max 10 zeroes*/ + { + uivector_push_back(&bitlen_lld_e, 17); + uivector_push_back(&bitlen_lld_e, j - 3); + } + else /*repeat code 18 supports max 138 zeroes*/ + { + if(j > 138) j = 138; + uivector_push_back(&bitlen_lld_e, 18); + uivector_push_back(&bitlen_lld_e, j - 11); + } + i += (j - 1); + } + else if(j >= 3) /*repeat code for value other than zero*/ + { + size_t k; + unsigned num = j / 6, rest = j % 6; + uivector_push_back(&bitlen_lld_e, bitlen_lld.data[i]); + for(k = 0; k < num; ++k) + { + uivector_push_back(&bitlen_lld_e, 16); + uivector_push_back(&bitlen_lld_e, 6 - 3); + } + if(rest >= 3) + { + uivector_push_back(&bitlen_lld_e, 16); + uivector_push_back(&bitlen_lld_e, rest - 3); + } + else j -= rest; + i += j; + } + else /*too short to benefit from repeat code*/ + { + uivector_push_back(&bitlen_lld_e, bitlen_lld.data[i]); + } + } + + /*generate tree_cl, the huffmantree of huffmantrees*/ + + if(!uivector_resizev(&frequencies_cl, NUM_CODE_LENGTH_CODES, 0)) ERROR_BREAK(83 /*alloc fail*/); + for(i = 0; i != bitlen_lld_e.size; ++i) + { + ++frequencies_cl.data[bitlen_lld_e.data[i]]; + /*after a repeat code come the bits that specify the number of repetitions, + those don't need to be in the frequencies_cl calculation*/ + if(bitlen_lld_e.data[i] >= 16) ++i; + } + + error = HuffmanTree_makeFromFrequencies(&tree_cl, frequencies_cl.data, + frequencies_cl.size, frequencies_cl.size, 7); + if(error) break; + + if(!uivector_resize(&bitlen_cl, tree_cl.numcodes)) ERROR_BREAK(83 /*alloc fail*/); + for(i = 0; i != tree_cl.numcodes; ++i) + { + /*lenghts of code length tree is in the order as specified by deflate*/ + bitlen_cl.data[i] = HuffmanTree_getLength(&tree_cl, CLCL_ORDER[i]); + } + while(bitlen_cl.data[bitlen_cl.size - 1] == 0 && bitlen_cl.size > 4) + { + /*remove zeros at the end, but minimum size must be 4*/ + if(!uivector_resize(&bitlen_cl, bitlen_cl.size - 1)) ERROR_BREAK(83 /*alloc fail*/); + } + if(error) break; + + /* + Write everything into the output + + After the BFINAL and BTYPE, the dynamic block consists out of the following: + - 5 bits HLIT, 5 bits HDIST, 4 bits HCLEN + - (HCLEN+4)*3 bits code lengths of code length alphabet + - HLIT + 257 code lenghts of lit/length alphabet (encoded using the code length + alphabet, + possible repetition codes 16, 17, 18) + - HDIST + 1 code lengths of distance alphabet (encoded using the code length + alphabet, + possible repetition codes 16, 17, 18) + - compressed data + - 256 (end code) + */ + + /*Write block type*/ + addBitToStream(bp, out, BFINAL); + addBitToStream(bp, out, 0); /*first bit of BTYPE "dynamic"*/ + addBitToStream(bp, out, 1); /*second bit of BTYPE "dynamic"*/ + + /*write the HLIT, HDIST and HCLEN values*/ + HLIT = (unsigned)(numcodes_ll - 257); + HDIST = (unsigned)(numcodes_d - 1); + HCLEN = (unsigned)bitlen_cl.size - 4; + /*trim zeroes for HCLEN. HLIT and HDIST were already trimmed at tree creation*/ + while(!bitlen_cl.data[HCLEN + 4 - 1] && HCLEN > 0) --HCLEN; + addBitsToStream(bp, out, HLIT, 5); + addBitsToStream(bp, out, HDIST, 5); + addBitsToStream(bp, out, HCLEN, 4); + + /*write the code lenghts of the code length alphabet*/ + for(i = 0; i != HCLEN + 4; ++i) addBitsToStream(bp, out, bitlen_cl.data[i], 3); + + /*write the lenghts of the lit/len AND the dist alphabet*/ + for(i = 0; i != bitlen_lld_e.size; ++i) + { + addHuffmanSymbol(bp, out, HuffmanTree_getCode(&tree_cl, bitlen_lld_e.data[i]), + HuffmanTree_getLength(&tree_cl, bitlen_lld_e.data[i])); + /*extra bits of repeat codes*/ + if(bitlen_lld_e.data[i] == 16) addBitsToStream(bp, out, bitlen_lld_e.data[++i], 2); + else if(bitlen_lld_e.data[i] == 17) addBitsToStream(bp, out, bitlen_lld_e.data[++i], 3); + else if(bitlen_lld_e.data[i] == 18) addBitsToStream(bp, out, bitlen_lld_e.data[++i], 7); + } + + /*write the compressed data symbols*/ + writeLZ77data(bp, out, &lz77_encoded, &tree_ll, &tree_d); + /*error: the length of the end code 256 must be larger than 0*/ + if(HuffmanTree_getLength(&tree_ll, 256) == 0) ERROR_BREAK(64); + + /*write the end code*/ + addHuffmanSymbol(bp, out, HuffmanTree_getCode(&tree_ll, 256), HuffmanTree_getLength(&tree_ll, 256)); + + break; /*end of error-while*/ + } + + /*cleanup*/ + uivector_cleanup(&lz77_encoded); + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + HuffmanTree_cleanup(&tree_cl); + uivector_cleanup(&frequencies_ll); + uivector_cleanup(&frequencies_d); + uivector_cleanup(&frequencies_cl); + uivector_cleanup(&bitlen_lld_e); + uivector_cleanup(&bitlen_lld); + uivector_cleanup(&bitlen_cl); + + return error; +} + +static unsigned deflateFixed(ucvector* out, size_t* bp, Hash* hash, + const unsigned char* data, + size_t datapos, size_t dataend, + const LodePNGCompressSettings* settings, unsigned final) +{ + HuffmanTree tree_ll; /*tree for literal values and length codes*/ + HuffmanTree tree_d; /*tree for distance codes*/ + + unsigned BFINAL = final; + unsigned error = 0; + size_t i; + + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + + generateFixedLitLenTree(&tree_ll); + generateFixedDistanceTree(&tree_d); + + addBitToStream(bp, out, BFINAL); + addBitToStream(bp, out, 1); /*first bit of BTYPE*/ + addBitToStream(bp, out, 0); /*second bit of BTYPE*/ + + if(settings->use_lz77) /*LZ77 encoded*/ + { + uivector lz77_encoded; + uivector_init(&lz77_encoded); + error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize, + settings->minmatch, settings->nicematch, settings->lazymatching); + if(!error) writeLZ77data(bp, out, &lz77_encoded, &tree_ll, &tree_d); + uivector_cleanup(&lz77_encoded); + } + else /*no LZ77, but still will be Huffman compressed*/ + { + for(i = datapos; i < dataend; ++i) + { + addHuffmanSymbol(bp, out, HuffmanTree_getCode(&tree_ll, data[i]), HuffmanTree_getLength(&tree_ll, data[i])); + } + } + /*add END code*/ + if(!error) addHuffmanSymbol(bp, out, HuffmanTree_getCode(&tree_ll, 256), HuffmanTree_getLength(&tree_ll, 256)); + + /*cleanup*/ + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + + return error; +} + +static unsigned lodepng_deflatev(ucvector* out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) +{ + unsigned error = 0; + size_t i, blocksize, numdeflateblocks; + size_t bp = 0; /*the bit pointer*/ + Hash hash; + + if(settings->btype > 2) return 61; + else if(settings->btype == 0) return deflateNoCompression(out, in, insize); + else if(settings->btype == 1) blocksize = insize; + else /*if(settings->btype == 2)*/ + { + /*on PNGs, deflate blocks of 65-262k seem to give most dense encoding*/ + blocksize = insize / 8 + 8; + if(blocksize < 65536) blocksize = 65536; + if(blocksize > 262144) blocksize = 262144; + } + + numdeflateblocks = (insize + blocksize - 1) / blocksize; + if(numdeflateblocks == 0) numdeflateblocks = 1; + + error = hash_init(&hash, settings->windowsize); + if(error) return error; + + for(i = 0; i != numdeflateblocks && !error; ++i) + { + unsigned final = (i == numdeflateblocks - 1); + size_t start = i * blocksize; + size_t end = start + blocksize; + if(end > insize) end = insize; + + if(settings->btype == 1) error = deflateFixed(out, &bp, &hash, in, start, end, settings, final); + else if(settings->btype == 2) error = deflateDynamic(out, &bp, &hash, in, start, end, settings, final); + } + + hash_cleanup(&hash); + + return error; +} + +unsigned lodepng_deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) +{ + unsigned error; + ucvector v; + ucvector_init_buffer(&v, *out, *outsize); + error = lodepng_deflatev(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +static unsigned deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) +{ + if(settings->custom_deflate) + { + return settings->custom_deflate(out, outsize, in, insize, settings); + } + else + { + return lodepng_deflate(out, outsize, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Adler32 */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static unsigned update_adler32(unsigned adler, const unsigned char* data, unsigned len) +{ + unsigned s1 = adler & 0xffff; + unsigned s2 = (adler >> 16) & 0xffff; + + while(len > 0) + { + /*at least 5550 sums can be done before the sums overflow, saving a lot of module divisions*/ + unsigned amount = len > 5550 ? 5550 : len; + len -= amount; + while(amount > 0) + { + s1 += (*data++); + s2 += s1; + --amount; + } + s1 %= 65521; + s2 %= 65521; + } + + return (s2 << 16) | s1; +} + +/*Return the adler32 of the bytes data[0..len-1]*/ +static unsigned adler32(const unsigned char* data, unsigned len) +{ + return update_adler32(1L, data, len); +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Zlib / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DECODER + +unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGDecompressSettings* settings) +{ + unsigned error = 0; + unsigned CM, CINFO, FDICT; + + if(insize < 2) return 53; /*error, size of zlib data too small*/ + /*read information from zlib header*/ + if((in[0] * 256 + in[1]) % 31 != 0) + { + /*error: 256 * in[0] + in[1] must be a multiple of 31, the FCHECK value is supposed to be made that way*/ + return 24; + } + + CM = in[0] & 15; + CINFO = (in[0] >> 4) & 15; + /*FCHECK = in[1] & 31;*/ /*FCHECK is already tested above*/ + FDICT = (in[1] >> 5) & 1; + /*FLEVEL = (in[1] >> 6) & 3;*/ /*FLEVEL is not used here*/ + + if(CM != 8 || CINFO > 7) + { + /*error: only compression method 8: inflate with sliding window of 32k is supported by the PNG spec*/ + return 25; + } + if(FDICT != 0) + { + /*error: the specification of PNG says about the zlib stream: + "The additional flags shall not specify a preset dictionary."*/ + return 26; + } + + error = inflate(out, outsize, in + 2, insize - 2, settings); + if(error) return error; + + if(!settings->ignore_adler32) + { + unsigned ADLER32 = lodepng_read32bitInt(&in[insize - 4]); + unsigned checksum = adler32(*out, (unsigned)(*outsize)); + if(checksum != ADLER32) return 58; /*error, adler checksum not correct, data must be corrupted*/ + } + + return 0; /*no error*/ +} + +static unsigned zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGDecompressSettings* settings) +{ + if(settings->custom_zlib) + { + return settings->custom_zlib(out, outsize, in, insize, settings); + } + else + { + return lodepng_zlib_decompress(out, outsize, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER + +unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) +{ + /*initially, *out must be NULL and outsize 0, if you just give some random *out + that's pointing to a non allocated buffer, this'll crash*/ + ucvector outv; + size_t i; + unsigned error; + unsigned char* deflatedata = 0; + size_t deflatesize = 0; + + /*zlib data: 1 byte CMF (CM+CINFO), 1 byte FLG, deflate data, 4 byte ADLER32 checksum of the Decompressed data*/ + unsigned CMF = 120; /*0b01111000: CM 8, CINFO 7. With CINFO 7, any window size up to 32768 can be used.*/ + unsigned FLEVEL = 0; + unsigned FDICT = 0; + unsigned CMFFLG = 256 * CMF + FDICT * 32 + FLEVEL * 64; + unsigned FCHECK = 31 - CMFFLG % 31; + CMFFLG += FCHECK; + + /*ucvector-controlled version of the output buffer, for dynamic array*/ + ucvector_init_buffer(&outv, *out, *outsize); + + ucvector_push_back(&outv, (unsigned char)(CMFFLG / 256)); + ucvector_push_back(&outv, (unsigned char)(CMFFLG % 256)); + + error = deflate(&deflatedata, &deflatesize, in, insize, settings); + + if(!error) + { + unsigned ADLER32 = adler32(in, (unsigned)insize); + for(i = 0; i != deflatesize; ++i) ucvector_push_back(&outv, deflatedata[i]); + lodepng_free(deflatedata); + lodepng_add32bitInt(&outv, ADLER32); + } + + *out = outv.data; + *outsize = outv.size; + + return error; +} + +/* compress using the default or custom zlib function */ +static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) +{ + if(settings->custom_zlib) + { + return settings->custom_zlib(out, outsize, in, insize, settings); + } + else + { + return lodepng_zlib_compress(out, outsize, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#else /*no LODEPNG_COMPILE_ZLIB*/ + +#ifdef LODEPNG_COMPILE_DECODER +static unsigned zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGDecompressSettings* settings) +{ + if(!settings->custom_zlib) return 87; /*no custom zlib function provided */ + return settings->custom_zlib(out, outsize, in, insize, settings); +} +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER +static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) +{ + if(!settings->custom_zlib) return 87; /*no custom zlib function provided */ + return settings->custom_zlib(out, outsize, in, insize, settings); +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#endif /*LODEPNG_COMPILE_ZLIB*/ + +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ENCODER + +/*this is a good tradeoff between speed and compression ratio*/ +#define DEFAULT_WINDOWSIZE 2048 + +void lodepng_compress_settings_init(LodePNGCompressSettings* settings) +{ + /*compress with dynamic huffman tree (not in the mathematical sense, just not the predefined one)*/ + settings->btype = 2; + settings->use_lz77 = 1; + settings->windowsize = DEFAULT_WINDOWSIZE; + settings->minmatch = 3; + settings->nicematch = 128; + settings->lazymatching = 1; + + settings->custom_zlib = 0; + settings->custom_deflate = 0; + settings->custom_context = 0; +} + +const LodePNGCompressSettings lodepng_default_compress_settings = {2, 1, DEFAULT_WINDOWSIZE, 3, 128, 1, 0, 0, 0}; + + +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings) +{ + settings->ignore_adler32 = 0; + + settings->custom_zlib = 0; + settings->custom_inflate = 0; + settings->custom_context = 0; +} + +const LodePNGDecompressSettings lodepng_default_decompress_settings = {0, 0, 0, 0}; + +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // End of Zlib related code. Begin of PNG related code. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_PNG + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / CRC32 / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/* CRC polynomial: 0xedb88320 */ +static unsigned lodepng_crc32_table[256] = { + 0u, 1996959894u, 3993919788u, 2567524794u, 124634137u, 1886057615u, 3915621685u, 2657392035u, + 249268274u, 2044508324u, 3772115230u, 2547177864u, 162941995u, 2125561021u, 3887607047u, 2428444049u, + 498536548u, 1789927666u, 4089016648u, 2227061214u, 450548861u, 1843258603u, 4107580753u, 2211677639u, + 325883990u, 1684777152u, 4251122042u, 2321926636u, 335633487u, 1661365465u, 4195302755u, 2366115317u, + 997073096u, 1281953886u, 3579855332u, 2724688242u, 1006888145u, 1258607687u, 3524101629u, 2768942443u, + 901097722u, 1119000684u, 3686517206u, 2898065728u, 853044451u, 1172266101u, 3705015759u, 2882616665u, + 651767980u, 1373503546u, 3369554304u, 3218104598u, 565507253u, 1454621731u, 3485111705u, 3099436303u, + 671266974u, 1594198024u, 3322730930u, 2970347812u, 795835527u, 1483230225u, 3244367275u, 3060149565u, + 1994146192u, 31158534u, 2563907772u, 4023717930u, 1907459465u, 112637215u, 2680153253u, 3904427059u, + 2013776290u, 251722036u, 2517215374u, 3775830040u, 2137656763u, 141376813u, 2439277719u, 3865271297u, + 1802195444u, 476864866u, 2238001368u, 4066508878u, 1812370925u, 453092731u, 2181625025u, 4111451223u, + 1706088902u, 314042704u, 2344532202u, 4240017532u, 1658658271u, 366619977u, 2362670323u, 4224994405u, + 1303535960u, 984961486u, 2747007092u, 3569037538u, 1256170817u, 1037604311u, 2765210733u, 3554079995u, + 1131014506u, 879679996u, 2909243462u, 3663771856u, 1141124467u, 855842277u, 2852801631u, 3708648649u, + 1342533948u, 654459306u, 3188396048u, 3373015174u, 1466479909u, 544179635u, 3110523913u, 3462522015u, + 1591671054u, 702138776u, 2966460450u, 3352799412u, 1504918807u, 783551873u, 3082640443u, 3233442989u, + 3988292384u, 2596254646u, 62317068u, 1957810842u, 3939845945u, 2647816111u, 81470997u, 1943803523u, + 3814918930u, 2489596804u, 225274430u, 2053790376u, 3826175755u, 2466906013u, 167816743u, 2097651377u, + 4027552580u, 2265490386u, 503444072u, 1762050814u, 4150417245u, 2154129355u, 426522225u, 1852507879u, + 4275313526u, 2312317920u, 282753626u, 1742555852u, 4189708143u, 2394877945u, 397917763u, 1622183637u, + 3604390888u, 2714866558u, 953729732u, 1340076626u, 3518719985u, 2797360999u, 1068828381u, 1219638859u, + 3624741850u, 2936675148u, 906185462u, 1090812512u, 3747672003u, 2825379669u, 829329135u, 1181335161u, + 3412177804u, 3160834842u, 628085408u, 1382605366u, 3423369109u, 3138078467u, 570562233u, 1426400815u, + 3317316542u, 2998733608u, 733239954u, 1555261956u, 3268935591u, 3050360625u, 752459403u, 1541320221u, + 2607071920u, 3965973030u, 1969922972u, 40735498u, 2617837225u, 3943577151u, 1913087877u, 83908371u, + 2512341634u, 3803740692u, 2075208622u, 213261112u, 2463272603u, 3855990285u, 2094854071u, 198958881u, + 2262029012u, 4057260610u, 1759359992u, 534414190u, 2176718541u, 4139329115u, 1873836001u, 414664567u, + 2282248934u, 4279200368u, 1711684554u, 285281116u, 2405801727u, 4167216745u, 1634467795u, 376229701u, + 2685067896u, 3608007406u, 1308918612u, 956543938u, 2808555105u, 3495958263u, 1231636301u, 1047427035u, + 2932959818u, 3654703836u, 1088359270u, 936918000u, 2847714899u, 3736837829u, 1202900863u, 817233897u, + 3183342108u, 3401237130u, 1404277552u, 615818150u, 3134207493u, 3453421203u, 1423857449u, 601450431u, + 3009837614u, 3294710456u, 1567103746u, 711928724u, 3020668471u, 3272380065u, 1510334235u, 755167117u +}; + +/*Return the CRC of the bytes buf[0..len-1].*/ +unsigned lodepng_crc32(const unsigned char* buf, size_t len) +{ + unsigned c = 0xffffffffL; + size_t n; + + for(n = 0; n < len; ++n) + { + c = lodepng_crc32_table[(c ^ buf[n]) & 0xff] ^ (c >> 8); + } + return c ^ 0xffffffffL; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Reading and writing single bits and bytes from/to stream for LodePNG / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static unsigned char readBitFromReversedStream(size_t* bitpointer, const unsigned char* bitstream) +{ + unsigned char result = (unsigned char)((bitstream[(*bitpointer) >> 3] >> (7 - ((*bitpointer) & 0x7))) & 1); + ++(*bitpointer); + return result; +} + +static unsigned readBitsFromReversedStream(size_t* bitpointer, const unsigned char* bitstream, size_t nbits) +{ + unsigned result = 0; + size_t i; + for(i = nbits - 1; i < nbits; --i) + { + result += (unsigned)readBitFromReversedStream(bitpointer, bitstream) << i; + } + return result; +} + +#ifdef LODEPNG_COMPILE_DECODER +static void setBitOfReversedStream0(size_t* bitpointer, unsigned char* bitstream, unsigned char bit) +{ + /*the current bit in bitstream must be 0 for this to work*/ + if(bit) + { + /*earlier bit of huffman code is in a lesser significant bit of an earlier byte*/ + bitstream[(*bitpointer) >> 3] |= (bit << (7 - ((*bitpointer) & 0x7))); + } + ++(*bitpointer); +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +static void setBitOfReversedStream(size_t* bitpointer, unsigned char* bitstream, unsigned char bit) +{ + /*the current bit in bitstream may be 0 or 1 for this to work*/ + if(bit == 0) bitstream[(*bitpointer) >> 3] &= (unsigned char)(~(1 << (7 - ((*bitpointer) & 0x7)))); + else bitstream[(*bitpointer) >> 3] |= (1 << (7 - ((*bitpointer) & 0x7))); + ++(*bitpointer); +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG chunks / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +unsigned lodepng_chunk_length(const unsigned char* chunk) +{ + return lodepng_read32bitInt(&chunk[0]); +} + +void lodepng_chunk_type(char type[5], const unsigned char* chunk) +{ + unsigned i; + for(i = 0; i != 4; ++i) type[i] = (char)chunk[4 + i]; + type[4] = 0; /*null termination char*/ +} + +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type) +{ + if(strlen(type) != 4) return 0; + return (chunk[4] == type[0] && chunk[5] == type[1] && chunk[6] == type[2] && chunk[7] == type[3]); +} + +unsigned char lodepng_chunk_ancillary(const unsigned char* chunk) +{ + return((chunk[4] & 32) != 0); +} + +unsigned char lodepng_chunk_private(const unsigned char* chunk) +{ + return((chunk[6] & 32) != 0); +} + +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk) +{ + return((chunk[7] & 32) != 0); +} + +unsigned char* lodepng_chunk_data(unsigned char* chunk) +{ + return &chunk[8]; +} + +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk) +{ + return &chunk[8]; +} + +unsigned lodepng_chunk_check_crc(const unsigned char* chunk) +{ + unsigned length = lodepng_chunk_length(chunk); + unsigned CRC = lodepng_read32bitInt(&chunk[length + 8]); + /*the CRC is taken of the data and the 4 chunk type letters, not the length*/ + unsigned checksum = lodepng_crc32(&chunk[4], length + 4); + if(CRC != checksum) return 1; + else return 0; +} + +void lodepng_chunk_generate_crc(unsigned char* chunk) +{ + unsigned length = lodepng_chunk_length(chunk); + unsigned CRC = lodepng_crc32(&chunk[4], length + 4); + lodepng_set32bitInt(chunk + 8 + length, CRC); +} + +unsigned char* lodepng_chunk_next(unsigned char* chunk) +{ + unsigned total_chunk_length = lodepng_chunk_length(chunk) + 12; + return &chunk[total_chunk_length]; +} + +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk) +{ + unsigned total_chunk_length = lodepng_chunk_length(chunk) + 12; + return &chunk[total_chunk_length]; +} + +unsigned lodepng_chunk_append(unsigned char** out, size_t* outlength, const unsigned char* chunk) +{ + unsigned i; + unsigned total_chunk_length = lodepng_chunk_length(chunk) + 12; + unsigned char *chunk_start, *new_buffer; + size_t new_length = (*outlength) + total_chunk_length; + if(new_length < total_chunk_length || new_length < (*outlength)) return 77; /*integer overflow happened*/ + + new_buffer = (unsigned char*)lodepng_realloc(*out, new_length); + if(!new_buffer) return 83; /*alloc fail*/ + (*out) = new_buffer; + (*outlength) = new_length; + chunk_start = &(*out)[new_length - total_chunk_length]; + + for(i = 0; i != total_chunk_length; ++i) chunk_start[i] = chunk[i]; + + return 0; +} + +unsigned lodepng_chunk_create(unsigned char** out, size_t* outlength, unsigned length, + const char* type, const unsigned char* data) +{ + unsigned i; + unsigned char *chunk, *new_buffer; + size_t new_length = (*outlength) + length + 12; + if(new_length < length + 12 || new_length < (*outlength)) return 77; /*integer overflow happened*/ + new_buffer = (unsigned char*)lodepng_realloc(*out, new_length); + if(!new_buffer) return 83; /*alloc fail*/ + (*out) = new_buffer; + (*outlength) = new_length; + chunk = &(*out)[(*outlength) - length - 12]; + + /*1: length*/ + lodepng_set32bitInt(chunk, (unsigned)length); + + /*2: chunk name (4 letters)*/ + chunk[4] = (unsigned char)type[0]; + chunk[5] = (unsigned char)type[1]; + chunk[6] = (unsigned char)type[2]; + chunk[7] = (unsigned char)type[3]; + + /*3: the data*/ + for(i = 0; i != length; ++i) chunk[8 + i] = data[i]; + + /*4: CRC (of the chunkname characters and the data)*/ + lodepng_chunk_generate_crc(chunk); + + return 0; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Color types and such / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*return type is a LodePNG error code*/ +static unsigned checkColorValidity(LodePNGColorType colortype, unsigned bd) /*bd = bitdepth*/ +{ + switch(colortype) + { + case 0: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 || bd == 16)) return 37; break; /*grey*/ + case 2: if(!( bd == 8 || bd == 16)) return 37; break; /*RGB*/ + case 3: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 )) return 37; break; /*palette*/ + case 4: if(!( bd == 8 || bd == 16)) return 37; break; /*grey + alpha*/ + case 6: if(!( bd == 8 || bd == 16)) return 37; break; /*RGBA*/ + default: return 31; + } + return 0; /*allowed color type / bits combination*/ +} + +static unsigned getNumColorChannels(LodePNGColorType colortype) +{ + switch(colortype) + { + case 0: return 1; /*grey*/ + case 2: return 3; /*RGB*/ + case 3: return 1; /*palette*/ + case 4: return 2; /*grey + alpha*/ + case 6: return 4; /*RGBA*/ + } + return 0; /*unexisting color type*/ +} + +static unsigned lodepng_get_bpp_lct(LodePNGColorType colortype, unsigned bitdepth) +{ + /*bits per pixel is amount of channels * bits per channel*/ + return getNumColorChannels(colortype) * bitdepth; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +void lodepng_color_mode_init(LodePNGColorMode* info) +{ + info->key_defined = 0; + info->key_r = info->key_g = info->key_b = 0; + info->colortype = LCT_RGBA; + info->bitdepth = 8; + info->palette = 0; + info->palettesize = 0; +} + +void lodepng_color_mode_cleanup(LodePNGColorMode* info) +{ + lodepng_palette_clear(info); +} + +unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source) +{ + size_t i; + lodepng_color_mode_cleanup(dest); + *dest = *source; + if(source->palette) + { + dest->palette = (unsigned char*)lodepng_malloc(1024); + if(!dest->palette && source->palettesize) return 83; /*alloc fail*/ + for(i = 0; i != source->palettesize * 4; ++i) dest->palette[i] = source->palette[i]; + } + return 0; +} + +static int lodepng_color_mode_equal(const LodePNGColorMode* a, const LodePNGColorMode* b) +{ + size_t i; + if(a->colortype != b->colortype) return 0; + if(a->bitdepth != b->bitdepth) return 0; + if(a->key_defined != b->key_defined) return 0; + if(a->key_defined) + { + if(a->key_r != b->key_r) return 0; + if(a->key_g != b->key_g) return 0; + if(a->key_b != b->key_b) return 0; + } + if(a->palettesize != b->palettesize) return 0; + for(i = 0; i != a->palettesize * 4; ++i) + { + if(a->palette[i] != b->palette[i]) return 0; + } + return 1; +} + +void lodepng_palette_clear(LodePNGColorMode* info) +{ + if(info->palette) lodepng_free(info->palette); + info->palette = 0; + info->palettesize = 0; +} + +unsigned lodepng_palette_add(LodePNGColorMode* info, + unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + unsigned char* data; + /*the same resize technique as C++ std::vectors is used, and here it's made so that for a palette with + the max of 256 colors, it'll have the exact alloc size*/ + if(!info->palette) /*allocate palette if empty*/ + { + /*room for 256 colors with 4 bytes each*/ + data = (unsigned char*)lodepng_realloc(info->palette, 1024); + if(!data) return 83; /*alloc fail*/ + else info->palette = data; + } + info->palette[4 * info->palettesize + 0] = r; + info->palette[4 * info->palettesize + 1] = g; + info->palette[4 * info->palettesize + 2] = b; + info->palette[4 * info->palettesize + 3] = a; + ++info->palettesize; + return 0; +} + +unsigned lodepng_get_bpp(const LodePNGColorMode* info) +{ + /*calculate bits per pixel out of colortype and bitdepth*/ + return lodepng_get_bpp_lct(info->colortype, info->bitdepth); +} + +unsigned lodepng_get_channels(const LodePNGColorMode* info) +{ + return getNumColorChannels(info->colortype); +} + +unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info) +{ + return info->colortype == LCT_GREY || info->colortype == LCT_GREY_ALPHA; +} + +unsigned lodepng_is_alpha_type(const LodePNGColorMode* info) +{ + return (info->colortype & 4) != 0; /*4 or 6*/ +} + +unsigned lodepng_is_palette_type(const LodePNGColorMode* info) +{ + return info->colortype == LCT_PALETTE; +} + +unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info) +{ + size_t i; + for(i = 0; i != info->palettesize; ++i) + { + if(info->palette[i * 4 + 3] < 255) return 1; + } + return 0; +} + +unsigned lodepng_can_have_alpha(const LodePNGColorMode* info) +{ + return info->key_defined + || lodepng_is_alpha_type(info) + || lodepng_has_palette_alpha(info); +} + +size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color) +{ + return (w * h * lodepng_get_bpp(color) + 7) / 8; +} + +size_t lodepng_get_raw_size_lct(unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) +{ + return (w * h * lodepng_get_bpp_lct(colortype, bitdepth) + 7) / 8; +} + + +#ifdef LODEPNG_COMPILE_PNG +#ifdef LODEPNG_COMPILE_DECODER +/*in an idat chunk, each scanline is a multiple of 8 bits, unlike the lodepng output buffer*/ +static size_t lodepng_get_raw_size_idat(unsigned w, unsigned h, const LodePNGColorMode* color) +{ + return h * ((w * lodepng_get_bpp(color) + 7) / 8); +} +#endif /*LODEPNG_COMPILE_DECODER*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +static void LodePNGUnknownChunks_init(LodePNGInfo* info) +{ + unsigned i; + for(i = 0; i != 3; ++i) info->unknown_chunks_data[i] = 0; + for(i = 0; i != 3; ++i) info->unknown_chunks_size[i] = 0; +} + +static void LodePNGUnknownChunks_cleanup(LodePNGInfo* info) +{ + unsigned i; + for(i = 0; i != 3; ++i) lodepng_free(info->unknown_chunks_data[i]); +} + +static unsigned LodePNGUnknownChunks_copy(LodePNGInfo* dest, const LodePNGInfo* src) +{ + unsigned i; + + LodePNGUnknownChunks_cleanup(dest); + + for(i = 0; i != 3; ++i) + { + size_t j; + dest->unknown_chunks_size[i] = src->unknown_chunks_size[i]; + dest->unknown_chunks_data[i] = (unsigned char*)lodepng_malloc(src->unknown_chunks_size[i]); + if(!dest->unknown_chunks_data[i] && dest->unknown_chunks_size[i]) return 83; /*alloc fail*/ + for(j = 0; j < src->unknown_chunks_size[i]; ++j) + { + dest->unknown_chunks_data[i][j] = src->unknown_chunks_data[i][j]; + } + } + + return 0; +} + +/******************************************************************************/ + +static void LodePNGText_init(LodePNGInfo* info) +{ + info->text_num = 0; + info->text_keys = NULL; + info->text_strings = NULL; +} + +static void LodePNGText_cleanup(LodePNGInfo* info) +{ + size_t i; + for(i = 0; i != info->text_num; ++i) + { + string_cleanup(&info->text_keys[i]); + string_cleanup(&info->text_strings[i]); + } + lodepng_free(info->text_keys); + lodepng_free(info->text_strings); +} + +static unsigned LodePNGText_copy(LodePNGInfo* dest, const LodePNGInfo* source) +{ + size_t i = 0; + dest->text_keys = 0; + dest->text_strings = 0; + dest->text_num = 0; + for(i = 0; i != source->text_num; ++i) + { + CERROR_TRY_RETURN(lodepng_add_text(dest, source->text_keys[i], source->text_strings[i])); + } + return 0; +} + +void lodepng_clear_text(LodePNGInfo* info) +{ + LodePNGText_cleanup(info); +} + +unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str) +{ + char** new_keys = (char**)(lodepng_realloc(info->text_keys, sizeof(char*) * (info->text_num + 1))); + char** new_strings = (char**)(lodepng_realloc(info->text_strings, sizeof(char*) * (info->text_num + 1))); + if(!new_keys || !new_strings) + { + lodepng_free(new_keys); + lodepng_free(new_strings); + return 83; /*alloc fail*/ + } + + ++info->text_num; + info->text_keys = new_keys; + info->text_strings = new_strings; + + string_init(&info->text_keys[info->text_num - 1]); + string_set(&info->text_keys[info->text_num - 1], key); + + string_init(&info->text_strings[info->text_num - 1]); + string_set(&info->text_strings[info->text_num - 1], str); + + return 0; +} + +/******************************************************************************/ + +static void LodePNGIText_init(LodePNGInfo* info) +{ + info->itext_num = 0; + info->itext_keys = NULL; + info->itext_langtags = NULL; + info->itext_transkeys = NULL; + info->itext_strings = NULL; +} + +static void LodePNGIText_cleanup(LodePNGInfo* info) +{ + size_t i; + for(i = 0; i != info->itext_num; ++i) + { + string_cleanup(&info->itext_keys[i]); + string_cleanup(&info->itext_langtags[i]); + string_cleanup(&info->itext_transkeys[i]); + string_cleanup(&info->itext_strings[i]); + } + lodepng_free(info->itext_keys); + lodepng_free(info->itext_langtags); + lodepng_free(info->itext_transkeys); + lodepng_free(info->itext_strings); +} + +static unsigned LodePNGIText_copy(LodePNGInfo* dest, const LodePNGInfo* source) +{ + size_t i = 0; + dest->itext_keys = 0; + dest->itext_langtags = 0; + dest->itext_transkeys = 0; + dest->itext_strings = 0; + dest->itext_num = 0; + for(i = 0; i != source->itext_num; ++i) + { + CERROR_TRY_RETURN(lodepng_add_itext(dest, source->itext_keys[i], source->itext_langtags[i], + source->itext_transkeys[i], source->itext_strings[i])); + } + return 0; +} + +void lodepng_clear_itext(LodePNGInfo* info) +{ + LodePNGIText_cleanup(info); +} + +unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str) +{ + char** new_keys = (char**)(lodepng_realloc(info->itext_keys, sizeof(char*) * (info->itext_num + 1))); + char** new_langtags = (char**)(lodepng_realloc(info->itext_langtags, sizeof(char*) * (info->itext_num + 1))); + char** new_transkeys = (char**)(lodepng_realloc(info->itext_transkeys, sizeof(char*) * (info->itext_num + 1))); + char** new_strings = (char**)(lodepng_realloc(info->itext_strings, sizeof(char*) * (info->itext_num + 1))); + if(!new_keys || !new_langtags || !new_transkeys || !new_strings) + { + lodepng_free(new_keys); + lodepng_free(new_langtags); + lodepng_free(new_transkeys); + lodepng_free(new_strings); + return 83; /*alloc fail*/ + } + + ++info->itext_num; + info->itext_keys = new_keys; + info->itext_langtags = new_langtags; + info->itext_transkeys = new_transkeys; + info->itext_strings = new_strings; + + string_init(&info->itext_keys[info->itext_num - 1]); + string_set(&info->itext_keys[info->itext_num - 1], key); + + string_init(&info->itext_langtags[info->itext_num - 1]); + string_set(&info->itext_langtags[info->itext_num - 1], langtag); + + string_init(&info->itext_transkeys[info->itext_num - 1]); + string_set(&info->itext_transkeys[info->itext_num - 1], transkey); + + string_init(&info->itext_strings[info->itext_num - 1]); + string_set(&info->itext_strings[info->itext_num - 1], str); + + return 0; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +void lodepng_info_init(LodePNGInfo* info) +{ + lodepng_color_mode_init(&info->color); + info->interlace_method = 0; + info->compression_method = 0; + info->filter_method = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + info->background_defined = 0; + info->background_r = info->background_g = info->background_b = 0; + + LodePNGText_init(info); + LodePNGIText_init(info); + + info->time_defined = 0; + info->phys_defined = 0; + + LodePNGUnknownChunks_init(info); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +void lodepng_info_cleanup(LodePNGInfo* info) +{ + lodepng_color_mode_cleanup(&info->color); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + LodePNGText_cleanup(info); + LodePNGIText_cleanup(info); + + LodePNGUnknownChunks_cleanup(info); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source) +{ + lodepng_info_cleanup(dest); + *dest = *source; + lodepng_color_mode_init(&dest->color); + CERROR_TRY_RETURN(lodepng_color_mode_copy(&dest->color, &source->color)); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + CERROR_TRY_RETURN(LodePNGText_copy(dest, source)); + CERROR_TRY_RETURN(LodePNGIText_copy(dest, source)); + + LodePNGUnknownChunks_init(dest); + CERROR_TRY_RETURN(LodePNGUnknownChunks_copy(dest, source)); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + return 0; +} + +void lodepng_info_swap(LodePNGInfo* a, LodePNGInfo* b) +{ + LodePNGInfo temp = *a; + *a = *b; + *b = temp; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +/*index: bitgroup index, bits: bitgroup size(1, 2 or 4), in: bitgroup value, out: octet array to add bits to*/ +static void addColorBits(unsigned char* out, size_t index, unsigned bits, unsigned in) +{ + unsigned m = bits == 1 ? 7 : bits == 2 ? 3 : 1; /*8 / bits - 1*/ + /*p = the partial index in the byte, e.g. with 4 palettebits it is 0 for first half or 1 for second half*/ + unsigned p = index & m; + in &= (1u << bits) - 1u; /*filter out any other bits of the input value*/ + in = in << (bits * (m - p)); + if(p == 0) out[index * bits / 8] = in; + else out[index * bits / 8] |= in; +} + +typedef struct ColorTree ColorTree; + +/* +One node of a color tree +This is the data structure used to count the number of unique colors and to get a palette +index for a color. It's like an octree, but because the alpha channel is used too, each +node has 16 instead of 8 children. +*/ +struct ColorTree +{ + ColorTree* children[16]; /*up to 16 pointers to ColorTree of next level*/ + int index; /*the payload. Only has a meaningful value if this is in the last level*/ +}; + +static void color_tree_init(ColorTree* tree) +{ + int i; + for(i = 0; i != 16; ++i) tree->children[i] = 0; + tree->index = -1; +} + +static void color_tree_cleanup(ColorTree* tree) +{ + int i; + for(i = 0; i != 16; ++i) + { + if(tree->children[i]) + { + color_tree_cleanup(tree->children[i]); + lodepng_free(tree->children[i]); + } + } +} + +/*returns -1 if color not present, its index otherwise*/ +static int color_tree_get(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + int bit = 0; + for(bit = 0; bit < 8; ++bit) + { + int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); + if(!tree->children[i]) return -1; + else tree = tree->children[i]; + } + return tree ? tree->index : -1; +} + +#ifdef LODEPNG_COMPILE_ENCODER +static int color_tree_has(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + return color_tree_get(tree, r, g, b, a) >= 0; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/*color is not allowed to already exist. +Index should be >= 0 (it's signed to be compatible with using -1 for "doesn't exist")*/ +static void color_tree_add(ColorTree* tree, + unsigned char r, unsigned char g, unsigned char b, unsigned char a, unsigned index) +{ + int bit; + for(bit = 0; bit < 8; ++bit) + { + int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); + if(!tree->children[i]) + { + tree->children[i] = (ColorTree*)lodepng_malloc(sizeof(ColorTree)); + color_tree_init(tree->children[i]); + } + tree = tree->children[i]; + } + tree->index = (int)index; +} + +/*put a pixel, given its RGBA color, into image of any color type*/ +static unsigned rgba8ToPixel(unsigned char* out, size_t i, + const LodePNGColorMode* mode, ColorTree* tree /*for palette*/, + unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + if(mode->colortype == LCT_GREY) + { + unsigned char grey = r; /*((unsigned short)r + g + b) / 3*/; + if(mode->bitdepth == 8) out[i] = grey; + else if(mode->bitdepth == 16) out[i * 2 + 0] = out[i * 2 + 1] = grey; + else + { + /*take the most significant bits of grey*/ + grey = (grey >> (8 - mode->bitdepth)) & ((1 << mode->bitdepth) - 1); + addColorBits(out, i, mode->bitdepth, grey); + } + } + else if(mode->colortype == LCT_RGB) + { + if(mode->bitdepth == 8) + { + out[i * 3 + 0] = r; + out[i * 3 + 1] = g; + out[i * 3 + 2] = b; + } + else + { + out[i * 6 + 0] = out[i * 6 + 1] = r; + out[i * 6 + 2] = out[i * 6 + 3] = g; + out[i * 6 + 4] = out[i * 6 + 5] = b; + } + } + else if(mode->colortype == LCT_PALETTE) + { + int index = color_tree_get(tree, r, g, b, a); + if(index < 0) return 82; /*color not in palette*/ + if(mode->bitdepth == 8) out[i] = index; + else addColorBits(out, i, mode->bitdepth, (unsigned)index); + } + else if(mode->colortype == LCT_GREY_ALPHA) + { + unsigned char grey = r; /*((unsigned short)r + g + b) / 3*/; + if(mode->bitdepth == 8) + { + out[i * 2 + 0] = grey; + out[i * 2 + 1] = a; + } + else if(mode->bitdepth == 16) + { + out[i * 4 + 0] = out[i * 4 + 1] = grey; + out[i * 4 + 2] = out[i * 4 + 3] = a; + } + } + else if(mode->colortype == LCT_RGBA) + { + if(mode->bitdepth == 8) + { + out[i * 4 + 0] = r; + out[i * 4 + 1] = g; + out[i * 4 + 2] = b; + out[i * 4 + 3] = a; + } + else + { + out[i * 8 + 0] = out[i * 8 + 1] = r; + out[i * 8 + 2] = out[i * 8 + 3] = g; + out[i * 8 + 4] = out[i * 8 + 5] = b; + out[i * 8 + 6] = out[i * 8 + 7] = a; + } + } + + return 0; /*no error*/ +} + +/*put a pixel, given its RGBA16 color, into image of any color 16-bitdepth type*/ +static void rgba16ToPixel(unsigned char* out, size_t i, + const LodePNGColorMode* mode, + unsigned short r, unsigned short g, unsigned short b, unsigned short a) +{ + if(mode->colortype == LCT_GREY) + { + unsigned short grey = r; /*((unsigned)r + g + b) / 3*/; + out[i * 2 + 0] = (grey >> 8) & 255; + out[i * 2 + 1] = grey & 255; + } + else if(mode->colortype == LCT_RGB) + { + out[i * 6 + 0] = (r >> 8) & 255; + out[i * 6 + 1] = r & 255; + out[i * 6 + 2] = (g >> 8) & 255; + out[i * 6 + 3] = g & 255; + out[i * 6 + 4] = (b >> 8) & 255; + out[i * 6 + 5] = b & 255; + } + else if(mode->colortype == LCT_GREY_ALPHA) + { + unsigned short grey = r; /*((unsigned)r + g + b) / 3*/; + out[i * 4 + 0] = (grey >> 8) & 255; + out[i * 4 + 1] = grey & 255; + out[i * 4 + 2] = (a >> 8) & 255; + out[i * 4 + 3] = a & 255; + } + else if(mode->colortype == LCT_RGBA) + { + out[i * 8 + 0] = (r >> 8) & 255; + out[i * 8 + 1] = r & 255; + out[i * 8 + 2] = (g >> 8) & 255; + out[i * 8 + 3] = g & 255; + out[i * 8 + 4] = (b >> 8) & 255; + out[i * 8 + 5] = b & 255; + out[i * 8 + 6] = (a >> 8) & 255; + out[i * 8 + 7] = a & 255; + } +} + +/*Get RGBA8 color of pixel with index i (y * width + x) from the raw image with given color type.*/ +static void getPixelColorRGBA8(unsigned char* r, unsigned char* g, + unsigned char* b, unsigned char* a, + const unsigned char* in, size_t i, + const LodePNGColorMode* mode) +{ + if(mode->colortype == LCT_GREY) + { + if(mode->bitdepth == 8) + { + *r = *g = *b = in[i]; + if(mode->key_defined && *r == mode->key_r) *a = 0; + else *a = 255; + } + else if(mode->bitdepth == 16) + { + *r = *g = *b = in[i * 2 + 0]; + if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; + else *a = 255; + } + else + { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = i * mode->bitdepth; + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + *r = *g = *b = (value * 255) / highest; + if(mode->key_defined && value == mode->key_r) *a = 0; + else *a = 255; + } + } + else if(mode->colortype == LCT_RGB) + { + if(mode->bitdepth == 8) + { + *r = in[i * 3 + 0]; *g = in[i * 3 + 1]; *b = in[i * 3 + 2]; + if(mode->key_defined && *r == mode->key_r && *g == mode->key_g && *b == mode->key_b) *a = 0; + else *a = 255; + } + else + { + *r = in[i * 6 + 0]; + *g = in[i * 6 + 2]; + *b = in[i * 6 + 4]; + if(mode->key_defined && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; + else *a = 255; + } + } + else if(mode->colortype == LCT_PALETTE) + { + unsigned index; + if(mode->bitdepth == 8) index = in[i]; + else + { + size_t j = i * mode->bitdepth; + index = readBitsFromReversedStream(&j, in, mode->bitdepth); + } + + if(index >= mode->palettesize) + { + /*This is an error according to the PNG spec, but common PNG decoders make it black instead. + Done here too, slightly faster due to no error handling needed.*/ + *r = *g = *b = 0; + *a = 255; + } + else + { + *r = mode->palette[index * 4 + 0]; + *g = mode->palette[index * 4 + 1]; + *b = mode->palette[index * 4 + 2]; + *a = mode->palette[index * 4 + 3]; + } + } + else if(mode->colortype == LCT_GREY_ALPHA) + { + if(mode->bitdepth == 8) + { + *r = *g = *b = in[i * 2 + 0]; + *a = in[i * 2 + 1]; + } + else + { + *r = *g = *b = in[i * 4 + 0]; + *a = in[i * 4 + 2]; + } + } + else if(mode->colortype == LCT_RGBA) + { + if(mode->bitdepth == 8) + { + *r = in[i * 4 + 0]; + *g = in[i * 4 + 1]; + *b = in[i * 4 + 2]; + *a = in[i * 4 + 3]; + } + else + { + *r = in[i * 8 + 0]; + *g = in[i * 8 + 2]; + *b = in[i * 8 + 4]; + *a = in[i * 8 + 6]; + } + } +} + +/*Similar to getPixelColorRGBA8, but with all the for loops inside of the color +mode test cases, optimized to convert the colors much faster, when converting +to RGBA or RGB with 8 bit per cannel. buffer must be RGBA or RGB output with +enough memory, if has_alpha is true the output is RGBA. mode has the color mode +of the input buffer.*/ +static void getPixelColorsRGBA8(unsigned char* buffer, size_t numpixels, + unsigned has_alpha, const unsigned char* in, + const LodePNGColorMode* mode) +{ + unsigned num_channels = has_alpha ? 4 : 3; + size_t i; + if(mode->colortype == LCT_GREY) + { + if(mode->bitdepth == 8) + { + for(i = 0; i != numpixels; ++i, buffer += num_channels) + { + buffer[0] = buffer[1] = buffer[2] = in[i]; + if(has_alpha) buffer[3] = mode->key_defined && in[i] == mode->key_r ? 0 : 255; + } + } + else if(mode->bitdepth == 16) + { + for(i = 0; i != numpixels; ++i, buffer += num_channels) + { + buffer[0] = buffer[1] = buffer[2] = in[i * 2]; + if(has_alpha) buffer[3] = mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r ? 0 : 255; + } + } + else + { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) + { + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + buffer[0] = buffer[1] = buffer[2] = (value * 255) / highest; + if(has_alpha) buffer[3] = mode->key_defined && value == mode->key_r ? 0 : 255; + } + } + } + else if(mode->colortype == LCT_RGB) + { + if(mode->bitdepth == 8) + { + for(i = 0; i != numpixels; ++i, buffer += num_channels) + { + buffer[0] = in[i * 3 + 0]; + buffer[1] = in[i * 3 + 1]; + buffer[2] = in[i * 3 + 2]; + if(has_alpha) buffer[3] = mode->key_defined && buffer[0] == mode->key_r + && buffer[1]== mode->key_g && buffer[2] == mode->key_b ? 0 : 255; + } + } + else + { + for(i = 0; i != numpixels; ++i, buffer += num_channels) + { + buffer[0] = in[i * 6 + 0]; + buffer[1] = in[i * 6 + 2]; + buffer[2] = in[i * 6 + 4]; + if(has_alpha) buffer[3] = mode->key_defined + && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b ? 0 : 255; + } + } + } + else if(mode->colortype == LCT_PALETTE) + { + unsigned index; + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) + { + if(mode->bitdepth == 8) index = in[i]; + else index = readBitsFromReversedStream(&j, in, mode->bitdepth); + + if(index >= mode->palettesize) + { + /*This is an error according to the PNG spec, but most PNG decoders make it black instead. + Done here too, slightly faster due to no error handling needed.*/ + buffer[0] = buffer[1] = buffer[2] = 0; + if(has_alpha) buffer[3] = 255; + } + else + { + buffer[0] = mode->palette[index * 4 + 0]; + buffer[1] = mode->palette[index * 4 + 1]; + buffer[2] = mode->palette[index * 4 + 2]; + if(has_alpha) buffer[3] = mode->palette[index * 4 + 3]; + } + } + } + else if(mode->colortype == LCT_GREY_ALPHA) + { + if(mode->bitdepth == 8) + { + for(i = 0; i != numpixels; ++i, buffer += num_channels) + { + buffer[0] = buffer[1] = buffer[2] = in[i * 2 + 0]; + if(has_alpha) buffer[3] = in[i * 2 + 1]; + } + } + else + { + for(i = 0; i != numpixels; ++i, buffer += num_channels) + { + buffer[0] = buffer[1] = buffer[2] = in[i * 4 + 0]; + if(has_alpha) buffer[3] = in[i * 4 + 2]; + } + } + } + else if(mode->colortype == LCT_RGBA) + { + if(mode->bitdepth == 8) + { + for(i = 0; i != numpixels; ++i, buffer += num_channels) + { + buffer[0] = in[i * 4 + 0]; + buffer[1] = in[i * 4 + 1]; + buffer[2] = in[i * 4 + 2]; + if(has_alpha) buffer[3] = in[i * 4 + 3]; + } + } + else + { + for(i = 0; i != numpixels; ++i, buffer += num_channels) + { + buffer[0] = in[i * 8 + 0]; + buffer[1] = in[i * 8 + 2]; + buffer[2] = in[i * 8 + 4]; + if(has_alpha) buffer[3] = in[i * 8 + 6]; + } + } + } +} + +/*Get RGBA16 color of pixel with index i (y * width + x) from the raw image with +given color type, but the given color type must be 16-bit itself.*/ +static void getPixelColorRGBA16(unsigned short* r, unsigned short* g, unsigned short* b, unsigned short* a, + const unsigned char* in, size_t i, const LodePNGColorMode* mode) +{ + if(mode->colortype == LCT_GREY) + { + *r = *g = *b = 256 * in[i * 2 + 0] + in[i * 2 + 1]; + if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; + else *a = 65535; + } + else if(mode->colortype == LCT_RGB) + { + *r = 256 * in[i * 6 + 0] + in[i * 6 + 1]; + *g = 256 * in[i * 6 + 2] + in[i * 6 + 3]; + *b = 256 * in[i * 6 + 4] + in[i * 6 + 5]; + if(mode->key_defined && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; + else *a = 65535; + } + else if(mode->colortype == LCT_GREY_ALPHA) + { + *r = *g = *b = 256 * in[i * 4 + 0] + in[i * 4 + 1]; + *a = 256 * in[i * 4 + 2] + in[i * 4 + 3]; + } + else if(mode->colortype == LCT_RGBA) + { + *r = 256 * in[i * 8 + 0] + in[i * 8 + 1]; + *g = 256 * in[i * 8 + 2] + in[i * 8 + 3]; + *b = 256 * in[i * 8 + 4] + in[i * 8 + 5]; + *a = 256 * in[i * 8 + 6] + in[i * 8 + 7]; + } +} + +unsigned lodepng_convert(unsigned char* out, const unsigned char* in, + LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, + unsigned w, unsigned h) +{ + size_t i; + ColorTree tree; + size_t numpixels = w * h; + + if(lodepng_color_mode_equal(mode_out, mode_in)) + { + size_t numbytes = lodepng_get_raw_size(w, h, mode_in); + for(i = 0; i != numbytes; ++i) out[i] = in[i]; + return 0; + } + + if(mode_out->colortype == LCT_PALETTE) + { + size_t palsize = 1u << mode_out->bitdepth; + if(mode_out->palettesize < palsize) palsize = mode_out->palettesize; + color_tree_init(&tree); + for(i = 0; i != palsize; ++i) + { + unsigned char* p = &mode_out->palette[i * 4]; + color_tree_add(&tree, p[0], p[1], p[2], p[3], i); + } + } + + if(mode_in->bitdepth == 16 && mode_out->bitdepth == 16) + { + for(i = 0; i != numpixels; ++i) + { + unsigned short r = 0, g = 0, b = 0, a = 0; + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + rgba16ToPixel(out, i, mode_out, r, g, b, a); + } + } + else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGBA) + { + getPixelColorsRGBA8(out, numpixels, 1, in, mode_in); + } + else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGB) + { + getPixelColorsRGBA8(out, numpixels, 0, in, mode_in); + } + else + { + unsigned char r = 0, g = 0, b = 0, a = 0; + for(i = 0; i != numpixels; ++i) + { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); + rgba8ToPixel(out, i, mode_out, &tree, r, g, b, a); + } + } + + if(mode_out->colortype == LCT_PALETTE) + { + color_tree_cleanup(&tree); + } + + return 0; /*no error (this function currently never has one, but maybe OOM detection added later.)*/ +} + +#ifdef LODEPNG_COMPILE_ENCODER + +void lodepng_color_profile_init(LodePNGColorProfile* profile) +{ + profile->colored = 0; + profile->key = 0; + profile->alpha = 0; + profile->key_r = profile->key_g = profile->key_b = 0; + profile->numcolors = 0; + profile->bits = 1; +} + +/*function used for debug purposes with C++*/ +/*void printColorProfile(LodePNGColorProfile* p) +{ + std::cout << "colored: " << (int)p->colored << ", "; + std::cout << "key: " << (int)p->key << ", "; + std::cout << "key_r: " << (int)p->key_r << ", "; + std::cout << "key_g: " << (int)p->key_g << ", "; + std::cout << "key_b: " << (int)p->key_b << ", "; + std::cout << "alpha: " << (int)p->alpha << ", "; + std::cout << "numcolors: " << (int)p->numcolors << ", "; + std::cout << "bits: " << (int)p->bits << std::endl; +}*/ + +/*Returns how many bits needed to represent given value (max 8 bit)*/ +static unsigned getValueRequiredBits(unsigned char value) +{ + if(value == 0 || value == 255) return 1; + /*The scaling of 2-bit and 4-bit values uses multiples of 85 and 17*/ + if(value % 17 == 0) return value % 85 == 0 ? 2 : 4; + return 8; +} + +/*profile must already have been inited with mode. +It's ok to set some parameters of profile to done already.*/ +unsigned lodepng_get_color_profile(LodePNGColorProfile* profile, + const unsigned char* in, unsigned w, unsigned h, + const LodePNGColorMode* mode) +{ + unsigned error = 0; + size_t i; + ColorTree tree; + size_t numpixels = w * h; + + unsigned colored_done = lodepng_is_greyscale_type(mode) ? 1 : 0; + unsigned alpha_done = lodepng_can_have_alpha(mode) ? 0 : 1; + unsigned numcolors_done = 0; + unsigned bpp = lodepng_get_bpp(mode); + unsigned bits_done = bpp == 1 ? 1 : 0; + unsigned maxnumcolors = 257; + unsigned sixteen = 0; + if(bpp <= 8) maxnumcolors = bpp == 1 ? 2 : (bpp == 2 ? 4 : (bpp == 4 ? 16 : 256)); + + color_tree_init(&tree); + + /*Check if the 16-bit input is truly 16-bit*/ + if(mode->bitdepth == 16) + { + unsigned short r, g, b, a; + for(i = 0; i != numpixels; ++i) + { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode); + if((r & 255) != ((r >> 8) & 255) || (g & 255) != ((g >> 8) & 255) || + (b & 255) != ((b >> 8) & 255) || (a & 255) != ((a >> 8) & 255)) /*first and second byte differ*/ + { + sixteen = 1; + break; + } + } + } + + if(sixteen) + { + unsigned short r = 0, g = 0, b = 0, a = 0; + profile->bits = 16; + bits_done = numcolors_done = 1; /*counting colors no longer useful, palette doesn't support 16-bit*/ + + for(i = 0; i != numpixels; ++i) + { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode); + + if(!colored_done && (r != g || r != b)) + { + profile->colored = 1; + colored_done = 1; + } + + if(!alpha_done) + { + unsigned matchkey = (r == profile->key_r && g == profile->key_g && b == profile->key_b); + if(a != 65535 && (a != 0 || (profile->key && !matchkey))) + { + profile->alpha = 1; + alpha_done = 1; + if(profile->bits < 8) profile->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + else if(a == 0 && !profile->alpha && !profile->key) + { + profile->key = 1; + profile->key_r = r; + profile->key_g = g; + profile->key_b = b; + } + else if(a == 65535 && profile->key && matchkey) + { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + profile->alpha = 1; + alpha_done = 1; + } + } + + if(alpha_done && numcolors_done && colored_done && bits_done) break; + } + } + else /* < 16-bit */ + { + for(i = 0; i != numpixels; ++i) + { + unsigned char r = 0, g = 0, b = 0, a = 0; + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode); + + if(!bits_done && profile->bits < 8) + { + /*only r is checked, < 8 bits is only relevant for greyscale*/ + unsigned bits = getValueRequiredBits(r); + if(bits > profile->bits) profile->bits = bits; + } + bits_done = (profile->bits >= bpp); + + if(!colored_done && (r != g || r != b)) + { + profile->colored = 1; + colored_done = 1; + if(profile->bits < 8) profile->bits = 8; /*PNG has no colored modes with less than 8-bit per channel*/ + } + + if(!alpha_done) + { + unsigned matchkey = (r == profile->key_r && g == profile->key_g && b == profile->key_b); + if(a != 255 && (a != 0 || (profile->key && !matchkey))) + { + profile->alpha = 1; + alpha_done = 1; + if(profile->bits < 8) profile->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + else if(a == 0 && !profile->alpha && !profile->key) + { + profile->key = 1; + profile->key_r = r; + profile->key_g = g; + profile->key_b = b; + } + else if(a == 255 && profile->key && matchkey) + { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + profile->alpha = 1; + alpha_done = 1; + if(profile->bits < 8) profile->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + } + + if(!numcolors_done) + { + if(!color_tree_has(&tree, r, g, b, a)) + { + color_tree_add(&tree, r, g, b, a, profile->numcolors); + if(profile->numcolors < 256) + { + unsigned char* p = profile->palette; + unsigned n = profile->numcolors; + p[n * 4 + 0] = r; + p[n * 4 + 1] = g; + p[n * 4 + 2] = b; + p[n * 4 + 3] = a; + } + ++profile->numcolors; + numcolors_done = profile->numcolors >= maxnumcolors; + } + } + + if(alpha_done && numcolors_done && colored_done && bits_done) break; + } + + /*make the profile's key always 16-bit for consistency - repeat each byte twice*/ + profile->key_r += (profile->key_r << 8); + profile->key_g += (profile->key_g << 8); + profile->key_b += (profile->key_b << 8); + } + + color_tree_cleanup(&tree); + return error; +} + +/*Automatically chooses color type that gives smallest amount of bits in the +output image, e.g. grey if there are only greyscale pixels, palette if there +are less than 256 colors, ... +Updates values of mode with a potentially smaller color model. mode_out should +contain the user chosen color model, but will be overwritten with the new chosen one.*/ +unsigned lodepng_auto_choose_color(LodePNGColorMode* mode_out, + const unsigned char* image, unsigned w, unsigned h, + const LodePNGColorMode* mode_in) +{ + LodePNGColorProfile prof; + unsigned error = 0; + unsigned i, n, palettebits, grey_ok, palette_ok; + + lodepng_color_profile_init(&prof); + error = lodepng_get_color_profile(&prof, image, w, h, mode_in); + if(error) return error; + mode_out->key_defined = 0; + + if(prof.key && w * h <= 16) + { + prof.alpha = 1; /*too few pixels to justify tRNS chunk overhead*/ + if(prof.bits < 8) prof.bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + grey_ok = !prof.colored && !prof.alpha; /*grey without alpha, with potentially low bits*/ + n = prof.numcolors; + palettebits = n <= 2 ? 1 : (n <= 4 ? 2 : (n <= 16 ? 4 : 8)); + palette_ok = n <= 256 && (n * 2 < w * h) && prof.bits <= 8; + if(w * h < n * 2) palette_ok = 0; /*don't add palette overhead if image has only a few pixels*/ + if(grey_ok && prof.bits <= palettebits) palette_ok = 0; /*grey is less overhead*/ + + if(palette_ok) + { + unsigned char* p = prof.palette; + lodepng_palette_clear(mode_out); /*remove potential earlier palette*/ + for(i = 0; i != prof.numcolors; ++i) + { + error = lodepng_palette_add(mode_out, p[i * 4 + 0], p[i * 4 + 1], p[i * 4 + 2], p[i * 4 + 3]); + if(error) break; + } + + mode_out->colortype = LCT_PALETTE; + mode_out->bitdepth = palettebits; + + if(mode_in->colortype == LCT_PALETTE && mode_in->palettesize >= mode_out->palettesize + && mode_in->bitdepth == mode_out->bitdepth) + { + /*If input should have same palette colors, keep original to preserve its order and prevent conversion*/ + lodepng_color_mode_cleanup(mode_out); + lodepng_color_mode_copy(mode_out, mode_in); + } + } + else /*8-bit or 16-bit per channel*/ + { + mode_out->bitdepth = prof.bits; + mode_out->colortype = prof.alpha ? (prof.colored ? LCT_RGBA : LCT_GREY_ALPHA) + : (prof.colored ? LCT_RGB : LCT_GREY); + + if(prof.key && !prof.alpha) + { + unsigned mask = (1u << mode_out->bitdepth) - 1u; /*profile always uses 16-bit, mask converts it*/ + mode_out->key_r = prof.key_r & mask; + mode_out->key_g = prof.key_g & mask; + mode_out->key_b = prof.key_b & mask; + mode_out->key_defined = 1; + } + } + + return error; +} + +#endif /* #ifdef LODEPNG_COMPILE_ENCODER */ + +/* +Paeth predicter, used by PNG filter type 4 +The parameters are of type short, but should come from unsigned chars, the shorts +are only needed to make the paeth calculation correct. +*/ +static unsigned char paethPredictor(short a, short b, short c) +{ + short pa = abs(b - c); + short pb = abs(a - c); + short pc = abs(a + b - c - c); + + if(pc < pa && pc < pb) return (unsigned char)c; + else if(pb < pa) return (unsigned char)b; + else return (unsigned char)a; +} + +/*shared values used by multiple Adam7 related functions*/ + +static const unsigned ADAM7_IX[7] = { 0, 4, 0, 2, 0, 1, 0 }; /*x start values*/ +static const unsigned ADAM7_IY[7] = { 0, 0, 4, 0, 2, 0, 1 }; /*y start values*/ +static const unsigned ADAM7_DX[7] = { 8, 8, 4, 4, 2, 2, 1 }; /*x delta values*/ +static const unsigned ADAM7_DY[7] = { 8, 8, 8, 4, 4, 2, 2 }; /*y delta values*/ + +/* +Outputs various dimensions and positions in the image related to the Adam7 reduced images. +passw: output containing the width of the 7 passes +passh: output containing the height of the 7 passes +filter_passstart: output containing the index of the start and end of each + reduced image with filter bytes +padded_passstart output containing the index of the start and end of each + reduced image when without filter bytes but with padded scanlines +passstart: output containing the index of the start and end of each reduced + image without padding between scanlines, but still padding between the images +w, h: width and height of non-interlaced image +bpp: bits per pixel +"padded" is only relevant if bpp is less than 8 and a scanline or image does not + end at a full byte +*/ +static void Adam7_getpassvalues(unsigned passw[7], unsigned passh[7], size_t filter_passstart[8], + size_t padded_passstart[8], size_t passstart[8], unsigned w, unsigned h, unsigned bpp) +{ + /*the passstart values have 8 values: the 8th one indicates the byte after the end of the 7th (= last) pass*/ + unsigned i; + + /*calculate width and height in pixels of each pass*/ + for(i = 0; i != 7; ++i) + { + passw[i] = (w + ADAM7_DX[i] - ADAM7_IX[i] - 1) / ADAM7_DX[i]; + passh[i] = (h + ADAM7_DY[i] - ADAM7_IY[i] - 1) / ADAM7_DY[i]; + if(passw[i] == 0) passh[i] = 0; + if(passh[i] == 0) passw[i] = 0; + } + + filter_passstart[0] = padded_passstart[0] = passstart[0] = 0; + for(i = 0; i != 7; ++i) + { + /*if passw[i] is 0, it's 0 bytes, not 1 (no filtertype-byte)*/ + filter_passstart[i + 1] = filter_passstart[i] + + ((passw[i] && passh[i]) ? passh[i] * (1 + (passw[i] * bpp + 7) / 8) : 0); + /*bits padded if needed to fill full byte at end of each scanline*/ + padded_passstart[i + 1] = padded_passstart[i] + passh[i] * ((passw[i] * bpp + 7) / 8); + /*only padded at end of reduced image*/ + passstart[i + 1] = passstart[i] + (passh[i] * passw[i] * bpp + 7) / 8; + } +} + +#ifdef LODEPNG_COMPILE_DECODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG Decoder / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*read the information from the header and store it in the LodePNGInfo. return value is error*/ +unsigned lodepng_inspect(unsigned* w, unsigned* h, LodePNGState* state, + const unsigned char* in, size_t insize) +{ + LodePNGInfo* info = &state->info_png; + if(insize == 0 || in == 0) + { + CERROR_RETURN_ERROR(state->error, 48); /*error: the given data is empty*/ + } + if(insize < 33) + { + CERROR_RETURN_ERROR(state->error, 27); /*error: the data length is smaller than the length of a PNG header*/ + } + + /*when decoding a new PNG image, make sure all parameters created after previous decoding are reset*/ + lodepng_info_cleanup(info); + lodepng_info_init(info); + + if(in[0] != 137 || in[1] != 80 || in[2] != 78 || in[3] != 71 + || in[4] != 13 || in[5] != 10 || in[6] != 26 || in[7] != 10) + { + CERROR_RETURN_ERROR(state->error, 28); /*error: the first 8 bytes are not the correct PNG signature*/ + } + if(in[12] != 'I' || in[13] != 'H' || in[14] != 'D' || in[15] != 'R') + { + CERROR_RETURN_ERROR(state->error, 29); /*error: it doesn't start with a IHDR chunk!*/ + } + + /*read the values given in the header*/ + *w = lodepng_read32bitInt(&in[16]); + *h = lodepng_read32bitInt(&in[20]); + info->color.bitdepth = in[24]; + info->color.colortype = (LodePNGColorType)in[25]; + info->compression_method = in[26]; + info->filter_method = in[27]; + info->interlace_method = in[28]; + + if(*w == 0 || *h == 0) + { + CERROR_RETURN_ERROR(state->error, 93); + } + + if(!state->decoder.ignore_crc) + { + unsigned CRC = lodepng_read32bitInt(&in[29]); + unsigned checksum = lodepng_crc32(&in[12], 17); + if(CRC != checksum) + { + CERROR_RETURN_ERROR(state->error, 57); /*invalid CRC*/ + } + } + + /*error: only compression method 0 is allowed in the specification*/ + if(info->compression_method != 0) CERROR_RETURN_ERROR(state->error, 32); + /*error: only filter method 0 is allowed in the specification*/ + if(info->filter_method != 0) CERROR_RETURN_ERROR(state->error, 33); + /*error: only interlace methods 0 and 1 exist in the specification*/ + if(info->interlace_method > 1) CERROR_RETURN_ERROR(state->error, 34); + + state->error = checkColorValidity(info->color.colortype, info->color.bitdepth); + return state->error; +} + +static unsigned unfilterScanline(unsigned char* recon, const unsigned char* scanline, const unsigned char* precon, + size_t bytewidth, unsigned char filterType, size_t length) +{ + /* + For PNG filter method 0 + unfilter a PNG image scanline by scanline. when the pixels are smaller than 1 byte, + the filter works byte per byte (bytewidth = 1) + precon is the previous unfiltered scanline, recon the result, scanline the current one + the incoming scanlines do NOT include the filtertype byte, that one is given in the parameter filterType instead + recon and scanline MAY be the same memory address! precon must be disjoint. + */ + + size_t i; + switch(filterType) + { + case 0: + for(i = 0; i != length; ++i) recon[i] = scanline[i]; + break; + case 1: + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) recon[i] = scanline[i] + recon[i - bytewidth]; + break; + case 2: + if(precon) + { + for(i = 0; i != length; ++i) recon[i] = scanline[i] + precon[i]; + } + else + { + for(i = 0; i != length; ++i) recon[i] = scanline[i]; + } + break; + case 3: + if(precon) + { + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i] + precon[i] / 2; + for(i = bytewidth; i < length; ++i) recon[i] = scanline[i] + ((recon[i - bytewidth] + precon[i]) / 2); + } + else + { + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) recon[i] = scanline[i] + recon[i - bytewidth] / 2; + } + break; + case 4: + if(precon) + { + for(i = 0; i != bytewidth; ++i) + { + recon[i] = (scanline[i] + precon[i]); /*paethPredictor(0, precon[i], 0) is always precon[i]*/ + } + for(i = bytewidth; i < length; ++i) + { + recon[i] = (scanline[i] + paethPredictor(recon[i - bytewidth], precon[i], precon[i - bytewidth])); + } + } + else + { + for(i = 0; i != bytewidth; ++i) + { + recon[i] = scanline[i]; + } + for(i = bytewidth; i < length; ++i) + { + /*paethPredictor(recon[i - bytewidth], 0, 0) is always recon[i - bytewidth]*/ + recon[i] = (scanline[i] + recon[i - bytewidth]); + } + } + break; + default: return 36; /*error: unexisting filter type given*/ + } + return 0; +} + +static unsigned unfilter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) +{ + /* + For PNG filter method 0 + this function unfilters a single image (e.g. without interlacing this is called once, with Adam7 seven times) + out must have enough bytes allocated already, in must have the scanlines + 1 filtertype byte per scanline + w and h are image dimensions or dimensions of reduced image, bpp is bits per pixel + in and out are allowed to be the same memory address (but aren't the same size since in has the extra filter bytes) + */ + + unsigned y; + unsigned char* prevline = 0; + + /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ + size_t bytewidth = (bpp + 7) / 8; + size_t linebytes = (w * bpp + 7) / 8; + + for(y = 0; y < h; ++y) + { + size_t outindex = linebytes * y; + size_t inindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + unsigned char filterType = in[inindex]; + + CERROR_TRY_RETURN(unfilterScanline(&out[outindex], &in[inindex + 1], prevline, bytewidth, filterType, linebytes)); + + prevline = &out[outindex]; + } + + return 0; +} + +/* +in: Adam7 interlaced image, with no padding bits between scanlines, but between + reduced images so that each reduced image starts at a byte. +out: the same pixels, but re-ordered so that they're now a non-interlaced image with size w*h +bpp: bits per pixel +out has the following size in bits: w * h * bpp. +in is possibly bigger due to padding bits between reduced images. +out must be big enough AND must be 0 everywhere if bpp < 8 in the current implementation +(because that's likely a little bit faster) +NOTE: comments about padding bits are only relevant if bpp < 8 +*/ +static void Adam7_deinterlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) +{ + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + if(bpp >= 8) + { + for(i = 0; i != 7; ++i) + { + unsigned x, y, b; + size_t bytewidth = bpp / 8; + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) + { + size_t pixelinstart = passstart[i] + (y * passw[i] + x) * bytewidth; + size_t pixeloutstart = ((ADAM7_IY[i] + y * ADAM7_DY[i]) * w + ADAM7_IX[i] + x * ADAM7_DX[i]) * bytewidth; + for(b = 0; b < bytewidth; ++b) + { + out[pixeloutstart + b] = in[pixelinstart + b]; + } + } + } + } + else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ + { + for(i = 0; i != 7; ++i) + { + unsigned x, y, b; + unsigned ilinebits = bpp * passw[i]; + unsigned olinebits = bpp * w; + size_t obp, ibp; /*bit pointers (for out and in buffer)*/ + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) + { + ibp = (8 * passstart[i]) + (y * ilinebits + x * bpp); + obp = (ADAM7_IY[i] + y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + x * ADAM7_DX[i]) * bpp; + for(b = 0; b < bpp; ++b) + { + unsigned char bit = readBitFromReversedStream(&ibp, in); + /*note that this function assumes the out buffer is completely 0, use setBitOfReversedStream otherwise*/ + setBitOfReversedStream0(&obp, out, bit); + } + } + } + } +} + +static void removePaddingBits(unsigned char* out, const unsigned char* in, + size_t olinebits, size_t ilinebits, unsigned h) +{ + /* + After filtering there are still padding bits if scanlines have non multiple of 8 bit amounts. They need + to be removed (except at last scanline of (Adam7-reduced) image) before working with pure image buffers + for the Adam7 code, the color convert code and the output to the user. + in and out are allowed to be the same buffer, in may also be higher but still overlapping; in must + have >= ilinebits*h bits, out must have >= olinebits*h bits, olinebits must be <= ilinebits + also used to move bits after earlier such operations happened, e.g. in a sequence of reduced images from Adam7 + only useful if (ilinebits - olinebits) is a value in the range 1..7 + */ + unsigned y; + size_t diff = ilinebits - olinebits; + size_t ibp = 0, obp = 0; /*input and output bit pointers*/ + for(y = 0; y < h; ++y) + { + size_t x; + for(x = 0; x < olinebits; ++x) + { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + ibp += diff; + } +} + +/*out must be buffer big enough to contain full image, and in must contain the full decompressed data from +the IDAT chunks (with filter index bytes and possible padding bits) +return value is error*/ +static unsigned postProcessScanlines(unsigned char* out, unsigned char* in, + unsigned w, unsigned h, const LodePNGInfo* info_png) +{ + /* + This function converts the filtered-padded-interlaced data into pure 2D image buffer with the PNG's colortype. + Steps: + *) if no Adam7: 1) unfilter 2) remove padding bits (= posible extra bits per scanline if bpp < 8) + *) if adam7: 1) 7x unfilter 2) 7x remove padding bits 3) Adam7_deinterlace + NOTE: the in buffer will be overwritten with intermediate data! + */ + unsigned bpp = lodepng_get_bpp(&info_png->color); + if(bpp == 0) return 31; /*error: invalid colortype*/ + + if(info_png->interlace_method == 0) + { + if(bpp < 8 && w * bpp != ((w * bpp + 7) / 8) * 8) + { + CERROR_TRY_RETURN(unfilter(in, in, w, h, bpp)); + removePaddingBits(out, in, w * bpp, ((w * bpp + 7) / 8) * 8, h); + } + /*we can immediatly filter into the out buffer, no other steps needed*/ + else CERROR_TRY_RETURN(unfilter(out, in, w, h, bpp)); + } + else /*interlace_method is 1 (Adam7)*/ + { + unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + for(i = 0; i != 7; ++i) + { + CERROR_TRY_RETURN(unfilter(&in[padded_passstart[i]], &in[filter_passstart[i]], passw[i], passh[i], bpp)); + /*TODO: possible efficiency improvement: if in this reduced image the bits fit nicely in 1 scanline, + move bytes instead of bits or move not at all*/ + if(bpp < 8) + { + /*remove padding bits in scanlines; after this there still may be padding + bits between the different reduced images: each reduced image still starts nicely at a byte*/ + removePaddingBits(&in[passstart[i]], &in[padded_passstart[i]], passw[i] * bpp, + ((passw[i] * bpp + 7) / 8) * 8, passh[i]); + } + } + + Adam7_deinterlace(out, in, w, h, bpp); + } + + return 0; +} + +static unsigned readChunk_PLTE(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) +{ + unsigned pos = 0, i; + if(color->palette) lodepng_free(color->palette); + color->palettesize = chunkLength / 3; + color->palette = (unsigned char*)lodepng_malloc(4 * color->palettesize); + if(!color->palette && color->palettesize) + { + color->palettesize = 0; + return 83; /*alloc fail*/ + } + if(color->palettesize > 256) return 38; /*error: palette too big*/ + + for(i = 0; i != color->palettesize; ++i) + { + color->palette[4 * i + 0] = data[pos++]; /*R*/ + color->palette[4 * i + 1] = data[pos++]; /*G*/ + color->palette[4 * i + 2] = data[pos++]; /*B*/ + color->palette[4 * i + 3] = 255; /*alpha*/ + } + + return 0; /* OK */ +} + +static unsigned readChunk_tRNS(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) +{ + unsigned i; + if(color->colortype == LCT_PALETTE) + { + /*error: more alpha values given than there are palette entries*/ + if(chunkLength > color->palettesize) return 38; + + for(i = 0; i != chunkLength; ++i) color->palette[4 * i + 3] = data[i]; + } + else if(color->colortype == LCT_GREY) + { + /*error: this chunk must be 2 bytes for greyscale image*/ + if(chunkLength != 2) return 30; + + color->key_defined = 1; + color->key_r = color->key_g = color->key_b = 256u * data[0] + data[1]; + } + else if(color->colortype == LCT_RGB) + { + /*error: this chunk must be 6 bytes for RGB image*/ + if(chunkLength != 6) return 41; + + color->key_defined = 1; + color->key_r = 256u * data[0] + data[1]; + color->key_g = 256u * data[2] + data[3]; + color->key_b = 256u * data[4] + data[5]; + } + else return 42; /*error: tRNS chunk not allowed for other color models*/ + + return 0; /* OK */ +} + + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*background color chunk (bKGD)*/ +static unsigned readChunk_bKGD(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) +{ + if(info->color.colortype == LCT_PALETTE) + { + /*error: this chunk must be 1 byte for indexed color image*/ + if(chunkLength != 1) return 43; + + info->background_defined = 1; + info->background_r = info->background_g = info->background_b = data[0]; + } + else if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) + { + /*error: this chunk must be 2 bytes for greyscale image*/ + if(chunkLength != 2) return 44; + + info->background_defined = 1; + info->background_r = info->background_g = info->background_b = 256u * data[0] + data[1]; + } + else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) + { + /*error: this chunk must be 6 bytes for greyscale image*/ + if(chunkLength != 6) return 45; + + info->background_defined = 1; + info->background_r = 256u * data[0] + data[1]; + info->background_g = 256u * data[2] + data[3]; + info->background_b = 256u * data[4] + data[5]; + } + + return 0; /* OK */ +} + +/*text chunk (tEXt)*/ +static unsigned readChunk_tEXt(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) +{ + unsigned error = 0; + char *key = 0, *str = 0; + unsigned i; + + while(!error) /*not really a while loop, only used to break on error*/ + { + unsigned length, string2_begin; + + length = 0; + while(length < chunkLength && data[length] != 0) ++length; + /*even though it's not allowed by the standard, no error is thrown if + there's no null termination char, if the text is empty*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + key[length] = 0; + for(i = 0; i != length; ++i) key[i] = (char)data[i]; + + string2_begin = length + 1; /*skip keyword null terminator*/ + + length = chunkLength < string2_begin ? 0 : chunkLength - string2_begin; + str = (char*)lodepng_malloc(length + 1); + if(!str) CERROR_BREAK(error, 83); /*alloc fail*/ + + str[length] = 0; + for(i = 0; i != length; ++i) str[i] = (char)data[string2_begin + i]; + + error = lodepng_add_text(info, key, str); + + break; + } + + lodepng_free(key); + lodepng_free(str); + + return error; +} + +/*compressed text chunk (zTXt)*/ +static unsigned readChunk_zTXt(LodePNGInfo* info, const LodePNGDecompressSettings* zlibsettings, + const unsigned char* data, size_t chunkLength) +{ + unsigned error = 0; + unsigned i; + + unsigned length, string2_begin; + char *key = 0; + ucvector decoded; + + ucvector_init(&decoded); + + while(!error) /*not really a while loop, only used to break on error*/ + { + for(length = 0; length < chunkLength && data[length] != 0; ++length) ; + if(length + 2 >= chunkLength) CERROR_BREAK(error, 75); /*no null termination, corrupt?*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + key[length] = 0; + for(i = 0; i != length; ++i) key[i] = (char)data[i]; + + if(data[length + 1] != 0) CERROR_BREAK(error, 72); /*the 0 byte indicating compression must be 0*/ + + string2_begin = length + 2; + if(string2_begin > chunkLength) CERROR_BREAK(error, 75); /*no null termination, corrupt?*/ + + length = chunkLength - string2_begin; + /*will fail if zlib error, e.g. if length is too small*/ + error = zlib_decompress(&decoded.data, &decoded.size, + (unsigned char*)(&data[string2_begin]), + length, zlibsettings); + if(error) break; + ucvector_push_back(&decoded, 0); + + error = lodepng_add_text(info, key, (char*)decoded.data); + + break; + } + + lodepng_free(key); + ucvector_cleanup(&decoded); + + return error; +} + +/*international text chunk (iTXt)*/ +static unsigned readChunk_iTXt(LodePNGInfo* info, const LodePNGDecompressSettings* zlibsettings, + const unsigned char* data, size_t chunkLength) +{ + unsigned error = 0; + unsigned i; + + unsigned length, begin, compressed; + char *key = 0, *langtag = 0, *transkey = 0; + ucvector decoded; + ucvector_init(&decoded); + + while(!error) /*not really a while loop, only used to break on error*/ + { + /*Quick check if the chunk length isn't too small. Even without check + it'd still fail with other error checks below if it's too short. This just gives a different error code.*/ + if(chunkLength < 5) CERROR_BREAK(error, 30); /*iTXt chunk too short*/ + + /*read the key*/ + for(length = 0; length < chunkLength && data[length] != 0; ++length) ; + if(length + 3 >= chunkLength) CERROR_BREAK(error, 75); /*no null termination char, corrupt?*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + key[length] = 0; + for(i = 0; i != length; ++i) key[i] = (char)data[i]; + + /*read the compression method*/ + compressed = data[length + 1]; + if(data[length + 2] != 0) CERROR_BREAK(error, 72); /*the 0 byte indicating compression must be 0*/ + + /*even though it's not allowed by the standard, no error is thrown if + there's no null termination char, if the text is empty for the next 3 texts*/ + + /*read the langtag*/ + begin = length + 3; + length = 0; + for(i = begin; i < chunkLength && data[i] != 0; ++i) ++length; + + langtag = (char*)lodepng_malloc(length + 1); + if(!langtag) CERROR_BREAK(error, 83); /*alloc fail*/ + + langtag[length] = 0; + for(i = 0; i != length; ++i) langtag[i] = (char)data[begin + i]; + + /*read the transkey*/ + begin += length + 1; + length = 0; + for(i = begin; i < chunkLength && data[i] != 0; ++i) ++length; + + transkey = (char*)lodepng_malloc(length + 1); + if(!transkey) CERROR_BREAK(error, 83); /*alloc fail*/ + + transkey[length] = 0; + for(i = 0; i != length; ++i) transkey[i] = (char)data[begin + i]; + + /*read the actual text*/ + begin += length + 1; + + length = chunkLength < begin ? 0 : chunkLength - begin; + + if(compressed) + { + /*will fail if zlib error, e.g. if length is too small*/ + error = zlib_decompress(&decoded.data, &decoded.size, + (unsigned char*)(&data[begin]), + length, zlibsettings); + if(error) break; + if(decoded.allocsize < decoded.size) decoded.allocsize = decoded.size; + ucvector_push_back(&decoded, 0); + } + else + { + if(!ucvector_resize(&decoded, length + 1)) CERROR_BREAK(error, 83 /*alloc fail*/); + + decoded.data[length] = 0; + for(i = 0; i != length; ++i) decoded.data[i] = data[begin + i]; + } + + error = lodepng_add_itext(info, key, langtag, transkey, (char*)decoded.data); + + break; + } + + lodepng_free(key); + lodepng_free(langtag); + lodepng_free(transkey); + ucvector_cleanup(&decoded); + + return error; +} + +static unsigned readChunk_tIME(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) +{ + if(chunkLength != 7) return 73; /*invalid tIME chunk size*/ + + info->time_defined = 1; + info->time.year = 256u * data[0] + data[1]; + info->time.month = data[2]; + info->time.day = data[3]; + info->time.hour = data[4]; + info->time.minute = data[5]; + info->time.second = data[6]; + + return 0; /* OK */ +} + +static unsigned readChunk_pHYs(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) +{ + if(chunkLength != 9) return 74; /*invalid pHYs chunk size*/ + + info->phys_defined = 1; + info->phys_x = 16777216u * data[0] + 65536u * data[1] + 256u * data[2] + data[3]; + info->phys_y = 16777216u * data[4] + 65536u * data[5] + 256u * data[6] + data[7]; + info->phys_unit = data[8]; + + return 0; /* OK */ +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/*read a PNG, the result will be in the same color type as the PNG (hence "generic")*/ +static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize) +{ + unsigned char IEND = 0; + const unsigned char* chunk; + size_t i; + ucvector idat; /*the data from idat chunks*/ + ucvector scanlines; + size_t predict; + size_t numpixels; + + /*for unknown chunk order*/ + unsigned unknown = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + unsigned critical_pos = 1; /*1 = after IHDR, 2 = after PLTE, 3 = after IDAT*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + + /*provide some proper output values if error will happen*/ + *out = 0; + + state->error = lodepng_inspect(w, h, state, in, insize); /*reads header and resets other parameters in state->info_png*/ + if(state->error) return; + + numpixels = *w * *h; + + /*multiplication overflow*/ + if(*h != 0 && numpixels / *h != *w) CERROR_RETURN(state->error, 92); + /*multiplication overflow possible further below. Allows up to 2^31-1 pixel + bytes with 16-bit RGBA, the rest is room for filter bytes.*/ + if(numpixels > 268435455) CERROR_RETURN(state->error, 92); + + ucvector_init(&idat); + chunk = &in[33]; /*first byte of the first chunk after the header*/ + + /*loop through the chunks, ignoring unknown chunks and stopping at IEND chunk. + IDAT data is put at the start of the in buffer*/ + while(!IEND && !state->error) + { + unsigned chunkLength; + const unsigned char* data; /*the data in the chunk*/ + + /*error: size of the in buffer too small to contain next chunk*/ + if((size_t)((chunk - in) + 12) > insize || chunk < in) CERROR_BREAK(state->error, 30); + + /*length of the data of the chunk, excluding the length bytes, chunk type and CRC bytes*/ + chunkLength = lodepng_chunk_length(chunk); + /*error: chunk length larger than the max PNG chunk size*/ + if(chunkLength > 2147483647) CERROR_BREAK(state->error, 63); + + if((size_t)((chunk - in) + chunkLength + 12) > insize || (chunk + chunkLength + 12) < in) + { + CERROR_BREAK(state->error, 64); /*error: size of the in buffer too small to contain next chunk*/ + } + + data = lodepng_chunk_data_const(chunk); + + /*IDAT chunk, containing compressed image data*/ + if(lodepng_chunk_type_equals(chunk, "IDAT")) + { + size_t oldsize = idat.size; + if(!ucvector_resize(&idat, oldsize + chunkLength)) CERROR_BREAK(state->error, 83 /*alloc fail*/); + for(i = 0; i != chunkLength; ++i) idat.data[oldsize + i] = data[i]; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + critical_pos = 3; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } + /*IEND chunk*/ + else if(lodepng_chunk_type_equals(chunk, "IEND")) + { + IEND = 1; + } + /*palette chunk (PLTE)*/ + else if(lodepng_chunk_type_equals(chunk, "PLTE")) + { + state->error = readChunk_PLTE(&state->info_png.color, data, chunkLength); + if(state->error) break; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + critical_pos = 2; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } + /*palette transparency chunk (tRNS)*/ + else if(lodepng_chunk_type_equals(chunk, "tRNS")) + { + state->error = readChunk_tRNS(&state->info_png.color, data, chunkLength); + if(state->error) break; + } +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*background color chunk (bKGD)*/ + else if(lodepng_chunk_type_equals(chunk, "bKGD")) + { + state->error = readChunk_bKGD(&state->info_png, data, chunkLength); + if(state->error) break; + } + /*text chunk (tEXt)*/ + else if(lodepng_chunk_type_equals(chunk, "tEXt")) + { + if(state->decoder.read_text_chunks) + { + state->error = readChunk_tEXt(&state->info_png, data, chunkLength); + if(state->error) break; + } + } + /*compressed text chunk (zTXt)*/ + else if(lodepng_chunk_type_equals(chunk, "zTXt")) + { + if(state->decoder.read_text_chunks) + { + state->error = readChunk_zTXt(&state->info_png, &state->decoder.zlibsettings, data, chunkLength); + if(state->error) break; + } + } + /*international text chunk (iTXt)*/ + else if(lodepng_chunk_type_equals(chunk, "iTXt")) + { + if(state->decoder.read_text_chunks) + { + state->error = readChunk_iTXt(&state->info_png, &state->decoder.zlibsettings, data, chunkLength); + if(state->error) break; + } + } + else if(lodepng_chunk_type_equals(chunk, "tIME")) + { + state->error = readChunk_tIME(&state->info_png, data, chunkLength); + if(state->error) break; + } + else if(lodepng_chunk_type_equals(chunk, "pHYs")) + { + state->error = readChunk_pHYs(&state->info_png, data, chunkLength); + if(state->error) break; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + else /*it's not an implemented chunk type, so ignore it: skip over the data*/ + { + /*error: unknown critical chunk (5th bit of first byte of chunk type is 0)*/ + if(!lodepng_chunk_ancillary(chunk)) CERROR_BREAK(state->error, 69); + + unknown = 1; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(state->decoder.remember_unknown_chunks) + { + state->error = lodepng_chunk_append(&state->info_png.unknown_chunks_data[critical_pos - 1], + &state->info_png.unknown_chunks_size[critical_pos - 1], chunk); + if(state->error) break; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } + + if(!state->decoder.ignore_crc && !unknown) /*check CRC if wanted, only on known chunk types*/ + { + if(lodepng_chunk_check_crc(chunk)) CERROR_BREAK(state->error, 57); /*invalid CRC*/ + } + + if(!IEND) chunk = lodepng_chunk_next_const(chunk); + } + + ucvector_init(&scanlines); + /*predict output size, to allocate exact size for output buffer to avoid more dynamic allocation. + If the decompressed size does not match the prediction, the image must be corrupt.*/ + if(state->info_png.interlace_method == 0) + { + /*The extra *h is added because this are the filter bytes every scanline starts with*/ + predict = lodepng_get_raw_size_idat(*w, *h, &state->info_png.color) + *h; + } + else + { + /*Adam-7 interlaced: predicted size is the sum of the 7 sub-images sizes*/ + const LodePNGColorMode* color = &state->info_png.color; + predict = 0; + predict += lodepng_get_raw_size_idat((*w + 7) / 8, (*h + 7) / 8, color) + (*h + 7) / 8; + if(*w > 4) predict += lodepng_get_raw_size_idat((*w + 3) / 8, (*h + 7) / 8, color) + (*h + 7) / 8; + predict += lodepng_get_raw_size_idat((*w + 3) / 4, (*h + 3) / 8, color) + (*h + 3) / 8; + if(*w > 2) predict += lodepng_get_raw_size_idat((*w + 1) / 4, (*h + 3) / 4, color) + (*h + 3) / 4; + predict += lodepng_get_raw_size_idat((*w + 1) / 2, (*h + 1) / 4, color) + (*h + 1) / 4; + if(*w > 1) predict += lodepng_get_raw_size_idat((*w + 0) / 2, (*h + 1) / 2, color) + (*h + 1) / 2; + predict += lodepng_get_raw_size_idat((*w + 0) / 1, (*h + 0) / 2, color) + (*h + 0) / 2; + } + if(!state->error && !ucvector_reserve(&scanlines, predict)) state->error = 83; /*alloc fail*/ + if(!state->error) + { + state->error = zlib_decompress(&scanlines.data, &scanlines.size, idat.data, + idat.size, &state->decoder.zlibsettings); + if(!state->error && scanlines.size != predict) state->error = 91; /*decompressed size doesn't match prediction*/ + } + ucvector_cleanup(&idat); + + if(!state->error) + { + size_t outsize = lodepng_get_raw_size(*w, *h, &state->info_png.color); + ucvector outv; + ucvector_init(&outv); + if(!ucvector_resizev(&outv, outsize, 0)) state->error = 83; /*alloc fail*/ + if(!state->error) state->error = postProcessScanlines(outv.data, scanlines.data, *w, *h, &state->info_png); + *out = outv.data; + } + ucvector_cleanup(&scanlines); +} + +unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize) +{ + *out = 0; + decodeGeneric(out, w, h, state, in, insize); + if(state->error) return state->error; + if(!state->decoder.color_convert || lodepng_color_mode_equal(&state->info_raw, &state->info_png.color)) + { + /*same color type, no copying or converting of data needed*/ + /*store the info_png color settings on the info_raw so that the info_raw still reflects what colortype + the raw image has to the end user*/ + if(!state->decoder.color_convert) + { + state->error = lodepng_color_mode_copy(&state->info_raw, &state->info_png.color); + if(state->error) return state->error; + } + } + else + { + /*color conversion needed; sort of copy of the data*/ + unsigned char* data = *out; + size_t outsize; + + /*TODO: check if this works according to the statement in the documentation: "The converter can convert + from greyscale input color type, to 8-bit greyscale or greyscale with alpha"*/ + if(!(state->info_raw.colortype == LCT_RGB || state->info_raw.colortype == LCT_RGBA) + && !(state->info_raw.bitdepth == 8)) + { + return 56; /*unsupported color mode conversion*/ + } + + outsize = lodepng_get_raw_size(*w, *h, &state->info_raw); + *out = (unsigned char*)lodepng_malloc(outsize); + if(!(*out)) + { + state->error = 83; /*alloc fail*/ + } + else state->error = lodepng_convert(*out, data, &state->info_raw, + &state->info_png.color, *w, *h); + lodepng_free(data); + } + return state->error; +} + +unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, + size_t insize, LodePNGColorType colortype, unsigned bitdepth) +{ + unsigned error; + LodePNGState state; + lodepng_state_init(&state); + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; + error = lodepng_decode(out, w, h, &state, in, insize); + lodepng_state_cleanup(&state); + return error; +} + +unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) +{ + return lodepng_decode_memory(out, w, h, in, insize, LCT_RGBA, 8); +} + +unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) +{ + return lodepng_decode_memory(out, w, h, in, insize, LCT_RGB, 8); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename, + LodePNGColorType colortype, unsigned bitdepth) +{ + unsigned char* buffer; + size_t buffersize; + unsigned error; + error = lodepng_load_file(&buffer, &buffersize, filename); + if(!error) error = lodepng_decode_memory(out, w, h, buffer, buffersize, colortype, bitdepth); + lodepng_free(buffer); + return error; +} + +unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) +{ + return lodepng_decode_file(out, w, h, filename, LCT_RGBA, 8); +} + +unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) +{ + return lodepng_decode_file(out, w, h, filename, LCT_RGB, 8); +} +#endif /*LODEPNG_COMPILE_DISK*/ + +void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings) +{ + settings->color_convert = 1; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + settings->read_text_chunks = 1; + settings->remember_unknown_chunks = 0; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + settings->ignore_crc = 0; + lodepng_decompress_settings_init(&settings->zlibsettings); +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) + +void lodepng_state_init(LodePNGState* state) +{ +#ifdef LODEPNG_COMPILE_DECODER + lodepng_decoder_settings_init(&state->decoder); +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER + lodepng_encoder_settings_init(&state->encoder); +#endif /*LODEPNG_COMPILE_ENCODER*/ + lodepng_color_mode_init(&state->info_raw); + lodepng_info_init(&state->info_png); + state->error = 1; +} + +void lodepng_state_cleanup(LodePNGState* state) +{ + lodepng_color_mode_cleanup(&state->info_raw); + lodepng_info_cleanup(&state->info_png); +} + +void lodepng_state_copy(LodePNGState* dest, const LodePNGState* source) +{ + lodepng_state_cleanup(dest); + *dest = *source; + lodepng_color_mode_init(&dest->info_raw); + lodepng_info_init(&dest->info_png); + dest->error = lodepng_color_mode_copy(&dest->info_raw, &source->info_raw); if(dest->error) return; + dest->error = lodepng_info_copy(&dest->info_png, &source->info_png); if(dest->error) return; +} + +#endif /* defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) */ + +#ifdef LODEPNG_COMPILE_ENCODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG Encoder / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*chunkName must be string of 4 characters*/ +static unsigned addChunk(ucvector* out, const char* chunkName, const unsigned char* data, size_t length) +{ + CERROR_TRY_RETURN(lodepng_chunk_create(&out->data, &out->size, (unsigned)length, chunkName, data)); + out->allocsize = out->size; /*fix the allocsize again*/ + return 0; +} + +static void writeSignature(ucvector* out) +{ + /*8 bytes PNG signature, aka the magic bytes*/ + ucvector_push_back(out, 137); + ucvector_push_back(out, 80); + ucvector_push_back(out, 78); + ucvector_push_back(out, 71); + ucvector_push_back(out, 13); + ucvector_push_back(out, 10); + ucvector_push_back(out, 26); + ucvector_push_back(out, 10); +} + +static unsigned addChunk_IHDR(ucvector* out, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth, unsigned interlace_method) +{ + unsigned error = 0; + ucvector header; + ucvector_init(&header); + + lodepng_add32bitInt(&header, w); /*width*/ + lodepng_add32bitInt(&header, h); /*height*/ + ucvector_push_back(&header, (unsigned char)bitdepth); /*bit depth*/ + ucvector_push_back(&header, (unsigned char)colortype); /*color type*/ + ucvector_push_back(&header, 0); /*compression method*/ + ucvector_push_back(&header, 0); /*filter method*/ + ucvector_push_back(&header, interlace_method); /*interlace method*/ + + error = addChunk(out, "IHDR", header.data, header.size); + ucvector_cleanup(&header); + + return error; +} + +static unsigned addChunk_PLTE(ucvector* out, const LodePNGColorMode* info) +{ + unsigned error = 0; + size_t i; + ucvector PLTE; + ucvector_init(&PLTE); + for(i = 0; i != info->palettesize * 4; ++i) + { + /*add all channels except alpha channel*/ + if(i % 4 != 3) ucvector_push_back(&PLTE, info->palette[i]); + } + error = addChunk(out, "PLTE", PLTE.data, PLTE.size); + ucvector_cleanup(&PLTE); + + return error; +} + +static unsigned addChunk_tRNS(ucvector* out, const LodePNGColorMode* info) +{ + unsigned error = 0; + size_t i; + ucvector tRNS; + ucvector_init(&tRNS); + if(info->colortype == LCT_PALETTE) + { + size_t amount = info->palettesize; + /*the tail of palette values that all have 255 as alpha, does not have to be encoded*/ + for(i = info->palettesize; i != 0; --i) + { + if(info->palette[4 * (i - 1) + 3] == 255) --amount; + else break; + } + /*add only alpha channel*/ + for(i = 0; i != amount; ++i) ucvector_push_back(&tRNS, info->palette[4 * i + 3]); + } + else if(info->colortype == LCT_GREY) + { + if(info->key_defined) + { + ucvector_push_back(&tRNS, (unsigned char)(info->key_r / 256)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_r % 256)); + } + } + else if(info->colortype == LCT_RGB) + { + if(info->key_defined) + { + ucvector_push_back(&tRNS, (unsigned char)(info->key_r / 256)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_r % 256)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_g / 256)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_g % 256)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_b / 256)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_b % 256)); + } + } + + error = addChunk(out, "tRNS", tRNS.data, tRNS.size); + ucvector_cleanup(&tRNS); + + return error; +} + +static unsigned addChunk_IDAT(ucvector* out, const unsigned char* data, size_t datasize, + LodePNGCompressSettings* zlibsettings) +{ + ucvector zlibdata; + unsigned error = 0; + + /*compress with the Zlib compressor*/ + ucvector_init(&zlibdata); + error = zlib_compress(&zlibdata.data, &zlibdata.size, data, datasize, zlibsettings); + if(!error) error = addChunk(out, "IDAT", zlibdata.data, zlibdata.size); + ucvector_cleanup(&zlibdata); + + return error; +} + +static unsigned addChunk_IEND(ucvector* out) +{ + unsigned error = 0; + error = addChunk(out, "IEND", 0, 0); + return error; +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +static unsigned addChunk_tEXt(ucvector* out, const char* keyword, const char* textstring) +{ + unsigned error = 0; + size_t i; + ucvector text; + ucvector_init(&text); + for(i = 0; keyword[i] != 0; ++i) ucvector_push_back(&text, (unsigned char)keyword[i]); + if(i < 1 || i > 79) return 89; /*error: invalid keyword size*/ + ucvector_push_back(&text, 0); /*0 termination char*/ + for(i = 0; textstring[i] != 0; ++i) ucvector_push_back(&text, (unsigned char)textstring[i]); + error = addChunk(out, "tEXt", text.data, text.size); + ucvector_cleanup(&text); + + return error; +} + +static unsigned addChunk_zTXt(ucvector* out, const char* keyword, const char* textstring, + LodePNGCompressSettings* zlibsettings) +{ + unsigned error = 0; + ucvector data, compressed; + size_t i, textsize = strlen(textstring); + + ucvector_init(&data); + ucvector_init(&compressed); + for(i = 0; keyword[i] != 0; ++i) ucvector_push_back(&data, (unsigned char)keyword[i]); + if(i < 1 || i > 79) return 89; /*error: invalid keyword size*/ + ucvector_push_back(&data, 0); /*0 termination char*/ + ucvector_push_back(&data, 0); /*compression method: 0*/ + + error = zlib_compress(&compressed.data, &compressed.size, + (unsigned char*)textstring, textsize, zlibsettings); + if(!error) + { + for(i = 0; i != compressed.size; ++i) ucvector_push_back(&data, compressed.data[i]); + error = addChunk(out, "zTXt", data.data, data.size); + } + + ucvector_cleanup(&compressed); + ucvector_cleanup(&data); + return error; +} + +static unsigned addChunk_iTXt(ucvector* out, unsigned compressed, const char* keyword, const char* langtag, + const char* transkey, const char* textstring, LodePNGCompressSettings* zlibsettings) +{ + unsigned error = 0; + ucvector data; + size_t i, textsize = strlen(textstring); + + ucvector_init(&data); + + for(i = 0; keyword[i] != 0; ++i) ucvector_push_back(&data, (unsigned char)keyword[i]); + if(i < 1 || i > 79) return 89; /*error: invalid keyword size*/ + ucvector_push_back(&data, 0); /*null termination char*/ + ucvector_push_back(&data, compressed ? 1 : 0); /*compression flag*/ + ucvector_push_back(&data, 0); /*compression method*/ + for(i = 0; langtag[i] != 0; ++i) ucvector_push_back(&data, (unsigned char)langtag[i]); + ucvector_push_back(&data, 0); /*null termination char*/ + for(i = 0; transkey[i] != 0; ++i) ucvector_push_back(&data, (unsigned char)transkey[i]); + ucvector_push_back(&data, 0); /*null termination char*/ + + if(compressed) + { + ucvector compressed_data; + ucvector_init(&compressed_data); + error = zlib_compress(&compressed_data.data, &compressed_data.size, + (unsigned char*)textstring, textsize, zlibsettings); + if(!error) + { + for(i = 0; i != compressed_data.size; ++i) ucvector_push_back(&data, compressed_data.data[i]); + } + ucvector_cleanup(&compressed_data); + } + else /*not compressed*/ + { + for(i = 0; textstring[i] != 0; ++i) ucvector_push_back(&data, (unsigned char)textstring[i]); + } + + if(!error) error = addChunk(out, "iTXt", data.data, data.size); + ucvector_cleanup(&data); + return error; +} + +static unsigned addChunk_bKGD(ucvector* out, const LodePNGInfo* info) +{ + unsigned error = 0; + ucvector bKGD; + ucvector_init(&bKGD); + if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) + { + ucvector_push_back(&bKGD, (unsigned char)(info->background_r / 256)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_r % 256)); + } + else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) + { + ucvector_push_back(&bKGD, (unsigned char)(info->background_r / 256)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_r % 256)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_g / 256)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_g % 256)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_b / 256)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_b % 256)); + } + else if(info->color.colortype == LCT_PALETTE) + { + ucvector_push_back(&bKGD, (unsigned char)(info->background_r % 256)); /*palette index*/ + } + + error = addChunk(out, "bKGD", bKGD.data, bKGD.size); + ucvector_cleanup(&bKGD); + + return error; +} + +static unsigned addChunk_tIME(ucvector* out, const LodePNGTime* time) +{ + unsigned error = 0; + unsigned char* data = (unsigned char*)lodepng_malloc(7); + if(!data) return 83; /*alloc fail*/ + data[0] = (unsigned char)(time->year / 256); + data[1] = (unsigned char)(time->year % 256); + data[2] = (unsigned char)time->month; + data[3] = (unsigned char)time->day; + data[4] = (unsigned char)time->hour; + data[5] = (unsigned char)time->minute; + data[6] = (unsigned char)time->second; + error = addChunk(out, "tIME", data, 7); + lodepng_free(data); + return error; +} + +static unsigned addChunk_pHYs(ucvector* out, const LodePNGInfo* info) +{ + unsigned error = 0; + ucvector data; + ucvector_init(&data); + + lodepng_add32bitInt(&data, info->phys_x); + lodepng_add32bitInt(&data, info->phys_y); + ucvector_push_back(&data, info->phys_unit); + + error = addChunk(out, "pHYs", data.data, data.size); + ucvector_cleanup(&data); + + return error; +} + +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +static void filterScanline(unsigned char* out, const unsigned char* scanline, const unsigned char* prevline, + size_t length, size_t bytewidth, unsigned char filterType) +{ + size_t i; + switch(filterType) + { + case 0: /*None*/ + for(i = 0; i != length; ++i) out[i] = scanline[i]; + break; + case 1: /*Sub*/ + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - scanline[i - bytewidth]; + break; + case 2: /*Up*/ + if(prevline) + { + for(i = 0; i != length; ++i) out[i] = scanline[i] - prevline[i]; + } + else + { + for(i = 0; i != length; ++i) out[i] = scanline[i]; + } + break; + case 3: /*Average*/ + if(prevline) + { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i] - prevline[i] / 2; + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - ((scanline[i - bytewidth] + prevline[i]) / 2); + } + else + { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - scanline[i - bytewidth] / 2; + } + break; + case 4: /*Paeth*/ + if(prevline) + { + /*paethPredictor(0, prevline[i], 0) is always prevline[i]*/ + for(i = 0; i != bytewidth; ++i) out[i] = (scanline[i] - prevline[i]); + for(i = bytewidth; i < length; ++i) + { + out[i] = (scanline[i] - paethPredictor(scanline[i - bytewidth], prevline[i], prevline[i - bytewidth])); + } + } + else + { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + /*paethPredictor(scanline[i - bytewidth], 0, 0) is always scanline[i - bytewidth]*/ + for(i = bytewidth; i < length; ++i) out[i] = (scanline[i] - scanline[i - bytewidth]); + } + break; + default: return; /*unexisting filter type given*/ + } +} + +/* log2 approximation. A slight bit faster than std::log. */ +static float flog2(float f) +{ + float result = 0; + while(f > 32) { result += 4; f /= 16; } + while(f > 2) { ++result; f /= 2; } + return result + 1.442695f * (f * f * f / 3 - 3 * f * f / 2 + 3 * f - 1.83333f); +} + +static unsigned filter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, + const LodePNGColorMode* info, const LodePNGEncoderSettings* settings) +{ + /* + For PNG filter method 0 + out must be a buffer with as size: h + (w * h * bpp + 7) / 8, because there are + the scanlines with 1 extra byte per scanline + */ + + unsigned bpp = lodepng_get_bpp(info); + /*the width of a scanline in bytes, not including the filter type*/ + size_t linebytes = (w * bpp + 7) / 8; + /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ + size_t bytewidth = (bpp + 7) / 8; + const unsigned char* prevline = 0; + unsigned x, y; + unsigned error = 0; + LodePNGFilterStrategy strategy = settings->filter_strategy; + + /* + There is a heuristic called the minimum sum of absolute differences heuristic, suggested by the PNG standard: + * If the image type is Palette, or the bit depth is smaller than 8, then do not filter the image (i.e. + use fixed filtering, with the filter None). + * (The other case) If the image type is Grayscale or RGB (with or without Alpha), and the bit depth is + not smaller than 8, then use adaptive filtering heuristic as follows: independently for each row, apply + all five filters and select the filter that produces the smallest sum of absolute values per row. + This heuristic is used if filter strategy is LFS_MINSUM and filter_palette_zero is true. + + If filter_palette_zero is true and filter_strategy is not LFS_MINSUM, the above heuristic is followed, + but for "the other case", whatever strategy filter_strategy is set to instead of the minimum sum + heuristic is used. + */ + if(settings->filter_palette_zero && + (info->colortype == LCT_PALETTE || info->bitdepth < 8)) strategy = LFS_ZERO; + + if(bpp == 0) return 31; /*error: invalid color type*/ + + if(strategy == LFS_ZERO) + { + for(y = 0; y != h; ++y) + { + size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + size_t inindex = linebytes * y; + out[outindex] = 0; /*filter type byte*/ + filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, 0); + prevline = &in[inindex]; + } + } + else if(strategy == LFS_MINSUM) + { + /*adaptive filtering*/ + size_t sum[5]; + ucvector attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t smallest = 0; + unsigned char type, bestType = 0; + + for(type = 0; type != 5; ++type) + { + ucvector_init(&attempt[type]); + if(!ucvector_resize(&attempt[type], linebytes)) return 83; /*alloc fail*/ + } + + if(!error) + { + for(y = 0; y != h; ++y) + { + /*try the 5 filter types*/ + for(type = 0; type != 5; ++type) + { + filterScanline(attempt[type].data, &in[y * linebytes], prevline, linebytes, bytewidth, type); + + /*calculate the sum of the result*/ + sum[type] = 0; + if(type == 0) + { + for(x = 0; x != linebytes; ++x) sum[type] += (unsigned char)(attempt[type].data[x]); + } + else + { + for(x = 0; x != linebytes; ++x) + { + /*For differences, each byte should be treated as signed, values above 127 are negative + (converted to signed char). Filtertype 0 isn't a difference though, so use unsigned there. + This means filtertype 0 is almost never chosen, but that is justified.*/ + unsigned char s = attempt[type].data[x]; + sum[type] += s < 128 ? s : (255U - s); + } + } + + /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || sum[type] < smallest) + { + bestType = type; + smallest = sum[type]; + } + } + + prevline = &in[y * linebytes]; + + /*now fill the out values*/ + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType].data[x]; + } + } + + for(type = 0; type != 5; ++type) ucvector_cleanup(&attempt[type]); + } + else if(strategy == LFS_ENTROPY) + { + float sum[5]; + ucvector attempt[5]; /*five filtering attempts, one for each filter type*/ + float smallest = 0; + unsigned type, bestType = 0; + unsigned count[256]; + + for(type = 0; type != 5; ++type) + { + ucvector_init(&attempt[type]); + if(!ucvector_resize(&attempt[type], linebytes)) return 83; /*alloc fail*/ + } + + for(y = 0; y != h; ++y) + { + /*try the 5 filter types*/ + for(type = 0; type != 5; ++type) + { + filterScanline(attempt[type].data, &in[y * linebytes], prevline, linebytes, bytewidth, type); + for(x = 0; x != 256; ++x) count[x] = 0; + for(x = 0; x != linebytes; ++x) ++count[attempt[type].data[x]]; + ++count[type]; /*the filter type itself is part of the scanline*/ + sum[type] = 0; + for(x = 0; x != 256; ++x) + { + float p = count[x] / (float)(linebytes + 1); + sum[type] += count[x] == 0 ? 0 : flog2(1 / p) * p; + } + /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || sum[type] < smallest) + { + bestType = type; + smallest = sum[type]; + } + } + + prevline = &in[y * linebytes]; + + /*now fill the out values*/ + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType].data[x]; + } + + for(type = 0; type != 5; ++type) ucvector_cleanup(&attempt[type]); + } + else if(strategy == LFS_PREDEFINED) + { + for(y = 0; y != h; ++y) + { + size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + size_t inindex = linebytes * y; + unsigned char type = settings->predefined_filters[y]; + out[outindex] = type; /*filter type byte*/ + filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, type); + prevline = &in[inindex]; + } + } + else if(strategy == LFS_BRUTE_FORCE) + { + /*brute force filter chooser. + deflate the scanline after every filter attempt to see which one deflates best. + This is very slow and gives only slightly smaller, sometimes even larger, result*/ + size_t size[5]; + ucvector attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t smallest = 0; + unsigned type = 0, bestType = 0; + unsigned char* dummy; + LodePNGCompressSettings zlibsettings = settings->zlibsettings; + /*use fixed tree on the attempts so that the tree is not adapted to the filtertype on purpose, + to simulate the true case where the tree is the same for the whole image. Sometimes it gives + better result with dynamic tree anyway. Using the fixed tree sometimes gives worse, but in rare + cases better compression. It does make this a bit less slow, so it's worth doing this.*/ + zlibsettings.btype = 1; + /*a custom encoder likely doesn't read the btype setting and is optimized for complete PNG + images only, so disable it*/ + zlibsettings.custom_zlib = 0; + zlibsettings.custom_deflate = 0; + for(type = 0; type != 5; ++type) + { + ucvector_init(&attempt[type]); + ucvector_resize(&attempt[type], linebytes); /*todo: give error if resize failed*/ + } + for(y = 0; y != h; ++y) /*try the 5 filter types*/ + { + for(type = 0; type != 5; ++type) + { + unsigned testsize = attempt[type].size; + /*if(testsize > 8) testsize /= 8;*/ /*it already works good enough by testing a part of the row*/ + + filterScanline(attempt[type].data, &in[y * linebytes], prevline, linebytes, bytewidth, type); + size[type] = 0; + dummy = 0; + zlib_compress(&dummy, &size[type], attempt[type].data, testsize, &zlibsettings); + lodepng_free(dummy); + /*check if this is smallest size (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || size[type] < smallest) + { + bestType = type; + smallest = size[type]; + } + } + prevline = &in[y * linebytes]; + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType].data[x]; + } + for(type = 0; type != 5; ++type) ucvector_cleanup(&attempt[type]); + } + else return 88; /* unknown filter strategy */ + + return error; +} + +static void addPaddingBits(unsigned char* out, const unsigned char* in, + size_t olinebits, size_t ilinebits, unsigned h) +{ + /*The opposite of the removePaddingBits function + olinebits must be >= ilinebits*/ + unsigned y; + size_t diff = olinebits - ilinebits; + size_t obp = 0, ibp = 0; /*bit pointers*/ + for(y = 0; y != h; ++y) + { + size_t x; + for(x = 0; x < ilinebits; ++x) + { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + /*obp += diff; --> no, fill in some value in the padding bits too, to avoid + "Use of uninitialised value of size ###" warning from valgrind*/ + for(x = 0; x != diff; ++x) setBitOfReversedStream(&obp, out, 0); + } +} + +/* +in: non-interlaced image with size w*h +out: the same pixels, but re-ordered according to PNG's Adam7 interlacing, with + no padding bits between scanlines, but between reduced images so that each + reduced image starts at a byte. +bpp: bits per pixel +there are no padding bits, not between scanlines, not between reduced images +in has the following size in bits: w * h * bpp. +out is possibly bigger due to padding bits between reduced images +NOTE: comments about padding bits are only relevant if bpp < 8 +*/ +static void Adam7_interlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) +{ + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + if(bpp >= 8) + { + for(i = 0; i != 7; ++i) + { + unsigned x, y, b; + size_t bytewidth = bpp / 8; + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) + { + size_t pixelinstart = ((ADAM7_IY[i] + y * ADAM7_DY[i]) * w + ADAM7_IX[i] + x * ADAM7_DX[i]) * bytewidth; + size_t pixeloutstart = passstart[i] + (y * passw[i] + x) * bytewidth; + for(b = 0; b < bytewidth; ++b) + { + out[pixeloutstart + b] = in[pixelinstart + b]; + } + } + } + } + else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ + { + for(i = 0; i != 7; ++i) + { + unsigned x, y, b; + unsigned ilinebits = bpp * passw[i]; + unsigned olinebits = bpp * w; + size_t obp, ibp; /*bit pointers (for out and in buffer)*/ + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) + { + ibp = (ADAM7_IY[i] + y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + x * ADAM7_DX[i]) * bpp; + obp = (8 * passstart[i]) + (y * ilinebits + x * bpp); + for(b = 0; b < bpp; ++b) + { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + } + } + } +} + +/*out must be buffer big enough to contain uncompressed IDAT chunk data, and in must contain the full image. +return value is error**/ +static unsigned preProcessScanlines(unsigned char** out, size_t* outsize, const unsigned char* in, + unsigned w, unsigned h, + const LodePNGInfo* info_png, const LodePNGEncoderSettings* settings) +{ + /* + This function converts the pure 2D image with the PNG's colortype, into filtered-padded-interlaced data. Steps: + *) if no Adam7: 1) add padding bits (= posible extra bits per scanline if bpp < 8) 2) filter + *) if adam7: 1) Adam7_interlace 2) 7x add padding bits 3) 7x filter + */ + unsigned bpp = lodepng_get_bpp(&info_png->color); + unsigned error = 0; + + if(info_png->interlace_method == 0) + { + *outsize = h + (h * ((w * bpp + 7) / 8)); /*image size plus an extra byte per scanline + possible padding bits*/ + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!(*out) && (*outsize)) error = 83; /*alloc fail*/ + + if(!error) + { + /*non multiple of 8 bits per scanline, padding bits needed per scanline*/ + if(bpp < 8 && w * bpp != ((w * bpp + 7) / 8) * 8) + { + unsigned char* padded = (unsigned char*)lodepng_malloc(h * ((w * bpp + 7) / 8)); + if(!padded) error = 83; /*alloc fail*/ + if(!error) + { + addPaddingBits(padded, in, ((w * bpp + 7) / 8) * 8, w * bpp, h); + error = filter(*out, padded, w, h, &info_png->color, settings); + } + lodepng_free(padded); + } + else + { + /*we can immediatly filter into the out buffer, no other steps needed*/ + error = filter(*out, in, w, h, &info_png->color, settings); + } + } + } + else /*interlace_method is 1 (Adam7)*/ + { + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned char* adam7; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + *outsize = filter_passstart[7]; /*image size plus an extra byte per scanline + possible padding bits*/ + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!(*out)) error = 83; /*alloc fail*/ + + adam7 = (unsigned char*)lodepng_malloc(passstart[7]); + if(!adam7 && passstart[7]) error = 83; /*alloc fail*/ + + if(!error) + { + unsigned i; + + Adam7_interlace(adam7, in, w, h, bpp); + for(i = 0; i != 7; ++i) + { + if(bpp < 8) + { + unsigned char* padded = (unsigned char*)lodepng_malloc(padded_passstart[i + 1] - padded_passstart[i]); + if(!padded) ERROR_BREAK(83); /*alloc fail*/ + addPaddingBits(padded, &adam7[passstart[i]], + ((passw[i] * bpp + 7) / 8) * 8, passw[i] * bpp, passh[i]); + error = filter(&(*out)[filter_passstart[i]], padded, + passw[i], passh[i], &info_png->color, settings); + lodepng_free(padded); + } + else + { + error = filter(&(*out)[filter_passstart[i]], &adam7[padded_passstart[i]], + passw[i], passh[i], &info_png->color, settings); + } + + if(error) break; + } + } + + lodepng_free(adam7); + } + + return error; +} + +/* +palette must have 4 * palettesize bytes allocated, and given in format RGBARGBARGBARGBA... +returns 0 if the palette is opaque, +returns 1 if the palette has a single color with alpha 0 ==> color key +returns 2 if the palette is semi-translucent. +*/ +static unsigned getPaletteTranslucency(const unsigned char* palette, size_t palettesize) +{ + size_t i; + unsigned key = 0; + unsigned r = 0, g = 0, b = 0; /*the value of the color with alpha 0, so long as color keying is possible*/ + for(i = 0; i != palettesize; ++i) + { + if(!key && palette[4 * i + 3] == 0) + { + r = palette[4 * i + 0]; g = palette[4 * i + 1]; b = palette[4 * i + 2]; + key = 1; + i = (size_t)(-1); /*restart from beginning, to detect earlier opaque colors with key's value*/ + } + else if(palette[4 * i + 3] != 255) return 2; + /*when key, no opaque RGB may have key's RGB*/ + else if(key && r == palette[i * 4 + 0] && g == palette[i * 4 + 1] && b == palette[i * 4 + 2]) return 2; + } + return key; +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +static unsigned addUnknownChunks(ucvector* out, unsigned char* data, size_t datasize) +{ + unsigned char* inchunk = data; + while((size_t)(inchunk - data) < datasize) + { + CERROR_TRY_RETURN(lodepng_chunk_append(&out->data, &out->size, inchunk)); + out->allocsize = out->size; /*fix the allocsize again*/ + inchunk = lodepng_chunk_next(inchunk); + } + return 0; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +unsigned lodepng_encode(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGState* state) +{ + LodePNGInfo info; + ucvector outv; + unsigned char* data = 0; /*uncompressed version of the IDAT chunk data*/ + size_t datasize = 0; + + /*provide some proper output values if error will happen*/ + *out = 0; + *outsize = 0; + state->error = 0; + + lodepng_info_init(&info); + lodepng_info_copy(&info, &state->info_png); + + if((info.color.colortype == LCT_PALETTE || state->encoder.force_palette) + && (info.color.palettesize == 0 || info.color.palettesize > 256)) + { + state->error = 68; /*invalid palette size, it is only allowed to be 1-256*/ + return state->error; + } + + if(state->encoder.auto_convert) + { + state->error = lodepng_auto_choose_color(&info.color, image, w, h, &state->info_raw); + } + if(state->error) return state->error; + + if(state->encoder.zlibsettings.btype > 2) + { + CERROR_RETURN_ERROR(state->error, 61); /*error: unexisting btype*/ + } + if(state->info_png.interlace_method > 1) + { + CERROR_RETURN_ERROR(state->error, 71); /*error: unexisting interlace mode*/ + } + + state->error = checkColorValidity(info.color.colortype, info.color.bitdepth); + if(state->error) return state->error; /*error: unexisting color type given*/ + state->error = checkColorValidity(state->info_raw.colortype, state->info_raw.bitdepth); + if(state->error) return state->error; /*error: unexisting color type given*/ + + if(!lodepng_color_mode_equal(&state->info_raw, &info.color)) + { + unsigned char* converted; + size_t size = (w * h * lodepng_get_bpp(&info.color) + 7) / 8; + + converted = (unsigned char*)lodepng_malloc(size); + if(!converted && size) state->error = 83; /*alloc fail*/ + if(!state->error) + { + state->error = lodepng_convert(converted, image, &info.color, &state->info_raw, w, h); + } + if(!state->error) preProcessScanlines(&data, &datasize, converted, w, h, &info, &state->encoder); + lodepng_free(converted); + } + else preProcessScanlines(&data, &datasize, image, w, h, &info, &state->encoder); + + ucvector_init(&outv); + while(!state->error) /*while only executed once, to break on error*/ + { +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + size_t i; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*write signature and chunks*/ + writeSignature(&outv); + /*IHDR*/ + addChunk_IHDR(&outv, w, h, info.color.colortype, info.color.bitdepth, info.interlace_method); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*unknown chunks between IHDR and PLTE*/ + if(info.unknown_chunks_data[0]) + { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[0], info.unknown_chunks_size[0]); + if(state->error) break; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*PLTE*/ + if(info.color.colortype == LCT_PALETTE) + { + addChunk_PLTE(&outv, &info.color); + } + if(state->encoder.force_palette && (info.color.colortype == LCT_RGB || info.color.colortype == LCT_RGBA)) + { + addChunk_PLTE(&outv, &info.color); + } + /*tRNS*/ + if(info.color.colortype == LCT_PALETTE && getPaletteTranslucency(info.color.palette, info.color.palettesize) != 0) + { + addChunk_tRNS(&outv, &info.color); + } + if((info.color.colortype == LCT_GREY || info.color.colortype == LCT_RGB) && info.color.key_defined) + { + addChunk_tRNS(&outv, &info.color); + } +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*bKGD (must come between PLTE and the IDAt chunks*/ + if(info.background_defined) addChunk_bKGD(&outv, &info); + /*pHYs (must come before the IDAT chunks)*/ + if(info.phys_defined) addChunk_pHYs(&outv, &info); + + /*unknown chunks between PLTE and IDAT*/ + if(info.unknown_chunks_data[1]) + { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[1], info.unknown_chunks_size[1]); + if(state->error) break; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*IDAT (multiple IDAT chunks must be consecutive)*/ + state->error = addChunk_IDAT(&outv, data, datasize, &state->encoder.zlibsettings); + if(state->error) break; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*tIME*/ + if(info.time_defined) addChunk_tIME(&outv, &info.time); + /*tEXt and/or zTXt*/ + for(i = 0; i != info.text_num; ++i) + { + if(strlen(info.text_keys[i]) > 79) + { + state->error = 66; /*text chunk too large*/ + break; + } + if(strlen(info.text_keys[i]) < 1) + { + state->error = 67; /*text chunk too small*/ + break; + } + if(state->encoder.text_compression) + { + addChunk_zTXt(&outv, info.text_keys[i], info.text_strings[i], &state->encoder.zlibsettings); + } + else + { + addChunk_tEXt(&outv, info.text_keys[i], info.text_strings[i]); + } + } + /*LodePNG version id in text chunk*/ + if(state->encoder.add_id) + { + unsigned alread_added_id_text = 0; + for(i = 0; i != info.text_num; ++i) + { + if(!strcmp(info.text_keys[i], "LodePNG")) + { + alread_added_id_text = 1; + break; + } + } + if(alread_added_id_text == 0) + { + addChunk_tEXt(&outv, "LodePNG", LODEPNG_VERSION_STRING); /*it's shorter as tEXt than as zTXt chunk*/ + } + } + /*iTXt*/ + for(i = 0; i != info.itext_num; ++i) + { + if(strlen(info.itext_keys[i]) > 79) + { + state->error = 66; /*text chunk too large*/ + break; + } + if(strlen(info.itext_keys[i]) < 1) + { + state->error = 67; /*text chunk too small*/ + break; + } + addChunk_iTXt(&outv, state->encoder.text_compression, + info.itext_keys[i], info.itext_langtags[i], info.itext_transkeys[i], info.itext_strings[i], + &state->encoder.zlibsettings); + } + + /*unknown chunks between IDAT and IEND*/ + if(info.unknown_chunks_data[2]) + { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[2], info.unknown_chunks_size[2]); + if(state->error) break; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + addChunk_IEND(&outv); + + break; /*this isn't really a while loop; no error happened so break out now!*/ + } + + lodepng_info_cleanup(&info); + lodepng_free(data); + /*instead of cleaning the vector up, give it to the output*/ + *out = outv.data; + *outsize = outv.size; + + return state->error; +} + +unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, const unsigned char* image, + unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) +{ + unsigned error; + LodePNGState state; + lodepng_state_init(&state); + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; + state.info_png.color.colortype = colortype; + state.info_png.color.bitdepth = bitdepth; + lodepng_encode(out, outsize, image, w, h, &state); + error = state.error; + lodepng_state_cleanup(&state); + return error; +} + +unsigned lodepng_encode32(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) +{ + return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGBA, 8); +} + +unsigned lodepng_encode24(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) +{ + return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGB, 8); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned lodepng_encode_file(const char* filename, const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) +{ + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode_memory(&buffer, &buffersize, image, w, h, colortype, bitdepth); + if(!error) error = lodepng_save_file(buffer, buffersize, filename); + lodepng_free(buffer); + return error; +} +unsigned lodepng_encode_fileW(const wchar_t* filename, const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) +{ + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode_memory(&buffer, &buffersize, image, w, h, colortype, bitdepth); + if(!error) error = lodepng_save_fileW(buffer, buffersize, filename); + lodepng_free(buffer); + return error; +} + +unsigned lodepng_encode32_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) +{ + return lodepng_encode_file(filename, image, w, h, LCT_RGBA, 8); +} +unsigned lodepng_encode32_fileW(const wchar_t* filename, const unsigned char* image, unsigned w, unsigned h) +{ + return lodepng_encode_fileW(filename, image, w, h, LCT_RGBA, 8); +} + + +unsigned lodepng_encode24_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) +{ + return lodepng_encode_file(filename, image, w, h, LCT_RGB, 8); +} +unsigned lodepng_encode24_fileW(const wchar_t* filename, const unsigned char* image, unsigned w, unsigned h) +{ + return lodepng_encode_fileW(filename, image, w, h, LCT_RGB, 8); +} +#endif /*LODEPNG_COMPILE_DISK*/ + +void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings) +{ + lodepng_compress_settings_init(&settings->zlibsettings); + settings->filter_palette_zero = 1; + settings->filter_strategy = LFS_MINSUM; + settings->auto_convert = 1; + settings->force_palette = 0; + settings->predefined_filters = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + settings->add_id = 0; + settings->text_compression = 1; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ERROR_TEXT +/* +This returns the description of a numerical error code in English. This is also +the documentation of all the error codes. +*/ +const char* lodepng_error_text(unsigned code) +{ + switch(code) + { + case 0: return "no error, everything went ok"; + case 1: return "nothing done yet"; /*the Encoder/Decoder has done nothing yet, error checking makes no sense yet*/ + case 10: return "end of input memory reached without huffman end code"; /*while huffman decoding*/ + case 11: return "error in code tree made it jump outside of huffman tree"; /*while huffman decoding*/ + case 13: return "problem while processing dynamic deflate block"; + case 14: return "problem while processing dynamic deflate block"; + case 15: return "problem while processing dynamic deflate block"; + case 16: return "unexisting code while processing dynamic deflate block"; + case 17: return "end of out buffer memory reached while inflating"; + case 18: return "invalid distance code while inflating"; + case 19: return "end of out buffer memory reached while inflating"; + case 20: return "invalid deflate block BTYPE encountered while decoding"; + case 21: return "NLEN is not ones complement of LEN in a deflate block"; + /*end of out buffer memory reached while inflating: + This can happen if the inflated deflate data is longer than the amount of bytes required to fill up + all the pixels of the image, given the color depth and image dimensions. Something that doesn't + happen in a normal, well encoded, PNG image.*/ + case 22: return "end of out buffer memory reached while inflating"; + case 23: return "end of in buffer memory reached while inflating"; + case 24: return "invalid FCHECK in zlib header"; + case 25: return "invalid compression method in zlib header"; + case 26: return "FDICT encountered in zlib header while it's not used for PNG"; + case 27: return "PNG file is smaller than a PNG header"; + /*Checks the magic file header, the first 8 bytes of the PNG file*/ + case 28: return "incorrect PNG signature, it's no PNG or corrupted"; + case 29: return "first chunk is not the header chunk"; + case 30: return "chunk length too large, chunk broken off at end of file"; + case 31: return "illegal PNG color type or bpp"; + case 32: return "illegal PNG compression method"; + case 33: return "illegal PNG filter method"; + case 34: return "illegal PNG interlace method"; + case 35: return "chunk length of a chunk is too large or the chunk too small"; + case 36: return "illegal PNG filter type encountered"; + case 37: return "illegal bit depth for this color type given"; + case 38: return "the palette is too big"; /*more than 256 colors*/ + case 39: return "more palette alpha values given in tRNS chunk than there are colors in the palette"; + case 40: return "tRNS chunk has wrong size for greyscale image"; + case 41: return "tRNS chunk has wrong size for RGB image"; + case 42: return "tRNS chunk appeared while it was not allowed for this color type"; + case 43: return "bKGD chunk has wrong size for palette image"; + case 44: return "bKGD chunk has wrong size for greyscale image"; + case 45: return "bKGD chunk has wrong size for RGB image"; + /*the input data is empty, maybe a PNG file doesn't exist or is in the wrong path*/ + case 48: return "empty input or file doesn't exist"; + case 49: return "jumped past memory while generating dynamic huffman tree"; + case 50: return "jumped past memory while generating dynamic huffman tree"; + case 51: return "jumped past memory while inflating huffman block"; + case 52: return "jumped past memory while inflating"; + case 53: return "size of zlib data too small"; + case 54: return "repeat symbol in tree while there was no value symbol yet"; + /*jumped past tree while generating huffman tree, this could be when the + tree will have more leaves than symbols after generating it out of the + given lenghts. They call this an oversubscribed dynamic bit lengths tree in zlib.*/ + case 55: return "jumped past tree while generating huffman tree"; + case 56: return "given output image colortype or bitdepth not supported for color conversion"; + case 57: return "invalid CRC encountered (checking CRC can be disabled)"; + case 58: return "invalid ADLER32 encountered (checking ADLER32 can be disabled)"; + case 59: return "requested color conversion not supported"; + case 60: return "invalid window size given in the settings of the encoder (must be 0-32768)"; + case 61: return "invalid BTYPE given in the settings of the encoder (only 0, 1 and 2 are allowed)"; + /*LodePNG leaves the choice of RGB to greyscale conversion formula to the user.*/ + case 62: return "conversion from color to greyscale not supported"; + case 63: return "length of a chunk too long, max allowed for PNG is 2147483647 bytes per chunk"; /*(2^31-1)*/ + /*this would result in the inability of a deflated block to ever contain an end code. It must be at least 1.*/ + case 64: return "the length of the END symbol 256 in the Huffman tree is 0"; + case 66: return "the length of a text chunk keyword given to the encoder is longer than the maximum of 79 bytes"; + case 67: return "the length of a text chunk keyword given to the encoder is smaller than the minimum of 1 byte"; + case 68: return "tried to encode a PLTE chunk with a palette that has less than 1 or more than 256 colors"; + case 69: return "unknown chunk type with 'critical' flag encountered by the decoder"; + case 71: return "unexisting interlace mode given to encoder (must be 0 or 1)"; + case 72: return "while decoding, unexisting compression method encountering in zTXt or iTXt chunk (it must be 0)"; + case 73: return "invalid tIME chunk size"; + case 74: return "invalid pHYs chunk size"; + /*length could be wrong, or data chopped off*/ + case 75: return "no null termination char found while decoding text chunk"; + case 76: return "iTXt chunk too short to contain required bytes"; + case 77: return "integer overflow in buffer size"; + case 78: return "failed to open file for reading"; /*file doesn't exist or couldn't be opened for reading*/ + case 79: return "failed to open file for writing"; + case 80: return "tried creating a tree of 0 symbols"; + case 81: return "lazy matching at pos 0 is impossible"; + case 82: return "color conversion to palette requested while a color isn't in palette"; + case 83: return "memory allocation failed"; + case 84: return "given image too small to contain all pixels to be encoded"; + case 86: return "impossible offset in lz77 encoding (internal bug)"; + case 87: return "must provide custom zlib function pointer if LODEPNG_COMPILE_ZLIB is not defined"; + case 88: return "invalid filter strategy given for LodePNGEncoderSettings.filter_strategy"; + case 89: return "text chunk keyword too short or long: must have size 1-79"; + /*the windowsize in the LodePNGCompressSettings. Requiring POT(==> & instead of %) makes encoding 12% faster.*/ + case 90: return "windowsize must be a power of two"; + case 91: return "invalid decompressed idat size"; + case 92: return "too many pixels, not supported"; + case 93: return "zero width or height is invalid"; + } + return "unknown error code"; +} +#endif /*LODEPNG_COMPILE_ERROR_TEXT*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // C++ Wrapper // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_CPP +namespace lodepng +{ + +#ifdef LODEPNG_COMPILE_DISK +void load_file(std::vector& buffer, const std::string& filename) +{ + std::ifstream file(filename.c_str(), std::ios::in|std::ios::binary|std::ios::ate); + + /*get filesize*/ + std::streamsize size = 0; + if(file.seekg(0, std::ios::end).good()) size = file.tellg(); + if(file.seekg(0, std::ios::beg).good()) size -= file.tellg(); + + /*read contents of the file into the vector*/ + buffer.resize(size_t(size)); + if(size > 0) file.read((char*)(&buffer[0]), size); +} + +/*write given buffer to the file, overwriting the file, it doesn't append to it.*/ +void save_file(const std::vector& buffer, const std::string& filename) +{ + std::ofstream file(filename.c_str(), std::ios::out|std::ios::binary); + file.write(buffer.empty() ? 0 : (char*)&buffer[0], std::streamsize(buffer.size())); +} +#endif /* LODEPNG_COMPILE_DISK */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_DECODER +unsigned decompress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings& settings) +{ + unsigned char* buffer = 0; + size_t buffersize = 0; + unsigned error = zlib_decompress(&buffer, &buffersize, in, insize, &settings); + if(buffer) + { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned decompress(std::vector& out, const std::vector& in, + const LodePNGDecompressSettings& settings) +{ + return decompress(out, in.empty() ? 0 : &in[0], in.size(), settings); +} +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +unsigned compress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings& settings) +{ + unsigned char* buffer = 0; + size_t buffersize = 0; + unsigned error = zlib_compress(&buffer, &buffersize, in, insize, &settings); + if(buffer) + { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned compress(std::vector& out, const std::vector& in, + const LodePNGCompressSettings& settings) +{ + return compress(out, in.empty() ? 0 : &in[0], in.size(), settings); +} +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_ZLIB */ + + +#ifdef LODEPNG_COMPILE_PNG + +State::State() +{ + lodepng_state_init(this); +} + +State::State(const State& other) +{ + lodepng_state_init(this); + lodepng_state_copy(this, &other); +} + +State::~State() +{ + lodepng_state_cleanup(this); +} + +State& State::operator=(const State& other) +{ + lodepng_state_copy(this, &other); + return *this; +} + +#ifdef LODEPNG_COMPILE_DECODER + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, const unsigned char* in, + size_t insize, LodePNGColorType colortype, unsigned bitdepth) +{ + unsigned char* buffer; + unsigned error = lodepng_decode_memory(&buffer, &w, &h, in, insize, colortype, bitdepth); + if(buffer && !error) + { + State state; + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; + size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw); + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::vector& in, LodePNGColorType colortype, unsigned bitdepth) +{ + return decode(out, w, h, in.empty() ? 0 : &in[0], (unsigned)in.size(), colortype, bitdepth); +} + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const unsigned char* in, size_t insize) +{ + unsigned char* buffer = NULL; + unsigned error = lodepng_decode(&buffer, &w, &h, &state, in, insize); + if(buffer && !error) + { + size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw); + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + } + lodepng_free(buffer); + return error; +} + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const std::vector& in) +{ + return decode(out, w, h, state, in.empty() ? 0 : &in[0], in.size()); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned decode(std::vector& out, unsigned& w, unsigned& h, const std::string& filename, + LodePNGColorType colortype, unsigned bitdepth) +{ + std::vector buffer; + load_file(buffer, filename); + return decode(out, w, h, buffer, colortype, bitdepth); +} +#endif /* LODEPNG_COMPILE_DECODER */ +#endif /* LODEPNG_COMPILE_DISK */ + +#ifdef LODEPNG_COMPILE_ENCODER +unsigned encode(std::vector& out, const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) +{ + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode_memory(&buffer, &buffersize, in, w, h, colortype, bitdepth); + if(buffer) + { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) +{ + if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84; + return encode(out, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth); +} + +unsigned encode(std::vector& out, + const unsigned char* in, unsigned w, unsigned h, + State& state) +{ + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode(&buffer, &buffersize, in, w, h, &state); + if(buffer) + { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + State& state) +{ + if(lodepng_get_raw_size(w, h, &state.info_raw) > in.size()) return 84; + return encode(out, in.empty() ? 0 : &in[0], w, h, state); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned encode(const std::string& filename, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) +{ + std::vector buffer; + unsigned error = encode(buffer, in, w, h, colortype, bitdepth); + if(!error) save_file(buffer, filename); + return error; +} + +unsigned encode(const std::string& filename, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) +{ + if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84; + return encode(filename, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth); +} +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_PNG */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ diff --git a/Plugins/Texture/lodepng.h b/Plugins/Texture/lodepng.h new file mode 100644 index 0000000..90b68da --- /dev/null +++ b/Plugins/Texture/lodepng.h @@ -0,0 +1,1723 @@ +/* +LodePNG version 20150418 + +Copyright (c) 2005-2015 Lode Vandevenne + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef LODEPNG_H +#define LODEPNG_H + +#include /*for size_t*/ + +#ifdef __cplusplus +#include +#include +#endif /*__cplusplus*/ + +extern const char* LODEPNG_VERSION_STRING; + +/* +The following #defines are used to create code sections. They can be disabled +to disable code sections, which can give faster compile time and smaller binary. +The "NO_COMPILE" defines are designed to be used to pass as defines to the +compiler command to disable them without modifying this header, e.g. +-DLODEPNG_NO_COMPILE_ZLIB for gcc. +*/ +/*deflate & zlib. If disabled, you must specify alternative zlib functions in +the custom_zlib field of the compress and decompress settings*/ +#ifndef LODEPNG_NO_COMPILE_ZLIB +#define LODEPNG_COMPILE_ZLIB +#endif +/*png encoder and png decoder*/ +#ifndef LODEPNG_NO_COMPILE_PNG +#define LODEPNG_COMPILE_PNG +#endif +/*deflate&zlib decoder and png decoder*/ +#ifndef LODEPNG_NO_COMPILE_DECODER +#define LODEPNG_COMPILE_DECODER +#endif +/*deflate&zlib encoder and png encoder*/ +#ifndef LODEPNG_NO_COMPILE_ENCODER +#define LODEPNG_COMPILE_ENCODER +#endif +/*the optional built in harddisk file loading and saving functions*/ +#ifndef LODEPNG_NO_COMPILE_DISK +#define LODEPNG_COMPILE_DISK +#endif +/*support for chunks other than IHDR, IDAT, PLTE, tRNS, IEND: ancillary and unknown chunks*/ +#ifndef LODEPNG_NO_COMPILE_ANCILLARY_CHUNKS +#define LODEPNG_COMPILE_ANCILLARY_CHUNKS +#endif +/*ability to convert error numerical codes to English text string*/ +#ifndef LODEPNG_NO_COMPILE_ERROR_TEXT +#define LODEPNG_COMPILE_ERROR_TEXT +#endif +/*Compile the default allocators (C's free, malloc and realloc). If you disable this, +you can define the functions lodepng_free, lodepng_malloc and lodepng_realloc in your +source files with custom allocators.*/ +#ifndef LODEPNG_NO_COMPILE_ALLOCATORS +#define LODEPNG_COMPILE_ALLOCATORS +#endif +/*compile the C++ version (you can disable the C++ wrapper here even when compiling for C++)*/ +#ifdef __cplusplus +#ifndef LODEPNG_NO_COMPILE_CPP +#define LODEPNG_COMPILE_CPP +#endif +#endif + +#ifdef LODEPNG_COMPILE_PNG +/*The PNG color types (also used for raw).*/ +typedef enum LodePNGColorType +{ + LCT_GREY = 0, /*greyscale: 1,2,4,8,16 bit*/ + LCT_RGB = 2, /*RGB: 8,16 bit*/ + LCT_PALETTE = 3, /*palette: 1,2,4,8 bit*/ + LCT_GREY_ALPHA = 4, /*greyscale with alpha: 8,16 bit*/ + LCT_RGBA = 6 /*RGB with alpha: 8,16 bit*/ +} LodePNGColorType; + +#ifdef LODEPNG_COMPILE_DECODER +/* +Converts PNG data in memory to raw pixel data. +out: Output parameter. Pointer to buffer that will contain the raw pixel data. + After decoding, its size is w * h * (bytes per pixel) bytes larger than + initially. Bytes per pixel depends on colortype and bitdepth. + Must be freed after usage with free(*out). + Note: for 16-bit per channel colors, uses big endian format like PNG does. +w: Output parameter. Pointer to width of pixel data. +h: Output parameter. Pointer to height of pixel data. +in: Memory buffer with the PNG file. +insize: size of the in buffer. +colortype: the desired color type for the raw output image. See explanation on PNG color types. +bitdepth: the desired bit depth for the raw output image. See explanation on PNG color types. +Return value: LodePNG error code (0 means no error). +*/ +unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_decode_memory, but always decodes to 32-bit RGBA raw image*/ +unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize); + +/*Same as lodepng_decode_memory, but always decodes to 24-bit RGB raw image*/ +unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize); + +#ifdef LODEPNG_COMPILE_DISK +/* +Load PNG from disk, from file with given name. +Same as the other decode functions, but instead takes a filename as input. +*/ +unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_decode_file, but always decodes to 32-bit RGBA raw image.*/ +unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename); + +/*Same as lodepng_decode_file, but always decodes to 24-bit RGB raw image.*/ +unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename); +#endif /*LODEPNG_COMPILE_DISK*/ +#endif /*LODEPNG_COMPILE_DECODER*/ + + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Converts raw pixel data into a PNG image in memory. The colortype and bitdepth + of the output PNG image cannot be chosen, they are automatically determined + by the colortype, bitdepth and content of the input pixel data. + Note: for 16-bit per channel colors, needs big endian format like PNG does. +out: Output parameter. Pointer to buffer that will contain the PNG image data. + Must be freed after usage with free(*out). +outsize: Output parameter. Pointer to the size in bytes of the out buffer. +image: The raw pixel data to encode. The size of this buffer should be + w * h * (bytes per pixel), bytes per pixel depends on colortype and bitdepth. +w: width of the raw pixel data in pixels. +h: height of the raw pixel data in pixels. +colortype: the color type of the raw input image. See explanation on PNG color types. +bitdepth: the bit depth of the raw input image. See explanation on PNG color types. +Return value: LodePNG error code (0 means no error). +*/ +unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_encode_memory, but always encodes from 32-bit RGBA raw image.*/ +unsigned lodepng_encode32(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h); + +/*Same as lodepng_encode_memory, but always encodes from 24-bit RGB raw image.*/ +unsigned lodepng_encode24(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h); + +#ifdef LODEPNG_COMPILE_DISK +/* +Converts raw pixel data into a PNG file on disk. +Same as the other encode functions, but instead takes a filename as output. +NOTE: This overwrites existing files without warning! +*/ +unsigned lodepng_encode_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth); +/* +Converts raw pixel data into a PNG file on disk. +Same as the other encode functions, but instead takes a filename as output. +NOTE: This overwrites existing files without warning! +*/ +unsigned lodepng_encode_fileW(const wchar_t* filename, + const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_encode_file, but always encodes from 32-bit RGBA raw image.*/ +unsigned lodepng_encode32_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h); +/*Same as lodepng_encode_fileW, but always encodes from 32-bit RGBA raw image.*/ +unsigned lodepng_encode32_fileW(const wchar_t* filename, + const unsigned char* image, unsigned w, unsigned h); + +/*Same as lodepng_encode_file, but always encodes from 24-bit RGB raw image.*/ +unsigned lodepng_encode24_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h); +/*Same as lodepng_encode_file, but always encodes from 24-bit RGB raw image.*/ +unsigned lodepng_encode24_fileW(const wchar_t* filename, + const unsigned char* image, unsigned w, unsigned h); +#endif /*LODEPNG_COMPILE_DISK*/ +#endif /*LODEPNG_COMPILE_ENCODER*/ + + +#ifdef LODEPNG_COMPILE_CPP +namespace lodepng +{ +#ifdef LODEPNG_COMPILE_DECODER +/*Same as lodepng_decode_memory, but decodes to an std::vector. The colortype +is the format to output the pixels to. Default is RGBA 8-bit per channel.*/ +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const unsigned char* in, size_t insize, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::vector& in, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#ifdef LODEPNG_COMPILE_DISK +/* +Converts PNG file from disk to raw pixel data in memory. +Same as the other decode functions, but instead takes a filename as input. +*/ +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::string& filename, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +/*Same as lodepng_encode_memory, but encodes to an std::vector. colortype +is that of the raw input data. The output PNG color type will be auto chosen.*/ +unsigned encode(std::vector& out, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#ifdef LODEPNG_COMPILE_DISK +/* +Converts 32-bit RGBA raw pixel data into a PNG file on disk. +Same as the other encode functions, but instead takes a filename as output. +NOTE: This overwrites existing files without warning! +*/ +unsigned encode(const std::string& filename, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned encode(const std::string& filename, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_ENCODER */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ERROR_TEXT +/*Returns an English description of the numerical error code.*/ +const char* lodepng_error_text(unsigned code); +#endif /*LODEPNG_COMPILE_ERROR_TEXT*/ + +#ifdef LODEPNG_COMPILE_DECODER +/*Settings for zlib decompression*/ +typedef struct LodePNGDecompressSettings LodePNGDecompressSettings; +struct LodePNGDecompressSettings +{ + unsigned ignore_adler32; /*if 1, continue and don't give an error message if the Adler32 checksum is corrupted*/ + + /*use custom zlib decoder instead of built in one (default: null)*/ + unsigned (*custom_zlib)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGDecompressSettings*); + /*use custom deflate decoder instead of built in one (default: null) + if custom_zlib is used, custom_deflate is ignored since only the built in + zlib function will call custom_deflate*/ + unsigned (*custom_inflate)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGDecompressSettings*); + + const void* custom_context; /*optional custom settings for custom functions*/ +}; + +extern const LodePNGDecompressSettings lodepng_default_decompress_settings; +void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Settings for zlib compression. Tweaking these settings tweaks the balance +between speed and compression ratio. +*/ +typedef struct LodePNGCompressSettings LodePNGCompressSettings; +struct LodePNGCompressSettings /*deflate = compress*/ +{ + /*LZ77 related settings*/ + unsigned btype; /*the block type for LZ (0, 1, 2 or 3, see zlib standard). Should be 2 for proper compression.*/ + unsigned use_lz77; /*whether or not to use LZ77. Should be 1 for proper compression.*/ + unsigned windowsize; /*must be a power of two <= 32768. higher compresses more but is slower. Default value: 2048.*/ + unsigned minmatch; /*mininum lz77 length. 3 is normally best, 6 can be better for some PNGs. Default: 0*/ + unsigned nicematch; /*stop searching if >= this length found. Set to 258 for best compression. Default: 128*/ + unsigned lazymatching; /*use lazy matching: better compression but a bit slower. Default: true*/ + + /*use custom zlib encoder instead of built in one (default: null)*/ + unsigned (*custom_zlib)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGCompressSettings*); + /*use custom deflate encoder instead of built in one (default: null) + if custom_zlib is used, custom_deflate is ignored since only the built in + zlib function will call custom_deflate*/ + unsigned (*custom_deflate)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGCompressSettings*); + + const void* custom_context; /*optional custom settings for custom functions*/ +}; + +extern const LodePNGCompressSettings lodepng_default_compress_settings; +void lodepng_compress_settings_init(LodePNGCompressSettings* settings); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_PNG +/* +Color mode of an image. Contains all information required to decode the pixel +bits to RGBA colors. This information is the same as used in the PNG file +format, and is used both for PNG and raw image data in LodePNG. +*/ +typedef struct LodePNGColorMode +{ + /*header (IHDR)*/ + LodePNGColorType colortype; /*color type, see PNG standard or documentation further in this header file*/ + unsigned bitdepth; /*bits per sample, see PNG standard or documentation further in this header file*/ + + /* + palette (PLTE and tRNS) + + Dynamically allocated with the colors of the palette, including alpha. + When encoding a PNG, to store your colors in the palette of the LodePNGColorMode, first use + lodepng_palette_clear, then for each color use lodepng_palette_add. + If you encode an image without alpha with palette, don't forget to put value 255 in each A byte of the palette. + + When decoding, by default you can ignore this palette, since LodePNG already + fills the palette colors in the pixels of the raw RGBA output. + + The palette is only supported for color type 3. + */ + unsigned char* palette; /*palette in RGBARGBA... order. When allocated, must be either 0, or have size 1024*/ + size_t palettesize; /*palette size in number of colors (amount of bytes is 4 * palettesize)*/ + + /* + transparent color key (tRNS) + + This color uses the same bit depth as the bitdepth value in this struct, which can be 1-bit to 16-bit. + For greyscale PNGs, r, g and b will all 3 be set to the same. + + When decoding, by default you can ignore this information, since LodePNG sets + pixels with this key to transparent already in the raw RGBA output. + + The color key is only supported for color types 0 and 2. + */ + unsigned key_defined; /*is a transparent color key given? 0 = false, 1 = true*/ + unsigned key_r; /*red/greyscale component of color key*/ + unsigned key_g; /*green component of color key*/ + unsigned key_b; /*blue component of color key*/ +} LodePNGColorMode; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_color_mode_init(LodePNGColorMode* info); +void lodepng_color_mode_cleanup(LodePNGColorMode* info); +/*return value is error code (0 means no error)*/ +unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source); + +void lodepng_palette_clear(LodePNGColorMode* info); +/*add 1 color to the palette*/ +unsigned lodepng_palette_add(LodePNGColorMode* info, + unsigned char r, unsigned char g, unsigned char b, unsigned char a); + +/*get the total amount of bits per pixel, based on colortype and bitdepth in the struct*/ +unsigned lodepng_get_bpp(const LodePNGColorMode* info); +/*get the amount of color channels used, based on colortype in the struct. +If a palette is used, it counts as 1 channel.*/ +unsigned lodepng_get_channels(const LodePNGColorMode* info); +/*is it a greyscale type? (only colortype 0 or 4)*/ +unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info); +/*has it got an alpha channel? (only colortype 2 or 6)*/ +unsigned lodepng_is_alpha_type(const LodePNGColorMode* info); +/*has it got a palette? (only colortype 3)*/ +unsigned lodepng_is_palette_type(const LodePNGColorMode* info); +/*only returns true if there is a palette and there is a value in the palette with alpha < 255. +Loops through the palette to check this.*/ +unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info); +/* +Check if the given color info indicates the possibility of having non-opaque pixels in the PNG image. +Returns true if the image can have translucent or invisible pixels (it still be opaque if it doesn't use such pixels). +Returns false if the image can only have opaque pixels. +In detail, it returns true only if it's a color type with alpha, or has a palette with non-opaque values, +or if "key_defined" is true. +*/ +unsigned lodepng_can_have_alpha(const LodePNGColorMode* info); +/*Returns the byte size of a raw image buffer with given width, height and color mode*/ +size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*The information of a Time chunk in PNG.*/ +typedef struct LodePNGTime +{ + unsigned year; /*2 bytes used (0-65535)*/ + unsigned month; /*1-12*/ + unsigned day; /*1-31*/ + unsigned hour; /*0-23*/ + unsigned minute; /*0-59*/ + unsigned second; /*0-60 (to allow for leap seconds)*/ +} LodePNGTime; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/*Information about the PNG image, except pixels, width and height.*/ +typedef struct LodePNGInfo +{ + /*header (IHDR), palette (PLTE) and transparency (tRNS) chunks*/ + unsigned compression_method;/*compression method of the original file. Always 0.*/ + unsigned filter_method; /*filter method of the original file*/ + unsigned interlace_method; /*interlace method of the original file*/ + LodePNGColorMode color; /*color type and bits, palette and transparency of the PNG file*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /* + suggested background color chunk (bKGD) + This color uses the same color mode as the PNG (except alpha channel), which can be 1-bit to 16-bit. + + For greyscale PNGs, r, g and b will all 3 be set to the same. When encoding + the encoder writes the red one. For palette PNGs: When decoding, the RGB value + will be stored, not a palette index. But when encoding, specify the index of + the palette in background_r, the other two are then ignored. + + The decoder does not use this background color to edit the color of pixels. + */ + unsigned background_defined; /*is a suggested background color given?*/ + unsigned background_r; /*red component of suggested background color*/ + unsigned background_g; /*green component of suggested background color*/ + unsigned background_b; /*blue component of suggested background color*/ + + /* + non-international text chunks (tEXt and zTXt) + + The char** arrays each contain num strings. The actual messages are in + text_strings, while text_keys are keywords that give a short description what + the actual text represents, e.g. Title, Author, Description, or anything else. + + A keyword is minimum 1 character and maximum 79 characters long. It's + discouraged to use a single line length longer than 79 characters for texts. + + Don't allocate these text buffers yourself. Use the init/cleanup functions + correctly and use lodepng_add_text and lodepng_clear_text. + */ + size_t text_num; /*the amount of texts in these char** buffers (there may be more texts in itext)*/ + char** text_keys; /*the keyword of a text chunk (e.g. "Comment")*/ + char** text_strings; /*the actual text*/ + + /* + international text chunks (iTXt) + Similar to the non-international text chunks, but with additional strings + "langtags" and "transkeys". + */ + size_t itext_num; /*the amount of international texts in this PNG*/ + char** itext_keys; /*the English keyword of the text chunk (e.g. "Comment")*/ + char** itext_langtags; /*language tag for this text's language, ISO/IEC 646 string, e.g. ISO 639 language tag*/ + char** itext_transkeys; /*keyword translated to the international language - UTF-8 string*/ + char** itext_strings; /*the actual international text - UTF-8 string*/ + + /*time chunk (tIME)*/ + unsigned time_defined; /*set to 1 to make the encoder generate a tIME chunk*/ + LodePNGTime time; + + /*phys chunk (pHYs)*/ + unsigned phys_defined; /*if 0, there is no pHYs chunk and the values below are undefined, if 1 else there is one*/ + unsigned phys_x; /*pixels per unit in x direction*/ + unsigned phys_y; /*pixels per unit in y direction*/ + unsigned phys_unit; /*may be 0 (unknown unit) or 1 (metre)*/ + + /* + unknown chunks + There are 3 buffers, one for each position in the PNG where unknown chunks can appear + each buffer contains all unknown chunks for that position consecutively + The 3 buffers are the unknown chunks between certain critical chunks: + 0: IHDR-PLTE, 1: PLTE-IDAT, 2: IDAT-IEND + Do not allocate or traverse this data yourself. Use the chunk traversing functions declared + later, such as lodepng_chunk_next and lodepng_chunk_append, to read/write this struct. + */ + unsigned char* unknown_chunks_data[3]; + size_t unknown_chunks_size[3]; /*size in bytes of the unknown chunks, given for protection*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGInfo; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_info_init(LodePNGInfo* info); +void lodepng_info_cleanup(LodePNGInfo* info); +/*return value is error code (0 means no error)*/ +unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +void lodepng_clear_text(LodePNGInfo* info); /*use this to clear the texts again after you filled them in*/ +unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str); /*push back both texts at once*/ + +void lodepng_clear_itext(LodePNGInfo* info); /*use this to clear the itexts again after you filled them in*/ +unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str); /*push back the 4 texts of 1 chunk at once*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/* +Converts raw buffer from one color type to another color type, based on +LodePNGColorMode structs to describe the input and output color type. +See the reference manual at the end of this header file to see which color conversions are supported. +return value = LodePNG error code (0 if all went ok, an error if the conversion isn't supported) +The out buffer must have size (w * h * bpp + 7) / 8, where bpp is the bits per pixel +of the output color type (lodepng_get_bpp). +For < 8 bpp images, there should not be padding bits at the end of scanlines. +For 16-bit per channel colors, uses big endian format like PNG does. +Return value is LodePNG error code +*/ +unsigned lodepng_convert(unsigned char* out, const unsigned char* in, + LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, + unsigned w, unsigned h); + +#ifdef LODEPNG_COMPILE_DECODER +/* +Settings for the decoder. This contains settings for the PNG and the Zlib +decoder, but not the Info settings from the Info structs. +*/ +typedef struct LodePNGDecoderSettings +{ + LodePNGDecompressSettings zlibsettings; /*in here is the setting to ignore Adler32 checksums*/ + + unsigned ignore_crc; /*ignore CRC checksums*/ + + unsigned color_convert; /*whether to convert the PNG to the color type you want. Default: yes*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + unsigned read_text_chunks; /*if false but remember_unknown_chunks is true, they're stored in the unknown chunks*/ + /*store all bytes from unknown chunks in the LodePNGInfo (off by default, useful for a png editor)*/ + unsigned remember_unknown_chunks; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGDecoderSettings; + +void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/*automatically use color type with less bits per pixel if losslessly possible. Default: AUTO*/ +typedef enum LodePNGFilterStrategy +{ + /*every filter at zero*/ + LFS_ZERO, + /*Use filter that gives minumum sum, as described in the official PNG filter heuristic.*/ + LFS_MINSUM, + /*Use the filter type that gives smallest Shannon entropy for this scanline. Depending + on the image, this is better or worse than minsum.*/ + LFS_ENTROPY, + /* + Brute-force-search PNG filters by compressing each filter for each scanline. + Experimental, very slow, and only rarely gives better compression than MINSUM. + */ + LFS_BRUTE_FORCE, + /*use predefined_filters buffer: you specify the filter type for each scanline*/ + LFS_PREDEFINED +} LodePNGFilterStrategy; + +/*Gives characteristics about the colors of the image, which helps decide which color model to use for encoding. +Used internally by default if "auto_convert" is enabled. Public because it's useful for custom algorithms.*/ +typedef struct LodePNGColorProfile +{ + unsigned colored; /*not greyscale*/ + unsigned key; /*if true, image is not opaque. Only if true and alpha is false, color key is possible.*/ + unsigned short key_r; /*these values are always in 16-bit bitdepth in the profile*/ + unsigned short key_g; + unsigned short key_b; + unsigned alpha; /*alpha channel or alpha palette required*/ + unsigned numcolors; /*amount of colors, up to 257. Not valid if bits == 16.*/ + unsigned char palette[1024]; /*Remembers up to the first 256 RGBA colors, in no particular order*/ + unsigned bits; /*bits per channel (not for palette). 1,2 or 4 for greyscale only. 16 if 16-bit per channel required.*/ +} LodePNGColorProfile; + +void lodepng_color_profile_init(LodePNGColorProfile* profile); + +/*Get a LodePNGColorProfile of the image.*/ +unsigned lodepng_get_color_profile(LodePNGColorProfile* profile, + const unsigned char* image, unsigned w, unsigned h, + const LodePNGColorMode* mode_in); +/*The function LodePNG uses internally to decide the PNG color with auto_convert. +Chooses an optimal color model, e.g. grey if only grey pixels, palette if < 256 colors, ...*/ +unsigned lodepng_auto_choose_color(LodePNGColorMode* mode_out, + const unsigned char* image, unsigned w, unsigned h, + const LodePNGColorMode* mode_in); + +/*Settings for the encoder.*/ +typedef struct LodePNGEncoderSettings +{ + LodePNGCompressSettings zlibsettings; /*settings for the zlib encoder, such as window size, ...*/ + + unsigned auto_convert; /*automatically choose output PNG color type. Default: true*/ + + /*If true, follows the official PNG heuristic: if the PNG uses a palette or lower than + 8 bit depth, set all filters to zero. Otherwise use the filter_strategy. Note that to + completely follow the official PNG heuristic, filter_palette_zero must be true and + filter_strategy must be LFS_MINSUM*/ + unsigned filter_palette_zero; + /*Which filter strategy to use when not using zeroes due to filter_palette_zero. + Set filter_palette_zero to 0 to ensure always using your chosen strategy. Default: LFS_MINSUM*/ + LodePNGFilterStrategy filter_strategy; + /*used if filter_strategy is LFS_PREDEFINED. In that case, this must point to a buffer with + the same length as the amount of scanlines in the image, and each value must <= 5. You + have to cleanup this buffer, LodePNG will never free it. Don't forget that filter_palette_zero + must be set to 0 to ensure this is also used on palette or low bitdepth images.*/ + const unsigned char* predefined_filters; + + /*force creating a PLTE chunk if colortype is 2 or 6 (= a suggested palette). + If colortype is 3, PLTE is _always_ created.*/ + unsigned force_palette; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*add LodePNG identifier and version as a text chunk, for debugging*/ + unsigned add_id; + /*encode text chunks as zTXt chunks instead of tEXt chunks, and use compression in iTXt chunks*/ + unsigned text_compression; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGEncoderSettings; + +void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings); +#endif /*LODEPNG_COMPILE_ENCODER*/ + + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) +/*The settings, state and information for extended encoding and decoding.*/ +typedef struct LodePNGState +{ +#ifdef LODEPNG_COMPILE_DECODER + LodePNGDecoderSettings decoder; /*the decoding settings*/ +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER + LodePNGEncoderSettings encoder; /*the encoding settings*/ +#endif /*LODEPNG_COMPILE_ENCODER*/ + LodePNGColorMode info_raw; /*specifies the format in which you would like to get the raw pixel buffer*/ + LodePNGInfo info_png; /*info of the PNG image obtained after decoding*/ + unsigned error; +#ifdef LODEPNG_COMPILE_CPP + /* For the lodepng::State subclass. */ + virtual ~LodePNGState(){} +#endif +} LodePNGState; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_state_init(LodePNGState* state); +void lodepng_state_cleanup(LodePNGState* state); +void lodepng_state_copy(LodePNGState* dest, const LodePNGState* source); +#endif /* defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) */ + +#ifdef LODEPNG_COMPILE_DECODER +/* +Same as lodepng_decode_memory, but uses a LodePNGState to allow custom settings and +getting much more information about the PNG image and color mode. +*/ +unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize); + +/* +Read the PNG header, but not the actual data. This returns only the information +that is in the header chunk of the PNG, such as width, height and color type. The +information is placed in the info_png field of the LodePNGState. +*/ +unsigned lodepng_inspect(unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize); +#endif /*LODEPNG_COMPILE_DECODER*/ + + +#ifdef LODEPNG_COMPILE_ENCODER +/*This function allocates the out buffer with standard malloc and stores the size in *outsize.*/ +unsigned lodepng_encode(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGState* state); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/* +The lodepng_chunk functions are normally not needed, except to traverse the +unknown chunks stored in the LodePNGInfo struct, or add new ones to it. +It also allows traversing the chunks of an encoded PNG file yourself. + +PNG standard chunk naming conventions: +First byte: uppercase = critical, lowercase = ancillary +Second byte: uppercase = public, lowercase = private +Third byte: must be uppercase +Fourth byte: uppercase = unsafe to copy, lowercase = safe to copy +*/ + +/* +Gets the length of the data of the chunk. Total chunk length has 12 bytes more. +There must be at least 4 bytes to read from. If the result value is too large, +it may be corrupt data. +*/ +unsigned lodepng_chunk_length(const unsigned char* chunk); + +/*puts the 4-byte type in null terminated string*/ +void lodepng_chunk_type(char type[5], const unsigned char* chunk); + +/*check if the type is the given type*/ +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type); + +/*0: it's one of the critical chunk types, 1: it's an ancillary chunk (see PNG standard)*/ +unsigned char lodepng_chunk_ancillary(const unsigned char* chunk); + +/*0: public, 1: private (see PNG standard)*/ +unsigned char lodepng_chunk_private(const unsigned char* chunk); + +/*0: the chunk is unsafe to copy, 1: the chunk is safe to copy (see PNG standard)*/ +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk); + +/*get pointer to the data of the chunk, where the input points to the header of the chunk*/ +unsigned char* lodepng_chunk_data(unsigned char* chunk); +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk); + +/*returns 0 if the crc is correct, 1 if it's incorrect (0 for OK as usual!)*/ +unsigned lodepng_chunk_check_crc(const unsigned char* chunk); + +/*generates the correct CRC from the data and puts it in the last 4 bytes of the chunk*/ +void lodepng_chunk_generate_crc(unsigned char* chunk); + +/*iterate to next chunks. don't use on IEND chunk, as there is no next chunk then*/ +unsigned char* lodepng_chunk_next(unsigned char* chunk); +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk); + +/* +Appends chunk to the data in out. The given chunk should already have its chunk header. +The out variable and outlength are updated to reflect the new reallocated buffer. +Returns error code (0 if it went ok) +*/ +unsigned lodepng_chunk_append(unsigned char** out, size_t* outlength, const unsigned char* chunk); + +/* +Appends new chunk to out. The chunk to append is given by giving its length, type +and data separately. The type is a 4-letter string. +The out variable and outlength are updated to reflect the new reallocated buffer. +Returne error code (0 if it went ok) +*/ +unsigned lodepng_chunk_create(unsigned char** out, size_t* outlength, unsigned length, + const char* type, const unsigned char* data); + + +/*Calculate CRC32 of buffer*/ +unsigned lodepng_crc32(const unsigned char* buf, size_t len); +#endif /*LODEPNG_COMPILE_PNG*/ + + +#ifdef LODEPNG_COMPILE_ZLIB +/* +This zlib part can be used independently to zlib compress and decompress a +buffer. It cannot be used to create gzip files however, and it only supports the +part of zlib that is required for PNG, it does not support dictionaries. +*/ + +#ifdef LODEPNG_COMPILE_DECODER +/*Inflate a buffer. Inflate is the decompression step of deflate. Out buffer must be freed after use.*/ +unsigned lodepng_inflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings); + +/* +Decompresses Zlib data. Reallocates the out buffer and appends the data. The +data must be according to the zlib specification. +Either, *out must be NULL and *outsize must be 0, or, *out must be a valid +buffer and *outsize its size in bytes. out must be freed by user after usage. +*/ +unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Compresses data with Zlib. Reallocates the out buffer and appends the data. +Zlib adds a small header and trailer around the deflate data. +The data is output in the format of the zlib specification. +Either, *out must be NULL and *outsize must be 0, or, *out must be a valid +buffer and *outsize its size in bytes. out must be freed by user after usage. +*/ +unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings); + +/* +Find length-limited Huffman code for given frequencies. This function is in the +public interface only for tests, it's used internally by lodepng_deflate. +*/ +unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies, + size_t numcodes, unsigned maxbitlen); + +/*Compress a buffer with deflate. See RFC 1951. Out buffer must be freed after use.*/ +unsigned lodepng_deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings); + +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_ZLIB*/ + +#ifdef LODEPNG_COMPILE_DISK +/* +Load a file from disk into buffer. The function allocates the out buffer, and +after usage you should free it. +out: output parameter, contains pointer to loaded buffer. +outsize: output parameter, size of the allocated out buffer +filename: the path to the file to load +return value: error code (0 means ok) +*/ +unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename); + +/* +Save a file from buffer to disk. Warning, if it exists, this function overwrites +the file without warning! +buffer: the buffer to write +buffersize: size of the buffer to write +filename: the path to the file to save to +return value: error code (0 means ok) +*/ +unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename); +#endif /*LODEPNG_COMPILE_DISK*/ + +#ifdef LODEPNG_COMPILE_CPP +/* The LodePNG C++ wrapper uses std::vectors instead of manually allocated memory buffers. */ +namespace lodepng +{ +#ifdef LODEPNG_COMPILE_PNG +class State : public LodePNGState +{ + public: + State(); + State(const State& other); + virtual ~State(); + State& operator=(const State& other); +}; + +#ifdef LODEPNG_COMPILE_DECODER +/* Same as other lodepng::decode, but using a State for more settings and information. */ +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const unsigned char* in, size_t insize); +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const std::vector& in); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* Same as other lodepng::encode, but using a State for more settings and information. */ +unsigned encode(std::vector& out, + const unsigned char* in, unsigned w, unsigned h, + State& state); +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + State& state); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DISK +/* +Load a file from disk into an std::vector. If the vector is empty, then either +the file doesn't exist or is an empty file. +*/ +void load_file(std::vector& buffer, const std::string& filename); + +/* +Save the binary data in an std::vector to a file on disk. The file is overwritten +without warning. +*/ +void save_file(const std::vector& buffer, const std::string& filename); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_PNG */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_DECODER +/* Zlib-decompress an unsigned char buffer */ +unsigned decompress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings& settings = lodepng_default_decompress_settings); + +/* Zlib-decompress an std::vector */ +unsigned decompress(std::vector& out, const std::vector& in, + const LodePNGDecompressSettings& settings = lodepng_default_decompress_settings); +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +/* Zlib-compress an unsigned char buffer */ +unsigned compress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings& settings = lodepng_default_compress_settings); + +/* Zlib-compress an std::vector */ +unsigned compress(std::vector& out, const std::vector& in, + const LodePNGCompressSettings& settings = lodepng_default_compress_settings); +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_ZLIB */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ + +/* +TODO: +[.] test if there are no memory leaks or security exploits - done a lot but needs to be checked often +[.] check compatibility with vareous compilers - done but needs to be redone for every newer version +[X] converting color to 16-bit per channel types +[ ] read all public PNG chunk types (but never let the color profile and gamma ones touch RGB values) +[ ] make sure encoder generates no chunks with size > (2^31)-1 +[ ] partial decoding (stream processing) +[X] let the "isFullyOpaque" function check color keys and transparent palettes too +[X] better name for the variables "codes", "codesD", "codelengthcodes", "clcl" and "lldl" +[ ] don't stop decoding on errors like 69, 57, 58 (make warnings) +[ ] let the C++ wrapper catch exceptions coming from the standard library and return LodePNG error codes +[ ] allow user to provide custom color conversion functions, e.g. for premultiplied alpha, padding bits or not, ... +*/ + +#endif /*LODEPNG_H inclusion guard*/ + +/* +LodePNG Documentation +--------------------- + +0. table of contents +-------------------- + + 1. about + 1.1. supported features + 1.2. features not supported + 2. C and C++ version + 3. security + 4. decoding + 5. encoding + 6. color conversions + 6.1. PNG color types + 6.2. color conversions + 6.3. padding bits + 6.4. A note about 16-bits per channel and endianness + 7. error values + 8. chunks and PNG editing + 9. compiler support + 10. examples + 10.1. decoder C++ example + 10.2. decoder C example + 11. changes + 12. contact information + + +1. about +-------- + +PNG is a file format to store raster images losslessly with good compression, +supporting different color types and alpha channel. + +LodePNG is a PNG codec according to the Portable Network Graphics (PNG) +Specification (Second Edition) - W3C Recommendation 10 November 2003. + +The specifications used are: + +*) Portable Network Graphics (PNG) Specification (Second Edition): + http://www.w3.org/TR/2003/REC-PNG-20031110 +*) RFC 1950 ZLIB Compressed Data Format version 3.3: + http://www.gzip.org/zlib/rfc-zlib.html +*) RFC 1951 DEFLATE Compressed Data Format Specification ver 1.3: + http://www.gzip.org/zlib/rfc-deflate.html + +The most recent version of LodePNG can currently be found at +http://lodev.org/lodepng/ + +LodePNG works both in C (ISO C90) and C++, with a C++ wrapper that adds +extra functionality. + +LodePNG exists out of two files: +-lodepng.h: the header file for both C and C++ +-lodepng.c(pp): give it the name lodepng.c or lodepng.cpp (or .cc) depending on your usage + +If you want to start using LodePNG right away without reading this doc, get the +examples from the LodePNG website to see how to use it in code, or check the +smaller examples in chapter 13 here. + +LodePNG is simple but only supports the basic requirements. To achieve +simplicity, the following design choices were made: There are no dependencies +on any external library. There are functions to decode and encode a PNG with +a single function call, and extended versions of these functions taking a +LodePNGState struct allowing to specify or get more information. By default +the colors of the raw image are always RGB or RGBA, no matter what color type +the PNG file uses. To read and write files, there are simple functions to +convert the files to/from buffers in memory. + +This all makes LodePNG suitable for loading textures in games, demos and small +programs, ... It's less suitable for full fledged image editors, loading PNGs +over network (it requires all the image data to be available before decoding can +begin), life-critical systems, ... + +1.1. supported features +----------------------- + +The following features are supported by the decoder: + +*) decoding of PNGs with any color type, bit depth and interlace mode, to a 24- or 32-bit color raw image, + or the same color type as the PNG +*) encoding of PNGs, from any raw image to 24- or 32-bit color, or the same color type as the raw image +*) Adam7 interlace and deinterlace for any color type +*) loading the image from harddisk or decoding it from a buffer from other sources than harddisk +*) support for alpha channels, including RGBA color model, translucent palettes and color keying +*) zlib decompression (inflate) +*) zlib compression (deflate) +*) CRC32 and ADLER32 checksums +*) handling of unknown chunks, allowing making a PNG editor that stores custom and unknown chunks. +*) the following chunks are supported (generated/interpreted) by both encoder and decoder: + IHDR: header information + PLTE: color palette + IDAT: pixel data + IEND: the final chunk + tRNS: transparency for palettized images + tEXt: textual information + zTXt: compressed textual information + iTXt: international textual information + bKGD: suggested background color + pHYs: physical dimensions + tIME: modification time + +1.2. features not supported +--------------------------- + +The following features are _not_ supported: + +*) some features needed to make a conformant PNG-Editor might be still missing. +*) partial loading/stream processing. All data must be available and is processed in one call. +*) The following public chunks are not supported but treated as unknown chunks by LodePNG + cHRM, gAMA, iCCP, sRGB, sBIT, hIST, sPLT + Some of these are not supported on purpose: LodePNG wants to provide the RGB values + stored in the pixels, not values modified by system dependent gamma or color models. + + +2. C and C++ version +-------------------- + +The C version uses buffers allocated with alloc that you need to free() +yourself. You need to use init and cleanup functions for each struct whenever +using a struct from the C version to avoid exploits and memory leaks. + +The C++ version has extra functions with std::vectors in the interface and the +lodepng::State class which is a LodePNGState with constructor and destructor. + +These files work without modification for both C and C++ compilers because all +the additional C++ code is in "#ifdef __cplusplus" blocks that make C-compilers +ignore it, and the C code is made to compile both with strict ISO C90 and C++. + +To use the C++ version, you need to rename the source file to lodepng.cpp +(instead of lodepng.c), and compile it with a C++ compiler. + +To use the C version, you need to rename the source file to lodepng.c (instead +of lodepng.cpp), and compile it with a C compiler. + + +3. Security +----------- + +Even if carefully designed, it's always possible that LodePNG contains possible +exploits. If you discover one, please let me know, and it will be fixed. + +When using LodePNG, care has to be taken with the C version of LodePNG, as well +as the C-style structs when working with C++. The following conventions are used +for all C-style structs: + +-if a struct has a corresponding init function, always call the init function when making a new one +-if a struct has a corresponding cleanup function, call it before the struct disappears to avoid memory leaks +-if a struct has a corresponding copy function, use the copy function instead of "=". + The destination must also be inited already. + + +4. Decoding +----------- + +Decoding converts a PNG compressed image to a raw pixel buffer. + +Most documentation on using the decoder is at its declarations in the header +above. For C, simple decoding can be done with functions such as +lodepng_decode32, and more advanced decoding can be done with the struct +LodePNGState and lodepng_decode. For C++, all decoding can be done with the +various lodepng::decode functions, and lodepng::State can be used for advanced +features. + +When using the LodePNGState, it uses the following fields for decoding: +*) LodePNGInfo info_png: it stores extra information about the PNG (the input) in here +*) LodePNGColorMode info_raw: here you can say what color mode of the raw image (the output) you want to get +*) LodePNGDecoderSettings decoder: you can specify a few extra settings for the decoder to use + +LodePNGInfo info_png +-------------------- + +After decoding, this contains extra information of the PNG image, except the actual +pixels, width and height because these are already gotten directly from the decoder +functions. + +It contains for example the original color type of the PNG image, text comments, +suggested background color, etc... More details about the LodePNGInfo struct are +at its declaration documentation. + +LodePNGColorMode info_raw +------------------------- + +When decoding, here you can specify which color type you want +the resulting raw image to be. If this is different from the colortype of the +PNG, then the decoder will automatically convert the result. This conversion +always works, except if you want it to convert a color PNG to greyscale or to +a palette with missing colors. + +By default, 32-bit color is used for the result. + +LodePNGDecoderSettings decoder +------------------------------ + +The settings can be used to ignore the errors created by invalid CRC and Adler32 +chunks, and to disable the decoding of tEXt chunks. + +There's also a setting color_convert, true by default. If false, no conversion +is done, the resulting data will be as it was in the PNG (after decompression) +and you'll have to puzzle the colors of the pixels together yourself using the +color type information in the LodePNGInfo. + + +5. Encoding +----------- + +Encoding converts a raw pixel buffer to a PNG compressed image. + +Most documentation on using the encoder is at its declarations in the header +above. For C, simple encoding can be done with functions such as +lodepng_encode32, and more advanced decoding can be done with the struct +LodePNGState and lodepng_encode. For C++, all encoding can be done with the +various lodepng::encode functions, and lodepng::State can be used for advanced +features. + +Like the decoder, the encoder can also give errors. However it gives less errors +since the encoder input is trusted, the decoder input (a PNG image that could +be forged by anyone) is not trusted. + +When using the LodePNGState, it uses the following fields for encoding: +*) LodePNGInfo info_png: here you specify how you want the PNG (the output) to be. +*) LodePNGColorMode info_raw: here you say what color type of the raw image (the input) has +*) LodePNGEncoderSettings encoder: you can specify a few settings for the encoder to use + +LodePNGInfo info_png +-------------------- + +When encoding, you use this the opposite way as when decoding: for encoding, +you fill in the values you want the PNG to have before encoding. By default it's +not needed to specify a color type for the PNG since it's automatically chosen, +but it's possible to choose it yourself given the right settings. + +The encoder will not always exactly match the LodePNGInfo struct you give, +it tries as close as possible. Some things are ignored by the encoder. The +encoder uses, for example, the following settings from it when applicable: +colortype and bitdepth, text chunks, time chunk, the color key, the palette, the +background color, the interlace method, unknown chunks, ... + +When encoding to a PNG with colortype 3, the encoder will generate a PLTE chunk. +If the palette contains any colors for which the alpha channel is not 255 (so +there are translucent colors in the palette), it'll add a tRNS chunk. + +LodePNGColorMode info_raw +------------------------- + +You specify the color type of the raw image that you give to the input here, +including a possible transparent color key and palette you happen to be using in +your raw image data. + +By default, 32-bit color is assumed, meaning your input has to be in RGBA +format with 4 bytes (unsigned chars) per pixel. + +LodePNGEncoderSettings encoder +------------------------------ + +The following settings are supported (some are in sub-structs): +*) auto_convert: when this option is enabled, the encoder will +automatically choose the smallest possible color mode (including color key) that +can encode the colors of all pixels without information loss. +*) btype: the block type for LZ77. 0 = uncompressed, 1 = fixed huffman tree, + 2 = dynamic huffman tree (best compression). Should be 2 for proper + compression. +*) use_lz77: whether or not to use LZ77 for compressed block types. Should be + true for proper compression. +*) windowsize: the window size used by the LZ77 encoder (1 - 32768). Has value + 2048 by default, but can be set to 32768 for better, but slow, compression. +*) force_palette: if colortype is 2 or 6, you can make the encoder write a PLTE + chunk if force_palette is true. This can used as suggested palette to convert + to by viewers that don't support more than 256 colors (if those still exist) +*) add_id: add text chunk "Encoder: LodePNG " to the image. +*) text_compression: default 1. If 1, it'll store texts as zTXt instead of tEXt chunks. + zTXt chunks use zlib compression on the text. This gives a smaller result on + large texts but a larger result on small texts (such as a single program name). + It's all tEXt or all zTXt though, there's no separate setting per text yet. + + +6. color conversions +-------------------- + +An important thing to note about LodePNG, is that the color type of the PNG, and +the color type of the raw image, are completely independent. By default, when +you decode a PNG, you get the result as a raw image in the color type you want, +no matter whether the PNG was encoded with a palette, greyscale or RGBA color. +And if you encode an image, by default LodePNG will automatically choose the PNG +color type that gives good compression based on the values of colors and amount +of colors in the image. It can be configured to let you control it instead as +well, though. + +To be able to do this, LodePNG does conversions from one color mode to another. +It can convert from almost any color type to any other color type, except the +following conversions: RGB to greyscale is not supported, and converting to a +palette when the palette doesn't have a required color is not supported. This is +not supported on purpose: this is information loss which requires a color +reduction algorithm that is beyong the scope of a PNG encoder (yes, RGB to grey +is easy, but there are multiple ways if you want to give some channels more +weight). + +By default, when decoding, you get the raw image in 32-bit RGBA or 24-bit RGB +color, no matter what color type the PNG has. And by default when encoding, +LodePNG automatically picks the best color model for the output PNG, and expects +the input image to be 32-bit RGBA or 24-bit RGB. So, unless you want to control +the color format of the images yourself, you can skip this chapter. + +6.1. PNG color types +-------------------- + +A PNG image can have many color types, ranging from 1-bit color to 64-bit color, +as well as palettized color modes. After the zlib decompression and unfiltering +in the PNG image is done, the raw pixel data will have that color type and thus +a certain amount of bits per pixel. If you want the output raw image after +decoding to have another color type, a conversion is done by LodePNG. + +The PNG specification gives the following color types: + +0: greyscale, bit depths 1, 2, 4, 8, 16 +2: RGB, bit depths 8 and 16 +3: palette, bit depths 1, 2, 4 and 8 +4: greyscale with alpha, bit depths 8 and 16 +6: RGBA, bit depths 8 and 16 + +Bit depth is the amount of bits per pixel per color channel. So the total amount +of bits per pixel is: amount of channels * bitdepth. + +6.2. color conversions +---------------------- + +As explained in the sections about the encoder and decoder, you can specify +color types and bit depths in info_png and info_raw to change the default +behaviour. + +If, when decoding, you want the raw image to be something else than the default, +you need to set the color type and bit depth you want in the LodePNGColorMode, +or the parameters colortype and bitdepth of the simple decoding function. + +If, when encoding, you use another color type than the default in the raw input +image, you need to specify its color type and bit depth in the LodePNGColorMode +of the raw image, or use the parameters colortype and bitdepth of the simple +encoding function. + +If, when encoding, you don't want LodePNG to choose the output PNG color type +but control it yourself, you need to set auto_convert in the encoder settings +to false, and specify the color type you want in the LodePNGInfo of the +encoder (including palette: it can generate a palette if auto_convert is true, +otherwise not). + +If the input and output color type differ (whether user chosen or auto chosen), +LodePNG will do a color conversion, which follows the rules below, and may +sometimes result in an error. + +To avoid some confusion: +-the decoder converts from PNG to raw image +-the encoder converts from raw image to PNG +-the colortype and bitdepth in LodePNGColorMode info_raw, are those of the raw image +-the colortype and bitdepth in the color field of LodePNGInfo info_png, are those of the PNG +-when encoding, the color type in LodePNGInfo is ignored if auto_convert + is enabled, it is automatically generated instead +-when decoding, the color type in LodePNGInfo is set by the decoder to that of the original + PNG image, but it can be ignored since the raw image has the color type you requested instead +-if the color type of the LodePNGColorMode and PNG image aren't the same, a conversion + between the color types is done if the color types are supported. If it is not + supported, an error is returned. If the types are the same, no conversion is done. +-even though some conversions aren't supported, LodePNG supports loading PNGs from any + colortype and saving PNGs to any colortype, sometimes it just requires preparing + the raw image correctly before encoding. +-both encoder and decoder use the same color converter. + +Non supported color conversions: +-color to greyscale: no error is thrown, but the result will look ugly because +only the red channel is taken +-anything to palette when that palette does not have that color in it: in this +case an error is thrown + +Supported color conversions: +-anything to 8-bit RGB, 8-bit RGBA, 16-bit RGB, 16-bit RGBA +-any grey or grey+alpha, to grey or grey+alpha +-anything to a palette, as long as the palette has the requested colors in it +-removing alpha channel +-higher to smaller bitdepth, and vice versa + +If you want no color conversion to be done (e.g. for speed or control): +-In the encoder, you can make it save a PNG with any color type by giving the +raw color mode and LodePNGInfo the same color mode, and setting auto_convert to +false. +-In the decoder, you can make it store the pixel data in the same color type +as the PNG has, by setting the color_convert setting to false. Settings in +info_raw are then ignored. + +The function lodepng_convert does the color conversion. It is available in the +interface but normally isn't needed since the encoder and decoder already call +it. + +6.3. padding bits +----------------- + +In the PNG file format, if a less than 8-bit per pixel color type is used and the scanlines +have a bit amount that isn't a multiple of 8, then padding bits are used so that each +scanline starts at a fresh byte. But that is NOT true for the LodePNG raw input and output. +The raw input image you give to the encoder, and the raw output image you get from the decoder +will NOT have these padding bits, e.g. in the case of a 1-bit image with a width +of 7 pixels, the first pixel of the second scanline will the the 8th bit of the first byte, +not the first bit of a new byte. + +6.4. A note about 16-bits per channel and endianness +---------------------------------------------------- + +LodePNG uses unsigned char arrays for 16-bit per channel colors too, just like +for any other color format. The 16-bit values are stored in big endian (most +significant byte first) in these arrays. This is the opposite order of the +little endian used by x86 CPU's. + +LodePNG always uses big endian because the PNG file format does so internally. +Conversions to other formats than PNG uses internally are not supported by +LodePNG on purpose, there are myriads of formats, including endianness of 16-bit +colors, the order in which you store R, G, B and A, and so on. Supporting and +converting to/from all that is outside the scope of LodePNG. + +This may mean that, depending on your use case, you may want to convert the big +endian output of LodePNG to little endian with a for loop. This is certainly not +always needed, many applications and libraries support big endian 16-bit colors +anyway, but it means you cannot simply cast the unsigned char* buffer to an +unsigned short* buffer on x86 CPUs. + + +7. error values +--------------- + +All functions in LodePNG that return an error code, return 0 if everything went +OK, or a non-zero code if there was an error. + +The meaning of the LodePNG error values can be retrieved with the function +lodepng_error_text: given the numerical error code, it returns a description +of the error in English as a string. + +Check the implementation of lodepng_error_text to see the meaning of each code. + + +8. chunks and PNG editing +------------------------- + +If you want to add extra chunks to a PNG you encode, or use LodePNG for a PNG +editor that should follow the rules about handling of unknown chunks, or if your +program is able to read other types of chunks than the ones handled by LodePNG, +then that's possible with the chunk functions of LodePNG. + +A PNG chunk has the following layout: + +4 bytes length +4 bytes type name +length bytes data +4 bytes CRC + +8.1. iterating through chunks +----------------------------- + +If you have a buffer containing the PNG image data, then the first chunk (the +IHDR chunk) starts at byte number 8 of that buffer. The first 8 bytes are the +signature of the PNG and are not part of a chunk. But if you start at byte 8 +then you have a chunk, and can check the following things of it. + +NOTE: none of these functions check for memory buffer boundaries. To avoid +exploits, always make sure the buffer contains all the data of the chunks. +When using lodepng_chunk_next, make sure the returned value is within the +allocated memory. + +unsigned lodepng_chunk_length(const unsigned char* chunk): + +Get the length of the chunk's data. The total chunk length is this length + 12. + +void lodepng_chunk_type(char type[5], const unsigned char* chunk): +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type): + +Get the type of the chunk or compare if it's a certain type + +unsigned char lodepng_chunk_critical(const unsigned char* chunk): +unsigned char lodepng_chunk_private(const unsigned char* chunk): +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk): + +Check if the chunk is critical in the PNG standard (only IHDR, PLTE, IDAT and IEND are). +Check if the chunk is private (public chunks are part of the standard, private ones not). +Check if the chunk is safe to copy. If it's not, then, when modifying data in a critical +chunk, unsafe to copy chunks of the old image may NOT be saved in the new one if your +program doesn't handle that type of unknown chunk. + +unsigned char* lodepng_chunk_data(unsigned char* chunk): +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk): + +Get a pointer to the start of the data of the chunk. + +unsigned lodepng_chunk_check_crc(const unsigned char* chunk): +void lodepng_chunk_generate_crc(unsigned char* chunk): + +Check if the crc is correct or generate a correct one. + +unsigned char* lodepng_chunk_next(unsigned char* chunk): +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk): + +Iterate to the next chunk. This works if you have a buffer with consecutive chunks. Note that these +functions do no boundary checking of the allocated data whatsoever, so make sure there is enough +data available in the buffer to be able to go to the next chunk. + +unsigned lodepng_chunk_append(unsigned char** out, size_t* outlength, const unsigned char* chunk): +unsigned lodepng_chunk_create(unsigned char** out, size_t* outlength, unsigned length, + const char* type, const unsigned char* data): + +These functions are used to create new chunks that are appended to the data in *out that has +length *outlength. The append function appends an existing chunk to the new data. The create +function creates a new chunk with the given parameters and appends it. Type is the 4-letter +name of the chunk. + +8.2. chunks in info_png +----------------------- + +The LodePNGInfo struct contains fields with the unknown chunk in it. It has 3 +buffers (each with size) to contain 3 types of unknown chunks: +the ones that come before the PLTE chunk, the ones that come between the PLTE +and the IDAT chunks, and the ones that come after the IDAT chunks. +It's necessary to make the distionction between these 3 cases because the PNG +standard forces to keep the ordering of unknown chunks compared to the critical +chunks, but does not force any other ordering rules. + +info_png.unknown_chunks_data[0] is the chunks before PLTE +info_png.unknown_chunks_data[1] is the chunks after PLTE, before IDAT +info_png.unknown_chunks_data[2] is the chunks after IDAT + +The chunks in these 3 buffers can be iterated through and read by using the same +way described in the previous subchapter. + +When using the decoder to decode a PNG, you can make it store all unknown chunks +if you set the option settings.remember_unknown_chunks to 1. By default, this +option is off (0). + +The encoder will always encode unknown chunks that are stored in the info_png. +If you need it to add a particular chunk that isn't known by LodePNG, you can +use lodepng_chunk_append or lodepng_chunk_create to the chunk data in +info_png.unknown_chunks_data[x]. + +Chunks that are known by LodePNG should not be added in that way. E.g. to make +LodePNG add a bKGD chunk, set background_defined to true and add the correct +parameters there instead. + + +9. compiler support +------------------- + +No libraries other than the current standard C library are needed to compile +LodePNG. For the C++ version, only the standard C++ library is needed on top. +Add the files lodepng.c(pp) and lodepng.h to your project, include +lodepng.h where needed, and your program can read/write PNG files. + +It is compatible with C90 and up, and C++03 and up. + +If performance is important, use optimization when compiling! For both the +encoder and decoder, this makes a large difference. + +Make sure that LodePNG is compiled with the same compiler of the same version +and with the same settings as the rest of the program, or the interfaces with +std::vectors and std::strings in C++ can be incompatible. + +CHAR_BITS must be 8 or higher, because LodePNG uses unsigned chars for octets. + +*) gcc and g++ + +LodePNG is developed in gcc so this compiler is natively supported. It gives no +warnings with compiler options "-Wall -Wextra -pedantic -ansi", with gcc and g++ +version 4.7.1 on Linux, 32-bit and 64-bit. + +*) Clang + +Fully supported and warning-free. + +*) Mingw + +The Mingw compiler (a port of gcc for Windows) should be fully supported by +LodePNG. + +*) Visual Studio and Visual C++ Express Edition + +LodePNG should be warning-free with warning level W4. Two warnings were disabled +with pragmas though: warning 4244 about implicit conversions, and warning 4996 +where it wants to use a non-standard function fopen_s instead of the standard C +fopen. + +Visual Studio may want "stdafx.h" files to be included in each source file and +give an error "unexpected end of file while looking for precompiled header". +This is not standard C++ and will not be added to the stock LodePNG. You can +disable it for lodepng.cpp only by right clicking it, Properties, C/C++, +Precompiled Headers, and set it to Not Using Precompiled Headers there. + +NOTE: Modern versions of VS should be fully supported, but old versions, e.g. +VS6, are not guaranteed to work. + +*) Compilers on Macintosh + +LodePNG has been reported to work both with gcc and LLVM for Macintosh, both for +C and C++. + +*) Other Compilers + +If you encounter problems on any compilers, feel free to let me know and I may +try to fix it if the compiler is modern and standards complient. + + +10. examples +------------ + +This decoder example shows the most basic usage of LodePNG. More complex +examples can be found on the LodePNG website. + +10.1. decoder C++ example +------------------------- + +#include "lodepng.h" +#include + +int main(int argc, char *argv[]) +{ + const char* filename = argc > 1 ? argv[1] : "test.png"; + + //load and decode + std::vector image; + unsigned width, height; + unsigned error = lodepng::decode(image, width, height, filename); + + //if there's an error, display it + if(error) std::cout << "decoder error " << error << ": " << lodepng_error_text(error) << std::endl; + + //the pixels are now in the vector "image", 4 bytes per pixel, ordered RGBARGBA..., use it as texture, draw it, ... +} + +10.2. decoder C example +----------------------- + +#include "lodepng.h" + +int main(int argc, char *argv[]) +{ + unsigned error; + unsigned char* image; + size_t width, height; + const char* filename = argc > 1 ? argv[1] : "test.png"; + + error = lodepng_decode32_file(&image, &width, &height, filename); + + if(error) printf("decoder error %u: %s\n", error, lodepng_error_text(error)); + + / * use image here * / + + free(image); + return 0; +} + + +11. changes +----------- + +The version number of LodePNG is the date of the change given in the format +yyyymmdd. + +Some changes aren't backwards compatible. Those are indicated with a (!) +symbol. + +*) 18 apr 2015: Boundary PM instead of just package-merge for faster encoding. +*) 23 aug 2014: Reduced needless memory usage of decoder. +*) 28 jun 2014: Removed fix_png setting, always support palette OOB for + simplicity. Made ColorProfile public. +*) 09 jun 2014: Faster encoder by fixing hash bug and more zeros optimization. +*) 22 dec 2013: Power of two windowsize required for optimization. +*) 15 apr 2013: Fixed bug with LAC_ALPHA and color key. +*) 25 mar 2013: Added an optional feature to ignore some PNG errors (fix_png). +*) 11 mar 2013 (!): Bugfix with custom free. Changed from "my" to "lodepng_" + prefix for the custom allocators and made it possible with a new #define to + use custom ones in your project without needing to change lodepng's code. +*) 28 jan 2013: Bugfix with color key. +*) 27 okt 2012: Tweaks in text chunk keyword length error handling. +*) 8 okt 2012 (!): Added new filter strategy (entropy) and new auto color mode. + (no palette). Better deflate tree encoding. New compression tweak settings. + Faster color conversions while decoding. Some internal cleanups. +*) 23 sep 2012: Reduced warnings in Visual Studio a little bit. +*) 1 sep 2012 (!): Removed #define's for giving custom (de)compression functions + and made it work with function pointers instead. +*) 23 jun 2012: Added more filter strategies. Made it easier to use custom alloc + and free functions and toggle #defines from compiler flags. Small fixes. +*) 6 may 2012 (!): Made plugging in custom zlib/deflate functions more flexible. +*) 22 apr 2012 (!): Made interface more consistent, renaming a lot. Removed + redundant C++ codec classes. Reduced amount of structs. Everything changed, + but it is cleaner now imho and functionality remains the same. Also fixed + several bugs and shrinked the implementation code. Made new samples. +*) 6 nov 2011 (!): By default, the encoder now automatically chooses the best + PNG color model and bit depth, based on the amount and type of colors of the + raw image. For this, autoLeaveOutAlphaChannel replaced by auto_choose_color. +*) 9 okt 2011: simpler hash chain implementation for the encoder. +*) 8 sep 2011: lz77 encoder lazy matching instead of greedy matching. +*) 23 aug 2011: tweaked the zlib compression parameters after benchmarking. + A bug with the PNG filtertype heuristic was fixed, so that it chooses much + better ones (it's quite significant). A setting to do an experimental, slow, + brute force search for PNG filter types is added. +*) 17 aug 2011 (!): changed some C zlib related function names. +*) 16 aug 2011: made the code less wide (max 120 characters per line). +*) 17 apr 2011: code cleanup. Bugfixes. Convert low to 16-bit per sample colors. +*) 21 feb 2011: fixed compiling for C90. Fixed compiling with sections disabled. +*) 11 dec 2010: encoding is made faster, based on suggestion by Peter Eastman + to optimize long sequences of zeros. +*) 13 nov 2010: added LodePNG_InfoColor_hasPaletteAlpha and + LodePNG_InfoColor_canHaveAlpha functions for convenience. +*) 7 nov 2010: added LodePNG_error_text function to get error code description. +*) 30 okt 2010: made decoding slightly faster +*) 26 okt 2010: (!) changed some C function and struct names (more consistent). + Reorganized the documentation and the declaration order in the header. +*) 08 aug 2010: only changed some comments and external samples. +*) 05 jul 2010: fixed bug thanks to warnings in the new gcc version. +*) 14 mar 2010: fixed bug where too much memory was allocated for char buffers. +*) 02 sep 2008: fixed bug where it could create empty tree that linux apps could + read by ignoring the problem but windows apps couldn't. +*) 06 jun 2008: added more error checks for out of memory cases. +*) 26 apr 2008: added a few more checks here and there to ensure more safety. +*) 06 mar 2008: crash with encoding of strings fixed +*) 02 feb 2008: support for international text chunks added (iTXt) +*) 23 jan 2008: small cleanups, and #defines to divide code in sections +*) 20 jan 2008: support for unknown chunks allowing using LodePNG for an editor. +*) 18 jan 2008: support for tIME and pHYs chunks added to encoder and decoder. +*) 17 jan 2008: ability to encode and decode compressed zTXt chunks added + Also vareous fixes, such as in the deflate and the padding bits code. +*) 13 jan 2008: Added ability to encode Adam7-interlaced images. Improved + filtering code of encoder. +*) 07 jan 2008: (!) changed LodePNG to use ISO C90 instead of C++. A + C++ wrapper around this provides an interface almost identical to before. + Having LodePNG be pure ISO C90 makes it more portable. The C and C++ code + are together in these files but it works both for C and C++ compilers. +*) 29 dec 2007: (!) changed most integer types to unsigned int + other tweaks +*) 30 aug 2007: bug fixed which makes this Borland C++ compatible +*) 09 aug 2007: some VS2005 warnings removed again +*) 21 jul 2007: deflate code placed in new namespace separate from zlib code +*) 08 jun 2007: fixed bug with 2- and 4-bit color, and small interlaced images +*) 04 jun 2007: improved support for Visual Studio 2005: crash with accessing + invalid std::vector element [0] fixed, and level 3 and 4 warnings removed +*) 02 jun 2007: made the encoder add a tag with version by default +*) 27 may 2007: zlib and png code separated (but still in the same file), + simple encoder/decoder functions added for more simple usage cases +*) 19 may 2007: minor fixes, some code cleaning, new error added (error 69), + moved some examples from here to lodepng_examples.cpp +*) 12 may 2007: palette decoding bug fixed +*) 24 apr 2007: changed the license from BSD to the zlib license +*) 11 mar 2007: very simple addition: ability to encode bKGD chunks. +*) 04 mar 2007: (!) tEXt chunk related fixes, and support for encoding + palettized PNG images. Plus little interface change with palette and texts. +*) 03 mar 2007: Made it encode dynamic Huffman shorter with repeat codes. + Fixed a bug where the end code of a block had length 0 in the Huffman tree. +*) 26 feb 2007: Huffman compression with dynamic trees (BTYPE 2) now implemented + and supported by the encoder, resulting in smaller PNGs at the output. +*) 27 jan 2007: Made the Adler-32 test faster so that a timewaste is gone. +*) 24 jan 2007: gave encoder an error interface. Added color conversion from any + greyscale type to 8-bit greyscale with or without alpha. +*) 21 jan 2007: (!) Totally changed the interface. It allows more color types + to convert to and is more uniform. See the manual for how it works now. +*) 07 jan 2007: Some cleanup & fixes, and a few changes over the last days: + encode/decode custom tEXt chunks, separate classes for zlib & deflate, and + at last made the decoder give errors for incorrect Adler32 or Crc. +*) 01 jan 2007: Fixed bug with encoding PNGs with less than 8 bits per channel. +*) 29 dec 2006: Added support for encoding images without alpha channel, and + cleaned out code as well as making certain parts faster. +*) 28 dec 2006: Added "Settings" to the encoder. +*) 26 dec 2006: The encoder now does LZ77 encoding and produces much smaller files now. + Removed some code duplication in the decoder. Fixed little bug in an example. +*) 09 dec 2006: (!) Placed output parameters of public functions as first parameter. + Fixed a bug of the decoder with 16-bit per color. +*) 15 okt 2006: Changed documentation structure +*) 09 okt 2006: Encoder class added. It encodes a valid PNG image from the + given image buffer, however for now it's not compressed. +*) 08 sep 2006: (!) Changed to interface with a Decoder class +*) 30 jul 2006: (!) LodePNG_InfoPng , width and height are now retrieved in different + way. Renamed decodePNG to decodePNGGeneric. +*) 29 jul 2006: (!) Changed the interface: image info is now returned as a + struct of type LodePNG::LodePNG_Info, instead of a vector, which was a bit clumsy. +*) 28 jul 2006: Cleaned the code and added new error checks. + Corrected terminology "deflate" into "inflate". +*) 23 jun 2006: Added SDL example in the documentation in the header, this + example allows easy debugging by displaying the PNG and its transparency. +*) 22 jun 2006: (!) Changed way to obtain error value. Added + loadFile function for convenience. Made decodePNG32 faster. +*) 21 jun 2006: (!) Changed type of info vector to unsigned. + Changed position of palette in info vector. Fixed an important bug that + happened on PNGs with an uncompressed block. +*) 16 jun 2006: Internally changed unsigned into unsigned where + needed, and performed some optimizations. +*) 07 jun 2006: (!) Renamed functions to decodePNG and placed them + in LodePNG namespace. Changed the order of the parameters. Rewrote the + documentation in the header. Renamed files to lodepng.cpp and lodepng.h +*) 22 apr 2006: Optimized and improved some code +*) 07 sep 2005: (!) Changed to std::vector interface +*) 12 aug 2005: Initial release (C++, decoder only) + + +12. contact information +----------------------- + +Feel free to contact me with suggestions, problems, comments, ... concerning +LodePNG. If you encounter a PNG image that doesn't work properly with this +decoder, feel free to send it and I'll use it to find and fix the problem. + +My email address is (puzzle the account and domain together with an @ symbol): +Domain: gmail dot com. +Account: lode dot vandevenne. + + +Copyright (c) 2005-2015 Lode Vandevenne +*/ diff --git a/Plugins/Texture/resource.h b/Plugins/Texture/resource.h new file mode 100644 index 0000000000000000000000000000000000000000..bbd7b105ea7d187eb0e68d0e0e9f82aac5031de4 GIT binary patch literal 4390 zcmbuDTTk0S5Ju;@QvZXcK2|DVdVMrbfCwj{IOg&caZ5#oKuSn2s`}U4p7F#ogm`!3 z$}-+H>*Mk4e6u_G`)5NolOEIxO2&#LHbBz;?i)g6v8*FN7flp_ey8poDw%2xS?CURF!Ta#la zxr5b-WLUk24dGU+&D&10G86ogGp8et9`n3M&-V^LIfbmA{wzgldk^}VmmBeYoAS2N z=9C%Cu`z+&4WK>#4kf$h(AXomr?Lxwo%k?z^DG*>kADv#C;WYEnY(T5=6N*s5q9fL zCbC1Mg!;zVK7ZHjtc=1F_#N*T82g=PF3sN>;e9W$_LH&Sd)T{BGv=|2+WW+#=CYd6 z)H3!54?7_Wd&J@}fLOoD*dIM?i<2GrNq`nBy0Jfb*eCcx<1gGF%2&3YO_|kVFvH?K z*~jA+<6$Z?c5`l3wyr;IHNPcpU8X3ejq1&ApDvgET?EC8vE8~%LU5)M2 zW&2ze2k3Iy?hy5u-ELi~Pg2%~oJ^R|3RxGbJQ#cSCtbL0^I$A)U8)jo$L5;3=l?UG zxOJKT7u&7NRWTT%Otags%XN2|^<27j*iU9jNvQ=}stJB{BT-=E4sC8CTrPV|qlryd0>!#aG gug>nP*yc%{3pZE2qU +#endif // STBI_NO_STDIO + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for desired_channels + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +#include +typedef unsigned char stbi_uc; +typedef unsigned short stbi_us; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef STBIDEF +#ifdef STB_IMAGE_STATIC +#define STBIDEF static +#else +#define STBIDEF extern +#endif +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// PRIMARY API - works on images of any type +// + +// +// load image by filename, open file, or memory buffer +// + +typedef struct +{ + int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int (*eof) (void *user); // returns nonzero if we are at end of file/data +} stbi_io_callbacks; + +//////////////////////////////////// +// +// 8-bits-per-channel interface +// + +STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +#endif + +#ifdef STBI_WINDOWS_UTF8 +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif + +//////////////////////////////////// +// +// 16-bits-per-channel interface +// + +STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +#endif + +//////////////////////////////////// +// +// float-per-channel interface +// +#ifndef STBI_NO_LINEAR + STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + + #ifndef STBI_NO_STDIO + STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); + #endif +#endif + +#ifndef STBI_NO_HDR + STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); + STBIDEF void stbi_hdr_to_ldr_scale(float scale); +#endif // STBI_NO_HDR + +#ifndef STBI_NO_LINEAR + STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); + STBIDEF void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_LINEAR + +// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename); +STBIDEF int stbi_is_hdr_from_file(FILE *f); +#endif // STBI_NO_STDIO + + +// get a VERY brief reason for failure +// on most compilers (and ALL modern mainstream compilers) this is threadsafe +STBIDEF const char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +STBIDEF void stbi_image_free (void *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len); +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user); + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit (char const *filename); +STBIDEF int stbi_is_16_bit_from_file(FILE *f); +#endif + + + +// for image formats that explicitly notate that they have premultiplied alpha, +// we just return the colors as stored in the file. set this flag to force +// unpremultiplication. results are undefined if the unpremultiply overflow. +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); + +// indicate whether we should process iphone images back to canonical format, +// or just pass them through "as-is" +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + +// flip the image vertically, so the first pixel in the output array is the bottom left +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); + +// as above, but only applies to images loaded on the thread that calls the function +// this function is only available if your compiler supports thread-local variables; +// calling it will fail to link if your compiler doesn't +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip); + +// ZLIB client - used by PNG, available for other purposes + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); +STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifdef STB_IMAGE_IMPLEMENTATION + +#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ + || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ + || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ + || defined(STBI_ONLY_ZLIB) + #ifndef STBI_ONLY_JPEG + #define STBI_NO_JPEG + #endif + #ifndef STBI_ONLY_PNG + #define STBI_NO_PNG + #endif + #ifndef STBI_ONLY_BMP + #define STBI_NO_BMP + #endif + #ifndef STBI_ONLY_PSD + #define STBI_NO_PSD + #endif + #ifndef STBI_ONLY_TGA + #define STBI_NO_TGA + #endif + #ifndef STBI_ONLY_GIF + #define STBI_NO_GIF + #endif + #ifndef STBI_ONLY_HDR + #define STBI_NO_HDR + #endif + #ifndef STBI_ONLY_PIC + #define STBI_NO_PIC + #endif + #ifndef STBI_ONLY_PNM + #define STBI_NO_PNM + #endif +#endif + +#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) +#define STBI_NO_ZLIB +#endif + + +#include +#include // ptrdiff_t on osx +#include +#include +#include + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#include // ldexp, pow +#endif + +#ifndef STBI_NO_STDIO +#include +#endif + +#ifndef STBI_ASSERT +#include +#define STBI_ASSERT(x) assert(x) +#endif + +#ifdef __cplusplus +#define STBI_EXTERN extern "C" +#else +#define STBI_EXTERN extern +#endif + + +#ifndef _MSC_VER + #ifdef __cplusplus + #define stbi_inline inline + #else + #define stbi_inline + #endif +#else + #define stbi_inline __forceinline +#endif + +#ifndef STBI_NO_THREAD_LOCALS + #if defined(__cplusplus) && __cplusplus >= 201103L + #define STBI_THREAD_LOCAL thread_local + #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L + #define STBI_THREAD_LOCAL _Thread_local + #elif defined(__GNUC__) + #define STBI_THREAD_LOCAL __thread + #elif defined(_MSC_VER) + #define STBI_THREAD_LOCAL __declspec(thread) +#endif +#endif + +#ifdef _MSC_VER +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include +typedef uint16_t stbi__uint16; +typedef int16_t stbi__int16; +typedef uint32_t stbi__uint32; +typedef int32_t stbi__int32; +#endif + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBI_NOTUSED(v) (void)(v) +#else +#define STBI_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef _MSC_VER +#define STBI_HAS_LROTL +#endif + +#ifdef STBI_HAS_LROTL + #define stbi_lrot(x,y) _lrotl(x,y) +#else + #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (32 - (y)))) +#endif + +#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) +// ok +#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." +#endif + +#ifndef STBI_MALLOC +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_REALLOC(p,newsz) realloc(p,newsz) +#define STBI_FREE(p) free(p) +#endif + +#ifndef STBI_REALLOC_SIZED +#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) +#endif + +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// which in turn means it gets to use SSE2 everywhere. This is unfortunate, +// but previous attempts to provide the SSE2 functions with runtime +// detection caused numerous issues. The way architecture extensions are +// exposed in GCC/Clang is, sadly, not really suited for one-file libs. +// New behavior: if compiled with -msse2, we use SSE2 without any +// detection; if not, we don't use it at all. +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) +// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET +// +// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the +// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. +// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not +// simultaneously enabling "-mstackrealign". +// +// See https://github.com/nothings/stb/issues/81 for more information. +// +// So default to no SSE2 on 32-bit MinGW. If you've read this far and added +// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. +#define STBI_NO_SIMD +#endif + +#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) +#define STBI_SSE2 +#include + +#ifdef _MSC_VER + +#if _MSC_VER >= 1400 // not VC6 +#include // __cpuid +static int stbi__cpuid3(void) +{ + int info[4]; + __cpuid(info,1); + return info[3]; +} +#else +static int stbi__cpuid3(void) +{ + int res; + __asm { + mov eax,1 + cpuid + mov res,edx + } + return res; +} +#endif + +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + int info3 = stbi__cpuid3(); + return ((info3 >> 26) & 1) != 0; +} +#endif + +#else // assume GCC-style if not VC++ +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + // If we're even attempting to compile this on GCC/Clang, that means + // -msse2 is on, which means the compiler is allowed to use SSE2 + // instructions at will, and so are we. + return 1; +} +#endif + +#endif +#endif + +// ARM NEON +#if defined(STBI_NO_SIMD) && defined(STBI_NEON) +#undef STBI_NEON +#endif + +#ifdef STBI_NEON +#include +// assume GCC or Clang on ARM targets +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#endif + +#ifndef STBI_SIMD_ALIGN +#define STBI_SIMD_ALIGN(type, name) type name +#endif + +/////////////////////////////////////////////// +// +// stbi__context struct and start_xxx functions + +// stbi__context structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct +{ + stbi__uint32 img_x, img_y; + int img_n, img_out_n; + + stbi_io_callbacks io; + void *io_user_data; + + int read_from_callbacks; + int buflen; + stbi_uc buffer_start[128]; + + stbi_uc *img_buffer, *img_buffer_end; + stbi_uc *img_buffer_original, *img_buffer_original_end; +} stbi__context; + + +static void stbi__refill_buffer(stbi__context *s); + +// initialize a memory-decode context +static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) +{ + s->io.read = NULL; + s->read_from_callbacks = 0; + s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; + s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; +} + +// initialize a callback-based context +static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) +{ + s->io = *c; + s->io_user_data = user; + s->buflen = sizeof(s->buffer_start); + s->read_from_callbacks = 1; + s->img_buffer_original = s->buffer_start; + stbi__refill_buffer(s); + s->img_buffer_original_end = s->img_buffer_end; +} + +#ifndef STBI_NO_STDIO + +static int stbi__stdio_read(void *user, char *data, int size) +{ + return (int) fread(data,1,size,(FILE*) user); +} + +static void stbi__stdio_skip(void *user, int n) +{ + fseek((FILE*) user, n, SEEK_CUR); +} + +static int stbi__stdio_eof(void *user) +{ + return feof((FILE*) user); +} + +static stbi_io_callbacks stbi__stdio_callbacks = +{ + stbi__stdio_read, + stbi__stdio_skip, + stbi__stdio_eof, +}; + +static void stbi__start_file(stbi__context *s, FILE *f) +{ + stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi__context *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi__rewind(stbi__context *s) +{ + // conceptually rewind SHOULD rewind to the beginning of the stream, + // but we just rewind to the beginning of the initial buffer, because + // we only use it after doing 'test', which only ever looks at at most 92 bytes + s->img_buffer = s->img_buffer_original; + s->img_buffer_end = s->img_buffer_original_end; +} + +enum +{ + STBI_ORDER_RGB, + STBI_ORDER_BGR +}; + +typedef struct +{ + int bits_per_channel; + int num_channels; + int channel_order; +} stbi__result_info; + +#ifndef STBI_NO_JPEG +static int stbi__jpeg_test(stbi__context *s); +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNG +static int stbi__png_test(stbi__context *s); +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__png_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_BMP +static int stbi__bmp_test(stbi__context *s); +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_TGA +static int stbi__tga_test(stbi__context *s); +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s); +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__psd_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_HDR +static int stbi__hdr_test(stbi__context *s); +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_test(stbi__context *s); +static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_GIF +static int stbi__gif_test(stbi__context *s); +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNM +static int stbi__pnm_test(stbi__context *s); +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +static +#ifdef STBI_THREAD_LOCAL +STBI_THREAD_LOCAL +#endif +const char *stbi__g_failure_reason; + +STBIDEF const char *stbi_failure_reason(void) +{ + return stbi__g_failure_reason; +} + +#ifndef STBI_NO_FAILURE_STRINGS +static int stbi__err(const char *str) +{ + stbi__g_failure_reason = str; + return 0; +} +#endif + +static void *stbi__malloc(size_t size) +{ + return STBI_MALLOC(size); +} + +// stb_image uses ints pervasively, including for offset calculations. +// therefore the largest decoded image size we can support with the +// current code, even on 64-bit targets, is INT_MAX. this is not a +// significant limitation for the intended use case. +// +// we do, however, need to make sure our size calculations don't +// overflow. hence a few helper functions for size calculations that +// multiply integers together, making sure that they're non-negative +// and no overflow occurs. + +// return 1 if the sum is valid, 0 on overflow. +// negative terms are considered invalid. +static int stbi__addsizes_valid(int a, int b) +{ + if (b < 0) return 0; + // now 0 <= b <= INT_MAX, hence also + // 0 <= INT_MAX - b <= INTMAX. + // And "a + b <= INT_MAX" (which might overflow) is the + // same as a <= INT_MAX - b (no overflow) + return a <= INT_MAX - b; +} + +// returns 1 if the product is valid, 0 on overflow. +// negative factors are considered invalid. +static int stbi__mul2sizes_valid(int a, int b) +{ + if (a < 0 || b < 0) return 0; + if (b == 0) return 1; // mul-by-0 is always safe + // portable way to check for no overflows in a*b + return a <= INT_MAX/b; +} + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow +static int stbi__mad2sizes_valid(int a, int b, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); +} +#endif + +// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow +static int stbi__mad3sizes_valid(int a, int b, int c, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__addsizes_valid(a*b*c, add); +} + +// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); +} +#endif + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// mallocs with size overflow checking +static void *stbi__malloc_mad2(int a, int b, int add) +{ + if (!stbi__mad2sizes_valid(a, b, add)) return NULL; + return stbi__malloc(a*b + add); +} +#endif + +static void *stbi__malloc_mad3(int a, int b, int c, int add) +{ + if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; + return stbi__malloc(a*b*c + add); +} + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) +{ + if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; + return stbi__malloc(a*b*c*d + add); +} +#endif + +// stbi__err - error +// stbi__errpf - error returning pointer to float +// stbi__errpuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS + #define stbi__err(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define stbi__err(x,y) stbi__err(y) +#else + #define stbi__err(x,y) stbi__err(x) +#endif + +#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) +#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) + +STBIDEF void stbi_image_free(void *retval_from_stbi_load) +{ + STBI_FREE(retval_from_stbi_load); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_HDR +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +static int stbi__vertically_flip_on_load_global = 0; + +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_global = flag_true_if_should_flip; +} + +#ifndef STBI_THREAD_LOCAL +#define stbi__vertically_flip_on_load stbi__vertically_flip_on_load_global +#else +static STBI_THREAD_LOCAL int stbi__vertically_flip_on_load_local, stbi__vertically_flip_on_load_set; + +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_local = flag_true_if_should_flip; + stbi__vertically_flip_on_load_set = 1; +} + +#define stbi__vertically_flip_on_load (stbi__vertically_flip_on_load_set \ + ? stbi__vertically_flip_on_load_local \ + : stbi__vertically_flip_on_load_global) +#endif // STBI_THREAD_LOCAL + +static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields + ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed + ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order + ri->num_channels = 0; + + #ifndef STBI_NO_JPEG + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PNG + if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_BMP + if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_GIF + if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PSD + if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); + #else + STBI_NOTUSED(bpc); + #endif + #ifndef STBI_NO_PIC + if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PNM + if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); + return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + #ifndef STBI_NO_TGA + // test tga last because it's a crappy test! + if (stbi__tga_test(s)) + return stbi__tga_load(s,x,y,comp,req_comp, ri); + #endif + + return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); +} + +static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi_uc *reduced; + + reduced = (stbi_uc *) stbi__malloc(img_len); + if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling + + STBI_FREE(orig); + return reduced; +} + +static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi__uint16 *enlarged; + + enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); + if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff + + STBI_FREE(orig); + return enlarged; +} + +static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel) +{ + int row; + size_t bytes_per_row = (size_t)w * bytes_per_pixel; + stbi_uc temp[2048]; + stbi_uc *bytes = (stbi_uc *)image; + + for (row = 0; row < (h>>1); row++) { + stbi_uc *row0 = bytes + row*bytes_per_row; + stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row; + // swap row0 with row1 + size_t bytes_left = bytes_per_row; + while (bytes_left) { + size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp); + memcpy(temp, row0, bytes_copy); + memcpy(row0, row1, bytes_copy); + memcpy(row1, temp, bytes_copy); + row0 += bytes_copy; + row1 += bytes_copy; + bytes_left -= bytes_copy; + } + } +} + +#ifndef STBI_NO_GIF +static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel) +{ + int slice; + int slice_size = w * h * bytes_per_pixel; + + stbi_uc *bytes = (stbi_uc *)image; + for (slice = 0; slice < z; ++slice) { + stbi__vertical_flip(bytes, w, h, bytes_per_pixel); + bytes += slice_size; + } +} +#endif + +static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); + + if (result == NULL) + return NULL; + + if (ri.bits_per_channel != 8) { + STBI_ASSERT(ri.bits_per_channel == 16); + result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 8; + } + + // @TODO: move stbi__convert_format to here + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc)); + } + + return (unsigned char *) result; +} + +static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); + + if (result == NULL) + return NULL; + + if (ri.bits_per_channel != 16) { + STBI_ASSERT(ri.bits_per_channel == 8); + result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 16; + } + + // @TODO: move stbi__convert_format16 to here + // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16)); + } + + return (stbi__uint16 *) result; +} + +#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR) +static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__vertically_flip_on_load && result != NULL) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(float)); + } +} +#endif + +#ifndef STBI_NO_STDIO + +#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) +STBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); +#endif + +#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbi__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode))) + return 0; + +#if _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + + +STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + unsigned char *result; + if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__uint16 *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + stbi__uint16 *result; + if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file_16(f,x,y,comp,req_comp); + fclose(f); + return result; +} + + +#endif //!STBI_NO_STDIO + +STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_mem(&s,buffer,len); + + result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp); + if (stbi__vertically_flip_on_load) { + stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); + } + + return result; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + stbi__result_info ri; + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); + if (hdr_data) + stbi__float_postprocess(hdr_data,x,y,comp,req_comp); + return hdr_data; + } + #endif + data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); + if (data) + return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); +} + +STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + float *result; + FILE *f = stbi__fopen(filename, "rb"); + if (!f) return stbi__errpf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_LINEAR + +// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is +// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always +// reports false! + +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; + #endif +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +STBIDEF int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + long pos = ftell(f); + int res; + stbi__context s; + stbi__start_file(&s,f); + res = stbi__hdr_test(&s); + fseek(f, pos, SEEK_SET); + return res; + #else + STBI_NOTUSED(f); + return 0; + #endif +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(clbk); + STBI_NOTUSED(user); + return 0; + #endif +} + +#ifndef STBI_NO_LINEAR +static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; + +STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } +STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } +#endif + +static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; + +STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } +STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + STBI__SCAN_load=0, + STBI__SCAN_type, + STBI__SCAN_header +}; + +static void stbi__refill_buffer(stbi__context *s) +{ + int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + if (n == 0) { + // at end of file, treat same as if from memory, but need to handle case + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file + s->read_from_callbacks = 0; + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start+1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static stbi_uc stbi__get8(stbi__context *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + stbi__refill_buffer(s); + return *s->img_buffer++; + } + return 0; +} + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_HDR) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +stbi_inline static int stbi__at_eof(stbi__context *s) +{ + if (s->io.read) { + if (!(s->io.eof)(s->io_user_data)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->read_from_callbacks == 0) return 1; + } + + return s->img_buffer >= s->img_buffer_end; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) +// nothing +#else +static void stbi__skip(stbi__context *s, int n) +{ + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + s->img_buffer = s->img_buffer_end; + (s->io.skip)(s->io_user_data, n - blen); + return; + } + } + s->img_buffer += n; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_TGA) && defined(STBI_NO_HDR) && defined(STBI_NO_PNM) +// nothing +#else +static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + int res, count; + + memcpy(buffer, s->img_buffer, blen); + + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); + res = (count == (n-blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } + + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static int stbi__get16be(stbi__context *s) +{ + int z = stbi__get8(s); + return (z << 8) + stbi__get8(s); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static stbi__uint32 stbi__get32be(stbi__context *s) +{ + stbi__uint32 z = stbi__get16be(s); + return (z << 16) + stbi__get16be(s); +} +#endif + +#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) +// nothing +#else +static int stbi__get16le(stbi__context *s) +{ + int z = stbi__get8(s); + return z + (stbi__get8(s) << 8); +} +#endif + +#ifndef STBI_NO_BMP +static stbi__uint32 stbi__get32le(stbi__context *s) +{ + stbi__uint32 z = stbi__get16le(s); + return z + (stbi__get16le(s) << 16); +} +#endif + +#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static stbi_uc stbi__compute_y(int r, int g, int b) +{ + return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); + if (good == NULL) { + STBI_FREE(data); + return stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 stbi__compute_y_16(int r, int g, int b) +{ + return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + stbi__uint16 *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); + if (good == NULL) { + STBI_FREE(data); + return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + stbi__uint16 *src = data + j * x * img_n ; + stbi__uint16 *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output; + if (!data) return NULL; + output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); + } + } + if (n < comp) { + for (i=0; i < x*y; ++i) { + output[i*comp + n] = data[i*comp + n]/255.0f; + } + } + STBI_FREE(data); + return output; +} +#endif + +#ifndef STBI_NO_HDR +#define stbi__float2int(x) ((int) (x)) +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output; + if (!data) return NULL; + output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + } + STBI_FREE(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder +// +// simple implementation +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - some SIMD kernels for common paths on targets with SSE2/NEON +// - uses a lot of intermediate memory, could cache poorly + +#ifndef STBI_NO_JPEG + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + stbi_uc fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + stbi__uint16 code[256]; + stbi_uc values[256]; + stbi_uc size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} stbi__huffman; + +typedef struct +{ + stbi__context *s; + stbi__huffman huff_dc[4]; + stbi__huffman huff_ac[4]; + stbi__uint16 dequant[4][64]; + stbi__int16 fast_ac[4][1 << FAST_BITS]; + +// sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + +// definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + stbi_uc *data; + void *raw_data, *raw_coeff; + stbi_uc *linebuf; + short *coeff; // progressive only + int coeff_w, coeff_h; // number of 8x8 coefficient blocks + } img_comp[4]; + + stbi__uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int progressive; + int spec_start; + int spec_end; + int succ_high; + int succ_low; + int eob_run; + int jfif; + int app14_color_transform; // Adobe APP14 tag + int rgb; + + int scan_n, order[4]; + int restart_interval, todo; + +// kernels + void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); + void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); + stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); +} stbi__jpeg; + +static int stbi__build_huffman(stbi__huffman *h, int *count) +{ + int i,j,k=0; + unsigned int code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) + for (j=0; j < count[i]; ++j) + h->size[k++] = (stbi_uc) (i+1); + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (stbi__uint16) (code++); + if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (stbi_uc) i; + } + } + } + return 1; +} + +// build a table that decodes both magnitude and value of small ACs in +// one go. +static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) +{ + int i; + for (i=0; i < (1 << FAST_BITS); ++i) { + stbi_uc fast = h->fast[i]; + fast_ac[i] = 0; + if (fast < 255) { + int rs = h->values[fast]; + int run = (rs >> 4) & 15; + int magbits = rs & 15; + int len = h->size[fast]; + + if (magbits && len + magbits <= FAST_BITS) { + // magnitude code followed by receive_extend code + int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); + int m = 1 << (magbits - 1); + if (k < m) k += (~0U << magbits) + 1; + // if the result is small enough, we can fit it in fast_ac table + if (k >= -128 && k <= 127) + fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits)); + } + } + } +} + +static void stbi__grow_buffer_unsafe(stbi__jpeg *j) +{ + do { + unsigned int b = j->nomore ? 0 : stbi__get8(j->s); + if (b == 0xff) { + int c = stbi__get8(j->s); + while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); + + sgn = (stbi__int32)j->code_buffer >> 31; // sign bit is always in MSB + k = stbi_lrot(j->code_buffer, n); + STBI_ASSERT(n >= 0 && n < (int) (sizeof(stbi__bmask)/sizeof(*stbi__bmask))); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k + (stbi__jbias[n] & ~sgn); +} + +// get some unsigned bits +stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) +{ + unsigned int k; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k; +} + +stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) +{ + unsigned int k; + if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + k = j->code_buffer; + j->code_buffer <<= 1; + --j->code_bits; + return k & 0x80000000; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static const stbi_uc stbi__jpeg_dezigzag[64+15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant) +{ + int diff,dc,k; + int t; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? stbi__extend_receive(j, t) : 0; + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc * dequant[0]); + + // decode AC components, see JPEG spec + k = 1; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * dequant[zig]); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); + } + } + } while (k < 64); + return 1; +} + +static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) +{ + int diff,dc; + int t; + if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + if (j->succ_high == 0) { + // first scan for DC coefficient, must be first + memset(data,0,64*sizeof(data[0])); // 0 all the ac values now + t = stbi__jpeg_huff_decode(j, hdc); + diff = t ? stbi__extend_receive(j, t) : 0; + + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc << j->succ_low); + } else { + // refinement scan for DC coefficient + if (stbi__jpeg_get_bit(j)) + data[0] += (short) (1 << j->succ_low); + } + return 1; +} + +// @OPTIMIZE: store non-zigzagged during the decode passes, +// and only de-zigzag when dequantizing +static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) +{ + int k; + if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->succ_high == 0) { + int shift = j->succ_low; + + if (j->eob_run) { + --j->eob_run; + return 1; + } + + k = j->spec_start; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) << shift); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r); + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + --j->eob_run; + break; + } + k += 16; + } else { + k += r; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) << shift); + } + } + } while (k <= j->spec_end); + } else { + // refinement scan for these AC coefficients + + short bit = (short) (1 << j->succ_low); + + if (j->eob_run) { + --j->eob_run; + for (k = j->spec_start; k <= j->spec_end; ++k) { + short *p = &data[stbi__jpeg_dezigzag[k]]; + if (*p != 0) + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } + } else { + k = j->spec_start; + do { + int r,s; + int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r) - 1; + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + r = 64; // force end of block + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } + } else { + if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); + // sign bit + if (stbi__jpeg_get_bit(j)) + s = bit; + else + s = -bit; + } + + // advance by r + while (k <= j->spec_end) { + short *p = &data[stbi__jpeg_dezigzag[k++]]; + if (*p != 0) { + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } else { + if (r == 0) { + *p = (short) s; + break; + } + --r; + } + } + } while (k <= j->spec_end); + } + } + return 1; +} + +// take a -128..127 value and stbi__clamp it and convert to 0..255 +stbi_inline static stbi_uc stbi__clamp(int x) +{ + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (stbi_uc) x; +} + +#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) +#define stbi__fsh(x) ((x) * 4096) + +// derived from jidctint -- DCT_ISLOW +#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * stbi__f2f(0.5411961f); \ + t2 = p1 + p3*stbi__f2f(-1.847759065f); \ + t3 = p1 + p2*stbi__f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = stbi__fsh(p2+p3); \ + t1 = stbi__fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ + t0 = t0*stbi__f2f( 0.298631336f); \ + t1 = t1*stbi__f2f( 2.053119869f); \ + t2 = t2*stbi__f2f( 3.072711026f); \ + t3 = t3*stbi__f2f( 1.501321110f); \ + p1 = p5 + p1*stbi__f2f(-0.899976223f); \ + p2 = p5 + p2*stbi__f2f(-2.562915447f); \ + p3 = p3*stbi__f2f(-1.961570560f); \ + p4 = p4*stbi__f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) +{ + int i,val[64],*v=val; + stbi_uc *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0]*4; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128<<17); + x1 += 65536 + (128<<17); + x2 += 65536 + (128<<17); + x3 += 65536 + (128<<17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = stbi__clamp((x0+t3) >> 17); + o[7] = stbi__clamp((x0-t3) >> 17); + o[1] = stbi__clamp((x1+t2) >> 17); + o[6] = stbi__clamp((x1-t2) >> 17); + o[2] = stbi__clamp((x2+t1) >> 17); + o[5] = stbi__clamp((x2-t1) >> 17); + o[3] = stbi__clamp((x3+t0) >> 17); + o[4] = stbi__clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SSE2 +// sse2 integer IDCT. not the fastest possible implementation but it +// produces bit-identical results to the generic C version so it's +// fully "transparent". +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + // This is constructed to match our regular (generic) integer IDCT exactly. + __m128i row0, row1, row2, row3, row4, row5, row6, row7; + __m128i tmp; + + // dot product constant: even elems=x, odd elems=y + #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) + + // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) + // out(1) = c1[even]*x + c1[odd]*y + #define dct_rot(out0,out1, x,y,c0,c1) \ + __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ + __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ + __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ + __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ + __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ + __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) + + // out = in << 12 (in 16-bit, out 32-bit) + #define dct_widen(out, in) \ + __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ + __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) + + // wide add + #define dct_wadd(out, a, b) \ + __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_add_epi32(a##_h, b##_h) + + // wide sub + #define dct_wsub(out, a, b) \ + __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) + + // butterfly a/b, add bias, then shift by "s" and pack + #define dct_bfly32o(out0, out1, a,b,bias,s) \ + { \ + __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ + __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ + dct_wadd(sum, abiased, b); \ + dct_wsub(dif, abiased, b); \ + out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ + out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ + } + + // 8-bit interleave step (for transposes) + #define dct_interleave8(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi8(a, b); \ + b = _mm_unpackhi_epi8(tmp, b) + + // 16-bit interleave step (for transposes) + #define dct_interleave16(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi16(a, b); \ + b = _mm_unpackhi_epi16(tmp, b) + + #define dct_pass(bias,shift) \ + { \ + /* even part */ \ + dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ + __m128i sum04 = _mm_add_epi16(row0, row4); \ + __m128i dif04 = _mm_sub_epi16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ + dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ + __m128i sum17 = _mm_add_epi16(row1, row7); \ + __m128i sum35 = _mm_add_epi16(row3, row5); \ + dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ + dct_wadd(x4, y0o, y4o); \ + dct_wadd(x5, y1o, y5o); \ + dct_wadd(x6, y2o, y5o); \ + dct_wadd(x7, y3o, y4o); \ + dct_bfly32o(row0,row7, x0,x7,bias,shift); \ + dct_bfly32o(row1,row6, x1,x6,bias,shift); \ + dct_bfly32o(row2,row5, x2,x5,bias,shift); \ + dct_bfly32o(row3,row4, x3,x4,bias,shift); \ + } + + __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); + __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); + __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); + __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); + __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); + __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); + __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); + __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); + + // rounding biases in column/row passes, see stbi__idct_block for explanation. + __m128i bias_0 = _mm_set1_epi32(512); + __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); + + // load + row0 = _mm_load_si128((const __m128i *) (data + 0*8)); + row1 = _mm_load_si128((const __m128i *) (data + 1*8)); + row2 = _mm_load_si128((const __m128i *) (data + 2*8)); + row3 = _mm_load_si128((const __m128i *) (data + 3*8)); + row4 = _mm_load_si128((const __m128i *) (data + 4*8)); + row5 = _mm_load_si128((const __m128i *) (data + 5*8)); + row6 = _mm_load_si128((const __m128i *) (data + 6*8)); + row7 = _mm_load_si128((const __m128i *) (data + 7*8)); + + // column pass + dct_pass(bias_0, 10); + + { + // 16bit 8x8 transpose pass 1 + dct_interleave16(row0, row4); + dct_interleave16(row1, row5); + dct_interleave16(row2, row6); + dct_interleave16(row3, row7); + + // transpose pass 2 + dct_interleave16(row0, row2); + dct_interleave16(row1, row3); + dct_interleave16(row4, row6); + dct_interleave16(row5, row7); + + // transpose pass 3 + dct_interleave16(row0, row1); + dct_interleave16(row2, row3); + dct_interleave16(row4, row5); + dct_interleave16(row6, row7); + } + + // row pass + dct_pass(bias_1, 17); + + { + // pack + __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 + __m128i p1 = _mm_packus_epi16(row2, row3); + __m128i p2 = _mm_packus_epi16(row4, row5); + __m128i p3 = _mm_packus_epi16(row6, row7); + + // 8bit 8x8 transpose pass 1 + dct_interleave8(p0, p2); // a0e0a1e1... + dct_interleave8(p1, p3); // c0g0c1g1... + + // transpose pass 2 + dct_interleave8(p0, p1); // a0c0e0g0... + dct_interleave8(p2, p3); // b0d0f0h0... + + // transpose pass 3 + dct_interleave8(p0, p2); // a0b0c0d0... + dct_interleave8(p1, p3); // a4b4c4d4... + + // store + _mm_storel_epi64((__m128i *) out, p0); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p2); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p1); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p3); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); + } + +#undef dct_const +#undef dct_rot +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_interleave8 +#undef dct_interleave16 +#undef dct_pass +} + +#endif // STBI_SSE2 + +#ifdef STBI_NEON + +// NEON integer IDCT. should produce bit-identical +// results to the generic C version. +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; + + int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); + int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); + int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); + int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); + int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); + int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); + int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); + int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); + int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); + int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); + int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); + int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); + +#define dct_long_mul(out, inq, coeff) \ + int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) + +#define dct_long_mac(out, acc, inq, coeff) \ + int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) + +#define dct_widen(out, inq) \ + int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ + int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) + +// wide add +#define dct_wadd(out, a, b) \ + int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vaddq_s32(a##_h, b##_h) + +// wide sub +#define dct_wsub(out, a, b) \ + int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vsubq_s32(a##_h, b##_h) + +// butterfly a/b, then shift using "shiftop" by "s" and pack +#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ + { \ + dct_wadd(sum, a, b); \ + dct_wsub(dif, a, b); \ + out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ + out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ + } + +#define dct_pass(shiftop, shift) \ + { \ + /* even part */ \ + int16x8_t sum26 = vaddq_s16(row2, row6); \ + dct_long_mul(p1e, sum26, rot0_0); \ + dct_long_mac(t2e, p1e, row6, rot0_1); \ + dct_long_mac(t3e, p1e, row2, rot0_2); \ + int16x8_t sum04 = vaddq_s16(row0, row4); \ + int16x8_t dif04 = vsubq_s16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + int16x8_t sum15 = vaddq_s16(row1, row5); \ + int16x8_t sum17 = vaddq_s16(row1, row7); \ + int16x8_t sum35 = vaddq_s16(row3, row5); \ + int16x8_t sum37 = vaddq_s16(row3, row7); \ + int16x8_t sumodd = vaddq_s16(sum17, sum35); \ + dct_long_mul(p5o, sumodd, rot1_0); \ + dct_long_mac(p1o, p5o, sum17, rot1_1); \ + dct_long_mac(p2o, p5o, sum35, rot1_2); \ + dct_long_mul(p3o, sum37, rot2_0); \ + dct_long_mul(p4o, sum15, rot2_1); \ + dct_wadd(sump13o, p1o, p3o); \ + dct_wadd(sump24o, p2o, p4o); \ + dct_wadd(sump23o, p2o, p3o); \ + dct_wadd(sump14o, p1o, p4o); \ + dct_long_mac(x4, sump13o, row7, rot3_0); \ + dct_long_mac(x5, sump24o, row5, rot3_1); \ + dct_long_mac(x6, sump23o, row3, rot3_2); \ + dct_long_mac(x7, sump14o, row1, rot3_3); \ + dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ + dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ + dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ + dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ + } + + // load + row0 = vld1q_s16(data + 0*8); + row1 = vld1q_s16(data + 1*8); + row2 = vld1q_s16(data + 2*8); + row3 = vld1q_s16(data + 3*8); + row4 = vld1q_s16(data + 4*8); + row5 = vld1q_s16(data + 5*8); + row6 = vld1q_s16(data + 6*8); + row7 = vld1q_s16(data + 7*8); + + // add DC bias + row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); + + // column pass + dct_pass(vrshrn_n_s32, 10); + + // 16bit 8x8 transpose + { +// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. +// whether compilers actually get this is another story, sadly. +#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } +#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } + + // pass 1 + dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 + dct_trn16(row2, row3); + dct_trn16(row4, row5); + dct_trn16(row6, row7); + + // pass 2 + dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 + dct_trn32(row1, row3); + dct_trn32(row4, row6); + dct_trn32(row5, row7); + + // pass 3 + dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 + dct_trn64(row1, row5); + dct_trn64(row2, row6); + dct_trn64(row3, row7); + +#undef dct_trn16 +#undef dct_trn32 +#undef dct_trn64 + } + + // row pass + // vrshrn_n_s32 only supports shifts up to 16, we need + // 17. so do a non-rounding shift of 16 first then follow + // up with a rounding shift by 1. + dct_pass(vshrn_n_s32, 16); + + { + // pack and round + uint8x8_t p0 = vqrshrun_n_s16(row0, 1); + uint8x8_t p1 = vqrshrun_n_s16(row1, 1); + uint8x8_t p2 = vqrshrun_n_s16(row2, 1); + uint8x8_t p3 = vqrshrun_n_s16(row3, 1); + uint8x8_t p4 = vqrshrun_n_s16(row4, 1); + uint8x8_t p5 = vqrshrun_n_s16(row5, 1); + uint8x8_t p6 = vqrshrun_n_s16(row6, 1); + uint8x8_t p7 = vqrshrun_n_s16(row7, 1); + + // again, these can translate into one instruction, but often don't. +#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } +#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } + + // sadly can't use interleaved stores here since we only write + // 8 bytes to each scan line! + + // 8x8 8-bit transpose pass 1 + dct_trn8_8(p0, p1); + dct_trn8_8(p2, p3); + dct_trn8_8(p4, p5); + dct_trn8_8(p6, p7); + + // pass 2 + dct_trn8_16(p0, p2); + dct_trn8_16(p1, p3); + dct_trn8_16(p4, p6); + dct_trn8_16(p5, p7); + + // pass 3 + dct_trn8_32(p0, p4); + dct_trn8_32(p1, p5); + dct_trn8_32(p2, p6); + dct_trn8_32(p3, p7); + + // store + vst1_u8(out, p0); out += out_stride; + vst1_u8(out, p1); out += out_stride; + vst1_u8(out, p2); out += out_stride; + vst1_u8(out, p3); out += out_stride; + vst1_u8(out, p4); out += out_stride; + vst1_u8(out, p5); out += out_stride; + vst1_u8(out, p6); out += out_stride; + vst1_u8(out, p7); + +#undef dct_trn8_8 +#undef dct_trn8_16 +#undef dct_trn8_32 + } + +#undef dct_long_mul +#undef dct_long_mac +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_pass +} + +#endif // STBI_NEON + +#define STBI__MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static stbi_uc stbi__get_marker(stbi__jpeg *j) +{ + stbi_uc x; + if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } + x = stbi__get8(j->s); + if (x != 0xff) return STBI__MARKER_none; + while (x == 0xff) + x = stbi__get8(j->s); // consume repeated 0xff fill bytes + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, stbi__jpeg_reset the entropy decoder and +// the dc prediction +static void stbi__jpeg_reset(stbi__jpeg *j) +{ + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0; + j->marker = STBI__MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + j->eob_run = 0; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int stbi__parse_entropy_coded_data(stbi__jpeg *z) +{ + stbi__jpeg_reset(z); + if (!z->progressive) { + if (z->scan_n == 1) { + int i,j; + STBI_SIMD_ALIGN(short, data[64]); + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + STBI_SIMD_ALIGN(short, data[64]); + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } else { + if (z->scan_n == 1) { + int i,j; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + if (z->spec_start == 0) { + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } else { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) + return 0; + } + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x); + int y2 = (j*z->img_comp[n].v + y); + short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } +} + +static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant) +{ + int i; + for (i=0; i < 64; ++i) + data[i] *= dequant[i]; +} + +static void stbi__jpeg_finish(stbi__jpeg *z) +{ + if (z->progressive) { + // dequantize and idct the data + int i,j,n; + for (n=0; n < z->s->img_n; ++n) { + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + } + } + } + } +} + +static int stbi__process_marker(stbi__jpeg *z, int m) +{ + int L; + switch (m) { + case STBI__MARKER_none: // no marker found + return stbi__err("expected marker","Corrupt JPEG"); + + case 0xDD: // DRI - specify restart interval + if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); + z->restart_interval = stbi__get16be(z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = stbi__get16be(z->s)-2; + while (L > 0) { + int q = stbi__get8(z->s); + int p = q >> 4, sixteen = (p != 0); + int t = q & 15,i; + if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG"); + if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); + + for (i=0; i < 64; ++i) + z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s)); + L -= (sixteen ? 129 : 65); + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = stbi__get16be(z->s)-2; + while (L > 0) { + stbi_uc *v; + int sizes[16],i,n=0; + int q = stbi__get8(z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = stbi__get8(z->s); + n += sizes[i]; + } + L -= 17; + if (tc == 0) { + if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < n; ++i) + v[i] = stbi__get8(z->s); + if (tc != 0) + stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); + L -= n; + } + return L==0; + } + + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + L = stbi__get16be(z->s); + if (L < 2) { + if (m == 0xFE) + return stbi__err("bad COM len","Corrupt JPEG"); + else + return stbi__err("bad APP len","Corrupt JPEG"); + } + L -= 2; + + if (m == 0xE0 && L >= 5) { // JFIF APP0 segment + static const unsigned char tag[5] = {'J','F','I','F','\0'}; + int ok = 1; + int i; + for (i=0; i < 5; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 5; + if (ok) + z->jfif = 1; + } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment + static const unsigned char tag[6] = {'A','d','o','b','e','\0'}; + int ok = 1; + int i; + for (i=0; i < 6; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 6; + if (ok) { + stbi__get8(z->s); // version + stbi__get16be(z->s); // flags0 + stbi__get16be(z->s); // flags1 + z->app14_color_transform = stbi__get8(z->s); // color transform + L -= 6; + } + } + + stbi__skip(z->s, L); + return 1; + } + + return stbi__err("unknown marker","Corrupt JPEG"); +} + +// after we see SOS +static int stbi__process_scan_header(stbi__jpeg *z) +{ + int i; + int Ls = stbi__get16be(z->s); + z->scan_n = stbi__get8(z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = stbi__get8(z->s), which; + int q = stbi__get8(z->s); + for (which = 0; which < z->s->img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s->img_n) return 0; // no match + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + + { + int aa; + z->spec_start = stbi__get8(z->s); + z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 + aa = stbi__get8(z->s); + z->succ_high = (aa >> 4); + z->succ_low = (aa & 15); + if (z->progressive) { + if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) + return stbi__err("bad SOS", "Corrupt JPEG"); + } else { + if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); + if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); + z->spec_end = 63; + } + } + + return 1; +} + +static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) +{ + int i; + for (i=0; i < ncomp; ++i) { + if (z->img_comp[i].raw_data) { + STBI_FREE(z->img_comp[i].raw_data); + z->img_comp[i].raw_data = NULL; + z->img_comp[i].data = NULL; + } + if (z->img_comp[i].raw_coeff) { + STBI_FREE(z->img_comp[i].raw_coeff); + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].coeff = 0; + } + if (z->img_comp[i].linebuf) { + STBI_FREE(z->img_comp[i].linebuf); + z->img_comp[i].linebuf = NULL; + } + } + return why; +} + +static int stbi__process_frame_header(stbi__jpeg *z, int scan) +{ + stbi__context *s = z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG + p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires + c = stbi__get8(s); + if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); + + z->rgb = 0; + for (i=0; i < s->img_n; ++i) { + static const unsigned char rgb[3] = { 'R', 'G', 'B' }; + z->img_comp[i].id = stbi__get8(s); + if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) + ++z->rgb; + q = stbi__get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); + z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); + } + + if (scan != STBI__SCAN_load) return 1; + + if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + // these sizes can't be more than 17 bits + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + // + // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) + // so these muls can't overflow with 32-bit ints (which we require) + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].coeff = 0; + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].linebuf = NULL; + z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); + if (z->img_comp[i].raw_data == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + // align blocks for idct using mmx/sse + z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + if (z->progressive) { + // w2, h2 are multiples of 8 (see above) + z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; + z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; + z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); + if (z->img_comp[i].raw_coeff == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); + } + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define stbi__DNL(x) ((x) == 0xdc) +#define stbi__SOI(x) ((x) == 0xd8) +#define stbi__EOI(x) ((x) == 0xd9) +#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) +#define stbi__SOS(x) ((x) == 0xda) + +#define stbi__SOF_progressive(x) ((x) == 0xc2) + +static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) +{ + int m; + z->jfif = 0; + z->app14_color_transform = -1; // valid values are 0,1,2 + z->marker = STBI__MARKER_none; // initialize cached marker to empty + m = stbi__get_marker(z); + if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); + if (scan == STBI__SCAN_type) return 1; + m = stbi__get_marker(z); + while (!stbi__SOF(m)) { + if (!stbi__process_marker(z,m)) return 0; + m = stbi__get_marker(z); + while (m == STBI__MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); + m = stbi__get_marker(z); + } + } + z->progressive = stbi__SOF_progressive(m); + if (!stbi__process_frame_header(z, scan)) return 0; + return 1; +} + +// decode image to YCbCr format +static int stbi__decode_jpeg_image(stbi__jpeg *j) +{ + int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = NULL; + j->img_comp[m].raw_coeff = NULL; + } + j->restart_interval = 0; + if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; + m = stbi__get_marker(j); + while (!stbi__EOI(m)) { + if (stbi__SOS(m)) { + if (!stbi__process_scan_header(j)) return 0; + if (!stbi__parse_entropy_coded_data(j)) return 0; + if (j->marker == STBI__MARKER_none ) { + // handle 0s at the end of image data from IP Kamera 9060 + while (!stbi__at_eof(j->s)) { + int x = stbi__get8(j->s); + if (x == 255) { + j->marker = stbi__get8(j->s); + break; + } + } + // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 + } + } else if (stbi__DNL(m)) { + int Ld = stbi__get16be(j->s); + stbi__uint32 NL = stbi__get16be(j->s); + if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); + if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); + } else { + if (!stbi__process_marker(j, m)) return 0; + } + m = stbi__get_marker(j); + } + if (j->progressive) + stbi__jpeg_finish(j); + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, + int w, int hs); + +#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) + +static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED(hs); + for (i=0; i < w; ++i) + out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + stbi_uc *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = stbi__div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = stbi__div4(n+input[i-1]); + out[i*2+1] = stbi__div4(n+input[i+1]); + } + out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + + STBI_NOTUSED(in_far); + STBI_NOTUSED(hs); + + return out; +} + +#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) + +static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = stbi__div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i=0,t0,t1; + + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + // process groups of 8 pixels for as long as we can. + // note we can't handle the last pixel in a row in this loop + // because we need to handle the filter boundary conditions. + for (; i < ((w-1) & ~7); i += 8) { +#if defined(STBI_SSE2) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + __m128i zero = _mm_setzero_si128(); + __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); + __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); + __m128i farw = _mm_unpacklo_epi8(farb, zero); + __m128i nearw = _mm_unpacklo_epi8(nearb, zero); + __m128i diff = _mm_sub_epi16(farw, nearw); + __m128i nears = _mm_slli_epi16(nearw, 2); + __m128i curr = _mm_add_epi16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + __m128i prv0 = _mm_slli_si128(curr, 2); + __m128i nxt0 = _mm_srli_si128(curr, 2); + __m128i prev = _mm_insert_epi16(prv0, t1, 0); + __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + __m128i bias = _mm_set1_epi16(8); + __m128i curs = _mm_slli_epi16(curr, 2); + __m128i prvd = _mm_sub_epi16(prev, curr); + __m128i nxtd = _mm_sub_epi16(next, curr); + __m128i curb = _mm_add_epi16(curs, bias); + __m128i even = _mm_add_epi16(prvd, curb); + __m128i odd = _mm_add_epi16(nxtd, curb); + + // interleave even and odd pixels, then undo scaling. + __m128i int0 = _mm_unpacklo_epi16(even, odd); + __m128i int1 = _mm_unpackhi_epi16(even, odd); + __m128i de0 = _mm_srli_epi16(int0, 4); + __m128i de1 = _mm_srli_epi16(int1, 4); + + // pack and write output + __m128i outv = _mm_packus_epi16(de0, de1); + _mm_storeu_si128((__m128i *) (out + i*2), outv); +#elif defined(STBI_NEON) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + uint8x8_t farb = vld1_u8(in_far + i); + uint8x8_t nearb = vld1_u8(in_near + i); + int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); + int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); + int16x8_t curr = vaddq_s16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + int16x8_t prv0 = vextq_s16(curr, curr, 7); + int16x8_t nxt0 = vextq_s16(curr, curr, 1); + int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); + int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + int16x8_t curs = vshlq_n_s16(curr, 2); + int16x8_t prvd = vsubq_s16(prev, curr); + int16x8_t nxtd = vsubq_s16(next, curr); + int16x8_t even = vaddq_s16(curs, prvd); + int16x8_t odd = vaddq_s16(curs, nxtd); + + // undo scaling and round, then store with even/odd phases interleaved + uint8x8x2_t o; + o.val[0] = vqrshrun_n_s16(even, 4); + o.val[1] = vqrshrun_n_s16(odd, 4); + vst2_u8(out + i*2, o); +#endif + + // "previous" value for next iter + t1 = 3*in_near[i+7] + in_far[i+7]; + } + + t0 = t1; + t1 = 3*in_near[i] + in_far[i]; + out[i*2] = stbi__div16(3*t1 + t0 + 8); + + for (++i; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} +#endif + +static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + STBI_NOTUSED(in_far); + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +// this is a reduced-precision calculation of YCbCr-to-RGB introduced +// to make sure the code produces the same results in both SIMD and scalar +#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) +{ + int i = 0; + +#ifdef STBI_SSE2 + // step == 3 is pretty ugly on the final interleave, and i'm not convinced + // it's useful in practice (you wouldn't use it for textures, for example). + // so just accelerate step == 4 case. + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + __m128i signflip = _mm_set1_epi8(-0x80); + __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); + __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); + __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); + __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); + __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); + __m128i xw = _mm_set1_epi16(255); // alpha channel + + for (; i+7 < count; i += 8) { + // load + __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); + __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); + __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); + __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 + __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 + + // unpack to short (and left-shift cr, cb by 8) + __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); + __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); + __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); + + // color transform + __m128i yws = _mm_srli_epi16(yw, 4); + __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); + __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); + __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); + __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); + __m128i rws = _mm_add_epi16(cr0, yws); + __m128i gwt = _mm_add_epi16(cb0, yws); + __m128i bws = _mm_add_epi16(yws, cb1); + __m128i gws = _mm_add_epi16(gwt, cr1); + + // descale + __m128i rw = _mm_srai_epi16(rws, 4); + __m128i bw = _mm_srai_epi16(bws, 4); + __m128i gw = _mm_srai_epi16(gws, 4); + + // back to byte, set up for transpose + __m128i brb = _mm_packus_epi16(rw, bw); + __m128i gxb = _mm_packus_epi16(gw, xw); + + // transpose to interleave channels + __m128i t0 = _mm_unpacklo_epi8(brb, gxb); + __m128i t1 = _mm_unpackhi_epi8(brb, gxb); + __m128i o0 = _mm_unpacklo_epi16(t0, t1); + __m128i o1 = _mm_unpackhi_epi16(t0, t1); + + // store + _mm_storeu_si128((__m128i *) (out + 0), o0); + _mm_storeu_si128((__m128i *) (out + 16), o1); + out += 32; + } + } +#endif + +#ifdef STBI_NEON + // in this version, step=3 support would be easy to add. but is there demand? + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + uint8x8_t signflip = vdup_n_u8(0x80); + int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); + int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); + int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); + int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); + + for (; i+7 < count; i += 8) { + // load + uint8x8_t y_bytes = vld1_u8(y + i); + uint8x8_t cr_bytes = vld1_u8(pcr + i); + uint8x8_t cb_bytes = vld1_u8(pcb + i); + int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); + int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); + + // expand to s16 + int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); + int16x8_t crw = vshll_n_s8(cr_biased, 7); + int16x8_t cbw = vshll_n_s8(cb_biased, 7); + + // color transform + int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); + int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); + int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); + int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); + int16x8_t rws = vaddq_s16(yws, cr0); + int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); + int16x8_t bws = vaddq_s16(yws, cb1); + + // undo scaling, round, convert to byte + uint8x8x4_t o; + o.val[0] = vqrshrun_n_s16(rws, 4); + o.val[1] = vqrshrun_n_s16(gws, 4); + o.val[2] = vqrshrun_n_s16(bws, 4); + o.val[3] = vdup_n_u8(255); + + // store, interleaving r/g/b/a + vst4_u8(out, o); + out += 8*4; + } + } +#endif + + for (; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +// set up the kernels +static void stbi__setup_jpeg(stbi__jpeg *j) +{ + j->idct_block_kernel = stbi__idct_block; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; + +#ifdef STBI_SSE2 + if (stbi__sse2_available()) { + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; + } +#endif + +#ifdef STBI_NEON + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; +#endif +} + +// clean up the temporary component buffers +static void stbi__cleanup_jpeg(stbi__jpeg *j) +{ + stbi__free_jpeg_components(j, j->s->img_n, 0); +} + +typedef struct +{ + resample_row_func resample; + stbi_uc *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi__resample; + +// fast 0..255 * 0..255 => 0..255 rounded multiplication +static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) +{ + unsigned int t = x*y + 128; + return (stbi_uc) ((t + (t >>8)) >> 8); +} + +static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n, is_rgb; + z->s->img_n = 0; // make stbi__cleanup_jpeg safe + + // validate req_comp + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + + // load a jpeg image from whichever source, but leave in YCbCr format + if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1; + + is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif)); + + if (z->s->img_n == 3 && n < 3 && !is_rgb) + decode_n = 1; + else + decode_n = z->s->img_n; + + // resample and color-convert + { + int k; + unsigned int i,j; + stbi_uc *output; + stbi_uc *coutput[4] = { NULL, NULL, NULL, NULL }; + + stbi__resample res_comp[4]; + + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s->img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; + else r->resample = stbi__resample_row_generic; + } + + // can't error after this so, this is safe + output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); + if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s->img_y; ++j) { + stbi_uc *out = output + n * z->s->img_x * j; + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + stbi_uc *y = coutput[0]; + if (z->s->img_n == 3) { + if (is_rgb) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = y[i]; + out[1] = coutput[1][i]; + out[2] = coutput[2][i]; + out[3] = 255; + out += n; + } + } else { + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else if (z->s->img_n == 4) { + if (z->app14_color_transform == 0) { // CMYK + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(coutput[0][i], m); + out[1] = stbi__blinn_8x8(coutput[1][i], m); + out[2] = stbi__blinn_8x8(coutput[2][i], m); + out[3] = 255; + out += n; + } + } else if (z->app14_color_transform == 2) { // YCCK + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(255 - out[0], m); + out[1] = stbi__blinn_8x8(255 - out[1], m); + out[2] = stbi__blinn_8x8(255 - out[2], m); + out += n; + } + } else { // YCbCr + alpha? Ignore the fourth channel for now + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else + for (i=0; i < z->s->img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + if (is_rgb) { + if (n == 1) + for (i=0; i < z->s->img_x; ++i) + *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + else { + for (i=0; i < z->s->img_x; ++i, out += 2) { + out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + out[1] = 255; + } + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 0) { + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + stbi_uc r = stbi__blinn_8x8(coutput[0][i], m); + stbi_uc g = stbi__blinn_8x8(coutput[1][i], m); + stbi_uc b = stbi__blinn_8x8(coutput[2][i], m); + out[0] = stbi__compute_y(r, g, b); + out[1] = 255; + out += n; + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 2) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]); + out[1] = 255; + out += n; + } + } else { + stbi_uc *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; } + } + } + } + stbi__cleanup_jpeg(z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output + return output; + } +} + +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + unsigned char* result; + stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); + STBI_NOTUSED(ri); + j->s = s; + stbi__setup_jpeg(j); + result = load_jpeg_image(j, x,y,comp,req_comp); + STBI_FREE(j); + return result; +} + +static int stbi__jpeg_test(stbi__context *s) +{ + int r; + stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); + j->s = s; + stbi__setup_jpeg(j); + r = stbi__decode_jpeg_header(j, STBI__SCAN_type); + stbi__rewind(s); + STBI_FREE(j); + return r; +} + +static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) +{ + if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { + stbi__rewind( j->s ); + return 0; + } + if (x) *x = j->s->img_x; + if (y) *y = j->s->img_y; + if (comp) *comp = j->s->img_n >= 3 ? 3 : 1; + return 1; +} + +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) +{ + int result; + stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); + j->s = s; + result = stbi__jpeg_info_raw(j, x, y, comp); + STBI_FREE(j); + return result; +} +#endif + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +#ifndef STBI_NO_ZLIB + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + stbi__uint16 fast[1 << STBI__ZFAST_BITS]; + stbi__uint16 firstcode[16]; + int maxcode[17]; + stbi__uint16 firstsymbol[16]; + stbi_uc size[288]; + stbi__uint16 value[288]; +} stbi__zhuffman; + +stbi_inline static int stbi__bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +stbi_inline static int stbi__bit_reverse(int v, int bits) +{ + STBI_ASSERT(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return stbi__bitreverse16(v) >> (16-bits); +} + +static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 0, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + if (sizes[i] > (1 << i)) + return stbi__err("bad sizes", "Corrupt PNG"); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (stbi__uint16) code; + z->firstsymbol[i] = (stbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); + z->size [c] = (stbi_uc ) s; + z->value[c] = (stbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int j = stbi__bit_reverse(next_code[s],s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + stbi_uc *zbuffer, *zbuffer_end; + int num_bits; + stbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + stbi__zhuffman z_length, z_distance; +} stbi__zbuf; + +stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) +{ + if (z->zbuffer >= z->zbuffer_end) return 0; + return *z->zbuffer++; +} + +static void stbi__fill_bits(stbi__zbuf *z) +{ + do { + STBI_ASSERT(z->code_buffer < (1U << z->num_bits)); + z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) stbi__fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s,k; + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = stbi__bit_reverse(a->code_buffer, 16); + for (s=STBI__ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s == 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + STBI_ASSERT(z->size[b] == s); + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s; + if (a->num_bits < 16) stbi__fill_bits(a); + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return stbi__zhuffman_decode_slowpath(a, z); +} + +static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes +{ + char *q; + int cur, limit, old_limit; + z->zout = zout; + if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); + cur = (int) (z->zout - z->zout_start); + limit = old_limit = (int) (z->zout_end - z->zout_start); + while (cur + n > limit) + limit *= 2; + q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); + STBI_NOTUSED(old_limit); + if (q == NULL) return stbi__err("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static const int stbi__zlength_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static const int stbi__zlength_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static const int stbi__zdist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int stbi__parse_huffman_block(stbi__zbuf *a) +{ + char *zout = a->zout; + for(;;) { + int z = stbi__zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes + if (zout >= a->zout_end) { + if (!stbi__zexpand(a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; + } else { + stbi_uc *p; + int len,dist; + if (z == 256) { + a->zout = zout; + return 1; + } + z -= 257; + len = stbi__zlength_base[z]; + if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); + z = stbi__zhuffman_decode(a, &a->z_distance); + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); + dist = stbi__zdist_base[z]; + if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); + if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); + if (zout + len > a->zout_end) { + if (!stbi__zexpand(a, zout, len)) return 0; + zout = a->zout; + } + p = (stbi_uc *) (zout - dist); + if (dist == 1) { // run of one byte; common in images. + stbi_uc v = *p; + if (len) { do *zout++ = v; while (--len); } + } else { + if (len) { do *zout++ = *p++; while (--len); } + } + } + } +} + +static int stbi__compute_huffman_codes(stbi__zbuf *a) +{ + static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + stbi__zhuffman z_codelength; + stbi_uc lencodes[286+32+137];//padding for maximum single op + stbi_uc codelength_sizes[19]; + int i,n; + + int hlit = stbi__zreceive(a,5) + 257; + int hdist = stbi__zreceive(a,5) + 1; + int hclen = stbi__zreceive(a,4) + 4; + int ntot = hlit + hdist; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = stbi__zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; + } + if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < ntot) { + int c = stbi__zhuffman_decode(a, &z_codelength); + if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); + if (c < 16) + lencodes[n++] = (stbi_uc) c; + else { + stbi_uc fill = 0; + if (c == 16) { + c = stbi__zreceive(a,2)+3; + if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); + fill = lencodes[n-1]; + } else if (c == 17) + c = stbi__zreceive(a,3)+3; + else { + STBI_ASSERT(c == 18); + c = stbi__zreceive(a,7)+11; + } + if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); + memset(lencodes+n, fill, c); + n += c; + } + } + if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); + if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int stbi__parse_uncompressed_block(stbi__zbuf *a) +{ + stbi_uc header[4]; + int len,nlen,k; + if (a->num_bits & 7) + stbi__zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check + a->code_buffer >>= 8; + a->num_bits -= 8; + } + STBI_ASSERT(a->num_bits == 0); + // now fill header the normal way + while (k < 4) + header[k++] = stbi__zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!stbi__zexpand(a, a->zout, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int stbi__parse_zlib_header(stbi__zbuf *a) +{ + int cmf = stbi__zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = stbi__zget8(a); + if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +static const stbi_uc stbi__zdefault_length[288] = +{ + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8 +}; +static const stbi_uc stbi__zdefault_distance[32] = +{ + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 +}; +/* +Init algorithm: +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; + for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; + for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; + for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; + + for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; +} +*/ + +static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!stbi__parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + do { + final = stbi__zreceive(a,1); + type = stbi__zreceive(a,2); + if (type == 0) { + if (!stbi__parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , 288)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; + } else { + if (!stbi__compute_huffman_codes(a)) return 0; + } + if (!stbi__parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return stbi__parse_zlib(a, parse_header); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer+len; + if (stbi__do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} +#endif + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + +#ifndef STBI_NO_PNG +typedef struct +{ + stbi__uint32 length; + stbi__uint32 type; +} stbi__pngchunk; + +static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) +{ + stbi__pngchunk c; + c.length = stbi__get32be(s); + c.type = stbi__get32be(s); + return c; +} + +static int stbi__check_png_header(stbi__context *s) +{ + static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi__context *s; + stbi_uc *idata, *expanded, *out; + int depth; +} stbi__png; + + +enum { + STBI__F_none=0, + STBI__F_sub=1, + STBI__F_up=2, + STBI__F_avg=3, + STBI__F_paeth=4, + // synthetic filters used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static stbi_uc first_row_filter[5] = +{ + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static int stbi__paeth(int a, int b, int c) +{ + int p = a + b - c; + int pa = abs(p-a); + int pb = abs(p-b); + int pc = abs(p-c); + if (pa <= pb && pa <= pc) return a; + if (pb <= pc) return b; + return c; +} + +static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; + +// create the png data from post-deflated data +static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) +{ + int bytes = (depth == 16? 2 : 1); + stbi__context *s = a->s; + stbi__uint32 i,j,stride = x*out_n*bytes; + stbi__uint32 img_len, img_width_bytes; + int k; + int img_n = s->img_n; // copy it into a local for later + + int output_bytes = out_n*bytes; + int filter_bytes = img_n*bytes; + int width = x; + + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into + if (!a->out) return stbi__err("outofmem", "Out of memory"); + + if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + img_len = (img_width_bytes + 1) * y; + + // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, + // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros), + // so just check for raw_len < img_len always. + if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *prior; + int filter = *raw++; + + if (filter > 4) + return stbi__err("invalid filter","Corrupt PNG"); + + if (depth < 8) { + STBI_ASSERT(img_width_bytes <= x); + cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place + filter_bytes = 1; + width = img_width_bytes; + } + prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above + + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + + // handle first byte explicitly + for (k=0; k < filter_bytes; ++k) { + switch (filter) { + case STBI__F_none : cur[k] = raw[k]; break; + case STBI__F_sub : cur[k] = raw[k]; break; + case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; + case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; + case STBI__F_avg_first : cur[k] = raw[k]; break; + case STBI__F_paeth_first: cur[k] = raw[k]; break; + } + } + + if (depth == 8) { + if (img_n != out_n) + cur[img_n] = 255; // first pixel + raw += img_n; + cur += out_n; + prior += out_n; + } else if (depth == 16) { + if (img_n != out_n) { + cur[filter_bytes] = 255; // first pixel top byte + cur[filter_bytes+1] = 255; // first pixel bottom byte + } + raw += filter_bytes; + cur += output_bytes; + prior += output_bytes; + } else { + raw += 1; + cur += 1; + prior += 1; + } + + // this is a little gross, so that we don't switch per-pixel or per-component + if (depth < 8 || img_n == out_n) { + int nk = (width - 1)*filter_bytes; + #define STBI__CASE(f) \ + case f: \ + for (k=0; k < nk; ++k) + switch (filter) { + // "none" filter turns into a memcpy here; make that explicit. + case STBI__F_none: memcpy(cur, raw, nk); break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break; + } + #undef STBI__CASE + raw += nk; + } else { + STBI_ASSERT(img_n+1 == out_n); + #define STBI__CASE(f) \ + case f: \ + for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ + for (k=0; k < filter_bytes; ++k) + switch (filter) { + STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break; + } + #undef STBI__CASE + + // the loop above sets the high byte of the pixels' alpha, but for + // 16 bit png files we also need the low byte set. we'll do that here. + if (depth == 16) { + cur = a->out + stride*j; // start at the beginning of the row again + for (i=0; i < x; ++i,cur+=output_bytes) { + cur[filter_bytes+1] = 255; + } + } + } + } + + // we make a separate pass to expand bits to pixels; for performance, + // this could run two scanlines behind the above code, so it won't + // intefere with filtering but will still be in the cache. + if (depth < 8) { + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; + // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit + // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop + stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + + // note that the final byte might overshoot and write more data than desired. + // we can allocate enough data that this never writes out of memory, but it + // could also overwrite the next scanline. can it overwrite non-empty data + // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. + // so we need to explicitly clamp the final ones + + if (depth == 4) { + for (k=x*img_n; k >= 2; k-=2, ++in) { + *cur++ = scale * ((*in >> 4) ); + *cur++ = scale * ((*in ) & 0x0f); + } + if (k > 0) *cur++ = scale * ((*in >> 4) ); + } else if (depth == 2) { + for (k=x*img_n; k >= 4; k-=4, ++in) { + *cur++ = scale * ((*in >> 6) ); + *cur++ = scale * ((*in >> 4) & 0x03); + *cur++ = scale * ((*in >> 2) & 0x03); + *cur++ = scale * ((*in ) & 0x03); + } + if (k > 0) *cur++ = scale * ((*in >> 6) ); + if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); + if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); + } else if (depth == 1) { + for (k=x*img_n; k >= 8; k-=8, ++in) { + *cur++ = scale * ((*in >> 7) ); + *cur++ = scale * ((*in >> 6) & 0x01); + *cur++ = scale * ((*in >> 5) & 0x01); + *cur++ = scale * ((*in >> 4) & 0x01); + *cur++ = scale * ((*in >> 3) & 0x01); + *cur++ = scale * ((*in >> 2) & 0x01); + *cur++ = scale * ((*in >> 1) & 0x01); + *cur++ = scale * ((*in ) & 0x01); + } + if (k > 0) *cur++ = scale * ((*in >> 7) ); + if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); + if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); + if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); + if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); + if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); + if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); + } + if (img_n != out_n) { + int q; + // insert alpha = 255 + cur = a->out + stride*j; + if (img_n == 1) { + for (q=x-1; q >= 0; --q) { + cur[q*2+1] = 255; + cur[q*2+0] = cur[q]; + } + } else { + STBI_ASSERT(img_n == 3); + for (q=x-1; q >= 0; --q) { + cur[q*4+3] = 255; + cur[q*4+2] = cur[q*3+2]; + cur[q*4+1] = cur[q*3+1]; + cur[q*4+0] = cur[q*3+0]; + } + } + } + } + } else if (depth == 16) { + // force the image data from big-endian to platform-native. + // this is done in a separate pass due to the decoding relying + // on the data being untouched, but could probably be done + // per-line during decode if care is taken. + stbi_uc *cur = a->out; + stbi__uint16 *cur16 = (stbi__uint16*)cur; + + for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { + *cur16 = (cur[0] << 8) | cur[1]; + } + } + + return 1; +} + +static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) +{ + int bytes = (depth == 16 ? 2 : 1); + int out_bytes = out_n * bytes; + stbi_uc *final; + int p; + if (!interlaced) + return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); + + // de-interlacing + final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE(final); + return 0; + } + for (j=0; j < y; ++j) { + for (i=0; i < x; ++i) { + int out_y = j*yspc[p]+yorig[p]; + int out_x = i*xspc[p]+xorig[p]; + memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, + a->out + (j*x+i)*out_bytes, out_bytes); + } + } + STBI_FREE(a->out); + image_data += img_len; + image_data_len -= img_len; + } + } + a->out = final; + + return 1; +} + +static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi__uint16 *p = (stbi__uint16*) z->out; + + // compute color-based transparency, assuming we've + // already got 65535 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i = 0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 65535); + p += 2; + } + } else { + for (i = 0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) +{ + stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + stbi_uc *p, *temp_out, *orig = a->out; + + p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + STBI_FREE(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi__unpremultiply_on_load = 0; +static int stbi__de_iphone_flag = 0; + +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load = flag_true_if_should_unpremultiply; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag = flag_true_if_should_convert; +} + +static void stbi__de_iphone(stbi__png *z) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + STBI_ASSERT(s->img_out_n == 4); + if (stbi__unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + stbi_uc a = p[3]; + stbi_uc t = p[0]; + if (a) { + stbi_uc half = a / 2; + p[0] = (p[2] * 255 + half) / a; + p[1] = (p[1] * 255 + half) / a; + p[2] = ( t * 255 + half) / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) + +static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) +{ + stbi_uc palette[1024], pal_img_n=0; + stbi_uc has_trans=0, tc[3]={0}; + stbi__uint16 tc16[3]; + stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, color=0, is_iphone=0; + stbi__context *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!stbi__check_png_header(s)) return 0; + + if (scan == STBI__SCAN_type) return 1; + + for (;;) { + stbi__pngchunk c = stbi__get_chunk_header(s); + switch (c.type) { + case STBI__PNG_TYPE('C','g','B','I'): + is_iphone = 1; + stbi__skip(s, c.length); + break; + case STBI__PNG_TYPE('I','H','D','R'): { + int comp,filter; + if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); + s->img_x = stbi__get32be(s); if (s->img_x > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); + s->img_y = stbi__get32be(s); if (s->img_y > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); + z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); + color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); + comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); + filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); + interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + if (scan == STBI__SCAN_header) return 1; + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); + // if SCAN_header, have to scan to see if we have a tRNS + } + break; + } + + case STBI__PNG_TYPE('P','L','T','E'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = stbi__get8(s); + palette[i*4+1] = stbi__get8(s); + palette[i*4+2] = stbi__get8(s); + palette[i*4+3] = 255; + } + break; + } + + case STBI__PNG_TYPE('t','R','N','S'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = stbi__get8(s); + } else { + if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); + if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); + has_trans = 1; + if (z->depth == 16) { + for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is + } else { + for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger + } + } + break; + } + + case STBI__PNG_TYPE('I','D','A','T'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); + if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } + if ((int)(ioff + c.length) < (int)ioff) return 0; + if (ioff + c.length > idata_limit) { + stbi__uint32 idata_limit_old = idata_limit; + stbi_uc *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + STBI_NOTUSED(idata_limit_old); + p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); + z->idata = p; + } + if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case STBI__PNG_TYPE('I','E','N','D'): { + stbi__uint32 raw_len, bpl; + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (scan != STBI__SCAN_load) return 1; + if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); + // initial guess for decoded data size to avoid unnecessary reallocs + bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); + if (z->expanded == NULL) return 0; // zlib should set error + STBI_FREE(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; + if (has_trans) { + if (z->depth == 16) { + if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; + } else { + if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; + } + } + if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) + stbi__de_iphone(z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } else if (has_trans) { + // non-paletted image with tRNS -> source image has (constant) alpha + ++s->img_n; + } + STBI_FREE(z->expanded); z->expanded = NULL; + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + return 1; + } + + default: + // if critical, fail + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX PNG chunk not known"; + invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); + invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); + invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); + invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); + #endif + return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); + } + stbi__skip(s, c.length); + break; + } + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + } +} + +static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) +{ + void *result=NULL; + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { + if (p->depth < 8) + ri->bits_per_channel = 8; + else + ri->bits_per_channel = p->depth; + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + if (ri->bits_per_channel == 8) + result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + else + result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_n; + } + STBI_FREE(p->out); p->out = NULL; + STBI_FREE(p->expanded); p->expanded = NULL; + STBI_FREE(p->idata); p->idata = NULL; + + return result; +} + +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi__png p; + p.s = s; + return stbi__do_png(&p, x,y,comp,req_comp, ri); +} + +static int stbi__png_test(stbi__context *s) +{ + int r; + r = stbi__check_png_header(s); + stbi__rewind(s); + return r; +} + +static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) +{ + if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { + stbi__rewind( p->s ); + return 0; + } + if (x) *x = p->s->img_x; + if (y) *y = p->s->img_y; + if (comp) *comp = p->s->img_n; + return 1; +} + +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__png p; + p.s = s; + return stbi__png_info_raw(&p, x, y, comp); +} + +static int stbi__png_is16(stbi__context *s) +{ + stbi__png p; + p.s = s; + if (!stbi__png_info_raw(&p, NULL, NULL, NULL)) + return 0; + if (p.depth != 16) { + stbi__rewind(p.s); + return 0; + } + return 1; +} +#endif + +// Microsoft/Windows BMP image + +#ifndef STBI_NO_BMP +static int stbi__bmp_test_raw(stbi__context *s) +{ + int r; + int sz; + if (stbi__get8(s) != 'B') return 0; + if (stbi__get8(s) != 'M') return 0; + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + stbi__get32le(s); // discard data offset + sz = stbi__get32le(s); + r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); + return r; +} + +static int stbi__bmp_test(stbi__context *s) +{ + int r = stbi__bmp_test_raw(s); + stbi__rewind(s); + return r; +} + + +// returns 0..31 for the highest set bit +static int stbi__high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) { n += 16; z >>= 16; } + if (z >= 0x00100) { n += 8; z >>= 8; } + if (z >= 0x00010) { n += 4; z >>= 4; } + if (z >= 0x00004) { n += 2; z >>= 2; } + if (z >= 0x00002) { n += 1;/* >>= 1;*/ } + return n; +} + +static int stbi__bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +// extract an arbitrarily-aligned N-bit value (N=bits) +// from v, and then make it 8-bits long and fractionally +// extend it to full full range. +static int stbi__shiftsigned(unsigned int v, int shift, int bits) +{ + static unsigned int mul_table[9] = { + 0, + 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/, + 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/, + }; + static unsigned int shift_table[9] = { + 0, 0,0,1,0,2,4,6,0, + }; + if (shift < 0) + v <<= -shift; + else + v >>= shift; + STBI_ASSERT(v < 256); + v >>= (8-bits); + STBI_ASSERT(bits >= 0 && bits <= 8); + return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits]; +} + +typedef struct +{ + int bpp, offset, hsz; + unsigned int mr,mg,mb,ma, all_a; + int extra_read; +} stbi__bmp_data; + +static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) +{ + int hsz; + if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + info->offset = stbi__get32le(s); + info->hsz = hsz = stbi__get32le(s); + info->mr = info->mg = info->mb = info->ma = 0; + info->extra_read = 14; + + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = stbi__get16le(s); + s->img_y = stbi__get16le(s); + } else { + s->img_x = stbi__get32le(s); + s->img_y = stbi__get32le(s); + } + if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); + info->bpp = stbi__get16le(s); + if (hsz != 12) { + int compress = stbi__get32le(s); + if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); + stbi__get32le(s); // discard sizeof + stbi__get32le(s); // discard hres + stbi__get32le(s); // discard vres + stbi__get32le(s); // discard colorsused + stbi__get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + } + if (info->bpp == 16 || info->bpp == 32) { + if (compress == 0) { + if (info->bpp == 32) { + info->mr = 0xffu << 16; + info->mg = 0xffu << 8; + info->mb = 0xffu << 0; + info->ma = 0xffu << 24; + info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 + } else { + info->mr = 31u << 10; + info->mg = 31u << 5; + info->mb = 31u << 0; + } + } else if (compress == 3) { + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->extra_read += 12; + // not documented, but generated by photoshop and handled by mspaint + if (info->mr == info->mg && info->mg == info->mb) { + // ?!?!? + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else { + int i; + if (hsz != 108 && hsz != 124) + return stbi__errpuc("bad BMP", "bad BMP"); + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->ma = stbi__get32le(s); + stbi__get32le(s); // discard color space + for (i=0; i < 12; ++i) + stbi__get32le(s); // discard color space parameters + if (hsz == 124) { + stbi__get32le(s); // discard rendering intent + stbi__get32le(s); // discard offset of profile data + stbi__get32le(s); // discard size of profile data + stbi__get32le(s); // discard reserved + } + } + } + return (void *) 1; +} + + +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + unsigned int mr=0,mg=0,mb=0,ma=0, all_a; + stbi_uc pal[256][4]; + int psize=0,i,j,width; + int flip_vertically, pad, target; + stbi__bmp_data info; + STBI_NOTUSED(ri); + + info.all_a = 255; + if (stbi__bmp_parse_header(s, &info) == NULL) + return NULL; // error code already set + + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + + mr = info.mr; + mg = info.mg; + mb = info.mb; + ma = info.ma; + all_a = info.all_a; + + if (info.hsz == 12) { + if (info.bpp < 24) + psize = (info.offset - info.extra_read - 24) / 3; + } else { + if (info.bpp < 16) + psize = (info.offset - info.extra_read - info.hsz) >> 2; + } + if (psize == 0) { + STBI_ASSERT(info.offset == (s->img_buffer - s->buffer_start)); + } + + if (info.bpp == 24 && ma == 0xff000000) + s->img_n = 3; + else + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + + // sanity-check size + if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "Corrupt BMP"); + + out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (info.bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + if (info.hsz != 12) stbi__get8(s); + pal[i][3] = 255; + } + stbi__skip(s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); + if (info.bpp == 1) width = (s->img_x + 7) >> 3; + else if (info.bpp == 4) width = (s->img_x + 1) >> 1; + else if (info.bpp == 8) width = s->img_x; + else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + if (info.bpp == 1) { + for (j=0; j < (int) s->img_y; ++j) { + int bit_offset = 7, v = stbi__get8(s); + for (i=0; i < (int) s->img_x; ++i) { + int color = (v>>bit_offset)&0x1; + out[z++] = pal[color][0]; + out[z++] = pal[color][1]; + out[z++] = pal[color][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + if((--bit_offset) < 0) { + bit_offset = 7; + v = stbi__get8(s); + } + } + stbi__skip(s, pad); + } + } else { + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=stbi__get8(s),v2=0; + if (info.bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (info.bpp == 8) ? stbi__get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + stbi__skip(s, pad); + } + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + stbi__skip(s, info.offset - info.extra_read - info.hsz); + if (info.bpp == 24) width = 3 * s->img_x; + else if (info.bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (info.bpp == 24) { + easy = 1; + } else if (info.bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + // right shift amt to put high bit in position #7 + rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); + gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); + bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); + ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + unsigned char a; + out[z+2] = stbi__get8(s); + out[z+1] = stbi__get8(s); + out[z+0] = stbi__get8(s); + z += 3; + a = (easy == 2 ? stbi__get8(s) : 255); + all_a |= a; + if (target == 4) out[z++] = a; + } + } else { + int bpp = info.bpp; + for (i=0; i < (int) s->img_x; ++i) { + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); + unsigned int a; + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); + a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); + all_a |= a; + if (target == 4) out[z++] = STBI__BYTECAST(a); + } + } + stbi__skip(s, pad); + } + } + + // if alpha channel is all 0s, replace with all 255s + if (target == 4 && all_a == 0) + for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) + out[i] = 255; + + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i]; p1[i] = p2[i]; p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} +#endif + +// Targa Truevision - TGA +// by Jonathan Dummer +#ifndef STBI_NO_TGA +// returns STBI_rgb or whatever, 0 on error +static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) +{ + // only RGB or RGBA (incl. 16bit) or grey allowed + if (is_rgb16) *is_rgb16 = 0; + switch(bits_per_pixel) { + case 8: return STBI_grey; + case 16: if(is_grey) return STBI_grey_alpha; + // fallthrough + case 15: if(is_rgb16) *is_rgb16 = 1; + return STBI_rgb; + case 24: // fallthrough + case 32: return bits_per_pixel/8; + default: return 0; + } +} + +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; + int sz, tga_colormap_type; + stbi__get8(s); // discard Offset + tga_colormap_type = stbi__get8(s); // colormap type + if( tga_colormap_type > 1 ) { + stbi__rewind(s); + return 0; // only RGB or indexed allowed + } + tga_image_type = stbi__get8(s); // image type + if ( tga_colormap_type == 1 ) { // colormapped (paletted) image + if (tga_image_type != 1 && tga_image_type != 9) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip image x and y origin + tga_colormap_bpp = sz; + } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE + if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { + stbi__rewind(s); + return 0; // only RGB or grey allowed, +/- RLE + } + stbi__skip(s,9); // skip colormap specification and image x/y origin + tga_colormap_bpp = 0; + } + tga_w = stbi__get16le(s); + if( tga_w < 1 ) { + stbi__rewind(s); + return 0; // test width + } + tga_h = stbi__get16le(s); + if( tga_h < 1 ) { + stbi__rewind(s); + return 0; // test height + } + tga_bits_per_pixel = stbi__get8(s); // bits per pixel + stbi__get8(s); // ignore alpha bits + if (tga_colormap_bpp != 0) { + if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { + // when using a colormap, tga_bits_per_pixel is the size of the indexes + // I don't think anything but 8 or 16bit indexes makes sense + stbi__rewind(s); + return 0; + } + tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); + } else { + tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); + } + if(!tga_comp) { + stbi__rewind(s); + return 0; + } + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp; + return 1; // seems to have passed everything +} + +static int stbi__tga_test(stbi__context *s) +{ + int res = 0; + int sz, tga_color_type; + stbi__get8(s); // discard Offset + tga_color_type = stbi__get8(s); // color type + if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed + sz = stbi__get8(s); // image type + if ( tga_color_type == 1 ) { // colormapped (paletted) image + if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + stbi__skip(s,4); // skip image x and y origin + } else { // "normal" image w/o colormap + if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE + stbi__skip(s,9); // skip colormap specification and image x/y origin + } + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height + sz = stbi__get8(s); // bits per pixel + if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + + res = 1; // if we got this far, everything's good and we can return 1 instead of 0 + +errorEnd: + stbi__rewind(s); + return res; +} + +// read 16bit value and convert to 24bit RGB +static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) +{ + stbi__uint16 px = (stbi__uint16)stbi__get16le(s); + stbi__uint16 fiveBitMask = 31; + // we have 3 channels with 5bits each + int r = (px >> 10) & fiveBitMask; + int g = (px >> 5) & fiveBitMask; + int b = px & fiveBitMask; + // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later + out[0] = (stbi_uc)((r * 255)/31); + out[1] = (stbi_uc)((g * 255)/31); + out[2] = (stbi_uc)((b * 255)/31); + + // some people claim that the most significant bit might be used for alpha + // (possibly if an alpha-bit is set in the "image descriptor byte") + // but that only made 16bit test images completely translucent.. + // so let's treat all 15 and 16bit TGAs as RGB with no alpha. +} + +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + // read in the TGA header stuff + int tga_offset = stbi__get8(s); + int tga_indexed = stbi__get8(s); + int tga_image_type = stbi__get8(s); + int tga_is_RLE = 0; + int tga_palette_start = stbi__get16le(s); + int tga_palette_len = stbi__get16le(s); + int tga_palette_bits = stbi__get8(s); + int tga_x_origin = stbi__get16le(s); + int tga_y_origin = stbi__get16le(s); + int tga_width = stbi__get16le(s); + int tga_height = stbi__get16le(s); + int tga_bits_per_pixel = stbi__get8(s); + int tga_comp, tga_rgb16=0; + int tga_inverted = stbi__get8(s); + // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4] = {0}; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + STBI_NOTUSED(ri); + STBI_NOTUSED(tga_x_origin); // @TODO + STBI_NOTUSED(tga_y_origin); // @TODO + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); + else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); + + if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency + return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); + + // tga info + *x = tga_width; + *y = tga_height; + if (comp) *comp = tga_comp; + + if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) + return stbi__errpuc("too large", "Corrupt TGA"); + + tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); + if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + stbi__skip(s, tga_offset ); + + if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { + for (i=0; i < tga_height; ++i) { + int row = tga_inverted ? tga_height -i - 1 : i; + stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; + stbi__getn(s, tga_row, tga_width * tga_comp); + } + } else { + // do I need to load a palette? + if ( tga_indexed) + { + // any data to skip? (offset usually = 0) + stbi__skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); + if (!tga_palette) { + STBI_FREE(tga_data); + return stbi__errpuc("outofmem", "Out of memory"); + } + if (tga_rgb16) { + stbi_uc *pal_entry = tga_palette; + STBI_ASSERT(tga_comp == STBI_rgb); + for (i=0; i < tga_palette_len; ++i) { + stbi__tga_read_rgb16(s, pal_entry); + pal_entry += tga_comp; + } + } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { + STBI_FREE(tga_data); + STBI_FREE(tga_palette); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + } + // load the data + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = stbi__get8(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if ( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if ( read_next_pixel ) + { + // load however much data we did have + if ( tga_indexed ) + { + // read in index, then perform the lookup + int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); + if ( pal_idx >= tga_palette_len ) { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_comp; + //Fix for first CVE issue from https://github.com/nothings/stb/issues/790 + int tmp_tga_comp = 0; + if ((pal_idx + tga_comp) < 0 || (pal_idx + tga_comp) >= tga_palette_len) + memset(raw_data, 0, tga_comp); + else + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else if(tga_rgb16) { + STBI_ASSERT(tga_comp == STBI_rgb); + stbi__tga_read_rgb16(s, raw_data); + } else { + // read in the data raw + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = stbi__get8(s); + } + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + + // copy data + for (j = 0; j < tga_comp; ++j) + tga_data[i*tga_comp+j] = raw_data[j]; + + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if ( tga_inverted ) + { + for (j = 0; j*2 < tga_height; ++j) + { + int index1 = j * tga_width * tga_comp; + int index2 = (tga_height - 1 - j) * tga_width * tga_comp; + for (i = tga_width * tga_comp; i > 0; --i) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if ( tga_palette != NULL ) + { + STBI_FREE( tga_palette ); + } + } + + // swap RGB - if the source data was RGB16, it already is in the right order + if (tga_comp >= 3 && !tga_rgb16) + { + unsigned char* tga_pixel = tga_data; + for (i=0; i < tga_width * tga_height; ++i) + { + unsigned char temp = tga_pixel[0]; + tga_pixel[0] = tga_pixel[2]; + tga_pixel[2] = temp; + tga_pixel += tga_comp; + } + } + + // convert to target component count + if (req_comp && req_comp != tga_comp) + tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); + + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + STBI_NOTUSED(tga_palette_start); + // OK, done + return tga_data; +} +#endif + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s) +{ + int r = (stbi__get32be(s) == 0x38425053); + stbi__rewind(s); + return r; +} + +static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) +{ + int count, nleft, len; + + count = 0; + while ((nleft = pixelCount - count) > 0) { + len = stbi__get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + if (len > nleft) return 0; // corrupt data + count += len; + while (len) { + *p = stbi__get8(s); + p += 4; + len--; + } + } else if (len > 128) { + stbi_uc val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len = 257 - len; + if (len > nleft) return 0; // corrupt data + val = stbi__get8(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + + return 1; +} + +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + int pixelCount; + int channelCount, compression; + int channel, i; + int bitdepth; + int w,h; + stbi_uc *out; + STBI_NOTUSED(ri); + + // Check identifier + if (stbi__get32be(s) != 0x38425053) // "8BPS" + return stbi__errpuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (stbi__get16be(s) != 1) + return stbi__errpuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + stbi__skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) + return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = stbi__get32be(s); + w = stbi__get32be(s); + + // Make sure the depth is 8 bits. + bitdepth = stbi__get16be(s); + if (bitdepth != 8 && bitdepth != 16) + return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (stbi__get16be(s) != 3) + return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + stbi__skip(s,stbi__get32be(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + stbi__skip(s, stbi__get32be(s) ); + + // Skip the reserved data. + stbi__skip(s, stbi__get32be(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = stbi__get16be(s); + if (compression > 1) + return stbi__errpuc("bad compression", "PSD has an unknown compression format"); + + // Check size + if (!stbi__mad3sizes_valid(4, w, h, 0)) + return stbi__errpuc("too large", "Corrupt PSD"); + + // Create the destination image. + + if (!compression && bitdepth == 16 && bpc == 16) { + out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); + ri->bits_per_channel = 16; + } else + out = (stbi_uc *) stbi__malloc(4 * w*h); + + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceded by a 2-byte data count for each row in the data, + // which we're going to just skip. + stbi__skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++, p += 4) + *p = (channel == 3 ? 255 : 0); + } else { + // Read the RLE data. + if (!stbi__psd_decode_rle(s, p, pixelCount)) { + STBI_FREE(out); + return stbi__errpuc("corrupt", "bad RLE data"); + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + if (channel >= channelCount) { + // Fill this channel with default data. + if (bitdepth == 16 && bpc == 16) { + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + stbi__uint16 val = channel == 3 ? 65535 : 0; + for (i = 0; i < pixelCount; i++, q += 4) + *q = val; + } else { + stbi_uc *p = out+channel; + stbi_uc val = channel == 3 ? 255 : 0; + for (i = 0; i < pixelCount; i++, p += 4) + *p = val; + } + } else { + if (ri->bits_per_channel == 16) { // output bpc + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + for (i = 0; i < pixelCount; i++, q += 4) + *q = (stbi__uint16) stbi__get16be(s); + } else { + stbi_uc *p = out+channel; + if (bitdepth == 16) { // input bpc + for (i = 0; i < pixelCount; i++, p += 4) + *p = (stbi_uc) (stbi__get16be(s) >> 8); + } else { + for (i = 0; i < pixelCount; i++, p += 4) + *p = stbi__get8(s); + } + } + } + } + } + + // remove weird white matte from PSD + if (channelCount >= 4) { + if (ri->bits_per_channel == 16) { + for (i=0; i < w*h; ++i) { + stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; + if (pixel[3] != 0 && pixel[3] != 65535) { + float a = pixel[3] / 65535.0f; + float ra = 1.0f / a; + float inv_a = 65535.0f * (1 - ra); + pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); + pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); + pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); + } + } + } else { + for (i=0; i < w*h; ++i) { + unsigned char *pixel = out + 4*i; + if (pixel[3] != 0 && pixel[3] != 255) { + float a = pixel[3] / 255.0f; + float ra = 1.0f / a; + float inv_a = 255.0f * (1 - ra); + pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); + pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); + pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); + } + } + } + } + + // convert to desired output format + if (req_comp && req_comp != 4) { + if (ri->bits_per_channel == 16) + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); + else + out = stbi__convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + if (comp) *comp = 4; + *y = h; + *x = w; + + return out; +} +#endif + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +#ifndef STBI_NO_PIC +static int stbi__pic_is4(stbi__context *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (stbi__get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int stbi__pic_test_core(stbi__context *s) +{ + int i; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + stbi__get8(s); + + if (!stbi__pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} stbi__pic_packet; + +static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); + dest[i]=stbi__get8(s); + } + } + + return dest; +} + +static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) +{ + int mask=0x80,i; + + for (i=0;i<4; ++i, mask>>=1) + if (channel&mask) + dest[i]=src[i]; +} + +static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + stbi__pic_packet packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return stbi__errpuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + + act_comp |= packet->channel; + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; ytype) { + default: + return stbi__errpuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;xchannel,dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left=width, i; + + while (left>0) { + stbi_uc count,value[4]; + + count=stbi__get8(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (stbi_uc) left; + + if (!stbi__readval(s,packet->channel,value)) return 0; + + for(i=0; ichannel,dest,value); + left -= count; + } + } + break; + + case 2: {//Mixed RLE + int left=width; + while (left>0) { + int count = stbi__get8(s), i; + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + + if (count==128) + count = stbi__get16be(s); + else + count -= 127; + if (count > left) + return stbi__errpuc("bad file","scanline overrun"); + + if (!stbi__readval(s,packet->channel,value)) + return 0; + + for(i=0;ichannel,dest,value); + } else { // Raw + ++count; + if (count>left) return stbi__errpuc("bad file","scanline overrun"); + + for(i=0;ichannel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) +{ + stbi_uc *result; + int i, x,y, internal_comp; + STBI_NOTUSED(ri); + + if (!comp) comp = &internal_comp; + + for (i=0; i<92; ++i) + stbi__get8(s); + + x = stbi__get16be(s); + y = stbi__get16be(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); + if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); + + stbi__get32be(s); //skip `ratio' + stbi__get16be(s); //skip `fields' + stbi__get16be(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); + memset(result, 0xff, x*y*4); + + if (!stbi__pic_load_core(s,x,y,comp, result)) { + STBI_FREE(result); + result=0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=stbi__convert_format(result,4,req_comp,x,y); + + return result; +} + +static int stbi__pic_test(stbi__context *s) +{ + int r = stbi__pic_test_core(s); + stbi__rewind(s); + return r; +} +#endif + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb + +#ifndef STBI_NO_GIF +typedef struct +{ + stbi__int16 prefix; + stbi_uc first; + stbi_uc suffix; +} stbi__gif_lzw; + +typedef struct +{ + int w,h; + stbi_uc *out; // output buffer (always 4 components) + stbi_uc *background; // The current "background" as far as a gif is concerned + stbi_uc *history; + int flags, bgindex, ratio, transparent, eflags; + stbi_uc pal[256][4]; + stbi_uc lpal[256][4]; + stbi__gif_lzw codes[8192]; + stbi_uc *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; + int delay; +} stbi__gif; + +static int stbi__gif_test_raw(stbi__context *s) +{ + int sz; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; + sz = stbi__get8(s); + if (sz != '9' && sz != '7') return 0; + if (stbi__get8(s) != 'a') return 0; + return 1; +} + +static int stbi__gif_test(stbi__context *s) +{ + int r = stbi__gif_test_raw(s); + stbi__rewind(s); + return r; +} + +static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + pal[i][3] = transp == i ? 0 : 255; + } +} + +static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) +{ + stbi_uc version; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') + return stbi__err("not GIF", "Corrupt GIF"); + + version = stbi__get8(s); + if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); + if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); + + stbi__g_failure_reason = ""; + g->w = stbi__get16le(s); + g->h = stbi__get16le(s); + g->flags = stbi__get8(s); + g->bgindex = stbi__get8(s); + g->ratio = stbi__get8(s); + g->transparent = -1; + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + if (!stbi__gif_header(s, g, comp, 1)) { + STBI_FREE(g); + stbi__rewind( s ); + return 0; + } + if (x) *x = g->w; + if (y) *y = g->h; + STBI_FREE(g); + return 1; +} + +static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) +{ + stbi_uc *p, *c; + int idx; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi__out_gif_code(g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + idx = g->cur_x + g->cur_y; + p = &g->out[idx]; + g->history[idx / 4] = 1; + + c = &g->color_table[g->codes[code].suffix * 4]; + if (c[3] > 128) { // don't render transparent pixels; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) +{ + stbi_uc lzw_cs; + stbi__int32 len, init_code; + stbi__uint32 first; + stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi__gif_lzw *p; + + lzw_cs = stbi__get8(s); + if (lzw_cs > 12) return NULL; + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (init_code = 0; init_code < clear; init_code++) { + g->codes[init_code].prefix = -1; + g->codes[init_code].first = (stbi_uc) init_code; + g->codes[init_code].suffix = (stbi_uc) init_code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = stbi__get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (stbi__int32) stbi__get8(s) << valid_bits; + valid_bits += 8; + } else { + stbi__int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + stbi__skip(s, len); + while ((len = stbi__get8(s)) > 0) + stbi__skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) { + return stbi__errpuc("no clear code", "Corrupt GIF"); + } + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 8192) { + return stbi__errpuc("too many codes", "Corrupt GIF"); + } + + p->prefix = (stbi__int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + + stbi__out_gif_code(g, (stbi__uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +// two back is the image from two frames ago, used for a very specific disposal format +static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back) +{ + int dispose; + int first_frame; + int pi; + int pcount; + STBI_NOTUSED(req_comp); + + // on first frame, any non-written pixels get the background colour (non-transparent) + first_frame = 0; + if (g->out == 0) { + if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header + if (!stbi__mad3sizes_valid(4, g->w, g->h, 0)) + return stbi__errpuc("too large", "GIF image is too large"); + pcount = g->w * g->h; + g->out = (stbi_uc *) stbi__malloc(4 * pcount); + g->background = (stbi_uc *) stbi__malloc(4 * pcount); + g->history = (stbi_uc *) stbi__malloc(pcount); + if (!g->out || !g->background || !g->history) + return stbi__errpuc("outofmem", "Out of memory"); + + // image is treated as "transparent" at the start - ie, nothing overwrites the current background; + // background colour is only used for pixels that are not rendered first frame, after that "background" + // color refers to the color that was there the previous frame. + memset(g->out, 0x00, 4 * pcount); + memset(g->background, 0x00, 4 * pcount); // state of the background (starts transparent) + memset(g->history, 0x00, pcount); // pixels that were affected previous frame + first_frame = 1; + } else { + // second frame - how do we dispoase of the previous one? + dispose = (g->eflags & 0x1C) >> 2; + pcount = g->w * g->h; + + if ((dispose == 3) && (two_back == 0)) { + dispose = 2; // if I don't have an image to revert back to, default to the old background + } + + if (dispose == 3) { // use previous graphic + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 ); + } + } + } else if (dispose == 2) { + // restore what was changed last frame to background before that frame; + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 ); + } + } + } else { + // This is a non-disposal case eithe way, so just + // leave the pixels as is, and they will become the new background + // 1: do not dispose + // 0: not specified. + } + + // background is what out is after the undoing of the previou frame; + memcpy( g->background, g->out, 4 * g->w * g->h ); + } + + // clear my history; + memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame + + for (;;) { + int tag = stbi__get8(s); + switch (tag) { + case 0x2C: /* Image Descriptor */ + { + stbi__int32 x, y, w, h; + stbi_uc *o; + + x = stbi__get16le(s); + y = stbi__get16le(s); + w = stbi__get16le(s); + h = stbi__get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + // if the width of the specified rectangle is 0, that means + // we may not see *any* pixels or the image is malformed; + // to make sure this is caught, move the current y down to + // max_y (which is what out_gif_code checks). + if (w == 0) + g->cur_y = g->max_y; + + g->lflags = stbi__get8(s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (stbi_uc *) g->lpal; + } else if (g->flags & 0x80) { + g->color_table = (stbi_uc *) g->pal; + } else + return stbi__errpuc("missing color table", "Corrupt GIF"); + + o = stbi__process_gif_raster(s, g); + if (!o) return NULL; + + // if this was the first frame, + pcount = g->w * g->h; + if (first_frame && (g->bgindex > 0)) { + // if first frame, any pixel not drawn to gets the background color + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi] == 0) { + g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be; + memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 ); + } + } + } + + return o; + } + + case 0x21: // Comment Extension. + { + int len; + int ext = stbi__get8(s); + if (ext == 0xF9) { // Graphic Control Extension. + len = stbi__get8(s); + if (len == 4) { + g->eflags = stbi__get8(s); + g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths. + + // unset old transparent + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 255; + } + if (g->eflags & 0x01) { + g->transparent = stbi__get8(s); + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 0; + } + } else { + // don't need transparent + stbi__skip(s, 1); + g->transparent = -1; + } + } else { + stbi__skip(s, len); + break; + } + } + while ((len = stbi__get8(s)) != 0) { + stbi__skip(s, len); + } + break; + } + + case 0x3B: // gif stream termination code + return (stbi_uc *) s; // using '1' causes warning on some compilers + + default: + return stbi__errpuc("unknown code", "Corrupt GIF"); + } + } +} + +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + if (stbi__gif_test(s)) { + int layers = 0; + stbi_uc *u = 0; + stbi_uc *out = 0; + stbi_uc *two_back = 0; + stbi__gif g; + int stride; + memset(&g, 0, sizeof(g)); + if (delays) { + *delays = 0; + } + + do { + u = stbi__gif_load_next(s, &g, comp, req_comp, two_back); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + + if (u) { + *x = g.w; + *y = g.h; + ++layers; + stride = g.w * g.h * 4; + + if (out) { + void *tmp = (stbi_uc*) STBI_REALLOC( out, layers * stride ); + if (NULL == tmp) { + STBI_FREE(g.out); + STBI_FREE(g.history); + STBI_FREE(g.background); + return stbi__errpuc("outofmem", "Out of memory"); + } + else + out = (stbi_uc*) tmp; + if (delays) { + *delays = (int*) STBI_REALLOC( *delays, sizeof(int) * layers ); + } + } else { + out = (stbi_uc*)stbi__malloc( layers * stride ); + if (delays) { + *delays = (int*) stbi__malloc( layers * sizeof(int) ); + } + } + memcpy( out + ((layers - 1) * stride), u, stride ); + if (layers >= 2) { + two_back = out - 2 * stride; + } + + if (delays) { + (*delays)[layers - 1U] = g.delay; + } + } + } while (u != 0); + + // free temp buffer; + STBI_FREE(g.out); + STBI_FREE(g.history); + STBI_FREE(g.background); + + // do the final conversion after loading everything; + if (req_comp && req_comp != 4) + out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h); + + *z = layers; + return out; + } else { + return stbi__errpuc("not GIF", "Image was not as a gif type."); + } +} + +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *u = 0; + stbi__gif g; + memset(&g, 0, sizeof(g)); + STBI_NOTUSED(ri); + + u = stbi__gif_load_next(s, &g, comp, req_comp, 0); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + if (u) { + *x = g.w; + *y = g.h; + + // moved conversion to after successful load so that the same + // can be done for multiple frames. + if (req_comp && req_comp != 4) + u = stbi__convert_format(u, 4, req_comp, g.w, g.h); + } else if (g.out) { + // if there was an error and we allocated an image buffer, free it! + STBI_FREE(g.out); + } + + // free buffers needed for multiple frame loading; + STBI_FREE(g.history); + STBI_FREE(g.background); + + return u; +} + +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) +{ + return stbi__gif_info_raw(s,x,y,comp); +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int stbi__hdr_test_core(stbi__context *s, const char *signature) +{ + int i; + for (i=0; signature[i]; ++i) + if (stbi__get8(s) != signature[i]) + return 0; + stbi__rewind(s); + return 1; +} + +static int stbi__hdr_test(stbi__context* s) +{ + int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); + stbi__rewind(s); + if(!r) { + r = stbi__hdr_test_core(s, "#?RGBE\n"); + stbi__rewind(s); + } + return r; +} + +#define STBI__HDR_BUFLEN 1024 +static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) stbi__get8(z); + + while (!stbi__at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == STBI__HDR_BUFLEN-1) { + // flush to end of line + while (!stbi__at_eof(z) && stbi__get8(z) != '\n') + ; + break; + } + c = (char) stbi__get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if ( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + const char *headerToken; + STBI_NOTUSED(ri); + + // Check identifier + headerToken = stbi__hdr_gettoken(s,buffer); + if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) + return stbi__errpf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = (int) strtol(token, NULL, 10); + + *x = width; + *y = height; + + if (comp) *comp = 3; + if (req_comp == 0) req_comp = 3; + + if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) + return stbi__errpf("too large", "HDR image is too large"); + + // Read data + hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); + if (!hdr_data) + return stbi__errpf("outofmem", "Out of memory"); + + // Load image data + // image data is stored as some number of sca + if ( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + stbi__getn(s, rgbe, 4); + stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = stbi__get8(s); + c2 = stbi__get8(s); + len = stbi__get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi_uc rgbe[4]; + rgbe[0] = (stbi_uc) c1; + rgbe[1] = (stbi_uc) c2; + rgbe[2] = (stbi_uc) len; + rgbe[3] = (stbi_uc) stbi__get8(s); + stbi__hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + STBI_FREE(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= stbi__get8(s); + if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) { + scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); + if (!scanline) { + STBI_FREE(hdr_data); + return stbi__errpf("outofmem", "Out of memory"); + } + } + + for (k = 0; k < 4; ++k) { + int nleft; + i = 0; + while ((nleft = width - i) > 0) { + count = stbi__get8(s); + if (count > 128) { + // Run + value = stbi__get8(s); + count -= 128; + if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = stbi__get8(s); + } + } + } + for (i=0; i < width; ++i) + stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + if (scanline) + STBI_FREE(scanline); + } + + return hdr_data; +} + +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int dummy; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (stbi__hdr_test(s) == 0) { + stbi__rewind( s ); + return 0; + } + + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) { + stbi__rewind( s ); + return 0; + } + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *y = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *x = (int) strtol(token, NULL, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +#ifndef STBI_NO_BMP +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) +{ + void *p; + stbi__bmp_data info; + + info.all_a = 255; + p = stbi__bmp_parse_header(s, &info); + stbi__rewind( s ); + if (p == NULL) + return 0; + if (x) *x = s->img_x; + if (y) *y = s->img_y; + if (comp) { + if (info.bpp == 24 && info.ma == 0xff000000) + *comp = 3; + else + *comp = info.ma ? 4 : 3; + } + return 1; +} +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) +{ + int channelCount, dummy, depth; + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + *y = stbi__get32be(s); + *x = stbi__get32be(s); + depth = stbi__get16be(s); + if (depth != 8 && depth != 16) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 3) { + stbi__rewind( s ); + return 0; + } + *comp = 4; + return 1; +} + +static int stbi__psd_is16(stbi__context *s) +{ + int channelCount, depth; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + (void) stbi__get32be(s); + (void) stbi__get32be(s); + depth = stbi__get16be(s); + if (depth != 16) { + stbi__rewind( s ); + return 0; + } + return 1; +} +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) +{ + int act_comp=0,num_packets=0,chained,dummy; + stbi__pic_packet packets[10]; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { + stbi__rewind(s); + return 0; + } + + stbi__skip(s, 88); + + *x = stbi__get16be(s); + *y = stbi__get16be(s); + if (stbi__at_eof(s)) { + stbi__rewind( s); + return 0; + } + if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi__rewind( s ); + return 0; + } + + stbi__skip(s, 8); + + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + act_comp |= packet->channel; + + if (stbi__at_eof(s)) { + stbi__rewind( s ); + return 0; + } + if (packet->size != 8) { + stbi__rewind( s ); + return 0; + } + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); + + return 1; +} +#endif + +// ************************************************************************************************* +// Portable Gray Map and Portable Pixel Map loader +// by Ken Miller +// +// PGM: http://netpbm.sourceforge.net/doc/pgm.html +// PPM: http://netpbm.sourceforge.net/doc/ppm.html +// +// Known limitations: +// Does not support comments in the header section +// Does not support ASCII image data (formats P2 and P3) +// Does not support 16-bit-per-channel + +#ifndef STBI_NO_PNM + +static int stbi__pnm_test(stbi__context *s) +{ + char p, t; + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + return 1; +} + +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + STBI_NOTUSED(ri); + + if (!stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n)) + return 0; + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + + if (!stbi__mad3sizes_valid(s->img_n, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "PNM too large"); + + out = (stbi_uc *) stbi__malloc_mad3(s->img_n, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + stbi__getn(s, out, s->img_n * s->img_x * s->img_y); + + if (req_comp && req_comp != s->img_n) { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + return out; +} + +static int stbi__pnm_isspace(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) +{ + for (;;) { + while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) + *c = (char) stbi__get8(s); + + if (stbi__at_eof(s) || *c != '#') + break; + + while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) + *c = (char) stbi__get8(s); + } +} + +static int stbi__pnm_isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int stbi__pnm_getinteger(stbi__context *s, char *c) +{ + int value = 0; + + while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { + value = value*10 + (*c - '0'); + *c = (char) stbi__get8(s); + } + + return value; +} + +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) +{ + int maxv, dummy; + char c, p, t; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + stbi__rewind(s); + + // Get identifier + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind(s); + return 0; + } + + *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm + + c = (char) stbi__get8(s); + stbi__pnm_skip_whitespace(s, &c); + + *x = stbi__pnm_getinteger(s, &c); // read width + stbi__pnm_skip_whitespace(s, &c); + + *y = stbi__pnm_getinteger(s, &c); // read height + stbi__pnm_skip_whitespace(s, &c); + + maxv = stbi__pnm_getinteger(s, &c); // read max value + + if (maxv > 255) + return stbi__err("max value > 255", "PPM image not 8-bit"); + else + return 1; +} +#endif + +static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNG + if (stbi__png_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_GIF + if (stbi__gif_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_BMP + if (stbi__bmp_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PIC + if (stbi__pic_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_info(s, x, y, comp)) return 1; + #endif + + // test tga last because it's a crappy test! + #ifndef STBI_NO_TGA + if (stbi__tga_info(s, x, y, comp)) + return 1; + #endif + return stbi__err("unknown image type", "Image not of any known type, or corrupt"); +} + +static int stbi__is_16_main(stbi__context *s) +{ + #ifndef STBI_NO_PNG + if (stbi__png_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_is16(s)) return 1; + #endif + + return 0; +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__info_main(&s,x,y,comp); + fseek(f,pos,SEEK_SET); + return r; +} + +STBIDEF int stbi_is_16_bit(char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_is_16_bit_from_file(f); + fclose(f); + return result; +} + +STBIDEF int stbi_is_16_bit_from_file(FILE *f) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__is_16_main(&s); + fseek(f,pos,SEEK_SET); + return r; +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__is_16_main(&s); +} + +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__is_16_main(&s); +} + +#endif // STB_IMAGE_IMPLEMENTATION + +/* + revision history: + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug + 1-bit BMP + *_is_16_bit api + avoid warnings + 2.16 (2017-07-23) all functions have 16-bit variants; + STBI_NO_STDIO works again; + compilation fixes; + fix rounding in unpremultiply; + optimize vertical flip; + disable raw_len validation; + documentation fixes + 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode; + warning fixes; disable run-time SSE detection on gcc; + uniform handling of optional "return" values; + thread-safe initialization of zlib tables + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) allocate large structures on the stack + remove white matting for transparent PSD + fix reported channel count for PNG & BMP + re-enable SSE2 in non-gcc 64-bit + support RGB-formatted JPEG + read 16-bit PNGs (only as 8-bit) + 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED + 2.09 (2016-01-16) allow comments in PNM files + 16-bit-per-pixel TGA (not bit-per-component) + info() for TGA could break due to .hdr handling + info() for BMP to shares code instead of sloppy parse + can use STBI_REALLOC_SIZED if allocator doesn't support realloc + code cleanup + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) fix compiler warnings + partial animated GIF support + limited 16-bpc PSD support + #ifdef unused functions + bug with < 92 byte PIC,PNM,HDR,TGA + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) + progressive JPEG (stb) + PGM/PPM support (Ken Miller) + STBI_MALLOC,STBI_REALLOC,STBI_FREE + GIF bugfix -- seemingly never worked + STBI_NO_*, STBI_ONLY_* + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) + optimize PNG (ryg) + fix bug in interlaced PNG with user-specified channel count (stb) + 1.46 (2014-08-26) + fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG + 1.45 (2014-08-16) + fix MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) + various warning fixes from Ronny Chevalier + 1.43 (2014-07-15) + fix MSVC-only compiler problem in code changed in 1.42 + 1.42 (2014-07-09) + don't define _CRT_SECURE_NO_WARNINGS (affects user code) + fixes to stbi__cleanup_jpeg path + added STBI_ASSERT to avoid requiring assert.h + 1.41 (2014-06-25) + fix search&replace from 1.36 that messed up comments/error messages + 1.40 (2014-06-22) + fix gcc struct-initialization warning + 1.39 (2014-06-15) + fix to TGA optimization when req_comp != number of components in TGA; + fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) + add support for BMP version 5 (more ignored fields) + 1.38 (2014-06-06) + suppress MSVC warnings on integer casts truncating values + fix accidental rename of 'skip' field of I/O + 1.37 (2014-06-04) + remove duplicate typedef + 1.36 (2014-06-03) + convert to header file single-file library + if de-iphone isn't set, load iphone images color-swapped instead of returning NULL + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi_uc to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) + 1.21 fix use of 'stbi_uc' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 (2008-08-02) + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - stbi__convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 (2006-11-19) + first released version +*/ + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/Plugins/Texture/stb_image_write.h b/Plugins/Texture/stb_image_write.h new file mode 100644 index 0000000..09b3944 --- /dev/null +++ b/Plugins/Texture/stb_image_write.h @@ -0,0 +1,1666 @@ +/* stb_image_write - v1.14 - public domain - http://nothings.org/stb + writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015 + no warranty implied; use at your own risk + + Before #including, + + #define STB_IMAGE_WRITE_IMPLEMENTATION + + in the file that you want to have the implementation. + + Will probably not work correctly with strict-aliasing optimizations. + +ABOUT: + + This header file is a library for writing images to C stdio or a callback. + + The PNG output is not optimal; it is 20-50% larger than the file + written by a decent optimizing implementation; though providing a custom + zlib compress function (see STBIW_ZLIB_COMPRESS) can mitigate that. + This library is designed for source code compactness and simplicity, + not optimal image file size or run-time performance. + +BUILDING: + + You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. + You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace + malloc,realloc,free. + You can #define STBIW_MEMMOVE() to replace memmove() + You can #define STBIW_ZLIB_COMPRESS to use a custom zlib-style compress function + for PNG compression (instead of the builtin one), it must have the following signature: + unsigned char * my_compress(unsigned char *data, int data_len, int *out_len, int quality); + The returned data will be freed with STBIW_FREE() (free() by default), + so it must be heap allocated with STBIW_MALLOC() (malloc() by default), + +UNICODE: + + If compiling for Windows and you wish to use Unicode filenames, compile + with + #define STBIW_WINDOWS_UTF8 + and pass utf8-encoded filenames. Call stbiw_convert_wchar_to_utf8 to convert + Windows wchar_t filenames to utf8. + +USAGE: + + There are five functions, one for each image file format: + + int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_jpg(char const *filename, int w, int h, int comp, const void *data, int quality); + int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); + + void stbi_flip_vertically_on_write(int flag); // flag is non-zero to flip data vertically + + There are also five equivalent functions that use an arbitrary write function. You are + expected to open/close your file-equivalent before and after calling these: + + int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); + int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); + + where the callback is: + void stbi_write_func(void *context, void *data, int size); + + You can configure it with these global variables: + int stbi_write_tga_with_rle; // defaults to true; set to 0 to disable RLE + int stbi_write_png_compression_level; // defaults to 8; set to higher for more compression + int stbi_write_force_png_filter; // defaults to -1; set to 0..5 to force a filter mode + + + You can define STBI_WRITE_NO_STDIO to disable the file variant of these + functions, so the library will not use stdio.h at all. However, this will + also disable HDR writing, because it requires stdio for formatted output. + + Each function returns 0 on failure and non-0 on success. + + The functions create an image file defined by the parameters. The image + is a rectangle of pixels stored from left-to-right, top-to-bottom. + Each pixel contains 'comp' channels of data stored interleaved with 8-bits + per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is + monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. + The *data pointer points to the first byte of the top-left-most pixel. + For PNG, "stride_in_bytes" is the distance in bytes from the first byte of + a row of pixels to the first byte of the next row of pixels. + + PNG creates output files with the same number of components as the input. + The BMP format expands Y to RGB in the file format and does not + output alpha. + + PNG supports writing rectangles of data even when the bytes storing rows of + data are not consecutive in memory (e.g. sub-rectangles of a larger image), + by supplying the stride between the beginning of adjacent rows. The other + formats do not. (Thus you cannot write a native-format BMP through the BMP + writer, both because it is in BGR order and because it may have padding + at the end of the line.) + + PNG allows you to set the deflate compression level by setting the global + variable 'stbi_write_png_compression_level' (it defaults to 8). + + HDR expects linear float data. Since the format is always 32-bit rgb(e) + data, alpha (if provided) is discarded, and for monochrome data it is + replicated across all three channels. + + TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed + data, set the global variable 'stbi_write_tga_with_rle' to 0. + + JPEG does ignore alpha channels in input data; quality is between 1 and 100. + Higher quality looks better but results in a bigger image. + JPEG baseline (no JPEG progressive). + +CREDITS: + + + Sean Barrett - PNG/BMP/TGA + Baldur Karlsson - HDR + Jean-Sebastien Guay - TGA monochrome + Tim Kelsey - misc enhancements + Alan Hickman - TGA RLE + Emmanuel Julien - initial file IO callback implementation + Jon Olick - original jo_jpeg.cpp code + Daniel Gibson - integrate JPEG, allow external zlib + Aarni Koskela - allow choosing PNG filter + + bugfixes: + github:Chribba + Guillaume Chereau + github:jry2 + github:romigrou + Sergio Gonzalez + Jonas Karlsson + Filip Wasil + Thatcher Ulrich + github:poppolopoppo + Patrick Boettcher + github:xeekworx + Cap Petschulat + Simon Rodriguez + Ivan Tikhonov + github:ignotion + Adam Schackart + +LICENSE + + See end of file for license information. + +*/ + +#ifndef INCLUDE_STB_IMAGE_WRITE_H +#define INCLUDE_STB_IMAGE_WRITE_H + +#include + +// if STB_IMAGE_WRITE_STATIC causes problems, try defining STBIWDEF to 'inline' or 'static inline' +#ifndef STBIWDEF +#ifdef STB_IMAGE_WRITE_STATIC +#define STBIWDEF static +#else +#ifdef __cplusplus +#define STBIWDEF extern "C" +#else +#define STBIWDEF extern +#endif +#endif +#endif + +#ifndef STB_IMAGE_WRITE_STATIC // C++ forbids static forward declarations +extern int stbi_write_tga_with_rle; +extern int stbi_write_png_compression_level; +extern int stbi_write_force_png_filter; +#endif + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, bool invert_origin, const void *data); +STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality); + +#ifdef STBI_WINDOWS_UTF8 +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif +#endif + +typedef void stbi_write_func(void *context, void *data, int size); + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); + +STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean); + +#endif//INCLUDE_STB_IMAGE_WRITE_H + +#ifdef STB_IMAGE_WRITE_IMPLEMENTATION + +#ifdef _WIN32 + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif + #ifndef _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_NONSTDC_NO_DEPRECATE + #endif +#endif + +#ifndef STBI_WRITE_NO_STDIO +#include +#endif // STBI_WRITE_NO_STDIO + +#include +#include +#include +#include + +#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) +// ok +#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." +#endif + +#ifndef STBIW_MALLOC +#define STBIW_MALLOC(sz) malloc(sz) +#define STBIW_REALLOC(p,newsz) realloc(p,newsz) +#define STBIW_FREE(p) free(p) +#endif + +#ifndef STBIW_REALLOC_SIZED +#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) +#endif + + +#ifndef STBIW_MEMMOVE +#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) +#endif + + +#ifndef STBIW_ASSERT +#include +#define STBIW_ASSERT(x) assert(x) +#endif + +#define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) + +#ifdef STB_IMAGE_WRITE_STATIC +static int stbi_write_png_compression_level = 8; +static int stbi_write_tga_with_rle = 1; +static int stbi_write_force_png_filter = -1; +#else +int stbi_write_png_compression_level = 8; +int stbi_write_tga_with_rle = 1; +int stbi_write_force_png_filter = -1; +#endif + +static int stbi__flip_vertically_on_write = 0; + +STBIWDEF void stbi_flip_vertically_on_write(int flag) +{ + stbi__flip_vertically_on_write = flag; +} + +typedef struct +{ + stbi_write_func *func; + void *context; +} stbi__write_context; + +// initialize a callback-based context +static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context) +{ + s->func = c; + s->context = context; +} + +#ifndef STBI_WRITE_NO_STDIO + +static void stbi__stdio_write(void *context, void *data, int size) +{ + fwrite(data,1,size,(FILE*) context); +} + +#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) +#ifdef __cplusplus +#define STBIW_EXTERN extern "C" +#else +#define STBIW_EXTERN extern +#endif +STBIW_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBIW_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); + +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbiw__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode))) + return 0; + +#if _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + +static int stbi__start_write_file(stbi__write_context *s, const char *filename) +{ + FILE *f = stbiw__fopen(filename, "wb"); + stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f); + return f != NULL; +} + +static void stbi__end_write_file(stbi__write_context *s) +{ + fclose((FILE *)s->context); +} + +#endif // !STBI_WRITE_NO_STDIO + +typedef unsigned int stbiw_uint32; +typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; + +static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v) +{ + while (*fmt) { + switch (*fmt++) { + case ' ': break; + case '1': { unsigned char x = STBIW_UCHAR(va_arg(v, int)); + s->func(s->context,&x,1); + break; } + case '2': { int x = va_arg(v,int); + unsigned char b[2]; + b[0] = STBIW_UCHAR(x); + b[1] = STBIW_UCHAR(x>>8); + s->func(s->context,b,2); + break; } + case '4': { stbiw_uint32 x = va_arg(v,int); + unsigned char b[4]; + b[0]=STBIW_UCHAR(x); + b[1]=STBIW_UCHAR(x>>8); + b[2]=STBIW_UCHAR(x>>16); + b[3]=STBIW_UCHAR(x>>24); + s->func(s->context,b,4); + break; } + default: + STBIW_ASSERT(0); + return; + } + } +} + +static void stbiw__writef(stbi__write_context *s, const char *fmt, ...) +{ + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); +} + +static void stbiw__putc(stbi__write_context *s, unsigned char c) +{ + s->func(s->context, &c, 1); +} + +static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c) +{ + unsigned char arr[3]; + arr[0] = a; arr[1] = b; arr[2] = c; + s->func(s->context, arr, 3); +} + +static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d) +{ + unsigned char bg[3] = { 255, 0, 255}, px[3]; + int k; + + if (write_alpha < 0) + s->func(s->context, &d[comp - 1], 1); + + switch (comp) { + case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case + case 1: + if (expand_mono) + stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp + else + s->func(s->context, d, 1); // monochrome TGA + break; + case 4: + if (!write_alpha) { + // composite against pink background + for (k = 0; k < 3; ++k) + px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255; + stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]); + break; + } + /* FALLTHROUGH */ + case 3: + stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]); + break; + } + if (write_alpha > 0) + s->func(s->context, &d[comp - 1], 1); +} + +static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono) +{ + stbiw_uint32 zero = 0; + int i,j, j_end; + + if (y <= 0) + return; + + if (stbi__flip_vertically_on_write) + vdir *= -1; + + if (vdir < 0) { + j_end = -1; j = y-1; + } else { + j_end = y; j = 0; + } + + for (; j != j_end; j += vdir) { + for (i=0; i < x; ++i) { + unsigned char *d = (unsigned char *) data + (j*x+i)*comp; + stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d); + } + s->func(s->context, &zero, scanline_pad); + } +} + +static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...) +{ + if (y < 0 || x < 0) { + return 0; + } else { + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); + stbiw__write_pixels(s,rgb_dir,vdir,x,y,comp,data,alpha,pad, expand_mono); + return 1; + } +} + +static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data) +{ + int pad = (-x*3) & 3; + return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad, + "11 4 22 4" "4 44 22 444444", + 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header + 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header +} + +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_bmp_core(&s, x, y, comp, data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_bmp_core(&s, x, y, comp, data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif //!STBI_WRITE_NO_STDIO + +static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, bool invert_origin, void *data) +{ + int has_alpha = (comp == 2 || comp == 4); + int colorbytes = has_alpha ? comp-1 : comp; + int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 + + if (y < 0 || x < 0) + return 0; + + if (!stbi_write_tga_with_rle) { + return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *) data, has_alpha, 0, + "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, (has_alpha * 8) | (invert_origin ? 32 : 0)); + } else { + int i,j,k; + int jend, jdir; + + stbiw__writef(s, "111 221 2222 11", 0,0,format+8, 0,0,0, 0,0,x,y, (colorbytes + has_alpha) * 8, (has_alpha * 8) | (invert_origin ? 32 : 0)); + + if (stbi__flip_vertically_on_write) { + j = 0; + jend = y; + jdir = 1; + } else { + j = y-1; + jend = -1; + jdir = -1; + } + for (; j != jend; j += jdir) { + unsigned char *row = (unsigned char *) data + j * x * comp; + int len; + + for (i = 0; i < x; i += len) { + unsigned char *begin = row + i * comp; + int diff = 1; + len = 1; + + if (i < x - 1) { + ++len; + diff = memcmp(begin, row + (i + 1) * comp, comp); + if (diff) { + const unsigned char *prev = begin; + for (k = i + 2; k < x && len < 128; ++k) { + if (memcmp(prev, row + k * comp, comp)) { + prev += comp; + ++len; + } else { + --len; + break; + } + } + } else { + for (k = i + 2; k < x && len < 128; ++k) { + if (!memcmp(begin, row + k * comp, comp)) { + ++len; + } else { + break; + } + } + } + } + + if (diff) { + unsigned char header = STBIW_UCHAR(len - 1); + s->func(s->context, &header, 1); + for (k = 0; k < len; ++k) { + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp); + } + } else { + unsigned char header = STBIW_UCHAR(len - 129); + s->func(s->context, &header, 1); + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin); + } + } + } + } + return 1; +} + +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_tga_core(&s, x, y, comp, false, (void *) data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_tga(char const *filename, int x, int y, int comp, bool invert_origin, const void *data) +{ + stbi__write_context s; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_tga_core(&s, x, y, comp, invert_origin, (void *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR writer +// by Baldur Karlsson + +#define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) + +static void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) +{ + int exponent; + float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); + + if (maxcomp < 1e-32f) { + rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; + } else { + float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp; + + rgbe[0] = (unsigned char)(linear[0] * normalize); + rgbe[1] = (unsigned char)(linear[1] * normalize); + rgbe[2] = (unsigned char)(linear[2] * normalize); + rgbe[3] = (unsigned char)(exponent + 128); + } +} + +static void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte) +{ + unsigned char lengthbyte = STBIW_UCHAR(length+128); + STBIW_ASSERT(length+128 <= 255); + s->func(s->context, &lengthbyte, 1); + s->func(s->context, &databyte, 1); +} + +static void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data) +{ + unsigned char lengthbyte = STBIW_UCHAR(length); + STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code + s->func(s->context, &lengthbyte, 1); + s->func(s->context, data, length); +} + +static void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline) +{ + unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; + unsigned char rgbe[4]; + float linear[3]; + int x; + + scanlineheader[2] = (width&0xff00)>>8; + scanlineheader[3] = (width&0x00ff); + + /* skip RLE for images too small or large */ + if (width < 8 || width >= 32768) { + for (x=0; x < width; x++) { + switch (ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + s->func(s->context, rgbe, 4); + } + } else { + int c,r; + /* encode into scratch buffer */ + for (x=0; x < width; x++) { + switch(ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + scratch[x + width*0] = rgbe[0]; + scratch[x + width*1] = rgbe[1]; + scratch[x + width*2] = rgbe[2]; + scratch[x + width*3] = rgbe[3]; + } + + s->func(s->context, scanlineheader, 4); + + /* RLE each component separately */ + for (c=0; c < 4; c++) { + unsigned char *comp = &scratch[width*c]; + + x = 0; + while (x < width) { + // find first run + r = x; + while (r+2 < width) { + if (comp[r] == comp[r+1] && comp[r] == comp[r+2]) + break; + ++r; + } + if (r+2 >= width) + r = width; + // dump up to first run + while (x < r) { + int len = r-x; + if (len > 128) len = 128; + stbiw__write_dump_data(s, len, &comp[x]); + x += len; + } + // if there's a run, output it + if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd + // find next byte after run + while (r < width && comp[r] == comp[x]) + ++r; + // output run up to r + while (x < r) { + int len = r-x; + if (len > 127) len = 127; + stbiw__write_run_data(s, len, comp[x]); + x += len; + } + } + } + } + } +} + +static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, float *data) +{ + if (y <= 0 || x <= 0 || data == NULL) + return 0; + else { + // Each component is stored separately. Allocate scratch space for full output scanline. + unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4); + int i, len; + char buffer[128]; + char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; + s->func(s->context, header, sizeof(header)-1); + +#ifdef __STDC_WANT_SECURE_LIB__ + len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#else + len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#endif + s->func(s->context, buffer, len); + + for(i=0; i < y; i++) + stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*x*(stbi__flip_vertically_on_write ? y-1-i : i)); + STBIW_FREE(scratch); + return 1; + } +} + +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data) +{ + stbi__write_context s; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_hdr_core(&s, x, y, comp, (float *) data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data) +{ + stbi__write_context s; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif // STBI_WRITE_NO_STDIO + + +////////////////////////////////////////////////////////////////////////////// +// +// PNG writer +// + +#ifndef STBIW_ZLIB_COMPRESS +// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size() +#define stbiw__sbraw(a) ((int *) (void *) (a) - 2) +#define stbiw__sbm(a) stbiw__sbraw(a)[0] +#define stbiw__sbn(a) stbiw__sbraw(a)[1] + +#define stbiw__sbneedgrow(a,n) ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a)) +#define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0) +#define stbiw__sbgrow(a,n) stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a))) + +#define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v)) +#define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) +#define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0) + +static void *stbiw__sbgrowf(void **arr, int increment, int itemsize) +{ + int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1; + void *p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2); + STBIW_ASSERT(p); + if (p) { + if (!*arr) ((int *) p)[1] = 0; + *arr = (void *) ((int *) p + 2); + stbiw__sbm(*arr) = m; + } + return *arr; +} + +static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) +{ + while (*bitcount >= 8) { + stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer)); + *bitbuffer >>= 8; + *bitcount -= 8; + } + return data; +} + +static int stbiw__zlib_bitrev(int code, int codebits) +{ + int res=0; + while (codebits--) { + res = (res << 1) | (code & 1); + code >>= 1; + } + return res; +} + +static unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, int limit) +{ + int i; + for (i=0; i < limit && i < 258; ++i) + if (a[i] != b[i]) break; + return i; +} + +static unsigned int stbiw__zhash(unsigned char *data) +{ + stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + return hash; +} + +#define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount)) +#define stbiw__zlib_add(code,codebits) \ + (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush()) +#define stbiw__zlib_huffa(b,c) stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c) +// default huffman tables +#define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8) +#define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9) +#define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256,7) +#define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280,8) +#define stbiw__zlib_huff(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n)) +#define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n)) + +#define stbiw__ZHASH 16384 + +#endif // STBIW_ZLIB_COMPRESS + +STBIWDEF unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) +{ +#ifdef STBIW_ZLIB_COMPRESS + // user provided a zlib compress implementation, use that + return STBIW_ZLIB_COMPRESS(data, data_len, out_len, quality); +#else // use builtin + static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 }; + static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; + static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 }; + static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 }; + unsigned int bitbuf=0; + int i,j, bitcount=0; + unsigned char *out = NULL; + unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(unsigned char**)); + if (hash_table == NULL) + return NULL; + if (quality < 5) quality = 5; + + stbiw__sbpush(out, 0x78); // DEFLATE 32K window + stbiw__sbpush(out, 0x5e); // FLEVEL = 1 + stbiw__zlib_add(1,1); // BFINAL = 1 + stbiw__zlib_add(1,2); // BTYPE = 1 -- fixed huffman + + for (i=0; i < stbiw__ZHASH; ++i) + hash_table[i] = NULL; + + i=0; + while (i < data_len-3) { + // hash next 3 bytes of data to be compressed + int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3; + unsigned char *bestloc = 0; + unsigned char **hlist = hash_table[h]; + int n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32768) { // if entry lies within window + int d = stbiw__zlib_countm(hlist[j], data+i, data_len-i); + if (d >= best) { best=d; bestloc=hlist[j]; } + } + } + // when hash table entry is too long, delete half the entries + if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2*quality) { + STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); + stbiw__sbn(hash_table[h]) = quality; + } + stbiw__sbpush(hash_table[h],data+i); + + if (bestloc) { + // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal + h = stbiw__zhash(data+i+1)&(stbiw__ZHASH-1); + hlist = hash_table[h]; + n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32767) { + int e = stbiw__zlib_countm(hlist[j], data+i+1, data_len-i-1); + if (e > best) { // if next match is better, bail on current match + bestloc = NULL; + break; + } + } + } + } + + if (bestloc) { + int d = (int) (data+i - bestloc); // distance back + STBIW_ASSERT(d <= 32767 && best <= 258); + for (j=0; best > lengthc[j+1]-1; ++j); + stbiw__zlib_huff(j+257); + if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]); + for (j=0; d > distc[j+1]-1; ++j); + stbiw__zlib_add(stbiw__zlib_bitrev(j,5),5); + if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]); + i += best; + } else { + stbiw__zlib_huffb(data[i]); + ++i; + } + } + // write out final bytes + for (;i < data_len; ++i) + stbiw__zlib_huffb(data[i]); + stbiw__zlib_huff(256); // end of block + // pad with 0 bits to byte boundary + while (bitcount) + stbiw__zlib_add(0,1); + + for (i=0; i < stbiw__ZHASH; ++i) + (void) stbiw__sbfree(hash_table[i]); + STBIW_FREE(hash_table); + + { + // compute adler32 on input + unsigned int s1=1, s2=0; + int blocklen = (int) (data_len % 5552); + j=0; + while (j < data_len) { + for (i=0; i < blocklen; ++i) { s1 += data[j+i]; s2 += s1; } + s1 %= 65521; s2 %= 65521; + j += blocklen; + blocklen = 5552; + } + stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s2)); + stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s1)); + } + *out_len = stbiw__sbn(out); + // make returned pointer freeable + STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); + return (unsigned char *) stbiw__sbraw(out); +#endif // STBIW_ZLIB_COMPRESS +} + +static unsigned int stbiw__crc32(unsigned char *buffer, int len) +{ +#ifdef STBIW_CRC32 + return STBIW_CRC32(buffer, len); +#else + static unsigned int crc_table[256] = + { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + unsigned int crc = ~0u; + int i; + for (i=0; i < len; ++i) + crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; + return ~crc; +#endif +} + +#define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4) +#define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v)); +#define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3]) + +static void stbiw__wpcrc(unsigned char **data, int len) +{ + unsigned int crc = stbiw__crc32(*data - len - 4, len+4); + stbiw__wp32(*data, crc); +} + +static unsigned char stbiw__paeth(int a, int b, int c) +{ + int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c); + if (pa <= pb && pa <= pc) return STBIW_UCHAR(a); + if (pb <= pc) return STBIW_UCHAR(b); + return STBIW_UCHAR(c); +} + +// @OPTIMIZE: provide an option that always forces left-predict or paeth predict +static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int width, int height, int y, int n, int filter_type, signed char *line_buffer) +{ + static int mapping[] = { 0,1,2,3,4 }; + static int firstmap[] = { 0,1,0,5,6 }; + int *mymap = (y != 0) ? mapping : firstmap; + int i; + int type = mymap[filter_type]; + unsigned char *z = pixels + stride_bytes * (stbi__flip_vertically_on_write ? height-1-y : y); + int signed_stride = stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes; + + if (type==0) { + memcpy(line_buffer, z, width*n); + return; + } + + // first loop isn't optimized since it's just one pixel + for (i = 0; i < n; ++i) { + switch (type) { + case 1: line_buffer[i] = z[i]; break; + case 2: line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: line_buffer[i] = z[i] - (z[i-signed_stride]>>1); break; + case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-signed_stride],0)); break; + case 5: line_buffer[i] = z[i]; break; + case 6: line_buffer[i] = z[i]; break; + } + } + switch (type) { + case 1: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-n]; break; + case 2: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - ((z[i-n] + z[i-signed_stride])>>1); break; + case 4: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-signed_stride], z[i-signed_stride-n]); break; + case 5: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - (z[i-n]>>1); break; + case 6: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break; + } +} + +STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) +{ + int force_filter = stbi_write_force_png_filter; + int ctype[5] = { -1, 0, 4, 2, 6 }; + unsigned char sig[8] = { 137,80,78,71,13,10,26,10 }; + unsigned char *out,*o, *filt, *zlib; + signed char *line_buffer; + int j,zlen; + + if (stride_bytes == 0) + stride_bytes = x * n; + + if (force_filter >= 5) { + force_filter = -1; + } + + filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0; + line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; } + for (j=0; j < y; ++j) { + int filter_type; + if (force_filter > -1) { + filter_type = force_filter; + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, force_filter, line_buffer); + } else { // Estimate the best filter by running through all of them: + int best_filter = 0, best_filter_val = 0x7fffffff, est, i; + for (filter_type = 0; filter_type < 5; filter_type++) { + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, filter_type, line_buffer); + + // Estimate the entropy of the line using this filter; the less, the better. + est = 0; + for (i = 0; i < x*n; ++i) { + est += abs((signed char) line_buffer[i]); + } + if (est < best_filter_val) { + best_filter_val = est; + best_filter = filter_type; + } + } + if (filter_type != best_filter) { // If the last iteration already got us the best filter, don't redo it + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, best_filter, line_buffer); + filter_type = best_filter; + } + } + // when we get here, filter_type contains the filter type, and line_buffer contains the data + filt[j*(x*n+1)] = (unsigned char) filter_type; + STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n); + } + STBIW_FREE(line_buffer); + zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, stbi_write_png_compression_level); + STBIW_FREE(filt); + if (!zlib) return 0; + + // each tag requires 12 bytes of overhead + out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12); + if (!out) return 0; + *out_len = 8 + 12+13 + 12+zlen + 12; + + o=out; + STBIW_MEMMOVE(o,sig,8); o+= 8; + stbiw__wp32(o, 13); // header length + stbiw__wptag(o, "IHDR"); + stbiw__wp32(o, x); + stbiw__wp32(o, y); + *o++ = 8; + *o++ = STBIW_UCHAR(ctype[n]); + *o++ = 0; + *o++ = 0; + *o++ = 0; + stbiw__wpcrc(&o,13); + + stbiw__wp32(o, zlen); + stbiw__wptag(o, "IDAT"); + STBIW_MEMMOVE(o, zlib, zlen); + o += zlen; + STBIW_FREE(zlib); + stbiw__wpcrc(&o, zlen); + + stbiw__wp32(o,0); + stbiw__wptag(o, "IEND"); + stbiw__wpcrc(&o,0); + + STBIW_ASSERT(o == out + *out_len); + + return out; +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes) +{ + FILE *f; + int len; + unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + + f = stbiw__fopen(filename, "wb"); + if (!f) { STBIW_FREE(png); return 0; } + fwrite(png, 1, len, f); + fclose(f); + STBIW_FREE(png); + return 1; +} +#endif + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes) +{ + int len; + unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + func(context, png, len); + STBIW_FREE(png); + return 1; +} + + +/* *************************************************************************** + * + * JPEG writer + * + * This is based on Jon Olick's jo_jpeg.cpp: + * public domain Simple, Minimalistic JPEG writer - http://www.jonolick.com/code.html + */ + +static const unsigned char stbiw__jpg_ZigZag[] = { 0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18, + 24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63 }; + +static void stbiw__jpg_writeBits(stbi__write_context *s, int *bitBufP, int *bitCntP, const unsigned short *bs) { + int bitBuf = *bitBufP, bitCnt = *bitCntP; + bitCnt += bs[1]; + bitBuf |= bs[0] << (24 - bitCnt); + while(bitCnt >= 8) { + unsigned char c = (bitBuf >> 16) & 255; + stbiw__putc(s, c); + if(c == 255) { + stbiw__putc(s, 0); + } + bitBuf <<= 8; + bitCnt -= 8; + } + *bitBufP = bitBuf; + *bitCntP = bitCnt; +} + +static void stbiw__jpg_DCT(float *d0p, float *d1p, float *d2p, float *d3p, float *d4p, float *d5p, float *d6p, float *d7p) { + float d0 = *d0p, d1 = *d1p, d2 = *d2p, d3 = *d3p, d4 = *d4p, d5 = *d5p, d6 = *d6p, d7 = *d7p; + float z1, z2, z3, z4, z5, z11, z13; + + float tmp0 = d0 + d7; + float tmp7 = d0 - d7; + float tmp1 = d1 + d6; + float tmp6 = d1 - d6; + float tmp2 = d2 + d5; + float tmp5 = d2 - d5; + float tmp3 = d3 + d4; + float tmp4 = d3 - d4; + + // Even part + float tmp10 = tmp0 + tmp3; // phase 2 + float tmp13 = tmp0 - tmp3; + float tmp11 = tmp1 + tmp2; + float tmp12 = tmp1 - tmp2; + + d0 = tmp10 + tmp11; // phase 3 + d4 = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * 0.707106781f; // c4 + d2 = tmp13 + z1; // phase 5 + d6 = tmp13 - z1; + + // Odd part + tmp10 = tmp4 + tmp5; // phase 2 + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + // The rotator is modified from fig 4-8 to avoid extra negations. + z5 = (tmp10 - tmp12) * 0.382683433f; // c6 + z2 = tmp10 * 0.541196100f + z5; // c2-c6 + z4 = tmp12 * 1.306562965f + z5; // c2+c6 + z3 = tmp11 * 0.707106781f; // c4 + + z11 = tmp7 + z3; // phase 5 + z13 = tmp7 - z3; + + *d5p = z13 + z2; // phase 6 + *d3p = z13 - z2; + *d1p = z11 + z4; + *d7p = z11 - z4; + + *d0p = d0; *d2p = d2; *d4p = d4; *d6p = d6; +} + +static void stbiw__jpg_calcBits(int val, unsigned short bits[2]) { + int tmp1 = val < 0 ? -val : val; + val = val < 0 ? val-1 : val; + bits[1] = 1; + while(tmp1 >>= 1) { + ++bits[1]; + } + bits[0] = val & ((1<0)&&(DU[end0pos]==0); --end0pos) { + } + // end0pos = first element in reverse order !=0 + if(end0pos == 0) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + return DU[0]; + } + for(i = 1; i <= end0pos; ++i) { + int startpos = i; + int nrzeroes; + unsigned short bits[2]; + for (; DU[i]==0 && i<=end0pos; ++i) { + } + nrzeroes = i-startpos; + if ( nrzeroes >= 16 ) { + int lng = nrzeroes>>4; + int nrmarker; + for (nrmarker=1; nrmarker <= lng; ++nrmarker) + stbiw__jpg_writeBits(s, bitBuf, bitCnt, M16zeroes); + nrzeroes &= 15; + } + stbiw__jpg_calcBits(DU[i], bits); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTAC[(nrzeroes<<4)+bits[1]]); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits); + } + if(end0pos != 63) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + } + return DU[0]; +} + +static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, int comp, const void* data, int quality) { + // Constants that don't pollute global namespace + static const unsigned char std_dc_luminance_nrcodes[] = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0}; + static const unsigned char std_dc_luminance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_luminance_nrcodes[] = {0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d}; + static const unsigned char std_ac_luminance_values[] = { + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, + 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, + 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, + 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, + 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, + 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + static const unsigned char std_dc_chrominance_nrcodes[] = {0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0}; + static const unsigned char std_dc_chrominance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_chrominance_nrcodes[] = {0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77}; + static const unsigned char std_ac_chrominance_values[] = { + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, + 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, + 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, + 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, + 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, + 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + // Huffman tables + static const unsigned short YDC_HT[256][2] = { {0,2},{2,3},{3,3},{4,3},{5,3},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9}}; + static const unsigned short UVDC_HT[256][2] = { {0,2},{1,2},{2,2},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9},{1022,10},{2046,11}}; + static const unsigned short YAC_HT[256][2] = { + {10,4},{0,2},{1,2},{4,3},{11,4},{26,5},{120,7},{248,8},{1014,10},{65410,16},{65411,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {12,4},{27,5},{121,7},{502,9},{2038,11},{65412,16},{65413,16},{65414,16},{65415,16},{65416,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {28,5},{249,8},{1015,10},{4084,12},{65417,16},{65418,16},{65419,16},{65420,16},{65421,16},{65422,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{503,9},{4085,12},{65423,16},{65424,16},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1016,10},{65430,16},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2039,11},{65438,16},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {123,7},{4086,12},{65446,16},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {250,8},{4087,12},{65454,16},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{32704,15},{65462,16},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65470,16},{65471,16},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65479,16},{65480,16},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1017,10},{65488,16},{65489,16},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{65497,16},{65498,16},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2040,11},{65506,16},{65507,16},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {65515,16},{65516,16},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65525,16},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const unsigned short UVAC_HT[256][2] = { + {0,2},{1,2},{4,3},{10,4},{24,5},{25,5},{56,6},{120,7},{500,9},{1014,10},{4084,12},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {11,4},{57,6},{246,8},{501,9},{2038,11},{4085,12},{65416,16},{65417,16},{65418,16},{65419,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {26,5},{247,8},{1015,10},{4086,12},{32706,15},{65420,16},{65421,16},{65422,16},{65423,16},{65424,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {27,5},{248,8},{1016,10},{4087,12},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{65430,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{502,9},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{65438,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1017,10},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{65446,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {121,7},{2039,11},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{65454,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2040,11},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{65462,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {249,8},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{65470,16},{65471,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {503,9},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{65479,16},{65480,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{65488,16},{65489,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{65497,16},{65498,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{65506,16},{65507,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{65515,16},{65516,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {16352,14},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{65525,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{32707,15},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const int YQT[] = {16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22, + 37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99}; + static const int UVQT[] = {17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99, + 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99}; + static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f, + 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f }; + + int row, col, i, k, subsample; + float fdtbl_Y[64], fdtbl_UV[64]; + unsigned char YTable[64], UVTable[64]; + + if(!data || !width || !height || comp > 4 || comp < 1) { + return 0; + } + + quality = quality ? quality : 90; + subsample = quality <= 90 ? 1 : 0; + quality = quality < 1 ? 1 : quality > 100 ? 100 : quality; + quality = quality < 50 ? 5000 / quality : 200 - quality * 2; + + for(i = 0; i < 64; ++i) { + int uvti, yti = (YQT[i]*quality+50)/100; + YTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (yti < 1 ? 1 : yti > 255 ? 255 : yti); + uvti = (UVQT[i]*quality+50)/100; + UVTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (uvti < 1 ? 1 : uvti > 255 ? 255 : uvti); + } + + for(row = 0, k = 0; row < 8; ++row) { + for(col = 0; col < 8; ++col, ++k) { + fdtbl_Y[k] = 1 / (YTable [stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + fdtbl_UV[k] = 1 / (UVTable[stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + } + } + + // Write Headers + { + static const unsigned char head0[] = { 0xFF,0xD8,0xFF,0xE0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xFF,0xDB,0,0x84,0 }; + static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 }; + const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,(unsigned char)(height>>8),STBIW_UCHAR(height),(unsigned char)(width>>8),STBIW_UCHAR(width), + 3,1,(unsigned char)(subsample?0x22:0x11),0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 }; + s->func(s->context, (void*)head0, sizeof(head0)); + s->func(s->context, (void*)YTable, sizeof(YTable)); + stbiw__putc(s, 1); + s->func(s->context, UVTable, sizeof(UVTable)); + s->func(s->context, (void*)head1, sizeof(head1)); + s->func(s->context, (void*)(std_dc_luminance_nrcodes+1), sizeof(std_dc_luminance_nrcodes)-1); + s->func(s->context, (void*)std_dc_luminance_values, sizeof(std_dc_luminance_values)); + stbiw__putc(s, 0x10); // HTYACinfo + s->func(s->context, (void*)(std_ac_luminance_nrcodes+1), sizeof(std_ac_luminance_nrcodes)-1); + s->func(s->context, (void*)std_ac_luminance_values, sizeof(std_ac_luminance_values)); + stbiw__putc(s, 1); // HTUDCinfo + s->func(s->context, (void*)(std_dc_chrominance_nrcodes+1), sizeof(std_dc_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_dc_chrominance_values, sizeof(std_dc_chrominance_values)); + stbiw__putc(s, 0x11); // HTUACinfo + s->func(s->context, (void*)(std_ac_chrominance_nrcodes+1), sizeof(std_ac_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_ac_chrominance_values, sizeof(std_ac_chrominance_values)); + s->func(s->context, (void*)head2, sizeof(head2)); + } + + // Encode 8x8 macroblocks + { + static const unsigned short fillBits[] = {0x7F, 7}; + int DCY=0, DCU=0, DCV=0; + int bitBuf=0, bitCnt=0; + // comp == 2 is grey+alpha (alpha is ignored) + int ofsG = comp > 2 ? 1 : 0, ofsB = comp > 2 ? 2 : 0; + const unsigned char *dataR = (const unsigned char *)data; + const unsigned char *dataG = dataR + ofsG; + const unsigned char *dataB = dataR + ofsB; + int x, y, pos; + if(subsample) { + for(y = 0; y < height; y += 16) { + for(x = 0; x < width; x += 16) { + float Y[256], U[256], V[256]; + for(row = y, pos = 0; row < y+16; ++row) { + // row >= height => use last input row + int clamped_row = (row < height) ? row : height - 1; + int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; + for(col = x; col < x+16; ++col, ++pos) { + // if col >= width => use pixel from last input column + int p = base_p + ((col < width) ? col : (width-1))*comp; + float r = dataR[p], g = dataG[p], b = dataB[p]; + Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; + U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; + V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; + } + } + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+0, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+8, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+128, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+136, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + + // subsample U,V + { + float subU[64], subV[64]; + int yy, xx; + for(yy = 0, pos = 0; yy < 8; ++yy) { + for(xx = 0; xx < 8; ++xx, ++pos) { + int j = yy*32+xx*2; + subU[pos] = (U[j+0] + U[j+1] + U[j+16] + U[j+17]) * 0.25f; + subV[pos] = (V[j+0] + V[j+1] + V[j+16] + V[j+17]) * 0.25f; + } + } + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subU, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subV, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + } + } else { + for(y = 0; y < height; y += 8) { + for(x = 0; x < width; x += 8) { + float Y[64], U[64], V[64]; + for(row = y, pos = 0; row < y+8; ++row) { + // row >= height => use last input row + int clamped_row = (row < height) ? row : height - 1; + int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; + for(col = x; col < x+8; ++col, ++pos) { + // if col >= width => use pixel from last input column + int p = base_p + ((col < width) ? col : (width-1))*comp; + float r = dataR[p], g = dataG[p], b = dataB[p]; + Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; + U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; + V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; + } + } + + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y, 8, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, U, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, V, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + } + + // Do the bit alignment of the EOI marker + stbiw__jpg_writeBits(s, &bitBuf, &bitCnt, fillBits); + } + + // EOI + stbiw__putc(s, 0xFF); + stbiw__putc(s, 0xD9); + + return 1; +} + +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_jpg_core(&s, x, y, comp, (void *) data, quality); +} + + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_jpg_core(&s, x, y, comp, data, quality); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +#endif // STB_IMAGE_WRITE_IMPLEMENTATION + +/* Revision history + 1.14 (2020-02-02) updated JPEG writer to downsample chroma channels + 1.13 + 1.12 + 1.11 (2019-08-11) + + 1.10 (2019-02-07) + support utf8 filenames in Windows; fix warnings and platform ifdefs + 1.09 (2018-02-11) + fix typo in zlib quality API, improve STB_I_W_STATIC in C++ + 1.08 (2018-01-29) + add stbi__flip_vertically_on_write, external zlib, zlib quality, choose PNG filter + 1.07 (2017-07-24) + doc fix + 1.06 (2017-07-23) + writing JPEG (using Jon Olick's code) + 1.05 ??? + 1.04 (2017-03-03) + monochrome BMP expansion + 1.03 ??? + 1.02 (2016-04-02) + avoid allocating large structures on the stack + 1.01 (2016-01-16) + STBIW_REALLOC_SIZED: support allocators with no realloc support + avoid race-condition in crc initialization + minor compile issues + 1.00 (2015-09-14) + installable file IO function + 0.99 (2015-09-13) + warning fixes; TGA rle support + 0.98 (2015-04-08) + added STBIW_MALLOC, STBIW_ASSERT etc + 0.97 (2015-01-18) + fixed HDR asserts, rewrote HDR rle logic + 0.96 (2015-01-17) + add HDR output + fix monochrome BMP + 0.95 (2014-08-17) + add monochrome TGA output + 0.94 (2014-05-31) + rename private functions to avoid conflicts with stb_image.h + 0.93 (2014-05-27) + warning fixes + 0.92 (2010-08-01) + casts to unsigned char to fix warnings + 0.91 (2010-07-17) + first public release + 0.90 first internal release +*/ + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/Plugins/Utility/CMakeLists.txt b/Plugins/Utility/CMakeLists.txt new file mode 100644 index 0000000..f3e7843 --- /dev/null +++ b/Plugins/Utility/CMakeLists.txt @@ -0,0 +1,7 @@ +add_library (Utility SHARED "Utility.cpp" Utility.def) +target_include_directories (Utility PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +set_target_properties(Utility PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/Plugins" + SUFFIX ".bep") +target_link_libraries(Utility PRIVATE UABE_Win32 AssetsTools libStringConverter) diff --git a/Plugins/Utility/Utility.cpp b/Plugins/Utility/Utility.cpp new file mode 100644 index 0000000..62fea37 --- /dev/null +++ b/Plugins/Utility/Utility.cpp @@ -0,0 +1,454 @@ +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN +#include +#include "../UABE_Win32/Win32PluginManager.h" +#include "../UABE_Generic/FileContextInfo.h" +#include "../UABE_Win32/Win32AppContext.h" +#include "../UABE_Win32/FileDialog.h" +#include "../AssetsTools/AssetsReplacer.h" +#include "../libStringConverter/convert.h" +#include + +enum class ResourceRefType +{ + StreamingInfo, + StreamedResource, + Other +}; +struct ResourceRef +{ + AssetTypeValueField* pOffsetField = nullptr; + AssetTypeValueField* pSizeField = nullptr; + AssetTypeValueField* pPathField = nullptr; + ResourceRefType type = ResourceRefType::Other; + bool Read(AssetTypeValueField* pParentField) + { + if (pParentField->GetType() == "StreamingInfo") + { + pOffsetField = pParentField->Get("offset"); + pSizeField = pParentField->Get("size"); + pPathField = pParentField->Get("path"); + type = ResourceRefType::StreamingInfo; + } + else if (pParentField->GetType() == "StreamedResource") + { + pOffsetField = pParentField->Get("m_Offset"); + pSizeField = pParentField->Get("m_Size"); + pPathField = pParentField->Get("m_Source"); + type = ResourceRefType::StreamedResource; + } + else + { + pOffsetField = nullptr; + pSizeField = nullptr; + pPathField = nullptr; + type = ResourceRefType::Other; + } + if (!pOffsetField || pOffsetField->IsDummy() || !pOffsetField->GetValue() + || !pSizeField || pSizeField->IsDummy() || !pSizeField->GetValue() + || !pPathField || pPathField->IsDummy() || !pPathField->GetValue() || pPathField->GetValue()->GetType() != ValueType_String) + return false; + return true; + } +}; + +static void reportError(Win32AppContext& appContext, const char *message) +{ + auto messageT = unique_MultiByteToTCHAR(message); + MessageBox(appContext.getMainWindow().getWindow(), + messageT.get(), TEXT("Asset Bundle Extractor"), MB_ICONERROR); +} + +class SelectedResourceExportProvider : public IAssetViewEntryOptionProvider +{ +public: + EAssetOptionType getType() + { + return EAssetOptionType::Export; + } + std::unique_ptr prepareForSelection( + class Win32AppContext& appContext, class AssetViewModifyDialog& assetViewDialog, + AssetViewModifyDialog::FieldInfo fieldInfo, + std::string& optionName) + { + ResourceRef resourceRef; + if (!resourceRef.Read(fieldInfo.pValueField)) + return nullptr; + optionName = "Export resource to file"; + return std::make_unique([&appContext, &assetViewDialog, fieldInfo, resourceRef]() + { + uint64_t offset = resourceRef.pOffsetField->GetValue()->AsUInt64(); + uint64_t size = resourceRef.pSizeField->GetValue()->AsUInt64(); + const char* path = resourceRef.pPathField->GetValue()->AsString(); + AssetIdentifier asset(fieldInfo.assetIDs.fileID, fieldInfo.assetIDs.pathID); + if (!asset.resolve(appContext)) + { + reportError(appContext, "Unable to find the asset."); + return; + } + std::shared_ptr pResourcesFile = nullptr; + try + { + pResourcesFile = FindResourcesFile(appContext, path, asset, {}); + } + catch (AssetUtilError e) + { + reportError(appContext, e.what()); + return; + } + assert(pResourcesFile != nullptr); //Should be guaranteed by FindResourcesFile. + std::shared_ptr pResourceReader = pResourcesFile->getResource(pResourcesFile, offset, size); + if (pResourceReader == nullptr) + { + reportError(appContext, "Unable to open the resource."); + return; + } + wchar_t* saveFilePath = nullptr; + if (SUCCEEDED(ShowFileSaveDialog(appContext.getMainWindow().getWindow(), + &saveFilePath, L"*.*|Any file:", + nullptr, nullptr, TEXT("Save resource data"), + UABE_FILEDIALOG_EXPIMPASSET_GUID))) + { + std::unique_ptr pWriter(Create_AssetsWriterToFile(saveFilePath, true, true, RWOpenFlags_Immediately)); + FreeCOMFilePathBuf(&saveFilePath); + if (pWriter == nullptr) + { + reportError(appContext, "Unable to open the output file."); + return; + } + std::unique_ptr pCopier(MakeAssetModifierFromReader(0, 0, 0, 0, pResourceReader.get(), size)); + if (pCopier->Write(0, pWriter.get()) != size) + { + reportError(appContext, "Unable to copy the data to the output file."); + return; + } + } + }); + } +}; + +class SelectedResourceImportProvider : public IAssetViewEntryOptionProvider +{ +public: + EAssetOptionType getType() + { + return EAssetOptionType::Import; + } + std::unique_ptr prepareForSelection( + class Win32AppContext& appContext, class AssetViewModifyDialog& assetViewDialog, + AssetViewModifyDialog::FieldInfo fieldInfo, + std::string& optionName) + { + ResourceRef resourceRef; + if (!resourceRef.Read(fieldInfo.pValueField)) + return nullptr; + optionName = "Import resource from file"; + return std::make_unique([&appContext, &assetViewDialog, fieldInfo, resourceRef]() + { + AssetIdentifier asset(fieldInfo.assetIDs.fileID, fieldInfo.assetIDs.pathID); + if (!asset.resolve(appContext)) + { + reportError(appContext, "Unable to find the asset."); + return; + } + std::string resourcesFileRefPath; + auto tryFindResourcesFile = [&appContext, &asset, &resourcesFileRefPath](const std::string& path) + { + std::shared_ptr pResourcesFile = nullptr; + try + { + pResourcesFile = FindResourcesFile(appContext, path, asset, {}); + if (pResourcesFile) resourcesFileRefPath = path; + } + catch (AssetUtilError e) {} + return pResourcesFile; + }; + std::shared_ptr pResourcesFile = tryFindResourcesFile(resourceRef.pPathField->GetValue()->AsString()); + if (pResourcesFile == nullptr) + { + std::string assetsFileRefName; + auto pParentBundle = std::dynamic_pointer_cast(appContext.getContextInfo(asset.pFile->getParentFileID())); + if (pParentBundle != 0) + { + std::string bundlePathName = pParentBundle->getBundlePathName(); + if (!bundlePathName.empty()) + { + assetsFileRefName = std::string("archive:/") + bundlePathName + "/" + asset.pFile->getFileName(); + } + else + assetsFileRefName = asset.pFile->getFileName(); + } + else + assetsFileRefName = asset.pFile->getFileName(); + if (assetsFileRefName.size() > 7 + && !strnicmp(&assetsFileRefName.data()[assetsFileRefName.size() - 7], ".assets", 7)) + assetsFileRefName.erase(assetsFileRefName.begin() + (assetsFileRefName.size() - 7), assetsFileRefName.end()); + //If the string iterator bug strikes again: assetsFileRefName = assetsFileRefName.substr(0, assetsFileRefName.size() - 7); + if (resourceRef.type == ResourceRefType::StreamedResource) + { + pResourcesFile = tryFindResourcesFile(assetsFileRefName + ".resources"); + if (!pResourcesFile) + pResourcesFile = tryFindResourcesFile(assetsFileRefName + ".resource"); + //".resources" + } + else //if (resourceRef.type == ResourceRefType::StreamingInfo) + pResourcesFile = tryFindResourcesFile(assetsFileRefName + ".resS"); + if (pResourcesFile == nullptr) + { + std::string message = std::format("Unable to find the associated resources file.\n" + "Make sure the {}{} file exists (or create an empty one) and open it within UABE.", + asset.pFile->getFileName(), + (resourceRef.type == ResourceRefType::StreamedResource) ? ".resource" : ".resS"); + reportError(appContext, message.c_str()); + return; + } + } + wchar_t* openFilePath = nullptr; + if (SUCCEEDED(ShowFileOpenDialog(appContext.getMainWindow().getWindow(), + &openFilePath, L"*.*|Any file:", + nullptr, nullptr, TEXT("Open file to import"), + UABE_FILEDIALOG_EXPIMPASSET_GUID))) + { + std::shared_ptr pReader(Create_AssetsReaderFromFile(openFilePath, true, RWOpenFlags_Immediately)); + FreeCOMFilePathBuf(&openFilePath); + if (pReader == nullptr) + { + reportError(appContext, "Unable to open the input file."); + return; + } + QWORD importSize = 0; + if (!pReader->Seek(AssetsSeek_End, 0) || !pReader->Tell(importSize) || !pReader->Seek(AssetsSeek_Begin, 0)) + { + reportError(appContext, "Unable to read the input file."); + return; + } + uint64_t newOffset = 0; + pResourcesFile->addResource(std::move(pReader), 0, importSize, newOffset); + + if (!assetViewDialog.setStringValue(resourceRef.pPathField, fieldInfo.assetIDs, resourcesFileRefPath)) + { + //Shouldn't happen. + reportError(appContext, "Unable to assign the new resource reference."); + return; + } + resourceRef.pOffsetField->GetValue()->Set(&newOffset, ValueType_UInt64); + resourceRef.pSizeField->GetValue()->Set(&importSize, ValueType_UInt64); + assetViewDialog.updateValueFieldText(fieldInfo); + } + }); + } +}; + +class SelectedByteArrayExportProvider : public IAssetViewEntryOptionProvider +{ +public: + EAssetOptionType getType() + { + return EAssetOptionType::Export; + } + std::unique_ptr prepareForSelection( + class Win32AppContext& appContext, class AssetViewModifyDialog& assetViewDialog, + AssetViewModifyDialog::FieldInfo fieldInfo, + std::string& optionName) + { + if (fieldInfo.pValueField->GetValue() == nullptr) + return nullptr; + if (fieldInfo.pValueField->GetValue()->GetType() == ValueType_Array) + { + if (fieldInfo.pValueField->GetTemplateField() == nullptr + || fieldInfo.pValueField->GetTemplateField()->children.size() != 2 + || fieldInfo.pValueField->GetTemplateField()->children[0].valueType != ValueType_Int32 + || (fieldInfo.pValueField->GetTemplateField()->children[1].valueType != ValueType_UInt8 + && fieldInfo.pValueField->GetTemplateField()->children[1].valueType != ValueType_Int8)) + return nullptr; + } + else if (fieldInfo.pValueField->GetValue()->GetType() != ValueType_ByteArray + && fieldInfo.pValueField->GetValue()->GetType() != ValueType_String) + return nullptr; + optionName = "Export data to file"; + return std::make_unique([&appContext, &assetViewDialog, fieldInfo]() + { + std::unique_ptr data_raii; + const uint8_t *data = nullptr; + size_t size = 0; + + switch (fieldInfo.pValueField->GetValue()->GetType()) + { + case ValueType_Array: + { + size = fieldInfo.pValueField->GetChildrenCount(); + data_raii.reset(new uint8_t[size]); + data = data_raii.get(); + AssetTypeValueField** ppChildren = fieldInfo.pValueField->GetChildrenList(); + for (size_t i = 0; i < size; ++i) + { + if (ppChildren[i]->GetValue() == nullptr) + { + reportError(appContext, "Unable to interpret the data."); + return; + } + data_raii[i] = (uint8_t)ppChildren[i]->GetValue()->AsUInt(); + } + } + break; + case ValueType_String: + { + data = reinterpret_cast(fieldInfo.pValueField->GetValue()->AsString()); + if (data == nullptr) + { + reportError(appContext, "Unable to interpret the data."); + return; + } + size = strlen(fieldInfo.pValueField->GetValue()->AsString()); + } + break; + case ValueType_ByteArray: + { + AssetTypeByteArray* pByteArray = fieldInfo.pValueField->GetValue()->AsByteArray(); + if (pByteArray == nullptr) + { + reportError(appContext, "Unable to interpret the data."); + return; + } + data = pByteArray->data; + size = pByteArray->size; + } + break; + default: + { + //Should be excluded by prepareForSelection. + reportError(appContext, "Unable to interpret the data."); + return; + } + } + + wchar_t* saveFilePath = nullptr; + if (FAILED(ShowFileSaveDialog(appContext.getMainWindow().getWindow(), + &saveFilePath, L"*.*|Any file:", + nullptr, nullptr, TEXT("Save resource data"), + UABE_FILEDIALOG_EXPIMPASSET_GUID))) + return; + std::unique_ptr pWriter(Create_AssetsWriterToFile(saveFilePath, true, true, RWOpenFlags_Immediately)); + FreeCOMFilePathBuf(&saveFilePath); + if (pWriter == nullptr) + { + reportError(appContext, "Unable to open the output file."); + return; + } + if (pWriter->Write(size, data) != size) + { + reportError(appContext, "Unable to write the data to the output file."); + return; + } + }); + } +}; + +class SelectedByteArrayImportProvider : public IAssetViewEntryOptionProvider +{ +public: + EAssetOptionType getType() + { + return EAssetOptionType::Import; + } + std::unique_ptr prepareForSelection( + class Win32AppContext& appContext, class AssetViewModifyDialog& assetViewDialog, + AssetViewModifyDialog::FieldInfo fieldInfo, + std::string& optionName) + { + if (fieldInfo.pValueField->GetValue() == nullptr) + return nullptr; + if (fieldInfo.pValueField->GetValue()->GetType() == ValueType_Array) + { + if (fieldInfo.pValueField->GetTemplateField() == nullptr + || fieldInfo.pValueField->GetTemplateField()->children.size() != 2 + || fieldInfo.pValueField->GetTemplateField()->children[0].valueType != ValueType_Int32 + || (fieldInfo.pValueField->GetTemplateField()->children[1].valueType != ValueType_UInt8 + && fieldInfo.pValueField->GetTemplateField()->children[1].valueType != ValueType_Int8) + || fieldInfo.pValueField->GetTemplateField()->children[1].align) + return nullptr; + } + else if (fieldInfo.pValueField->GetValue()->GetType() != ValueType_ByteArray + && fieldInfo.pValueField->GetValue()->GetType() != ValueType_String) + return nullptr; + optionName = "Import data from file"; + return std::make_unique([&appContext, &assetViewDialog, fieldInfo]() + { + wchar_t* openFilePath = nullptr; + if (FAILED(ShowFileOpenDialog(appContext.getMainWindow().getWindow(), + &openFilePath, L"*.*|Any file:", + nullptr, nullptr, TEXT("Open file to import"), + UABE_FILEDIALOG_EXPIMPASSET_GUID))) + return; + std::shared_ptr pReader(Create_AssetsReaderFromFile(openFilePath, true, RWOpenFlags_Immediately)); + FreeCOMFilePathBuf(&openFilePath); + if (pReader == nullptr) + { + reportError(appContext, "Unable to open the input file."); + return; + } + QWORD importSize = 0; + if (!pReader->Seek(AssetsSeek_End, 0) || !pReader->Tell(importSize) || !pReader->Seek(AssetsSeek_Begin, 0) + || importSize > std::numeric_limits::max()) + { + reportError(appContext, "Unable to read the input file."); + return; + } + std::unique_ptr importData(new uint8_t[(size_t)importSize]); + if (pReader->Read(importSize, importData.get()) != importSize) + { + reportError(appContext, "Unable to read the input file."); + return; + } + + if (!assetViewDialog.setByteArrayValue(fieldInfo, std::move(importData), (size_t)importSize)) + { + //Shouldn't happen. + reportError(appContext, "Unable to assign the new data."); + return; + } + assetViewDialog.updateValueFieldText(fieldInfo); + }); + } +}; + +class UtilityPluginDesc : public IPluginDesc +{ + std::vector> pProviders; +public: + UtilityPluginDesc() + { + pProviders = { + std::make_shared(), std::make_shared(), + std::make_shared(), std::make_shared() + }; + } + std::string getName() + { + return "Utility"; + } + std::string getAuthor() + { + return ""; + } + std::string getDescriptionText() + { + return "Collection of small utility features."; + } + //The IPluginDesc object should keep a reference to the returned options, as the caller may keep only std::weak_ptrs. + //Note: May be called early, e.g. before program UI initialization. + std::vector> getPluginOptions(class AppContext& appContext) + { + return pProviders; + } +}; + +IPluginDesc* GetUABEPluginDesc1(size_t sizeof_AppContext, size_t sizeof_BundleFileContextInfo) +{ + if (sizeof_AppContext != sizeof(AppContext) || sizeof_BundleFileContextInfo != sizeof(BundleFileContextInfo)) + { + assert(false); + return nullptr; + } + return new UtilityPluginDesc(); +} diff --git a/Plugins/Utility/Utility.def b/Plugins/Utility/Utility.def new file mode 100644 index 0000000..092c897 --- /dev/null +++ b/Plugins/Utility/Utility.def @@ -0,0 +1,3 @@ +LIBRARY Utility +EXPORTS + GetUABEPluginDesc1 diff --git a/README.md b/README.md index 3b54d6f..6d3b5eb 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,32 @@ -# Unity Assets Bundle Extractor +# Unity Asset Bundle Extractor Unity .assets and AssetBundle editor -UABE is an editor for Unity 3.4+/4/5/2017/2018 .assets and AssetBundle files. It can create standalone mod installers from changes to .assets and/or bundles. -Type information extracted from Unity is used in order to generate text representations of various asset types. Custom MonoBehaviour types also are supported. +UABE is an editor for Unity 3.4+/4/5/2017-2021.3 .assets and AssetBundle files. It can create standalone mod installers from changes to .assets and/or bundles. + There are multiple plugins to convert Unity assets from/to common file formats : -- The Texture plugin can export and import .png and .tga files and decode&encode most texture formats used by Unity. +- The Texture plugin can export and import .png and .tga files (Texture2D only) and decode&encode most texture formats used by Unity. - The TextAsset plugin can export and import .txt files. -- The AudioClip plugin can export uncompressed .wav files from U5's AudioClip assets using FMOD, .m4a files from WebGL builds and Unity 4 sound files. +- The AudioClip plugin can export uncompressed .wav files from Unity 5+ AudioClip assets using FMOD, .m4a files from WebGL builds and Unity 4 sound files. - The Mesh plugin can export .obj and .dae (Collada) files, also supporting rigged SkinnedMeshRenderers. -- The MovieTexture plugin can export and import .ogv (Ogg Theora) files. -- The TerrainData plugin can export and import .raw files readable by Unity. +- The Utility plugin can export and import byte arrays and resources (StreamingInfo, StreamedResource) within the View Data editor. + +## Building +UABE can be built within Visual Studio (Community) 2022 using the Open Folder option (CMake). + +The non-proprietary dependencies are downloaded and patched during CMake configuration. +The proprietary dependencies are optional and can be disabled: +- FMOD: Remove the AudioClip plugin by removing the corresponding line in Plugins/CMakeLists.txt. +- PVRTexTool: Remove TexToolWrap by removing the corresponding line in CMakeLists.txt. This removes support for some texture formats used (mostly) for mobile games. + +To embed the proprietary SDKs, set the PVRTexTool_ROOT and FMOD_ROOT CMake variables accordingly. +The CMakeSettings.Example.json shows how a CMakeSettings.json for Visual Studio could look like. +If the build process cannot find the SDKs, check if the cmake files in CMakeModules look in the correct subfolders. Also note that UABE is still using an old version of FMOD (with plans to substitute it entirely), so it may not work with recent versions. + +### Portability Notes +- UABE uses plain Win32 for the GUI. The GUI portions are isolated to the UABE_Win32 module, some plugins and the mCtrl dependency. winelib could be an option for a Linux GUI port, however. +- Compilers other than MSVC++ are not tested with UABE and likely require some code changes. +- Uses C++20-feature std::format, which is not supported by gcc yet (as of writing this). [fmtlib](https://github.com/fmtlib/fmt) may be a quick drop-in replacement. -You can find the main project page on https://community.7daystodie.com/topic/1871-unity-assets-bundle-extractor/. +## License +UABE is licensed under the Eclipse Public License, v. 2.0 (EPL 2.0) license (see [Licenses/license.txt](Licenses/license.txt)). +See [Readme.License.txt](Readme.License.txt) for more details, including a listing of dependencies and copyright notices. diff --git a/Readme.License.txt b/Readme.License.txt new file mode 100644 index 0000000..a764614 --- /dev/null +++ b/Readme.License.txt @@ -0,0 +1,27 @@ +The license files referred to in this document are located in the Licenses directory. +By using this software, you agree to be bound by the terms and conditions of the Eclipse Public License, v. 2.0 (EPL 2.0), see license.txt. +The components listed below are not distributed under the EPL 2.0 license. The FMOD Sound System and PowerVR Tools components are only distributed in binary releases of AssetBundleExtractor. + +AssetBundleExtractor uses the open-source libraries +LodePNG (see LodePNG_license.txt, https://github.com/lvandeve/lodepng), +libsquish (see libsquish_license.txt), +stb_image (public domain, https://github.com/nothings/stb), +crunch (public domain, https://github.com/richgel999/crunch), +crunch Unity fork (see crunch-unity_license.txt, https://github.com/Unity-Technologies/crunch/tree/unity), +LZMA SDK (public domain, http://7-zip.org/sdk.html), +lz4 (see lz4_license.txt, https://github.com/Cyan4973/lz4), +astc-encoder (see astcenc_license.txt, https://github.com/ARM-software/astc-encoder), +half (see half_license.txt, http://half.sourceforge.net/), +Fast ISPC Texture Compressor (see ispc_texcomp_license.txt, https://github.com/GameTechDev/ISPCTextureCompressor), +jsmn (see jsmn_license.txt, https://github.com/zserge/jsmn), +texgenpack (see texgenpack_license.txt, https://github.com/hglm/texgenpack) with its dependencies libfgen (see libfgen_license.txt and libfgen_lgpl.txt, https://github.com/hglm/libfgen) and pthreads-win32 (see pthreads_license.txt, https://sourceforge.net/projects/pthreads4w/), +mCtrl (see mctrl_license.txt, https://www.mctrl.org/), +Cecil (see cecil_license.txt, https://github.com/jbevain/cecil), +assimp (see assimp_license.txt, https://github.com/assimp/assimp) with its dependency poly2tri (see lower section of assimp_license.txt, http://code.google.com/p/poly2tri/), +wavfile (CC BY 4.0, see https://creativecommons.org/licenses/by/4.0/, by Douglas Thain, https://www3.nd.edu/~dthain/courses/cse20211/fall2013/wavfile/ - slightly modified for UABE), +and portions of vgmstream (see vgmstream_license.txt, https://github.com/vgmstream/vgmstream). + +Uses FMOD Sound System, copyright © Firelight Technologies Pty, Ltd., 1994-2015. +This product includes components of the PowerVR Tools Software from Imagination Technologies Limited. + +Unity is a registered trademark of Unity Technologies. The creator of this tool is in no way affiliated with Unity Technologies. \ No newline at end of file diff --git a/TexToolWrap/CMakeLists.txt b/TexToolWrap/CMakeLists.txt new file mode 100644 index 0000000..2891e4e --- /dev/null +++ b/TexToolWrap/CMakeLists.txt @@ -0,0 +1,24 @@ +find_package(PVRTexTool REQUIRED) + +add_library (TexToolWrap SHARED "TexToolWrap.cpp" exports.def) +target_include_directories (TexToolWrap PRIVATE ${PVRTexTool_INCLUDE_DIR}) + +set_target_properties(TexToolWrap PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") +target_link_libraries(TexToolWrap PUBLIC ${PVRTexTool_LIBRARIES}) + +set(TexToolWrap_MODULE_DEPENDENCY_PATHS ${PVRTexTool_MODULES}) + +file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/bin") +foreach(depfile ${TexToolWrap_MODULE_DEPENDENCY_PATHS}) + configure_file("${depfile}" "${CMAKE_BINARY_DIR}/bin" COPYONLY) +endforeach() + +install(TARGETS TexToolWrap + RUNTIME + DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +install(FILES ${TexToolWrap_MODULE_DEPENDENCY_PATHS} + DESTINATION ${CMAKE_INSTALL_BINDIR}/ +) diff --git a/TexToolWrap/ETexFmts.h b/TexToolWrap/ETexFmts.h new file mode 100644 index 0000000..0497bda --- /dev/null +++ b/TexToolWrap/ETexFmts.h @@ -0,0 +1,221 @@ +#pragma once +enum TextureFormat { + TexFmt_Alpha8 = 1, + TexFmt_ARGB4444, + TexFmt_RGB24, + TexFmt_RGBA32, + TexFmt_ARGB32, + TexFmt_UNUSED06, + TexFmt_RGB565, + TexFmt_UNUSED08, + TexFmt_R16, + TexFmt_DXT1, + TexFmt_UNUSED11, + TexFmt_DXT5, + TexFmt_RGBA4444, + TexFmt_BGRA32New, //Unity 5 + TexFmt_RHalf, + TexFmt_RGHalf, + TexFmt_RGBAHalf, + TexFmt_RFloat, + TexFmt_RGFloat, + TexFmt_RGBAFloat, + TexFmt_YUV2, + TexFmt_UNUSED22, + TexFmt_UNUSED23, + TexFmt_UNUSED24, + TexFmt_UNUSED25, + TexFmt_UNUSED26, + TexFmt_UNUSED27, + TexFmt_DXT1Crunched, + TexFmt_DXT5Crunched, + TexFmt_PVRTC_RGB2, + TexFmt_PVRTC_RGBA2, + TexFmt_PVRTC_RGB4, + TexFmt_PVRTC_RGBA4, + TexFmt_ETC_RGB4, + TexFmt_ATC_RGB4, + TexFmt_ATC_RGBA8, + TexFmt_BGRA32Old, //Unity 4 + TexFMT_UNUSED38, //TexFmt_ATF_RGB_DXT1, + TexFMT_UNUSED39, //TexFmt_ATF_RGBA_JPG, + TexFMT_UNUSED40, //TexFmt_ATF_RGB_JPG, + TexFmt_EAC_R, + TexFmt_EAC_R_SIGNED, + TexFmt_EAC_RG, + TexFmt_EAC_RG_SIGNED, + TexFmt_ETC2_RGB4, + TexFmt_ETC2_RGBA1, + TexFmt_ETC2_RGBA8, + TexFmt_ASTC_RGB_4x4, + TexFmt_ASTC_RGB_5x5, + TexFmt_ASTC_RGB_6x6, + TexFmt_ASTC_RGB_8x8, + TexFmt_ASTC_RGB_10x10, + TexFmt_ASTC_RGB_12x12, + TexFmt_ASTC_RGBA_4x4, + TexFmt_ASTC_RGBA_5x5, + TexFmt_ASTC_RGBA_6x6, + TexFmt_ASTC_RGBA_8x8, + TexFmt_ASTC_RGBA_10x10, + TexFmt_ASTC_RGBA_12x12, + TexFmt_ETC_RGB4_3DS, + TexFmt_ETC_RGBA8_3DS, + TexFmt_MAX +}; + +PVRTuint64 PixelTypeByTextureFormat[] = +{ + PVRTGENPIXELID4('\0', '\0', '\0', '\0', 0, 0, 0, 0), //none + PVRTGENPIXELID4('a', '\0', '\0', '\0', 8, 0, 0, 0), //alpha8 + PVRTGENPIXELID4('a', 'r', 'g', 'b', 4, 4, 4, 4), //argb4444 + PVRTGENPIXELID4('r', 'g', 'b', '\0', 8, 8, 8, 0), //rgb24 + PVRTGENPIXELID4('r', 'g', 'b', 'a', 8, 8, 8, 8), //rgba32 + PVRTGENPIXELID4('a', 'r', 'g', 'b', 8, 8, 8, 8), //argb32 + PVRTGENPIXELID4('\0', '\0', '\0', '\0', 0, 0, 0, 0), //none + PVRTGENPIXELID4('r', 'g', 'b', '\0', 5, 6, 5, 0), //rgb565, possibly wrong due to endianess + PVRTGENPIXELID4('\0', '\0', '\0', '\0', 0, 0, 0, 0), //none + PVRTGENPIXELID4('r', '\0', '\0', '\0', 16, 0, 0, 0), //r16 + PVRTLPF_DXT1, //dxt1 + PVRTGENPIXELID4('\0', '\0', '\0', '\0', 0, 0, 0, 0), //none + PVRTLPF_DXT5, //dxt5 + PVRTGENPIXELID4('b', 'a', 'r', 'g', 4, 4, 4, 4), //rgba4444, actually barg4444 + PVRTGENPIXELID4('b', 'g', 'r', 'a', 8, 8, 8, 8), //bgra32 (Unity 5) + PVRTGENPIXELID4('r', '\0', '\0', '\0', 16, 0, 0, 0), //RHalf + PVRTGENPIXELID4('r', 'g', '\0', '\0', 16, 16, 0, 0), //RGHalf + PVRTGENPIXELID4('r', 'g', 'b', 'a', 16, 16, 16, 16), //RGBAHalf + PVRTGENPIXELID4('r', '\0', '\0', '\0', 32, 0, 0, 0), //RFloat + PVRTGENPIXELID4('r', 'g', '\0', '\0', 32, 32, 0, 0), //RGFloat + PVRTGENPIXELID4('r', 'g', 'b', 'a', 32, 32, 32, 32), //RGBAFloat + PVRTLPF_YUY2_422, //YUV2 + PVRTLPF_SharedExponentR9G9B9E5, //RGB9e5 + PVRTGENPIXELID4('\0', '\0', '\0', '\0', 0, 0, 0, 0), //none + PVRTGENPIXELID4('\0', '\0', '\0', '\0', 0, 0, 0, 0), //BC6H + PVRTLPF_BC7, //BC7 + PVRTLPF_BC4, //BC4 + PVRTLPF_BC5, //BC5 + PVRTGENPIXELID4('\0', '\0', '\0', '\0', 0, 0, 0, 0), //DXT1Crunched + PVRTGENPIXELID4('\0', '\0', '\0', '\0', 0, 0, 0, 0), //DXT5Crunched + PVRTLPF_PVRTCI_2bpp_RGB, //TexFmt_PVRTC_RGB2 + PVRTLPF_PVRTCI_2bpp_RGBA, //TexFmt_PVRTC_RGBA2 + PVRTLPF_PVRTCI_4bpp_RGB, //TexFmt_PVRTC_RGB4 + PVRTLPF_PVRTCI_4bpp_RGBA, //TexFmt_PVRTC_RGBA4 + PVRTLPF_ETC1, //TexFmt_ETC_RGB4 + PVRTGENPIXELID4('\0', '\0', '\0', '\0', 0, 0, 0, 0), //ATC_RGB4 (obsolete) + PVRTGENPIXELID4('\0', '\0', '\0', '\0', 0, 0, 0, 0), //ATC_RGBA8 (obsolete) + PVRTGENPIXELID4('b', 'g', 'r', 'a', 8, 8, 8, 8), //bgra32 (Unity 4) + PVRTGENPIXELID4('\0', '\0', '\0', '\0', 0, 0, 0, 0), //none + PVRTGENPIXELID4('\0', '\0', '\0', '\0', 0, 0, 0, 0), //none + PVRTGENPIXELID4('\0', '\0', '\0', '\0', 0, 0, 0, 0), //none + PVRTLPF_EAC_R11, //eac_r + PVRTLPF_EAC_R11, //eac_r_signed + PVRTLPF_EAC_RG11, //eac_rg + PVRTLPF_EAC_RG11, //eac_rg_signed + PVRTLPF_ETC2_RGB, //etc2_rgb4 + PVRTLPF_ETC2_RGB_A1, //etc2_rgba1 + PVRTLPF_ETC2_RGBA, //etc2_rgba8 + PVRTLPF_ASTC_4x4, //pvrtexture::PixelType('\0', '\0', '\0', '\0', 0, 0, 0, 0).PixelTypeID, //astc_rgb_4x4 (only rgba supported) + PVRTLPF_ASTC_5x5, //pvrtexture::PixelType('\0', '\0', '\0', '\0', 0, 0, 0, 0).PixelTypeID, //astc_rgb_5x5 (only rgba supported) + PVRTLPF_ASTC_6x6, //pvrtexture::PixelType('\0', '\0', '\0', '\0', 0, 0, 0, 0).PixelTypeID, //astc_rgb_6x6 (only rgba supported) + PVRTLPF_ASTC_8x8, //pvrtexture::PixelType('\0', '\0', '\0', '\0', 0, 0, 0, 0).PixelTypeID, //astc_rgb_8x8 (only rgba supported) + PVRTLPF_ASTC_10x10, //pvrtexture::PixelType('\0', '\0', '\0', '\0', 0, 0, 0, 0).PixelTypeID, //astc_rgb_10x10 (only rgba supported) + PVRTLPF_ASTC_12x12, //pvrtexture::PixelType('\0', '\0', '\0', '\0', 0, 0, 0, 0).PixelTypeID, //astc_rgb_12x12 (only rgba supported) + PVRTLPF_ASTC_4x4, //astc_rgba_4x4 + PVRTLPF_ASTC_5x5, //astc_rgba_5x5 + PVRTLPF_ASTC_6x6, //astc_rgba_6x6 + PVRTLPF_ASTC_8x8, //astc_rgba_8x8 + PVRTLPF_ASTC_10x10, //astc_rgba_10x10 + PVRTLPF_ASTC_12x12, //astc_rgba_12x12 + PVRTLPF_ETC2_RGB, //etc_rgb4_3ds + PVRTLPF_ETC2_RGBA, //etc_rgba8_3ds + PVRTGENPIXELID4('r', 'g', '\0', '\0', 8, 8, 0, 0), //RG16 + PVRTGENPIXELID4('r', '\0', '\0', '\0', 8, 0, 0, 0), //R8 + PVRTGENPIXELID4('\0', '\0', '\0', '\0', 8, 0, 0, 0), //ETC_RGB4Crunched + PVRTGENPIXELID4('\0', '\0', '\0', '\0', 8, 0, 0, 0), //ETC2_RGBA8Crunched + PVRTGENPIXELID4('\0', '\0', '\0', '\0', 0, 0, 0, 0), //ASTC_HDR_4x4 + PVRTGENPIXELID4('\0', '\0', '\0', '\0', 0, 0, 0, 0), //ASTC_HDR_5x5 + PVRTGENPIXELID4('\0', '\0', '\0', '\0', 0, 0, 0, 0), //ASTC_HDR_6x6 + PVRTGENPIXELID4('\0', '\0', '\0', '\0', 0, 0, 0, 0), //ASTC_HDR_8x8 + PVRTGENPIXELID4('\0', '\0', '\0', '\0', 0, 0, 0, 0), //ASTC_HDR_10x10 + PVRTGENPIXELID4('\0', '\0', '\0', '\0', 0, 0, 0, 0), //ASTC_HDR_12x12 + PVRTGENPIXELID4('r', 'g', '\0', '\0', 16, 16, 0, 0), //RG32 + PVRTGENPIXELID4('r', 'g', 'b', '\0', 16, 16, 16, 0), //RGB48 + PVRTGENPIXELID4('r', 'g', 'b', 'a', 16, 16, 16, 16), //RGBA64 +}; +PVRTexLibVariableType VariableTypeByTextureFormat[] = { + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_SignedFloat, + PVRTLVT_SignedFloat, + PVRTLVT_SignedFloat, + PVRTLVT_SignedFloat, + PVRTLVT_SignedFloat, + PVRTLVT_SignedFloat, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, //ETC_RGB4 + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm,//BGRA32Old + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedIntegerNorm, //EAC_R + PVRTLVT_SignedIntegerNorm, //EAC_R_SIGNED + PVRTLVT_UnsignedIntegerNorm, //EAC_RG + PVRTLVT_SignedIntegerNorm, //EAC_RG_SIGNED + PVRTLVT_UnsignedByteNorm, //ETC2_RGB4 + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, //ASTC_RGB_4x4 + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, //ASTC_RGBA_4x4 + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, //ETC2_RGB4_3DS + PVRTLVT_UnsignedByteNorm, + PVRTLVT_UnsignedByteNorm, //RG16 + PVRTLVT_UnsignedByteNorm, //R8 + PVRTLVT_UnsignedByteNorm, //ETC_RGB4Crunched + PVRTLVT_UnsignedByteNorm, //ETC2_RGBA8Crunched + PVRTLVT_UnsignedByteNorm, //ASTC_HDR_4x4 + PVRTLVT_UnsignedByteNorm, //ASTC_HDR_5x5 + PVRTLVT_UnsignedByteNorm, //ASTC_HDR_6x6 + PVRTLVT_UnsignedByteNorm, //ASTC_HDR_8x8 + PVRTLVT_UnsignedByteNorm, //ASTC_HDR_10x10 + PVRTLVT_UnsignedByteNorm, //ASTC_HDR_12x12 + PVRTLVT_UnsignedByteNorm, //RG32 + PVRTLVT_UnsignedByteNorm, //RGB48 + PVRTLVT_UnsignedByteNorm, //RGBA64 +}; \ No newline at end of file diff --git a/TexToolWrap/TexToolWrap.cpp b/TexToolWrap/TexToolWrap.cpp new file mode 100644 index 0000000..99f3c87 --- /dev/null +++ b/TexToolWrap/TexToolWrap.cpp @@ -0,0 +1,88 @@ +#include +#include +#define PVRTEXLIB_IMPORT +#include +#include "ETexFmts.h" + +const PVRTuint64 pvr_ARGB8 = PVRTGENPIXELID4('r', 'g', 'b', 'a', 8, 8, 8, 8); +const PVRTuint64 pvr_RGB8 = PVRTGENPIXELID4('r', 'g', 'b', 0, 8, 8, 8, 0); +size_t Compress(uint32_t texFmt, unsigned int height, unsigned int width, unsigned int mipCount, void *pInBuf, size_t inBufLen, void *pOutBuf, size_t outBufLen, int compressQuality); +unsigned int GetMaxCompressedSize(int width, int height, uint32_t texFmt); +size_t Decompress(uint32_t texFmt, unsigned int height, unsigned int width, unsigned int mipCount, void *pInBuf, size_t inBufLen, void *pOutBuf, size_t outBufLen); + +size_t Compress(uint32_t texFmt, unsigned int height, unsigned int width, unsigned int mipCount, void *pInBuf, size_t inBufLen, void *pOutBuf, size_t outBufLen, int compressQuality) +{ + if ((texFmt >= TexFmt_MAX) || (inBufLen < (width * height * 4)) || (outBufLen < GetMaxCompressedSize(width, height, texFmt))) + return 0; + pvrtexlib::PVRTextureHeader header(pvr_ARGB8, width, height, 1, mipCount, 1, 1, PVRTLCS_Linear); + + pvrtexlib::PVRTexture tex(header, pInBuf); + if (!tex.Transcode(PixelTypeByTextureFormat[texFmt], VariableTypeByTextureFormat[texFmt], PVRTLCS_Linear, (PVRTexLibCompressorQuality)compressQuality)) + return 0; + memcpy(pOutBuf, tex.GetTextureDataPointer(), (outBufLen < tex.GetTextureDataSize()) ? outBufLen : tex.GetTextureDataSize()); + return tex.GetTextureDataSize(); +} +unsigned int GetMaxCompressedSize(int width, int height, uint32_t texFmt) +{ + if (texFmt >= TexFmt_MAX) + return 0; + switch (texFmt) + { + case TexFmt_EAC_R: + case TexFmt_EAC_RG: + case TexFmt_EAC_R_SIGNED: + case TexFmt_EAC_RG_SIGNED: + case TexFmt_ETC_RGB4: + case TexFmt_ETC_RGB4_3DS: + case TexFmt_ETC_RGBA8_3DS: + case TexFmt_ETC2_RGB4: + case TexFmt_ETC2_RGBA1: + case TexFmt_ETC2_RGBA8: + //the compressions are limited to block sizes + width = width + ((width & 3) ? 1 : 0); + height = height + ((height & 3) ? 1 : 0); + break; + case TexFmt_ASTC_RGB_4x4: + case TexFmt_ASTC_RGBA_4x4: + return (unsigned int)(ceil((double)width / 4.0F) * ceil((double)height / 4.0F)) * 16; + case TexFmt_ASTC_RGB_5x5: + case TexFmt_ASTC_RGBA_5x5: + return (unsigned int)(ceil((double)width / 5.0F) * ceil((double)height / 5.0F)) * 16; + case TexFmt_ASTC_RGB_6x6: + case TexFmt_ASTC_RGBA_6x6: + return (unsigned int)(ceil((double)width / 6.0F) * ceil((double)height / 6.0F)) * 16; + case TexFmt_ASTC_RGB_8x8: + case TexFmt_ASTC_RGBA_8x8: + return (unsigned int)(ceil((double)width / 8.0F) * ceil((double)height / 8.0F)) * 16; + case TexFmt_ASTC_RGB_10x10: + case TexFmt_ASTC_RGBA_10x10: + return (unsigned int)(ceil((double)width / 10.0F) * ceil((double)height / 10.0F)) * 16; + case TexFmt_ASTC_RGB_12x12: + case TexFmt_ASTC_RGBA_12x12: + return (unsigned int)(ceil((double)width / 12.0F) * ceil((double)height / 12.0F)) * 16; + } + pvrtexlib::PVRTextureHeader header(PixelTypeByTextureFormat[texFmt], width, height, 1, 1, 1, 1, PVRTLCS_Linear); + return header.GetTextureDataSize(); +} +size_t Decompress(uint32_t texFmt, unsigned int height, unsigned int width, unsigned int mipCount, void *pInBuf, size_t inBufLen, void *pOutBuf, size_t outBufLen) +{ + if (mipCount == (unsigned int)-1) + mipCount = 1; + if (texFmt >= TexFmt_MAX) + return 0; + pvrtexlib::PVRTextureHeader header(PixelTypeByTextureFormat[texFmt], width, height, 1, mipCount, 1, 1, PVRTLCS_Linear); + unsigned int bpp = header.GetTextureBitsPerPixel(); + if (((bpp * width * height) > (inBufLen * 8)) || (4 * width * height) > outBufLen) + { + return 0; + } + else + { + pvrtexlib::PVRTexture tex(header, pInBuf); + if (!tex.Transcode(pvr_ARGB8, PVRTLVT_UnsignedByteNorm, PVRTLCS_Linear, PVRTLCQ_PVRTCBest)) + return 0; + + memcpy(pOutBuf, tex.GetTextureDataPointer(), width * height * 4); + return width * height * 4; + } +} diff --git a/TexToolWrap/exports.def b/TexToolWrap/exports.def new file mode 100644 index 0000000..5289609 --- /dev/null +++ b/TexToolWrap/exports.def @@ -0,0 +1,5 @@ +LIBRARY TexToolWrap +EXPORTS + Compress + GetMaxCompressedSize + Decompress diff --git a/Tools/Mono.Cecil.Rocks.dll b/Tools/Mono.Cecil.Rocks.dll new file mode 100644 index 0000000000000000000000000000000000000000..c54bd0ef7d9b8e209f1d42f2e50e742767e32aa3 GIT binary patch literal 23040 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 q7k+x|!3blRr4SahnWos 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

x7*PqHFGgij%u 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 SU8(aNHUlA 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 K*U0~|2L1ur!$0f* literal 0 HcmV?d00001 diff --git a/TypeTreeGenerator/CMakeLists.txt b/TypeTreeGenerator/CMakeLists.txt new file mode 100644 index 0000000..5a01564 --- /dev/null +++ b/TypeTreeGenerator/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required (VERSION 3.18) +project(TypeTreeGenerator VERSION 1.0.0 LANGUAGES CSharp) + +add_executable (TypeTreeGenerator Program.cs ClassDatabaseFile2.cs EngineVersion.cs HelperClass.cs Logger.cs TypeField.cs) +#Use a 'dummy' generator expression so VS does not add another subdirectory (e.g. Debug) below the given path. +set_target_properties (TypeTreeGenerator PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/../Tools$") + +set_target_properties (TypeTreeGenerator PROPERTIES VS_DOTNET_TARGET_FRAMEWORK_VERSION "v4.6.1") +set_target_properties (TypeTreeGenerator PROPERTIES WIN32_EXECUTABLE TRUE) +set (CMAKE_CSharp_FLAGS "/platform:x86") + +set_target_properties (TypeTreeGenerator PROPERTIES VS_DOTNET_REFERENCE_MonoCecil "${CMAKE_CURRENT_LIST_DIR}/../Tools/Mono.Cecil.dll") +set_target_properties (TypeTreeGenerator PROPERTIES VS_DOTNET_REFERENCE_MonoCecilRocks "${CMAKE_CURRENT_LIST_DIR}/../Tools/Mono.Cecil.Rocks.dll") +set_target_properties (TypeTreeGenerator PROPERTIES VS_DOTNET_REFERENCES "System") diff --git a/TypeTreeGenerator/CMakeSettings.json b/TypeTreeGenerator/CMakeSettings.json new file mode 100644 index 0000000..172dc9f --- /dev/null +++ b/TypeTreeGenerator/CMakeSettings.json @@ -0,0 +1,28 @@ +{ + "configurations": [ + { + "name": "x86-Release", + "generator": "Visual Studio 17 2022", + "configurationType": "Release", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "msvc_x86" ], + "variables": [] + }, + { + "name": "x86-Debug", + "generator": "Visual Studio 17 2022", + "configurationType": "Debug", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "msvc_x86" ], + "variables": [] + } + ] +} \ No newline at end of file diff --git a/TypeTreeGenerator/ClassDatabaseFile2.cs b/TypeTreeGenerator/ClassDatabaseFile2.cs new file mode 100644 index 0000000..72f2b10 --- /dev/null +++ b/TypeTreeGenerator/ClassDatabaseFile2.cs @@ -0,0 +1,274 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; + +namespace TypeTreeGenerator +{ + class ClassDatabaseFile2 + { + List behaviourTypes = new List(); + static readonly byte[] binaryFileHeader = { (byte)'c', (byte)'l', (byte)'d', (byte)'b' }; + static readonly UInt32 fileHeaderUInt = 0x62646C63; + + private bool CheckEndlessRecursion(TypeField type, List parents) + { + parents.Add(type); + foreach (TypeField child in type.children) + { + if (parents.Contains(child)) + return true; + if (CheckEndlessRecursion(child, new List(parents))) + return true; + } + return false; + } + + public void Add(TypeField type) + { + if (CheckEndlessRecursion(type, new List())) + { + HelperClass.logger.Warning("Type \"" + type.type + "\" is not serializable (endless recursion)!"); + } + else if (!behaviourTypes.Contains(type)) + { + behaviourTypes.Add(type); + } + } + + + private static byte[] MakeCChar(string str) + { + byte[] ret = new byte[str.Length + 1]; + for (int i = 0; i < str.Length; i++) + ret[i] = (byte)str[i]; + ret[ret.Length - 1] = 0; + return ret; + } + private static int AddToStringTable(MemoryStream table, string str) + { + byte[] strCChar = MakeCChar(str); + int ret = (int)table.Position; + table.Seek(0, SeekOrigin.Begin); + byte[] stringTableBuf = table.GetBuffer(); long stringTableLen = table.Length; + if (stringTableLen > strCChar.Length) + { + bool found = false; + for (int i = 0; i <= stringTableLen - strCChar.Length; i++) + { + found = true; + for (int j = 0; j < strCChar.Length; j++) + { + if (stringTableBuf[i + j] != strCChar[j]) + { + found = false; + break; + } + } + if (found) + { + table.Seek(ret, SeekOrigin.Begin); + return i; + } + } + } + /*byte[] altBuffer = new byte[strCChar.Length]; + int tablePos = 0; int remainingBytes = ret; + while (remainingBytes > str.Length) + { + table.Seek(tablePos, SeekOrigin.Begin); + table.Read(altBuffer, 0, altBuffer.Length); + bool buffersEqual = true; + for (int i = 0; i < strCChar.Length; i++) + { + if (altBuffer[i] != strCChar[i]) + { + buffersEqual = false; + break; + } + } + if (buffersEqual) + { + table.Seek(ret, SeekOrigin.Begin); + return tablePos; + } + remainingBytes--; tablePos++; + }*/ + table.Seek(ret, SeekOrigin.Begin); + table.Write(strCChar, 0, strCChar.Length); + return ret; + } + + + private int WriteTypeField(MemoryStream stringTable, BinaryWriter writer, TypeField tfield, int depth, string overrideName = null) + { + int ret = 1; + int fieldTypeStringOffset = AddToStringTable(stringTable, tfield.type); + writer.Write(fieldTypeStringOffset); + int fieldNameStringOffset = AddToStringTable(stringTable, (overrideName == null) ? tfield.name : overrideName); + writer.Write(fieldNameStringOffset); + writer.Write((byte)depth); + writer.Write(tfield.isArray); + writer.Write(tfield.size); + writer.Write((ushort)tfield.version); + writer.Write((UInt32)(tfield.hasAlignment ? 0x4000 : 0)); //flags2 + + for (int k = 0; k < tfield.children.Count; k++) + { + TypeField childfield = tfield.children[k]; + ret += WriteTypeField(stringTable, writer, childfield, depth + 1); + } + return ret; + } + public void Write(BinaryWriter writer) + { + using (MemoryStream stringTable = new MemoryStream()) + { + writer.Write(binaryFileHeader, 0, 4); + writer.Write((byte)4); //fileVersion + writer.Write((byte)1); //flags + writer.Write((byte)0); //compressionType + long header_FileSizes = writer.BaseStream.Position; + writer.Write((UInt32)0); //compressedSize + writer.Write((UInt32)0); //uncompressedSize + writer.Write((byte)0); //assetsVersionCount + writer.Flush(); + long header_StringTableLen = writer.BaseStream.Position; + writer.Write((UInt32)0); //stringTableLen + writer.Write((UInt32)0); //stringTablePos + + writer.Write((UInt32)(behaviourTypes.Count)); + for (int i = 0; i < behaviourTypes.Count; i++) + { + writer.Write(-1); //classId + writer.Write(114); //baseClass (MonoBehaviour) + int monoTypeStringOffset = AddToStringTable(stringTable, behaviourTypes[i].monoType); + writer.Write(monoTypeStringOffset); + int baseModuleStringOffset = AddToStringTable(stringTable, behaviourTypes[i].baseModuleName); + writer.Write(baseModuleStringOffset); + long fieldCountPos = writer.BaseStream.Position; + writer.Write((UInt32)0); + int actualCount = WriteTypeField(stringTable, writer, behaviourTypes[i], 0, "Base"); + long endPos = writer.BaseStream.Position; + writer.Seek((int)fieldCountPos, SeekOrigin.Begin); + writer.Write((UInt32)actualCount); + writer.Seek((int)endPos, SeekOrigin.Begin); + } + writer.Flush(); + long stringTablePos = writer.BaseStream.Position; + writer.Write(stringTable.GetBuffer(), 0, (int)stringTable.Length); + writer.Seek((int)header_FileSizes, SeekOrigin.Begin); + writer.Write((UInt32)(stringTablePos + stringTable.Length)); //compressedSize + writer.Write((UInt32)(stringTablePos + stringTable.Length)); //uncompressedSize + writer.Seek((int)header_StringTableLen, SeekOrigin.Begin); + writer.Write((UInt32)stringTable.Length); //stringTableLen + writer.Write((UInt32)stringTablePos); //stringTablePos + writer.Seek(0, SeekOrigin.End); + writer.Flush(); + } + } + private string GetString(char[] buffer, UInt32 offset) + { + UInt32 len = 0; + for (UInt32 i = offset; i < buffer.Length; i++) + { + if (buffer[i] == 0) + { + len = i - offset; + break; + } + } + if (len == 0) + return String.Empty; + char[] chars = new char[len]; + UInt32 charIndex = 0; + for (UInt32 i = offset; charIndex < len; i++) + { + chars[charIndex] = buffer[i]; + charIndex++; + } + return new String(chars); + } + private char[] MakeStringTable(BinaryReader reader, long pos, uint len) + { + long oldFilePosition = reader.BaseStream.Position; + reader.BaseStream.Position = pos; + byte[] stringBuffer = new byte[len]; + reader.Read(stringBuffer, 0, (int)len); + char[] ret = new char[len]; + for (uint i = 0; i < len; i++) + ret[i] = (char)stringBuffer[i]; + reader.BaseStream.Position = oldFilePosition; + return ret; + } + private void ReadTypeField(BinaryReader reader, char[] stringTable, StreamWriter dump) + { + UInt32 typeStringOffset = reader.ReadUInt32(); + UInt32 nameStringOffset = reader.ReadUInt32(); + string type = GetString(stringTable, typeStringOffset); + string name = GetString(stringTable, nameStringOffset); + byte depth = reader.ReadByte(); + bool isArray = reader.ReadBoolean(); + int size = reader.ReadInt32(); + int version = reader.ReadInt16(); + int flags2 = reader.ReadInt32(); + + for (int i = 0; i < depth; i++) + dump.Write(' '); + + bool hasAlignment = (flags2 & 0x4000) != 0; + string additional = " ("; + additional += "size = " + size + ", "; + if (isArray) + additional += "array, "; + if (hasAlignment) + additional += "aligned, "; + additional = additional.Substring(0, additional.Length - 2) + ")"; + dump.WriteLine(type + " " + name + additional); + } + public bool Read(BinaryReader reader, StreamWriter dump) + { + reader.BaseStream.Position = 0; + UInt32 curHeaderUInt = reader.ReadUInt32(); + if (curHeaderUInt != fileHeaderUInt) + return false; + int version = reader.ReadByte(); + int flags = (version >= 4) ? reader.ReadByte() : 0; + byte compressionType = reader.ReadByte(); + uint compressedSize = reader.ReadUInt32(); + uint uncompressedSize = reader.ReadUInt32(); + int assetsVersionCount = reader.ReadByte(); + for (int i = 0; i < assetsVersionCount; i++) + reader.ReadByte(); + UInt32 stringTableLength = reader.ReadUInt32(); + UInt32 stringTablePos = reader.ReadUInt32(); + char[] stringTable = MakeStringTable(reader, stringTablePos, stringTableLength); + + UInt32 typeListLen = reader.ReadUInt32(); + for (UInt32 i = 0; i < typeListLen; i++) + { + int classId = reader.ReadInt32(); + int baseClassId = reader.ReadInt32(); + if ((flags & 1) == 0) + { + UInt32 stringTable_typeNameOffs = reader.ReadUInt32(); + string typeName = GetString(stringTable, stringTable_typeNameOffs); + dump.WriteLine("Type \"" + typeName + "\""); + } + else + { + UInt32 stringTable_typeNameOffs = reader.ReadUInt32(); + string typeName = GetString(stringTable, stringTable_typeNameOffs); + UInt32 stringTable_moduleNameOffs = reader.ReadUInt32(); + string moduleName = GetString(stringTable, stringTable_moduleNameOffs); + dump.WriteLine("Type \"" + typeName + "\" (" + moduleName + ")"); + } + int childCount = reader.ReadInt32(); + for (int k = 0; k < childCount; k++) + ReadTypeField(reader, stringTable, dump); + } + return true; + } + } +} diff --git a/TypeTreeGenerator/EngineVersion.cs b/TypeTreeGenerator/EngineVersion.cs new file mode 100644 index 0000000..af6a466 --- /dev/null +++ b/TypeTreeGenerator/EngineVersion.cs @@ -0,0 +1,9 @@ +namespace TypeTreeGenerator +{ + public struct EngineVersion + { + public uint year; + public uint release; + public EngineVersion(uint year, uint release) { this.year = year; this.release = release; } + } +} diff --git a/TypeTreeGenerator/HelperClass.cs b/TypeTreeGenerator/HelperClass.cs new file mode 100644 index 0000000..d7c83c5 --- /dev/null +++ b/TypeTreeGenerator/HelperClass.cs @@ -0,0 +1,855 @@ +using System; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Cecil.Rocks; +using System.Reflection; +using System.Collections.Generic; + +namespace TypeTreeGenerator +{ + public class HelperClass + { + public static Logger logger = null; + public static void SetLogger(Logger _logger) + { + logger = _logger; + } + + public static GenericFuncContainer MemberNameComparer(string name) where T : IMemberDefinition + { + return new GenericFuncContainer(member => { + return member.Name.Equals(name); + }); + } + public static GenericFuncContainer MemberFullNameComparer(string name) where T : IMemberDefinition + { + return new GenericFuncContainer(member => { + return member.FullName.Equals(name); + }); + } + //doesn't work on System.Object + public static GenericFuncContainer DeclaringTypeBaseTypeComparer(TypeDefinition baseType) where T : IMemberDefinition + { + return new GenericFuncContainer(member => { + TypeDefinition curBaseType = member.DeclaringType; + while (curBaseType != null && curBaseType.BaseType != null) + { + if (curBaseType.Equals(baseType)) + return true; + curBaseType = curBaseType.BaseType.Resolve(); + } + return false; + }); + } + public static GenericFuncContainer BaseInterfaceComparer(TypeDefinition baseType) + { + return new GenericFuncContainer(type => { + TypeDefinition curBaseType = type; + while (curBaseType != null) + { + foreach (TypeReference curInterfaceRef in curBaseType.Interfaces) + { + TypeDefinition interfaceDef = curInterfaceRef.Resolve(); + if (interfaceDef == null) + continue; + if (interfaceDef.Equals(baseType)) + return true; + } + TypeReference nextBaseType = curBaseType.BaseType; + if (nextBaseType == null) + break; + curBaseType = nextBaseType.Resolve(); + } + return false; + }); + } + public static GenericFuncContainer CustomAttributeTypeComparer(string attrName) + { + //logger.KeyInfo("baseType = " + baseType.FullName); + return new GenericFuncContainer(type => { + foreach (CustomAttribute attr in type.CustomAttributes) + { + if (attr.AttributeType.FullName.Equals(attrName)) + return true; + } + return false; + }); + } + public static GenericFuncContainer BaseTypeComparer(TypeDefinition baseType) + { + //logger.KeyInfo("baseType = " + baseType.FullName); + return new GenericFuncContainer(type => { + TypeDefinition curBaseType = type; + while (curBaseType != null) + { + if (curBaseType.Equals(baseType)) + return true; + TypeReference nextBaseType = curBaseType.BaseType; + if (nextBaseType == null) + break; + curBaseType = nextBaseType.Resolve(); + } + return false; + }); + } + public static GenericFuncContainer BaseTypeComparer(string baseType) + { + //logger.KeyInfo("baseType = " + baseType.FullName); + return new GenericFuncContainer(type => { + TypeDefinition curBaseType = type; + while (curBaseType != null) + { + if (curBaseType.FullName.Equals(baseType)) + return true; + TypeReference nextBaseType = curBaseType.BaseType; + if (nextBaseType == null) + break; + curBaseType = nextBaseType.Resolve(); + } + return false; + }); + } + public static string GetFullName(TypeReference tref) + { + string ret = tref.Namespace; + if (ret.Length > 0) + ret += "."; + ret += tref.Name; + if (tref is GenericInstanceType) + { + System.Text.StringBuilder genBuilder = new System.Text.StringBuilder(); + GenericInstanceType gref = (GenericInstanceType)tref; + for (int i = 0; i < gref.GenericArguments.Count; i++) + { + genBuilder.Append(GetFullName(gref.GenericArguments[i])); + genBuilder.Append(", "); + } + if (genBuilder.Length > 0) + genBuilder.Remove(genBuilder.Length-2, 2); + ret += "<" + genBuilder.ToString() + ">"; + } + return ret; + } + private static TypeReference ResolveGenericTypeRefs_(GenericInstanceType _base, TypeReference typeToResolve) + { + bool resolved = false; + int arrayDims = 0; + while (typeToResolve is ArrayType) + { + arrayDims++; + typeToResolve = ((ArrayType)typeToResolve).ElementType; + } + if (typeToResolve is GenericParameter) + { + GenericParameter genPar = (GenericParameter)typeToResolve; + //string fullName = genPar.DeclaringType.Resolve().FullName; + //foreach (GenericInstanceType _base in bases) + { + //bool doBreak = false; + //if (_base.Resolve().FullName.Equals(fullName)) + { + for (int i = 0; i < _base.GenericArguments.Count; i++) + { + if (_base.Resolve().GenericParameters[i].FullName.Equals(genPar.FullName)) + { + typeToResolve = _base.GenericArguments[i]; + resolved = true; + //doBreak = true; + break; + } + } + } + //if (doBreak) + // break; + } + } + if (typeToResolve is GenericInstanceType) + { + GenericInstanceType genType = (GenericInstanceType)typeToResolve; + TypeReference[] genericParameters = genType.GenericArguments.ToArray(); + for (int i = 0; i < genType.GenericArguments.Count; i++) + { + /*for (int k = 0; k < _base.GenericArguments.Count; k++) + { + if (_base.GenericParameters[k].FullName.Equals(genType.GenericParameters[i].FullName)) + { + genericParameters[i] = _base.GenericArguments[k]; + } + }*/ + genericParameters[i] = ResolveGenericTypeRefs_(_base, genericParameters[i]); + } + TypeReference elemType = genType.ElementType; + if (elemType is GenericParameter) + { + elemType = ResolveGenericTypeRefs_(_base, elemType); + } + if (elemType is GenericInstanceType) + { + typeToResolve = ((GenericInstanceType)elemType).MakeGenericInstanceType(genericParameters); + } + else + { + typeToResolve = elemType.Resolve().MakeGenericInstanceType(genericParameters); + } + } + if (arrayDims > 0) + typeToResolve = typeToResolve.MakeArrayType(arrayDims); + return typeToResolve; + } + public static TypeReference ResolveGenericTypeRefs(TypeReference _base, TypeReference typeToResolve) + { + if (!(_base is GenericInstanceType)) + return typeToResolve; + return ResolveGenericTypeRefs_((GenericInstanceType)_base, typeToResolve); + //List bases = new List(); + //bases.Add((GenericInstanceType)_base); + //TypeReference ret = ResolveGenericTypeRefs(bases, typeToResolve); + //if (ret == null) + // return typeToResolve; + //return ret; + } + protected static string writeGenericArgument(TypeReference tref) + { + if (tref.Resolve() == null) + return ""; + System.Text.StringBuilder retBuilder = new System.Text.StringBuilder(); + retBuilder.Append(tref.Resolve().FullName); + if (retBuilder.Length > 2 && retBuilder[retBuilder.Length-2] == '\u0060') + retBuilder.Remove(retBuilder.Length-2,2); + if (tref is GenericInstanceType && ((GenericInstanceType)tref).GenericArguments.Count > 0) + { + GenericInstanceType gitPar = (GenericInstanceType)tref; + if (gitPar.GenericArguments.Count > 0) + retBuilder.Append('<'); + foreach (TypeReference _gpar in gitPar.GenericArguments) { + retBuilder.Append(writeGenericArgument(_gpar)); + } + if (gitPar.GenericArguments.Count > 0) + retBuilder[retBuilder.Length-1] = '>'; + } + return retBuilder.ToString() + ","; + } + public static GenericFuncContainer FieldTypeComparer(string fieldType) + { + return new GenericFuncContainer(field => { + TypeReference tref = field.FieldType; + TypeDefinition type = field.FieldType.Resolve(); + if (type == null) + return false; + System.Text.StringBuilder typeNameBuilder = new System.Text.StringBuilder(); + typeNameBuilder.Append(type.FullName); + if ((type.GenericParameters.Count > 0) && (tref is GenericInstanceType)) + { + typeNameBuilder.Remove(typeNameBuilder.Length-2,2); + typeNameBuilder.Append('<'); + foreach (TypeReference garg in ((GenericInstanceType)tref).GenericArguments) + { + typeNameBuilder.Append(writeGenericArgument(garg)); + } + typeNameBuilder[typeNameBuilder.Length-1] = '>'; + } + return typeNameBuilder.ToString().Equals(fieldType); + }); + } + public static GenericFuncContainer FieldAttributeComparer(Mono.Cecil.FieldAttributes attrs) + { + return new GenericFuncContainer(field => { + return (field.Attributes & attrs) == attrs; + }); + } + public static GenericFuncContainer FieldNegAttributeComparer(Mono.Cecil.FieldAttributes negAttrs) + { + return new GenericFuncContainer(field => { + return (field.Attributes & negAttrs) == 0; + }); + } + public static GenericFuncContainer MethodAttributeComparer(Mono.Cecil.MethodAttributes attrs) + { + return new GenericFuncContainer(method => { + return (method.Attributes & attrs) == attrs; + }); + } + public static GenericFuncContainer MethodNegAttributeComparer(Mono.Cecil.MethodAttributes negAttrs) + { + return new GenericFuncContainer(method => { + return (method.Attributes & negAttrs) == 0; + }); + } + public static GenericFuncContainer TypeAttributeComparer(Mono.Cecil.TypeAttributes attrs) + { + return new GenericFuncContainer(type => { + return (type.Attributes & attrs) == attrs; + }); + } + public static GenericFuncContainer TypeNegAttributeComparer(Mono.Cecil.TypeAttributes negAttrs) + { + return new GenericFuncContainer(type => { + return (type.Attributes & negAttrs) == 0; + }); + } + public static GenericFuncContainer MethodReturnTypeComparer(string returnType) + { + return new GenericFuncContainer(method => { + return method.ReturnType.FullName.Equals(returnType); + }); + } + public static GenericFuncContainer MethodReturnTypeComparer(TypeDefinition returnType) + { + return new GenericFuncContainer(method => { + TypeDefinition curReturnType = method.ReturnType.Resolve(); + if (curReturnType == null) + { + HelperClass.OnError(ErrorCode.RETURNTYPE_RESOLVE_ERROR, method.FullName, Environment.StackTrace); + return false; + } + return curReturnType.Equals(returnType); + }); + } + public static GenericFuncContainer MethodParametersComparerEx(params string[] parameterTypes) + { + return new GenericFuncContainer (method => { + if (method.Parameters.Count != parameterTypes.Length) + return false; + for (int i = 0; i < method.Parameters.Count; i++) + { + if (parameterTypes[i].Length == 0) + continue; + TypeReference curParTref = method.Parameters[i].ParameterType; + TypeDefinition curParTdef = curParTref.Resolve(); + if (curParTdef == null) + throw new Exception("Unable to resolve the type '" + curParTref.FullName + "'!"); + System.Text.StringBuilder typeNameBuilder = new System.Text.StringBuilder (); + typeNameBuilder.Append (curParTdef.FullName); + if ((curParTdef.GenericParameters.Count > 0) && (curParTref is GenericInstanceType)) + { + typeNameBuilder.Remove (typeNameBuilder.Length - 2, 2); + typeNameBuilder.Append ('<'); + foreach (TypeReference garg in ((GenericInstanceType)curParTref).GenericArguments) + { + typeNameBuilder.Append (writeGenericArgument (garg)); + } + typeNameBuilder [typeNameBuilder.Length - 1] = '>'; + if (curParTref.IsArray) + typeNameBuilder.Append("[]"); + } + if (!typeNameBuilder.ToString().Equals(parameterTypes[i])) + return false; + } + return true; + }); + } + public static GenericFuncContainer MethodParametersComparer(params string[] parameterTypes) + { + return new GenericFuncContainer(method => { + if (method.Parameters.Count != parameterTypes.Length) + return false; + for (int i = 0; i < method.Parameters.Count; i++) + { + if (parameterTypes[i].Length != 0 && !method.Parameters[i].ParameterType.FullName.Equals(parameterTypes[i])) + return false; + } + return true; + }); + } + public static GenericFuncContainer MethodParametersComparer(TypeDefinition[] parameterTypes) + { + return new GenericFuncContainer(method => { + if (method.Parameters.Count != parameterTypes.Length) + return false; + for (int i = 0; i < method.Parameters.Count; i++) + { + TypeDefinition curParameterType = method.Parameters[i].ParameterType.Resolve(); + if (curParameterType == null) + { + HelperClass.OnError(ErrorCode.PARAMETER_RESOLVE_ERROR, i, method.FullName, Environment.StackTrace); + return false; + } + if (parameterTypes[i] != null && !curParameterType.Equals(parameterTypes[i])) + return false; + } + return true; + }); + } + public static GenericFuncContainer MethodParameterNamesComparer(params string[] parameterNames) + { + return new GenericFuncContainer(method => { + if (method.Parameters.Count != parameterNames.Length) + return false; + for (int i = 0; i < method.Parameters.Count; i++) + { + if (parameterNames[i].Length != 0 && !method.Parameters[i].Name.Equals(parameterNames[i])) + return false; + } + return true; + }); + } + public static GenericFuncContainer MethodOPCodeComparer(int[] indices, OpCode[] opCodes, object[] operands) + { + if (indices.Length != opCodes.Length || (operands != null && operands.Length != opCodes.Length)) + { + OnError(ErrorCode.INVALID_PARAMETER, "MethodOPCodeComparer : all arrays should have the same size"); + return null; + } + return new GenericFuncContainer(method => { + Instruction[] instrs = method.Body.Instructions.ToArray(); + for (int i = 0; i < indices.Length; i++) + { + int index = indices[i] < 0 ? (instrs.Length + indices[i]) : indices[i]; + if ((index > instrs.Length) || (index < 0)) + return false; + if (!HelperClass.OPMatches(instrs[index], opCodes[i], (operands != null) ? operands[i] : null)) + return false; + } + return true; + }); + } + + //only returns true if all comparers return true + //used for findType to make sure multiple attributes apply to a member + public static GenericFuncContainer CombinedComparer(params GenericFuncContainer[] childComparers) + { + return new GenericFuncContainer(member => { + foreach (GenericFuncContainer comparer in childComparers) + { + if (!comparer.Execute(member)) + return false; + } + return true; + }); + } + //returns true if at least one of the comparers return true + //used for findType to make sure multiple attributes apply to a member + public static GenericFuncContainer CombinedORComparer(params GenericFuncContainer[] childComparers) + { + return new GenericFuncContainer(member => { + foreach (GenericFuncContainer comparer in childComparers) + { + if (comparer.Execute(member)) + return true; + } + return false; + }); + } + //used to compare attributes of a nested type's member + public static GenericFuncContainer TypeMembersComparer(params FuncContainer[] childComparers) + { + return new GenericFuncContainer(type => { + Dictionary comparersApply = new Dictionary(); + foreach (FuncContainer comparer in childComparers) + comparersApply.Add(comparer, false); + + foreach (MethodDefinition method in type.Methods) + compareMember(method, comparersApply); + foreach (FieldDefinition field in type.Fields) + compareMember(field, comparersApply); + foreach (PropertyDefinition property in type.Properties) + compareMember(property, comparersApply); + foreach (TypeDefinition curType in type.NestedTypes) + compareMember(curType, comparersApply); + + foreach (KeyValuePair comparerApplies in comparersApply) + { + if (!comparerApplies.Value) + { + return false; + } + } + return true; + }); + } + + public static T findMember(ModuleDefinition module, object type, bool allowMultipleResults, bool mustHaveResult, params GenericFuncContainer[] comparers) + where T : IMemberDefinition + { + T[] ret = findMembers(module, type, comparers); + List nullContainer = new List(); + //workaround for compiler errors (I assume that nobody tries to create a value type implementing IMemberDefinition..) + nullContainer.GetType().GetMethod("Add", new Type[]{typeof(T)}).Invoke(nullContainer, new object[]{ null }); + if (ret == null || ret.Length == 0) + { + if (mustHaveResult) + OnError(ErrorCode.MEMBER_NOT_FOUND, typeof(T).Name, Environment.StackTrace); + return nullContainer[0]; + } + //if (ret.Length == 0) + // return nullContainer[0]; + if (!allowMultipleResults && ret.Length > 1) + { + OnError(ErrorCode.MULTIPLE_RESULTS, typeof(T).Name, Environment.StackTrace); + return nullContainer[0]; + } + return ret[0]; + } + public static T findMember(ModuleDefinition module, object type, bool allowMultipleResults, params GenericFuncContainer[] comparers) + where T : IMemberDefinition + { + return findMember(module, type, allowMultipleResults, true, comparers); + } + public static T[] findMembers(ModuleDefinition module, object type, params GenericFuncContainer[] comparers) + where T : IMemberDefinition + { + if (type != null) + { + TypeDefinition tdef = (type is string) ? module.GetType((string)type) : ((type is TypeDefinition) ? ((TypeDefinition)type) : null); + if (tdef == null) { + OnError (ErrorCode.TYPE_NOT_FOUND, (type is string) ? ((string)type) : "(null)"); + return null; + } + T[] memberArray = null; + object memberCollection = null; + if (typeof(T) == typeof(MethodDefinition)) + memberCollection = tdef.Methods; + else if (typeof(T) == typeof(FieldDefinition)) + memberCollection = tdef.Fields; + else if (typeof(T) == typeof(PropertyDefinition)) + memberCollection = tdef.Properties; + else if (typeof(T) == typeof(TypeDefinition)) + memberCollection = tdef.NestedTypes; + if (memberCollection == null) + throw new NotSupportedException ("member type " + typeof(T).Name); + //another workaround for compiler errors (when converting to T[]) + memberArray = (T[])memberCollection.GetType().GetMethod("ToArray", new Type[0]).Invoke(memberCollection, new object[0]); + List members = new List(); + foreach (T member in memberArray) + { + bool matches = true; + foreach (GenericFuncContainer comparer in comparers) + { + if (comparer == null || !comparer.Execute(member)) { + matches = false; + break; + } + } + if (matches) + members.Add(member); + } + return members.ToArray(); + } + else + { + List members = new List(); + foreach (TypeDefinition tdef in module.Types) + members.AddRange(findMembers(module, tdef, comparers)); + return members.ToArray(); + } + } + + //comparers : Dictionary + // doesApply : set to true if the comparer returned true; not set to false otherwise + private static void compareMember(T member, Dictionary comparers) + { + FuncContainer[] keys = new FuncContainer[comparers.Count]; + comparers.Keys.CopyTo(keys, 0); + for (int i = 0; i < comparers.Count; i++) + { + FuncContainer comparer = keys[i]; + if (comparer.GetArgType().Equals(typeof(T)) && ((bool)comparer.Execute(member))) + { + comparers[comparer] = true; + } + } + } + + //Executes the comparers on all matching type members. + //(Almost) The same as getting the declaring type of findMembers with a null type, + // except that findTypes works with multiple member types to compare. + public static TypeDefinition[] findTypes(ModuleDefinition module, params FuncContainer[] comparers) + { + List ret = new List(); + + Dictionary comparersApply = new Dictionary(); + foreach (FuncContainer comparer in comparers) + comparersApply.Add(comparer, false); + + foreach (TypeDefinition type in module.Types) + { + compareMember(type, comparersApply); + foreach (MethodDefinition method in type.Methods) + compareMember(method, comparersApply); + foreach (FieldDefinition field in type.Fields) + compareMember(field, comparersApply); + foreach (PropertyDefinition property in type.Properties) + compareMember(property, comparersApply); + foreach (TypeDefinition curType in type.NestedTypes) + compareMember(curType, comparersApply); + + bool typeDoesMatch = true; + foreach (KeyValuePair comparerApplies in comparersApply) + { + if (!comparerApplies.Value) + { + typeDoesMatch = false; + break; + } + } + if (typeDoesMatch) + ret.Add(type); + + foreach (FuncContainer comparer in comparers) + comparersApply[comparer] = false; + } + return ret.ToArray(); + } + //Executes the comparers on all matching type members. + //(Almost) The same as getting the declaring type of findMember with a null type, + // except that findType works with multiple member types to compare. + public static TypeDefinition findType(ModuleDefinition module, bool allowMultipleResults, params FuncContainer[] comparers) + { + TypeDefinition[] ret = findTypes(module, comparers); + if (ret == null || ret.Length == 0) + { + OnError(ErrorCode.MEMBER_NOT_FOUND, "TypeDefinition", Environment.StackTrace); + return null; + } + if (!allowMultipleResults && ret.Length > 1) + { + OnError(ErrorCode.MULTIPLE_RESULTS, "TypeDefinition", Environment.StackTrace); + return null; + } + return ret[0]; + } + + public static Func MethodAttributeSetter(Mono.Cecil.MethodAttributes attrs) + { + return method => { + method.Attributes = (method.Attributes & ~attrs) | attrs; + return true; + }; + } + public static Func MemberNameSetter(string name) + where T : IMemberDefinition + { + return member => { + member.Name = name; + return true; + }; + } + public static void executeActions(ModuleDefinition module, object type, GenericFuncContainer[] comparers, params Func[] actions) + where T : IMemberDefinition + { + T[] members = findMembers(module, type, comparers); + if (members.Length == 0) + OnError (ErrorCode.MEMBER_NOT_FOUND, typeof(T).Name, Environment.StackTrace); + foreach (T member in members) + { + foreach (Func action in actions) + { + if (!action(member)) + OnError(ErrorCode.ACTION_FAILED, "member " + member.FullName); + } + logger.Info("Patched " + member.FullName + "."); + } + } + public static void executeActions(ModuleDefinition module, object type, params GenericFuncContainer[] actions) + { + TypeDefinition tdef = (type is string) ? module.GetType((string)type) : ((type is TypeDefinition) ? ((TypeDefinition)type) : null); + if (tdef == null) { + OnError(ErrorCode.TYPE_NOT_FOUND, (type is string) ? ((string)type) : "(null)"); + return; + } + foreach (GenericFuncContainer action in actions) + { + if (!action.Execute(tdef)) + OnError(ErrorCode.ACTION_FAILED, "type " + tdef.FullName); + } + logger.Info("Patched " + tdef.FullName + "."); + } + public static void executeActions(T type, params GenericFuncContainer[] actions) + where T : IMemberDefinition + { + string fullName; + PropertyInfo fullNameProp = type.GetType().GetProperty("FullName"); + if (fullNameProp != null) + fullName = (string)fullNameProp.GetGetMethod().Invoke(type, new object[0]); + else + fullName = type.ToString(); + + foreach (GenericFuncContainer action in actions) + { + if (!action.Execute(type)) + OnError(ErrorCode.ACTION_FAILED, fullName); + } + logger.Info("Patched " + type.FullName + "."); + } + + protected static bool OPMatches(Instruction instr, OpCode op, object operand) + { + if (instr.OpCode != op || + ( + (operand != null) ? + ((operand != null && instr.Operand == null) || + !operand.Equals(instr.Operand)) + : false + )) + { + return false; + } + return true; + } + + public static int[] FindOPCodePattern(MethodDefinition mdef, OpCode[] pattern, int offset = 0, object[] operands = null) + { + if (pattern.Length == 0) + return new int[0]; + Mono.Collections.Generic.Collection instrs = mdef.Body.Instructions; + List results = new List(); + for (int i = 0; i < (instrs.Count-pattern.Length); i++) + { + bool matches = true; + for (int _i = 0; _i < pattern.Length; _i++) + { + Instruction curInstr = instrs[i+_i]; + if (!OPMatches(curInstr, pattern[_i], (operands == null) ? null : operands[_i])) + { + matches = false; + break; + } + } + if (matches) + results.Add(i + offset); + } + return results.ToArray(); + } + + public static bool RenameVirtualMethod(MethodDefinition method, string newName) + { + TypeDefinition[] parameters = new TypeDefinition[method.Parameters.Count]; + string oldName = method.Name; + for (int i = 0; i < parameters.Length; i++) + { + parameters[i] = method.Parameters[i].ParameterType.Resolve (); + if (parameters[i] == null) + { + HelperClass.OnError(ErrorCode.PARAMETER_RESOLVE_ERROR, i, method.FullName, Environment.StackTrace); + return false; + } + } + + GenericFuncContainer vMethodComparer = CombinedComparer( + MethodParametersComparer(parameters), + MemberNameComparer(method.Name), + MethodAttributeComparer(Mono.Cecil.MethodAttributes.Virtual)); + + TypeDefinition curBaseType = method.DeclaringType; + while (curBaseType != null) + { + if (findMember(null, curBaseType, true, false, vMethodComparer) == null) + break; + TypeReference curBaseRef = curBaseType.BaseType; + if (curBaseRef == null) + break; + TypeDefinition curBaseDef = curBaseRef.Resolve(); + if (curBaseDef == null) + break; + curBaseType = curBaseDef; + } + + vMethodComparer = CombinedComparer(vMethodComparer, DeclaringTypeBaseTypeComparer(curBaseType)); + //doesn't work for multiple assemblies + foreach (MethodDefinition curVMethod in findMembers(curBaseType.Module, null, vMethodComparer)) + { + curVMethod.Name = newName; + } + return true; + } + + public interface FuncContainer + { + Type GetArgType(); + Type GetRetType(); + object Execute(object arg1); + } + public class GenericFuncContainer : FuncContainer + { + private static MethodInfo genericExecuteMethod; + static GenericFuncContainer() + { + genericExecuteMethod = typeof(GenericFuncContainer).GetMethod("Execute", new Type[]{typeof(T)}); + } + public Func value; + public GenericFuncContainer(Func value) + { + this.value = value; + } + public Type GetArgType() + { + return typeof(T); + } + public Type GetRetType() + { + return typeof(K); + } + public K Execute(T arg1) + { + return value(arg1); + } + public object Execute(object arg1) + { + //T generic_arg1; + if (arg1 == null) { + if (typeof(T).IsValueType) + throw new InvalidCastException("arg1 must be a boxed value!"); + //List nullContainer = new List(); + //workaround for compiler errors (I already checked if it is a value type before) + //nullContainer.GetType().GetMethod("Add", new Type[]{typeof(T)}).Invoke(nullContainer, new object[]{ null }); + //generic_arg1 = nullContainer[0]; + } + else if (!typeof(T).IsAssignableFrom(arg1.GetType())) + throw new InvalidCastException("Cannot assign arg1 to " + typeof(T).FullName + "!"); + else + { + //generic_arg1 = arg1 as T; + } + return genericExecuteMethod.Invoke(this, new object[]{ arg1 }); + } + } + + class ErrorInfo : Attribute + { + public string description; + public int code; + public ErrorInfo(string description, int code) + { + this.description = description; + this.code = code; + } + } + private enum ErrorCode + { + [ErrorInfo("Unable to find type {0}!", 0)] + TYPE_NOT_FOUND, + [ErrorInfo("Multiple results of type {0} found!\r\n{1}", 1)] + MULTIPLE_RESULTS, + [ErrorInfo("Unable to find a member of type {0}!\r\n{1}", 2)] + MEMBER_NOT_FOUND, + [ErrorInfo("Unable to apply patches to {0}!", 3)] + ACTION_FAILED, + [ErrorInfo("Invalid parameter : {0}!", 4)] + INVALID_PARAMETER, + [ErrorInfo("Unable to resolve the type of parameter {0} of {1}!\r\n{2}", 5)] + PARAMETER_RESOLVE_ERROR, + [ErrorInfo("Unable to resolve the return type of {1}!\r\n{2}", 6)] + RETURNTYPE_RESOLVE_ERROR, + }; + private static void OnError(ErrorCode error, params object[] args) + { + if (logger == null) + return; + MemberInfo[] codeInfo = typeof(ErrorCode).GetMember(error.ToString()); + if (codeInfo == null || codeInfo.Length <= 0) + { + logger.Error("Something really bad happened while executing OnError (cannot find " + error.ToString() + " in ErrorCode)."); + return; + } + object[] errorInfoAttrs = codeInfo[0].GetCustomAttributes(typeof(ErrorInfo), false); + if (errorInfoAttrs.Length == 1 && errorInfoAttrs[0] is ErrorInfo) + { + ErrorInfo errInfo = (ErrorInfo)errorInfoAttrs[0]; + logger.Error(String.Format(errInfo.description, args)); + } + else + logger.Error("OnError : cannot find the ErrorInfo attribute for " + error.ToString() + "."); + } + } +} + diff --git a/TypeTreeGenerator/Logger.cs b/TypeTreeGenerator/Logger.cs new file mode 100644 index 0000000..cd2c80d --- /dev/null +++ b/TypeTreeGenerator/Logger.cs @@ -0,0 +1,127 @@ +using System; +using System.IO; + +namespace TypeTreeGenerator +{ + public class Logger + { + class PrependText : Attribute + { + public string prependText; + public PrependText(string prependText) + { + this.prependText = prependText; + } + } + public enum Level + { + [PrependText("INFO : ")] + INFO, + [PrependText("INFO : ")] + KEYINFO, + [PrependText("")] + NONE, + [PrependText("WARNING : ")] + WARNING, + [PrependText("ERROR : ")] + ERROR, + } + + public static string Level_ToString(Level level) + { + return ((PrependText)level.GetType().GetMember(level.ToString())[0].GetCustomAttributes(typeof(PrependText),false)[0]).prependText; + } + + private int minLogLevel; + private FileStream fileLogger; + private StreamWriter fileWriter; + private Logger parentLogger; + public Logger(string logPath, Logger parentLogger, bool logToConsole) + : this(logPath, parentLogger, !logToConsole ? -1 : (int)Level.INFO) + { + } + public Logger(string logPath, Logger parentLogger, int minLogToConsole) + { + this.minLogLevel = minLogToConsole; + fileLogger = null; + fileWriter = null; + this.parentLogger = parentLogger; + if (logPath != null) { + try { + File.Delete(logPath); + fileLogger = File.OpenWrite(logPath); + fileWriter = new StreamWriter(fileLogger); + } catch (Exception) { + fileLogger = null; + fileWriter = null; + LogConsole(Level.WARNING, "Unable to open file '" + logPath + "' for writing!", true); + } + } + } + + public void KeyInfo(string str = "") + { + Log(Level.KEYINFO, str); + } + public void Info(string str = "") + { + Log(Level.INFO, str); + } + public void Write(string str = "") + { + Log(Level.NONE, str); + } + public void Warning(string str = "") + { + Log(Level.WARNING, str); + } + public void Error(string str = "") + { + Log(Level.ERROR, str); + } + + public void Log(Level level, string str = "") + { + LogConsole(level, str); + try { + LogFile(level, str); + } catch (Exception e) { + LogConsole(Level.WARNING, "An exception occured while writing to the log file :", true); + LogConsole(Level.WARNING, e.ToString(), true); + } + if (parentLogger != null) { + parentLogger.Log(level, str); + } + } + public bool LogFile(Level level, string str) + { + str = Level_ToString(level) + str; + if (fileWriter != null) { + fileWriter.Write(str + "\r\n"); + fileWriter.Flush(); + } + return fileWriter != null; + } + public void LogConsole(Level level, string str) + { + str = Level_ToString(level) + str; + if ((int)level >= minLogLevel) { + Console.WriteLine(str); + } + } + private void LogConsole(Level level, string str, bool force) + { + str = Level_ToString(level) + str; + if (force || ((int)level >= minLogLevel)) { + Console.WriteLine(str); + } + } + + public void Close() + { + if (fileWriter != null) + fileWriter.Close(); + } + } +} + diff --git a/TypeTreeGenerator/Program.cs b/TypeTreeGenerator/Program.cs new file mode 100644 index 0000000..d76ef4e --- /dev/null +++ b/TypeTreeGenerator/Program.cs @@ -0,0 +1,272 @@ +using System; +using System.IO; +using System.Text; +using System.Collections.Generic; +using Mono.Cecil; + +namespace TypeTreeGenerator +{ + class MainClass + { + private static byte[] makeStderrMessage(String header, String errorMessage) + { + int headerLen = Encoding.Unicode.GetByteCount(header); + int messageLen = Encoding.Unicode.GetByteCount(errorMessage); + byte[] message = new byte[8 + headerLen + messageLen]; + message[0] = (byte)((uint)headerLen & 0xFF); message[4] = (byte)((uint)messageLen & 0xFF); + message[1] = (byte)(((uint)headerLen >> 8) & 0xFF); message[5] = (byte)(((uint)messageLen >> 8) & 0xFF); + message[2] = (byte)(((uint)headerLen >> 16) & 0xFF); message[6] = (byte)(((uint)messageLen >> 16) & 0xFF); + message[3] = (byte)(((uint)headerLen >> 24) & 0xFF); message[7] = (byte)(((uint)messageLen >> 24) & 0xFF); + Encoding.Unicode.GetBytes(header, 0, header.Length, message, 8); + Encoding.Unicode.GetBytes(errorMessage, 0, errorMessage.Length, message, 8 + headerLen); + return message; + } + class AssemblyArgs + { + public string path; + public EngineVersion engineVersion; + public AssemblyArgs(string path, EngineVersion engineVersion) + { + this.path = path; + this.engineVersion = engineVersion; + } + } + + static Logger logger; + public static void Main(string[] args) + { + /*args = new string[]{ "-f", "E:\\Programme\\SteamLibrary\\SteamApps\\common\\7 Days To Die\\7DaysToDie_Data\\Managed\\Assembly-CSharp.dll", + "-f", "E:\\Programme\\SteamLibrary\\SteamApps\\common\\7 Days To Die\\7DaysToDie_Data\\Managed\\Assembly-CSharp-firstpass.dll"};*/ + //args = new string[] { "-f", @"P:\Downloads\users_assetbundle-demo\demo\build9 test\test_Data\Managed\Assembly-CSharp.dll" }; + //args = new string[] { "-f", @"P:\Programme\Unity 5.6.0f3\Editor\Data\Managed\UnityEngine.dll", "-f", @"P:\Downloads\UABEHelp31032018_Assembly-CSharp_RefFix.dll", "-c", "UnityTest.BoolComparer" }; + if (args.Length == 0) + { + Console.Out.WriteLine("TypeTreeGenerator Parameters (evaluated from first to last) :"); + Console.Out.WriteLine("-longpathid 0|1 : Generate int (0) or SInt64 (1) PathIDs in PPtrs."); + Console.Out.WriteLine("-ver : Specify the Unity engine version for following files."); + Console.Out.WriteLine("-f : Extract the type information from an assembly file. Can be used multiple times."); + Console.Out.WriteLine("-c : Only extract the type information from a specific class. Can be used multiple times."); + Console.Out.WriteLine("-stdout : Write the binary ClassDatabaseFile to stdout instead of a text representation."); + Console.Out.WriteLine("-stdin : Read more parameters from stdin (UTF-16 LE) separated with null terminators, ending with two terminators."); + return; + } + List argsList = new List(args); + EngineVersion engineVersion = new EngineVersion(0, 0); + List targetAssemblies = new List(); + List targetClasses = new List(); + bool outputToStdout = false; + for (int i = 0; i < argsList.Count; i++) + { + if (argsList[i].Equals("-stdout")) + { + outputToStdout = true; + } + else if (argsList[i].Equals("-stdin")) + { + byte[] inBytes; + using (Stream stream = Console.OpenStandardInput()) + { + using (MemoryStream memStream = new MemoryStream()) + { + byte[] tempBuffer = new byte[2048]; + while (true) + { + int count = stream.Read(tempBuffer, 0, tempBuffer.Length); + if (count == 0) + { + memStream.WriteByte(0); + memStream.WriteByte(0); + break; + } + memStream.Write(tempBuffer, 0, count); + if (memStream.Length > 4) + { + memStream.Seek(-4, SeekOrigin.End); + byte[] nullBuf = new byte[4]; + memStream.Read(nullBuf, 0, 4); + if (nullBuf[0] == 0 && nullBuf[1] == 0 && nullBuf[2] == 0 && nullBuf[3] == 0) + break; + } + } + inBytes = new byte[memStream.Length]; + Array.Copy(memStream.GetBuffer(), inBytes, inBytes.Length); + } + } + int strBegin = 0; + for (int k = 0; k < (inBytes.Length - 1); k += 2) + { + if (inBytes[k] == 0 && inBytes[k + 1] == 0) //string end + { + if (k > strBegin) + { + argsList.Add(Encoding.Unicode.GetString(inBytes, strBegin, k - strBegin)); + } + strBegin = k + 2; + } + } + } + else if (argsList[i].Equals("-longpathid")) + { + if ((i + 1) < argsList.Count) + { + if (argsList[i + 1].Equals("1") || argsList[i + 1].Equals("true") || argsList[i + 1].Equals("on")) + { + if (engineVersion.year < 5) + { + engineVersion.year = 5; + engineVersion.release = 0; + } + } + else + { + if (engineVersion.year >= 5) + { + engineVersion.year = 0; + engineVersion.release = 0; + } + } + i++; + } + } + else if (argsList[i].Equals("-ver")) + { + if ((i + 2) < argsList.Count) + { + uint.TryParse(argsList[i + 1], out engineVersion.year); + uint.TryParse(argsList[i + 2], out engineVersion.release); + i += 2; + } + } + else if (argsList[i].Equals("-f")) + { + if ((i + 1) < argsList.Count) + { + targetAssemblies.Add(new AssemblyArgs(argsList[i + 1], engineVersion)); + i++; + } + } + else if (argsList[i].Equals("-c")) + { + if ((i + 1) < argsList.Count) + { + targetClasses.Add(argsList[i + 1]); + i++; + } + } + } + if (targetAssemblies.Count == 0) + return; + + HelperClass.GenericFuncContainer typeComparer; + if (targetClasses.Count > 0) + { + var typeNameComparers = new HelperClass.GenericFuncContainer[targetClasses.Count]; + for (int i = 0; i < targetClasses.Count; i++) + typeNameComparers[i] = HelperClass.MemberFullNameComparer(targetClasses[i]); + typeComparer = HelperClass.CombinedORComparer(typeNameComparers); + } + else + { + typeComparer = HelperClass.CombinedORComparer( + HelperClass.BaseTypeComparer("UnityEngine.Object"), + HelperClass.TypeAttributeComparer(TypeAttributes.Serializable) + ); + } + + Stream stderr = null; + if (outputToStdout) + { + Console.Out.Close(); + Console.Error.Close(); + stderr = Console.OpenStandardError(); + } + logger = new Logger(outputToStdout ? null : "typetreelog.txt", null, outputToStdout ? Int32.MaxValue : (int)Logger.Level.INFO); + HelperClass.SetLogger(logger); + ClassDatabaseFile2 databaseFile = new ClassDatabaseFile2(); + int curFieldIndex = 0; + + DefaultAssemblyResolver resolver = new DefaultAssemblyResolver(); + + for (int i = 0; i < targetAssemblies.Count; i++) + { + FileInfo fileInfo = new FileInfo(targetAssemblies[i].path); + resolver.AddSearchDirectory(fileInfo.Directory.FullName); + } + + for (int i = 0; i < targetAssemblies.Count; i++) + { + FileInfo fileInfo = new FileInfo(targetAssemblies[i].path); + try + { + AssemblyDefinition csharpDef = AssemblyDefinition.ReadAssembly(targetAssemblies[i].path, new ReaderParameters { AssemblyResolver = resolver }); + TypeDefinition[] typesToCheck = HelperClass.findTypes(csharpDef.Modules[0], typeComparer); + foreach (TypeDefinition tDef in typesToCheck) + { + if (tDef.HasGenericParameters) + continue; + if (tDef.IsAbstract) + continue; + try + { + TypeField baseField = new TypeField(csharpDef.Modules[0].Import(tDef), "Base", targetAssemblies[i].engineVersion, new List(), true); + baseField.baseModuleName = fileInfo.Name; + databaseFile.Add(baseField); + baseField.Dump(0, logger); + curFieldIndex++; + } + catch (Exception e) + { + if (outputToStdout) + { + byte[] message = makeStderrMessage(fileInfo.Name + " : " + tDef.FullName, e.ToString()); + stderr.Write(message, 0, message.Length); + //Console.Error.WriteLine(message); + } + else + { + string message = "Failed at " + fileInfo.Name + " : " + tDef.FullName + "\n" + e.ToString(); + logger.Error(message); + } + //return; + } + } + } + catch (Exception e) + { + if (outputToStdout) + { + byte[] message = makeStderrMessage(fileInfo.Name, e.ToString()); + stderr.Write(message, 0, message.Length); + } + else + { + string message = "Failed at " + fileInfo.Name + "\n" + e.ToString(); + logger.Error(message); + } + } + } + if (outputToStdout) + { + //ClassDatabaseFile2::Write requires Seek capability which stdout doesn't have. + MemoryStream stream = new MemoryStream(); + using (BinaryWriter databaseStreamWriter = new BinaryWriter(stream)) + { + databaseFile.Write(databaseStreamWriter); + databaseStreamWriter.Flush(); + byte[] buffer = stream.GetBuffer(); + Console.OpenStandardOutput().Write(buffer, 0, (int)stream.Length); + } + } + else + { + Stream databaseStream = new FileStream("behaviourdb.dat", FileMode.Create, FileAccess.Write, FileShare.None, 128, FileOptions.RandomAccess); + using (BinaryWriter databaseStreamWriter = new BinaryWriter(databaseStream)) + { + databaseFile.Write(databaseStreamWriter); + } + } + + logger = null; + } + } +} diff --git a/TypeTreeGenerator/TypeField.cs b/TypeTreeGenerator/TypeField.cs new file mode 100644 index 0000000..adbb4a6 --- /dev/null +++ b/TypeTreeGenerator/TypeField.cs @@ -0,0 +1,967 @@ +using System; +using System.Collections.Generic; +using Mono.Cecil; +using Mono.Cecil.Rocks; + +namespace TypeTreeGenerator +{ + public class TypeField + { + class TypeInfo + { + public string fullName; + public TypeReference cecilType; + public TypeField _base; + + public List _instances; + public bool baseComplete; + public TypeInfo(TypeReference tref) + { + baseComplete = false; + this.fullName = tref.FullName; + this._base = null; + this.cecilType = tref; + this._instances = new List(); + } + } + static List resolvedTypes = new List(); + static TypeInfo FindTypeInDatabase(string fullName) + { + foreach (TypeInfo curType in resolvedTypes) + { + if (curType.fullName.Equals(fullName)) + return curType; + } + return null; + } + static TypeInfo FindTypeInDatabase(TypeReference tref) + { + return FindTypeInDatabase(tref.FullName); + } + static TypeInfo AddToDatabase(TypeInfo type) + { + if (type.cecilType.Resolve().IsEnum) + return null; + switch (type.fullName) + { + case "System.Boolean": + case "System.Byte": + case "System.Char": + case "System.SByte": + case "System.Int16": + case "System.UInt16": + case "System.Int32": + case "System.UInt32": + case "System.Int64": + case "System.UInt64": + case "System.Single": + case "System.Double": + case "System.String": + case "System.Collections.Generic.List": + case "System.Collections.Generic.HashSet": + case "System.Collections.Generic.Dictionary": + case "System.Collections.Hashtable": + return null; + } + TypeInfo ret = FindTypeInDatabase(type.fullName); + if (ret == null) + { + ret = type; + resolvedTypes.Add(ret); + } + return ret; + } + public List children; + /*public enum ValueType + { + _None=0, + _Bool, + _Int8, + _UInt8, + _Int16, + _UInt16, + _Int32, + _UInt32, + _Int64, + _UInt64, + _Float, + _Double, + _String, + _Array + } + ValueType valueType;*/ + public bool isBuiltinType = false; + + public bool isArray = false; + //for MonoBehaviors, types that are not 4 or 8 bytes long (bytes,bools,shorts,strings,...) always are aligned (8 1-byte variables take 8*4 bytes) + //flag 0x4000 + public bool hasAlignment = false; + public int version = 1; + public TypeField _base; + public string type; + public string monoType; public string baseModuleName; + public string name; + public int size + { + get + { + if (children.Count == 0) + { + switch (this.type) + { + case "bool": + case "UInt8": + case "char": + case "SInt8": + return 1; + case "SInt16": + case "UInt16": + return 2; + case "int": + case "unsigned int": + case "float": + return 4; + case "double": + case "SInt64": + case "UInt64": + return 8; + default: + return -1; + } + } + else if (this.isArray) + return -1; + else + { + int ret = 0; + foreach (TypeField child in children) + { + int childSize = child.size; //Don't include alignment. + if (childSize == -1) + return -1; + ret += childSize; + } + return ret; + } + } + } + public TypeField(TypeField[] children, string type, string name) + { + _base = this; + this.children = new List(children); + this.type = type; + this.name = name; + } + bool TestEquation_(TypeField other, List tested, bool testName) + { + if ((other.isArray == this.isArray) && + (other.hasAlignment == this.hasAlignment) && + (other.children.Count == this.children.Count) && + (other.type.Equals(this.type)) && + (testName ? other.name.Equals(this.name) : true)) + { + if (!tested.Contains(this)) + { + for (int i = 0; i < this.children.Count; i++) + if (!this.children[i].TestEquation_(other.children[i], new List(tested), true)) + return false; + tested.Add(this); + } + return true; + } + return false; + } + public bool TestEquation(TypeField other, bool testName = true) + { + if ((other.isArray == this.isArray) && + (other.hasAlignment == this.hasAlignment) && + (other.children.Count == this.children.Count) && + (other.type.Equals(this.type)) && + (testName ? other.name.Equals(this.name) : true)) + { + List tparents = new List(); + tparents.Add(other); + for (int i = 0; i < this.children.Count; i++) + if (!this.children[i].TestEquation_(other.children[i], new List(tparents), true)) + return false; + return true; + } + return false; + } + private void TestSelfContain(TypeReference self, TypeReference curType, List parents) + { + bool isObject = false; + TypeDefinition curBase = curType.Resolve(); + while (curBase != null) + { + if (curBase.FullName.Equals("UnityEngine.Object")) + { + isObject = true; + break; + } + TypeReference baseRef = curBase.BaseType; + if (baseRef == null) + break; + curBase = baseRef.Resolve(); + } + if (!isObject) + { + if (HelperClass.GetFullName(curType).Equals(HelperClass.GetFullName(self))) + throw new ArgumentException("The class contains itself! (Unity itself allows this but stops serializing at a certain depth)"); + /*foreach (TypeField parent in parents) + { + if (parent.type.Equals(curType.Name)) + throw new ArgumentException("A class contains one of its parent classes!"); + }*/ + } + } + + //https://docs.unity3d.com/560/Documentation/Manual/script-Serialization.html + private static readonly string[] SerializableInternalUnityTypes = { + "UnityEngine.Vector2", + "UnityEngine.Vector3", + "UnityEngine.Vector4", + "UnityEngine.Rect", + "UnityEngine.Quaternion", + "UnityEngine.Matrix4x4", + "UnityEngine.Color", + "UnityEngine.Color32", + "UnityEngine.LayerMask", + "UnityEngine.AnimationCurve", + "UnityEngine.Gradient", + "UnityEngine.RectOffset", + "UnityEngine.GUIStyle", + }; + private bool IsSerializable(TypeReference typeRef) + { + TypeDefinition _typeDef = typeRef.Resolve(); + if (typeRef.IsArray) + { + return IsSerializable(((ArrayType)typeRef).ElementType); + } + if (typeRef.FullName.StartsWith("System.Collections.Generic.List")) + { + if (typeRef is GenericInstanceType) + { + TypeReference nestedType = ((GenericInstanceType)typeRef).GenericArguments[0]; + //TODO: nestedType should not be inside mscorlib + return IsSerializable(nestedType); + } + else + return false; + } + if (typeRef.Name.EndsWith("Dictionary`2")) + return false; + if (_typeDef != null) + { + if (_typeDef.IsInterface || _typeDef.IsAbstract) + return false; + if (_typeDef.IsEnum || _typeDef.IsSerializable || HelperClass.BaseTypeComparer("UnityEngine.Object").Execute(_typeDef)) + return true; + } + if (typeRef.Namespace.StartsWith("UnityEngine")) + { + foreach (string builtinType in SerializableInternalUnityTypes) + { + if (typeRef.FullName.Equals(builtinType)) + return true; + } + } + return false; + } + private void EmitArrayType(TypeReference type, TypeReference elementType, EngineVersion engineVersion, List parents) + { + TestSelfContain(type, elementType, parents); + TypeField itemCount = new TypeField(new TypeField[0], "int", "size"); + + TypeInfo typeInfo = null; + TypeField data = null; + bool isObject = HelperClass.BaseTypeComparer("UnityEngine.Object").Execute(elementType.Resolve()); + if (!isObject) + { + typeInfo = FindTypeInDatabase(elementType); + if (typeInfo == null) + { + TypeField baseField = new TypeField(elementType, String.Empty, engineVersion, new List(parents)); + typeInfo = new TypeInfo(elementType); + typeInfo._base = baseField; + typeInfo = AddToDatabase(typeInfo); + } + } + if (typeInfo != null) + { + data = new TypeField(typeInfo._base.children.ToArray(), typeInfo._base.type/*"Generic Mono"*/, "data"); + data.children = typeInfo._base.children; + data._base = typeInfo._base; + } + else //the type either is a value type or a UnityEngine.Object + { + data = new TypeField(elementType, "data", engineVersion, new List(parents)); + if (data.hasAlignment) + { + int elementSize = data.size; + if (elementSize > 0 && elementSize < 4) //For byte/word arrays, align the array instead of each element. + { + data.hasAlignment = false; + this.hasAlignment = true; + } + } + } + this.type = data.isBuiltinType ? "vector" : data.type; //Seems strange, but Unity does name the array field type that way. + TypeField array = new TypeField(new TypeField[] { itemCount, data }, "Array", "Array"); + array.isArray = true; + if (data.size != -1 || data.children.Count != 0) + { + children.Add(array); + } + } + public TypeField(TypeReference type, string name, EngineVersion engineVersion, List parents = null, bool isBase = false) + { + bool pathIdIsInt64 = (engineVersion.year >= 5); + this.monoType = HelperClass.GetFullName(type); + this.type = type.Name; + this.baseModuleName = type.Module.Name; + _base = this; + TypeInfo ownTypeInfo = null, _tmpOwnTypeInfo = null; + if ((_tmpOwnTypeInfo = FindTypeInDatabase(type)) == null) + { + ownTypeInfo = AddToDatabase(new TypeInfo(type)); + if (ownTypeInfo != null) + { + ownTypeInfo._base = this; + } + } + else if (!_tmpOwnTypeInfo.baseComplete) + _tmpOwnTypeInfo._instances.Add(this); + if (parents == null) + parents = new List(); + parents.Add(this); + + this.children = new List(); + + TypeDefinition tDef = type.Resolve(); + this.name = name; + + if (type.IsArray) + { + EmitArrayType(type, ((ArrayType)type).ElementType, engineVersion, parents); + } + else if (type.FullName.StartsWith("System.Collections.Generic.List")/* || type.FullName.StartsWith("System.Collections.Generic.HashSet")*/) + { + if (type is GenericInstanceType) + { + GenericInstanceType genType = (GenericInstanceType)type; + EmitArrayType(type, genType.GenericArguments[0], engineVersion, parents); + } + } + else + { + if (!tDef.IsEnum) + { + bool isObject = HelperClass.BaseTypeComparer("UnityEngine.Object").Execute(tDef); + if (!isBase && isObject) + { + isBuiltinType = true; + TypeField fileId = new TypeField(new TypeField[0], "int", "m_FileID"); + TypeField pathId = new TypeField(new TypeField[0], pathIdIsInt64 ? "SInt64" : "int", "m_PathID"); + children.Add(fileId); + children.Add(pathId); + this.type = "PPtr<$" + tDef.Name + ">"; + if (_tmpOwnTypeInfo != null && !_tmpOwnTypeInfo.baseComplete) + _tmpOwnTypeInfo._instances.Remove(this); + } + else + { + string valueTypeName = ""; + hasAlignment = false; //Only set for simple value types; in Arrays of UInt8, only the UInt8 data has alignment set. + switch (tDef.FullName) + { + case "System.Boolean": + case "System.Byte": + isBuiltinType = true; + hasAlignment = true; + valueTypeName = "UInt8"; + break; + case "System.SByte": + isBuiltinType = true; + hasAlignment = true; + valueTypeName = "SInt8"; + break; + case "System.Int16": + isBuiltinType = true; + hasAlignment = true; + valueTypeName = "SInt16"; + break; + case "System.Char": + case "System.UInt16": + isBuiltinType = true; + hasAlignment = true; + valueTypeName = "UInt16"; + break; + case "System.Int32": + isBuiltinType = true; + hasAlignment = false; + valueTypeName = "int"; + break; + case "System.UInt32": + isBuiltinType = true; + hasAlignment = false; + valueTypeName = "unsigned int"; + break; + case "System.Int64": + isBuiltinType = true; + hasAlignment = false; + valueTypeName = "SInt64"; + break; + case "System.UInt64": + isBuiltinType = true; + hasAlignment = false; + valueTypeName = "UInt64"; + break; + case "System.Single": + isBuiltinType = true; + hasAlignment = false; + valueTypeName = "float"; + break; + case "System.Double": + isBuiltinType = true; + hasAlignment = false; + valueTypeName = "double"; + break; + case "UnityEngine.AnimationCurve": + { + isBuiltinType = true; + hasAlignment = false; + valueTypeName = "AnimationCurve"; + version=1; //2 since 5.5.* + int keyframe_version = 1; //2 since 5.5.*, 3 since 2018.1.* + if (engineVersion.year > 5 || (engineVersion.year == 5 && engineVersion.release >= 5)) + { + version = 2; + keyframe_version = 2; + } + if (engineVersion.year >= 2018) + { + keyframe_version = 3; + } + TypeField data; + switch (keyframe_version) + { + default: + case 3: + data = new TypeField(new TypeField[] + { + new TypeField(new TypeField[0], "float", "time"), + new TypeField(new TypeField[0], "float", "value"), + new TypeField(new TypeField[0], "float", "inSlope"), + new TypeField(new TypeField[0], "float", "outSlope"), + new TypeField(new TypeField[0], "int", "weightedMode"), + new TypeField(new TypeField[0], "float", "inWeight"), + new TypeField(new TypeField[0], "float", "outWeight"), + }, "Keyframe", "data"); + data.version = 3; + break; + case 2: //Seems to be identical to v1? + data = new TypeField(new TypeField[] + { + new TypeField(new TypeField[0], "float", "time"), + new TypeField(new TypeField[0], "float", "value"), + new TypeField(new TypeField[0], "float", "inSlope"), + new TypeField(new TypeField[0], "float", "outSlope"), + }, "Keyframe", "data"); + data.version = 2; + break; + case 1: + data = new TypeField(new TypeField[] + { + new TypeField(new TypeField[0], "float", "time"), + new TypeField(new TypeField[0], "float", "value"), + new TypeField(new TypeField[0], "float", "inSlope"), + new TypeField(new TypeField[0], "float", "outSlope"), + }, "Keyframe", "data"); + data.version = 1; + break; + } + TypeField size = new TypeField(new TypeField[0], "int", "size"); + TypeField array = new TypeField(new TypeField[]{size,data}, "Array", "Array"); + array.isArray = true; + TypeField curve = new TypeField(new TypeField[]{array}, "vector", "m_Curve"); + children.Add(curve); + + TypeField preInfinity = new TypeField(new TypeField[0], "int", "m_PreInfinity"); + children.Add(preInfinity); + + TypeField postInfinity = new TypeField(new TypeField[0], "int", "m_PostInfinity"); + children.Add(postInfinity); + + if (version >= 2 || (version == 1 && engineVersion.year == 5 && engineVersion.release >= 3)) + { + //Version 1 AnimationCurves from 5.3.* also have this field. + TypeField rotationOrder = new TypeField(new TypeField[0], "int", "m_RotationOrder"); + children.Add(rotationOrder); + } + } + break; + case "UnityEngine.Vector2": + { + isBuiltinType = true; + hasAlignment = false; + valueTypeName = "Vector2f"; + TypeField x = new TypeField(new TypeField[0], "float", "x"); + children.Add(x); + TypeField y = new TypeField(new TypeField[0], "float", "y"); + children.Add(y); + } + break; + case "UnityEngine.Vector3": + { + isBuiltinType = true; + hasAlignment = false; + valueTypeName = "Vector3f"; + TypeField x = new TypeField(new TypeField[0], "float", "x"); + children.Add(x); + TypeField y = new TypeField(new TypeField[0], "float", "y"); + children.Add(y); + TypeField z = new TypeField(new TypeField[0], "float", "y"); + children.Add(z); + } + break; + case "UnityEngine.Vector4": + { + isBuiltinType = true; + hasAlignment = false; + valueTypeName = "Vector4f"; + TypeField x = new TypeField(new TypeField[0], "float", "x"); + children.Add(x); + TypeField y = new TypeField(new TypeField[0], "float", "y"); + children.Add(y); + TypeField z = new TypeField(new TypeField[0], "float", "z"); + children.Add(z); + TypeField w = new TypeField(new TypeField[0], "float", "w"); + children.Add(w); + } + break; + case "UnityEngine.Rect": + { + isBuiltinType = true; + hasAlignment = false; + version = 2; //What is version 1? + valueTypeName = "Rectf"; + TypeField x = new TypeField(new TypeField[0], "float", "x"); + children.Add(x); + TypeField y = new TypeField(new TypeField[0], "float", "y"); + children.Add(y); + TypeField w = new TypeField(new TypeField[0], "float", "width"); + children.Add(w); + TypeField h = new TypeField(new TypeField[0], "float", "height"); + children.Add(h); + } + break; + case "UnityEngine.RectOffset": + { + isBuiltinType = true; + hasAlignment = false; + valueTypeName = "RectOffset"; + TypeField l = new TypeField(new TypeField[0], "int", "m_Left"); + children.Add(l); + TypeField r = new TypeField(new TypeField[0], "int", "m_Right"); + children.Add(r); + TypeField t = new TypeField(new TypeField[0], "int", "m_Top"); + children.Add(t); + TypeField b = new TypeField(new TypeField[0], "int", "m_Bottom"); + children.Add(b); + } + break; + case "UnityEngine.Quaternion": + { + isBuiltinType = true; + hasAlignment = false; + valueTypeName = "Quaternionf"; + TypeField x = new TypeField(new TypeField[0], "float", "x"); + children.Add(x); + TypeField y = new TypeField(new TypeField[0], "float", "y"); + children.Add(y); + TypeField z = new TypeField(new TypeField[0], "float", "z"); + children.Add(z); + TypeField w = new TypeField(new TypeField[0], "float", "w"); + children.Add(w); + } + break; + case "UnityEngine.Matrix4x4": + { + isBuiltinType = true; + hasAlignment = false; + valueTypeName = "Matrix4x4f"; + for (int a = 0; a <= 3; a++) + { + for (int b = 0; b <= 3; b++) + { + children.Add(new TypeField(new TypeField[0], "float", "e"+a+b)); + } + } + } + break; + case "UnityEngine.Color": + { + isBuiltinType = true; + hasAlignment = false; + valueTypeName = "ColorRGBA"; + TypeField r = new TypeField(new TypeField[0], "float", "r"); + children.Add(r); + TypeField g = new TypeField(new TypeField[0], "float", "g"); + children.Add(g); + TypeField b = new TypeField(new TypeField[0], "float", "b"); + children.Add(b); + TypeField a = new TypeField(new TypeField[0], "float", "a"); + children.Add(a); + } + break; + case "UnityEngine.Color32": + { + isBuiltinType = true; + hasAlignment = false; + valueTypeName = "ColorRGBA"; //Same type name as the float ColorRGBA. + TypeField rgba = new TypeField(new TypeField[0], "unsigned int", "rgba"); + children.Add(rgba); + } + break; + case "UnityEngine.LayerMask": //Transfer_Blittable_SingleValueField + { + isBuiltinType = true; + hasAlignment = false; + version = 2; //What is 1? + valueTypeName = "BitField"; + TypeField bits = new TypeField(new TypeField[0], "unsigned int", "m_Bits"); + children.Add(bits); + } + break; + case "UnityEngine.GUIStyle": + { + isBuiltinType = true; + hasAlignment = false; + valueTypeName = "GUIStyle"; + //string m_Name + { + TypeField size = new TypeField(new TypeField[0], "int", "size"); + TypeField data = new TypeField(new TypeField[0], "char", "data"); + TypeField array = new TypeField(new TypeField[] { size, data }, "Array", "Array"); + array.isArray = true; array.hasAlignment = true; + TypeField nameString = new TypeField(new TypeField[] { array }, "string", "m_Name"); + nameString.hasAlignment = true; //Unlike string fields in usual scripts. + children.Add(nameString); + } + //GUIStyleState + { + TypeField fileId = new TypeField(new TypeField[0], "int", "m_FileID"); + TypeField pathId = new TypeField(new TypeField[0], pathIdIsInt64 ? "SInt64" : "int", "m_PathID"); + TypeField background = new TypeField(new TypeField[] { fileId, pathId }, "PPtr", "m_Background"); + + //m_ScaledBackgrounds is located in the type information of MonoBehaviours in bundled .assets + //but it's not actually serialized (at least in 5.6.0f3); + //while leaving it out clearly solves reading/writing dumps, it still breaks the type hash. + //TODO : Further check this and consider adding a "ghost field" flag to type information so such fields can be skipped for (de)serializing. + /*TypeField size = new TypeField(new TypeField[0], "int", "size"); + TypeField data = new TypeField(new TypeField[] { fileId, pathId }, "PPtr", "data"); + TypeField array = new TypeField(new TypeField[] { size, data }, "Array", "Array"); + array.isArray = true; + TypeField scaledBackgrounds = new TypeField(new TypeField[] { array }, "vector", "m_ScaledBackgrounds");*/ + + TypeField r = new TypeField(new TypeField[0], "float", "r"); + TypeField g = new TypeField(new TypeField[0], "float", "g"); + TypeField b = new TypeField(new TypeField[0], "float", "b"); + TypeField a = new TypeField(new TypeField[0], "float", "a"); + TypeField textColor = new TypeField(new TypeField[] { r, g, b, a }, "ColorRGBA", "m_TextColor"); + + TypeField normal = new TypeField(new TypeField[] { background/*, scaledBackgrounds*/, textColor }, "GUIStyleState", "m_Normal"); + children.Add(normal); + TypeField hover = new TypeField(new TypeField[] { background/*, scaledBackgrounds*/, textColor }, "GUIStyleState", "m_Hover"); + children.Add(hover); + TypeField active = new TypeField(new TypeField[] { background/*, scaledBackgrounds*/, textColor }, "GUIStyleState", "m_Active"); + children.Add(active); + TypeField focused = new TypeField(new TypeField[] { background/*, scaledBackgrounds*/, textColor }, "GUIStyleState", "m_Focused"); + children.Add(focused); + TypeField onNormal = new TypeField(new TypeField[] { background/*, scaledBackgrounds*/, textColor }, "GUIStyleState", "m_OnNormal"); + children.Add(onNormal); + TypeField onHover = new TypeField(new TypeField[] { background/*, scaledBackgrounds*/, textColor }, "GUIStyleState", "m_OnHover"); + children.Add(onHover); + TypeField onActive = new TypeField(new TypeField[] { background/*, scaledBackgrounds*/, textColor }, "GUIStyleState", "m_OnActive"); + children.Add(onActive); + TypeField onFocused = new TypeField(new TypeField[] { background/*, scaledBackgrounds*/, textColor }, "GUIStyleState", "m_OnFocused"); + children.Add(onFocused); + } + //RectOffset + { + TypeField l = new TypeField(new TypeField[0], "int", "m_Left"); + TypeField r = new TypeField(new TypeField[0], "int", "m_Right"); + TypeField t = new TypeField(new TypeField[0], "int", "m_Top"); + TypeField b = new TypeField(new TypeField[0], "int", "m_Bottom"); + + TypeField border = new TypeField(new TypeField[] { l, r, t, b }, "RectOffset", "m_Border"); + children.Add(border); + TypeField margin = new TypeField(new TypeField[] { l, r, t, b }, "RectOffset", "m_Margin"); + children.Add(margin); + TypeField padding = new TypeField(new TypeField[] { l, r, t, b }, "RectOffset", "m_Padding"); + children.Add(padding); + TypeField overflow = new TypeField(new TypeField[] { l, r, t, b }, "RectOffset", "m_Overflow"); + children.Add(overflow); + } + //PPtr m_Font + { + TypeField fileId = new TypeField(new TypeField[0], "int", "m_FileID"); + TypeField pathId = new TypeField(new TypeField[0], pathIdIsInt64 ? "SInt64" : "int", "m_PathID"); + TypeField font = new TypeField(new TypeField[] { fileId, pathId }, "PPtr", "m_Font"); + children.Add(font); + } + TypeField fontSize = new TypeField(new TypeField[0], "int", "m_FontSize"); + children.Add(fontSize); + TypeField fontStyle = new TypeField(new TypeField[0], "int", "m_FontStyle"); + children.Add(fontStyle); + TypeField alignment = new TypeField(new TypeField[0], "int", "m_Alignment"); + children.Add(alignment); + TypeField wordWrap = new TypeField(new TypeField[0], "bool", "m_WordWrap"); + children.Add(wordWrap); + TypeField richText = new TypeField(new TypeField[0], "bool", "m_RichText"); + richText.hasAlignment = true; + children.Add(richText); + TypeField textClipping = new TypeField(new TypeField[0], "int", "m_TextClipping"); + children.Add(textClipping); + TypeField imagePosition = new TypeField(new TypeField[0], "int", "m_ImagePosition"); + children.Add(imagePosition); + //Vector2f m_ContentOffset + { + TypeField x = new TypeField(new TypeField[0], "float", "x"); + TypeField y = new TypeField(new TypeField[0], "float", "y"); + TypeField contentOffset = new TypeField(new TypeField[] { x, y }, "Vector2f", "m_ContentOffset"); + children.Add(contentOffset); + } + TypeField fixedWidth = new TypeField(new TypeField[0], "float", "m_FixedWidth"); + children.Add(fixedWidth); + TypeField fixedHeight = new TypeField(new TypeField[0], "float", "m_FixedHeight"); + children.Add(fixedHeight); + TypeField stretchWidth = new TypeField(new TypeField[0], "bool", "m_StretchWidth"); + children.Add(stretchWidth); + TypeField stretchHeight = new TypeField(new TypeField[0], "bool", "m_StretchHeight"); + stretchHeight.hasAlignment = true; + children.Add(stretchHeight); + } + break; + case "System.String": + { + isBuiltinType = true; + hasAlignment = false; + TypeField size = new TypeField(new TypeField[0], "int", "size"); + TypeField data = new TypeField(new TypeField[0], "char", "data"); + TypeField array = new TypeField(new TypeField[]{size,data}, "Array", "Array"); + array.isArray = true; array.hasAlignment = true; + children.Add(array); + valueTypeName = "string"; + } + break; + case "UnityEngine.Gradient": + { + isBuiltinType = true; + /* version 1 : + * only 8x ColorRGBA32 key0-key7 (each has unsigned int rgba) + * */ + hasAlignment = false; + valueTypeName = "Gradient"; + version = 2; + TypeField r = new TypeField(new TypeField[0], "float", "r"); + TypeField g = new TypeField(new TypeField[0], "float", "g"); + TypeField b = new TypeField(new TypeField[0], "float", "b"); + TypeField a = new TypeField(new TypeField[0], "float", "a"); + string[] colorNames = { "key0", "key1", "key2", "key3", "key4", "key5", "key6", "key7" }; + for (int i = 0; i < colorNames.Length; i++) + { + TypeField key = new TypeField(new TypeField[] { r, g, b, a }, "ColorRGBA", colorNames[i]); + children.Add(key); + } + string[] ctimeNames = { "ctime0", "ctime1", "ctime2", "ctime3", "ctime4", "ctime5", "ctime6", "ctime7" }; + for (int i = 0; i < ctimeNames.Length; i++) + { + TypeField ctime = new TypeField(new TypeField[0], "UInt16", ctimeNames[i]); + children.Add(ctime); + } + string[] atimeNames = { "atime0", "atime1", "atime2", "atime3", "atime4", "atime5", "atime6", "atime7" }; + for (int i = 0; i < atimeNames.Length; i++) + { + TypeField atime = new TypeField(new TypeField[0], "UInt16", atimeNames[i]); + children.Add(atime); + } + TypeField mode = new TypeField(new TypeField[0], "int", "m_Mode"); + children.Add(mode); + TypeField colorKeyNum = new TypeField(new TypeField[0], "UInt8", "m_NumColorKeys"); + children.Add(colorKeyNum); + TypeField alphaKeyNum = new TypeField(new TypeField[0], "UInt8", "m_NumAlphaKeys"); + children.Add(alphaKeyNum); + + break; + } + /*case "UnityEngine.Bounds": + { + hasAlignment = false; + TypeField centerX = new TypeField(new TypeField[0], "float", "x"); + TypeField centerY = new TypeField(new TypeField[0], "float", "y"); + TypeField centerZ = new TypeField(new TypeField[0], "float", "z"); + TypeField center = new TypeField(new TypeField[]{centerX,centerY,centerZ}, "Vector3f", "m_Center"); + children.Add(center); + TypeField extentsX = new TypeField(new TypeField[0], "float", "x"); + TypeField extentsY = new TypeField(new TypeField[0], "float", "y"); + TypeField extentsZ = new TypeField(new TypeField[0], "float", "z"); + TypeField extents = new TypeField(new TypeField[]{centerX,centerY,centerZ}, "Vector3f", "m_Extents"); + children.Add(extents); + valueTypeName = "Bounds"; + } + break;*/ + default: + valueTypeName = this.type; + //Recursively resolve the base fields (i.e. superclass fields). + { + TypeReference curBase = tDef.BaseType; + if (curBase != null + && !curBase.FullName.Equals("UnityEngine.ScriptableObject") + && !curBase.FullName.Equals("UnityEngine.MonoBehaviour") + && !curBase.FullName.Equals("UnityEngine.Object") + && !curBase.FullName.Equals("System.Object") + && !curBase.Name.Equals("Dictionary`2")) //Scripting::IsSystemCollectionsGenericDictionary + { + TypeReference fullBaseTypeRef = HelperClass.ResolveGenericTypeRefs(type, curBase); + + //TestSelfContain(type, curBase, parents); + TypeField tempField = new TypeField(fullBaseTypeRef, "TempBase", engineVersion, new List(parents), true); + children.InsertRange(0, tempField.children); + } + } + + //SerializePrivateVariables is obsolete but still checked for in Unity (as of 5.6.0f3). + bool doSerializePrivateVariables = false;//tDef.IsValueType; + foreach (CustomAttribute curAttribute in tDef.CustomAttributes) + { + if (curAttribute.AttributeType.FullName.Equals("UnityEngine.SerializePrivateVariables")) + { + doSerializePrivateVariables = true; + break; + } + } + foreach (FieldDefinition field in tDef.Fields) + { + FieldDefinition newField = field; + TypeReference fieldTypeRef = HelperClass.ResolveGenericTypeRefs(type, newField.FieldType); + TypeDefinition fieldType = fieldTypeRef.Resolve(); + + if (fieldType == null) + { + throw new ArgumentException("Unable to resolve the field type!"); + } + bool forceFieldSerialize = false; + foreach (CustomAttribute attr in field.CustomAttributes) + { + if (attr.AttributeType.FullName.Equals("UnityEngine.SerializeField")) + { + forceFieldSerialize = true; + break; + } + } + isObject = HelperClass.BaseTypeComparer("UnityEngine.Object").Execute(fieldType); + if ((!field.IsPublic && !forceFieldSerialize && !doSerializePrivateVariables)) + continue; + if (field.IsNotSerialized) + continue; + if (HelperClass.BaseTypeComparer("System.MulticastDelegate").Execute(fieldType)) + continue; + + string fieldName = newField.Name; + if (!field.IsNotSerialized && !field.IsStatic && !field.IsInitOnly && IsSerializable(fieldTypeRef)) + { + TypeInfo typeInfo = null; + if (!isObject) + { + typeInfo = FindTypeInDatabase(newField.FieldType); + if (typeInfo == null) + { + TypeField baseField = new TypeField(fieldTypeRef, String.Empty, engineVersion); + typeInfo = new TypeInfo(fieldTypeRef); + typeInfo._base = baseField; + typeInfo = AddToDatabase(typeInfo); + } + } + TypeReference[] gargs = new TypeReference[0]; + if (type is GenericInstanceType) + { + gargs = ((GenericInstanceType)type).GenericArguments.ToArray(); + } + + TypeField newChildField; + if (typeInfo != null) + { + newChildField = new TypeField(typeInfo._base.children.ToArray(), typeInfo._base.type, fieldName); + newChildField.hasAlignment = typeInfo._base.hasAlignment; + newChildField.isArray = typeInfo._base.isArray; + newChildField._base = typeInfo._base; + } + else + newChildField = new TypeField(fieldTypeRef, fieldName, engineVersion, new List(parents)); + if (newChildField.size != -1 || newChildField.children.Count != 0) + { + children.Add(newChildField); + } + } + } + break; + } + this.type = valueTypeName; + } + } + else + { + hasAlignment = false; + this.type = "int"; + } + } + if (ownTypeInfo != null) + { + foreach (TypeField instance in ownTypeInfo._instances) + { + instance.children.Clear(); + for (int i = 0; i < this.children.Count; i++) + instance.children.Add(this.children[i]); + } + ownTypeInfo._instances.Clear(); + ownTypeInfo.baseComplete = true; + } + } + public TypeField(FieldDefinition field, EngineVersion engineVersion) : this(field.FieldType, field.Name, engineVersion) + { + } + + public void Dump(int depth, Logger logger, List previouslyDumped = null) + { + if (previouslyDumped == null) + previouslyDumped = new List(); + string logLine = ""; + for (int i = 0; i < depth; i++) + logLine += ' '; + string additionalInfo = ""; + if (this.isArray || this.hasAlignment) + { + additionalInfo += " ("; + if (this.isArray) + additionalInfo += "array, "; + if (this.hasAlignment) + additionalInfo += "aligned, "; + additionalInfo = additionalInfo.Substring(0, additionalInfo.Length-2) + ")"; + } + logLine += this.type + " " + this.name + additionalInfo; + logger.Write(logLine); + + if (previouslyDumped.Contains(this._base)) + return; + previouslyDumped.Add(this._base); + for (int i = 0; i < children.Count; i++) + { + children[i].Dump(depth + 1, logger, new List(previouslyDumped)); + } + } + } +} + diff --git a/UABE_Generic/AppContext.cpp b/UABE_Generic/AppContext.cpp new file mode 100644 index 0000000..d914fd5 --- /dev/null +++ b/UABE_Generic/AppContext.cpp @@ -0,0 +1,833 @@ +#include "AppContext.h" +#include +#include +#include +#include +#include "../libStringConverter/convert.h" + +AppContext::FileOpenTask::FileOpenTask(AppContext *pContext, std::shared_ptr _pReader, bool readerIsModified, const std::string &path, + unsigned int parentFileID, unsigned int directoryEntryIdx, + bool tryAsBundle, bool tryAsAssets, bool tryAsResources, bool tryAsGeneric) : + pContext(pContext), pReader(std::move(_pReader)), readerIsModified(readerIsModified), filePath(path), pFileContext(nullptr), + parentFileID(parentFileID), directoryEntryIdx(directoryEntryIdx), + tryAsBundle(tryAsBundle), tryAsAssets(tryAsAssets), tryAsResources(tryAsResources), tryAsGeneric(tryAsGeneric) +{ + name = "Open file: " + path; +} +void AppContext::FileOpenTask::setParentContextInfo(std::shared_ptr &pContextInfo) +{ + this->pParentContextInfo = pContextInfo; +} +const std::string &AppContext::FileOpenTask::getName() +{ + return name; +} +TaskResult AppContext::FileOpenTask::execute(TaskProgressManager &progressManager) +{ + //TODO (maybe) : make this task cancelable + this->pFileContext = nullptr; + this->bundleOpenStatus = (EBundleFileOpenStatus)0; + this->assetsOpenStatus = (EAssetsFileOpenStatus)0; + if (tryAsBundle) + { + progressManager.setProgressDesc("Opening as a bundle file"); + BundleFileContext *pBundleContext = new BundleFileContext(filePath, pReader, readerIsModified); + EBundleFileOpenStatus bundleOpenStatus = pBundleContext->OpenInsideTask(&progressManager, 0, 200); + if (bundleOpenStatus >= 0 && bundleOpenStatus != BundleFileOpenStatus_Pend) + { + progressManager.setProgress(200, 200); + this->pFileContext = pBundleContext; + this->bundleOpenStatus = bundleOpenStatus; + return 1; + } + delete pBundleContext; + } + if (tryAsAssets) + { + progressManager.setProgressDesc("Opening as a .assets file"); + AssetsFileContext *pAssetsContext = new AssetsFileContext(filePath, pReader, readerIsModified); + EAssetsFileOpenStatus assetsOpenStatus = pAssetsContext->OpenInsideTask(&progressManager, true, 100, 200); + if (assetsOpenStatus >= 0 && assetsOpenStatus != AssetsFileOpenStatus_Pend) + { + progressManager.setProgress(200, 200); + this->pFileContext = pAssetsContext; + this->assetsOpenStatus = assetsOpenStatus; + return 2; + } + delete pAssetsContext; + } + if (tryAsResources/* && ((filePath.size() >= 5 && !filePath.compare(filePath.size() - 5, std::string::npos, ".resS")) + || (filePath.size() >= 9 && !filePath.compare(filePath.size() - 9, std::string::npos, ".resource")) + || (filePath.size() >= 10 && !filePath.compare(filePath.size() - 10, std::string::npos, ".resources")))*/) + { + progressManager.setProgressDesc("Opening as a resources file"); + ResourcesFileContext *pResourcesContext = new ResourcesFileContext(filePath, pReader, readerIsModified); + EResourcesFileOpenStatus resourcesOpenStatus = pResourcesContext->Open(); + if (resourcesOpenStatus >= 0) + { + progressManager.setProgress(200, 200); + this->pFileContext = pResourcesContext; + return 3; + } + delete pResourcesContext; + } + if (tryAsGeneric) + { + progressManager.setProgressDesc("Opening as a generic file"); + GenericFileContext *pGenericContext = new GenericFileContext(filePath, pReader, readerIsModified); + EGenericFileOpenStatus genericOpenStatus = pGenericContext->Open(); + if (genericOpenStatus >= 0) + { + progressManager.setProgress(200, 200); + this->pFileContext = pGenericContext; + return 4; + } + delete pGenericContext; + } + this->pReader.reset(); + return -1; +} + +AppContext::AppContext() + : taskManager(1), maxFileID(0), lastError(0), autoDetectDependencies(true) +{ + taskManager.addCallback(this); +} +AppContext::~AppContext(void) +{ + //contextInfo.clear(); + //contextInfoByFileID.clear(); +} + +void AppContext::OnCompletion(std::shared_ptr &pTask, TaskResult result) +{ + if (std::shared_ptr pFileOpenTask = std::dynamic_pointer_cast(pTask)) + { + switch (result) + { + case -1: + signalMainThread(AppContextMsg_OnFileOpenFail, new std::shared_ptr(pFileOpenTask)); + break; + case 1: //bundle + signalMainThread(AppContextMsg_OnFileOpenAsBundle, new std::shared_ptr(pFileOpenTask)); + break; + case 2: //.assets + signalMainThread(AppContextMsg_OnFileOpenAsAssets, new std::shared_ptr(pFileOpenTask)); + break; + case 3: //resources + signalMainThread(AppContextMsg_OnFileOpenAsResources, new std::shared_ptr(pFileOpenTask)); + break; + case 4: //generic + signalMainThread(AppContextMsg_OnFileOpenAsGeneric, new std::shared_ptr(pFileOpenTask)); + break; + default: assert(false); + } + } + else if (auto pContainersTask = std::dynamic_pointer_cast(pTask)) + { + signalMainThread(AppContextMsg_OnContainersLoaded, new std::shared_ptr(pContainersTask)); + } + else if (auto pDecompressTask = std::dynamic_pointer_cast(pTask)) + { + signalMainThread(AppContextMsg_OnBundleDecompressed, + new std::tuple,TaskResult>(pDecompressTask, result)); + } +} +bool AppContext::processMessage(EAppContextMsg message, void *args) +{ + switch (message) + { + case AppContextMsg_OnFileOpenFail: + { + auto ppTask = (std::shared_ptr*)args; + this->OnFileOpenFail(*ppTask, (*ppTask)->logText); + delete ppTask; + } + return true; + case AppContextMsg_OnFileOpenAsBundle: + { + auto ppTask = (std::shared_ptr*)args; + FileOpenTask *_pTask = (*ppTask).get(); + this->OnFileOpenAsBundle(*ppTask, (BundleFileContext*)_pTask->pFileContext, _pTask->bundleOpenStatus, _pTask->parentFileID, _pTask->directoryEntryIdx); + delete ppTask; + } + return true; + case AppContextMsg_OnFileOpenAsAssets: + { + auto ppTask = (std::shared_ptr*)args; + FileOpenTask *_pTask = (*ppTask).get(); + this->OnFileOpenAsAssets(*ppTask, (AssetsFileContext*)_pTask->pFileContext, _pTask->assetsOpenStatus, _pTask->parentFileID, _pTask->directoryEntryIdx); + delete ppTask; + } + return true; + case AppContextMsg_OnFileOpenAsResources: + { + auto ppTask = (std::shared_ptr*)args; + FileOpenTask *_pTask = (*ppTask).get(); + this->OnFileOpenAsResources(*ppTask, (ResourcesFileContext*)_pTask->pFileContext, _pTask->parentFileID, _pTask->directoryEntryIdx); + delete ppTask; + } + return true; + case AppContextMsg_OnFileOpenAsGeneric: + { + auto ppTask = (std::shared_ptr*)args; + FileOpenTask *_pTask = (*ppTask).get(); + this->OnFileOpenAsGeneric(*ppTask, (GenericFileContext*)_pTask->pFileContext, _pTask->parentFileID, _pTask->directoryEntryIdx); + delete ppTask; + } + return true; + case AppContextMsg_OnContainersLoaded: + { + auto ppTask = (std::shared_ptr*)args; + OnGenerateContainers((*ppTask)->getFileContextInfo()); + delete ppTask; + } + return true; + case AppContextMsg_OnBundleDecompressed: + { + auto *pInfo = (std::tuple,TaskResult>*)args; + auto pTask = std::get<0>(*pInfo); + OnDecompressBundle(pTask.get(), std::get<1>(*pInfo)); + delete pInfo; + } + return true; + case AppContextMsg_OnAssetChanged: + { + auto *pInfo = (std::tuple*)args; + std::shared_lock contextInfoMapLock(this->contextInfoMapMutex); + auto contextInfoIt = contextInfoByFileID.find(std::get<0>(*pInfo)); + if (contextInfoIt != contextInfoByFileID.end()) + { + std::shared_ptr pFile = std::dynamic_pointer_cast(contextInfoIt->second); + contextInfoMapLock.unlock(); + assert(pFile); + if (pFile) + OnChangeAsset(pFile.get(), std::get<1>(*pInfo), std::get<2>(*pInfo)); + } + delete pInfo; + } + return true; + case AppContextMsg_DoMainThreadCallback: + { + auto *pInfo = (std::tuple*)args; + std::get<0>(*pInfo)(std::get<1>(*pInfo), std::get<2>(*pInfo)); + delete pInfo; + } + return true; + case AppContextMsg_OnBundleEntryChanged: + { + auto *pInfo = (std::tuple*)args; + std::shared_lock contextInfoMapLock(this->contextInfoMapMutex); + auto contextInfoIt = contextInfoByFileID.find(std::get<0>(*pInfo)); + if (contextInfoIt != contextInfoByFileID.end()) + { + std::shared_ptr pFile = std::dynamic_pointer_cast(contextInfoIt->second); + contextInfoMapLock.unlock(); + assert(pFile); + if (pFile) + OnChangeBundleEntry(pFile.get(), std::get<1>(*pInfo)); + } + else + contextInfoMapLock.unlock(); + delete pInfo; + } + return true; + default: +#ifdef _DEBUG + assert(false); +#endif + return false; + } +} +void AppContext::OnDecompressBundle(BundleFileContextInfo::DecompressTask *pTask, TaskResult result) +{} +std::shared_ptr AppContext::OnFileOpenAsBundle(std::shared_ptr pTask, BundleFileContext *pContext, EBundleFileOpenStatus openStatus, unsigned int parentFileID, unsigned int directoryEntryIdx) +{ + BundleFileContextInfo *pBundleInfo = new BundleFileContextInfo(pContext, 0, parentFileID); + std::shared_ptr pInfo(pBundleInfo); + + //Carry on the VisibleFileEntry from the FileOpenTask. + //-> The bundle file itself applies the replacer modifications + pBundleInfo->modificationsToApply = std::move(pTask->modificationsToApply); + + AddContextInfo(pInfo, directoryEntryIdx); + return pInfo; +} +std::shared_ptr AppContext::OnFileOpenAsAssets(std::shared_ptr pTask, AssetsFileContext *pContext, EAssetsFileOpenStatus openStatus, unsigned int parentFileID, unsigned int directoryEntryIdx) +{ + AssetsFileContextInfo *pAssetsInfo = new AssetsFileContextInfo(pContext, 0, parentFileID); + std::shared_ptr pInfo(pAssetsInfo); + + //Apply all AssetsReplacers for this file from a VisibleFileEntry, if existent. + for (size_t i = 0; pTask->modificationsToApply && i < pTask->modificationsToApply->replacers.size(); ++i) + { + std::shared_ptr &pGenericReplacer = pTask->modificationsToApply->replacers[i].pReplacer; + if (std::shared_ptr pAssetsReplacer = std::dynamic_pointer_cast(pGenericReplacer)) + { + if (pAssetsReplacer->GetType() == AssetsReplacement_AddOrModify + || pAssetsReplacer->GetType() == AssetsReplacement_Remove) + { + pAssetsInfo->addReplacer(std::reinterpret_pointer_cast(pAssetsReplacer), *this, true, false); + } + else if (pAssetsReplacer->GetType() == AssetsReplacement_Dependencies) + { + AssetsDependenciesReplacer *pReplacer = reinterpret_cast(pAssetsReplacer.get()); + auto refLock = pAssetsInfo->lockReferencesWrite(); + const std::vector &dependencies = pReplacer->GetDependencies(); + pAssetsInfo->getDependenciesWrite(refLock) = dependencies; + pAssetsInfo->getReferencesWrite(refLock).clear(); + pAssetsInfo->getReferencesWrite(refLock).resize(dependencies.size(), 0); + pAssetsInfo->setDependenciesChanged(); + } + } + } + + AddContextInfo(pInfo, directoryEntryIdx); + return pInfo; +} +std::shared_ptr AppContext::OnFileOpenAsResources(std::shared_ptr pTask, ResourcesFileContext *pContext, unsigned int parentFileID, unsigned int directoryEntryIdx) +{ + ResourcesFileContextInfo *pResourcesInfo = new ResourcesFileContextInfo(pContext, 0, parentFileID); + std::shared_ptr pInfo(pResourcesInfo); + + //Apply the BundleEntryModifierByResources for this file from a VisibleFileEntry, if existent. + if (pTask->modificationsToApply && pTask->modificationsToApply->replacers.size() == 1 + && dynamic_cast(pTask->modificationsToApply->replacers[0].pReplacer.get()) != nullptr) + { + std::shared_ptr pGenericReplacer = pTask->modificationsToApply->replacers[0].pReplacer; + bool result = pResourcesInfo->setByReplacer(*this, reinterpret_cast(pGenericReplacer.get())); + assert(result); + } + + AddContextInfo(pInfo, directoryEntryIdx); + return pInfo; +} +std::shared_ptr AppContext::OnFileOpenAsGeneric(std::shared_ptr pTask, GenericFileContext *pContext, unsigned int parentFileID, unsigned int directoryEntryIdx) +{ + GenericFileContextInfo *pGenericInfo = new GenericFileContextInfo(pContext, 0, parentFileID); + std::shared_ptr pInfo(pGenericInfo); + + AddContextInfo(pInfo, directoryEntryIdx); + return pInfo; +} +void AppContext::OnFileOpenFail(std::shared_ptr pTask, std::string &logText) +{ +} +std::shared_ptr AppContext::CreateFileOpenTask(const std::string &path, bool basedOnExistingFile) +{ + std::string actualPath = path; + std::shared_ptr pReader; + if (actualPath.size() >= 7 && !actualPath.compare(actualPath.size() - 7, std::string::npos, ".split0")) + { + actualPath = actualPath.substr(0, actualPath.size() - 7); + pReader = std::shared_ptr( + Create_AssetsReaderFromSplitFile(actualPath.c_str(), true, false, RWOpenFlags_Immediately), + Free_AssetsReader); + } + else + { + pReader = std::shared_ptr( + Create_AssetsReaderFromFile(actualPath.c_str(), true, RWOpenFlags_Immediately), + Free_AssetsReader); + if (!pReader) + { + std::string splitPath = path + ".split0"; + pReader = std::shared_ptr( + Create_AssetsReaderFromSplitFile(splitPath.c_str(), true, false, RWOpenFlags_Immediately), + Free_AssetsReader); + } + } + if (!pReader && !basedOnExistingFile) + { + //Create an empty reader. + pReader = std::shared_ptr( + Create_AssetsReaderFromMemory(nullptr, 0, false, nullptr), + Free_AssetsReader); + } + if (!pReader) + { + lastError = AppContextErr_FileNotFound; + return nullptr; + } + bool tryAsResources = false; + if ((actualPath.size() >= 5 && !strnicmp(&path.data()[path.size() - 5], ".ress", 5)) + || (actualPath.size() >= 10 && !strnicmp(&path.data()[path.size() - 10], ".resources", 10)) + || (actualPath.size() >= 9 && !strnicmp(&path.data()[path.size() - 9], ".resource", 9))) + tryAsResources = true; + return std::shared_ptr(new FileOpenTask(this, std::move(pReader), false, path, 0, 0, true, true, tryAsResources, false)); +} +std::shared_ptr AppContext::CreateBundleEntryOpenTask(std::shared_ptr &pBundleContextInfo, unsigned int directoryEntryIdx) +{ + { + BundleFileContext *pBundleContext = pBundleContextInfo->getBundleFileContext(); + std::string entryName = pBundleContextInfo->getNewEntryName(directoryEntryIdx); + //pBundleContextInfo->modificationsToApply.replacers + bool readerIsModified = false; + std::shared_ptr pChildReader = pBundleContextInfo->makeEntryReader(directoryEntryIdx, readerIsModified); + if (!pChildReader) + return nullptr; + bool tryAsResources = false; + if ((entryName.size() >= 5 && !entryName.compare(entryName.size() - 5, std::string::npos, ".resS")) + || (entryName.size() >= 9 && !entryName.compare(entryName.size() - 9, std::string::npos, ".resource")) + || (entryName.size() >= 10 && !entryName.compare(entryName.size() - 10, std::string::npos, ".resources"))) + tryAsResources = true; + FileOpenTask *ret = new FileOpenTask(this, std::move(pChildReader), readerIsModified, entryName, pBundleContextInfo->getFileID(), directoryEntryIdx, true, true, tryAsResources, true); + ret->setParentContextInfo(pBundleContextInfo); + + if (pBundleContextInfo->modificationsToApply != nullptr) + { + //If we have a matching subFile in the VisibleFileEntry, move it to the entry open task. + // -> Each subFile entry is generated by parsing a BundleReplacer based on an existing file: + // => BundleEntryModifierFromAssets + // => BundleEntryModifierFromBundle + // => BundleEntryModifierByResources if at least one resource has fromOriginalFile set to true. + // -> Note: BundleFileContextInfo::onDirectoryReady handles BundleEntryModifierByResources replacer entries + // by creating a bundle directory entry with an empty reader + // and then adding a new subFile entry to modificationsToApply with the same replacer. + //TODO: Put some faster name lookup table in the BundleFileContextInfo (running this loop for every bundle entry -> O(N²) time). + for (size_t _i = pBundleContextInfo->modificationsToApply->subFiles.size(); _i > 0; --_i) + { + size_t i = _i - 1; + VisibleFileEntry &subFile = pBundleContextInfo->modificationsToApply->subFiles[i]; + if (!stricmp(entryName.c_str(), subFile.pathNull ? subFile.newName.c_str() : subFile.pathOrName.c_str())) + { + this->OpenTask_SetModifications(ret, std::make_unique(std::move(subFile))); + pBundleContextInfo->modificationsToApply->subFiles.erase(pBundleContextInfo->modificationsToApply->subFiles.begin() + i); + } + } + } + return std::shared_ptr(ret); + } + return nullptr; +} +void AppContext::OpenTask_SetModifications(ITask *pTask, std::unique_ptr modificationsToApply) +{ + FileOpenTask *pFileOpenTask = dynamic_cast(pTask); + if (pFileOpenTask == nullptr) + throw std::invalid_argument("OpenTask_SetModifications: pTask is not a FileOpenTask."); + pFileOpenTask->modificationsToApply = std::move(modificationsToApply); +} +static const char *getDependencyFileName(const char *dependency) +{ + const char *fileName = dependency; + //if (!strncmp(dependency, "archive:/", 9)) + // fileName = &dependency[9]; + const char *subFileName = strrchr(fileName, '/'); + if (subFileName != nullptr) + fileName = subFileName + 1; + return fileName; +} +unsigned int AppContext::TryResolveDependency(AssetsFileContextInfo* pFileFrom, const AssetsFileDependency& dependency, bool allowSeveral) +{ + if (dependency.type != 0) + return 0; + const char* depFileName = getDependencyFileName(dependency.assetPath); + bool hasSeveralCandidates = false; + size_t candidateIdx = (size_t)-1; + for (size_t k = 0; k < contextInfo.size(); k++) + { + IFileContext* pFileContext = contextInfo[k]->getFileContext(); + if (pFileContext->getType() == FileContext_Assets && + !stricmp(depFileName, contextInfo[k]->getFileName().c_str())) + { + if (candidateIdx != (size_t)-1) + { + hasSeveralCandidates = true; + break; + } + candidateIdx = k; + } + } + //Only resolve the dependency if there is exactly one match. + if (candidateIdx != (size_t)-1 && (!hasSeveralCandidates || allowSeveral)) + { + return contextInfo[candidateIdx]->getFileID(); + } + return 0; +} +bool AppContext::AddContextInfo(std::shared_ptr &info, unsigned int directoryEntryIdx) +{ + std::vector> dependencyCallbackArgs; //Receives arguments for OnUpdateDependencies calls. + bool ret = true; + info->fileID = ++maxFileID; + if (autoDetectDependencies && info->getFileContext() && info->getFileContext()->getType() == FileContext_Assets + && static_cast(info->getFileContext())->getAssetsFile()) + { + if (AssetsFileContextInfo *pNewAssetsInfo = dynamic_cast(info.get())) + { + auto refLock = pNewAssetsInfo->lockReferencesWrite(); + std::vector &references = pNewAssetsInfo->getReferencesWrite(refLock); + const std::vector &dependencies = pNewAssetsInfo->getDependenciesWrite(refLock); + assert(references.size() == dependencies.size()); + //Resolve references for the newly loaded file. + for (size_t i = 0; i < dependencies.size(); i++) + { + //For each dependency, look for a previously loaded file that matches the name. + if (i >= references.size()) + break; + const AssetsFileDependency *pDependency = &dependencies[i]; + references[i] = TryResolveDependency(pNewAssetsInfo, dependencies[i], false); + } + refLock.unlock(); + + //Resolve references for previously loaded files. + std::string newFileName = info->getFileName(); + for (size_t iContext = 0; iContext < contextInfo.size(); iContext++) + { + //For each loaded Assets file context, check if the new file resolves a missing dependency. + IFileContext *pPrevFileContext = contextInfo[iContext]->getFileContext(); + if (pPrevFileContext->getType() != FileContext_Assets + || !static_cast(pPrevFileContext)->getAssetsFile()) + continue; + if (AssetsFileContextInfo *pPrevAssetsInfo = dynamic_cast(contextInfo[iContext].get())) + { + auto prevRefLock = pPrevAssetsInfo->lockReferencesWrite(); + std::vector &prevReferences = pPrevAssetsInfo->getReferencesWrite(prevRefLock); + const std::vector &prevDependencies = pPrevAssetsInfo->getDependenciesWrite(prevRefLock); + assert(prevReferences.size() == prevDependencies.size()); + for (size_t iRef = 0; iRef < prevReferences.size(); iRef++) + { + if (iRef >= prevDependencies.size()) + break; + const AssetsFileDependency *pDependency = &prevDependencies[iRef]; + if (pDependency->type != 0) + continue; + bool issueDependencyCallback = false; + if (prevReferences[iRef] != 0) + { + //Only update references that aren't set already. + if (contextInfoByFileID.find(prevReferences[iRef]) == contextInfoByFileID.end()) + { + //References may be set to a closed file. In that case, treat it like it isn't set. + prevReferences[iRef] = 0; //While we're at it, set it to 0. + issueDependencyCallback = true; + } + else + continue; + } + bool solvesReference = false; + const char *depFileName = getDependencyFileName(pDependency->assetPath); + if (!stricmp(newFileName.c_str(), depFileName)) + { + prevReferences[iRef] = info->fileID; //Assign the new file as a dependency. + + //Mark the dependant file as a container source, if it has any containers. + AssetContainerList &prevContainers = pPrevAssetsInfo->lockContainersRead(); + if (prevContainers.getContainerCount() > 0) + pNewAssetsInfo->getContainerSources().push_back(pPrevAssetsInfo->getFileID()); + pPrevAssetsInfo->unlockContainersRead(); + + issueDependencyCallback = true; + solvesReference = true; + } + if (issueDependencyCallback) + dependencyCallbackArgs.push_back(std::make_pair(pPrevAssetsInfo, iRef)); + if (solvesReference) + break; //Each assets file should only have one entry per dependency + } + prevRefLock.unlock(); + } + } + } + } + contextInfo.push_back(info); + std::unique_lock contextInfoMapLock(this->contextInfoMapMutex); + if (info->parentFileID == 0) + { + contextInfoByFileID[info->fileID] = info; + if (info->getFileContext()) + { + info->lastFileName = info->getFileName(); + contextInfoByFileName.insert({ info->lastFileName, info }); + } + contextInfoMapLock.unlock(); + } + else + { + auto parentIt = contextInfoByFileID.find(info->parentFileID); + if (parentIt != contextInfoByFileID.end()) + { + //contextInfo still keeps a FileContextInfo reference for the main thread. + FileContextInfo *pContextInfo = parentIt->second.get(); + contextInfoByFileID[info->fileID] = info; + if (info->getFileContext()) + { + info->lastFileName = info->getFileName(); + contextInfoByFileName.insert({ info->lastFileName, info }); + } + contextInfoMapLock.unlock(); + if (pContextInfo->getFileContext()->getType() == FileContext_Bundle) + { + BundleFileContextInfo *pBundleContextInfo = (BundleFileContextInfo*)pContextInfo; + contextInfoMapLock.lock(); + if (directoryEntryIdx < pBundleContextInfo->directoryRefs.size()) + { + pBundleContextInfo->directoryRefs[directoryEntryIdx].fileID = info->fileID; + } + else + assert(false); + contextInfoMapLock.unlock(); + } + else + assert(false); + } + else + { + //Parent file has closed already. + return false; + } + } + assert(!contextInfoMapLock.owns_lock()); + //Call the dependency callbacks after the file has been registered properly. + for (auto it = dependencyCallbackArgs.begin(); it != dependencyCallbackArgs.end(); ++it) + this->OnUpdateDependencies(it->first, it->second, it->second); + return true; +} +void AppContext::RemoveContextInfo(FileContextInfo *info) +{ + std::unique_lock contextInfoMapLock(this->contextInfoMapMutex); + if (info->parentFileID != 0) + { + auto parentIt = contextInfoByFileID.find(info->parentFileID); + if (parentIt != contextInfoByFileID.end()) + { + parentIt->second->onCloseChild(info->getFileID()); + } + } + contextInfoByFileID.erase(info->getFileID()); + auto fileMapEntryIt = contextInfoByFileName.end(); + if (info->getFileContext()) + { + auto rangeItPair = contextInfoByFileName.equal_range(info->lastFileName); + for (auto it = rangeItPair.first; it != rangeItPair.second; ++it) + { + if (it->second.get() == info) + { + fileMapEntryIt = it; + break; + } + } + } + if (fileMapEntryIt == contextInfoByFileName.end()) + { + for (auto it = contextInfoByFileName.begin(); it != contextInfoByFileName.end(); ++it) + { + if (it->second.get() == info) + { + fileMapEntryIt = it; + break; + } + } + } + if (fileMapEntryIt != contextInfoByFileName.end()) + contextInfoByFileName.erase(fileMapEntryIt); + contextInfoMapLock.unlock(); + for (size_t i = 0; i < contextInfo.size(); i++) + { + if (contextInfo[i].get() == info) + { + contextInfo.erase(contextInfo.begin() + i); + break; + } + } +} +void AppContext::OnGenerateContainers(AssetsFileContextInfo *info) +{ + OnUpdateContainers(info); + const std::vector references = info->getReferences(); + for (size_t i = 0; i < references.size(); i++) + { + std::shared_lock contextInfoMapLock(this->contextInfoMapMutex); + auto ref = contextInfoByFileID.find(references[i]); + FileContextInfo *pContextInfo = nullptr; + if (ref != contextInfoByFileID.end()) + pContextInfo = ref->second.get(); + contextInfoMapLock.unlock(); + if (pContextInfo && pContextInfo->getFileContext()->getType() == FileContext_Assets) + { + AssetsFileContextInfo *pTarget = static_cast(pContextInfo); + bool addAsSource = true; + for (size_t k = 0; k < pTarget->containerSources.size(); k++) + { + if (pTarget->containerSources[k] == info->getFileID()) + { + addAsSource = false; + break; + } + } + if (addAsSource) + pTarget->containerSources.push_back(info->getFileID()); + OnUpdateContainers(pTarget); + } + } +} +void AppContext::OnChangeAsset_Async(AssetsFileContextInfo *info, pathid_t pathID, bool removed) +{ + signalMainThread(AppContextMsg_OnAssetChanged, new std::tuple(info->getFileID(), pathID, removed)); +} +void AppContext::OnChangeBundleEntry_Async(BundleFileContextInfo *info, size_t index) +{ + signalMainThread(AppContextMsg_OnBundleEntryChanged, new std::tuple(info->getFileID(), index)); +} +FileContextInfo_ptr AppContext::getContextInfo(unsigned int fileID) +{ + if (fileID == 0) + return nullptr; + std::shared_ptr ret(nullptr); + std::shared_lock contextInfoMapLock(this->contextInfoMapMutex); + auto ref = contextInfoByFileID.find(fileID); + if (ref != contextInfoByFileID.end()) + ret = ref->second; + return ret; +} +std::vector AppContext::getContextInfo(const std::string& relFileName, FileContextInfo* pFileFrom) +{ + if (relFileName.starts_with("archive:/")) + { + size_t lastSlashPos = relFileName.rfind('/', std::string::npos); + if (lastSlashPos == std::string::npos || lastSlashPos == 0) + { + assert(false); //"archive:/" contains a '/' + return {}; + } + //Find all candidates (file name match). + std::vector ret_unfiltered = getContextInfo(relFileName.substr(lastSlashPos + 1)); + std::vector ret; + for (size_t i = 0; i < ret_unfiltered.size(); ++i) + { + FileContextInfo_ptr pCurFile = ret_unfiltered[i]; + size_t cutoffPos = lastSlashPos; + size_t searchPos = lastSlashPos; + bool success = true; + while ((searchPos = relFileName.rfind('/', searchPos - 1)) != std::string::npos && searchPos != 0) + { + unsigned int parentFileID = pCurFile->getParentFileID(); + FileContextInfo_ptr pParentFile; + if (parentFileID == 0 + || (pParentFile = getContextInfo(parentFileID)) == nullptr) + { + success = false; + break; + } + std::string parentFileName; + if (pParentFile->getFileContext() && pParentFile->getFileContext()->getType() == FileContext_Bundle) + { + //Bundle name is determined based on its first directory entry. + auto pBundleParent = reinterpret_cast(pParentFile.get()); + parentFileName = pBundleParent->getBundlePathName(); + } + else + { + //Shouldn't normally happen (Unity appears to use "archive:/" references only for bundled files). + parentFileName = pParentFile->getFileName(); + } + //Weird error in Debug: "cannot seek string iterator because the iterator was invalidated" when incrementing the string iterator? + //-> string::data() should do for now. + if (!std::equal(relFileName.data() + (searchPos + 1), relFileName.data() + cutoffPos, parentFileName.begin(), parentFileName.end())) + { + success = false; + break; + } + + cutoffPos = searchPos; + } + if (success) + ret.push_back(std::move(ret_unfiltered[i])); + } + return ret; + } + std::shared_lock contextInfoMapLock(this->contextInfoMapMutex); + std::vector candidates; + auto range = contextInfoByFileName.equal_range(relFileName); + //Only place the value (i.e. second) of each contextInfoByFileName iterator in ret. + std::transform(range.first, range.second, std::back_inserter(candidates), [](auto x) {return x.second; }); + + if (pFileFrom == nullptr || pFileFrom->getFileContext() == nullptr) + return candidates; + + //Search relative to a given file context. + if (candidates.empty()) + return {}; + const std::string& fromFilePathStr = pFileFrom->getFileContext()->getFilePath(); + //char8_t: Interpret string as UTF-8 even on Win32. + std::filesystem::path fromFilePath( + reinterpret_cast(&fromFilePathStr.data()[0]), + reinterpret_cast(&fromFilePathStr.data()[fromFilePathStr.size()])); + std::vector ret; + for (size_t i = 0; i < candidates.size(); ++i) + { + if (candidates[i]->getParentFileID() != 0 + || candidates[i]->getFileContext() == nullptr) + continue; + const std::string& candidateFilePathStr = candidates[i]->getFileContext()->getFilePath(); + std::filesystem::path candidateFilePath( + reinterpret_cast(&candidateFilePathStr.data()[0]), + reinterpret_cast(&candidateFilePathStr.data()[candidateFilePathStr.size()])); + if (std::filesystem::equivalent(fromFilePath.parent_path(), candidateFilePath.parent_path())) + ret.push_back(std::move(candidates[i])); + } + return ret; +} +void AppContext::OnUpdateContainers(AssetsFileContextInfo *info) {} +void AppContext::OnUpdateDependencies(AssetsFileContextInfo *info, size_t from, size_t to) {} //from/to: indices for info->references +void AppContext::OnChangeAsset(AssetsFileContextInfo *pFile, pathid_t pathID, bool wasRemoved) {} +void AppContext::OnChangeBundleEntry(BundleFileContextInfo *pFile, size_t index) +{ + // Locate the opened child file (if present). + std::vector childFileIDs; + pFile->getChildFileIDs(childFileIDs); + FileContextInfo_ptr pChildInfo = nullptr; + if (childFileIDs.size() > index && childFileIDs[index] != 0 + && (pChildInfo = getContextInfo(childFileIDs[index]))) + { + auto newFileName = pChildInfo->getFileName(); + bool nameChanged = newFileName != pChildInfo->lastFileName; + std::shared_lock contextInfoMapLock(this->contextInfoMapMutex); + if (nameChanged) + { + //Erase the existing entry for the renamed file. + auto range = contextInfoByFileName.equal_range(pChildInfo->lastFileName); + for (auto it = range.first; it != range.second; ++it) + { + if (it->second.get() == pChildInfo.get()) + { + contextInfoByFileName.erase(it); + break; + } + } + } + if (nameChanged) + { + //Insert a new entry for the renamed file. + pChildInfo->lastFileName = std::move(newFileName); + if (!pChildInfo->lastFileName.empty()) + contextInfoByFileName.insert({ pChildInfo->lastFileName, pChildInfo }); + } + } +} + +bool AppContext::LoadClassDatabasePackage(const std::string &appBaseDir, std::string &errorMessage) +{ + bool ret = true; + IAssetsReader *pDatabaseFileReader = Create_AssetsReaderFromFile("classdata.tpk", true, RWOpenFlags_Immediately); + if (pDatabaseFileReader == NULL) + { + std::string targetDir = appBaseDir + "classdata.tpk"; + pDatabaseFileReader = Create_AssetsReaderFromFile(targetDir.c_str(), true, RWOpenFlags_Immediately); + } + if (pDatabaseFileReader != NULL) + { + if (!classPackage.Read(pDatabaseFileReader)) + { + ret = false; + errorMessage = "Invalid type database package!"; + } + Free_AssetsReader(pDatabaseFileReader); + } + else + { + ret = false; + errorMessage = "Unable to open the class database package file!"; + } + return ret; +} diff --git a/UABE_Generic/AppContext.h b/UABE_Generic/AppContext.h new file mode 100644 index 0000000..e56437b --- /dev/null +++ b/UABE_Generic/AppContext.h @@ -0,0 +1,201 @@ +#pragma once +#include "api.h" +#include "FileContext.h" +#include "AssetContainerList.h" +#include "FileContextInfo.h" +#include "AssetIterator.h" +#include "FileModTree.h" +#include "IAssetBatchImportDesc.h" +#include "PluginManager.h" +#include +#include +#include +#include +#include + +enum EAppContextMsg +{ + AppContextMsg_OnFileOpenFail, //args: std::shared_ptr* + AppContextMsg_OnFileOpenAsBundle, //args: std::shared_ptr* + AppContextMsg_OnFileOpenAsAssets, //args: std::shared_ptr* + AppContextMsg_OnFileOpenAsResources, //args: std::shared_ptr* + AppContextMsg_OnFileOpenAsGeneric, //args: std::shared_ptr* + AppContextMsg_OnContainersLoaded, //args : std::shared_ptr* + AppContextMsg_OnBundleDecompressed, //args : std::tuple,TaskResult>* + AppContextMsg_OnAssetChanged, //args : std::tuple* (file ID; path ID; was removed?). + AppContextMsg_DoMainThreadCallback, //args : std::tuple* (callback; param1; param2), created with operator new. + AppContextMsg_OnBundleEntryChanged, //args : std::tuple* (file ID; entry index). + AppContextMsg_COUNT +}; + +#define AppContextErr_FileNotFound 1 +class AppContext : TaskProgressCallback +{ + std::unordered_map> contextInfoByFileID; + std::unordered_multimap> contextInfoByFileName; + std::shared_mutex contextInfoMapMutex; + +protected: + PluginMapping plugins; + + class FileOpenTask : public ITask + { + std::shared_ptr pParentContextInfo; //Only to maintain object lifetime (bundled files only). + + AppContext *pContext; + std::shared_ptr pReader; bool readerIsModified; + std::string filePath; + std::string name; + bool tryAsBundle, tryAsAssets, tryAsResources, tryAsGeneric; + public: + EBundleFileOpenStatus bundleOpenStatus; + EAssetsFileOpenStatus assetsOpenStatus; + IFileContext *pFileContext; + std::string logText; + unsigned int parentFileID, directoryEntryIdx; //For bundled files + std::unique_ptr modificationsToApply; //Just carried by the task, so the open result event handler can use it later on. + UABE_Generic_API FileOpenTask(AppContext *pContext, std::shared_ptr pReader, bool readerIsModified, const std::string &path, + unsigned int parentFileID = 0, unsigned int directoryEntryIdx = 0, + bool tryAsBundle=true, bool tryAsAssets=true, bool tryAsResources=true, bool tryAsGeneric=false); + UABE_Generic_API void setParentContextInfo(std::shared_ptr &pParentContextInfo); + UABE_Generic_API const std::string &getName(); + UABE_Generic_API TaskResult execute(TaskProgressManager &progressManager); + }; + + UABE_Generic_API void OnCompletion(std::shared_ptr &pTask, TaskResult result); + UABE_Generic_API void OnGenerateContainers(AssetsFileContextInfo *info); + UABE_Generic_API void OnChangeAsset_Async(AssetsFileContextInfo *info, pathid_t pathID, bool removed); + UABE_Generic_API void OnChangeBundleEntry_Async(BundleFileContextInfo *info, size_t index); + + template + requires std::invocable + static inline void mainThreadCallbackDispatcher(uintptr_t param1, uintptr_t param2) + { + if (param1 != 0) + { + std::unique_ptr pCallable(reinterpret_cast(param1)); + (*pCallable)(param2); + } + } + template + requires std::invocable + static inline void mainThreadCallbackDispatcher(uintptr_t param1, uintptr_t param2) + { + if (param1 != 0) + { + std::unique_ptr pCallable(reinterpret_cast(param1)); + (*pCallable)(); + } + } +public: + TaskManager taskManager; + //Passes a message to the main thread. On Win32, PostMessage can be used; for console, custom synchronization routines are required. + UABE_Generic_API virtual void signalMainThread(EAppContextMsg message, void *args)=0; + + //Uses the signalMainThread mechanism to post a callback request on the main thread. + //No guarantees are made regarding the callback timing. + template + requires (std::invocable || std::invocable) && std::copy_constructible + inline void postMainThreadCallback(TCallable _callable, uintptr_t param = 0) + { + signalMainThread(AppContextMsg_DoMainThreadCallback, + new std::tuple{ + &mainThreadCallbackDispatcher, reinterpret_cast(new TCallable(std::move(_callable))), param}); + } +protected: + ClassDatabasePackage classPackage; //Do not change during runtime. + std::vector> contextInfo; //Only use on the main thread. + unsigned int maxFileID; + unsigned int lastError; + bool autoDetectDependencies; //Automatically assign the references of newly loaded and existing AssetsFileContextInfo. + + //Processes a message. Must be called by the main thread after a corresponding signalMainThread call. + UABE_Generic_API virtual bool processMessage(EAppContextMsg message, void *args); + + //The following functions are called by processMessage. + //FileOpenTask / BundleEntryOpenTask done + UABE_Generic_API virtual std::shared_ptr OnFileOpenAsBundle(std::shared_ptr pTask, BundleFileContext *pContext, EBundleFileOpenStatus openStatus, unsigned int parentFileID, unsigned int directoryEntryIdx); + UABE_Generic_API virtual std::shared_ptr OnFileOpenAsAssets(std::shared_ptr pTask, AssetsFileContext *pContext, EAssetsFileOpenStatus openStatus, unsigned int parentFileID, unsigned int directoryEntryIdx); + UABE_Generic_API virtual std::shared_ptr OnFileOpenAsResources(std::shared_ptr pTask, ResourcesFileContext *pContext, unsigned int parentFileID, unsigned int directoryEntryIdx); + UABE_Generic_API virtual std::shared_ptr OnFileOpenAsGeneric(std::shared_ptr pTask, GenericFileContext *pContext, unsigned int parentFileID, unsigned int directoryEntryIdx); + UABE_Generic_API virtual void OnFileOpenFail(std::shared_ptr pTask, std::string &logText); + //AssetsFileContextInfo: ContainersTask done. First called for the assets file the ContainersTask was created for, and then for all dependencies of that assets file. + public: UABE_Generic_API virtual void OnUpdateContainers(AssetsFileContextInfo *info); + //Called when an AssetsFileContextInfo reference has been resolved, or when the file name of an AssetsFile dependency entry has changed. + //Is not called for newly loaded files that have some of their dependencies resolved immediately. + UABE_Generic_API virtual void OnUpdateDependencies(AssetsFileContextInfo *info, size_t from, size_t to); //from/to: indices for info->references +protected: + //BundleFileContextInfo: DecompressTask done or failed. + UABE_Generic_API virtual void OnDecompressBundle(BundleFileContextInfo::DecompressTask *pTask, TaskResult result); + //Called whenever a replacer was added (adding, modifying or removing an asset). The caller holds a reference to pFile. + UABE_Generic_API virtual void OnChangeAsset(AssetsFileContextInfo *pFile, pathid_t pathID, bool wasRemoved); + //Called whenever a bundle entry was changed (renamed, reader overridden, removed). The caller holds a reference to pFile. + UABE_Generic_API virtual void OnChangeBundleEntry(BundleFileContextInfo *pFile, size_t index); + + //Load the class database package. appBaseDir is the directory where UABE is located with a trailing path separator. + //Should be called early on, i.e. before any file contexts are opened. + //Can be overridden to override default loading behaviour (look for classdata.tpk in current dir and then in appBaseDir). + UABE_Generic_API virtual bool LoadClassDatabasePackage(const std::string &appBaseDir, std::string &errorMessage); + + //When inheriting this function, call AppContext::AddContextInfo() first to initialize the fileID (and to insert it into the AppContext lists)! + UABE_Generic_API virtual bool AddContextInfo(std::shared_ptr &info, unsigned int directoryEntryIdx); + UABE_Generic_API virtual void RemoveContextInfo(FileContextInfo *info); + + //Call from the main (e.g. UI) thread only. + UABE_Generic_API std::shared_ptr CreateBundleEntryOpenTask(std::shared_ptr &pBundleContextInfo, unsigned int directoryEntryIdx); +public: + //Call from the main (e.g. UI) thread only. + //Tries to find a fileID that matches the dependency from the given file. + //If allowSeveral is set, the function also succeeds if there are several matches (specifically, it returns the first result). + UABE_Generic_API unsigned int TryResolveDependency(AssetsFileContextInfo* pFileFrom, const AssetsFileDependency &dependency, bool allowSeveral); + + //Call from the main (e.g. UI) thread only. + //basedOnExistingFile: If false and the file cannot be opened, open a 'zero length reader' instead of failing. + UABE_Generic_API std::shared_ptr CreateFileOpenTask(const std::string &path, bool basedOnExistingFile = true); + UABE_Generic_API void OpenTask_SetModifications(ITask *pTask, std::unique_ptr modificationsToApply); + + UABE_Generic_API FileContextInfo_ptr getContextInfo(unsigned int fileID); + //Finds all matching loaded files by their name, optionally filtering by the location of another file. + //Also supports "archive://" paths. + //Otherwise, assumes that relFileName is a simple file name without directory separators. + // Used to resolve resource file references. + // Example: Two resource files with the same name are loaded: + // - GameA_Data/sharedassets0.resources + // - GameB_Data/sharedassets0.resources + // Now, a reference from within GameB_Data/sharedassets0.assets (pFileFrom) to sharedassets0.resources is to be resolved. + // The result should be just the file context for GameB_Data/sharedassets0.resources. + // If the caller set pFileFrom to nullptr, both resources file contexts will be returned. + UABE_Generic_API std::vector getContextInfo(const std::string &relFileName, FileContextInfo *pFileFrom = nullptr); + + UABE_Generic_API virtual bool ShowAssetBatchImportDialog(IAssetBatchImportDesc* pDesc, std::string basePath)=0; + + //Asks the user to provide an export file or directory path. An empty string signals cancelling the action. + // -> If assets.size() == 0, returns an empty string. + // -> If assets.size() == 1, the user is asked to select one output file path. + // -> If assets.size() > 1, the user is asked to select an output directory. + //Assumes that all AssetIdentifiers in assets are resolved. + UABE_Generic_API virtual std::string QueryAssetExportLocation(const std::vector& assets, + const std::string &extension, const std::string &extensionFilter) = 0; + + //Asks the user to provide an import file or directory path. An empty return value signals cancelling the action. + //The user may possibly change the set of assets to actually import. + //The returned vector indices correspond to the indices in the (potentially modified) assets vector. + // -> If assets.size() == 0, returns an empty vector. + // -> If assets.size() == 1, the user is asked to select one file from the filesystem. + // -> If assets.size() > 1, the user is asked to select an input directory and a batch import dialog is shown. + //Assumes all assets have resolved AssetIdentifiers already. + UABE_Generic_API virtual std::vector QueryAssetImportLocation(std::vector& assets, + std::string extension, std::string extensionRegex, std::string extensionFilter) = 0; + + UABE_Generic_API AppContext(); + UABE_Generic_API virtual ~AppContext(); + + inline const PluginMapping& getPlugins() + { + return plugins; + } + + friend class AssetsFileContextInfo; + friend class BundleFileContextInfo; + friend class ResourcesFileContextInfo; +}; \ No newline at end of file diff --git a/UABE_Generic/AssetContainerList.cpp b/UABE_Generic/AssetContainerList.cpp new file mode 100644 index 0000000..34c0835 --- /dev/null +++ b/UABE_Generic/AssetContainerList.cpp @@ -0,0 +1,455 @@ +#include "AssetContainerList.h" +#include +#include + + +class ContainerPPtrIdx : public ContainerPPtr +{ +public: + unsigned int containerIdx; + ContainerPPtrIdx(ResourceManager_PPtr &rsrcPPtr, unsigned int containerIdx = -1) + : ContainerPPtr((unsigned int)rsrcPPtr.fileId, rsrcPPtr.pathId), containerIdx(containerIdx) + {} + ContainerPPtrIdx(PreloadData &preloadPPtr, unsigned int containerIdx = -1) + : ContainerPPtr((unsigned int)preloadPPtr.fileId, preloadPPtr.pathId), containerIdx(containerIdx) + {} + ContainerPPtrIdx(unsigned int fileID, long long int pathID, unsigned int containerIdx = -1) + : ContainerPPtr(fileID, pathID), containerIdx(containerIdx) + {} +}; +namespace std +{ + template<> struct hash + { + std::size_t operator()(ContainerPPtrIdx const& pptr) const + { + return hash()(pptr); + } + }; +} + +inline void prepareContainerEntry(ContainerData &containerData, ContainerEntry &containerEntry, size_t firstDep, size_t dependenciesSize) +{ + containerEntry.name.assign(containerData.name); + containerEntry.fileID = (unsigned int)containerData.ids.fileId; + containerEntry.pathID = containerData.ids.pathId; + + containerEntry.firstDependencyIdx = firstDep + (unsigned int)containerData.preloadIndex; + containerEntry.dependencyCount = (unsigned int)containerData.preloadSize; + if (containerEntry.firstDependencyIdx >= dependenciesSize) + { + containerEntry.firstDependencyIdx = dependenciesSize; + containerEntry.dependencyCount = 0; + } + else if (containerEntry.firstDependencyIdx + (size_t)containerEntry.dependencyCount > dependenciesSize) + { + containerEntry.dependencyCount = dependenciesSize - containerEntry.firstDependencyIdx; + } +} + +AssetContainerList::AssetContainerList() + : pDependencies(new std::vector()), + dependencySet(1 << 14, + hash_ContainerDependencyLookupEntry(*pDependencies), + equality_ContainerDependencyLookupEntry(*pDependencies)) +{ +} +AssetContainerList &AssetContainerList::operator=(AssetContainerList &&other) +{ + containers = std::move(other.containers); + containerMap = std::move(other.containerMap); + dependencyStackHistory = std::move(other.dependencyStackHistory); + pDependencies = std::move(other.pDependencies); + dependencySet = std::move(other.dependencySet); + return (*this); +} +AssetContainerList::~AssetContainerList() +{ +} +bool AssetContainerList::LoadFrom(AssetBundleAsset &fileTable) +{ + size_t containerStart = this->containers.size(); + if (containerStart >= (0xFFFFFFFF - fileTable.containerArrayLen - 1)) //Count the potential main asset container. + { + assert(false); +#ifdef _DEBUG + //MessageBoxA(NULL, "AssetContainerList::LoadFrom : Containers overflow.", "UABE", 16); +#endif + return false; + } + size_t firstDep = this->pDependencies->size(); + if (firstDep >= (0xFFFFFFFF - fileTable.preloadArrayLen)) + { + assert(false); +#ifdef _DEBUG + //MessageBoxA(NULL, "AssetContainerList::LoadFrom : Dependencies overflow.", "UABE", 16); +#endif + return false; + } + size_t stackHistoryStart = this->dependencyStackHistory.size(); + if (stackHistoryStart >= (0xFFFFFFFF - fileTable.containerArrayLen - 1)) //Count the potential main asset container. + { + assert(false); +#ifdef _DEBUG + //MessageBoxA(NULL, "AssetContainerList::LoadFrom : Stack history overflow.", "UABE", 16); +#endif + return false; + } + + //Insert the preload entries. + this->pDependencies->reserve(fileTable.preloadArrayLen); + for (unsigned int k = 0; k < fileTable.preloadArrayLen; k++) + { + PreloadData &prelDep = fileTable.preloadArray[k]; + this->pDependencies->push_back(ContainerDependencyEntry((unsigned int)prelDep.fileId, prelDep.pathId, (unsigned int)stackHistoryStart, 0)); + //Link the dependency into dependencySet for quick lookup. + unsigned int curMatchIdx = this->dependencySet.insert(ContainerDependencyLookupEntry((unsigned int)(firstDep + k))).first->lastRefIdx; + if (curMatchIdx != (firstDep + k)) + { + while ((*this->pDependencies)[curMatchIdx].nextOccurrence != 0) + curMatchIdx = (*this->pDependencies)[curMatchIdx].nextOccurrence; + (*this->pDependencies)[curMatchIdx].nextOccurrence = (unsigned int)(firstDep + k); + } + } + + //Temporary helpers to simplify the process of correcting activeStackHistorySize in dependencies. + std::vector dependencyStackPopLocations; dependencyStackPopLocations.reserve(fileTable.containerArrayLen + 1); + std::vector dependencyStackPushLocations; dependencyStackPushLocations.reserve(fileTable.containerArrayLen + 1); + + //Insert the containers and prepare dependencyStackHistory. + this->dependencyStackHistory.reserve(this->dependencyStackHistory.size() + fileTable.containerArrayLen + 1); + this->containers.resize(this->containers.size() + fileTable.containerArrayLen); + for (unsigned int i = 0; i < fileTable.containerArrayLen; i++) + { + ContainerData &containerData = fileTable.containerArray[i]; + size_t newIndex = containerStart + i; + ContainerEntry &containerEntry = this->containers[newIndex]; + prepareContainerEntry(containerData, containerEntry, firstDep, this->pDependencies->size()); + if (containerEntry.dependencyCount > 0) + { + this->dependencyStackHistory.push_back((unsigned int)newIndex); + dependencyStackPushLocations.push_back((unsigned int)containerEntry.firstDependencyIdx); + dependencyStackPopLocations.push_back((unsigned int)(containerEntry.firstDependencyIdx + containerEntry.dependencyCount)); + //This loop should work, however this would result in O(N*M) performance (N : container count, M : average dependency count), + // which would defeat the point of this data structure - N*M can be quite large in practice. + //For this reason, two internal vectors are used to mark the push and pop locations, + // by which the stack depth is calculated (see below). + //for (unsigned int k = 0; k < containerEntry.dependencyCount; k++) + // this->dependencies[containerEntry.firstDependencyIdx + k].activeStackHistorySize++; + } + unsigned int curMatchIdx = this->containerMap.insert(std::make_pair(ContainerPPtr(containerEntry.fileID, containerEntry.pathID),(unsigned int)newIndex)).first->second; + if (curMatchIdx != newIndex) + { + while (this->containers[curMatchIdx].nextOccurrence != 0) + curMatchIdx = this->containers[curMatchIdx].nextOccurrence; + this->containers[curMatchIdx].nextOccurrence = (unsigned int)newIndex; + } + } + //Insert the main asset container, if needed. + if (fileTable.mainAsset.ids.pathId != 0) + { + ContainerData &containerData = fileTable.mainAsset; + this->containers.resize(this->containers.size() + 1); + size_t newIndex = this->containers.size() - 1; + ContainerEntry &containerEntry = this->containers[newIndex]; + prepareContainerEntry(containerData, containerEntry, firstDep, this->pDependencies->size()); + if (containerEntry.dependencyCount > 0) + { + this->dependencyStackHistory.push_back((unsigned int)newIndex); + dependencyStackPushLocations.push_back((unsigned int)containerEntry.firstDependencyIdx); + dependencyStackPopLocations.push_back((unsigned int)(containerEntry.firstDependencyIdx + containerEntry.dependencyCount)); + //for (unsigned int k = 0; k < containerEntry.dependencyCount; k++) + // this->dependencies[containerEntry.firstDependencyIdx + k].activeStackHistorySize++; + } + unsigned int curMatchIdx = this->containerMap.insert(std::make_pair(ContainerPPtr(containerEntry.fileID, containerEntry.pathID),(unsigned int)newIndex)).first->second; + if (curMatchIdx != newIndex) + { + while (this->containers[curMatchIdx].nextOccurrence != 0) + curMatchIdx = this->containers[curMatchIdx].nextOccurrence; + this->containers[curMatchIdx].nextOccurrence = (unsigned int)newIndex; + } + } + + //Sort the new elements of dependencyStackHistory by firstDependencyIdx. + struct StackHistoryComparer { + std::vector &containers; + StackHistoryComparer(std::vector &containers) + : containers(containers) + {} + inline bool operator()(unsigned int a, unsigned int b) const + { + return containers[a].firstDependencyIdx < containers[b].firstDependencyIdx; + } + }; + StackHistoryComparer stackHistoryComparer = StackHistoryComparer(this->containers); + //Sort dependencyStackHistory, so this->containers[this->dependencyStackHistory[i]].firstDependencyIdx is sorted ascendingly. + std::sort(this->dependencyStackHistory.begin() + stackHistoryStart, this->dependencyStackHistory.end(), stackHistoryComparer); + std::sort(dependencyStackPopLocations.begin(), dependencyStackPopLocations.end()); + std::sort(dependencyStackPushLocations.begin(), dependencyStackPushLocations.end()); + + //Fix the dependency stack history indices in dependencies. + size_t stackRefUpdate_UpperBound = this->pDependencies->size(); + for (size_t _i = this->dependencyStackHistory.size(); _i > stackHistoryStart; _i--) + { + size_t i = _i - 1; + size_t firstDependencyIdx = this->containers[this->dependencyStackHistory[i]].firstDependencyIdx; + for (size_t k = firstDependencyIdx; k < stackRefUpdate_UpperBound; k++) + { + (*this->pDependencies)[k].activeStackHistory = (unsigned int)(i); + } + stackRefUpdate_UpperBound = firstDependencyIdx; + } + + bool shownBugMessage = false; + //Fix the stack depths in dependencies. + if (dependencyStackPushLocations.size() > 0) + { + size_t start = dependencyStackPushLocations[0]; + unsigned int curDepth = 1; + size_t pushIdx = 1; + size_t popIdx = 0; + for (size_t i = start; i < this->pDependencies->size(); i++) + { + while (pushIdx < dependencyStackPushLocations.size() && i >= dependencyStackPushLocations[pushIdx]) + { + curDepth++; + pushIdx++; + } + while (popIdx < dependencyStackPopLocations.size() && i >= dependencyStackPopLocations[popIdx]) + { + if (curDepth == 0) + { + if (!shownBugMessage) + { + assert(false); +#ifdef _DEBUG + //MessageBox(NULL, TEXT("An error occured walking the container dependency stack."), TEXT("UABE"), 16); +#endif + } + shownBugMessage = true; + } + else + curDepth--; + popIdx++; + } + (*this->pDependencies)[i].activeStackHistorySize = curDepth; + } + } + return true; +} +bool AssetContainerList::LoadFrom(ResourceManagerFile &resourceFile) +{ + size_t containerStart = this->containers.size(); + std::unordered_set containerSetTemp; + //Copy containers. + unsigned int containerArrayLen = resourceFile.containers.size(); + if (containerArrayLen > 0x7FFFFFFF) containerArrayLen = 0x7FFFFFFF; + this->containers.resize(this->containers.size() + containerArrayLen); + for (unsigned int i = 0; i < containerArrayLen; i++) + { + this->containers[containerStart + i].name.assign(resourceFile.containers[i].name); + this->containers[containerStart + i].dependencyCount = 0; + this->containers[containerStart + i].firstDependencyIdx = 0; + ContainerPPtrIdx pptr(resourceFile.containers[i].ids, i); + this->containers[containerStart + i].fileID = pptr.fileID; + this->containers[containerStart + i].pathID = pptr.pathID; + containerSetTemp.insert(pptr); + + unsigned int curMatchIdx = this->containerMap.insert(std::make_pair(ContainerPPtr(pptr.fileID, pptr.pathID),(unsigned int)(containerStart + i))).first->second; + if (curMatchIdx != (containerStart + i)) + { + while (this->containers[curMatchIdx].nextOccurrence != 0) + curMatchIdx = this->containers[curMatchIdx].nextOccurrence; + this->containers[curMatchIdx].nextOccurrence = (unsigned int)(containerStart + i); + } + } + //Copy dependencies. + unsigned int dependenciesArrayLen = resourceFile.dependencyLists.size(); + if (dependenciesArrayLen > 0x7FFFFFFF) dependenciesArrayLen = 0x7FFFFFFF; + for (unsigned int i = 0; i < dependenciesArrayLen; i++) + { + ResourceManager_AssetDependencies &rsrcDepList = resourceFile.dependencyLists[i]; + unsigned int nextContainerIdx = (unsigned int)(this->containers.size() - containerStart); + const ContainerPPtrIdx &pptr = *containerSetTemp.insert(ContainerPPtrIdx(rsrcDepList.asset, nextContainerIdx)).first; + size_t containerIdx = containerStart + pptr.containerIdx; + if (pptr.containerIdx == nextContainerIdx) //New item, treat like a container with an empty name. + { + this->containers.resize(this->containers.size() + 1); + this->containers[containerIdx].fileID = pptr.fileID; + this->containers[containerIdx].pathID = pptr.pathID; + } + if (this->containers[containerIdx].dependencyCount != 0) + { + //We are doomed. + //This means that there are multiple dependency lists for the same PPtr. + assert(false); +#ifdef _DEBUG + //MessageBoxA(NULL, "AssetContainerList::LoadFrom : ResourceManager asset has multiple dependency lists for the same asset.", "UABE", 16); + //This break intentionally is for debug mode only. There may be a use to continue if the following dependency lists are for new PPtrs. + break; +#endif + //Otherwise : Silent ignore. + } + else if (containerIdx > 0xFFFFFFFEU || rsrcDepList.dependencies.size() > 0xFFFFFFFEU && this->pDependencies->size() > (0xFFFFFFFEU - rsrcDepList.dependencies.size())) + { + //This is strange. + //If there were this many containers and/or dependencies, they would take at least ~100GiB. + assert(false); +#ifdef _DEBUG + //MessageBoxA(NULL, "AssetContainerList::LoadFrom : Containers or dependencies overflow.", "UABE", 16); +#endif + break; + } + else if (this->dependencyStackHistory.size() > 0xFFFFFFFFU) + { + //Should not happen after the previous checks. + assert(false); +#ifdef _DEBUG + //MessageBoxA(NULL, "AssetContainerList::LoadFrom : Stack history overflow.", "UABE", 16); +#endif + break; + } + else if (rsrcDepList.dependencies.size() > 0) + { + this->containers[containerIdx].firstDependencyIdx = this->pDependencies->size(); + this->containers[containerIdx].dependencyCount = rsrcDepList.dependencies.size(); + this->dependencyStackHistory.push_back((unsigned int)containerIdx); + unsigned int stackHistorySize = (unsigned int)this->dependencyStackHistory.size(); + + size_t firstDep = this->pDependencies->size(); + this->pDependencies->reserve(rsrcDepList.dependencies.size()); + for (unsigned int k = 0; k < rsrcDepList.dependencies.size(); k++) + { + ResourceManager_PPtr &rsrcDep = rsrcDepList.dependencies[k]; + //Due to the way ResourceManager assets are structured, we have no overlapping dependency list parts. + this->pDependencies->push_back(ContainerDependencyEntry((unsigned int)rsrcDep.fileId, rsrcDep.pathId, stackHistorySize - 1, 1)); + //Link the dependency into dependencySet for quick lookup. + unsigned int curMatchIdx = this->dependencySet.insert(ContainerDependencyLookupEntry((unsigned int)(firstDep + k))).first->lastRefIdx; + if (curMatchIdx != (firstDep + k)) + { + while ((*this->pDependencies)[curMatchIdx].nextOccurrence != 0) + curMatchIdx = (*this->pDependencies)[curMatchIdx].nextOccurrence; + (*this->pDependencies)[curMatchIdx].nextOccurrence = (unsigned int)(firstDep + k); + } + } + } + } + return true; +} + +std::vector AssetContainerList::getContainers(unsigned int fileID, long long pathID) const +{ + std::vector ret; + auto result = containerMap.find(ContainerPPtr(fileID, pathID)); + if (result != containerMap.end()) + { + unsigned int entryIdx = result->second; + do { + const ContainerEntry &entry = this->containers[entryIdx]; + ret.push_back(&entry); + entryIdx = entry.nextOccurrence; + } while (entryIdx != 0); + } + return ret; +} + +void AssetContainerList::getContainersFor(std::vector &ret, unsigned int dependencyIdx) const +{ + unsigned int remStackDepth = (*this->pDependencies)[dependencyIdx].activeStackHistorySize; + for (unsigned int i = (*this->pDependencies)[dependencyIdx].activeStackHistory; i != (unsigned int)-1 && remStackDepth > 0; i--) + { + const ContainerEntry &entry = this->containers[this->dependencyStackHistory[i]]; + if (entry.firstDependencyIdx + entry.dependencyCount >= dependencyIdx) + { + ret.push_back(&entry); + remStackDepth--; + } + } + assert(remStackDepth == 0); +} + +static thread_local unsigned int curVirtualContainerLookupPlaceholder_fileID; +static thread_local long long curVirtualContainerLookupPlaceholder_pathID; + +std::vector AssetContainerList::getParentContainers(unsigned int fileID, long long pathID) const +{ + std::size_t targetHash = std::hash()(ContainerDependencyEntry(fileID, pathID, 0, 0)); + std::vector ret; + + //Set the thread local placeholder PPtr for the hash and equality functions to use. + // -> We aren't allowed (and shouldn't) insert anything into dependencySet in this function, + // so we define UINT_MAX as a placeholder index + // that the hasher and equality pred of dependencySet translate to curVirtualContainerLookupPlaceholder. + curVirtualContainerLookupPlaceholder_fileID = fileID; + curVirtualContainerLookupPlaceholder_pathID = pathID; + + auto dependencySetIt = dependencySet.find(ContainerDependencyLookupEntry(UINT_MAX)); + + if (dependencySetIt != dependencySet.end()) + { + unsigned int curMatchIdx = dependencySetIt->lastRefIdx; + const ContainerDependencyEntry &entry = (*this->pDependencies)[dependencySetIt->lastRefIdx]; + assert(entry.fileID == fileID && entry.pathID == pathID); + if (entry.fileID == fileID && entry.pathID == pathID) + { + getContainersFor(ret, curMatchIdx); + while ((curMatchIdx = (*this->pDependencies)[curMatchIdx].nextOccurrence) != 0) + getContainersFor(ret, curMatchIdx); + } + } + return ret; +} + +namespace std +{ + std::size_t hash::operator()(ContainerPPtr const& pptr) const + { + static std::hash uintHash; + static std::hash llHash; + //static std::hash sizetHash; + size_t fidHash = uintHash(pptr.fileID); + size_t pidHash = llHash(pptr.pathID); + return (fidHash << 1) ^ pidHash; //sizetHash((fidHash << 1) ^ pidHash); + } + std::size_t hash::operator()(ContainerDependencyEntry const& pptr) const + { + static std::hash uintHash; + static std::hash llHash; + //static std::hash sizetHash; + size_t fidHash = uintHash(pptr.fileID); + size_t pidHash = llHash(pptr.pathID); + return (fidHash << 1) ^ pidHash; //sizetHash((fidHash << 1) ^ pidHash); + } +} + +hash_ContainerDependencyLookupEntry::hash_ContainerDependencyLookupEntry(std::vector &dependencies) + : pDependencies(&dependencies) +{} +std::size_t hash_ContainerDependencyLookupEntry::operator()(ContainerDependencyLookupEntry const& entry) const +{ + if (entry.lastRefIdx == UINT_MAX) + return std::hash()(ContainerPPtr(curVirtualContainerLookupPlaceholder_fileID, curVirtualContainerLookupPlaceholder_pathID)); + return entryHash((*pDependencies)[entry.lastRefIdx]); +} + +equality_ContainerDependencyLookupEntry::equality_ContainerDependencyLookupEntry(std::vector &dependencies) + : pDependencies(&dependencies) +{} +bool equality_ContainerDependencyLookupEntry::operator()(ContainerDependencyLookupEntry const& entryrefA, ContainerDependencyLookupEntry const& entryrefB) const +{ + ContainerPPtr pptrA = ContainerPPtr(curVirtualContainerLookupPlaceholder_fileID, curVirtualContainerLookupPlaceholder_pathID); + if (entryrefA.lastRefIdx != UINT_MAX) + { + ContainerDependencyEntry const &entryA = (*pDependencies)[entryrefA.lastRefIdx]; + pptrA.fileID = entryA.fileID; + pptrA.pathID = entryA.pathID; + } + ContainerPPtr pptrB = ContainerPPtr(curVirtualContainerLookupPlaceholder_fileID, curVirtualContainerLookupPlaceholder_pathID); + if (entryrefB.lastRefIdx != UINT_MAX) + { + ContainerDependencyEntry const &entryB = (*pDependencies)[entryrefB.lastRefIdx]; + pptrB.fileID = entryB.fileID; + pptrB.pathID = entryB.pathID; + } + return (pptrA.fileID == pptrB.fileID && pptrA.pathID == pptrB.pathID); +} diff --git a/UABE_Generic/AssetContainerList.h b/UABE_Generic/AssetContainerList.h new file mode 100644 index 0000000..994c024 --- /dev/null +++ b/UABE_Generic/AssetContainerList.h @@ -0,0 +1,142 @@ +#pragma once +#include "api.h" +#include +#include +#include +#include +#include "AssetBundleFileTable.h" +#include "ResourceManagerFile.h" + +#if (defined(_MSC_VER) && _MSC_VER<=1600) +#define thread_local __declspec(thread) +#endif + +//Somewhat complex way to store the container and dependency data. +//Meant to allow quick access to a) the list of dependencies of a container +// and b) all parent containers of any asset without having O(n^2) worst case memory usage for the dependency reverse list (n : amount of containers). +class ContainerDependencyLookupEntry +{ +public: + inline ContainerDependencyLookupEntry(unsigned int lastRefIdx) + : lastRefIdx(lastRefIdx) + {} + unsigned int lastRefIdx; //Index into a AssetContainerList::dependencies vector. Placeholder: UINT_MAX + inline bool operator==(const ContainerDependencyLookupEntry &other) const + { + return lastRefIdx == other.lastRefIdx; + } +}; +class ContainerDependencyEntry +{ +public: + inline ContainerDependencyEntry(unsigned int fileID, long long int pathID, unsigned int activeStackHistory, unsigned int activeStackHistorySize) + : fileID(fileID), pathID(pathID), activeStackHistory(activeStackHistory), activeStackHistorySize(activeStackHistorySize), nextOccurrence(0) + {} + //Size of the stack history that applies to this entry + // (i.e. the last history index where the dependency entry index is >= container.firstDependencyIdx). + unsigned int activeStackHistory; + unsigned int activeStackHistorySize; //Dependency stack depth at this point. Limits the amount of backtracking on the stack history. + unsigned int nextOccurrence; //Index of the next entry with the same fileID/pathID, or 0. + unsigned int fileID; + long long int pathID; +}; +class ContainerEntry +{ +public: + inline ContainerEntry() + : firstDependencyIdx(0), dependencyCount(0), nextOccurrence(0), fileID(0), pathID(0) + {} + std::string name; //Set to "" if this entry does not actually have a name. + size_t firstDependencyIdx; + unsigned int dependencyCount; + unsigned int nextOccurrence; + unsigned int fileID; + long long int pathID; +}; +class ContainerPPtr +{ +public: + inline ContainerPPtr() : fileID(0), pathID(0) {} + inline ContainerPPtr(unsigned int fileID, long long int pathID) + : fileID(fileID), pathID(pathID) + {} + unsigned int fileID; + long long int pathID; + inline bool operator==(const ContainerPPtr &other) const + { + return fileID == other.fileID && pathID == other.pathID; + } +}; +class AssetContainerList; +namespace std +{ + template<> struct hash + { + UABE_Generic_API std::size_t operator()(ContainerDependencyEntry const& pptr) const; + }; + template<> struct hash + { + UABE_Generic_API std::size_t operator()(ContainerPPtr const& pptr) const; + }; +} +//ContainerDependencyLookupEntry are intended as proxies to a ContainerDependencyEntry. +// Hash and equality for the unordered_set in AssetContainerList are defined over the fileID&pathID of the corresponding entry. +struct equality_ContainerDependencyLookupEntry +{ + std::vector *pDependencies; + std::hash entryHash; + UABE_Generic_API equality_ContainerDependencyLookupEntry(std::vector &dependencies); + + UABE_Generic_API bool operator()(ContainerDependencyLookupEntry const& pptrref_a, ContainerDependencyLookupEntry const& pptrref_b) const; + +}; +struct hash_ContainerDependencyLookupEntry +{ + std::vector *pDependencies; + std::hash entryHash; + UABE_Generic_API hash_ContainerDependencyLookupEntry(std::vector &dependencies); + + UABE_Generic_API std::size_t operator()(ContainerDependencyLookupEntry const& pptr) const; +}; +//Not thread safe! Make sure that if one thread calls a non-const method, nothing else is called in parallel. +class AssetContainerList +{ + + std::vector containers; + std::unordered_map containerMap; + //Indices into the containers vector, sorted by containers.firstDependencyIdx. + //Each entry stands for a 'push', while 'pop' is implicit at the dependency index : container.firstDependencyIdx+container.dependencyCount. + std::vector dependencyStackHistory; + std::unique_ptr> pDependencies; + + //Hash set containing indices into the vector dependencies, using the fileID and pathID members of the corresponding entry. + //This contains one entry per fileID/pathID pair. Since the pairs can occur multiple times, use dependency.nextOccurence to look for more matches. + std::unordered_set dependencySet; + +private: + void getContainersFor(std::vector &ret, unsigned int dependencyIdx) const; +public: + UABE_Generic_API AssetContainerList(); + UABE_Generic_API AssetContainerList &operator=(AssetContainerList &&other); + UABE_Generic_API ~AssetContainerList(); + inline void Clear() + { + containers.clear(); + containerMap.clear(); + dependencyStackHistory.clear(); + pDependencies->clear(); + dependencySet.clear(); + } + UABE_Generic_API bool LoadFrom(AssetBundleAsset &fileTable); + UABE_Generic_API bool LoadFrom(ResourceManagerFile &resourceFile); + inline const ContainerEntry* getContainers() const + { + return containers.data(); + } + inline size_t getContainerCount() const + { + return containers.size(); + } + UABE_Generic_API std::vector getContainers(unsigned int fileID, long long pathID) const; + UABE_Generic_API std::vector getParentContainers(unsigned int fileID, long long pathID) const; +}; \ No newline at end of file diff --git a/UABE_Generic/AssetIterator.cpp b/UABE_Generic/AssetIterator.cpp new file mode 100644 index 0000000..66222b2 --- /dev/null +++ b/UABE_Generic/AssetIterator.cpp @@ -0,0 +1,304 @@ +#include "AssetIterator.h" +#include "AppContext.h" +#include + +//Instantiate an AssetIdentifier with a relative fileID relative to the dependencies for an absolute fileID and a pathID. +AssetIdentifier::AssetIdentifier(unsigned int referenceFromFileID, unsigned int relFileID, pathid_t pathID) + : fileID(relFileID), pathID(pathID), fileIDIsRelative(true), referenceFromFileID(referenceFromFileID), pFile(nullptr), pAssetInfo(nullptr), pReplacer(nullptr) +{ + +} +AssetIdentifier::AssetIdentifier(unsigned int fileID, std::shared_ptr &pReplacer) + : fileID(fileID), pathID(pReplacer->GetPathID()), fileIDIsRelative(false), pFile(nullptr), pAssetInfo(nullptr), pReplacer(pReplacer) +{} +//Instantiate an AssetIdentifier with a relative fileID (from pReferenceFromFile->references) and a pathID. +AssetIdentifier::AssetIdentifier(std::shared_ptr &pReferenceFromFile, unsigned int relFileID, pathid_t pathID) + : fileID(pReferenceFromFile->resolveRelativeFileID(relFileID)), pathID(pathID), fileIDIsRelative(false), pFile(nullptr), pAssetInfo(nullptr), pReplacer(nullptr) +{ +} +AssetIdentifier::AssetIdentifier(std::shared_ptr _pFile, pathid_t pathID) + : fileID(_pFile->getFileID()), pathID(pathID), fileIDIsRelative(false), pFile(std::move(_pFile)), pAssetInfo(nullptr), pReplacer(nullptr) +{ +} +AssetIdentifier::AssetIdentifier(std::shared_ptr _pFile, AssetFileInfoEx *pAssetInfo) + : fileID(_pFile->getFileID()), pathID(pAssetInfo->index), fileIDIsRelative(false), pFile(std::move(_pFile)), pAssetInfo(pAssetInfo), pReplacer(nullptr) +{} +AssetIdentifier::AssetIdentifier(std::shared_ptr _pFile, std::shared_ptr &pReplacer) + : fileID(_pFile->getFileID()), pathID(pAssetInfo->index), fileIDIsRelative(false), pFile(std::move(_pFile)), pAssetInfo(nullptr), pReplacer(pReplacer) +{} +bool AssetIdentifier::resolve(class AppContext &appContext) +{ + if (!pFile) + { + unsigned int absFileID = fileID; + if (fileIDIsRelative) + { + std::shared_ptr pSourceContextInfoRaw = appContext.getContextInfo(referenceFromFileID); + if (!pSourceContextInfoRaw) + return false; + AssetsFileContextInfo *pSourceContextInfo = dynamic_cast(pSourceContextInfoRaw.get()); + if (!pSourceContextInfo) + return false; + absFileID = pSourceContextInfo->resolveRelativeFileID(fileID); + } + if (absFileID == 0) + return false; + std::shared_ptr pContextInfo = appContext.getContextInfo(absFileID); + if (!pContextInfo) + return false; + pFile = std::dynamic_pointer_cast(pContextInfo); + } + if (!pFile) + return false; + if (!pAssetInfo && !pReplacer) + { + pReplacer = pFile->getReplacer(pathID); + if (!pReplacer) + pAssetInfo = pFile->getAssetsFileContext()->getAssetsFileTable()->getAssetInfo(pathID); + } + if (!pAssetInfo && !pReplacer) + return false; + return true; +} +int32_t AssetIdentifier::getClassID() +{ + if (pReplacer) + return pReplacer->GetClassID(); + if (pAssetInfo) + return (int32_t)pAssetInfo->curFileType; + return INT32_MIN; +} +uint16_t AssetIdentifier::getMonoScriptID() +{ + if (pReplacer) + return pReplacer->GetMonoScriptID(); + if (pAssetInfo) + return pAssetInfo->scriptIndex; + return 0xFFFF; +} +uint64_t AssetIdentifier::getDataSize() //Will return 0 if resolve was not called successfully (unless the (pFile,pAssetInfo) constructor was called) +{ + if (pReplacer) + return pReplacer->GetSize(); + if (pAssetInfo) + return pAssetInfo->curFileSize; + return 0; +} +size_t AssetIdentifier::read(size_t size, void *buffer) //Returns the actually read bytes. +{ + if (pReplacer) + { + IAssetsWriterToMemory *pWriter = Create_AssetsWriterToMemory(buffer, size); + if (!pWriter) + return 0; + uint64_t length = pReplacer->Write(0, pWriter); + Free_AssetsWriter(pWriter); + return length; + } + if (pAssetInfo && pFile) + { + QWORD pos = pAssetInfo->absolutePos; + uint32_t assetSize = pAssetInfo->curFileSize; + if (size > assetSize) + size = assetSize; + //Unsafe is OK here since we explicitly state the file position in the Read call. + IAssetsReader *pReader = pFile->getAssetsFileContext()->getReaderUnsafe(); + if (!pReader) + return 0; + uint64_t length = pReader->Read(pos, assetSize, buffer, false); + return length; + } + return 0; +} + +IAssetsReader_ptr AssetIdentifier::makeReader() +{ + IAssetsReader_ptr ret(nullptr, DummyAssetsReaderDeleter); + if (pReplacer) + { + if (pReplacer->GetSize() > SIZE_MAX) + return ret; + IAssetsWriterToMemory *pWriter = Create_AssetsWriterToMemory((size_t)pReplacer->GetSize()); + if (!pWriter) + return ret; + uint64_t length = pReplacer->Write(0, pWriter); + void *dataBuffer = nullptr; size_t dataSize = 0; + IAssetsReader *pReader = nullptr; + if (pWriter->GetBuffer(dataBuffer, dataSize)) + { + pReader = Create_AssetsReaderFromMemory(dataBuffer, dataSize, false, Free_AssetsWriterToMemory_DynBuf); + if (pReader) + pWriter->SetFreeBuffer(false); + } + Free_AssetsWriter(pWriter); + return IAssetsReader_ptr(pReader, Free_AssetsReader);// pReader; + } + if (pAssetInfo && pFile) + { + QWORD pos = pAssetInfo->absolutePos; + uint32_t assetSize = pAssetInfo->curFileSize; + //Unsafe is OK here since AssetsReaderFromReaderRange behaves like a view if alwaysSeek is set to true. + IAssetsReader *pReader = pFile->getAssetsFileContext()->getReaderUnsafe(); + if (!pReader) + return ret; + return IAssetsReader_ptr(Create_AssetsReaderFromReaderRange(pReader, pos, assetSize, true), Free_AssetsReader); + } + return ret; +} +bool AssetIdentifier::isBigEndian() +{ + if (pFile) + { + bool result; pFile->getEndianness(result); return result; + } + return false; +} + + +AssetIterator::AssetIterator(AssetsFileContextInfo *pContextInfo, bool ignoreExisting, bool ignoreReplacers, bool ignoreRemoverReplacers) + : pContextInfo(pContextInfo), pAssetsFileTable(nullptr), assetIndex(0), pAssetReplacerHint(nullptr), + ignoreReplacers(ignoreReplacers), ignoreExisting(ignoreExisting), ignoreRemoverReplacers(ignoreRemoverReplacers) +{ + if (pContextInfo->pContext) + pAssetsFileTable = pContextInfo->pContext->getAssetsFileTable(); + if (pAssetsFileTable && ignoreExisting) + assetIndex = pAssetsFileTable->assetFileInfoCount; + if (!ignoreReplacers) + { + pContextInfo->lockReplacersRead(); + replacersIterator = pContextInfo->pReplacersByPathID.cbegin(); + if (!ignoreExisting && pAssetsFileTable) + { + updateAssetReplacerHint(); + if (pAssetReplacerHint && (*pAssetReplacerHint)->GetType() == AssetsReplacement_Remove) + ++(*this); //Skip removed assets + } + if (ignoreExisting || !pAssetsFileTable || assetIndex >= pAssetsFileTable->assetFileInfoCount) + { + while (replacersIterator != pContextInfo->pReplacersByPathID.cend() + && (ignoreRemoverReplacers && replacersIterator->second.pReplacer->GetType() == AssetsReplacement_Remove)) //Skip removed assets + ++replacersIterator; + } + } +} +AssetIterator::~AssetIterator() +{ + if (!ignoreReplacers && pContextInfo) + pContextInfo->unlockReplacersRead(); +} +AssetIterator::AssetIterator(AssetIterator &&other) +{ + pContextInfo = other.pContextInfo; + pAssetsFileTable = other.pAssetsFileTable; + assetIndex = other.assetIndex; + pAssetReplacerHint = other.pAssetReplacerHint; + ignoreReplacers = other.ignoreReplacers; + ignoreExisting = other.ignoreExisting; + ignoreRemoverReplacers = other.ignoreRemoverReplacers; + replacersIterator = other.replacersIterator; + other.ignoreExisting = true; + other.ignoreReplacers = true; //The old iterator must not unlock the replacers list. +} +AssetIterator &AssetIterator::operator=(const AssetIterator &other) +{ + if ((!ignoreReplacers && pContextInfo) && (other.ignoreReplacers || pContextInfo != other.pContextInfo)) + { + if (pContextInfo) pContextInfo->unlockReplacersRead(); + } + if ((ignoreReplacers || pContextInfo != other.pContextInfo) && !other.ignoreReplacers) + other.pContextInfo->lockReplacersRead(); + pContextInfo = other.pContextInfo; + pAssetsFileTable = other.pAssetsFileTable; + assetIndex = other.assetIndex; + pAssetReplacerHint = other.pAssetReplacerHint; + ignoreExisting = other.ignoreExisting; + ignoreReplacers = other.ignoreReplacers; + ignoreRemoverReplacers = other.ignoreRemoverReplacers; + replacersIterator = other.replacersIterator; + return *this; +} +void AssetIterator::updateAssetReplacerHint() +{ + pAssetReplacerHint = nullptr; + if (!ignoreReplacers && assetIndex < pAssetsFileTable->assetFileInfoCount) + { + //Look for replacers for this asset. + auto it = pContextInfo->pReplacersByPathID.find((pathid_t)pAssetsFileTable->pAssetFileInfo[assetIndex].index); + if (it != pContextInfo->pReplacersByPathID.end()) + { + pAssetReplacerHint = &it->second.pReplacer; + assert(it->second.replacesExistingAsset); + } + } +} +AssetIterator &AssetIterator::operator++() +{ + bool increment = true; + if (!ignoreExisting && pAssetsFileTable && assetIndex < pAssetsFileTable->assetFileInfoCount) + { + if (assetIndex == pAssetsFileTable->assetFileInfoCount - 1) + increment = false; + while (++assetIndex < pAssetsFileTable->assetFileInfoCount && assetIndex != 0) //Also detect overflow + { + updateAssetReplacerHint(); + if (pAssetReplacerHint && (*pAssetReplacerHint)->GetType() == AssetsReplacement_Remove) + continue; //Skip removed assets + return (*this); + } + assetIndex = pAssetsFileTable->assetFileInfoCount; + } + if (!ignoreReplacers && pContextInfo) + { + if (increment) ++replacersIterator; + while (replacersIterator != pContextInfo->pReplacersByPathID.cend() + && ((!ignoreExisting && replacersIterator->second.replacesExistingAsset) //Skip replacers that were already iterated over before. + || (replacersIterator->second.pReplacer->GetType() == AssetsReplacement_Remove))) //Skip removed assets + ++replacersIterator; + return (*this); + } + return (*this); +} +bool AssetIterator::isEnd() const +{ + if (!ignoreExisting && pAssetsFileTable) + { + if (assetIndex < pAssetsFileTable->assetFileInfoCount) + return false; + } + if (!ignoreReplacers && pContextInfo) + { + if (replacersIterator != pContextInfo->pReplacersByPathID.cend()) + return false; + } + return true; +} +void AssetIterator::get(AssetIdentifier &identifier) +{ + assert(pContextInfo != nullptr); + assert(!isEnd()); + identifier.fileID = pContextInfo->getFileID(); + identifier.fileIDIsRelative = false; + if (identifier.pFile.get() != pContextInfo) + identifier.pFile = nullptr; + if (!ignoreExisting && pAssetsFileTable && assetIndex < pAssetsFileTable->assetFileInfoCount) + { + identifier.pAssetInfo = &pAssetsFileTable->pAssetFileInfo[assetIndex]; + identifier.pathID = (pathid_t)identifier.pAssetInfo->index; + if (pAssetReplacerHint) + identifier.pReplacer = *pAssetReplacerHint; + else + identifier.pReplacer = nullptr; + return; + } + if (!ignoreReplacers && pContextInfo) + { + identifier.pAssetInfo = nullptr; + identifier.pReplacer = replacersIterator->second.pReplacer; + identifier.pathID = (pathid_t)identifier.pReplacer->GetPathID(); + return; + } + assert(false); + identifier.pAssetInfo = nullptr; + identifier.pReplacer = nullptr; + identifier.pathID = 0; +} diff --git a/UABE_Generic/AssetIterator.h b/UABE_Generic/AssetIterator.h new file mode 100644 index 0000000..76232db --- /dev/null +++ b/UABE_Generic/AssetIterator.h @@ -0,0 +1,116 @@ +#pragma once +#include "api.h" +#include "../AssetsTools/AssetsFileTable.h" +#include "../AssetsTools/AssetsReplacer.h" +#include "FileContextInfo.h" +#include +#include + +//Identifies an asset and optionally allows faster lookup than just fileID&pathID. +//Behaves like a snapshot once pFile and pAssetInfo/pReplacer are set by the constructor or by resolve(.) . +class AssetIdentifier +{ +public: + unsigned int fileID; //Mandatory + pathid_t pathID; //Mandatory + + bool fileIDIsRelative; //Mandatory + unsigned int referenceFromFileID; //(Mandatory <=> fileIDIsRelative) + + std::shared_ptr pFile; //Optional + AssetFileInfoEx *pAssetInfo; //Optional, for existing assets + std::shared_ptr pReplacer; //Optional, for new assets and modified existing assets + //Instantiate an AssetIdentifier with an absolute fileID and a pathID. + inline AssetIdentifier() + : fileID(0), pathID(0), fileIDIsRelative(false), referenceFromFileID(0), pFile(nullptr), pAssetInfo(nullptr) + {} + inline AssetIdentifier(unsigned int fileID, pathid_t pathID) + : fileID(fileID), pathID(pathID), fileIDIsRelative(false), referenceFromFileID(0), pFile(nullptr), pAssetInfo(nullptr) + {} + UABE_Generic_API AssetIdentifier(unsigned int fileID, std::shared_ptr &pReplacer); + //Instantiate an AssetIdentifier with a relative fileID relative to the dependencies for an absolute fileID and a pathID. + UABE_Generic_API AssetIdentifier(unsigned int referenceFromFileID, unsigned int relFileID, pathid_t pathID); + //Instantiate an AssetIdentifier with a relative fileID (from pReferenceFromFile->references) and a pathID. + UABE_Generic_API AssetIdentifier(std::shared_ptr &pReferenceFromFile, unsigned int relFileID, pathid_t pathID); + UABE_Generic_API AssetIdentifier(std::shared_ptr pFile, pathid_t pathID); + UABE_Generic_API AssetIdentifier(std::shared_ptr pFile, AssetFileInfoEx *pAssetInfo); + UABE_Generic_API AssetIdentifier(std::shared_ptr pFile, std::shared_ptr &pReplacer); + UABE_Generic_API bool resolve(class AppContext &appContext); + + //Will return INT32_MIN if resolve was not called successfully (if a constructor with pathID was used) + UABE_Generic_API int32_t getClassID(); + inline int32_t getClassID(class AppContext &appContext) + { + if (resolve(appContext)) return getClassID(); + return INT32_MIN; + } + //Will return 0xFFFF if resolve was not called successfully (if a constructor with pathID was used) + //(Note: 0xFFFF is the general default if this asset is not a serialized script.) + UABE_Generic_API uint16_t getMonoScriptID(); + inline uint16_t getMonoScriptID(class AppContext &appContext) + { + if (resolve(appContext)) return getMonoScriptID(); + return 0xFFFF; + } + + //Will return 0 if resolve was not called successfully (if a constructor with pathID was used) + UABE_Generic_API uint64_t getDataSize(); + inline uint64_t getDataSize(class AppContext &appContext) + { + if (resolve(appContext)) return getDataSize(); + return 0; + } + //Will return 0 also if resolve was not called successfully (if a constructor with pathID and fileID or relFileID was used) + UABE_Generic_API size_t read(size_t size, void *buffer); //Returns the actually read bytes. + inline size_t read(class AppContext &appContext, size_t size, void *buffer) + { + if (resolve(appContext)) return read(size, buffer); + return 0; + } + //Will return nullptr if resolve was not called successfully (if a constructor with pathID and fileID or relFileID was used) + //Generates a reader or reader view for the asset. Make sure that the reader pointer is destructed before freeing the AssetIdentifier. + UABE_Generic_API IAssetsReader_ptr makeReader(); + inline IAssetsReader_ptr makeReader(class AppContext &appContext) + { + if (resolve(appContext)) return makeReader(); + return IAssetsReader_ptr(nullptr, DummyAssetsReaderDeleter); + } + + UABE_Generic_API bool isBigEndian(); +}; + +//Asset iterator class, intended for short use. +//Note: May keep a shared lock on the replacers list (if ignoreReplacers==false) for the entire object lifetime. +//-> Do not add a replacer to the file while holding an AssetIterator, else a deadlock can occur! +class AssetIterator +{ + class AssetsFileContextInfo *pContextInfo; + AssetsFileTable *pAssetsFileTable; + unsigned int assetIndex; std::shared_ptr *pAssetReplacerHint; + bool ignoreReplacers, ignoreExisting, ignoreRemoverReplacers; + std::unordered_map::const_iterator replacersIterator; + void updateAssetReplacerHint(); +public: + inline AssetIterator() + : pContextInfo(nullptr), pAssetsFileTable(nullptr), pAssetReplacerHint(nullptr), assetIndex(0), + ignoreReplacers(true), ignoreExisting(true), ignoreRemoverReplacers(true) + {} + //ignoreExisting : If set to true, original assets from the file will be skipped. + //ignoreReplacers : If set to true, replacers will be skipped. + //ignoreRemoverReplacers : If set to true, remover replacers and removed assets will be skipped. + // If set to false, remover replacers for original are iterated over if ignoreExisting is set to true and ignoreReplacers is set to false. + // Specifically, since removers are only stored for original assets, setting this option to false only has an effect if ignoreExisting is set to true. + UABE_Generic_API AssetIterator(class AssetsFileContextInfo *pContextInfo, bool ignoreExisting = false, bool ignoreReplacers = false, bool ignoreRemoverReplacers = true); + UABE_Generic_API ~AssetIterator(); + inline AssetIterator(const AssetIterator &other) { (*this) = other; } + UABE_Generic_API AssetIterator(AssetIterator &&other); + UABE_Generic_API AssetIterator &operator=(const AssetIterator &other); + + UABE_Generic_API AssetIterator &operator++(); + inline AssetIterator operator++(int) { AssetIterator ret(*this); ++(*this); return ret; } + + UABE_Generic_API bool isEnd() const; + //Does not set identifier.pFile (and resets it if it does not point to the correct AssetsFileContextInfo). + //If it was not set before, identifier.resolve(.) needs to be called to use it. + UABE_Generic_API void get(AssetIdentifier &identifier); +}; diff --git a/UABE_Generic/AssetPluginUtil.cpp b/UABE_Generic/AssetPluginUtil.cpp new file mode 100644 index 0000000..3baf551 --- /dev/null +++ b/UABE_Generic/AssetPluginUtil.cpp @@ -0,0 +1,1697 @@ +#include "AssetPluginUtil.h" +#include "../AssetsTools/AssetTypeClass.h" +#include "AppContext.h" +#include +#include +#include +#include +#include +#include +#include + +std::string AssetUtilDesc::makeExportFilePath(std::unordered_map& nameCountBuffer, + const std::string &extension, std::string baseDir) const +{ + if (asset.pFile == nullptr || asset.pFile->getFileContext() == nullptr) + return ""; + if (baseDir.empty() && asset.pFile->getParentFileID() == 0) + baseDir = asset.pFile->getFileContext()->getFileDirectoryPath(); + if (!baseDir.empty()) + baseDir += (char)std::filesystem::path::preferred_separator; + static_assert((std::filesystem::path::preferred_separator & ~0x7F) == 0, "Path separator is outside the ASCII space"); + //std::filesystem::path appears to be a mess with std::string carrying UTF-8 chars on Windows. + //std::filesystem::u8path is deprecated, path(std::string) interprets the string as non-UTF-8. + //-> Only way would be to copy it to a char8_t container or reinterpret the raw string data pointers to char8_t* and pass it to path(..). + //Plain std::string concatenation is easier and should be reliable enough, given that baseDir is good. + std::string& ret = baseDir; + ret += MakeAssetExportName(asset.pathID, + assetName, nameCountBuffer, + assetsFileName); + ret += extension; + return ret; +} + +std::string MakeAssetExportName(pathid_t pathID, std::string assetName, + std::unordered_map& nameCountBuffer, + std::string assetsFileName) +{ + FilterNameForExportInplace(assetName); + if (assetName.empty()) assetName = "unnamed asset"; + + auto previousInstance = nameCountBuffer.end(); + + char identifier[32]; identifier[0] = 0; + if (pathID != 0) + { + sprintf_s(identifier, "-%lld", (int64_t)pathID); + } + else if (!nameCountBuffer.empty()) + { + previousInstance = nameCountBuffer.find(assetName); + if (previousInstance != nameCountBuffer.end()) + { + sprintf_s(identifier, "-%zu", previousInstance->second + 1); + previousInstance->second++; + } + else + nameCountBuffer.insert({ assetName, 1 }); + } + else + nameCountBuffer.insert({ assetName, 1 }); + + std::string ret = std::move(assetName); + if (!assetsFileName.empty()) + { + ret += "-"; + FilterNameForExportInplace(assetsFileName); + ret += assetsFileName; + } + ret += identifier; + return ret; +} + +std::shared_ptr FindResourcesFile(class AppContext &appContext, + const std::string& streamDataFileName, AssetIdentifier& asset, + std::optional> progressManager) +{ + std::shared_ptr ret = nullptr; + + std::vector matchingFiles = appContext.getContextInfo(streamDataFileName, asset.pFile.get()); + size_t numActualCandidates = 0; + for (FileContextInfo_ptr& curFile : matchingFiles) + { + if (curFile->getFileContext() != nullptr && curFile->getFileContext()->getType() == FileContext_Resources) + { + numActualCandidates++; + if (!ret) //Use the first candidate. + ret = std::dynamic_pointer_cast(curFile); + } + } + if (ret == nullptr) + { + //This case may be a candidate for on-demand loading with C++20 coroutines, e.g. co_await taskManager.enqueue(appContext.CreateFileOpenTask(...)) + //-> Support for coroutines within AssetExportTask as well as AsyncTask required for this. + throw AssetUtilError(std::string("Unable to locate the streamed data file. Make sure to open it inside the application. Name: ") + streamDataFileName); + } + if (numActualCandidates > 1 && progressManager.has_value()) + { + //If several matching resource files are found, the plugin uses the 'first one'. + //However, 'first one' is defined by the order in an unordered_map within AppContext, i.e. is arbitrary. + progressManager->get().logMessage(std::format( + "Warning while exporting an asset (File ID {0}, Path ID {1}): {2}", + asset.fileID, (int64_t)asset.pathID, "Found multiple matching streamed data files. Using an arbitrary one.")); + } + return ret; +} +AssetTypeTemplateField& TypeTemplateCache::getTemplateField(class AppContext& appContext, class AssetIdentifier &asset, + std::function newTemplateCallback) +{ + if (!asset.pFile) + { + asset.resolve(appContext); + if (!asset.pFile) + throw AssetUtilError("TypeTemplateCache::getTemplateField: Cannot resolve the asset."); + } + unsigned int fileID = asset.pFile->getFileID(); + int32_t classID = asset.getClassID(); + + { + std::shared_lock templateCacheLock(templateCacheMutex); + auto resultIt = templateCache.find(ClassIdentifier{ fileID, classID }); + if (resultIt != templateCache.end()) + return *resultIt->second; + } + + auto pTemplateBase = std::make_unique(); + if (!asset.pFile->MakeTemplateField(pTemplateBase.get(), appContext, asset.getClassID(), asset.getMonoScriptID(), &asset)) + throw AssetUtilError("Unable to extract type information."); + newTemplateCallback(*pTemplateBase); + + { + std::scoped_lock templateCacheLock(templateCacheMutex); + auto insertResult = templateCache.insert({ ClassIdentifier{ fileID, classID }, std::move(pTemplateBase) }); + //insertResult.second: true if the insertion actually took place, false if someone else added it in the meantime. + //insertResult.first: Iterator to the inserted or existing map entry (which is a . + assert(insertResult.first->second != nullptr); + return *insertResult.first->second; + } +} + +AssetExportTask::AssetExportTask(std::vector _assets, std::string _taskName, + std::string _extension, std::string _baseDir, + bool stopOnError, bool writeOnCompletionOnly) + : assets(std::move(_assets)), taskName(std::move(_taskName)), + extension(std::move(_extension)), baseDir(std::move(_baseDir)), + stopOnError(stopOnError), writeOnCompletionOnly(writeOnCompletionOnly) +{} +const std::string& AssetExportTask::getName() +{ + return taskName; +} +TaskResult AssetExportTask::execute(TaskProgressManager& progressManager) +{ + unsigned int progressRange = static_cast(std::min(assets.size(), 10000)); + size_t assetsPerProgressStep = assets.size() / progressRange; + progressManager.setProgress(0, progressRange); + progressManager.setCancelable(); + auto lastDescTime = std::chrono::high_resolution_clock::now(); + std::unordered_map nameClashMap; + bool encounteredErrors = false; + for (size_t i = 0; i < assets.size(); ++i) + { + if (progressManager.isCanceled()) + return TaskResult_Canceled; + if ((i % assetsPerProgressStep) == 0) + progressManager.setProgress((unsigned int)(i / assetsPerProgressStep), progressRange); + auto curTime = std::chrono::high_resolution_clock::now(); + if (i == 0 || std::chrono::duration_cast(curTime - lastDescTime).count() >= 500) + { + progressManager.setProgressDesc(std::format("Exporting {}/{}", (i + 1), assets.size())); + lastDescTime = curTime; + } + std::string exportPath = (assets.size() > 1) ? assets[i].makeExportFilePath(nameClashMap, extension, baseDir) : baseDir; + try { + bool result = exportAsset(assets[i], exportPath, progressManager); + if (!result && stopOnError) + return (TaskResult)-1; + if (!result) + encounteredErrors = true; + } + catch (AssetUtilError err) { + progressManager.logMessage(std::format( + "Error exporting an asset (File ID {0}, Path ID {1}): {2}", + assets[i].asset.fileID, (int64_t)assets[i].asset.pathID, err.what())); + if (err.getMayStop() && stopOnError) + return (TaskResult)-1; + encounteredErrors = true; + } + } + try { + onCompletion(baseDir, progressManager); + } + catch (AssetUtilError err) { + progressManager.logMessage(std::format( + "Error finishing the export task: {0}", err.what())); + if (err.getMayStop() && stopOnError) + return (TaskResult)-1; + encounteredErrors = true; + } + return (TaskResult)(encounteredErrors ? -2 : 0); +} +void AssetExportTask::onCompletion(const std::string& outputPath, std::optional> progressManager) +{} + +AssetExportRawTask::AssetExportRawTask(std::vector _assets, std::string _taskName, + std::string _extension, std::string _baseDir, bool stopOnError) + : AssetExportTask(std::move(_assets), std::move(_taskName), std::move(_extension), std::move(_baseDir), stopOnError) +{} +bool AssetExportRawTask::exportAsset(AssetUtilDesc& desc, std::string path, std::optional> progressManager) +{ + IAssetsReader_ptr pReader = desc.asset.makeReader(); + if (pReader == nullptr) + throw AssetUtilError("Unable to read the asset."); + QWORD size = 0; + if (!pReader->Seek(AssetsSeek_End, 0) || !pReader->Tell(size) || !pReader->Seek(AssetsSeek_Begin, 0)) + throw AssetUtilError("Unable to read the asset."); + std::unique_ptr pReplacer(MakeAssetModifierFromReader(0, 0, -1, (uint16_t)-1, pReader.get(), size)); + if (pReplacer == nullptr) + throw AssetUtilError("Unexpected runtime error."); + std::unique_ptr pWriter(Create_AssetsWriterToFile(path.c_str(), true, true, RWOpenFlags_Immediately)); + if (pWriter == nullptr) + throw AssetUtilError("Unable to create the output file."); + + if (pReplacer->Write(0, pWriter.get()) != size) + throw AssetUtilError("Unable to write the asset."); + return true; +} + +template +requires std::same_as || std::same_as +static void formatFloatStr(floatT fval, std::string& outStr) +{ + //float and double are always IEEE-754 binary32/64. + if (fval == std::numeric_limits::infinity() + || fval == -std::numeric_limits::infinity()) + { + outStr.insert(outStr.size(), (fval < 0) ? "\"-" : "\"+"); + outStr.insert(outStr.size(), "Infinity\""); + } + else if (isinf(fval) || isnan(fval)) + { + assert(!isinf(fval)); //Should be covered by the previous check. + typename std::conditional::type f_as_uint; + f_as_uint = *reinterpret_cast(&fval); + constexpr std::string_view fmt = (sizeof(floatT) == 4) ? "\"0x{:08X}\"" : "\"0x{:016X}\""; + std::format_to(std::back_inserter(outStr), fmt, f_as_uint); + } + else + { + constexpr std::string_view fmt = (sizeof(floatT) == 4) ? "{:.9g}" : "{:.17g}"; + std::format_to(std::back_inserter(outStr), fmt, fval); + } +} + +template +requires std::same_as || std::same_as +static bool parseFloatStr(floatT &outFloat, const std::string& str) +{ + outFloat = 0.0; + if (str.empty()) + return false; + if (str[0] == '"') + { + if (str.length() == 1 || str.back() != '"') + return false; + if (0==str.compare(1, std::string::npos, "+Infinity\"") + || 0==str.compare(1, std::string::npos, "Infinity\"")) + { + outFloat = std::numeric_limits::infinity(); + return true; + } + if (0==str.compare(1, std::string::npos, "-Infinity\"")) + { + outFloat = -std::numeric_limits::infinity(); + return true; + } + if (0==str.compare(1, 2, "0x")) + { + typename std::conditional::type f_as_uint; + char* endptr = nullptr; + f_as_uint = (decltype(f_as_uint))_strtoui64(&str.c_str()[3], &endptr, 16); + if (endptr != &str.c_str()[str.size() - 1]) //Position of closing '"' + return false; + outFloat = *reinterpret_cast(&f_as_uint); + return true; + } + return false; + } + try { + struct _stof_wrap + { + inline float operator()(const std::string &str) { return std::stof(str); } + }; + struct _stod_wrap + { + inline double operator()(const std::string& str) { return std::stod(str); } + }; + typename std::conditional::type converter; + outFloat = converter(str); + return true; + } + catch (std::invalid_argument) { return false; } + catch (std::out_of_range) { return false; } + assert(false); + return false; +} + +AssetExportDumpTask::AssetExportDumpTask(class AppContext& appContext, + std::vector _assets, std::string _taskName, + std::string _extension, std::string _baseDir, bool stopOnError) + : AssetExportTask(std::move(_assets), std::move(_taskName), std::move(_extension), std::move(_baseDir), stopOnError), + appContext(appContext) +{} +bool AssetExportDumpTask::exportAsset(AssetUtilDesc& desc, std::string path, std::optional> progressManager) +{ + if (desc.asset.pFile == nullptr) + throw AssetUtilError("Unable to find the .assets file."); + + IAssetsReader_ptr pReader = desc.asset.makeReader(); + if (pReader == nullptr) + throw AssetUtilError("Unable to read the asset."); + QWORD size = 0; + if (!pReader->Seek(AssetsSeek_End, 0) || !pReader->Tell(size) || !pReader->Seek(AssetsSeek_Begin, 0)) + throw AssetUtilError("Unable to read the asset."); + + AssetTypeTemplateField templateBase; + if (!desc.asset.pFile->MakeTemplateField(&templateBase, appContext, desc.asset.getClassID(), desc.asset.getMonoScriptID(), &desc.asset)) + throw AssetUtilError("Unable to extract type information."); + AssetTypeTemplateField *pTemplateBase = &templateBase; + + AssetTypeInstance assetInstance(1, &pTemplateBase, size, pReader.get(), desc.asset.isBigEndian()); + AssetTypeValueField* pBaseField = assetInstance.GetBaseField(); + if (pBaseField == nullptr || pBaseField->IsDummy()) + throw AssetUtilError("Unable to deserialize the asset."); + + std::unique_ptr pWriter(Create_AssetsWriterToFile(path.c_str(), true, true, RWOpenFlags_Immediately)); + if (pWriter == nullptr) + throw AssetUtilError("Unable to create the output file."); + + dumpAsset(pReader.get(), pBaseField, pWriter.get()); + + return true; +} + +struct _Dump_OutputLineBufHandler +{ + std::string& lineBuf; + IAssetsWriter* pWriter; + _Dump_OutputLineBufHandler(std::string& lineBuf, IAssetsWriter* pWriter) + : lineBuf(lineBuf), pWriter(pWriter) + {} + void operator()() + { + size_t size = lineBuf.size() * sizeof(decltype(lineBuf.data()[0])); + if (size > 0 && size != pWriter->Write(size, lineBuf.data())) + throw AssetUtilError("Unable to write the dump."); + } +}; +struct _Dump_OutputRawCharsHandler +{ + IAssetsWriter* pWriter; + _Dump_OutputRawCharsHandler(IAssetsWriter* pWriter) + : pWriter(pWriter) + {} + void operator()(const char* begin, size_t count) + { + size_t size = count * sizeof(decltype(begin[0])); + if (size > 0 && size != pWriter->Write(size, begin)) + throw AssetUtilError("Unable to write the dump."); + } +}; + +AssetExportTextDumpTask::AssetExportTextDumpTask(class AppContext& appContext, + std::vector _assets, std::string _taskName, + std::string _extension, std::string _baseDir, bool stopOnError) + : AssetExportDumpTask(appContext, std::move(_assets), std::move(_taskName), std::move(_extension), std::move(_baseDir), stopOnError) +{} +void AssetExportTextDumpTask::dumpAsset(IAssetsReader* pReader, AssetTypeValueField* pBaseField, IAssetsWriter* pDumpWriter) +{ + std::string lineBuf; + recursiveDumpAsset(pReader, pBaseField, 0, pDumpWriter, lineBuf); +} +void AssetExportTextDumpTask::recursiveDumpAsset(IAssetsReader* pReader, AssetTypeValueField* pField, size_t depth, + IAssetsWriter* pDumpWriter, std::string &lineBuf) +{ + if (pField == nullptr) + throw AssetUtilError("Null field encountered."); + + _Dump_OutputLineBufHandler outputLineBuf(lineBuf, pDumpWriter); + _Dump_OutputRawCharsHandler outputRawChars(pDumpWriter); + + lineBuf.clear(); + for (size_t i = 0; i < depth; ++i) + lineBuf += " "; + outputLineBuf(); + + EnumValueTypes valueType = ValueType_None; + if (pField->GetValue() != NULL) + valueType = pField->GetValue()->GetType(); + int alignment = (int)pField->GetTemplateField()->align; + + switch (valueType) + { + case ValueType_Bool: + lineBuf.clear(); + std::format_to(std::back_inserter(lineBuf), + "{} {} {} = {}\r\n", alignment, pField->GetType(), pField->GetName(), pField->GetValue()->AsBool() ? "true" : "false"); + outputLineBuf(); + break; + case ValueType_Int8: + case ValueType_Int16: + case ValueType_Int32: + case ValueType_Int64: + lineBuf.clear(); + std::format_to(std::back_inserter(lineBuf), + "{} {} {} = {}\r\n", alignment, pField->GetType(), pField->GetName(), pField->GetValue()->AsInt64()); + outputLineBuf(); + break; + case ValueType_UInt8: + case ValueType_UInt16: + case ValueType_UInt32: + case ValueType_UInt64: + lineBuf.clear(); + std::format_to(std::back_inserter(lineBuf), + "{} {} {} = {}\r\n", alignment, pField->GetType(), pField->GetName(), pField->GetValue()->AsUInt64()); + outputLineBuf(); + break; + case ValueType_Float: + lineBuf.clear(); + std::format_to(std::back_inserter(lineBuf), + "{} {} {} = ", alignment, pField->GetType(), pField->GetName()); + formatFloatStr(pField->GetValue()->AsFloat(), lineBuf); + lineBuf.insert(lineBuf.size(), "\r\n"); + outputLineBuf(); + break; + case ValueType_Double: + lineBuf.clear(); + std::format_to(std::back_inserter(lineBuf), + "{} {} {} = ", alignment, pField->GetType(), pField->GetName()); + formatFloatStr(pField->GetValue()->AsDouble(), lineBuf); + lineBuf.insert(lineBuf.size(), "\r\n"); + outputLineBuf(); + break; + case ValueType_String: + { + if (pField->GetTemplateField()->children.size() > 0 && pField->GetTemplateField()->children[0].align) + alignment = true; + lineBuf.clear(); + std::format_to(std::back_inserter(lineBuf), + "{} {} {} = \"", alignment, pField->GetType(), pField->GetName()); + outputLineBuf(); + char *strValue = pField->GetValue()->AsString(); + //Escape the string: '\r' -> "\\r", '\n' -> "\\n" and '\\' -> "\\\\". + // '\"' is not escaped, since the string end can be inferred from the line end. + size_t strLen = strlen(strValue); size_t strPos = 0; + for (size_t i = 0; i < strLen; i++) + { + if (strValue[i] == '\\') + { + //Output characters: strValue[strPos, i] + outputRawChars(&strValue[strPos], i - strPos + 1); + outputRawChars("\\", 1); + strPos = i+1; + } + else if (strValue[i] == '\r' || strValue[i] == '\n') + { + //Output characters: strValue[strPos, i) + outputRawChars(&strValue[strPos], i - strPos); + outputRawChars((strValue[i] == '\r') ? "\\r" : "\\n", 2); + strPos = i+1; + } + } + //Output characters: strValue[strPos, strLen) + outputRawChars(&strValue[strPos], strLen - strPos); + outputRawChars("\"\r\n", 3); + } + break; + case ValueType_ByteArray: + { + if (pField->GetTemplateField()->children.size() != 2) + throw AssetUtilError("Unexpected byte array serialization."); + AssetTypeByteArray *pByteArray = pField->GetValue()->AsByteArray(); + lineBuf.clear(); + std::format_to(std::back_inserter(lineBuf), + "{} {} {} ({} items)\r\n", alignment, pField->GetType(), pField->GetName(), pByteArray->size); + outputLineBuf(); + lineBuf.clear(); + for (size_t i = 0; i <= depth; ++i) + lineBuf += " "; + AssetTypeTemplateField *countVal = &pField->GetTemplateField()->children[0]; + std::format_to(std::back_inserter(lineBuf), + "{} {} {} = {}\r\n", 0, countVal->type, countVal->name, pByteArray->size); + outputLineBuf(); + AssetTypeTemplateField *byteVal = &pField->GetTemplateField()->children[1]; + for (uint32_t i = 0; i < pByteArray->size; i++) + { + lineBuf.clear(); + for (size_t i = 0; i <= depth; ++i) + lineBuf += " "; + std::format_to(std::back_inserter(lineBuf), + "[{}]\r\n", i); + outputLineBuf(); + lineBuf.clear(); + for (size_t i = 0; i <= depth+1; ++i) + lineBuf += " "; + std::format_to(std::back_inserter(lineBuf), + "{} {} {} = {}\r\n", 0, byteVal->type, byteVal->name, pByteArray->data[i]); + outputLineBuf(); + } + } + break; + case ValueType_Array: + { + if (pField->GetTemplateField()->children.size() != 2) + throw AssetUtilError("Unexpected array serialization."); + lineBuf.clear(); + std::format_to(std::back_inserter(lineBuf), + "{} {} {} ({} items)\r\n", alignment, pField->GetType(), pField->GetName(), pField->GetChildrenCount()); + outputLineBuf(); + lineBuf.clear(); + for (size_t i = 0; i <= depth; ++i) + lineBuf += " "; + AssetTypeTemplateField *countVal = &pField->GetTemplateField()->children[0]; + std::format_to(std::back_inserter(lineBuf), + "{} {} {} = {}\r\n", 0, countVal->type, countVal->name, pField->GetChildrenCount()); + outputLineBuf(); + for (uint32_t i = 0; i < pField->GetChildrenCount(); i++) + { + lineBuf.clear(); + for (size_t i = 0; i <= depth; ++i) + lineBuf += " "; + std::format_to(std::back_inserter(lineBuf), + "[{}]\r\n", i); + outputLineBuf(); + recursiveDumpAsset(pReader, pField->operator[](i), depth + 2, pDumpWriter, lineBuf); + } + } + break; + case ValueType_None: + { + lineBuf.clear(); + std::format_to(std::back_inserter(lineBuf), + "{} {} {}\r\n", alignment, pField->GetType(), pField->GetName()); + outputLineBuf(); + for (uint32_t i = 0; i < pField->GetChildrenCount(); i++) + { + recursiveDumpAsset(pReader, pField->operator[](i), depth + 1, pDumpWriter, lineBuf); + } + } + break; + } +} + +AssetExportJSONDumpTask::AssetExportJSONDumpTask(class AppContext& appContext, + std::vector _assets, std::string _taskName, + std::string _extension, std::string _baseDir, bool stopOnError) + : AssetExportDumpTask(appContext, std::move(_assets), std::move(_taskName), std::move(_extension), std::move(_baseDir), stopOnError) +{} +void AssetExportJSONDumpTask::dumpAsset(IAssetsReader* pReader, AssetTypeValueField* pBaseField, IAssetsWriter* pDumpWriter) +{ + std::string lineBuf; + _Dump_OutputLineBufHandler outputLineBuf(lineBuf, pDumpWriter); + lineBuf.assign("{\r\n "); + outputLineBuf(); + + recursiveDumpAsset(pReader, pBaseField, 1, pDumpWriter, lineBuf); + + lineBuf.assign("\r\n}"); + outputLineBuf(); +} + + +void AssetExportJSONDumpTask::recursiveDumpAsset(IAssetsReader* pReader, AssetTypeValueField* pField, size_t depth, + IAssetsWriter* pDumpWriter, std::string& lineBuf, bool dumpValueOnly) +{ + constexpr bool stripExtendedInfo = false; + if (pField == nullptr) + throw AssetUtilError("Null field encountered."); + + _Dump_OutputLineBufHandler outputLineBuf(lineBuf, pDumpWriter); + _Dump_OutputRawCharsHandler outputRawChars(pDumpWriter); + + EnumValueTypes valueType = ValueType_None; + if (pField->GetValue() != NULL) + valueType = pField->GetValue()->GetType(); + + if (!dumpValueOnly) + { + bool alignment = pField->GetTemplateField()->align || + (valueType == ValueType_String && (pField->GetTemplateField()->children.size() > 0 && pField->GetTemplateField()->children[0].align)); + std::string fieldName; + if (stripExtendedInfo) + fieldName.assign(pField->GetName()); + else + fieldName = std::format("{} {} {}", alignment ? "1" : "0", pField->GetType(), pField->GetName()); + lineBuf.clear(); + std::format_to(std::back_inserter(lineBuf), + "\"{}\": ", fieldName); + outputLineBuf(); + } + + switch (valueType) + { + case ValueType_Bool: + if (pField->GetValue()->AsBool()) + lineBuf.assign("true"); + else + lineBuf.assign("false"); + outputLineBuf(); + break; + case ValueType_Int8: + case ValueType_Int16: + case ValueType_Int32: + lineBuf.clear(); + std::format_to(std::back_inserter(lineBuf), "{}", pField->GetValue()->AsInt()); + outputLineBuf(); + break; + case ValueType_UInt8: + case ValueType_UInt16: + case ValueType_UInt32: + lineBuf.clear(); + std::format_to(std::back_inserter(lineBuf), "{}", pField->GetValue()->AsUInt()); + outputLineBuf(); + break; + case ValueType_Int64: + lineBuf.clear(); + std::format_to(std::back_inserter(lineBuf), "\"{}\"", pField->GetValue()->AsInt64()); + outputLineBuf(); + break; + case ValueType_UInt64: + lineBuf.clear(); + std::format_to(std::back_inserter(lineBuf), "\"{}\"", pField->GetValue()->AsUInt64()); + outputLineBuf(); + break; + case ValueType_Float: + lineBuf.clear(); + formatFloatStr(pField->GetValue()->AsFloat(), lineBuf); + outputLineBuf(); + break; + case ValueType_Double: + lineBuf.clear(); + formatFloatStr(pField->GetValue()->AsDouble(), lineBuf); + outputLineBuf(); + break; + case ValueType_String: + { + outputRawChars("\"", 1); + char *strValue = pField->GetValue()->AsString(); + size_t strLen = strlen(strValue); size_t strPos = 0; + for (size_t i = 0; i < strLen; i++) + { + if (strValue[i] & 0x80) //UTF-8 character has another byte ((char & 0xC0) == 0x80 for the 2nd/3rd/4th byte) + { + //Output strValue[strPos,i] + outputRawChars(&strValue[strPos], i - strPos + 1); + strPos = i+1; + } + else + { + lineBuf.clear(); + const char *replaceString = NULL; + switch (strValue[i]) + { + case '\\': + lineBuf.assign("\\\\"); + break; + case '"': + lineBuf.assign("\\\""); + break; + case '\b': + lineBuf.assign("\\b"); + break; + case '\f': + lineBuf.assign("\\f"); + break; + case '\n': + lineBuf.assign("\\n"); + break; + case '\r': + lineBuf.assign("\\r"); + break; + case '\t': + lineBuf.assign("\\t"); + break; + default: + if (strValue[i] < 0x20) + std::format_to(std::back_inserter(lineBuf), "\\u{:04u}", strValue[i]); + break; + } + if (!lineBuf.empty()) + { + //Output strValue[strPos,i) + outputRawChars(&strValue[strPos], i - strPos); + strPos = i; + outputLineBuf(); + strPos++; + } + } + } + //Output strValue[strPos,strLen) + outputRawChars(&strValue[strPos], strLen - strPos); + outputRawChars("\"", 1); + } + break; + case ValueType_ByteArray: + { + AssetTypeByteArray *pByteArray = pField->GetValue()->AsByteArray(); + outputRawChars("[", 1); + for (uint32_t i = 0; i < pByteArray->size; i++) + { + if (i) + pDumpWriter->Write(2, ", "); + lineBuf.clear(); + std::format_to(std::back_inserter(lineBuf), "{}", pByteArray->data[i]); + outputLineBuf(); + } + outputRawChars("]", 1); + } + break; + case ValueType_Array: + { + if (pField->GetChildrenCount() > 0) + outputRawChars("[\r\n", 3); + else + outputRawChars("[", 1); + //bool writeChildAsObject = !field->GetTemplateField()->children[1].hasValue; + for (uint32_t i = 0; i < pField->GetChildrenCount(); i++) + { + lineBuf.clear(); + if (i != 0) + lineBuf += ",\r\n"; + for (size_t _i = 0; _i <= depth; ++_i) + lineBuf += " "; + if (!stripExtendedInfo) + lineBuf += "{"; + outputLineBuf(); + recursiveDumpAsset(pReader, pField->operator[](i), depth + 1, pDumpWriter, lineBuf, stripExtendedInfo); + if (!stripExtendedInfo) + outputRawChars("}", 1); + } + if (pField->GetChildrenCount() > 0) + { + lineBuf.clear(); + lineBuf += "\r\n"; + for (size_t _i = 0; _i < depth; _i++) + lineBuf += " "; + outputLineBuf(); + } + outputRawChars("]", 1); + } + break; + case ValueType_None: + { + bool drawBrackets = false; + if (dumpValueOnly) + { + if (pField->GetChildrenCount() > 1) + { + outputRawChars("{\r\n", 3); + drawBrackets = true; + } + } + else if (pField->GetChildrenCount() > 1) + { + lineBuf.clear(); + lineBuf += "\r\n"; + for (size_t _i = 0; _i < depth; _i++) + lineBuf += " "; + lineBuf += "{\r\n"; + outputLineBuf(); + drawBrackets = true; + } + else + { + outputRawChars(" {\r\n", 4); + drawBrackets = true; + } + for (uint32_t i = 0; i < pField->GetChildrenCount(); i++) + { + lineBuf.clear(); + if (i != 0) + lineBuf += ",\r\n"; + for (size_t _i = 0; _i <= depth; _i++) + lineBuf += " "; + outputLineBuf(); + recursiveDumpAsset(pReader, pField->operator[](i), depth + 1, pDumpWriter, lineBuf); + } + lineBuf.clear(); + if (pField->GetChildrenCount() > 0) + lineBuf += "\r\n"; + if (drawBrackets) + { + for (size_t _i = 0; _i < depth; _i++) + lineBuf += " "; + lineBuf += "}"; + } + outputLineBuf(); + } + break; + } +} + + + + +CGenericBatchImportDialogDesc::CGenericBatchImportDialogDesc(std::vector _elements, const std::string& extensionRegex) + : elements(std::move(_elements)) +{ + for (size_t i = 0; i < elements.size(); ++i) + { + if (elements[i].asset.pathID != 0) + elementByPathID.insert({ elements[i].asset.pathID, i }); + } + importFilePaths.resize(elements.size()); + importFilePathOverrides.resize(elements.size()); + regex = MakeImportFileNameRegex(extensionRegex); +} +bool CGenericBatchImportDialogDesc::GetImportableAssetDescs(OUT std::vector& nameList) +{ + nameList.reserve(elements.size()); + for (size_t i = 0; i < elements.size(); i++) + { + AssetDesc curDesc; + std::string& desc = getElementDescription(i); + curDesc.description = desc.c_str(); + curDesc.assetsFileName = elements[i].assetsFileName.c_str(); + curDesc.pathID = static_cast(elements[i].asset.pathID); + nameList.push_back(curDesc); + } + return true; +} +bool CGenericBatchImportDialogDesc::GetFilenameMatchStrings(OUT std::vector& regexList, OUT bool& checkSubDirs) +{ + regexList.push_back(regex.c_str()); + checkSubDirs = false; + return true; +} +bool CGenericBatchImportDialogDesc::GetFilenameMatchInfo(IN const char* filename, IN std::vector& capturingGroups, OUT size_t& matchIndex) +{ + const char* assetsFileName = nullptr; pathid_t pathID = 0; + matchIndex = (size_t)-1; + if (RetrieveImportRegexInfo(capturingGroups, assetsFileName, pathID)) + { + auto it_pair = elementByPathID.equal_range(pathID); + for (auto it = it_pair.first; it != it_pair.second; ++it) + { + size_t i = it->second; + assert(pathID == elements[i].asset.pathID); + std::string elementFileNameFiltered = elements[i].assetsFileName; + FilterNameForExportInplace(elementFileNameFiltered); + if (elementFileNameFiltered == assetsFileName) + { + matchIndex = i; + return true; + } + } + } + return false; +} +void CGenericBatchImportDialogDesc::SetInputFilepath(IN size_t matchIndex, IN const char* filepath) +{ + if (matchIndex < importFilePaths.size()) + { + importFilePaths[matchIndex].assign(filepath ? filepath : ""); + } +} +bool CGenericBatchImportDialogDesc::HasFilenameOverride(IN size_t matchIndex, OUT std::string& filenameOverride, OUT bool& relativeToBasePath) +{ + relativeToBasePath = false; + if (matchIndex >= importFilePathOverrides.size()) + return false; + if (importFilePathOverrides[matchIndex].size() > 0) + { + filenameOverride.assign(importFilePathOverrides[matchIndex]); + return true; + } + filenameOverride.clear(); + return false; +} + +AssetImportTask::AssetImportTask(std::vector _assets, std::vector _importFilePaths, + std::string _taskName, bool stopOnError) + : assets(std::move(_assets)), importFilePaths(std::move(_importFilePaths)), + taskName(std::move(_taskName)), stopOnError(stopOnError) +{ + if (assets.size() != importFilePaths.size()) + throw std::invalid_argument("AssetImportTask: assets and importFilePaths should have matching numbers of elements!"); +} +const std::string& AssetImportTask::getName() +{ + return taskName; +} +TaskResult AssetImportTask::execute(TaskProgressManager& progressManager) +{ + unsigned int progressRange = static_cast(std::min(assets.size(), 10000)); + size_t assetsPerProgressStep = assets.size() / progressRange; + constexpr size_t assetsPerDescUpdate = 8; + progressManager.setCancelable(); + progressManager.setProgress(0, progressRange); + auto lastDescTime = std::chrono::high_resolution_clock::time_point::min(); + bool encounteredErrors = false; + for (size_t i = 0; i < assets.size(); ++i) + { + if (progressManager.isCanceled()) + return TaskResult_Canceled; + if ((i % assetsPerProgressStep) == 0) + progressManager.setProgress((unsigned int)(i / assetsPerProgressStep), progressRange); + auto curTime = std::chrono::high_resolution_clock::now(); + if (std::chrono::duration_cast(curTime - lastDescTime).count() >= 500) + { + progressManager.setProgressDesc(std::format("Importing {}/{}", (i + 1), assets.size())); + lastDescTime = curTime; + } + try { + bool result = (importFilePaths[i].empty() || importAsset(assets[i], importFilePaths[i], progressManager)); + if (!result && stopOnError) + return (TaskResult)-1; + if (!result) + encounteredErrors = true; + } + catch (AssetUtilError err) { + progressManager.logMessage(std::format( + "Error importing an asset (File ID {0}, Path ID {1}): {2}", + assets[i].asset.fileID, (int64_t)assets[i].asset.pathID, err.what())); + if (err.getMayStop() && stopOnError) + return (TaskResult)-1; + encounteredErrors = true; + } + } + return (TaskResult)(encounteredErrors ? -2 : 0); +} + +static void Free_AssetsWriterToMemory_Delete(void* buffer) +{ + if (buffer) + delete[] (uint8_t*)buffer; +} + +AssetImportRawTask::AssetImportRawTask(AppContext& appContext, + std::vector _assets, std::vector _importFilePaths, + std::string _taskName, bool stopOnError) + : AssetImportTask(std::move(_assets), std::move(_importFilePaths), std::move(_taskName), stopOnError), + appContext(appContext) +{} + +bool AssetImportRawTask::importAsset(AssetUtilDesc& asset, std::string path, std::optional> progressManager) +{ + if (asset.asset.pFile == nullptr) + throw AssetUtilError("Unable to find the target .assets file."); + std::unique_ptr pReader(Create_AssetsReaderFromFile(path.c_str(), true, RWOpenFlags_Immediately));// desc.asset.makeReader(); + if (pReader == nullptr) + throw AssetUtilError("Unable to read the file."); + QWORD size = 0; + if (!pReader->Seek(AssetsSeek_End, 0) || !pReader->Tell(size) || !pReader->Seek(AssetsSeek_Begin, 0) || size >= SIZE_MAX) + throw AssetUtilError("Unable to read the file."); + std::unique_ptr memBuf(new uint8_t[(size_t)size]); + if (pReader->Read(size, memBuf.get()) != size) + throw AssetUtilError("Unable to read the file."); + std::shared_ptr pReplacer(MakeAssetModifierFromMemory(0, asset.asset.pathID, + asset.asset.getClassID(), asset.asset.getMonoScriptID(), + memBuf.release(), (size_t)size, Free_AssetsWriterToMemory_Delete)); + if (pReplacer == nullptr) + throw AssetUtilError("Unexpected runtime error."); + asset.asset.pFile->addReplacer(pReplacer, appContext); + return true; +} + +AssetImportDumpTask::AssetImportDumpTask(AppContext& appContext, + std::vector _assets, std::vector _importFilePaths, + std::string _taskName, bool stopOnError) + : AssetImportTask(std::move(_assets), std::move(_importFilePaths), std::move(_taskName), stopOnError), + appContext(appContext) +{} + +bool AssetImportDumpTask::importAsset(AssetUtilDesc& asset, std::string path, std::optional> progressManager) +{ + if (asset.asset.pFile == nullptr) + throw AssetUtilError("Unable to find the target .assets file."); + std::unique_ptr pReader(Create_AssetsReaderFromFile(path.c_str(), true, RWOpenFlags_Immediately));// desc.asset.makeReader(); + if (pReader == nullptr) + throw AssetUtilError("Unable to read the file."); + QWORD size = 0; + if (!pReader->Seek(AssetsSeek_End, 0) || !pReader->Tell(size) || !pReader->Seek(AssetsSeek_Begin, 0)) + throw AssetUtilError("Unable to read the file."); + std::unique_ptr pWriter(Create_AssetsWriterToMemory()); + parseDump(pReader.get(), pWriter.get()); + + size_t bufferLen = 0; void* buffer = NULL; + pWriter->GetBuffer(buffer, bufferLen); + pWriter->SetFreeBuffer(false); + + std::shared_ptr pReplacer(MakeAssetModifierFromMemory(0, asset.asset.pathID, + asset.asset.getClassID(), asset.asset.getMonoScriptID(), + buffer, bufferLen, Free_AssetsWriterToMemory_DynBuf)); + if (pReplacer == nullptr) + throw AssetUtilError("Unexpected runtime error."); + + asset.asset.pFile->addReplacer(pReplacer, appContext); + return true; +} +void AssetImportDumpTask::parseDump(IAssetsReader* pDumpReader, IAssetsWriter* pWriter) +{ + pDumpReader->Seek(AssetsSeek_Begin, 0); + unsigned char curChar = 0; + while (pDumpReader->Read(1, &curChar) && ((curChar <= 0x20) || (curChar & 0x80))) {} + pDumpReader->Seek(AssetsSeek_Begin, 0); + if (curChar == '{') + parseJSONDump(pDumpReader, pWriter); + else + parseTextDump(pDumpReader, pWriter); +} + +//Parses an integer type (valueType: ValueType_Int*, ValueType_UInt*) in base-10 string representation (valueStr) +// and writes it out. +//valueStr need not end after the number representation. +// +//Returns false iff the represented value is out of range for the type (otherwise true). +static bool parsePrimitiveInt(EnumValueTypes valueType, const char *valueStr, QWORD &filePos, IAssetsWriter* pWriter) +{ + switch (valueType) + { + case ValueType_Int8: + { + int32_t int8Tmp = strtol((char*)valueStr, NULL, 10); + if (int8Tmp < INT8_MIN || int8Tmp > INT8_MAX) + return false; + pWriter->Write(filePos, 1, &int8Tmp); filePos += 1; + } + break; + case ValueType_Int16: + { + int32_t int16Tmp = strtol((char*)valueStr, NULL, 10); + if (int16Tmp < INT16_MIN || int16Tmp > INT16_MAX) + return false; + pWriter->Write(filePos, 2, &int16Tmp); filePos += 2; + } + break; + case ValueType_Int32: + { + errno = 0; + static_assert(sizeof(long) == sizeof(int32_t), "Unsupported long size."); + int32_t int32Tmp = strtol((char*)valueStr, NULL, 10); + if ((int32Tmp == LONG_MIN || int32Tmp == LONG_MAX) && errno == ERANGE) + return false; + pWriter->Write(filePos, 4, &int32Tmp); filePos += 4; + } + break; + case ValueType_Int64: + { + static_assert(sizeof(long long) == sizeof(int64_t), "Unsupported long long size."); + errno = 0; + int64_t int64Tmp = strtoll((char*)valueStr, NULL, 10); + if ((int64Tmp == LLONG_MIN || int64Tmp == LLONG_MAX) && errno == ERANGE) + return false; + pWriter->Write(filePos, 8, &int64Tmp); filePos += 8; + } + break; + case ValueType_UInt8: + { + uint32_t uint8Tmp = strtoul((char*)valueStr, NULL, 10); + if (uint8Tmp > UINT8_MAX) + return false; + pWriter->Write(filePos, 1, &uint8Tmp); filePos += 1; + } + break; + case ValueType_UInt16: + { + uint32_t uint16Tmp = strtoul((char*)valueStr, NULL, 10); + if (uint16Tmp > UINT16_MAX) + return false; + pWriter->Write(filePos, 2, &uint16Tmp); filePos += 2; + } + break; + case ValueType_UInt32: + { + static_assert(sizeof(unsigned long) == sizeof(uint32_t), "Unsupported unsigned long size."); + errno = 0; + uint32_t uint32Tmp = strtoul((char*)valueStr, NULL, 10); + if (uint32Tmp == ULONG_MAX && errno == ERANGE) + return false; + pWriter->Write(filePos, 4, &uint32Tmp); filePos += 4; + } + break; + case ValueType_UInt64: + { + static_assert(sizeof(unsigned long long) == sizeof(uint64_t), "Unsupported unsigned long long size."); + errno = 0; + uint64_t uint64Tmp = strtoull((char*)valueStr, NULL, 10); + if (uint64Tmp == ULLONG_MAX && errno == ERANGE) + return false; + pWriter->Write(filePos, 8, &uint64Tmp); filePos += 8; + } + break; + } + return true; +} + +void AssetImportDumpTask::parseTextDump(IAssetsReader* pDumpReader, IAssetsWriter* pWriter) +{ + struct _TypeInfo + { + bool align; + short depth; + }; + std::vector<_TypeInfo> previousTypes; + std::vector lineBuffer; + auto readLine = [&lineBuffer, pDumpReader]() + { + size_t lineLen = -1; size_t lineBufferPos = 0; size_t readBytes = 0; + do { + lineBuffer.resize(lineBufferPos + 256); + readBytes = pDumpReader->Read(256, &lineBuffer[lineBufferPos]); + if (readBytes == 0) + break; + lineBuffer.resize(lineBufferPos + readBytes); + for (size_t i = 0; i < readBytes; i++) + { + if (lineBuffer[lineBufferPos + i] == '\n') + { + lineLen = lineBufferPos + i; + //fseek(pDumpFile, (long)(i+1+((nextCh=='\n')?1:0))-((long)readBytes), SEEK_CUR); + pDumpReader->Seek(AssetsSeek_Cur, (int64_t)((i + 1) - readBytes)); + lineBuffer.resize(lineBufferPos + i); + readBytes = i; + break; + } + } + lineBufferPos += readBytes; + } while (lineLen == -1); + if (!lineBuffer.empty() && lineBuffer.back() == '\r') + lineBuffer.pop_back(); + lineBuffer.push_back(0); + return (lineLen != -1); + }; + QWORD filePos = 0; + //Checks the depths of the previous lines. + //If the new line exits at least one depth level, + // the alignment of these depth levels is looked up and applied. + auto checkPrevDepthsAndAlign = [&previousTypes, &filePos, pWriter](short newDepth) { + short previousDepth; + if (previousTypes.size() > 0 && ((previousDepth = previousTypes.back().depth) > newDepth)) + { + //The new line exits at least one depth level compared to its predecessor. + //As the predecessor must be a value type (since it apparently does not have any children), + // we need to further check its predecessors to find out if alignment needs to be applied. + // (Value types have their alignment applied immediately). + for (size_t _i = previousTypes.size() - 2 + 1; _i > 0; _i--) + { + size_t i = _i - 1; + if (previousTypes[i].depth < newDepth) + break; //This line precedes opening the depth level for the new line. + if (previousTypes[i].depth < previousDepth) + { + //The depth level of this line's children is closed. + if (previousTypes[i].align) + { + uint32_t dwNull = 0; int paddingLen = 3 - ((filePos - 1) & 3); + pWriter->Write(filePos, paddingLen, &dwNull); filePos += paddingLen; + //No need to check further, as we are aligned to the 4 byte boundary. + break; + } + else + previousDepth = previousTypes[i].depth; + //The new line either closes this line's depth level, too, + // or is a direct neighbour to this line. + } + if (previousTypes[i].depth == newDepth) + break; //Found a preceding neighbour, so the lookup can stop now. + } + } + }; + size_t numLine = 0; + auto getLineMessage = [&numLine]() { + return std::string("Line ") + std::to_string(numLine) + ": "; + }; + while (true) + { + lineBuffer.clear(); + if (!readLine()) + break; + ++numLine; + assert(lineBuffer.size() > 0 && lineBuffer.back() == 0); + size_t lineLen = lineBuffer.size() - 1; + _TypeInfo ti; ti.depth = -1; + for (size_t i = 0; i < lineBuffer.size() - 1; i++) + { + if (lineBuffer[i] != ' ') + { + ti.depth = (i > SHRT_MAX) ? -1 : (short)i; + break; + } + } + if (ti.depth == -1) + continue; + if (lineBuffer[ti.depth] != '0' && lineBuffer[ti.depth] != '1') //no alignment info (either invalid file or array index item) + continue; //as it neither has a value nor specifies alignment, we can ignore this + if (ti.depth + 2 >= lineBuffer.size()) + continue; + ti.align = lineBuffer[ti.depth] == '1'; + char8_t* fieldType = &lineBuffer[ti.depth + 2]; + char8_t* fieldName = nullptr; size_t fieldNamePos = 0; + size_t _tmpTypeBegin = ti.depth + 2; + //"unsigned int" is the only built-in type with a space in its name. + if (!_strnicmp((char*)fieldType, "unsigned ", 9)) + { + _tmpTypeBegin = ti.depth + 2 + 9; + } + for (size_t lineBufferPos = _tmpTypeBegin; lineBufferPos < lineLen; lineBufferPos++) + { + if (lineBuffer[lineBufferPos] == ' ') + { + fieldName = &lineBuffer[lineBufferPos + 1]; + fieldNamePos = lineBufferPos + 1; + break; + } + } + + checkPrevDepthsAndAlign(ti.depth); + previousTypes.push_back(ti); + + if (fieldName == nullptr) + continue; + + size_t valueBeginPos = 0; + for (size_t lineBufferPos = fieldNamePos; lineBufferPos < lineLen; lineBufferPos++) + { + if (lineBuffer[lineBufferPos] == '=') + { + bool hasAdditionalSpace = false; + if ((lineBufferPos + 1) < lineLen) + { + if (lineBuffer[lineBufferPos + 1] == ' ') + hasAdditionalSpace = true; + } + else + break; + valueBeginPos = lineBufferPos + (hasAdditionalSpace ? 2 : 1); + break; + } + } + if (valueBeginPos != 0) + { + size_t valueEndPos = valueBeginPos; + for (size_t _i = lineLen; _i > valueBeginPos; --_i) + { + size_t i = _i - 1; + if (lineBuffer[i] > 0x20) + { + valueEndPos = i + 1; + break; + } + } + lineBuffer[fieldNamePos - 1] = 0; //so GetValueTypeByTypeName can find the type + char8_t* value = &lineBuffer[valueBeginPos]; + EnumValueTypes valueType = GetValueTypeByTypeName((char*)fieldType); + switch (valueType) + { + case ValueType_Bool: + { + uint8_t boolTmp = !_strnicmp((char*)value, "true", 4) ? 1 : 0; + pWriter->Write(filePos, 1, &boolTmp); filePos += 1; + } + break; + case ValueType_Int8: + case ValueType_Int16: + case ValueType_Int32: + case ValueType_Int64: + case ValueType_UInt8: + case ValueType_UInt16: + case ValueType_UInt32: + case ValueType_UInt64: + { + if (!parsePrimitiveInt(valueType, (char*)value, filePos, pWriter)) + throw AssetUtilError(getLineMessage() + "Primitive value out of range."); + } + break; + case ValueType_Float: + { + float floatTmp; + if (!parseFloatStr(floatTmp, std::string(&lineBuffer[valueBeginPos], &lineBuffer[valueEndPos]))) + throw AssetUtilError(std::string("Line ") + std::to_string(numLine) + ": Unable to parse value."); + pWriter->Write(filePos, 4, &floatTmp); filePos += 4; + } + break; + case ValueType_Double: + { + double doubleTmp; + if (!parseFloatStr(doubleTmp, std::string(&lineBuffer[valueBeginPos], &lineBuffer[valueEndPos]))) + throw AssetUtilError(std::string("Line ") + std::to_string(numLine) + ": Unable to parse value."); + pWriter->Write(filePos, 8, &doubleTmp); filePos += 8; + } + break; + case ValueType_String: + { + if (((valueBeginPos + 1) > (size_t)lineLen) || (lineBuffer[valueBeginPos] != '"')) + break; + value = &value[1]; + for (size_t lineBufferPos = (size_t)lineLen - 1; lineBufferPos > valueBeginPos; lineBufferPos--) + { + if (lineBuffer[lineBufferPos] == '"') + { + lineBuffer[lineBufferPos] = 0; + break; + } + } + size_t valueLen = strlen((char*)value); + std::vector valueBuffer(valueLen + 1); + size_t valueBufferIndex = 0; + for (size_t i = 0; i < valueLen; i++) + { + if (value[i] == '\\' && ((i + 1) < (valueLen))) + { + switch (value[i + 1]) + { + case '\\': + valueBuffer[valueBufferIndex++] = '\\'; + i++; + break; + case 'r': + valueBuffer[valueBufferIndex++] = '\r'; + i++; + break; + case 'n': + valueBuffer[valueBufferIndex++] = '\n'; + i++; + break; + default: + valueBuffer[valueBufferIndex++] = value[i]; + break; + } + } + else + { + valueBuffer[valueBufferIndex++] = value[i]; + } + } + pWriter->Write(filePos, 4, &valueBufferIndex); filePos += 4; + pWriter->Write(filePos, valueBufferIndex, valueBuffer.data()); filePos += valueBufferIndex; + } + break; + default: + valueType = ValueType_None; + break; + } + if (valueType != ValueType_None) + { + if (ti.align) + { + uint32_t dwNull = 0; int paddingLen = 3 - ((filePos - 1) & 3); + pWriter->Write(filePos, paddingLen, &dwNull); filePos += paddingLen; + } + } + } + } + checkPrevDepthsAndAlign(0); +} + +#define JSMN_PARENT_LINKS +#define JSMN_STATIC +#include +void AssetImportDumpTask::parseJSONDump(IAssetsReader* pDumpReader, IAssetsWriter* pWriter) +{ + QWORD filePos = 0; + jsmn_parser parser; + jsmn_init(&parser); + pDumpReader->Seek(AssetsSeek_End, 0); + QWORD jsonLen = 0; + pDumpReader->Tell(jsonLen); + pDumpReader->Seek(AssetsSeek_Begin, 0); + //fseek(pDumpFile, 0, SEEK_END); + //size_t jsonLen = (size_t)ftell(pDumpFile); + //fseek(pDumpFile, 0, SEEK_SET); + std::vector jsonBuffer(jsonLen + 1); + jsonLen = pDumpReader->Read(jsonLen, jsonBuffer.data()); + //jsonLen = fread(jsonBuffer, 1, jsonLen, pDumpFile); + if (jsonLen == 0) + { + throw AssetUtilError("Unable to read the file."); + return; + } + jsonBuffer[jsonLen] = 0; + std::vector tokens(16); size_t actualTokenCount = 0; + while (true) + { + int err = jsmn_parse(&parser, jsonBuffer.data(), (size_t)jsonLen, &tokens[0], (unsigned int)tokens.size()); + size_t startIndex = actualTokenCount; + if (err < 0) + { + switch (err) + { + default: + case JSMN_ERROR_INVAL: + case JSMN_ERROR_PART: + throw AssetUtilError("The JSON file is invalid or cut off or bigger than 2 GiB."); + case JSMN_ERROR_NOMEM: + actualTokenCount = (size_t)parser.toknext; + if (tokens.size() > INT_MAX) + throw AssetUtilError("The JSON token count is out of range."); + tokens.resize(std::min(INT_MAX, tokens.size() * 2)); + break; + } + } + else + { + actualTokenCount = (size_t)err; + break; + } + } + if (actualTokenCount > INT_MAX) + throw AssetUtilError("The JSON token count is out of range."); + bool doubleConvertErr = false; + for (size_t i = 0; i < actualTokenCount; i++) + { + jsmntok_t& token = tokens[i]; + //Safety checks; Probably not required because jsmn shouldn't allow such cases. + if ((token.start < 0) || (token.start > token.end) || ((unsigned long)token.end > jsonLen) || (token.parent != -1 && token.parent >= i)) + throw AssetUtilError("Invalid JSON file."); + switch (token.type) + { + case JSMN_ARRAY: + { + if (token.parent == -1 || tokens[token.parent].type != JSMN_STRING) //Has no name + throw AssetUtilError("UABE json dump not as expected; A JSON array is missing a name."); + unsigned int count = (unsigned int)token.size; + pWriter->Write(filePos, 4, &count); filePos += 4; + } + break; + case JSMN_STRING: + { + if (jsonBuffer[token.end] != '"') + throw AssetUtilError("Unable to parse a JSON string object."); + if (/*(token.parent == -1 || tokens[token.parent].type != 5) + && */((int)actualTokenCount > (i + 1)) && tokens[i + 1].parent == i) //Is a token name. + { + if ((token.end - token.start) < 3 + || (jsonBuffer[token.start] != '0' && jsonBuffer[token.start] != '1') + || jsonBuffer[token.start + 1] != ' ') + { + throw AssetUtilError("UABE json dump not as expected; Field name format is unexpected."); + } + jsonBuffer[token.end] = 0; + //token.type = (jsmntype_t)5; //Custom type for names. + break; + } + //Is a string value. + bool parsed = false; + //Handle non-JSON primitives (depending on the type determined by the JSON field name). + if (token.parent != -1 && tokens[token.parent].type == JSMN_STRING) + { + jsmntok_t& nameToken = tokens[token.parent]; + int size = nameToken.end - nameToken.start; + int start = nameToken.start + 2; + if (size > 11 && !strncmp(&jsonBuffer[start], "unsigned ", 9)) + start += 9; + for (int i = start; i < nameToken.end; i++) + { + if (jsonBuffer[i] == ' ') + { + jsonBuffer[i] = 0; + break; + } + } + EnumValueTypes valueType = GetValueTypeByTypeName(&jsonBuffer[nameToken.start + 2]); + char* value = &jsonBuffer[token.start]; + switch (valueType) + { + case ValueType_Int64: + case ValueType_UInt64: + if (!parsePrimitiveInt(valueType, value, filePos, pWriter)) + throw AssetUtilError("Primitive value out of range."); + parsed = true; + break; + case ValueType_Float: + { + float floatTmp; + if (!parseFloatStr(floatTmp, "\"" + std::string(&jsonBuffer[token.start], &jsonBuffer[token.end]) + "\"")) + throw AssetUtilError("Unable to parse float value."); + pWriter->Write(filePos, 4, &floatTmp); filePos += 4; + } + parsed = true; + break; + case ValueType_Double: + { + double doubleTmp; + if (!parseFloatStr(doubleTmp, "\"" + std::string(&jsonBuffer[token.start], &jsonBuffer[token.end]) + "\"")) + throw AssetUtilError("Unable to parse float value."); + pWriter->Write(filePos, 8, &doubleTmp); filePos += 8; + } + parsed = true; + break; + } + } + if (parsed) + break; + // Handle escaped string values. + QWORD outStartPos = filePos; + unsigned int inCount = 0; + unsigned int outCount = 0; + pWriter->Write(filePos, 4, &outCount); filePos += 4; + for (int i = token.start; i < token.end; i++) + { + if (jsonBuffer[i] != '\\') + continue; + //Write the pending characters. + pWriter->Write(filePos, i - (token.start + inCount), &jsonBuffer[token.start + inCount]); + filePos += i - (token.start + inCount); + inCount = i - token.start; + //inCount now points to the '\\' character. + if ((i + 1) >= token.end) + break; + i++; + //Parse the escape sequence. + switch (jsonBuffer[i]) + { + case '"': + case '\\': + case '/': + //The character will be directly copied (next Write call). + //Increment inCount beyond the '\\' character. + inCount++; + break; + case 'b': + pWriter->Write(filePos, 1, "\b"); + filePos++; inCount += 2; + break; + case 'f': + pWriter->Write(filePos, 1, "\f"); + filePos++; inCount += 2; + break; + case 'n': + pWriter->Write(filePos, 1, "\n"); + filePos++; inCount += 2; + break; + case 'r': + pWriter->Write(filePos, 1, "\r"); + filePos++; inCount += 2; + break; + case 't': + pWriter->Write(filePos, 1, "\t"); + filePos++; inCount += 2; + break; + case 'u': + //Should also suppate UTF-16 surrogate pairs. + if ((i + 5) >= token.end || (i + 5) < 0) + break; + { + wchar_t fullCharacter[2] = { 0,0 }; + size_t endIdx = SIZE_MAX; + bool valid = false; + try { + //Parse primary "\\u" sequence. + wchar_t firstChar = (wchar_t) + std::stoul(std::string(&jsonBuffer[i + 1], &jsonBuffer[i + 1 + 4]), &endIdx, 16); + valid = (endIdx == 4); + i += 4; inCount += 6; + if (valid) + { + fullCharacter[0] = firstChar; + if (firstChar >= 0xD800 && firstChar <= 0xDFFF) + { + //Look for and parse secondary "\\u" sequence. + if ((i + 6) < token.end && (i + 6) > 0 && jsonBuffer[i + 1] == '\\' && jsonBuffer[i + 2] == 'u') + { + wchar_t secondChar = (wchar_t) + std::stoul(std::string(&jsonBuffer[i + 3], &jsonBuffer[i + 3 + 4]), &endIdx, 16); + valid = (endIdx == 4); + i += 6; inCount += 6; + fullCharacter[1] = secondChar; + } + else + valid = false; + } + } + } + catch (std::invalid_argument) { valid = false; } + catch (std::out_of_range) { valid = false; } + if (!valid) + throw AssetUtilError("Unable to parse a JSON \\u sequence (invalid characters?)"); + std::string utf8Str = std::wstring_convert>(""). + to_bytes(&fullCharacter[0], &fullCharacter[fullCharacter[1] ? 2 : 1]); + if (utf8Str.empty()) + throw AssetUtilError("Unable to convert a JSON \\u sequence (invalid UTF-16?)"); + pWriter->Write(filePos, utf8Str.size() * sizeof(char), utf8Str.data()); + filePos += utf8Str.size() * sizeof(char); + } + break; + } + } + pWriter->Write(filePos, token.end - (token.start + inCount), &jsonBuffer[token.start + inCount]); + filePos += token.end - (token.start + inCount); + outCount = (unsigned int)(filePos - outStartPos - 4); + pWriter->Write(outStartPos, 4, &outCount); + } + break; + case JSMN_PRIMITIVE: + { + jsonBuffer[token.end] = 0; + EnumValueTypes valueType = ValueType_None; + if (token.parent != -1) + { + if (tokens[token.parent].type == JSMN_STRING) //parent is the name + { + int size = tokens[token.parent].end - tokens[token.parent].start; + int start = tokens[token.parent].start + 2; + if (size > 11 && !strncmp(&jsonBuffer[start], "unsigned ", 9)) + start += 9; + for (int i = start; i < tokens[token.parent].end; i++) + { + if (jsonBuffer[i] == ' ') + { + jsonBuffer[i] = 0; + break; + } + } + valueType = GetValueTypeByTypeName(&jsonBuffer[tokens[token.parent].start + 2]); + } + else if (tokens[token.parent].type == JSMN_ARRAY) //byte array + { + valueType = ValueType_UInt8; + } + } + if (valueType == ValueType_None) + throw AssetUtilError("Encountered an unknown primitive value type."); + char* value = &jsonBuffer[token.start]; + switch (valueType) + { + case ValueType_Bool: + { + uint8_t boolTmp = (!_strnicmp(value, "1", 1) || !_strnicmp(value, "true", 4)) ? 1 : 0; + pWriter->Write(filePos, 1, &boolTmp); filePos += 1; + } + break; + case ValueType_Int8: + case ValueType_Int16: + case ValueType_Int32: + case ValueType_Int64: + case ValueType_UInt8: + case ValueType_UInt16: + case ValueType_UInt32: + case ValueType_UInt64: + { + if (!parsePrimitiveInt(valueType, value, filePos, pWriter)) + throw AssetUtilError("Primitive value out of range."); + } + break; + case ValueType_Float: + { + float floatTmp; + if (!parseFloatStr(floatTmp, std::string(&jsonBuffer[token.start], &jsonBuffer[token.end]))) + throw AssetUtilError("Unable to parse float value."); + pWriter->Write(filePos, 4, &floatTmp); filePos += 4; + } + break; + case ValueType_Double: + { + double doubleTmp; + if (!parseFloatStr(doubleTmp, std::string(&jsonBuffer[token.start], &jsonBuffer[token.end]))) + throw AssetUtilError("Unable to parse float value."); + pWriter->Write(filePos, 8, &doubleTmp); filePos += 8; + } + break; + } + } + } + //Alignment + { + //Because a value token's name is its parent token, this works when the next token is a direct neighbour or a parent's neighbour. + int target = -1; + if ((i + 1) < actualTokenCount) + { + if (tokens[i + 1].parent == i) + //Don't need to check because : + //1) i is a JSMN_OBJECT, i+1 a child, + //or 2) i is a JSMN_STRING and the name of i+1, so i isn't represented in the binary and doesn't need alignment. + continue; + target = tokens[i + 1].parent; + } + jsmntok_t& curParentToken = token; + while ((curParentToken.parent != -1) && (curParentToken.parent > target)) + { + curParentToken = tokens[curParentToken.parent]; + if (curParentToken.type == JSMN_STRING) //The name of i or one of i's parents. + { + if (jsonBuffer[curParentToken.start] == '1') //Alignment indicator is on. + { + QWORD alignCount = (4 - (filePos & 3)) & 3; + if (alignCount > 0) + { + uint32_t nullDw = 0; + pWriter->Write(filePos, alignCount, &nullDw); filePos += alignCount; + } + break; //Don't need to continue as another alignment is a redundant one. + } + } + } + } + } +} diff --git a/UABE_Generic/AssetPluginUtil.h b/UABE_Generic/AssetPluginUtil.h new file mode 100644 index 0000000..1a81f44 --- /dev/null +++ b/UABE_Generic/AssetPluginUtil.h @@ -0,0 +1,308 @@ +#pragma once +#include "AssetIterator.h" +#include "AsyncTask.h" +#include "../AssetsTools/AssetTypeClass.h" +#include "IAssetBatchImportDesc.h" +#include "api.h" +#include +#include +#include +#include + +struct AssetUtilDesc +{ + AssetIdentifier asset; + std::string assetsFileName; + std::string assetName; + //Generates an export file path for the current asset. + //nameCountBuf: Counts how often each assetName has been processed to avoid duplicates in file names. + // -> Only used if asset.pathID == 0. + //extension: e.g. ".dat", or "" for no extension. + //baseDir: Base directory without trailing directory separator. + UABE_Generic_API std::string makeExportFilePath(std::unordered_map& nameCountBuffer, + const std::string &extension, std::string baseDir = "") const; +}; + +//Exception type for AssetExportTask and AssetImportTask. +class AssetUtilError : public std::exception +{ + std::string desc; + bool mayStop; +public: + //mayStop: For AssetExportTask::execute and AssetImportTask::execute - + // Stop the task if stopOnError is set for the task type. + inline AssetUtilError(std::string _desc, bool mayStop = false) + : desc(std::move(_desc)), mayStop(mayStop) + {} + inline const char* what() + { + return desc.c_str(); + } + inline bool getMayStop() + { + return mayStop; + } +}; + +inline void FilterNameForExportInplace(std::string& str) +{ + for (size_t i = 0; i < str.size(); i++) + { + switch (str[i]) + { + case '\\': case '/': case ':': case '?': case '\"': case '<': case '>': case '|': + case '-': + str[i] = '_'; + break; + default: + break; + } + } +} +UABE_Generic_API std::string MakeAssetExportName(pathid_t pathID, std::string assetName, + std::unordered_map& nameCountBuffer, + std::string assetsFileName = ""); + +//Finds the loaded resources file with the given name based on a referencing asset, +// or throws an AssetUtilError if no matches are found. +//If several candidates are found, an arbitrary one is returned. +//If progressManager is given, a warning will be logged if several candidates are found. +UABE_Generic_API std::shared_ptr FindResourcesFile(class AppContext& appContext, + const std::string& streamDataFileName, AssetIdentifier& asset, + std::optional> progressManager); + +//Thread-safe cache for type templates. +//If a plugin processes many assets of the same type, +// this class can be used to reduce the total overhead of type template generation. +class TypeTemplateCache +{ + struct ClassIdentifier + { + unsigned int fileID; + int32_t classID; + inline bool operator==(const ClassIdentifier& other) const + { + return (fileID == other.fileID) + && (classID == other.classID); + } + }; + struct ClassIdentifierHasher + { + inline size_t operator()(const ClassIdentifier& entry) const + { + return std::hash()(entry.classID | (((uint64_t)entry.fileID) << 32)); + } + }; + std::shared_mutex templateCacheMutex; + std::unordered_map, ClassIdentifierHasher> templateCache; +public: + //Throws an AssetUtilError if desc.asset cannot be resolved. + //The caller can modify the template field, + // but has to take concurrent callers accessing the same field into account. + inline AssetTypeTemplateField& getTemplateField(class AppContext& appContext, class AssetIdentifier& asset) + { + return getTemplateField(appContext, asset, [](AssetTypeTemplateField&) {}); + } + + //Throws an AssetUtilError if desc.asset cannot be resolved. + //Calls newTemplateCallback if a template is first encountered. + // (Concurrent calls with a new asset fileID/classID combo may trigger two calls for equivalent templates). + UABE_Generic_API AssetTypeTemplateField& getTemplateField(class AppContext& appContext, class AssetIdentifier& asset, + std::function newTemplateCallback); +}; + +class AssetExportTask : public ITask +{ + std::vector assets; + std::string taskName; + std::string baseDir; + std::string extension; + bool stopOnError; + bool writeOnCompletionOnly; +public: + //If assets.size() == 1, baseDir will be used as the full path for the asset. + //Otherwise, the output paths will be generated via AssetUtilDesc::makeExportFilePath(..). + //extension: File extension, e.g. ".dat", or "" for no extension. + //writeOnCompletionOnly: If set, no individual output paths will be generated regardless of whether assets.size() > 1. + // The given baseDir is passed on to onCompletion unless the task was stopped due to an error. + UABE_Generic_API AssetExportTask(std::vector assets, std::string taskName, + std::string extension, std::string baseDir = "", bool stopOnError = false, + bool writeOnCompletionOnly = false); + UABE_Generic_API const std::string& getName(); + UABE_Generic_API TaskResult execute(TaskProgressManager& progressManager); + //Can be called directly, without a preceding execute call. + //asset: Asset to export (not required to be within the assets vector). + //path: Output file path. + UABE_Generic_API virtual bool exportAsset(AssetUtilDesc& asset, std::string path, std::optional> progressManager = {}) = 0; + UABE_Generic_API virtual void onCompletion(const std::string &outputPath, std::optional> progressManager); +}; + +class AssetExportRawTask : public AssetExportTask +{ +public: + UABE_Generic_API AssetExportRawTask(std::vector assets, std::string taskName, + std::string extension, std::string baseDir = "", bool stopOnError = false); + inline ~AssetExportRawTask() noexcept(true) {} + UABE_Generic_API bool exportAsset(AssetUtilDesc& desc, std::string path, std::optional> progressManager = {}); +}; + +class AssetExportDumpTask : public AssetExportTask +{ + class AppContext& appContext; + virtual void dumpAsset(IAssetsReader* pReader, AssetTypeValueField* pBaseField, IAssetsWriter* pDumpWriter)=0; +public: + UABE_Generic_API AssetExportDumpTask(class AppContext& appContext, + std::vector assets, std::string taskName, + std::string extension, std::string baseDir = "", bool stopOnError = false); + inline ~AssetExportDumpTask() noexcept(true) {} + UABE_Generic_API bool exportAsset(AssetUtilDesc& desc, std::string path, std::optional> progressManager = {}); +}; + +class AssetExportTextDumpTask : public AssetExportDumpTask +{ + void recursiveDumpAsset(IAssetsReader* pReader, AssetTypeValueField* pField, size_t depth, + IAssetsWriter *pDumpWriter, std::string &lineBuf); + void dumpAsset(IAssetsReader* pReader, AssetTypeValueField* pBaseField, IAssetsWriter* pDumpWriter); +public: + UABE_Generic_API AssetExportTextDumpTask(class AppContext& appContext, + std::vector assets, std::string taskName, + std::string extension, std::string baseDir = "", bool stopOnError = false); +}; + +class AssetExportJSONDumpTask : public AssetExportDumpTask +{ + void recursiveDumpAsset(IAssetsReader* pReader, AssetTypeValueField* pField, size_t depth, + IAssetsWriter* pDumpWriter, std::string& lineBuf, bool dumpValueOnly = false); + void dumpAsset(IAssetsReader* pReader, AssetTypeValueField* pBaseField, IAssetsWriter* pDumpWriter); +public: + UABE_Generic_API AssetExportJSONDumpTask(class AppContext& appContext, + std::vector assets, std::string taskName, + std::string extension, std::string baseDir = "", bool stopOnError = false); +}; + +inline std::string MakeImportFileNameRegex(const std::string& extension) +{ + //(?:assetName-)?(assetsFileName)-(pathId) + //With repeatCount : regexBase = "(?:.*?)-((?:.)*)-((?:\\d)*)(?:-(?:\\d)*)?"; + return std::string("(?:.*?)-([^-]*)-((?:|-)(?:\\d)*)") + extension; +} +inline bool RetrieveImportRegexInfo(IN std::vector& capturingGroups, OUT const char*& assetsFileName, OUT pathid_t& pathID) +{ + assetsFileName = nullptr; pathID = 0; + if (capturingGroups.size() < 2) return false; + if (capturingGroups[0] == nullptr || capturingGroups[1] == nullptr) return false; + assetsFileName = capturingGroups[0]; + + *_errno() = 0; + const char* pathIDGroup = capturingGroups[1]; + pathID = static_cast(_strtoi64(pathIDGroup, NULL, 0)); + if (errno == ERANGE) + { + *_errno() = 0; + pathID = _strtoui64(pathIDGroup, NULL, 0); + } + if (errno == ERANGE) + { + pathID = 0; + return false; + } + return true; +} + +class CGenericBatchImportDialogDesc : public IAssetBatchImportDesc +{ + inline std::string& getElementDescription(size_t i) + { + return elements[i].assetName; + } + std::vector elements; + std::unordered_multimap elementByPathID; +public: + inline const std::vector& getElements() const + { + return elements; + } + std::string regex; + + std::vector importFilePaths; + std::vector importFilePathOverrides; + + inline std::vector clearAndGetElements() + { + elementByPathID.clear(); + importFilePaths.clear(); + importFilePathOverrides.clear(); + std::vector ret; + elements.swap(ret); + return ret; + } +public: + UABE_Generic_API CGenericBatchImportDialogDesc(std::vector _elements, const std::string& extensionRegex); + UABE_Generic_API bool GetImportableAssetDescs(OUT std::vector& nameList); + UABE_Generic_API bool GetFilenameMatchStrings(OUT std::vector& regexList, OUT bool& checkSubDirs); + UABE_Generic_API bool GetFilenameMatchInfo(IN const char* filename, IN std::vector& capturingGroups, OUT size_t& matchIndex); + UABE_Generic_API void SetInputFilepath(IN size_t matchIndex, IN const char* filepath); + UABE_Generic_API bool HasFilenameOverride(IN size_t matchIndex, OUT std::string& filenameOverride, OUT bool& relativeToBasePath); + //Returns full file paths. + inline std::vector getImportFilePaths() + { + std::vector ret(importFilePaths.size()); + for (size_t i = 0; i < importFilePaths.size(); ++i) + { + ret[i] = getImportFilePath(i); + } + return ret; + } + inline std::string getImportFilePath(size_t i) + { + if (importFilePathOverrides.size() > i && !importFilePathOverrides[i].empty()) + return importFilePathOverrides[i]; + else + return importFilePaths[i]; + } +}; + +class AssetImportTask : public ITask +{ + std::vector assets; + std::vector importFilePaths; + std::string taskName; + bool stopOnError; +public: + //If assets.size() == 1, baseDir will be used as the full path for the asset. + //Otherwise, the output paths will be generated via AssetUtilDesc::makeExportFilePath(..). + //extension: File extension, e.g. ".dat", or "" for no extension. + UABE_Generic_API AssetImportTask(std::vector assets, std::vector importFilePaths, + std::string taskName, bool stopOnError = false); + UABE_Generic_API const std::string& getName(); + UABE_Generic_API TaskResult execute(TaskProgressManager& progressManager); + //Can be called directly, without a preceding execute call. + //asset: Asset to export (not required to be within the assets vector). + //path: Output file path. + UABE_Generic_API virtual bool importAsset(AssetUtilDesc& asset, std::string path, std::optional> progressManager = {}) = 0; +}; + +class AssetImportRawTask : public AssetImportTask +{ + AppContext& appContext; +public: + UABE_Generic_API AssetImportRawTask(AppContext& appContext, + std::vector assets, std::vector importFilePaths, + std::string taskName, bool stopOnError = false); + UABE_Generic_API bool importAsset(AssetUtilDesc& asset, std::string path, std::optional> progressManager = {}); +}; + +class AssetImportDumpTask : public AssetImportTask +{ + AppContext& appContext; +public: + UABE_Generic_API AssetImportDumpTask(AppContext& appContext, + std::vector assets, std::vector importFilePaths, + std::string taskName, bool stopOnError = false); + UABE_Generic_API bool importAsset(AssetUtilDesc& asset, std::string path, std::optional> progressManager = {}); + + UABE_Generic_API void parseTextDump(IAssetsReader* pDumpReader, IAssetsWriter* pWriter); + UABE_Generic_API void parseJSONDump(IAssetsReader* pDumpReader, IAssetsWriter* pWriter); + UABE_Generic_API void parseDump(IAssetsReader* pDumpReader, IAssetsWriter* pWriter); +}; + diff --git a/UABE_Generic/AsyncTask.cpp b/UABE_Generic/AsyncTask.cpp new file mode 100644 index 0000000..4058674 --- /dev/null +++ b/UABE_Generic/AsyncTask.cpp @@ -0,0 +1,413 @@ +#include "AsyncTask.h" +#include +#include + +ITask::ITask() +{} +ITask::~ITask() +{} +void ITask::onEnqueue(TaskProgressManager &progressManager) +{} +bool ITask::isReady() +{ + return true; +} + +TaskProgressCallback::TaskProgressCallback() {} +TaskProgressCallback::~TaskProgressCallback() {} +void TaskProgressCallback::OnAdd(std::shared_ptr &pTask){} +void TaskProgressCallback::OnProgress(std::shared_ptr &pTask, unsigned int progress, unsigned int range){} +void TaskProgressCallback::OnProgressDesc(std::shared_ptr &pTask, const std::string &desc){} +void TaskProgressCallback::OnLogMessage(std::shared_ptr &pTask, const std::string &msg){} +void TaskProgressCallback::OnCompletion(std::shared_ptr &pTask, TaskResult result){} +void TaskProgressCallback::OnCancelableChange(std::shared_ptr& pTask, bool cancelable) {} + +TaskProgressManager::TaskProgressManager(TaskManager *pManager, std::shared_ptr _pTask) + : pManager(pManager), pTask(std::move(_pTask)), cancelable(false), canceled(false) +{} + +std::shared_ptr &TaskProgressManager::getTask() +{ + return this->pTask; +} +void TaskProgressManager::setProgress(unsigned int progress, unsigned int range) +{ + this->pManager->LockCallbacksDelete(); + std::unique_lock taskListLock(this->pManager->taskListMutex); + std::vector tempCallbacks(this->pManager->callbacks); + taskListLock.unlock(); + + for (size_t i = 0; i < tempCallbacks.size(); i++) + tempCallbacks[i]->OnProgress(this->pTask, progress, range); + + this->pManager->UnlockCallbacksDelete(); +} +void TaskProgressManager::setProgressDesc(const std::string &desc) +{ + this->pManager->LockCallbacksDelete(); + std::unique_lock taskListLock(this->pManager->taskListMutex); + std::vector tempCallbacks(this->pManager->callbacks); + taskListLock.unlock(); + + for (size_t i = 0; i < tempCallbacks.size(); i++) + tempCallbacks[i]->OnProgressDesc(this->pTask, desc); + + this->pManager->UnlockCallbacksDelete(); +} +void TaskProgressManager::logMessage(const std::string &msg) +{ + this->pManager->LockCallbacksDelete(); + std::unique_lock taskListLock(this->pManager->taskListMutex); + std::vector tempCallbacks(this->pManager->callbacks); + taskListLock.unlock(); + + for (size_t i = 0; i < tempCallbacks.size(); i++) + tempCallbacks[i]->OnLogMessage(this->pTask, msg); + + this->pManager->UnlockCallbacksDelete(); +} +void TaskProgressManager::setCancelable() +{ + bool prevCancelable = this->cancelable.exchange(true); + if (prevCancelable) + return; + this->pManager->LockCallbacksDelete(); + std::unique_lock taskListLock(this->pManager->taskListMutex); + std::vector tempCallbacks(this->pManager->callbacks); + taskListLock.unlock(); + + for (size_t i = 0; i < tempCallbacks.size(); i++) + tempCallbacks[i]->OnCancelableChange(this->pTask, true); + + this->pManager->UnlockCallbacksDelete(); +} +bool TaskProgressManager::isCanceled() +{ + return this->canceled.load(); +} + +struct TaskThreadParam +{ + TaskManager *pThis; + unsigned int threadIdx; +}; +void TaskManager::TaskThread(void *param) +{ + TaskManager *pThis; + unsigned int threadIdx; + { + TaskThreadParam *pParam = (TaskThreadParam*)param; + pThis = pParam->pThis; + threadIdx = pParam->threadIdx; + delete pParam; + } + ++pThis->nThreadsReady; + bool lastHadTask = false; + //HANDLE waitHandles[2] = {pThis->hNewTaskEvent, pThis->hThreadCloseEvent}; + while (true) + { + std::unique_lock threadEventLock(pThis->threadEventMutex); + if (!lastHadTask) + { + pThis->threadEventVar.wait(threadEventLock, [pThis]() {return pThis->newTaskEvent || pThis->threadCloseEvent; }); + } + //if (lastHadTask || WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE) == WAIT_OBJECT_0) + if (lastHadTask || pThis->newTaskEvent) + { + //Have to unlock threadEventLock here to prevent deadlocks + // if the owner of taskListMutex waits for threadEventMutex. + threadEventLock.unlock(); + std::unique_lock taskListLock(pThis->taskListMutex); + if (pThis->tasks.size() < 1) + { + //newTaskEvent is always set within + pThis->newTaskEvent = false; + taskListLock.unlock(); + lastHadTask = false; + } + else + { + //New tasks are always pushed to the front. + //std::list has persistent iterators, i.e. the iterator stays valid until the element is removed. + auto elemIterator = pThis->tasks.begin(); + while (!elemIterator->taken && !elemIterator->pTask->isReady()) { + elemIterator++; + } + bool taken = (elemIterator == pThis->tasks.end()) || (elemIterator->taken); + std::shared_ptr pCurTask; + TaskProgressManager *pCurProgMgr; + if (!taken) + { + TaskDesc &curTaskDesc = *elemIterator; + pCurTask = curTaskDesc.pTask; + pCurProgMgr = curTaskDesc.pProgMgr; + //Take this task and move it to the back. + curTaskDesc.taken = true; + + TaskDesc curTaskDescCopy = curTaskDesc; + pThis->tasks.erase(elemIterator); + pThis->tasks.push_back(curTaskDescCopy); + elemIterator = pThis->tasks.end(); + --elemIterator; + + --pThis->nThreadsReady; + ++pThis->nThreadsWorking; + //Reset the new task event if no more tasks are waiting. + if (pThis->tasks.size() >= 1 && pThis->tasks.front().taken) + pThis->newTaskEvent = false; + } + taskListLock.unlock(); + if (!taken) + { + pThis->LockCallbacksDelete(); + taskListLock.lock(); + std::vector tempCallbacks(pThis->callbacks); + taskListLock.unlock(); + + for (size_t i = 0; i < tempCallbacks.size(); i++) + tempCallbacks[i]->OnCancelableChange(pCurTask, false); + tempCallbacks.clear(); + pThis->UnlockCallbacksDelete(); + + //Execute the task. + TaskResult result = pCurTask->execute(*pCurProgMgr); + + pThis->LockCallbacksDelete(); + taskListLock.lock(); + tempCallbacks = pThis->callbacks; + //Remove the task from the list. + pThis->tasks.erase(elemIterator); + taskListLock.unlock(); + + for (size_t i = 0; i < tempCallbacks.size(); i++) + tempCallbacks[i]->OnCompletion(pCurTask, result); + pThis->UnlockCallbacksDelete(); + + + delete pCurProgMgr; + + --pThis->nThreadsWorking; + ++pThis->nThreadsReady; + lastHadTask = true; + } + else + lastHadTask = false; + } + } + else if (threadIdx >= pThis->nMaxThreads) + break; + else + { + threadEventLock.unlock(); + using namespace std::chrono_literals; + std::this_thread::sleep_for(10ms); + } + } + --pThis->nThreadsReady; + --pThis->nThreads; + { + std::scoped_lock lock(pThis->threadEventMutex); + pThis->threadClosedEvent = true; + } + pThis->threadClosedEventVar.notify_all(); + --pThis->nThreadsCommit; + return; +} +void TaskManager::LockCallbacksDelete() +{ + //Make sure no callbacks are being removed while we operate. Busy waiting. + while (true) { + ++this->nThreadsProcessingCallbacks; + //Alternative : Surround increment and if condition in critical section (also see removeCallback). + std::atomic_thread_fence(std::memory_order::seq_cst); + if (this->nCallbackRemovalsInProgress) + { + --this->nThreadsProcessingCallbacks; + while (this->nCallbackRemovalsInProgress) {std::this_thread::yield();} + continue; + } + else + break; + } +} +void TaskManager::UnlockCallbacksDelete() +{ + --this->nThreadsProcessingCallbacks; +} + +TaskManager::TaskManager(unsigned int nMaxThreads) +{ + this->newTaskEvent = false; + this->threadCloseEvent = false; + this->threadClosedEvent = false; + this->nMaxThreads = nMaxThreads; + this->nThreads = 0; + this->nThreadsCommit = 0; + this->nThreadsReady = 0; + this->nThreadsProcessingCallbacks = 0; + this->nCallbackRemovalsInProgress = 0; +} +TaskManager::~TaskManager() +{ + setMaxThreads(0); + //Make sure we don't delete hThreadClosedEvent before the last SetEvent call has been processed. + while (this->nThreadsCommit > 0){} + auto taskIterator = this->tasks.cbegin(); + for (; taskIterator != this->tasks.cend(); ++taskIterator) {delete taskIterator->pProgMgr;} + this->tasks.clear(); +} +bool TaskManager::setMaxThreads(unsigned int nMaxThreads, bool allowRequiredWait, bool waitClose) +{ + auto waitThreadsClosed = [this]() + { + { + std::scoped_lock lock(this->threadEventMutex); + this->threadCloseEvent = true; + } + this->threadEventVar.notify_all(); + while (this->nThreads > this->nMaxThreads) + { + std::unique_lock lock(this->threadEventMutex); + this->threadClosedEventVar.wait(lock, [this]() {return this->threadClosedEvent; }); + this->threadClosedEvent = false; + } + threads.erase(threads.begin() + nThreads, threads.end()); + }; + if (this->nThreads > this->nMaxThreads && nMaxThreads > this->nMaxThreads) + { + //Still waiting for some threads to close after having reduced nMaxThreads. + //Raising nMaxThreads again could otherwise cause gaps in the thread ID assignment. + if (allowRequiredWait) + { + waitThreadsClosed(); + } + else + { + //Make sure the value is updated if setMaxThreads is called again. + std::atomic_thread_fence(std::memory_order::seq_cst); + return false; + } + } + this->nMaxThreads = nMaxThreads; + waitThreadsClosed(); + //ResetEvent(this->hThreadCloseEvent); + return true; +} +bool TaskManager::enqueue(std::shared_ptr pTask) +{ + TaskDesc taskDesc; + taskDesc.pProgMgr = new TaskProgressManager(this, pTask); + taskDesc.pTask = pTask; taskDesc.taken = false; + pTask->onEnqueue(*taskDesc.pProgMgr); + + bool startNewThread = false; + unsigned int newThreadIdx; + + //callbacks is only modified by the main thread. + for (size_t i = 0; i < this->callbacks.size(); i++) + { + this->callbacks[i]->OnAdd(pTask); + this->callbacks[i]->OnCancelableChange(pTask, true); + } + + std::unique_lock taskListLock(this->taskListMutex); + auto taskIterator = this->tasks.cbegin(); + for (; taskIterator != this->tasks.cend() && !taskIterator->taken; ++taskIterator) {} + this->tasks.emplace(taskIterator, taskDesc); + if (this->nThreadsReady == 0 && this->nThreads < this->nMaxThreads) + { + newThreadIdx = this->nThreads; + ++this->nThreads; + startNewThread = true; + } + { + std::scoped_lock threadEventLock(threadEventMutex); + newTaskEvent = true; + } + //notify_one would probably be more performant since each Task can only be run by one thread. + threadEventVar.notify_all(); + taskListLock.unlock(); + + if (startNewThread) + { + TaskThreadParam *pParam = new TaskThreadParam; + pParam->pThis = this; + pParam->threadIdx = newThreadIdx; + ++this->nThreadsCommit; + assert(newThreadIdx == threads.size()); + threads.emplace_back(&TaskThread, (void*)pParam); + } + return true; +} +bool TaskManager::cancel(ITask *pTask, bool *out_taskRunning) +{ + bool ret = false, taskRunning = false; + if (out_taskRunning) + *out_taskRunning = false; + std::unique_lock taskListLock(this->taskListMutex); + //callbacks is only modified by the main thread. + std::vector tempCallbacks = this->callbacks; + auto taskIterator = this->tasks.cbegin(); + std::shared_ptr taskRef; + for (; taskIterator != this->tasks.cend() && taskIterator->pTask.get() != pTask; ++taskIterator) {} + if (taskIterator != this->tasks.cend()) + { + taskRef = taskIterator->pTask; + if (taskIterator->taken) + { + if (taskIterator->pProgMgr->cancelable) + { + taskIterator->pProgMgr->canceled.store(true); + ret = true; + taskRunning = true; + if (out_taskRunning) + *out_taskRunning = true; + } + } + else + { + delete taskIterator->pProgMgr; + this->tasks.erase(taskIterator); + ret = true; + } + } + taskListLock.unlock(); + if (ret) + { + for (size_t i = 0; i < tempCallbacks.size(); i++) + { + if (taskRunning) + tempCallbacks[i]->OnCancelableChange(taskRef, false); + else + tempCallbacks[i]->OnCompletion(taskRef, TaskResult_Canceled); + } + } + return ret; +} +void TaskManager::addCallback(TaskProgressCallback *pCallback) +{ + std::scoped_lock taskListLock(this->taskListMutex); + this->callbacks.push_back(pCallback); +} +void TaskManager::removeCallback(TaskProgressCallback *pCallback) +{ + //Custom critical section : no thread may use callbacks while the main thread removes one to prevent use-after-free conditions. + ++this->nCallbackRemovalsInProgress; + //Alternative : Surround "...=true;" and first condition check in critical section (also see TaskThread). + std::atomic_thread_fence(std::memory_order::seq_cst); + while (this->nThreadsProcessingCallbacks > 0) {std::this_thread::yield();} + + //Inner critical section specifically for callbacks. + std::unique_lock taskListLock(this->taskListMutex); + for (size_t _i = this->callbacks.size(); _i > 0; _i--) + { + size_t i = _i - 1; + if (this->callbacks[i] == pCallback) + { + this->callbacks.erase(this->callbacks.begin() + i); + break; + } + } + taskListLock.unlock(); + --this->nCallbackRemovalsInProgress; +} \ No newline at end of file diff --git a/UABE_Generic/AsyncTask.h b/UABE_Generic/AsyncTask.h new file mode 100644 index 0000000..fa34b93 --- /dev/null +++ b/UABE_Generic/AsyncTask.h @@ -0,0 +1,132 @@ +#pragma once +#include "api.h" +#include +#include +#include +#include +#include +#include +#include + +//result -128 : canceled; result < 0 : error. result >= 0 : success. +typedef int TaskResult; +static const int TaskResult_Canceled = -128; + +class ITask; +class TaskProgressCallback +{ +public: + UABE_Generic_API TaskProgressCallback(); + UABE_Generic_API virtual ~TaskProgressCallback(); + //Callbacks can be called from the main thread as well as from any task thread! + UABE_Generic_API virtual void OnAdd(std::shared_ptr &pTask); + UABE_Generic_API virtual void OnProgress(std::shared_ptr &pTask, unsigned int progress, unsigned int range); + UABE_Generic_API virtual void OnProgressDesc(std::shared_ptr &pTask, const std::string &desc); + UABE_Generic_API virtual void OnLogMessage(std::shared_ptr &pTask, const std::string &msg); + UABE_Generic_API virtual void OnCompletion(std::shared_ptr &pTask, TaskResult result); //Result 0 means success, < 0 means error. Specific values depend on the task. + UABE_Generic_API virtual void OnCancelableChange(std::shared_ptr& pTask, bool cancelable); +}; + +class TaskProgressManager; +class ITask +{ +public: + UABE_Generic_API ITask(); + UABE_Generic_API virtual ~ITask(); + UABE_Generic_API virtual const std::string &getName()=0; + //Configure the task progress manager before it lands in the queue. Default : stub. + UABE_Generic_API virtual void onEnqueue(TaskProgressManager &progressManager); + UABE_Generic_API virtual TaskResult execute(TaskProgressManager &progressManager)=0; + //Assumes that if this task is not ready, there is another task in queue that is ready and that works toward making this task ready. + //Should return very fast as this is executed while the task manager is locked. + //Default: return true. + UABE_Generic_API virtual bool isReady(); +}; + +class TaskManager; +class TaskProgressManager +{ + TaskManager *pManager; + std::shared_ptr pTask; + std::atomic_bool cancelable; + std::atomic_bool canceled; + TaskProgressManager(TaskManager *pManager, std::shared_ptr pTask); +public: + UABE_Generic_API std::shared_ptr &getTask(); + UABE_Generic_API void setProgress(unsigned int progress, unsigned int range); + UABE_Generic_API void setProgressDesc(const std::string &desc); + UABE_Generic_API void logMessage(const std::string &msg); + UABE_Generic_API void setCancelable(); + UABE_Generic_API bool isCanceled(); + friend class TaskManager; +}; + +class TaskManager +{ + struct TaskDesc + { + std::shared_ptr pTask; + TaskProgressManager *pProgMgr; + bool taken; + }; +protected: + void LockCallbacksDelete(); + void UnlockCallbacksDelete(); + std::vector callbacks; +private: + unsigned int nMaxThreads; + std::atomic_uint nThreads; + std::atomic_uint nThreadsCommit; + std::atomic_uint nThreadsReady; + std::atomic_uint nThreadsWorking; + std::atomic_uint nThreadsProcessingCallbacks; + std::atomic_uint nCallbackRemovalsInProgress; + std::list tasks; + //Only used by the main thread. + std::vector threads; + + std::recursive_mutex taskListMutex; + std::mutex threadEventMutex; + std::condition_variable threadEventVar; + bool newTaskEvent; + bool threadCloseEvent; + std::condition_variable threadClosedEventVar; + bool threadClosedEvent; + + static void TaskThread(void *param); + +public: + UABE_Generic_API TaskManager(unsigned int nMaxThreads = 1); + UABE_Generic_API ~TaskManager(); + + //All of these functions except removeCallback must be called by one thread at a time only. + + //Sets the maximum amount of threads. + // If there are more than nMaxThreads running, waits for the excess threads to stop, unless waitClose is set to false. + // However, a wait will be neccessary if nMaxThreads is reduced and then raised with threads still pending to close. + // This ensures that the internal thread indices are sequential from 0 to n-1. + // If allowRequiredWait is set to false in this case, the function will fail and return false. + // -> Win32 threads with open window handles should set both allowMandatoryWait and waitClose to false to prevent ugly deadlocks. + // See the Microsoft documentation on WaitForSingleObject. + UABE_Generic_API bool setMaxThreads(unsigned int nMaxThreads, bool allowRequiredWait = true, bool waitClose = true); + inline unsigned int getNumThreads() + { + return this->nThreads.load(); + } + //Note: getNumThreadsWorking() changes asynchronously. + inline unsigned int getNumThreadsWorking() + { + return this->nThreadsWorking.load(); + } + + UABE_Generic_API void addCallback(TaskProgressCallback *pCallback); + UABE_Generic_API void removeCallback(TaskProgressCallback *pCallback); + //Enqueues a new task. May or may not create new worker threads. + UABE_Generic_API bool enqueue(std::shared_ptr pTask); + //If the task does not run yet, removes it from the list and sets *taskRunning to false. + //If the task runs and is cancelable, sets the isCanceled flag in TaskProgressManager for the specified task and sets *taskRunning to true. + //Returns true if the task was found and cancelled or removed. + UABE_Generic_API bool cancel(ITask *pTask, bool *taskRunning = nullptr); + + friend class TaskProgressManager; +}; \ No newline at end of file diff --git a/UABE_Generic/CMakeLists.txt b/UABE_Generic/CMakeLists.txt new file mode 100644 index 0000000..e0086a8 --- /dev/null +++ b/UABE_Generic/CMakeLists.txt @@ -0,0 +1,8 @@ +add_library (UABE_Generic SHARED AppContext.cpp AssetContainerList.cpp AssetIterator.cpp AsyncTask.cpp CreateEmptyValueField.cpp FileContext.cpp FileContextInfo.cpp FileModTree.cpp "IProgressIndicator.h" "IProgressIndicator.cpp" "api.h" "AssetPluginUtil.cpp" "IAssetBatchImportDesc.h" "TaskStatusTracker.cpp" "PluginManager.cpp") +target_include_directories (UABE_Generic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +target_compile_definitions(UABE_Generic PRIVATE UABE_Generic_EXPORTS) +set_target_properties(UABE_Generic PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") +target_link_libraries(UABE_Generic PUBLIC AssetsTools) +target_link_libraries(UABE_Generic PRIVATE ModInstaller libCompression libStringConverter jsmn) + diff --git a/UABE_Generic/CreateEmptyValueField.cpp b/UABE_Generic/CreateEmptyValueField.cpp new file mode 100644 index 0000000..7a55297 --- /dev/null +++ b/UABE_Generic/CreateEmptyValueField.cpp @@ -0,0 +1,247 @@ +#pragma once +#include "CreateEmptyValueField.h" + +AssetTypeValueField *CreateEmptyValueFieldFromTemplate(AssetTypeTemplateField *pTemplate, + std::vector>& allocatedMemory) +{ + static const QWORD nullValue = 0; //Is the 0 or empty value for types below. + switch (pTemplate->valueType) + { + case ValueType_String: + if (pTemplate->children.size() < 1 || !pTemplate->children[0].isArray) return nullptr; //Invalid string. + case ValueType_Bool: + case ValueType_Int8: + case ValueType_UInt8: + case ValueType_Int16: + case ValueType_UInt16: + case ValueType_Int32: + case ValueType_UInt32: + case ValueType_Int64: + case ValueType_UInt64: + case ValueType_Float: + case ValueType_Double: + { + uint8_t *pCurMem = new uint8_t[sizeof(AssetTypeValueField) + sizeof(AssetTypeValue)]; + allocatedMemory.push_back(std::unique_ptr(pCurMem)); + AssetTypeValueField *pNewField = (AssetTypeValueField*)pCurMem; + AssetTypeValue *pNewValue = (AssetTypeValue*)(&pNewField[1]); + + *pNewValue = AssetTypeValue(pTemplate->valueType, const_cast(&nullValue)); + pNewField->Read(pNewValue, pTemplate, 0, nullptr); + return pNewField; + } + case ValueType_ByteArray: + case ValueType_Array: + case ValueType_None: + if (pTemplate->isArray) + { + if (pTemplate->children.size() < 2) return nullptr; //Invalid array. + uint8_t *pCurMem = new uint8_t[sizeof(AssetTypeValueField) + sizeof(AssetTypeValue)]; + allocatedMemory.push_back(std::unique_ptr(pCurMem)); + AssetTypeValueField *pNewField = (AssetTypeValueField*)pCurMem; + AssetTypeValue *pNewValue = (AssetTypeValue*)(&pNewField[1]); + + AssetTypeByteArray _tmpArray; + _tmpArray.size = 0; + _tmpArray.data = nullptr; + *pNewValue = AssetTypeValue(ValueType_ByteArray, &_tmpArray); + pNewField->Read(pNewValue, pTemplate, 0, nullptr); + + return pNewField; + } + else + { + uint8_t *pCurMem = new uint8_t[sizeof(AssetTypeValueField) + (sizeof(AssetTypeValueField*) * pTemplate->children.size())]; + allocatedMemory.push_back(std::unique_ptr(pCurMem)); + AssetTypeValueField *pNewField = (AssetTypeValueField*)pCurMem; + AssetTypeValueField **pNewChildList = (AssetTypeValueField**)((uintptr_t)pCurMem + sizeof(AssetTypeValueField)); + for (size_t i = 0; i < pTemplate->children.size(); i++) + { + if (! (pNewChildList[i] = CreateEmptyValueFieldFromTemplate(&pTemplate->children[i], allocatedMemory)) ) + { + return nullptr; + } + } + pNewField->Read(nullptr, pTemplate, (uint32_t)pTemplate->children.size(), pNewChildList); + return pNewField; + } + default: + return nullptr; //Unknown type. + } +} + +AssetsEntryReplacer *MakeEmptyAssetReplacer( + AppContext &appContext, std::shared_ptr pFileInfo, long long int pathID, int classID, int monoClassID, + unsigned int relFileID_MonoScript, long long int pathID_MonoScript, Hash128 propertiesHash_MonoScript) +{ + AssetIdentifier initialAsset; + //CAssetInterface *pInitialInterface = nullptr; + int monoBehaviourClass = pFileInfo->GetClassByName("MonoBehaviour"); + if (monoClassID != -1 && monoBehaviourClass != -1 && pathID_MonoScript != 0) + { + AssetTypeTemplateField behavTemplateBase; + if (pFileInfo->MakeTemplateField(&behavTemplateBase, appContext, monoBehaviourClass)) + { + std::vector> valueMemory; + AssetTypeValueField *pBaseField = CreateEmptyValueFieldFromTemplate(&behavTemplateBase, valueMemory); + if (pBaseField) + { + AssetTypeValueField *pFileIDField = pBaseField->Get("m_Script")->Get("m_FileID"); + AssetTypeValueField *pPathIDField = pBaseField->Get("m_Script")->Get("m_PathID"); + if (!pFileIDField->IsDummy() && pFileIDField->GetValue() && !pPathIDField->IsDummy() && pPathIDField->GetValue()) + { + pFileIDField->GetValue()->Set(&relFileID_MonoScript); + pPathIDField->GetValue()->Set(&pathID_MonoScript); + IAssetsWriterToMemory *pWriter = Create_AssetsWriterToMemory(); + bool isBigEndian = false; pFileInfo->getEndianness(isBigEndian); + pBaseField->Write(pWriter, 0, isBigEndian); + void *pData = nullptr; size_t dataLen = 0; + if (pWriter->GetBuffer(pData, dataLen)) + { + AssetsEntryReplacer *pInitialReplacer = MakeAssetModifierFromMemory( + (uint32_t)pFileInfo->getFileID(), (QWORD)pathID, classID, (uint16_t)monoClassID, + pData, dataLen, Free_AssetsWriterToMemory_DynBuf); + if (pInitialReplacer) + pWriter->SetFreeBuffer(false); + initialAsset = AssetIdentifier(pFileInfo->getFileID(), pathID); + initialAsset.pFile = pFileInfo; + initialAsset.pReplacer.reset(pInitialReplacer, FreeAssetsReplacer); + } + Free_AssetsWriter(pWriter); + } + } + } + } + AssetTypeTemplateField templateBase; + if (pFileInfo->MakeTemplateField(&templateBase, appContext, classID, (uint16_t)monoClassID, &initialAsset)) + { + std::vector> valueMemory; + AssetTypeValueField *pBaseField = CreateEmptyValueFieldFromTemplate(&templateBase, valueMemory); + if (pBaseField) + { + std::shared_ptr pBaseClassDb = pFileInfo->GetClassDatabase(); + + ClassDatabaseFile *pAssetTypeDb = nullptr; + ClassDatabaseType *pAssetType = nullptr; + ClassDatabaseFile tempCombinedFile; + Hash128 scriptID; bool hasScriptID = false; + if (monoClassID != -1 && pathID_MonoScript != 0) + { + AssetTypeValueField *pFileIDField = pBaseField->Get("m_Script")->Get("m_FileID"); + AssetTypeValueField *pPathIDField = pBaseField->Get("m_Script")->Get("m_PathID"); + if (!pFileIDField->IsDummy() && pFileIDField->GetValue() && !pPathIDField->IsDummy() && pPathIDField->GetValue()) + { + pFileIDField->GetValue()->Set(&relFileID_MonoScript); + pPathIDField->GetValue()->Set(&pathID_MonoScript); + } + ClassDatabaseFile *pScriptClassDb = nullptr; + ClassDatabaseType *pScriptClassType = nullptr; + if (initialAsset.pReplacer != nullptr && pBaseClassDb && + pFileInfo->FindScriptClassDatabaseEntry(pScriptClassDb, pScriptClassType, initialAsset, appContext, &scriptID)) + { + hasScriptID = true; + ClassDatabaseType *pMonoBehaviourClassType = nullptr; + int monoBehaviourClass = pFileInfo->GetClassByName("MonoBehaviour"); + if (monoBehaviourClass >= 0) + { + for (size_t i = 0; i < pBaseClassDb->classes.size(); i++) + { + if (pBaseClassDb->classes[i].classId == monoBehaviourClass) + { + pMonoBehaviourClassType = &pBaseClassDb->classes[i]; + break; + } + } + if (pMonoBehaviourClassType) + { + //Generate the combined ClassDatabaseType. + tempCombinedFile.classes.resize(1); + ClassDatabaseType &newType = tempCombinedFile.classes[0]; + + newType.assemblyFileName.fromStringTable = false; + newType.assemblyFileName.str.string = ""; + newType.name.fromStringTable = false; + newType.name.str.string = ""; + + newType.classId = -(monoClassID + 1); + newType.baseClass = monoBehaviourClass; + + for (size_t i = 0; i < pMonoBehaviourClassType->fields.size(); i++) + { + newType.fields.push_back(pMonoBehaviourClassType->fields[i]); + ClassDatabaseTypeField &curMonoBehavField = newType.fields[newType.fields.size() - 1]; + curMonoBehavField.fieldName.str.string = curMonoBehavField.fieldName.GetString(pBaseClassDb.get()); + curMonoBehavField.fieldName.fromStringTable = false; + curMonoBehavField.typeName.str.string = curMonoBehavField.typeName.GetString(pBaseClassDb.get()); + curMonoBehavField.typeName.fromStringTable = false; + } + for (size_t i = 1; i < pScriptClassType->fields.size(); i++) + { + newType.fields.push_back(pScriptClassType->fields[i]); + ClassDatabaseTypeField &curMonoBehavField = newType.fields[newType.fields.size() - 1]; + curMonoBehavField.fieldName.str.string = curMonoBehavField.fieldName.GetString(pScriptClassDb); + curMonoBehavField.fieldName.fromStringTable = false; + curMonoBehavField.typeName.str.string = curMonoBehavField.typeName.GetString(pScriptClassDb); + curMonoBehavField.typeName.fromStringTable = false; + } + + pAssetTypeDb = &tempCombinedFile; + pAssetType = &newType; + } + } + } + } + else if (pBaseClassDb) + { + for (size_t i = 0; i < pBaseClassDb->classes.size(); i++) + { + if (pBaseClassDb->classes[i].classId == classID) + { + pAssetTypeDb = pBaseClassDb.get(); + pAssetType = &pBaseClassDb->classes[i]; + break; + } + } + } + std::unique_ptr pWriter(Create_AssetsWriterToMemory()); + bool bigEndian = false; pFileInfo->getEndianness(bigEndian); + pBaseField->Write(pWriter.get(), 0, bigEndian); + void *pData = nullptr; size_t dataLen = 0; + if (pWriter->GetBuffer(pData, dataLen)) + { + AssetsEntryReplacer *pReplacer = MakeAssetModifierFromMemory( + (uint32_t)pFileInfo->getFileID(), (QWORD)pathID, classID, (uint16_t)monoClassID, + pData, dataLen, Free_AssetsWriterToMemory_DynBuf); + if (pReplacer) + { + pWriter->SetFreeBuffer(false); + if (hasScriptID) + pReplacer->SetScriptIDHash(scriptID); + if (pAssetTypeDb && pAssetType) + { + if (propertiesHash_MonoScript.qValue[0] == 0 && propertiesHash_MonoScript.qValue[1] == 0) + pReplacer->SetPropertiesHash(pAssetType->MakeTypeHash(pAssetTypeDb)); + else + pReplacer->SetPropertiesHash(propertiesHash_MonoScript); + //TODO: Don't add the type information if it's already in the type tree or not needed (i.e. .assets file has no tree)! + auto pReplacerDatabase = std::make_shared(); + if (pReplacerDatabase->InsertFrom(pAssetTypeDb, pAssetType)) + { + assert(pReplacerDatabase->classes.size() == 1); + if (pReplacerDatabase->classes.size() == 1) + pReplacer->SetTypeInfo(std::move(pReplacerDatabase), &pReplacerDatabase->classes[0]); + } + } + if (monoClassID != -1 && pathID_MonoScript != 0) + { + AssetPPtr pptr; + pptr.fileID = relFileID_MonoScript; pptr.pathID = (QWORD)pathID_MonoScript; + pReplacer->AddPreloadDependency(pptr); + } + return pReplacer; + } + } + } + } + return NULL; +} diff --git a/UABE_Generic/CreateEmptyValueField.h b/UABE_Generic/CreateEmptyValueField.h new file mode 100644 index 0000000..4574a5b --- /dev/null +++ b/UABE_Generic/CreateEmptyValueField.h @@ -0,0 +1,14 @@ +#pragma once +#include "api.h" +#include "AppContext.h" +#include "../AssetsTools/AssetTypeClass.h" +#include +#include +#include + +UABE_Generic_API AssetTypeValueField *CreateEmptyValueFieldFromTemplate(AssetTypeTemplateField *pTemplate, + std::vector> &allocatedMemory); + +UABE_Generic_API AssetsEntryReplacer *MakeEmptyAssetReplacer( + AppContext &appContext, std::shared_ptr pFileInfo, long long int pathID, int classID, int monoClassID, + unsigned int relFileID_MonoScript, long long int pathID_MonoScript, Hash128 propertiesHash_MonoScript); diff --git a/UABE_Generic/FileContext.cpp b/UABE_Generic/FileContext.cpp new file mode 100644 index 0000000..ba704e7 --- /dev/null +++ b/UABE_Generic/FileContext.cpp @@ -0,0 +1,646 @@ +#include "FileContext.h" +#include "../libStringConverter/convert.h" + +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#include +#include + +IFileOpenCallback::IFileOpenCallback() +{} +IFileOpenCallback::~IFileOpenCallback() +{} + +IFileContext::IFileContext(const std::string &filePath, IFileContext *pParent) +{ + this->filePath.assign(filePath); + if (pParent != nullptr) + this->fileName.assign(filePath); + else + { + const char *fullPathC = this->filePath.c_str(); + size_t fileNameIndex; + for (fileNameIndex = this->filePath.size(); fileNameIndex > 0; fileNameIndex--) + { + if (fullPathC[fileNameIndex-1] == '/' || fullPathC[fileNameIndex-1] == '\\') + break; + } + this->fileName.assign(this->filePath.substr(fileNameIndex)); + } + this->pParent = pParent; +} +IFileContext::~IFileContext() +{} + +const std::string &IFileContext::getFileName() +{ + return this->fileName; +} +const std::string &IFileContext::getFilePath() +{ + return this->filePath; +} +std::string IFileContext::getFileDirectoryPath() +{ + size_t slash = std::string::npos; size_t slashA = this->filePath.rfind('/'); size_t slashB = this->filePath.rfind('\\'); + if (slashA != std::string::npos && (slashB == std::string::npos || slashB <= slashA)) + slash = slashA; + else if (slashB != std::string::npos) + slash = slashB; + return this->filePath.substr(0, slash); +} +IFileContext *IFileContext::getParent() +{ + return this->pParent; +} + +#pragma region BundleFileContext +BundleFileContext::OpenTaskCallback::OpenTaskCallback(BundleFileContext *pContext) + : pContext(pContext) +{} +void BundleFileContext::OpenTaskCallback::OnCompletion(std::shared_ptr &pTask, TaskResult result) +{ + if (result >= 0) + this->pContext->openState.OnCompletion(); + else + this->pContext->openState.OnFailure(); + if (this->pContext->pOpenCallback) + this->pContext->pOpenCallback->OnFileOpenResult(this->pContext, result); +} + +BundleFileContext::OpenTask::OpenTask(BundleFileContext *pContext) + : pContext(pContext) +{ + name = "Open bundle : " + pContext->fileName; +} +const std::string &BundleFileContext::OpenTask::getName() +{ + return name; +} +TaskResult BundleFileContext::OpenTask::execute(TaskProgressManager &progressManager) +{ + return pContext->OpenSync(&progressManager); +} + +BundleFileContext::BundleFileContext(const std::string &filePath, std::shared_ptr _pReader, bool readerIsModified) + : IFileContext(filePath, nullptr), + openTask(this), openTaskCallback(this), + pOpenCallback(nullptr), pDecompressCallback(nullptr), + pReader(std::move(_pReader)), inheritReader(pReader != nullptr), readerIsModified(pReader != nullptr && readerIsModified), + lastOpenStatus(BundleFileOpenStatus_OK), lastDecompressStatus(BundleFileDecompressStatus_OK) +{ +} +BundleFileContext::BundleFileContext(const std::string &filePath, IFileContext *pParent, std::shared_ptr _pReader, bool readerIsModified) + : IFileContext(filePath, pParent), + openTask(this), openTaskCallback(this), + pOpenCallback(nullptr), pDecompressCallback(nullptr), + pReader(std::move(_pReader)), inheritReader(true), readerIsModified(pReader != nullptr && readerIsModified), + lastOpenStatus(BundleFileOpenStatus_OK), lastDecompressStatus(BundleFileDecompressStatus_OK) +{ + assert(pParent && this->pReader); +} +BundleFileContext::~BundleFileContext() +{ + this->Close(); +} + +EBundleFileOpenStatus BundleFileContext::OpenSync(TaskProgressManager *pProgressManager, unsigned int initProgress, unsigned int progressScale) +{ + if (pProgressManager) pProgressManager->setProgress(initProgress, progressScale); + if (!this->inheritReader) + { + IAssetsReader *pReader = Create_AssetsReaderFromFile(this->filePath.c_str(), true, RWOpenFlags_Immediately); + if (pReader == nullptr) + { + if (pProgressManager) pProgressManager->setProgress(initProgress + 100, progressScale); + if (pProgressManager) pProgressManager->logMessage("[ERROR] Unable to open the bundle file."); + return BundleFileOpenStatus_ErrFileOpen; + } + this->pReader = std::shared_ptr(pReader, Free_AssetsReader); + } + else + assert(this->pReader != nullptr); + if (pProgressManager) pProgressManager->setProgress(initProgress + 25, progressScale); + if (pProgressManager) pProgressManager->setProgressDesc("Processing bundle file"); + if (!this->bundle.Read(pReader.get(), nullptr, true)) + { + if (pProgressManager) pProgressManager->setProgress(initProgress + 100, progressScale); + if (pProgressManager) pProgressManager->logMessage("Open as bundle: [ERROR] Unable to process the bundle file header or lists."); + this->pReader.reset(); + return BundleFileOpenStatus_ErrInvalid; + } + if (pProgressManager) pProgressManager->setProgressDesc("Processing bundle directory"); + if (this->bundle.bundleHeader3.fileVersion >= 6) + { + if (!this->bundle.bundleInf6) + { + if (pProgressManager) pProgressManager->setProgress(initProgress + 100, progressScale); + if ((this->bundle.bundleHeader6.flags & 0x3F) != 0) + return BundleFileOpenStatus_CompressedDirectory; + if (pProgressManager) pProgressManager->logMessage("[ERROR] Unable to process the bundle directory."); + this->bundle.Close(); + this->pReader.reset(); + return BundleFileOpenStatus_ErrInvalid; + } + else + { + for (DWORD i = 0; i < this->bundle.bundleInf6->blockCount; i++) + { + if ((this->bundle.bundleInf6->blockInf[i].flags & 0x3F) != 0) + { + if (pProgressManager) pProgressManager->setProgress(initProgress + 100, progressScale); + return BundleFileOpenStatus_CompressedData; + } + } + if (pProgressManager) pProgressManager->setProgress(initProgress + 100, progressScale); + return BundleFileOpenStatus_OK; + } + } + else if (this->bundle.bundleHeader3.fileVersion == 3) + { + if (pProgressManager) pProgressManager->setProgress(initProgress + 100, progressScale); + if (!strcmp(this->bundle.bundleHeader3.signature, "UnityWeb")) + return BundleFileOpenStatus_CompressedDirectory; + else if (!this->bundle.assetsLists3) + { + if (pProgressManager) pProgressManager->logMessage("[ERROR] Unable to process the bundle directory."); + this->bundle.Close(); + this->pReader.reset(); + return BundleFileOpenStatus_ErrInvalid; + } + else + return BundleFileOpenStatus_OK; + } + else + { + if (pProgressManager) pProgressManager->setProgress(initProgress + 100, progressScale); + if (pProgressManager) pProgressManager->logMessage("Open as bundle: [ERROR] Unknown bundle file version."); + this->bundle.Close(); + this->pReader.reset(); + return BundleFileOpenStatus_ErrUnknownVersion; + } +} + +EBundleFileOpenStatus BundleFileContext::Open() +{ + if (this->openState.Start()) + { + EBundleFileOpenStatus ret = OpenSync(nullptr); + if (ret >= 0) + this->openState.OnCompletion(); + else + this->openState.OnFailure(); + return ret; + } + if (this->openState.isReady()) + return lastOpenStatus; + return BundleFileOpenStatus_Pend; +} +EBundleFileOpenStatus BundleFileContext::OpenInsideTask(TaskProgressManager *pProgressManager, unsigned int initProgress, unsigned int progressScale) +{ + if (this->openState.Start()) + { + EBundleFileOpenStatus ret = OpenSync(pProgressManager, initProgress, progressScale); + if (ret >= 0) + this->openState.OnCompletion(); + else + this->openState.OnFailure(); + return ret; + } + if (this->openState.isReady()) + return lastOpenStatus; + return BundleFileOpenStatus_Pend; +} + +void BundleFileContext::Close() +{ + this->decompressState.Close(); + if (this->openState.Close()) + { + bundle.Close(); + pReader.reset(); + //TODO: Free any open resources. + } +} + +EBundleFileDecompressStatus BundleFileContext::DecompressSync(TaskProgressManager *pProgressManager, const std::string &outPath) +{ + if (!this->openState.isReady()) + return BundleFileDecompressStatus_ErrBundleNotOpened; + IAssetsWriter *pWriter = Create_AssetsWriterToFile(outPath.c_str(), true, true, RWOpenFlags_Immediately); + if (!pWriter) + return BundleFileDecompressStatus_ErrOutFileOpen; + EBundleFileDecompressStatus ret = BundleFileDecompressStatus_OK; + if (!bundle.Unpack(this->pReader.get(), pWriter)) + ret = BundleFileDecompressStatus_ErrDecompress; + Free_AssetsWriter(pWriter); + return ret; +} + +IAssetsReader *BundleFileContext::getReaderUnsafe(bool *isInherited) +{ + if (isInherited) + *isInherited = this->inheritReader; + if (this->openState.isReady()) + return this->pReader.get(); + return nullptr; +} +AssetBundleFile *BundleFileContext::getBundleFile() +{ + if (this->openState.isReady()) + return &this->bundle; + return nullptr; +} +//For v3 bundles: Returns false. For v6 bundles: Returns the directory flag "has serialized data". +//-> If true, the file is supposed to be an .assets file. +bool BundleFileContext::hasSerializedData(size_t index) +{ + if (this->openState.isReady()) + { + if (index >= getEntryCount()) + return false; + if (this->bundle.bundleHeader6.fileVersion >= 6) + return (this->bundle.bundleInf6->dirInf[index].flags & 4) != 0; + } + return false; +} +std::shared_ptr BundleFileContext::makeEntryReader(size_t index) +{ + if (this->openState.isReady()) + { + if (index >= getEntryCount()) + return nullptr; + if (this->bundle.bundleHeader6.fileVersion >= 6) + return std::shared_ptr( + this->bundle.MakeAssetsFileReader(this->pReader.get(), &this->bundle.bundleInf6->dirInf[index]), + FreeAssetBundle_FileReader); + else if (this->bundle.bundleHeader6.fileVersion == 3) + return std::shared_ptr( + this->bundle.MakeAssetsFileReader(this->pReader.get(), this->bundle.assetsLists3->ppEntries[index]), + FreeAssetBundle_FileReader); + } + return nullptr; +} +const char *BundleFileContext::getEntryName(size_t index) +{ + if (this->openState.isReady()) + { + if (index >= getEntryCount()) + return nullptr; + if (this->bundle.bundleHeader6.fileVersion >= 6) + return this->bundle.bundleInf6->dirInf[index].name; + else if (this->bundle.bundleHeader6.fileVersion == 3) + return this->bundle.assetsLists3->ppEntries[index]->name; + } + return nullptr; +} +size_t BundleFileContext::getEntryCount() +{ + if (this->openState.isReady()) + { + if (this->bundle.bundleHeader6.fileVersion >= 6 && this->bundle.bundleInf6 != nullptr) + return this->bundle.bundleInf6->directoryCount; + else if (this->bundle.bundleHeader6.fileVersion == 3 && this->bundle.assetsLists3 != nullptr) + return this->bundle.assetsLists3->count; + } + return 0; +} + +EFileContextType BundleFileContext::getType() +{ + return FileContext_Bundle; +} +#pragma endregion BundleFileContext + +#pragma region AssetsFileContext +AssetsFileContext::OpenTaskCallback::OpenTaskCallback(AssetsFileContext *pContext) + : pContext(pContext) +{} +void AssetsFileContext::OpenTaskCallback::OnCompletion(std::shared_ptr &pTask, TaskResult result) +{ + if (result >= 0) + this->pContext->openState.OnCompletion(); + else + this->pContext->openState.OnFailure(); + if (this->pContext->pOpenCallback) + this->pContext->pOpenCallback->OnFileOpenResult(this->pContext, result); +} + +AssetsFileContext::OpenTask::OpenTask(AssetsFileContext *pContext) + : pContext(pContext) +{ + name = "Open .assets : " + pContext->fileName; +} +const std::string &AssetsFileContext::OpenTask::getName() +{ + return name; +} +TaskResult AssetsFileContext::OpenTask::execute(TaskProgressManager &progressManager) +{ + return pContext->OpenSync(&progressManager, pContext->doMakeBinaryTable, 0, pContext->getProgressScale()); +} + +AssetsFileContext::AssetsFileContext(const std::string &filePath, std::shared_ptr _pReader, bool readerIsModified) + : IFileContext(filePath, nullptr), + openTask(this), openTaskCallback(this), pOpenCallback(nullptr), + pReader(std::move(_pReader)), inheritReader(pReader != nullptr), readerIsModified(pReader != nullptr && readerIsModified), + lastOpenStatus(AssetsFileOpenStatus_OK), pAssetsFile(nullptr), pAssetsFileTable(nullptr) +{} +AssetsFileContext::AssetsFileContext(const std::string &filePath, IFileContext *pParent, std::shared_ptr _pReader, bool readerIsModified) + : IFileContext(filePath, pParent), + openTask(this), openTaskCallback(this), pOpenCallback(nullptr), + pReader(std::move(_pReader)), inheritReader(true), readerIsModified(pReader != nullptr && readerIsModified), + lastOpenStatus(AssetsFileOpenStatus_OK), pAssetsFile(nullptr), pAssetsFileTable(nullptr) +{ + assert(pParent && this->pReader); +} +AssetsFileContext::~AssetsFileContext() +{ + this->Close(); +} + +int AssetsFileContext::getProgressScale() +{ + return 100; +} +EAssetsFileOpenStatus AssetsFileContext::OpenSync(TaskProgressManager *pProgressManager, bool makeBinaryTable, unsigned int initProgress, unsigned int progressScale) +{ + if (pProgressManager) pProgressManager->setProgress(initProgress, progressScale); + if (!this->inheritReader) + { + IAssetsReader *pReader = Create_AssetsReaderFromFile(this->filePath.c_str(), true, RWOpenFlags_Immediately); + if (pReader == nullptr) + { + if (pProgressManager) pProgressManager->setProgress(initProgress + 100, progressScale); + if (pProgressManager) pProgressManager->logMessage("[ERROR] Unable to open the .assets file."); + return AssetsFileOpenStatus_ErrFileOpen; + } + this->pReader = std::shared_ptr(pReader, Free_AssetsReader); + } + else + assert(this->pReader != nullptr); + if (pProgressManager) pProgressManager->setProgress(initProgress + (makeBinaryTable ? 15 : 25), progressScale); + if (pProgressManager) pProgressManager->setProgressDesc("Processing .assets file"); + AssetsFile *pAssetsFile = new AssetsFile(this->pReader.get()); + if (!pAssetsFile->VerifyAssetsFile()) + { + delete pAssetsFile; + this->pReader.reset(); + if (pProgressManager) pProgressManager->setProgress(initProgress + 100, progressScale); + if (pProgressManager) pProgressManager->logMessage("Open as .assets: [ERROR] .assets file is invalid or unsupported."); + return AssetsFileOpenStatus_ErrInvalidOrUnsupported; + } + this->pAssetsFile = pAssetsFile; + if (pProgressManager) pProgressManager->setProgress(initProgress + (makeBinaryTable ? 25 : 50), progressScale); + if (pProgressManager) pProgressManager->setProgressDesc("Processing asset list"); + + this->pAssetsFileTable = new AssetsFileTable(pAssetsFile); + if (makeBinaryTable) + { + if (pProgressManager) pProgressManager->setProgress(initProgress + 50, progressScale); + if (pProgressManager) pProgressManager->setProgressDesc("Generating asset lookup tree"); + if (!this->pAssetsFileTable->GenerateQuickLookupTree()) + { + if (pProgressManager) pProgressManager->logMessage("[WARNING] Failed to generate the asset quick lookup tree."); + } + } + + if (pProgressManager) pProgressManager->setProgress(initProgress + 100, progressScale); + return AssetsFileOpenStatus_OK; +} + +EAssetsFileOpenStatus AssetsFileContext::Open(bool makeBinaryTable) +{ + if (this->openState.Start()) + { + EAssetsFileOpenStatus ret = OpenSync(nullptr, makeBinaryTable, 0, this->getProgressScale()); + if (ret >= 0) + this->openState.OnCompletion(); + else + this->openState.OnFailure(); + return ret; + } + if (this->openState.isReady()) + return lastOpenStatus; + return AssetsFileOpenStatus_Pend; +} +EAssetsFileOpenStatus AssetsFileContext::OpenInsideTask(TaskProgressManager *pProgressManager, bool makeBinaryTable, unsigned int initProgress, unsigned int progressScale) +{ + if (this->openState.Start()) + { + EAssetsFileOpenStatus ret = OpenSync(pProgressManager, makeBinaryTable, initProgress, progressScale); + if (ret >= 0) + this->openState.OnCompletion(); + else + this->openState.OnFailure(); + return ret; + } + if (this->openState.isReady()) + return lastOpenStatus; + return AssetsFileOpenStatus_Pend; +} +void AssetsFileContext::Close() +{ + if (this->openState.Close()) + { + delete this->pAssetsFile; + delete this->pAssetsFileTable; + this->pAssetsFile = nullptr; + this->pAssetsFileTable = nullptr; + this->pReader.reset(); + } +} +IAssetsReader *AssetsFileContext::getReaderUnsafe(bool *isInherited) +{ + if (isInherited) + *isInherited = this->inheritReader; + if (this->openState.isReady()) + return this->pReader.get(); + return nullptr; +} +IAssetsReader *AssetsFileContext::createReaderView(bool *isInherited) +{ + IAssetsReader *pReader = getReaderUnsafe(isInherited); + if (pReader) + return pReader->CreateView(); + return nullptr; +} +AssetsFile *AssetsFileContext::getAssetsFile() +{ + if (this->openState.isReady()) + return this->pAssetsFile; + return nullptr; +} +AssetsFileTable *AssetsFileContext::getAssetsFileTable() +{ + if (this->openState.isReady()) + return this->pAssetsFileTable; + return nullptr; +} + +EFileContextType AssetsFileContext::getType() +{ + return FileContext_Assets; +} +#pragma endregion AssetsFileContext + +#pragma region ResourcesFileContext +ResourcesFileContext::ResourcesFileContext(const std::string &filePath, std::shared_ptr _pReader, bool readerIsModified) + : IFileContext(filePath, nullptr), + pReader(std::move(_pReader)), inheritReader(pReader != nullptr), readerIsModified(pReader != nullptr && readerIsModified) +{ +} +ResourcesFileContext::ResourcesFileContext(const std::string &filePath, IFileContext *pParent, std::shared_ptr _pReader, bool readerIsModified) + : IFileContext(filePath, pParent), + pReader(std::move(_pReader)), inheritReader(true), readerIsModified(pReader != nullptr && readerIsModified) +{ + assert(pParent && this->pReader); +} +ResourcesFileContext::~ResourcesFileContext() +{ + this->Close(); +} +EResourcesFileOpenStatus ResourcesFileContext::Open() +{ + if (!this->inheritReader) + { + IAssetsReader *pReader = Create_AssetsReaderFromFile(this->filePath.c_str(), true, RWOpenFlags_Immediately); + if (pReader == nullptr) + { + return ResourcesFileOpenStatus_ErrFileOpen; + } + this->pReader = std::shared_ptr(pReader, Free_AssetsReader); + } + else + assert(this->pReader != nullptr); + return ResourcesFileOpenStatus_OK; +} +void ResourcesFileContext::Close() +{ + this->pReader.reset(); +} +IAssetsReader *ResourcesFileContext::getReaderUnsafe(bool *isInherited) +{ + if (isInherited) + *isInherited = this->inheritReader; + return this->pReader.get(); +} +EFileContextType ResourcesFileContext::getType() +{ + return FileContext_Resources; +} +#pragma endregion ResourcesFileContext + + + +#pragma region GenericFileContext +GenericFileContext::GenericFileContext(const std::string &filePath, std::shared_ptr _pReader, bool readerIsModified) + : IFileContext(filePath, nullptr), + pReader(std::move(_pReader)), inheritReader(pReader != nullptr), readerIsModified(pReader != nullptr && readerIsModified) +{ +} +GenericFileContext::GenericFileContext(const std::string &filePath, IFileContext *pParent, std::shared_ptr _pReader, bool readerIsModified) + : IFileContext(filePath, pParent), + pReader(std::move(_pReader)), inheritReader(true), readerIsModified(pReader != nullptr && readerIsModified) +{ + assert(pParent && this->pReader); +} +GenericFileContext::~GenericFileContext() +{ + this->Close(); +} +EGenericFileOpenStatus GenericFileContext::Open() +{ + if (!this->inheritReader) + { + IAssetsReader *pReader = Create_AssetsReaderFromFile(this->filePath.c_str(), true, RWOpenFlags_Immediately); + if (pReader == nullptr) + { + return GenericFileOpenStatus_ErrFileOpen; + } + this->pReader = std::shared_ptr(pReader, Free_AssetsReader); + } + else + assert(this->pReader != nullptr); + return GenericFileOpenStatus_OK; +} +void GenericFileContext::Close() +{ + this->pReader.reset(); +} +IAssetsReader *GenericFileContext::getReaderUnsafe(bool *isInherited) +{ + if (isInherited) + *isInherited = this->inheritReader; + return this->pReader.get(); +} +EFileContextType GenericFileContext::getType() +{ + return FileContext_Generic; +} +#pragma endregion GenericFileContext + +#pragma region AsyncOperationState +AsyncOperationState::AsyncOperationState() +{ + this->state.val = 0; + this->hOperationCompleteEvent = CreateEvent(NULL, TRUE, FALSE, NULL); +} +AsyncOperationState::~AsyncOperationState() +{ + CloseHandle(this->hOperationCompleteEvent); +} +//Sets the isWorking flag if neither isWorking nor isReady is set, and returns true. Returns false otherwise. +bool AsyncOperationState::Start() +{ + State oldState; oldState.val = this->state.val; + if (!oldState.isWorking && !oldState.isReady) + { + State newState = {}; + newState.isReady = false; newState.isWorking = true; + oldState.val = InterlockedExchange(&this->state.val, newState.val); + if (oldState.isWorking || oldState.isReady) + { + oldState.val = InterlockedExchange(&this->state.val, oldState.val); + if (oldState.isReady) + { + this->state.isReady = true; + std::atomic_thread_fence(std::memory_order::seq_cst); + this->state.isWorking = false; + } + return false; + } + else + { + return true; + } + } + return false; +} +//Waits for completion if it is in the working state. Resets the ready flag if necessary, returns true if any open resources should be freed. +bool AsyncOperationState::Close() +{ + if (this->state.isWorking) + WaitForSingleObject(this->hOperationCompleteEvent, INFINITE); + if (this->state.isReady) + { + this->state.isReady = false; + ResetEvent(this->hOperationCompleteEvent); + return true; + } + return false; +} +void AsyncOperationState::OnCompletion() +{ + this->state.isReady = true; + std::atomic_thread_fence(std::memory_order::seq_cst); + this->state.isWorking = false; + SetEvent(this->hOperationCompleteEvent); +} +void AsyncOperationState::OnFailure() +{ + this->state.isWorking = false; + SetEvent(this->hOperationCompleteEvent); +} +#pragma endregion AsyncOperationState \ No newline at end of file diff --git a/UABE_Generic/FileContext.h b/UABE_Generic/FileContext.h new file mode 100644 index 0000000..9055254 --- /dev/null +++ b/UABE_Generic/FileContext.h @@ -0,0 +1,351 @@ +#pragma once +#include "api.h" +#include +#include "AsyncTask.h" +#include "../AssetsTools/AssetBundleFileFormat.h" +#include "../AssetsTools/AssetsFileFormat.h" +#include "../AssetsTools/AssetsFileTable.h" +#include "../AssetsTools/AssetsFileReader.h" + +class IFileContext; +class IFileOpenCallback +{ +public: + UABE_Generic_API IFileOpenCallback(); + UABE_Generic_API virtual ~IFileOpenCallback(); + UABE_Generic_API virtual void OnFileOpenResult(IFileContext *pContext, int result)=0; +}; + +enum EFileContextType +{ + FileContext_Bundle=0, + FileContext_Assets=1, + FileContext_Resources=2, + FileContext_Generic=3, + FileContext_COUNT +}; +class IFileContext +{ +protected: + std::string fileName; + std::string filePath; + IFileContext *pParent; +public: + UABE_Generic_API IFileContext(const std::string &filePath, IFileContext *pParent); + UABE_Generic_API virtual ~IFileContext(); + UABE_Generic_API virtual EFileContextType getType()=0; + UABE_Generic_API virtual IFileContext *getParent(); + + UABE_Generic_API const std::string& getFileName(); //UTF-8; May return getFilePath(), e.g. if it has a parent. + UABE_Generic_API const std::string& getFilePath(); //UTF-8 + UABE_Generic_API std::string getFileDirectoryPath(); //UTF-8 +}; + +class AsyncOperationState +{ +public: +#ifndef WINAPI +#define _TMPDEF_WINAPI +#define HANDLE void* +#endif + HANDLE hOperationCompleteEvent; +#ifdef _TMPDEF_WINAPI +#undef _TMPDEF_WINAPI +#undef HANDLE +#endif + union State { + struct { + bool isWorking; + bool isReady; + }; + unsigned int val; + } state; + UABE_Generic_API AsyncOperationState(); + UABE_Generic_API ~AsyncOperationState(); + //Sets the isWorking flag if neither isWorking nor isReady is set, and returns true. Returns false otherwise. + UABE_Generic_API bool Start(); + //Waits for completion if it is in the working state. Resets the ready flag if necessary, returns true if any open resources should be freed. + UABE_Generic_API bool Close(); + UABE_Generic_API void OnCompletion(); + UABE_Generic_API void OnFailure(); + inline bool isReady() + { + return this->state.isReady; + } +}; + +enum EBundleFileOpenStatus +{ + BundleFileOpenStatus_ErrUnknownVersion=-3, + BundleFileOpenStatus_ErrInvalid=-2, + BundleFileOpenStatus_ErrFileOpen=-1, + BundleFileOpenStatus_OK=0, + BundleFileOpenStatus_Pend=1, + BundleFileOpenStatus_CompressedDirectory=2, //The block&directory list is compressed and has not been processed. The context is considered open. + BundleFileOpenStatus_CompressedData=3, //Reading succeeded but there are compressed blocks. +}; +enum EBundleFileDecompressStatus +{ + BundleFileDecompressStatus_ErrBundleNotOpened=-3, + BundleFileDecompressStatus_ErrDecompress=-2, + BundleFileDecompressStatus_ErrOutFileOpen=-1, + BundleFileDecompressStatus_OK=0, + BundleFileDecompressStatus_Pend=1, +}; +class IBundleDecompressCallback : public IFileOpenCallback +{}; +class BundleFileContext : public IFileContext +{ + class OpenTaskCallback : public TaskProgressCallback + { + BundleFileContext *pContext; + public: + OpenTaskCallback(BundleFileContext *pContext); + void OnCompletion(std::shared_ptr &pTask, TaskResult result); + }; + class OpenTask : public ITask + { + BundleFileContext *pContext; + std::string name; + public: + OpenTask(BundleFileContext *pContext); + const std::string &getName(); + TaskResult execute(TaskProgressManager &progressManager); + }; + std::shared_ptr pReader; bool inheritReader; bool readerIsModified; + AssetBundleFile bundle; + + AsyncOperationState openState; + AsyncOperationState decompressState; + + EBundleFileOpenStatus lastOpenStatus; + OpenTaskCallback openTaskCallback; + OpenTask openTask; + + EBundleFileDecompressStatus lastDecompressStatus; + + IFileOpenCallback *pOpenCallback; + IBundleDecompressCallback *pDecompressCallback; + + EBundleFileOpenStatus OpenSync(TaskProgressManager *pProgressManager, unsigned int initProgress = 0, unsigned int progressScale = 100); + EBundleFileDecompressStatus DecompressSync(TaskProgressManager *pProgressManager, const std::string &outPath); + + //For v3 bundles: Returns false. For v6 bundles: Returns the directory flag "has serialized data". + //-> If true, the file is supposed to be an .assets file. + bool hasSerializedData(size_t index); +public: + UABE_Generic_API BundleFileContext(const std::string &filePath, std::shared_ptr pReader = nullptr, bool readerIsModified = false); + UABE_Generic_API BundleFileContext(const std::string &filePath, IFileContext *pParent, std::shared_ptr pReader, bool readerIsModified = false); + ~BundleFileContext(); + + UABE_Generic_API EBundleFileOpenStatus Open(); + //Run the open task inside another task. Takes up 100 progress units, starting with initProgress. Requires progressScale >= initProgress + 100. + UABE_Generic_API EBundleFileOpenStatus OpenInsideTask(TaskProgressManager *pProgressManager, unsigned int initProgress = 0, unsigned int progressScale = 100); + + UABE_Generic_API void Close(); + + //Returns the internal reader. While the Read function itself is thread-safe, the internal reader position must not be relied on. + //Returns null if the context has not been opened. + UABE_Generic_API IAssetsReader *getReaderUnsafe(bool *isInherited = nullptr); + //Creates a view of the internal reader for thread-safe access that can be freed with Free_AssetsReader. + //Note that the reader view must not be used on several threads in parallel. Further CreateView calls on the view are required in that case. + //Returns null if the context has not been opened or if creating a view failed. + inline IAssetsReader *createReaderView(bool *isInherited = nullptr) + { + IAssetsReader *pReader = getReaderUnsafe(isInherited); + if (pReader) + return pReader->CreateView(); + return nullptr; + } + + //Returns null if the context has not been opened. + UABE_Generic_API AssetBundleFile *getBundleFile(); + //Returns nullptr if either the context has not been opened or the bundle is compressed (or I/O errors). + UABE_Generic_API std::shared_ptr makeEntryReader(size_t index); + //Returns null if either the context has not been opened or the file table is compressed. + UABE_Generic_API const char *getEntryName(size_t index); + //Returns 0 if either no entries exist or the context has not been opened. + UABE_Generic_API size_t getEntryCount(); + + inline void setFreeReaderOnClose(bool free) + { + inheritReader = !free; + } + + inline bool getReaderIsModified() + { + return readerIsModified; + } + + EFileContextType getType(); + + friend class BundleFileContextInfo; +}; + +enum EAssetsFileOpenStatus +{ + AssetsFileOpenStatus_ErrInvalidOrUnsupported=-2, + AssetsFileOpenStatus_ErrFileOpen=-1, + AssetsFileOpenStatus_OK=0, + AssetsFileOpenStatus_Pend=1, +}; +class AssetsFileContext : public IFileContext +{ + class OpenTaskCallback : public TaskProgressCallback + { + AssetsFileContext *pContext; + public: + OpenTaskCallback(AssetsFileContext *pContext); + void OnCompletion(std::shared_ptr &pTask, TaskResult result); + }; + class OpenTask : public ITask + { + AssetsFileContext *pContext; + std::string name; + public: + OpenTask(AssetsFileContext *pContext); + const std::string &getName(); + TaskResult execute(TaskProgressManager &progressManager); + }; + std::shared_ptr pReader; bool inheritReader; bool readerIsModified; + AssetsFile *pAssetsFile; + AssetsFileTable *pAssetsFileTable; + + AsyncOperationState openState; + + EAssetsFileOpenStatus lastOpenStatus; + OpenTaskCallback openTaskCallback; + OpenTask openTask; + + IFileOpenCallback *pOpenCallback; + bool doMakeBinaryTable; + + //The "total" amount of work to be done. 100 is the amount of work AssetsFileContext does. + // Return a higher number in a deriving class in order to extend the progress scale for additional OpenSync functionality. + virtual int getProgressScale(); + virtual EAssetsFileOpenStatus OpenSync(TaskProgressManager *pProgressManager, bool makeBinaryTable, unsigned int initProgress, unsigned int progressScale); +public: + UABE_Generic_API AssetsFileContext(const std::string &filePath, std::shared_ptr pReader = nullptr, bool readerIsModified = false); + UABE_Generic_API AssetsFileContext(const std::string &filePath, IFileContext *pParent, std::shared_ptr pReader, bool readerIsModified = false); + ~AssetsFileContext(); + + UABE_Generic_API EAssetsFileOpenStatus Open(bool makeBinaryTable); + //Run the open task inside another task. Takes up 100 progress units, starting with initProgress. Requires progressScale >= initProgress + 100. + UABE_Generic_API EAssetsFileOpenStatus OpenInsideTask(TaskProgressManager *pProgressManager, bool makeBinaryTable, unsigned int initProgress = 0, unsigned int progressScale = 100); + + UABE_Generic_API void Close(); + + //Returns the internal reader. While the Read function itself is thread-safe, the internal reader position must not be relied on. + //Returns null if the context has not been opened. + UABE_Generic_API IAssetsReader *getReaderUnsafe(bool *isInherited = nullptr); + //Creates a view of the internal reader for thread-safe access that can be freed with Free_AssetsReader. + //Note that the reader view must not be used on several threads in parallel. Further CreateView calls on the view are required in that case. + //Returns null if the context has not been opened or if creating a view failed. + UABE_Generic_API IAssetsReader *createReaderView(bool *isInherited = nullptr); + + //Returns null if the context has not been opened. + UABE_Generic_API AssetsFile *getAssetsFile(); + //Returns null if the context has not been opened. + UABE_Generic_API AssetsFileTable *getAssetsFileTable(); + + inline void setFreeReaderOnClose(bool free) + { + inheritReader = !free; + } + + inline bool getReaderIsModified() + { + return readerIsModified; + } + + EFileContextType getType(); +}; + +enum EResourcesFileOpenStatus +{ + ResourcesFileOpenStatus_ErrFileOpen=-1, + ResourcesFileOpenStatus_OK=0, +}; +class ResourcesFileContext : public IFileContext +{ + std::shared_ptr pReader; bool inheritReader; bool readerIsModified; + +public: + UABE_Generic_API ResourcesFileContext(const std::string &filePath, std::shared_ptr pReader = nullptr, bool readerIsModified = false); + UABE_Generic_API ResourcesFileContext(const std::string &filePath, IFileContext *pParent, std::shared_ptr pReader, bool readerIsModified = false); + ~ResourcesFileContext(); + + UABE_Generic_API EResourcesFileOpenStatus Open(); + + UABE_Generic_API void Close(); + + //Returns the internal reader. While the Read function itself is thread-safe, the internal reader position must not be relied on. + //Returns null if the context has not been opened. + UABE_Generic_API IAssetsReader *getReaderUnsafe(bool *isInherited = nullptr); + //Creates a view of the internal reader for thread-safe access that can be freed with Free_AssetsReader. + //Note that the reader view must not be used on several threads in parallel. Further CreateView calls on the view are required in that case. + //Returns null if the context has not been opened or if creating a view failed. + inline IAssetsReader *createReaderView(bool *isInherited = nullptr) + { + IAssetsReader *pReader = getReaderUnsafe(isInherited); + if (pReader) + return pReader->CreateView(); + return nullptr; + } + + inline void setFreeReaderOnClose(bool free) + { + inheritReader = !free; + } + + inline bool getReaderIsModified() + { + return readerIsModified; + } + + EFileContextType getType(); +}; + +enum EGenericFileOpenStatus +{ + GenericFileOpenStatus_ErrFileOpen=-1, + GenericFileOpenStatus_OK=0, +}; +class GenericFileContext : public IFileContext +{ + std::shared_ptr pReader; bool inheritReader; bool readerIsModified; +public: + UABE_Generic_API GenericFileContext(const std::string &filePath, std::shared_ptr pReader = nullptr, bool readerIsModified = false); + UABE_Generic_API GenericFileContext(const std::string &filePath, IFileContext *pParent, std::shared_ptr pReader, bool readerIsModified = false); + ~GenericFileContext(); + + UABE_Generic_API EGenericFileOpenStatus Open(); + + UABE_Generic_API void Close(); + + //Returns the internal reader. While the Read function itself is thread-safe, the internal reader position must not be relied on. + //Returns null if the context has not been opened. + UABE_Generic_API IAssetsReader *getReaderUnsafe(bool *isInherited = nullptr); + //Creates a view of the internal reader for thread-safe access that can be freed with Free_AssetsReader. + //Note that the reader view must not be used on several threads in parallel. Further CreateView calls on the view are required in that case. + //Returns null if the context has not been opened or if creating a view failed. + inline IAssetsReader *createReaderView(bool *isInherited = nullptr) + { + IAssetsReader *pReader = getReaderUnsafe(isInherited); + if (pReader) + return pReader->CreateView(); + return nullptr; + } + + inline void setFreeReaderOnClose(bool free) + { + inheritReader = !free; + } + + inline bool getReaderIsModified() + { + return readerIsModified; + } + + EFileContextType getType(); +}; diff --git a/UABE_Generic/FileContextInfo.cpp b/UABE_Generic/FileContextInfo.cpp new file mode 100644 index 0000000..87e1cb8 --- /dev/null +++ b/UABE_Generic/FileContextInfo.cpp @@ -0,0 +1,2230 @@ +#include "FileContextInfo.h" +#include "AppContext.h" +#include +#include + +FileContextInfo::FileContextInfo(unsigned int fileID, unsigned int parentFileID) + : fileID(fileID), parentFileID(parentFileID) +{} +FileContextInfo::~FileContextInfo() +{} +std::string FileContextInfo::getFileName() +{ + { + std::lock_guard fileNameLock(fileNameOverrideMutex); + if (!fileNameOverride.empty()) + return fileNameOverride; + } + if (getFileContext()) + return getFileContext()->getFileName(); + return ""; +} +void FileContextInfo::setFileName(std::string name) +{ + std::lock_guard fileNameLock(fileNameOverrideMutex); + fileNameOverride = std::move(name); +} + +AssetsFileContextInfo::AssetsFileContextInfo(AssetsFileContext *pContext, unsigned int fileID, unsigned int parentFileID) + : FileContextInfo(fileID, parentFileID), pContext(pContext), pClassDatabase(nullptr, ClassDatabaseFileDeleter_Dummy) +{ + assert(pContext->getAssetsFile()); + if (pContext->getAssetsFile()) + { + references.resize(pContext->getAssetsFile()->dependencies.dependencyCount, 0); + dependencies.assign(&pContext->getAssetsFile()->dependencies.pDependencies[0], + &pContext->getAssetsFile()->dependencies.pDependencies[references.size()]); + } +} +AssetsFileContextInfo::~AssetsFileContextInfo() +{ + IAssetsReader *pReader = this->pContext->getReaderUnsafe(); + this->pContext->Close(); + delete this->pContext; + this->pContext = nullptr; +} +IFileContext *AssetsFileContextInfo::getFileContext() +{ + return this->pContext; +} +void AssetsFileContextInfo::getChildFileIDs(std::vector &childFileIDs) +{ + childFileIDs.clear(); +} +void AssetsFileContextInfo::onCloseChild(unsigned int childFileID) +{ +} +bool AssetsFileContextInfo::hasAnyChanges(AppContext &appContext) +{ + if (permanentChangedFlag) + return true; + if (this->getAssetsFileContext() != nullptr && this->getAssetsFileContext()->getReaderIsModified()) + return true; + this->lockReplacersRead(); + bool ret = !this->pReplacersByPathID.empty(); + this->unlockReplacersRead(); + if (ret) + return true; + auto refLock = this->lockReferencesRead(); + ret = this->dependenciesChanged; + return ret; +} +bool AssetsFileContextInfo::hasNewChanges(AppContext &appContext) +{ + return this->changedFlag; +} +uint64_t AssetsFileContextInfo::write(class AppContext &appContext, IAssetsWriter *pWriter, uint64_t start, bool resetChangedFlag) +{ + auto refLock = this->lockReferencesRead(); + this->lockReplacersRead(); + if (this->pContext == nullptr || this->pContext->getAssetsFile() == nullptr) + { + this->unlockReplacersRead(); + return 0; + } + std::vector replacers; replacers.reserve(this->pReplacersByPathID.size()); + for (auto it = this->pReplacersByPathID.begin(); it != this->pReplacersByPathID.end(); ++it) + { + assert(it->second.pReplacer != nullptr); + replacers.push_back(it->second.pReplacer.get()); + } + std::unique_ptr pDependenciesReplacer_raii; + if (dependenciesChanged) + { + std::vector dependencies = this->getDependenciesRead(refLock); + pDependenciesReplacer_raii.reset(MakeAssetsDependenciesReplacer(0, std::move(dependencies))); + replacers.push_back(pDependenciesReplacer_raii.get()); + } + uint64_t ret = this->pContext->getAssetsFile()->Write(pWriter, start, replacers.data(), replacers.size(), (uint32_t)-1, this->pClassDatabase.get()); + if (resetChangedFlag && ret != 0) + this->changedFlag = false; + this->unlockReplacersRead(); + return ret; +} +std::unique_ptr AssetsFileContextInfo::makeBundleReplacer(class AppContext &appContext, + const char *oldName, const char *newName, uint32_t bundleIndex, + bool resetChangedFlag) +{ + auto refLock = this->lockReferencesRead(); + this->lockReplacersRead(); + if (this->pContext == nullptr || this->pContext->getAssetsFile() == nullptr + || (this->pReplacersByPathID.empty() && !dependenciesChanged)) + { + this->unlockReplacersRead(); + return nullptr; + } + std::shared_lock classDatabaseLock(this->classDatabaseMutex); + ClassDatabaseFile_sharedptr pClassDatabase = this->pClassDatabase; + classDatabaseLock.unlock(); + std::vector> pReplacers; + pReplacers.reserve(this->pReplacersByPathID.size()); + for (auto replacerIt = this->pReplacersByPathID.begin(); replacerIt != this->pReplacersByPathID.end(); ++replacerIt) + { + if (replacerIt->second.pReplacer != nullptr + && (replacerIt->second.pReplacer->GetFileID() == 0 || replacerIt->second.pReplacer->GetFileID() == this->getFileID())) + pReplacers.push_back(replacerIt->second.pReplacer); + else + assert(false); //Replacer is nullptr, or for another file. + } + if (dependenciesChanged) + { + std::vector dependencies = this->getDependenciesRead(refLock); + pReplacers.emplace_back(MakeAssetsDependenciesReplacer(0, std::move(dependencies))); + } + if (resetChangedFlag) + this->changedFlag = false; + this->unlockReplacersRead(); + return MakeBundleEntryModifierFromAssets(oldName, newName, + std::move(pClassDatabase), std::move(pReplacers), + this->getFileID(), bundleIndex); +} + + +std::vector> AssetsFileContextInfo::getAllReplacers() +{ + std::vector> ret; + auto refLock = lockReferencesRead(); + lockReplacersRead(); + for (auto it = this->pReplacersByPathID.begin(); it != this->pReplacersByPathID.end(); ++it) + ret.push_back(it->second.pReplacer); + if (dependenciesChanged) + { + std::vector dependencies = this->getDependenciesRead(refLock); + ret.emplace_back(MakeAssetsDependenciesReplacer(0, std::move(dependencies))); + } + unlockReplacersRead(); + return ret; +} +std::shared_ptr AssetsFileContextInfo::getReplacer(pathid_t pathID) +{ + std::shared_ptr ret; + lockReplacersRead(); + auto it = this->pReplacersByPathID.find(pathID); + if (it != this->pReplacersByPathID.end()) + ret = it->second.pReplacer; + unlockReplacersRead(); + return ret; +} +void AssetsFileContextInfo::addReplacer(std::shared_ptr replacer, AppContext &appContext, bool reuseTypeMetaFromOldReplacer, bool signalMainThread) //Removes any previous replacers for that path ID. +{ + assert(this->pContext && this->pContext->getAssetsFileTable()); + uint64_t pathID = replacer->GetPathID(); + bool isRemover = replacer->GetType() == AssetsReplacement_Remove; + lockReplacersWrite(); + auto it = this->pReplacersByPathID.find((pathid_t)replacer->GetPathID()); + if (it != this->pReplacersByPathID.end()) + { + if (!it->second.replacesExistingAsset && replacer->GetType() == AssetsReplacement_Remove) + this->pReplacersByPathID.erase(it); //The asset was created by a replacer => the remover does not need to be stored. + else + { + if (reuseTypeMetaFromOldReplacer && replacer->GetType() != AssetsReplacement_Remove) + { + Hash128 origPropertiesHash; + if (it->second.pReplacer->GetPropertiesHash(origPropertiesHash)) + replacer->SetPropertiesHash(origPropertiesHash); + Hash128 origScriptIDHash; + if (it->second.pReplacer->GetScriptIDHash(origScriptIDHash)) + replacer->SetScriptIDHash(origScriptIDHash); + assert(replacer->GetMonoScriptID() == it->second.pReplacer->GetMonoScriptID()); + + const AssetPPtr *pPreloadList; size_t preloadListLen = 0; + if (it->second.pReplacer->GetPreloadDependencies(pPreloadList, preloadListLen)) + replacer->SetPreloadDependencies(pPreloadList, preloadListLen); + + std::shared_ptr pOrigClassFile; + ClassDatabaseType *pOrigClassType = nullptr; + if (it->second.pReplacer->GetTypeInfo(pOrigClassFile, pOrigClassType)) + replacer->SetTypeInfo(std::move(pOrigClassFile), pOrigClassType); + } + it->second.pReplacer = std::move(replacer); //The previous replacer was overridden. + } + } + else + { + ReplacerEntry entry; + //Insert or overwrite the replacer entry for the path ID. + auto insertResult = this->pReplacersByPathID.insert(std::make_pair(replacer->GetPathID(), entry)); + insertResult.first->second.replacesExistingAsset = (this->pContext->getAssetsFileTable()->getAssetInfo(replacer->GetPathID()) != nullptr); + insertResult.first->second.pReplacer = std::move(replacer); + } + this->changedFlag = true; + unlockReplacersWrite(); + if (signalMainThread) + appContext.OnChangeAsset_Async(this, pathID, isRemover); +} + +std::vector> &AssetsFileContextInfo::lockScriptDatabases() +{ + this->scriptDatabasesMutex.lock(); + return this->pScriptDatabases; +} +void AssetsFileContextInfo::unlockScriptDatabases() +{ + this->scriptDatabasesMutex.unlock(); +} + +AssetsFileContextInfo::ContainersTask::ContainersTask(AppContext &appContext, std::shared_ptr &pContextInfo) + : appContext(appContext), pFileContextInfo(pContextInfo) +{ + assert(pFileContextInfo->getAssetsFileContext() && pFileContextInfo->getAssetsFileContext()->getAssetsFile()); + name = "Resolve containers : " + pContextInfo->getFileName() + ""; +} +const std::string &AssetsFileContextInfo::ContainersTask::getName() +{ + return name; +} +TaskResult AssetsFileContextInfo::ContainersTask::execute(TaskProgressManager &progressManager) +{ + if (!pFileContextInfo->getAssetsFileContext() || !pFileContextInfo->getAssetsFileContext()->getAssetsFile()) + { + progressManager.logMessage("Assets file not loaded!"); + return -1; + } + + progressManager.setProgressDesc("Searching for container assets"); + progressManager.setProgress(0, 0); + AssetIterator iterator(this->pFileContextInfo.get()); + AssetIdentifier identifier; + identifier.pFile = this->pFileContextInfo; + + int32_t resmgrClassID = this->pFileContextInfo->GetClassByName("ResourceManager"); + if (resmgrClassID == -1) resmgrClassID = ASSETTYPE_RESOURCEMANAGER; + AssetTypeTemplateField resmgrTemplateBase; + bool hasResmgrTemplate = this->pFileContextInfo->MakeTemplateField(&resmgrTemplateBase, this->appContext, resmgrClassID); + + int32_t bundleClassID = this->pFileContextInfo->GetClassByName("AssetBundle"); + if (bundleClassID == -1) bundleClassID = ASSETTYPE_ASSETBUNDLE; + AssetTypeTemplateField bundleTemplateBase; + bool hasBundleTemplate = this->pFileContextInfo->MakeTemplateField(&bundleTemplateBase, this->appContext, bundleClassID); + + AssetContainerList newContainerList; + TaskResult result = 0; + for (; !iterator.isEnd(); ++iterator) + { + iterator.get(identifier); + if (identifier.resolve(this->appContext)) + { + int32_t curClassID = identifier.getClassID(); + if (curClassID == resmgrClassID) + { + ResourceManagerFile resMgrFile; + if (hasResmgrTemplate) + { + IAssetsReader_ptr pReader = identifier.makeReader(); + AssetTypeTemplateField *pResmgrTemplateBase = &resmgrTemplateBase; + AssetTypeInstance resmgrInstance(1, &pResmgrTemplateBase, identifier.getDataSize(), pReader.get(), identifier.isBigEndian()); + AssetTypeValueField *pInstanceBase = resmgrInstance.GetBaseField(); + if (pInstanceBase) + { + resMgrFile.Read(pInstanceBase); + } + else + { + progressManager.logMessage("Unable to deserialize a ResourceManager asset!"); + result = 1; + continue; + } + } + else + { + uint64_t dataSize = identifier.getDataSize(); + if (dataSize > SIZE_MAX) + { + progressManager.logMessage("Data size invalid!"); + result = 2; + continue; + } + std::unique_ptr resmgrBuf(new uint8_t[(size_t)dataSize]); + if (identifier.read(dataSize, resmgrBuf.get()) != dataSize) + { + progressManager.logMessage("Unable to lock the reader!"); + result = 3; + continue; + } + size_t filePos = 0; + resMgrFile.Read(resmgrBuf.get(), (size_t)dataSize, &filePos, pFileContextInfo->getAssetsFileContext()->getAssetsFile()->header.format, identifier.isBigEndian()); + } + if (resMgrFile.IsRead()) + { + if (!newContainerList.LoadFrom(resMgrFile)) + { + progressManager.logMessage("Unable to populate the containers list with a ResourceManager asset!"); + result = 4; + } + } + else + { + progressManager.logMessage("Unable to deserialize a ResourceManager asset!"); + result = 5; + } + } + else if (curClassID == bundleClassID) + { + AssetBundleAsset assetBundleFile; + + uint64_t dataSize = identifier.getDataSize(); + if (dataSize > SIZE_MAX) + { + progressManager.logMessage("Data size invalid!"); + result = 6; + continue; + } + std::unique_ptr bundleBuf(new uint8_t[(size_t)dataSize]); + if (identifier.read(dataSize, bundleBuf.get()) != dataSize) + { + progressManager.logMessage("Unable to read the data!"); + result = 7; + continue; + } + if (hasBundleTemplate) + { + size_t filePos = 0; + assetBundleFile.ReadBundleFile(bundleBuf.get(), (size_t)dataSize, &filePos, &bundleTemplateBase, identifier.isBigEndian()); + } + else + { + size_t filePos = 0; + assetBundleFile.ReadBundleFile(bundleBuf.get(), (size_t)dataSize, &filePos, pFileContextInfo->getAssetsFileContext()->getAssetsFile()->header.format, identifier.isBigEndian()); + } + if (assetBundleFile.IsRead()) + { + if (!newContainerList.LoadFrom(assetBundleFile)) + { + progressManager.logMessage("Unable to populate the containers list with an AssetBundle asset!"); + result = 8; + } + } + else + { + progressManager.logMessage("Unable to deserialize an AssetBundle asset!"); + result = 9; + } + } + } + else + progressManager.logMessage("Unable to resolve an asset!"); + } + this->pFileContextInfo->lockContainersWrite(); + this->pFileContextInfo->containers = std::move(newContainerList); + this->pFileContextInfo->unlockContainersWrite(); + return result; +} +bool AssetsFileContextInfo::EnqueueContainersTask(AppContext &appContext, std::shared_ptr selfPointer) +{ + assert(selfPointer.get() == this); + std::shared_ptr pTask = std::make_shared(appContext, selfPointer); + if (appContext.taskManager.enqueue(pTask)) + return true; + return false; +} + + +bool AssetsFileContextInfo::_GetMonoBehaviourScriptInfo(AssetIdentifier &asset, AppContext &appContext, + std::string &fullClassName, std::string &assemblyName, + std::string &className, std::string &namespaceName, + AssetIdentifier &scriptAsset) +{ + if (!pContext || !pContext->getAssetsFile()) + return false; + bool ret = false; + //Don't use class data from the .assets file (especially for bundled ones) as it might resolve MonoBehaviour to -1 or -2. + //int monoBehaviourClass = -1;//pInterface->GetClassByName(pAssetsFile, "MonoBehaviour"); + if (asset.pathID != 0) + { + AssetTypeTemplateField behaviourBase; + AssetTypeTemplateField *pBehaviourBase = &behaviourBase; + std::shared_lock classDatabaseLock(this->classDatabaseMutex); + bool failed = true; + if (pClassDatabase != nullptr) + { + for (size_t i = 0; i < pClassDatabase->classes.size(); i++) + { + if (!strcmp(pClassDatabase->classes[i].name.GetString(pClassDatabase.get()), "MonoBehaviour")) + { + failed = !pBehaviourBase->FromClassDatabase(pClassDatabase.get(), &pClassDatabase->classes[i], 0); + //monoBehaviourClass = pFile->classes[i].classId; + break; + } + } + } + classDatabaseLock.unlock(); + if (!failed) + { + //Try to find and read the script asset. + IAssetsReader_ptr pReader = asset.makeReader(); + unsigned __int64 fileSize = asset.getDataSize(); + AssetTypeInstance behaviourInstance(1, &pBehaviourBase, fileSize, pReader.get(), asset.isBigEndian()); + pReader.reset(); + + AssetTypeValueField *pBehaviourBase = behaviourInstance.GetBaseField(); + AssetTypeValueField *pScriptFileIDField; + AssetTypeValueField *pScriptPathIDField; + if ((pBehaviourBase != NULL) && + (pScriptFileIDField = pBehaviourBase->Get("m_Script")->Get("m_FileID"))->GetValue() && + (pScriptPathIDField = pBehaviourBase->Get("m_Script")->Get("m_PathID"))->GetValue()) + { + unsigned int scriptFileID = asset.pFile->resolveRelativeFileID(pScriptFileIDField->GetValue()->AsInt()); + long long int scriptPathID = pScriptPathIDField->GetValue()->AsInt64(); + scriptAsset = AssetIdentifier(scriptFileID, scriptPathID); + if (scriptAsset.resolve(appContext)) + { + AssetsFileContextInfo *pScriptAssetsContextInfo = scriptAsset.pFile.get(); + bool foundScript = false; + int monoScriptClass = GetClassByName("MonoScript"); + AssetTypeTemplateField scriptTemplateBase; + if ((monoScriptClass >= 0) && pScriptAssetsContextInfo->MakeTemplateField(&scriptTemplateBase, appContext, monoScriptClass)) + { + unsigned long long curFileSize = scriptAsset.getDataSize(); + IAssetsReader_ptr pReader = scriptAsset.makeReader(); + if (pReader != nullptr) + { + AssetTypeTemplateField *pScriptTemplateBase = &scriptTemplateBase; + AssetTypeInstance scriptInstance(1, &pScriptTemplateBase, curFileSize, pReader.get(), scriptAsset.isBigEndian()); + AssetTypeValueField *pScriptBase = scriptInstance.GetBaseField(); + AssetTypeValueField *pScriptClassNameField; const char *scriptClassName; + AssetTypeValueField *pScriptNamespaceField; const char *scriptNamespace; + AssetTypeValueField *pScriptAssemblyNameField; const char *scriptAssemblyName; + if ((pScriptBase != NULL) && + (pScriptClassNameField = pScriptBase->Get("m_ClassName"))->GetValue() + && (scriptClassName = pScriptClassNameField->GetValue()->AsString()) && + (pScriptNamespaceField = pScriptBase->Get("m_Namespace"))->GetValue() + && (scriptNamespace = pScriptNamespaceField->GetValue()->AsString())) + { + if ((pScriptAssemblyNameField = pScriptBase->Get("m_AssemblyName"))->GetValue() + && (scriptAssemblyName = pScriptAssemblyNameField->GetValue()->AsString())) + {} + else + scriptAssemblyName = ""; + fullClassName = std::string(scriptNamespace); + if (fullClassName.size() > 0) + fullClassName += "."; + fullClassName += scriptClassName; + assemblyName = std::string(scriptAssemblyName); + className = std::string(scriptClassName); + namespaceName = std::string(scriptNamespace); + ret = true; + } + } + } + } + } + } + } + return ret; +} +bool AssetsFileContextInfo::FindScriptClassDatabaseEntry(ClassDatabaseFile *&pClassFile, ClassDatabaseType *&pClassType, AssetIdentifier &asset, AppContext &appContext, Hash128 *pScriptID) +{ + pClassFile = nullptr; + pClassType = nullptr; + { + //Try to find a matching script class database entry. + std::string fullScriptName; + std::string assemblyName; + std::string scriptName; + std::string namespaceName; + AssetIdentifier scriptAsset; + if (_GetMonoBehaviourScriptInfo(asset, appContext, fullScriptName, assemblyName, scriptName, namespaceName, scriptAsset)) + { + //Look for the script database in the .assets file where the MonoScript asset lies. + auto &scriptDatabases = scriptAsset.pFile->lockScriptDatabases(); + bool found = false; + for (size_t i = 0; i < scriptDatabases.size(); i++) + { + std::shared_ptr &pCurDatabase = scriptDatabases[i]; + if (!(pCurDatabase->header.flags & 1)) + continue; + for (size_t k = 0; k < pCurDatabase->classes.size(); k++) + { + ClassDatabaseType &curType = pCurDatabase->classes[k]; + const char *curAssemblyName = assemblyName.size() > 0 ? curType.assemblyFileName.GetString(pCurDatabase.get()) : ""; + const char *curFullClassName = curType.name.GetString(pCurDatabase.get()); + if (curType.fields.size() > 1 && !stricmp(curAssemblyName, assemblyName.c_str()) && !strcmp(curFullClassName, fullScriptName.c_str())) + { + if (pScriptID) + { + *pScriptID = MakeScriptID(scriptName.c_str(), namespaceName.c_str(), curType.assemblyFileName.GetString(pCurDatabase.get())); + } + pClassFile = pCurDatabase.get(); + pClassType = &curType; + scriptAsset.pFile->unlockScriptDatabases(); + return true; + } + } + } + scriptAsset.pFile->unlockScriptDatabases(); + } + } + if (pScriptID) *pScriptID = Hash128(); + return false; +} +bool AssetsFileContextInfo::MakeTemplateField(AssetTypeTemplateField *pTemplateBase, AppContext &appContext, int32_t classID, uint16_t scriptIndex, AssetIdentifier *pAsset, + std::optional> missingScriptTypeInfo) +{ + if (missingScriptTypeInfo.has_value()) + missingScriptTypeInfo->get() = false; + if (!pContext || !pContext->getAssetsFile()) + return false; + pTemplateBase->Clear(); + if (pAsset != nullptr && pAsset->pReplacer != nullptr) + { + ClassDatabaseFile_sharedptr pCldbFile; ClassDatabaseType* pCldbType = nullptr; + if (pAsset->pReplacer->GetTypeInfo(pCldbFile, pCldbType)) + { + pTemplateBase->Clear(); + if (pTemplateBase->FromClassDatabase(pCldbFile.get(), pCldbType, 0)) + return true; + } + } + AssetsFile *pAssetsFile = pContext->getAssetsFile(); + + union { + Type_07 *pU4TypeEntry; + Type_0D *pU5TypeEntry; + } typeEntry = {NULL}; + if (classID < 0 && missingScriptTypeInfo.has_value()) + missingScriptTypeInfo->get() = true; + if (classID < 0 && (pAssetsFile->header.format >= 0x10)) + { + int monoBehaviourClass = this->GetClassByName("MonoBehaviour"); + for (size_t i = 0; i < pAssetsFile->typeTree.fieldCount; i++) + { + if ((pAssetsFile->typeTree.pTypes_Unity5[i].classId == monoBehaviourClass) && + pAssetsFile->typeTree.pTypes_Unity5[i].scriptIndex == scriptIndex) + { + typeEntry.pU5TypeEntry = &pAssetsFile->typeTree.pTypes_Unity5[i]; + break; + } + } + } + else if (pAssetsFile->header.format >= 0x0D) + { + for (size_t i = 0; i < pAssetsFile->typeTree.fieldCount; i++) + { + Type_0D &type = pAssetsFile->typeTree.pTypes_Unity5[i]; + if (type.classId == classID && (type.scriptIndex == 0xFFFF || scriptIndex != 0xFFFF || classID < 0)) + { + typeEntry.pU5TypeEntry = &pAssetsFile->typeTree.pTypes_Unity5[i]; + break; + } + } + } + else + { + for (size_t i = 0; i < pAssetsFile->typeTree.fieldCount; i++) + { + if (pAssetsFile->typeTree.pTypes_Unity4[i].classId == classID) + { + typeEntry.pU4TypeEntry = &pAssetsFile->typeTree.pTypes_Unity4[i]; + break; + } + } + } + + bool failed = false; + if ((typeEntry.pU5TypeEntry != NULL) || (typeEntry.pU4TypeEntry != NULL)) + { + if (missingScriptTypeInfo.has_value()) + missingScriptTypeInfo->get() = false; + if (pAssetsFile->header.format >= 0x0D) + { + if (typeEntry.pU5TypeEntry->typeFieldsExCount > 0) + failed = !pTemplateBase->From0D(typeEntry.pU5TypeEntry, 0); + } + else + { + failed = !pTemplateBase->From07(&typeEntry.pU4TypeEntry->base); + } + if (failed) + { + pTemplateBase->Clear(); + //return false; + } + } + + if (pTemplateBase->children.size() == 0) + { + pTemplateBase->Clear(); + std::shared_lock classDatabaseLock(classDatabaseMutex); + if (pClassDatabase != nullptr) + { + ClassDatabaseFile *pFile = pClassDatabase.get(); + failed = false; + for (size_t i = 0; i < pFile->classes.size(); i++) + { + if (pFile->classes[i].classId == classID) + { + failed = !pTemplateBase->FromClassDatabase(pFile, &pFile->classes[i], 0); + break; + } + } + if (!failed && (pTemplateBase->children.size() == 0) && (classID < 0)) + { + int monoBehaviourClass = this->GetClassByName("MonoBehaviour"); + if (monoBehaviourClass >= 0) + { + failed = true; + for (size_t i = 0; i < pFile->classes.size(); i++) + { + if (pFile->classes[i].classId == monoBehaviourClass) + { + failed = !pTemplateBase->FromClassDatabase(pFile, &pFile->classes[i], 0); + break; + } + } + classDatabaseLock.unlock(); + if (!failed && pAsset) + { + assert(classID == pAsset->getClassID()); + assert(scriptIndex == pAsset->getMonoScriptID()); + + ClassDatabaseFile *pScriptDatabase = nullptr; + ClassDatabaseType *pScriptType = nullptr; + //Try to find a matching script class database entry and append the resulting template fields to the MonoBehaviour template. + if (FindScriptClassDatabaseEntry(pScriptDatabase, pScriptType, *pAsset, appContext)) + { + if (missingScriptTypeInfo.has_value()) + missingScriptTypeInfo->get() = false; + AssetTypeTemplateField tempBase; + if (tempBase.FromClassDatabase(pScriptDatabase, pScriptType, 0) && tempBase.children.size() > 0) + { + uint32_t targetOffset = (uint32_t)pTemplateBase->children.size(); + pTemplateBase->AddChildren(tempBase.children.size()); + std::copy(tempBase.children.begin(), tempBase.children.end(), pTemplateBase->children.begin() + targetOffset); + } + } + else if (missingScriptTypeInfo.has_value()) + missingScriptTypeInfo->get() = true; + } + } + } + } + if (failed) + { + pTemplateBase->Clear(); + return false; + } + if (pTemplateBase->children.empty()) + { + pTemplateBase->Clear(); + return false; + } + } + return true; +} +int32_t AssetsFileContextInfo::GetClassByName(const char *name) +{ + union { + Type_07 *pU4TypeEntry; + Type_0D *pU5TypeEntry; + } typeEntry = {NULL}; + AssetsFile *pAssetsFile; + if (!this->pContext || !(pAssetsFile = this->pContext->getAssetsFile())) + return -1; + if (pAssetsFile->header.format >= 0x0D) + { + for (size_t i = 0; i < pAssetsFile->typeTree.fieldCount; i++) + { + Type_0D *pType = &pAssetsFile->typeTree.pTypes_Unity5[i]; + if (pType->typeFieldsExCount > 0 && + !strcmp(pType->pTypeFieldsEx[0].GetTypeString(pType->pStringTable, pType->stringTableLen), name)) + { + return pType->classId; + } + } + } + else + { + for (size_t i = 0; i < pAssetsFile->typeTree.fieldCount; i++) + { + if (!strcmp(pAssetsFile->typeTree.pTypes_Unity4[i].base.type, name)) + { + return pAssetsFile->typeTree.pTypes_Unity4[i].classId; + } + } + } + + std::shared_lock classDatabaseLock(classDatabaseMutex); + if (pClassDatabase != nullptr) + { + for (size_t i = 0; i < pClassDatabase->classes.size(); i++) + { + if (!strcmp(pClassDatabase->classes[i].name.GetString(pClassDatabase.get()), name)) + { + return pClassDatabase->classes[i].classId; + } + } + } + return -1; +} +std::string AssetsFileContextInfo::GetClassName_(AppContext &appContext, int32_t classID, uint16_t scriptIndex, AssetIdentifier *pAsset) +{ + union { + Type_07 *pU4TypeEntry; + Type_0D *pU5TypeEntry; + } typeEntry = {NULL}; + AssetsFile *pAssetsFile; + if (!this->pContext || !(pAssetsFile = this->pContext->getAssetsFile())) + return std::string(); + if (classID >= 0 && scriptIndex == 0xFFFF) + { + if (pAssetsFile->header.format >= 0x0D) + { + if (pAssetsFile->typeTree.hasTypeTree) + for (size_t i = 0; i < pAssetsFile->typeTree.fieldCount; i++) + { + Type_0D *pType = &pAssetsFile->typeTree.pTypes_Unity5[i]; + if (pType->typeFieldsExCount > 0 && pType->classId == classID && pType->scriptIndex == scriptIndex) + { + const char *className = pType->pTypeFieldsEx->GetTypeString(pType->pStringTable, pType->stringTableLen); + const char *baseName = pType->pTypeFieldsEx->GetNameString(pType->pStringTable, pType->stringTableLen); + if (!stricmp(baseName, "Base")) + return std::string(className); + else + break; + } + } + } + else + { + for (size_t i = 0; i < pAssetsFile->typeTree.fieldCount; i++) + { + Type_07 *pType = &pAssetsFile->typeTree.pTypes_Unity4[i]; + if (pType->classId == classID) + { + if (!strnicmp(pType->base.name, "Base", 5)) + { + return std::string(&pType->base.type[0], + &pType->base.type[strnlen(pType->base.type, sizeof(pType->base.type) / sizeof(char))]); + } + else + break; + } + } + } + + std::shared_lock classDatabaseLock(classDatabaseMutex); + if (pClassDatabase != nullptr) + { + for (size_t i = 0; i < pClassDatabase->classes.size(); i++) + { + if (pClassDatabase->classes[i].classId == classID) + { + return std::string(pClassDatabase->classes[i].name.GetString(pClassDatabase.get())); + } + } + } + classDatabaseLock.unlock(); + + const char *typeName = NULL; + char sprntTmp[12]; + switch (classID) + { + case 0x01: + typeName = "GameObject"; + break; + case 0x04: + typeName = "Transform"; + break; + case 0x14: + typeName = "Camera"; + break; + case 0x15: + typeName = "Material"; + break; + case 0x17: + typeName = "MeshRenderer"; + break; + case 0x1C: + typeName = "Texture2D"; + break; + case 0x21: + typeName = "MeshFilter"; + break; + case 0x30: + typeName = "Shader"; + break; + case 0x31: + typeName = "Text"; + break; + case 0x41: + typeName = "BoxCollider"; + break; + case 0x53: + typeName = "Audio"; + break; + case 0x68: + typeName = "RenderSettings"; + break; + case 0x6C: + typeName = "Light"; + break; + case 0x7C: + typeName = "Behaviour"; + break; + case 0x7F: + typeName = "LevelGameManager"; + break; + case 0x87: + typeName = "SphereCollider"; + break; + case ASSETTYPE_ASSETBUNDLE: + typeName = "AssetBundle file table"; + break; + case 0x93: + typeName = "ResourceManager file table"; + break; + case 0x96: + typeName = "PreloadData"; + break; + case 0x9D: + typeName = "LightmapSettings"; + break; + case 0xD4: + typeName = "SpriteRenderer"; + break; + case 0xD5: + typeName = "Sprite"; + break; + case 0x122: + typeName = "AssetBundleManifest"; + break; + default: + sprintf_s(sprntTmp, "0x%08X", classID); + typeName = sprntTmp; + break; + } + return std::string(typeName); + } + else + { + if (pAsset != NULL && classID < 0) + { + std::string className; + std::string assemblyName; + std::string scriptName; + std::string namespaceName; + AssetIdentifier scriptAsset; + if (_GetMonoBehaviourScriptInfo(*pAsset, appContext, className, assemblyName, scriptName, namespaceName, scriptAsset)) + { + return std::string("MonoBehaviour : ") + className + " (" + assemblyName + ")"; + } + } + return std::string("MonoBehaviour"); + } + assert(false); + return std::string(); +} + +struct VersionRange +{ + char lastSeparator; //0 as a wildcard, otherwise typically '.', 'b', 'f', 'p'. + uint32_t start; + uint32_t end; + VersionRange(uint32_t start = 0, uint32_t end = UINT32_MAX) + : lastSeparator(0), start(start), end(end) + {} +}; +//Supports normal engine version strings (5.4.3p1, 2019.3.13f1, ...) and range strings (5.4.*, 5.4.2+, 5.4.3p*, 5.4.3p1-5.4.4p4, etc.). +// '*' matches any [uint]; value:=[uint] '+' matches any [uint] >= value; value:=[uint] matches only [uint] = value; +// Separators such as '.','b','f','p' must be equal, +// unless the current separator or a previous one in a range string of the form (from'-'to) differs between from and to. +// +//String format (char matches any non-digit including whitespace but not '-','+','*') : +// Version: Range {char Range} ('-' uint {char uint})? +// Range: uint | uint '+' | '*' +inline bool parseRange(const std::string &versionString, size_t start, size_t &next, VersionRange &range) +{ + if (versionString.size() <= start) + return false; + if (versionString[start] == '*') + { + next = start + 1; + range.start = 0; + range.end = (uint32_t)-1; + return true; + } + if (versionString[start] >= '0' && versionString[start] <= '9') + { + size_t uintEnd = start + 1; + for (uintEnd = start + 1; uintEnd < versionString.size(); uintEnd++) + { + if (versionString[uintEnd] < '0' || versionString[uintEnd] > '9') + break; + } + bool hasPlusSign = false; + if (uintEnd < versionString.size() && versionString[uintEnd] == '+') + { + hasPlusSign = true; + next = uintEnd + 1; + } + else + next = uintEnd; + uint32_t uintVal = 0; + try { + uintVal = (uint32_t)std::stoul(versionString.substr(start, uintEnd - start)); + } catch (...) { //Invalid number format, out of range, ... + return false; + } + range.start = uintVal; + range.end = (hasPlusSign ? UINT32_MAX : uintVal); + return true; + } + return false; +} +static std::vector parseVersionString(std::string versionString) +{ + std::vector ret; + VersionRange curRange(0, UINT32_MAX); + size_t pos = 0; + while (parseRange(versionString, pos, pos, curRange)) + { + ret.push_back(curRange); + //Match a char or '-' + if (pos >= versionString.length()) + return ret; + if (versionString[pos] == '+' || versionString[pos] == '*' + || (versionString[pos] >= '0' && versionString[pos] <= '9')) + return ret; + if (versionString[pos] == '-') + break; + curRange = VersionRange(); + curRange.lastSeparator = versionString[pos++]; + } + if (pos < versionString.length() && versionString[pos] == '-') + { + //The version string has the format from'-'to. + //Also match ranges here for simplicity (i.e. allowing the uint'+' and '*' cases). + size_t i = 0; + bool anySeparator = false; + while (parseRange(versionString, pos, pos, curRange)) + { + if (ret.size() > i) + { + if (curRange.lastSeparator != ret[i].lastSeparator) + anySeparator = true; + if (anySeparator) + ret[i].lastSeparator = 0; //wildcard + ret[i].end = curRange.end; + } + else + { + //Wildcard the separator, since there is no strict order between 'f' (final?) and 'p' (patch) releases. + curRange.lastSeparator = 0; + curRange.start = 0; + ret.push_back(curRange); + } + if (pos >= versionString.length()) + return ret; + //Match a char. + if (versionString[pos] == '+' || versionString[pos] == '*' || versionString[pos] == '-' + || (versionString[pos] >= '0' && versionString[pos] <= '9')) + return ret; + curRange = VersionRange(); + curRange.lastSeparator = versionString[pos++]; + i++; + } + } + return ret; +} + +bool versionRangesOverlap(const std::vector &a, const std::vector &b) +{ + if (a.size() > b.size()) + return versionRangesOverlap(b, a); + for (size_t i = 0; i < a.size(); i++) + { + if (i >= b.size()) + return true; + //Compare the separators and allow wildcards. + if (a[i].lastSeparator && b[i].lastSeparator && a[i].lastSeparator != b[i].lastSeparator) + return false; + //Cases : + //Range from a starts before range from b but still overlaps + //Range from a starts inside range from b + if (a[i].end >= a[i].start && b[i].end >= b[i].start && ( + (a[i].start <= b[i].start && a[i].end >= b[i].start) + || (a[i].start >= b[i].start && a[i].start <= b[i].end))) + {} + else + return false; + } + return true; +} + +bool AssetsFileContextInfo::FindClassDatabase(ClassDatabasePackage &package) +{ + AssetsFile *pAssetsFile; + if (!this->pContext || !(pAssetsFile = this->pContext->getAssetsFile())) + return false; + + //Assuming that all Unity version strings are of the form 'Major.Minor.HotfixBuild'. + std::vector targetVersion = parseVersionString(std::string(pAssetsFile->typeTree.unityVersion)); + if (targetVersion.size() == 4) //Safety measure against unexpected results (e.g. if parsing failed, all class databases would match). + { + for (uint32_t k = 0; k < package.header.fileCount; k++) + { + for (int l = 0; l < package.files[k]->header.unityVersionCount; l++) + { + char *query = package.files[k]->header.pUnityVersions[l]; + std::vector referenceRange = parseVersionString(std::string(query)); + + if (versionRangesOverlap(targetVersion, referenceRange)) + { + std::scoped_lock classDatabaseLock(this->classDatabaseMutex); + pClassDatabase = ClassDatabaseFile_sharedptr(package.files[k], ClassDatabaseFileDeleter_Dummy); + break; + } + } + if (pClassDatabase) + break; + } + if (pClassDatabase) + return true; //Found a matching class database. + } + if (pAssetsFile->AssetCount == 0 || (pAssetsFile->typeTree.fieldCount > 0 && pAssetsFile->typeTree.hasTypeTree)) + return true; //The type information is stored (hopefully for all types). + return false; +} + +void AssetsFileContextInfo::SetClassDatabase(ClassDatabaseFile_sharedptr pClassDatabase) +{ + std::scoped_lock classDatabaseLock(this->classDatabaseMutex); + this->pClassDatabase = std::move(pClassDatabase); +} +ClassDatabaseFile_sharedptr AssetsFileContextInfo::GetClassDatabase() +{ + std::shared_lock classDatabaseLock(this->classDatabaseMutex); + ClassDatabaseFile_sharedptr ret = this->pClassDatabase; + return ret; +} + +BundleFileContextInfo::BundleFileContextInfo(BundleFileContext *pContext, unsigned int fileID, unsigned int parentFileID) + : FileContextInfo(fileID, parentFileID), pContext(pContext), isDecompressed(false) +{ +} +BundleFileContextInfo::~BundleFileContextInfo() +{ + if (this->pContext) + CloseContext(); +} +void BundleFileContextInfo::CloseContext() +{ + IAssetsReader *pReader = this->pContext->getReaderUnsafe(); + this->pContext->Close(); + delete this->pContext; + this->pContext = nullptr; +} +IFileContext *BundleFileContextInfo::getFileContext() +{ + return this->pContext; +} +void BundleFileContextInfo::getChildFileIDs(std::vector &childFileIDs) +{ + std::shared_lock directoryLock(this->directoryMutex); + childFileIDs.resize(directoryRefs.size()); + for (size_t i = 0; i < directoryRefs.size(); ++i) + childFileIDs[i] = directoryRefs[i].fileID; +} +void BundleFileContextInfo::onCloseChild(unsigned int childFileID) +{ + std::scoped_lock directoryLock(this->directoryMutex); + for (size_t i = 0; i < directoryRefs.size(); i++) + { + if (directoryRefs[i].fileID == childFileID) + directoryRefs[i].fileID = 0; + } +} +bool BundleFileContextInfo::hasAnyChanges(AppContext &appContext) +{ + if (this->getBundleFileContext() != nullptr && this->getBundleFileContext()->getReaderIsModified()) + return true; + std::shared_lock directoryLock(this->directoryMutex); + for (size_t i = 0; i < directoryRefs.size(); i++) + { + unsigned int refFileID = directoryRefs[i].fileID; + bool hasChanges = false; + if (refFileID != 0) + { + FileContextInfo_ptr pContextInfo = appContext.getContextInfo(refFileID); + if (pContextInfo && pContextInfo->hasAnyChanges(appContext)) + hasChanges = true; + } + else if ((!directoryRefs[i].entryRemoved && directoryRefs[i].pOverrideReader != nullptr) + || (getBundleFileContext() != nullptr + && (directoryRefs[i].entryRemoved || (!directoryRefs[i].entryRemoved && directoryRefs[i].entryNameOverridden)) + && i < getBundleFileContext()->getEntryCount())) + hasChanges = true; + if (hasChanges) + return true; + } + return false; +} +bool BundleFileContextInfo::hasNewChanges(AppContext &appContext) +{ + std::shared_lock directoryLock(this->directoryMutex); + for (size_t i = 0; i < directoryRefs.size(); i++) + { + unsigned int refFileID = directoryRefs[i].fileID; + bool hasChanges = false; + if (refFileID != 0) + { + FileContextInfo_ptr pContextInfo = appContext.getContextInfo(refFileID); + if (pContextInfo && pContextInfo->hasNewChanges(appContext)) + hasChanges = true; + } + else if (directoryRefs[i].newChangeFlag) + hasChanges = true; + if (hasChanges) + { + return true; + } + } + return false; +} +std::string BundleFileContextInfo::getNewEntryName(size_t index, bool acquireLock) +{ + //Note: May be called while the caller has a exclusive lock on directoryLock. + std::shared_lock directoryLock(this->directoryMutex, std::defer_lock); + if (acquireLock) directoryLock.lock(); + if (index < directoryRefs.size() && directoryRefs[index].entryNameOverridden) + { + std::string ret = directoryRefs[index].newEntryName; + return ret; + } + if (acquireLock) directoryLock.unlock(); + if (!getBundleFileContext()) return ""; + auto res = getBundleFileContext()->getEntryName(index); + if (res == nullptr) return ""; + return res; +} +std::string BundleFileContextInfo::getBundlePathName() +{ + std::string firstEntryName = getNewEntryName(0); + if (firstEntryName.starts_with("BuildPlayer-") || firstEntryName.starts_with("CustomAssetBundle") || firstEntryName.starts_with("CAB")) + { + size_t iSlash = firstEntryName.rfind('/'); + if (iSlash != std::string::npos) + firstEntryName = firstEntryName.substr(iSlash + 1); + size_t iExtension = firstEntryName.rfind('.'); + if (iExtension != std::string::npos) + firstEntryName = firstEntryName.substr(0, iExtension); + return firstEntryName; + } + return ""; +} +//Returns nullptr on error (e.g. index out of range, I/O error, ...), or if the entry has been deleted. +std::shared_ptr BundleFileContextInfo::makeEntryReader(size_t index, bool &readerIsModified) +{ + readerIsModified = false; + BundleFileContext *pFileContext = this->getBundleFileContext(); + if (pFileContext == nullptr) + return nullptr; + struct CustomDeleter { + CustomDeleter(std::shared_ptr _parentReader) + : parentReader(std::move(_parentReader)) + {} + std::shared_ptr parentReader; + void operator()(IAssetsReader *pReaderToFree) + { + Free_AssetsReader(pReaderToFree); + parentReader.reset(); + } + }; + std::shared_lock directoryLock(this->directoryMutex); + if (index < directoryRefs.size()) + { + bool doRet = false; + std::shared_ptr ret; + if (directoryRefs[index].entryRemoved) + doRet = true; + else if (directoryRefs[index].pOverrideReader != nullptr) + { + doRet = true; + ret = std::shared_ptr( + directoryRefs[index].pOverrideReader->CreateView(), + CustomDeleter(directoryRefs[index].pOverrideReader)); + readerIsModified = true; + } + if (doRet) + { + return ret; + } + } + directoryLock.unlock(); + if (index < pFileContext->getEntryCount()) + { + std::shared_ptr ret = pFileContext->makeEntryReader(index); + if (ret != nullptr) + { + IAssetsReader *retView = ret->CreateView(); + readerIsModified = pFileContext->getReaderIsModified(); + return std::shared_ptr(retView, CustomDeleter(std::move(ret))); + } + } + return nullptr; +} +std::vector> BundleFileContextInfo::makeEntryReplacers(class AppContext &appContext, bool resetChangedFlag) +{ + BundleFileContext *pFileContext = this->getBundleFileContext(); + assert(pFileContext != nullptr); + + std::unique_lock directoryLockExclusive(this->directoryMutex, std::defer_lock); + std::shared_lock directoryLockShared(this->directoryMutex, std::defer_lock); + if (resetChangedFlag) + directoryLockExclusive.lock(); + else + directoryLockShared.lock(); + std::vector> pReplacers; + uint32_t nEntries = (uint32_t)std::min(UINT_MAX, directoryRefs.size()); + for (uint32_t i = 0; i < nEntries; i++) + { + const char *oldName = pFileContext->getEntryName(i); + assert(oldName != nullptr || i >= pFileContext->getEntryCount()); + if (oldName == nullptr) + { + if (i < pFileContext->getEntryCount()) + continue; + } + std::string newName = getNewEntryName(i, false); + assert(!newName.empty() || oldName == nullptr); + if (newName.empty()) newName.assign(oldName); + unsigned int refFileID = directoryRefs[i].fileID; + uint32_t bundleEntryIndex = (i < pFileContext->getEntryCount()) ? (uint32_t)i : (uint32_t)-1; + FileContextInfo_ptr pContextInfo = (refFileID != 0) ? appContext.getContextInfo(refFileID) : nullptr; + if (pContextInfo && pContextInfo->hasAnyChanges(appContext)) + { + const char *replacerOldName = oldName; + if (pContextInfo->getFileContext()) + { + IAssetsReader *pModifiedReader = nullptr; + IFileContext *pContext = pContextInfo->getFileContext(); + switch (pContext->getType()) + { + case FileContext_Assets: + if (reinterpret_cast(pContext)->getReaderIsModified()) + pModifiedReader = reinterpret_cast(pContext)->createReaderView(); + break; + case FileContext_Bundle: + if (reinterpret_cast(pContext)->getReaderIsModified()) + pModifiedReader = reinterpret_cast(pContext)->createReaderView(); + break; + case FileContext_Resources: + if (reinterpret_cast(pContext)->getReaderIsModified()) + pModifiedReader = reinterpret_cast(pContext)->createReaderView(); + break; + case FileContext_Generic: + if (reinterpret_cast(pContext)->getReaderIsModified()) + pModifiedReader = reinterpret_cast(pContext)->createReaderView(); + break; + } + if (pModifiedReader != nullptr) + { + QWORD size = 0; + if (!pModifiedReader->Seek(AssetsSeek_End, 0)) + assert(false); + if (!pModifiedReader->Tell(size)) + assert(false); + if (!pModifiedReader->Seek(AssetsSeek_Begin, 0)) + assert(false); + + struct { + std::shared_ptr contextInfoRef; + void operator()(IAssetsReader* pReader) { Free_AssetsReader(pReader); contextInfoRef.reset(); } + } fullReaderDeleter; + fullReaderDeleter.contextInfoRef = pContextInfo; + + pReplacers.push_back(std::unique_ptr(MakeBundleEntryModifier(oldName, newName.c_str(), + (pContext->getType() == FileContext_Assets), + std::shared_ptr(pModifiedReader, fullReaderDeleter), size, + 0, 0, bundleEntryIndex))); + replacerOldName = newName.c_str(); + } + } + std::unique_ptr curReplacer = pContextInfo->makeBundleReplacer(appContext, replacerOldName, newName.c_str(), bundleEntryIndex, resetChangedFlag); + if (curReplacer) + pReplacers.push_back(std::move(curReplacer)); + } + else if (directoryRefs[i].entryRemoved) + { + if (i < pFileContext->getEntryCount()) + pReplacers.push_back(std::unique_ptr(MakeBundleEntryRemover(oldName, bundleEntryIndex))); + } + else if (directoryRefs[i].pOverrideReader != nullptr) + { + IAssetsReader *pReaderView = directoryRefs[i].pOverrideReader->CreateView(); + QWORD size = 0; + if (!pReaderView->Seek(AssetsSeek_End, 0)) + assert(false); + if (!pReaderView->Tell(size)) + assert(false); + if (!pReaderView->Seek(AssetsSeek_Begin, 0)) + assert(false); + struct { + std::shared_ptr baseReaderRef; + void operator()(IAssetsReader* pReader) { Free_AssetsReader(pReader); baseReaderRef.reset(); } + } fullReaderDeleter; + fullReaderDeleter.baseReaderRef = directoryRefs[i].pOverrideReader; + pReplacers.push_back(std::unique_ptr(MakeBundleEntryModifier(oldName, newName.c_str(), + directoryRefs[i].hasSerializedData, std::shared_ptr(pReaderView, fullReaderDeleter), + size, 0, 0, bundleEntryIndex))); + } + else if (directoryRefs[i].entryNameOverridden && (bundleEntryIndex != (uint32_t)-1)) + { + pReplacers.push_back(std::unique_ptr(MakeBundleEntryRenamer(oldName, newName.c_str(), + directoryRefs[i].hasSerializedData, bundleEntryIndex))); + } + if (resetChangedFlag) + directoryRefs[i].newChangeFlag = false; + } + return pReplacers; +} +uint64_t BundleFileContextInfo::write(class AppContext &appContext, IAssetsWriter *pWriter, uint64_t start, bool resetChangedFlag) +{ + assert(this->getBundleFileContext() != nullptr); + if (this->getBundleFileContext() == nullptr || this->getBundleFileContext()->getBundleFile() == nullptr) + return 0; + BundleFileContext *pFileContext = this->getBundleFileContext(); + IAssetsReader *pReaderView = pFileContext->getReaderUnsafe()->CreateView(); + assert(pReaderView != nullptr); + if (pReaderView == nullptr) + return 0; + IAssetsWriter *pWriterOffset = Create_AssetsWriterToWriterOffset(pWriter, start); + + std::vector> pReplacers = makeEntryReplacers(appContext, resetChangedFlag); + std::vector pReplacers_raw(pReplacers.size()); + for (size_t i = 0; i < pReplacers.size(); ++i) + pReplacers_raw[i] = pReplacers[i].get(); + bool written = pFileContext->getBundleFile()->Write(pReaderView, pWriter, pReplacers_raw.data(), pReplacers_raw.size()); + + Free_AssetsReader(pReaderView); + uint64_t endPos = 0; + if (written && (!pWriterOffset->Seek(AssetsSeek_End, 0) || !pWriterOffset->Tell(endPos))) + assert(false); + Free_AssetsWriter(pWriterOffset); + return endPos; +} +std::unique_ptr BundleFileContextInfo::makeBundleReplacer(class AppContext &appContext, + const char *oldName, const char *newName, uint32_t bundleIndex, + bool resetChangedFlag) +{ + assert(this->getBundleFileContext() != nullptr); + if (this->getBundleFileContext() == nullptr || this->getBundleFileContext()->getBundleFile() == nullptr) + return nullptr; + std::vector> pReplacers = makeEntryReplacers(appContext, resetChangedFlag); + if (pReplacers.empty()) + return nullptr; + return MakeBundleEntryModifierFromBundle(oldName, newName, std::move(pReplacers), bundleIndex); +} +void BundleFileContextInfo::onDirectoryReady(class AppContext &appContext) +{ + { + std::scoped_lock directoryLock(this->directoryMutex); + this->directoryRefs.clear(); + this->directoryRefs.resize(pContext->getEntryCount()); + for (size_t i = 0; i < pContext->getEntryCount(); ++i) + this->directoryRefs[i].hasSerializedData = pContext->hasSerializedData(i); + } + + for (size_t i = 0; this->modificationsToApply && i < this->modificationsToApply->replacers.size(); ++i) + { + std::shared_ptr pReplacer = this->modificationsToApply->replacers[i].pReplacer; + BundleReplacer *pBundleReplacer = reinterpret_cast(this->modificationsToApply->replacers[i].pReplacer.get()); + const char *origEntryName = pBundleReplacer->GetOriginalEntryName(); + size_t index = (size_t)-1; + if (origEntryName != nullptr) + { + for (size_t k = 0; k < pContext->getEntryCount(); ++k) + { + const char *curEntryName = pContext->getEntryName(k); + if (curEntryName != nullptr && !stricmp(curEntryName, origEntryName)) + { + index = k; + break; + } + } + std::shared_lock directoryLock(this->directoryMutex); + for (size_t k = 0; k < this->directoryRefs.size(); ++k) + { + if (this->directoryRefs[k].entryNameOverridden && !this->directoryRefs[k].newEntryName.compare(origEntryName)) + { + index = k; + break; + } + } + } + //std::shared_ptr pOrigEntryReader = pContext->makeEntryReader(); + switch (pBundleReplacer->GetType()) + { + case BundleReplacement_Remove: + assert(index != (size_t)-1); + if (index != (size_t)-1) + this->removeEntry(appContext, index); + break; + case BundleReplacement_Rename: + { + const char *newEntryName = pBundleReplacer->GetEntryName(); + assert(index != (size_t)-1 && newEntryName != nullptr); + if (index != (size_t)-1 && newEntryName != nullptr) + this->renameEntry(appContext, index, newEntryName); + } + break; + case BundleReplacement_AddOrModify: + { + const char *newEntryName = pBundleReplacer->GetEntryName(); + assert(newEntryName != nullptr); + if (!newEntryName) + newEntryName = origEntryName; + if (auto* pResourceModifier = dynamic_cast(pBundleReplacer)) + { + //Generate an empty reader, and then add a new child file with the resource replacer. + assert(!pResourceModifier->RequiresEntryReader()); + std::shared_ptr pReader = std::shared_ptr( + Create_AssetsReaderFromMemory(nullptr, 0, false, nullptr), + Free_AssetsReader); + std::shared_ptr pBundleReplacer = std::reinterpret_pointer_cast(pReplacer); + this->modificationsToApply->subFiles.emplace_back(appContext, pBundleReplacer, BundleReplacer_BundleEntryModifierByResources); + if (index != (size_t)-1) + { + this->overrideEntryReader(appContext, index, pReader, pBundleReplacer->HasSerializedData(), std::string(newEntryName)); + } + else + { + this->addEntry(appContext, pReader, pBundleReplacer->HasSerializedData(), std::string(newEntryName)); + } + } + else + { + std::shared_ptr pReader = MakeReaderFromBundleEntryModifier(std::shared_ptr(pReplacer, pBundleReplacer)); + assert(pReader != nullptr); + if (pReader != nullptr) + { + if (index != (size_t)-1) + { + this->overrideEntryReader(appContext, index, pReader, pBundleReplacer->HasSerializedData(), std::string(newEntryName)); + } + else + { + this->addEntry(appContext, pReader, pBundleReplacer->HasSerializedData(), std::string(newEntryName)); + } + } + } + } + break; + } + } + if (this->modificationsToApply) + this->modificationsToApply->replacers.clear(); +} +bool BundleFileContextInfo::renameEntry(class AppContext &appContext, size_t index, std::string newEntryName) +{ + bool ret = false; + std::unique_lock directoryLock(this->directoryMutex); + if (index < this->directoryRefs.size() && (this->directoryRefs[index].fileID != 0 || !this->directoryRefs[index].entryRemoved)) + { + this->directoryRefs[index].newEntryName = newEntryName; + this->directoryRefs[index].entryNameOverridden = true; + this->directoryRefs[index].newChangeFlag = true; + ret = true; + auto pChildInfo = appContext.getContextInfo(this->directoryRefs[index].fileID); + if (pChildInfo) + { + pChildInfo->setFileName(std::move(newEntryName)); + } + } + directoryLock.unlock(); + appContext.OnChangeBundleEntry_Async(this, index); + return ret; +} +bool BundleFileContextInfo::overrideEntryReader(AppContext &appContext, size_t index, std::shared_ptr pReader, bool hasSerializedData) +{ + bool ret = false; + std::unique_lock directoryLock(this->directoryMutex); + if (index < this->directoryRefs.size() && (this->directoryRefs[index].fileID != 0 || !this->directoryRefs[index].entryRemoved)) + { + this->directoryRefs[index].pOverrideReader = std::move(pReader); + this->directoryRefs[index].hasSerializedData = hasSerializedData; + this->directoryRefs[index].newChangeFlag = true; + ret = true; + } + directoryLock.unlock(); + appContext.OnChangeBundleEntry_Async(this, index); + return ret; +} +bool BundleFileContextInfo::removeEntry(class AppContext &appContext, size_t index) +{ + bool ret = false; + std::unique_lock directoryLock(this->directoryMutex); + if (index < this->directoryRefs.size()) + { + if (!this->directoryRefs[index].entryRemoved) + { + this->directoryRefs[index].entryRemoved = true; + this->directoryRefs[index].newChangeFlag = true; + } + ret = true; + } + directoryLock.unlock(); + appContext.OnChangeBundleEntry_Async(this, index); + return ret; +} +size_t BundleFileContextInfo::addEntry(class AppContext &appContext, std::shared_ptr pReader, bool hasSerializedData, std::string entryName) +{ + std::unique_lock directoryLock(this->directoryMutex); + + this->directoryRefs.push_back(BundleFileDirectoryInfo()); + size_t index = this->directoryRefs.size() - 1; + this->directoryRefs[index].pOverrideReader = std::move(pReader); + this->directoryRefs[index].hasSerializedData = hasSerializedData; + this->directoryRefs[index].newEntryName = std::move(entryName); + this->directoryRefs[index].entryNameOverridden = true; + this->directoryRefs[index].newChangeFlag = true; + + directoryLock.unlock(); + appContext.OnChangeBundleEntry_Async(this, index); + return index; +} +//Checks whether an entry has been removed. Can return true even if the child file still is open. +bool BundleFileContextInfo::entryIsRemoved(size_t index) +{ + bool ret = false; + std::shared_lock directoryLock(this->directoryMutex); + if (index < this->directoryRefs.size()) + { + ret = this->directoryRefs[index].entryRemoved; + } + return ret; +} +//Checks whether an entry has changed (renamed, reader overridden, removed, added). Does not check for changes in the child FileContextInfo. +bool BundleFileContextInfo::entryHasChanged(size_t index) +{ + bool ret = false; + std::shared_lock directoryLock(this->directoryMutex); + if (index < this->directoryRefs.size()) + { + if (this->getBundleFileContext() && index >= this->getBundleFileContext()->getEntryCount()) + ret = true; + else if (this->directoryRefs[index].entryNameOverridden || this->directoryRefs[index].entryRemoved + || this->directoryRefs[index].pOverrideReader != nullptr) + ret = true; + } + return ret; +} +size_t BundleFileContextInfo::getEntryCount() +{ + std::shared_lock directoryLock(this->directoryMutex); + size_t ret = this->directoryRefs.size(); + return ret; +} +BundleFileContextInfo::DecompressTask::DecompressTask(AppContext &appContext, std::shared_ptr &pContextInfo, std::string outputPath) + : appContext(appContext), pFileContextInfo(pContextInfo), outputPath(outputPath), + decompressStatus(static_cast(TaskResult_Canceled)), + reopenStatus(static_cast(TaskResult_Canceled)) +{ + assert(pFileContextInfo->getBundleFileContext() && pFileContextInfo->getBundleFileContext()->getBundleFile()); + name = "Decompress bundle : " + pContextInfo->getFileName() + ""; +} +const std::string &BundleFileContextInfo::DecompressTask::getName() +{ + return name; +} +TaskResult BundleFileContextInfo::DecompressTask::execute(TaskProgressManager &progressManager) +{ + if (!this->pFileContextInfo->getBundleFileContext() || !this->pFileContextInfo->getBundleFileContext()->getBundleFile()) + { + progressManager.logMessage("Bundle file not loaded!"); + return -1; + } + progressManager.setProgress(0, 250); + progressManager.setProgressDesc("Decompressing the bundle file."); + //Assuming that this is the first and only time DecompressSync is called. + this->decompressStatus = this->pFileContextInfo->getBundleFileContext()->DecompressSync(nullptr, this->outputPath); + if (this->decompressStatus != BundleFileDecompressStatus_OK) + { + switch (this->decompressStatus) + { + case BundleFileDecompressStatus_ErrOutFileOpen: + progressManager.logMessage("Unable to open the output file for decompression!"); + break; + case BundleFileDecompressStatus_ErrDecompress: + progressManager.logMessage("Bundle file not loaded!"); + break; + default: + progressManager.logMessage("An unknown error occured during decompression!"); + } + progressManager.setProgress(250, 250); + return -3; + } + std::shared_ptr pDecompressedReader = std::shared_ptr( + Create_AssetsReaderFromFile(this->outputPath.c_str(), true, RWOpenFlags_Immediately), + Free_AssetsReader); + if (pDecompressedReader == nullptr) + { + progressManager.logMessage("Cannot reopen the decompressed file!"); + return -2; + } + progressManager.setProgress(150, 250); + progressManager.setProgressDesc("Reopening the bundle file."); + + IFileContext *pParentFileContext = this->pFileContextInfo->getBundleFileContext()->getParent(); + BundleFileContext *pNewBundleContext; + if (pParentFileContext) + pNewBundleContext = new BundleFileContext(this->pFileContextInfo->getBundleFileContext()->getFilePath(), pParentFileContext, pDecompressedReader); + else + pNewBundleContext = new BundleFileContext(this->pFileContextInfo->getBundleFileContext()->getFilePath(), pDecompressedReader); + + this->reopenStatus = pNewBundleContext->OpenInsideTask(&progressManager, 150, 250); + progressManager.setProgress(250, 250); + if (this->reopenStatus >= 0 && this->reopenStatus != BundleFileOpenStatus_Pend) + { + if (this->reopenStatus == BundleFileOpenStatus_CompressedDirectory || + this->reopenStatus == BundleFileOpenStatus_CompressedData) + { + progressManager.logMessage("The decompressed file still appears to be compressed!"); + } + else + { + this->pFileContextInfo->CloseContext(); + this->pFileContextInfo->pContext = pNewBundleContext; + this->pFileContextInfo->isDecompressed = true; + this->pFileContextInfo->onDirectoryReady(appContext); + return 0; + } + } + delete pNewBundleContext; + + return -1; +} +std::shared_ptr BundleFileContextInfo::EnqueueDecompressTask( + AppContext &appContext, std::shared_ptr &selfPointer, std::string outputPath) +{ + assert(selfPointer.get() == this); + std::shared_ptr pTask = std::make_shared(appContext, selfPointer, outputPath); + if (appContext.taskManager.enqueue(pTask)) + return pTask; + return nullptr; +} + +#pragma region ResourcesFileContextInfo +ResourcesFileContextInfo::ResourcesFileContextInfo(ResourcesFileContext *pContext, unsigned int fileID, unsigned int parentFileID) + : FileContextInfo(fileID, parentFileID), pContext(pContext), changedFlag(false) +{ + IAssetsReader* pReader = this->pContext->getReaderUnsafe(); + if (pReader == nullptr) + throw std::invalid_argument("ResourcesFileContextInfo: Context has a null reader!"); + IAssetsReader* pReaderView = pReader->CreateView(); + if (pReaderView == nullptr) + throw std::runtime_error("ResourcesFileContextInfo: Unable to create a reader view!"); + pReaderView->Seek(AssetsSeek_End, 0); + originalFileSize = 0; + pReaderView->Tell(originalFileSize); + Free_AssetsReader(pReaderView); + + ReplacedResourceDesc placeholderDesc = {}; + placeholderDesc.outRangeBegin = 0; + placeholderDesc.rangeSize = originalFileSize; + placeholderDesc.reader = nullptr; + placeholderDesc.inRangeBegin = 0; + placeholderDesc.fromOriginalFile = true; + resources.push_front(placeholderDesc); +} +ResourcesFileContextInfo::~ResourcesFileContextInfo() +{ + IAssetsReader *pReader = this->pContext->getReaderUnsafe(); + this->pContext->Close(); + delete this->pContext; + this->pContext = nullptr; +} +IFileContext *ResourcesFileContextInfo::getFileContext() +{ + return this->pContext; +} +void ResourcesFileContextInfo::getChildFileIDs(std::vector &childFileIDs) +{ + childFileIDs.clear(); +} +void ResourcesFileContextInfo::onCloseChild(unsigned int childFileID) +{ +} +bool ResourcesFileContextInfo::hasNewChanges(class AppContext &appContext) +{ + return this->changedFlag; +} +bool ResourcesFileContextInfo::hasAnyChanges(class AppContext &appContext) +{ + if (this->getResourcesFileContext() != nullptr && this->getResourcesFileContext()->getReaderIsModified()) + return true; + std::shared_lock resourcesLock(this->resourcesMutex); + uint64_t curOutRange = 0; + for (auto resourcesIt = this->resources.begin(); resourcesIt != this->resources.end(); ++resourcesIt) + { + if (resourcesIt->rangeSize > 0) + { + if (!resourcesIt->fromOriginalFile || resourcesIt->reader != nullptr) + return true; + if (resourcesIt->inRangeBegin != resourcesIt->outRangeBegin) + return true; + } + if (resourcesIt->outRangeBegin != curOutRange) + { + assert(false); + return true; + } + curOutRange += resourcesIt->rangeSize; + } + if (curOutRange != originalFileSize) + return true; + return false; +} +uint64_t ResourcesFileContextInfo::write(class AppContext &appContext, IAssetsWriter *pWriter, uint64_t start, bool resetChangedFlag) +{ + assert(this->pContext != nullptr); + if (this->pContext == nullptr) + return 0; + IAssetsReader* pOrigReader = this->pContext->getReaderUnsafe()->CreateView(); + std::vector buffer(1024 * 1024); //1MiB + + std::shared_lock resourcesLock(this->resourcesMutex); + uint64_t curOutPos = 0; + bool errorsOccured = false; + for (auto resourcesIt = this->resources.begin(); resourcesIt != this->resources.end(); ++resourcesIt) + { + assert(resourcesIt->outRangeBegin == curOutPos); + IAssetsReader* pCurReader = resourcesIt->reader.get(); + if (pCurReader == nullptr && resourcesIt->fromOriginalFile) + pCurReader = this->pContext->getReaderUnsafe(); + if (pCurReader == nullptr) + { + //Fill buffer with zeroes. + size_t bufSize = buffer.size(); + buffer.clear(); + buffer.resize(bufSize, 0); + } + else + { + pCurReader = pCurReader->CreateView(); + if (pCurReader == nullptr) + { + assert(false); + errorsOccured = true; + break; + } + pCurReader->SetPosition(resourcesIt->inRangeBegin); + } + for (uint64_t i = 0; i < resourcesIt->rangeSize; ) + { + uint64_t curRead = 0; + //Read a block of data. + if (pCurReader != nullptr) + curRead = pCurReader->Read(std::min(buffer.size(), resourcesIt->rangeSize - i), buffer.data()); + else + curRead = std::min(buffer.size(), resourcesIt->rangeSize - i); //Zeroes already in the buffer. + if (curRead == 0) + { + assert(false); + errorsOccured = true; + break; + } + //Write the data + uint64_t curWritten = pWriter->Write(start + curOutPos, curRead, buffer.data()); + curOutPos += curWritten; + if (curWritten != curRead) + { + assert(false); + errorsOccured = true; + break; + } + i += curWritten; + } + if (pCurReader != nullptr) + Free_AssetsReader(pCurReader); + } + if (!errorsOccured && resetChangedFlag) + this->changedFlag = false; + return curOutPos; +} +std::unique_ptr ResourcesFileContextInfo::makeBundleReplacer(class AppContext &appContext, + const char *oldName, const char *newName, uint32_t bundleIndex, + bool resetChangedFlag) +{ + assert(this->pContext != nullptr); + if (this->pContext == nullptr) + return nullptr; + std::shared_lock resourcesLock(this->resourcesMutex); + if (resetChangedFlag) + this->changedFlag = false; + return MakeBundleEntryModifierByResources(oldName, newName, + std::vector(this->resources.begin(), this->resources.end()), 0, bundleIndex); +} +bool ResourcesFileContextInfo::setByReplacer(class AppContext& appContext, BundleReplacer* pReplacer) +{ + if (BundleEntryModifierByResources* pBundleModifier = dynamic_cast(pReplacer)) + { + std::shared_lock resourcesLock(this->resourcesMutex); + resources.clear(); + const auto& resourcesIn = pBundleModifier->getResources(); + resources.assign(resourcesIn.begin(), resourcesIn.end()); + this->changedFlag = hasAnyChanges(appContext); + return true; + } + return false; +} +void ResourcesFileContextInfo::addResource(std::shared_ptr pReader, uint64_t readerOffs, uint64_t size, uint64_t &resourcesFilePos) +{ + std::unique_lock resourcesLock(this->resourcesMutex); + resourcesFilePos = 0; + if (!resources.empty()) + resourcesFilePos = resources.back().outRangeBegin + resources.back().rangeSize; + resources.emplace_back(); + resources.back().outRangeBegin = resourcesFilePos; + resources.back().rangeSize = size; + resources.back().reader = pReader; + resources.back().inRangeBegin = readerOffs; + resources.back().fromOriginalFile = false; + this->changedFlag = (size > 0); +} +std::shared_ptr ResourcesFileContextInfo::getResource(std::shared_ptr selfRef, uint64_t offs, uint64_t size) +{ + if (selfRef.get() != this) + throw std::invalid_argument("ResourcesFileContextInfo::getResource: selfRef does not point to this!"); + std::shared_lock resourcesLock(this->resourcesMutex); + //TODO: Implement a faster lookup by file position, e.g. using a map (probably not necessary for now). + + //Find the first resource in range (returns resources.end() if none are found). + auto itFirstInRange = std::find_if(resources.begin(), resources.end(), + [offs](const ReplacedResourceDesc& a) {return a.outRangeBegin + a.rangeSize >= offs; }); + //Find the last resource in range. + uint64_t rangeEnd = (size < std::numeric_limits::max()-offs) ? (offs + size) : std::numeric_limits::max(); + auto itFirstOutOfRange = std::find_if(itFirstInRange, resources.end(), + [rangeEnd](const ReplacedResourceDesc& a) {return a.outRangeBegin >= rangeEnd; }); + + if (itFirstInRange == resources.end() || itFirstInRange == itFirstOutOfRange) + return nullptr; + class ResourcesReader : public IAssetsReader + { + std::vector resources; + std::shared_ptr origReader; + + std::unique_ptr origReaderView; + std::vector> readerViews; + + std::recursive_mutex positionMutex; + QWORD pos = 0; + size_t resourceIdx = 0; + + void createViews() + { + origReaderView.reset(); + readerViews.clear(); + if (origReader) + { + origReaderView.reset(origReader->CreateView()); + if (origReaderView == nullptr) + throw std::runtime_error("Unable to open a reader view!"); + } + readerViews.resize(resources.size()); + for (size_t i = 0; i < resources.size(); ++i) + { + if (resources[i].reader != nullptr) + { + readerViews[i].reset(resources[i].reader->CreateView()); + if (readerViews[i] == nullptr) + throw std::runtime_error("Unable to open a reader view!"); + } + } + } + inline QWORD getSize() + { + return resources.empty() ? 0 : (resources.back().outRangeBegin + resources.back().rangeSize); + } + inline IAssetsReader* getReaderViewFor(size_t resourceIdx) + { + IAssetsReader* pReader = nullptr; + if (resources[resourceIdx].reader) + { + //Customized resource. + pReader = readerViews[resourceIdx].get(); + if (pReader == nullptr) + { + assert(false); + return nullptr; + } + } + else if (resources[resourceIdx].fromOriginalFile) + { + //Resource from the original file. + pReader = origReaderView.get(); + if (pReader == nullptr) + { + assert(false); + return nullptr; + } + } + return pReader; + } + public: + ResourcesReader(std::vector _resources, std::shared_ptr _origReader) + : resources(std::move(_resources)), origReader(std::move(_origReader)) + { + createViews(); + } + ResourcesReader(const ResourcesReader& other) + { + (*this) = other; + } + ResourcesReader& operator=(const ResourcesReader& other) + { + this->resources = other.resources; + this->origReader = other.origReader; + this->createViews(); + return *this; + } + + bool Reopen() + { + std::lock_guard posLock(positionMutex); + if (resourceIdx < resources.size()) + { + if (readerViews[resourceIdx] != nullptr) + return readerViews[resourceIdx]->Reopen(); + else if (resources[resourceIdx].fromOriginalFile) + return origReaderView->Reopen(); + } + return true; + } + bool IsOpen() + { + std::lock_guard posLock(positionMutex); + if (resourceIdx < resources.size()) + { + if (readerViews[resourceIdx] != nullptr) + return readerViews[resourceIdx]->IsOpen(); + else if (resources[resourceIdx].fromOriginalFile) + return origReaderView->IsOpen(); + } + return true; + } + bool Close() { return false; } + + AssetsRWTypes GetType() { return AssetsRWType_Reader; } + AssetsRWClasses GetClass() { return AssetsRWClass_Unknown; } + bool IsView() { return true; } + + bool Tell(QWORD& pos) + { + std::lock_guard posLock(positionMutex); + pos = this->pos; + return true; + } + bool Seek(AssetsSeekTypes origin, long long offset) + { + std::lock_guard posLock(positionMutex); + bool ret = false; + QWORD newPos = this->pos; + switch (origin) + { + case AssetsSeek_Begin: + if (offset < 0) return false; + newPos = offset; + break; + case AssetsSeek_Cur: + if (offset < 0) + { + offset = -offset; + if (offset < 0) return false; //INT64_MIN + if ((unsigned long long)offset > newPos) return false; + newPos -= (unsigned long long)offset; + } + else + newPos += (unsigned long long)offset; + break; + case AssetsSeek_End: + if (offset > 0) return false; + offset = -offset; + if (offset < 0) return false; //INT64_MIN + if ((unsigned long long)offset > getSize()) + return false; + newPos = resources.empty() + ? 0 + : (resources.back().outRangeBegin + resources.back().rangeSize) - (unsigned long long)offset; + break; + } + return SetPosition(newPos); + } + bool SetPosition(QWORD pos) + { + std::lock_guard posLock(positionMutex); + if (pos > getSize()) + return false; + if (pos == getSize()) + { + this->pos = pos; + this->resourceIdx = resources.size(); + return true; + } + //Get the first resource with outRangeBegin + rangeSize > pos. + //-> Since resources is guaranteed to be sorted by out position and without overlapping regions (and start with 0), + // this should always return the resource corresponding to pos, or resources.end() if out of range. + auto itMatchingResource = std::upper_bound(resources.begin(), resources.end(), pos, + [](QWORD pos, const ReplacedResourceDesc& resource) + { + return pos < (resource.outRangeBegin + resource.rangeSize); + }); + if (itMatchingResource == resources.end()) + return false; + if (itMatchingResource->outRangeBegin > pos) + { + assert(false); //Should not happen due to the requirements for resources. + return false; + } + this->pos = pos; + this->resourceIdx = std::distance(resources.begin(), itMatchingResource); + IAssetsReader* pReader = getReaderViewFor(resourceIdx); + if (pReader != nullptr) + return pReader->SetPosition(itMatchingResource->inRangeBegin + (pos - itMatchingResource->outRangeBegin)); + return true; + } + + QWORD Read(QWORD pos, QWORD size, void* outBuffer, bool nullUnread = true) + { + std::lock_guard posLock(positionMutex); + if (pos == (QWORD)-1) + pos = this->pos; + QWORD numRead = 0; + if ((pos != (QWORD)-1) && !SetPosition(pos)) + numRead = 0; + else + { + QWORD remaining = size; + while (remaining > 0) + { + if (resourceIdx >= resources.size()) + break; + ReplacedResourceDesc& curResource = resources[resourceIdx]; + if (pos >= curResource.outRangeBegin + curResource.rangeSize) + { + resourceIdx++; + continue; + } + assert(pos >= curResource.outRangeBegin); + QWORD bytesToRead = std::min(remaining, curResource.rangeSize - (pos - curResource.outRangeBegin)); + bytesToRead = std::min(bytesToRead, std::numeric_limits::max()); + QWORD curBytesRead = 0; + IAssetsReader* pReader = getReaderViewFor(resourceIdx); + if (pReader != nullptr) + { + curBytesRead = pReader->Read( + pos - curResource.outRangeBegin + curResource.inRangeBegin, + bytesToRead, + &((uint8_t*)outBuffer)[numRead], + false); + } + else + { + //Pseudo-resource filled with zeroes. + memset(&((uint8_t*)outBuffer)[numRead], 0, (size_t)bytesToRead); + curBytesRead = bytesToRead; + } + if (curBytesRead == 0) + break; + numRead += curBytesRead; + remaining -= curBytesRead; + } + } + if (nullUnread && (numRead < size)) + memset(&((uint8_t*)outBuffer)[numRead], 0, size - numRead); + this->pos = pos + numRead; + return numRead; + } + IAssetsReader *CreateView() + { + return new ResourcesReader(*this); + } + }; + std::vector resourcesInRange(itFirstInRange, itFirstOutOfRange); + if (resourcesInRange.size() > 0) + { + //Correct the offsets and resource size of the first entry to match the chosen range start. + resourcesInRange.front().inRangeBegin += (offs - resourcesInRange.front().outRangeBegin); + resourcesInRange.front().rangeSize -= (offs - resourcesInRange.front().outRangeBegin); + resourcesInRange.front().outRangeBegin = offs; + } + if (resourcesInRange.size() > 0) + { + //Correct the resource size of the last entry to match the chosen range end position. + if ((resourcesInRange.back().outRangeBegin - offs) + resourcesInRange.back().rangeSize > size) + resourcesInRange.back().rangeSize = size - (resourcesInRange.back().outRangeBegin - offs); + } + //Subtract the start offset from each entry to match the reader positions. + for (ReplacedResourceDesc& resourceInRange : resourcesInRange) + resourceInRange.outRangeBegin -= offs; + return std::make_shared(std::move(resourcesInRange), + std::shared_ptr(selfRef, this->pContext->getReaderUnsafe())); +} +#pragma endregion ResourcesFileContextInfo + + + +#pragma region GenericFileContextInfo +GenericFileContextInfo::GenericFileContextInfo(GenericFileContext *pContext, unsigned int fileID, unsigned int parentFileID) + : FileContextInfo(fileID, parentFileID), pContext(pContext), changedFlag(false) +{ +} +GenericFileContextInfo::~GenericFileContextInfo() +{ + IAssetsReader *pReader = this->pContext->getReaderUnsafe(); + this->pContext->Close(); + delete this->pContext; + this->pContext = nullptr; +} +IFileContext *GenericFileContextInfo::getFileContext() +{ + return this->pContext; +} +void GenericFileContextInfo::getChildFileIDs(std::vector &childFileIDs) +{ + childFileIDs.clear(); +} +void GenericFileContextInfo::onCloseChild(unsigned int childFileID) +{ +} +bool GenericFileContextInfo::hasAnyChanges(class AppContext &appContext) +{ + if (this->getGenericFileContext() != nullptr && this->getGenericFileContext()->getReaderIsModified()) + return true; + std::scoped_lock replacementReaderLock(this->replacementReaderMutex); + bool ret = (!this->replacementReaderHistory.empty()); + return ret; +} +bool GenericFileContextInfo::hasNewChanges(class AppContext &appContext) +{ + return changedFlag; +} +uint64_t GenericFileContextInfo::write(class AppContext &appContext, IAssetsWriter *pWriter, uint64_t start, bool resetChangedFlag) +{ + assert(this->pContext != nullptr); + if (this->pContext == nullptr) + return 0; + std::unique_lock replacementReaderLock(this->replacementReaderMutex); + IAssetsReader *pReader; + if (!this->replacementReaderHistory.empty()) + pReader = this->replacementReaderHistory.back()->CreateView(); + else + pReader = this->pContext->getReaderUnsafe()->CreateView(); + replacementReaderLock.unlock(); + uint64_t fileSize = 0; + if (!pReader->Seek(AssetsSeek_End, 0) || !pReader->Tell(fileSize) || !pReader->Seek(AssetsSeek_Begin, 0)) + { + Free_AssetsReader(pReader); + return 0; + } + std::vector buffer(1024 * 1024); //1MiB + for (uint64_t i = 0; i < fileSize; ) + { + uint64_t curRead = pReader->Read(std::min(buffer.size(), fileSize - i), buffer.data()); + if (curRead == 0) + { + assert(false); + fileSize = i; + break; + } + uint64_t curWritten = pWriter->Write(start + i, curRead, buffer.data()); + if (curWritten < curRead) + { + assert(false); + fileSize = curWritten; + break; + } + } + Free_AssetsReader(pReader); + if (resetChangedFlag) + changedFlag = false; + return fileSize; +} +std::unique_ptr GenericFileContextInfo::makeBundleReplacer(class AppContext &appContext, + const char *oldName, const char *newName, uint32_t bundleIndex, + bool resetChangedFlag) +{ + assert(this->pContext != nullptr); + if (this->pContext == nullptr) + return nullptr; + + std::unique_lock replacementReaderLock(this->replacementReaderMutex); + IAssetsReader *pReader = nullptr; + if (!this->replacementReaderHistory.empty()) + pReader = this->replacementReaderHistory.back()->CreateView(); + else + pReader = this->pContext->getReaderUnsafe()->CreateView(); + replacementReaderLock.unlock(); + + if (pReader == nullptr) + return nullptr; + assert(pReader->Seek(AssetsSeek_End, 0)); + QWORD totalSize = 0; + assert(pReader->Tell(totalSize)); + assert(pReader->Seek(AssetsSeek_Begin, 0)); + if (resetChangedFlag) + changedFlag = false; + return std::unique_ptr(MakeBundleEntryModifier(oldName, newName, false, pReader, Free_AssetsReader, totalSize, 0, 0, bundleIndex)); +} +#pragma endregion GenericFileContextInfo diff --git a/UABE_Generic/FileContextInfo.h b/UABE_Generic/FileContextInfo.h new file mode 100644 index 0000000..836cea0 --- /dev/null +++ b/UABE_Generic/FileContextInfo.h @@ -0,0 +1,498 @@ +#pragma once +#include "api.h" +#include "FileContext.h" +#include "AssetContainerList.h" +#include "FileModTree.h" +#include "../AssetsTools/AssetsReplacer.h" +#include "../AssetsTools/BundleReplacer.h" +#include +#include +#include +#include +#include +#include + +//unique_ptr to a IAssetsReader with a deleter. +typedef std::unique_ptr IAssetsReader_ptr; +static void DummyAssetsReaderDeleter(IAssetsReader*){} + +typedef std::shared_ptr ClassDatabaseFile_sharedptr; +static void ClassDatabaseFileDeleter_Dummy(ClassDatabaseFile*) {} +static void ClassDatabaseFileDeleter_delete(ClassDatabaseFile *pFile) +{ + delete pFile; +} + +typedef uint64_t pathid_t; + +//Wrapper class around FileContext to track dynamically changeable information +// and to provide additional meta functionality. +class FileContextInfo +{ + unsigned int fileID; + unsigned int parentFileID; //0 : has no parent + std::string lastFileName; //Used only for internal management in AppContext (friend class). + std::mutex fileNameOverrideMutex; + std::string fileNameOverride; +public: + UABE_Generic_API FileContextInfo(unsigned int fileID, unsigned int parentFileID); + UABE_Generic_API virtual ~FileContextInfo(); + UABE_Generic_API virtual IFileContext *getFileContext()=0; + UABE_Generic_API virtual void getChildFileIDs(std::vector &childFileIDs)=0; + UABE_Generic_API virtual void onCloseChild(unsigned int childFileID)=0; + UABE_Generic_API virtual bool hasNewChanges(class AppContext &appContext)=0; + UABE_Generic_API virtual bool hasAnyChanges(class AppContext &appContext)=0; + UABE_Generic_API virtual uint64_t write(class AppContext &appContext, IAssetsWriter *pWriter, uint64_t start, bool resetChangedFlag)=0; + UABE_Generic_API virtual std::unique_ptr makeBundleReplacer(class AppContext &appContext, const char *oldName, const char *newName, uint32_t bundleIndex, bool resetChangedFlag)=0; + inline unsigned int getFileID() { return fileID; } + inline unsigned int getParentFileID() { return parentFileID; } + UABE_Generic_API std::string getFileName(); + UABE_Generic_API void setFileName(std::string name); + + friend class AppContext; +}; +typedef std::shared_ptr FileContextInfo_ptr; + +class AssetsFileContextInfo : public FileContextInfo +{ + //Locking order (to prevent deadlocks): + //1) referencesLock + //2) containersLock + //2) replacersLock + //3) classDatabaseLock + //4) scriptDatabasesMutex + + AssetsFileContext *pContext; + + std::shared_mutex containersMutex; + AssetContainerList containers; + //After locking&unlocking containers for write, the AppContext has to be notified about a containers update. + // Since the UI thread may not wait for the read lock, any kind of lock needs to be followed by such a notification. + inline void lockContainersWrite() + { + containersMutex.lock(); + } + inline void unlockContainersWrite() + { + containersMutex.unlock(); + } + std::shared_mutex referencesMutex; + std::vector references; //File IDs this has references to, in the same order as the .assets file dependency list. May contain 0 entries as fillers. + std::vector dependencies; //Overrides the .assets file dependency list. Locked by referencesMutex. + bool dependenciesChanged = false; + std::vector containerSources; //File IDs that have a container list and references to this file. + std::shared_mutex classDatabaseMutex; //Lock for atomic writes of pClassDatabase. Lock replacersLock first if both are to be locked. + ClassDatabaseFile_sharedptr pClassDatabase; //Current main class database. + std::mutex scriptDatabasesMutex; + std::vector> pScriptDatabases; + std::shared_mutex replacersMutex; + bool changedFlag = false; + bool permanentChangedFlag = false; + //shared_ptr adds overhead, e.g. while copying and freeing. + // Since new AssetsEntryReplacers are only added and removed by user interaction (and in small amounts), this should not impose a problem. + struct ReplacerEntry + { + bool replacesExistingAsset; //Set to true if the replacer's path ID matches the path ID of an asset existing in the underlying file. + std::shared_ptr pReplacer; + }; + std::unordered_map pReplacersByPathID; + inline void lockReplacersRead() + { + replacersMutex.lock_shared(); + } + inline void unlockReplacersRead() + { + replacersMutex.unlock_shared(); + } + //Lock before classDatabaseLock first if both are to be locked. + inline void lockReplacersWrite() + { + replacersMutex.lock(); + } + inline void unlockReplacersWrite() + { + replacersMutex.unlock(); + } + + bool _GetMonoBehaviourScriptInfo(class AssetIdentifier &asset, class AppContext &appContext, + std::string &fullClassName, std::string &assemblyName, + std::string &className, std::string &namespaceName, + class AssetIdentifier &scriptAsset); + + //TODO: Replace this with a shared reader lock at some point. + UABE_Generic_API std::vector> &lockScriptDatabases(); + UABE_Generic_API void unlockScriptDatabases(); + + //Adds a replacer for an asset. Removes any previous replacers for that path ID. + //If reuseTypeMetaFromOldReplacer is set and the replacer is not a remover, + // type information from any previous replacer is transferred to the new one. + UABE_Generic_API void addReplacer(std::shared_ptr replacer, class AppContext &appContext, bool reuseTypeMetaFromOldReplacer, bool signalMainThread); + +public: + UABE_Generic_API AssetsFileContextInfo(AssetsFileContext *pContext, unsigned int fileID, unsigned int parentFileID); + ~AssetsFileContextInfo(); + IFileContext *getFileContext(); + void getChildFileIDs(std::vector &childFileIDs); + void onCloseChild(unsigned int childFileID); + bool hasNewChanges(class AppContext &appContext); + bool hasAnyChanges(class AppContext &appContext); + uint64_t write(class AppContext &appContext, IAssetsWriter *pWriter, uint64_t start, bool resetChangedFlag); + std::unique_ptr makeBundleReplacer(class AppContext &appContext, const char *oldName, const char *newName, uint32_t bundleIndex, bool resetChangedFlag); + inline AssetsFileContext *getAssetsFileContext() { return pContext; } + + inline std::unique_lock lockReferencesWrite() + { + return std::unique_lock(referencesMutex); + } + inline std::vector& getReferencesWrite(const std::unique_lock& lock) + { +#ifdef _DEBUG + if (!lock.owns_lock() || lock.mutex() != &referencesMutex) + throw std::invalid_argument("AssetsFileContextInfo::getReferencesWrite: Unexpected lock"); +#endif + return references; + } + //Important: When adding or removing elements, + // the references vector must also be changed respectively! + inline std::vector& getDependenciesWrite(const std::unique_lock& lock) + { +#ifdef _DEBUG + if (!lock.owns_lock() || lock.mutex() != &referencesMutex) + throw std::invalid_argument("AssetsFileContextInfo::getDependenciesWrite: Unexpected lock"); +#endif + return dependencies; + } + inline std::shared_lock lockReferencesRead() + { + return std::shared_lock(referencesMutex); + } + inline const std::vector& getReferencesRead(const std::shared_lock& lock) + { +#ifdef _DEBUG + if (!lock.owns_lock() || lock.mutex() != &referencesMutex) + throw std::invalid_argument("AssetsFileContextInfo::getReferencesRead: Unexpected lock"); +#endif + return references; + } + inline const std::vector& getDependenciesRead(const std::shared_lock& lock) + { +#ifdef _DEBUG + if (!lock.owns_lock() || lock.mutex() != &referencesMutex) + throw std::invalid_argument("AssetsFileContextInfo::getDependenciesRead: Unexpected lock"); +#endif + return dependencies; + } + inline const std::vector getReferences() + { + auto lock = lockReferencesRead(); + const std::vector ret = getReferencesRead(lock); + return ret; + } + + //Only call from the UI thread. + inline std::vector &getContainerSources() + { + return containerSources; + } + + inline void setDependenciesChanged() + { + dependenciesChanged = true; + changedFlag = true; + } + //Set if the underlying AssetsFile has changed. + inline void setPermanentChangedFlag() + { + permanentChangedFlag = true; + changedFlag = true; + } + + inline AssetContainerList *tryLockContainersRead() + { + if (containersMutex.try_lock_shared()) + return &containers; + return nullptr; + } + inline AssetContainerList &lockContainersRead() + { + containersMutex.lock_shared(); + return containers; + } + inline void unlockContainersRead() + { + containersMutex.unlock_shared(); + } + + inline bool getEndianness(bool &isBigEndian) + { + isBigEndian = false; + if (pContext && pContext->getAssetsFile()) + { + isBigEndian = (pContext->getAssetsFile()->header.endianness == 1); + return true; + } + return false; + } + + inline unsigned int resolveRelativeFileID(unsigned int relFileID) + { + if (relFileID == 0) + return getFileID(); + auto lock = lockReferencesRead(); + const decltype(this->references) &references = getReferencesRead(lock); + unsigned int ret = (references.size() < relFileID) ? 0 : references[relFileID-1]; + return ret; + } + + //Guaranteed to return only up to one AssetsEntryReplacer per pathID. + UABE_Generic_API std::vector> getAllReplacers(); + UABE_Generic_API std::shared_ptr getReplacer(pathid_t pathID); + //Adds a replacer for an asset. Removes any previous replacers for that path ID. + //If reuseTypeMetaFromOldReplacer is set and the replacer is not a remover, + // type information from any previous replacer is transferred to the new one. + inline void addReplacer(std::shared_ptr replacer, class AppContext &appContext, bool reuseTypeMetaFromOldReplacer = true) + { + addReplacer(std::move(replacer), appContext, reuseTypeMetaFromOldReplacer, true); + } + + //Retrieves the classID for the requested type, or -1 if it wasn't found. + UABE_Generic_API int32_t GetClassByName(const char *name); + //Returns the name of the requested type, or an empty string if it wasn't found. Note that the name is not guaranteed to work in a GetClassByName call. + // The weird underscore is to avoid conflicts with the Win32 GetClassName TCHAR macro. + UABE_Generic_API std::string GetClassName_(class AppContext &appContext, int32_t classID, uint16_t scriptIndex = 0xFFFF, class AssetIdentifier *pAsset = nullptr); + UABE_Generic_API bool MakeTemplateField(AssetTypeTemplateField* pTemplateBase, class AppContext& appContext, int32_t classID, uint16_t scriptIndex = 0xFFFF, class AssetIdentifier* pAsset = nullptr, + std::optional> missingScriptTypeInfo = {}); + UABE_Generic_API bool FindScriptClassDatabaseEntry(ClassDatabaseFile *&pClassFile, ClassDatabaseType *&pClassType, class AssetIdentifier &asset, class AppContext &appContext, Hash128 *pScriptID = nullptr); + + //Tries to find a proper class database from the package. + // Returns true if a proper ClassDatabaseFile was found and/or the full type information is embedded in the .assets file. + UABE_Generic_API bool FindClassDatabase(ClassDatabasePackage &package); + //Sets the class database. + UABE_Generic_API void SetClassDatabase(ClassDatabaseFile_sharedptr pClassDatabase); + //Gets the class database (may return an empty pointer). + UABE_Generic_API ClassDatabaseFile_sharedptr GetClassDatabase(); + + inline void appendScriptDatabase(std::shared_ptr pDatabase) + { + auto &pScriptDatabases = lockScriptDatabases(); + pScriptDatabases.push_back(std::move(pDatabase)); + unlockScriptDatabases(); + } + inline bool hasAnyScriptDatabases() + { + auto &pScriptDatabases = lockScriptDatabases(); + bool ret = !pScriptDatabases.empty(); + unlockScriptDatabases(); + return ret; + } + +public: + class ContainersTask : public ITask + { + std::shared_ptr pFileContextInfo; //Maintain object lifetime. + + std::string name; + class AppContext &appContext; + public: + ContainersTask(class AppContext &appContext, std::shared_ptr &pContextInfo); + const std::string &getName(); + TaskResult execute(TaskProgressManager &progressManager); + inline AssetsFileContextInfo *getFileContextInfo() const + { + return pFileContextInfo.get(); + } + }; + //Must be called from the main app thread. + UABE_Generic_API bool EnqueueContainersTask(class AppContext &appContext, std::shared_ptr selfPointer); + + friend class AssetIterator; + friend class AppContext; +}; + +struct BundleFileDirectoryInfo +{ + //File ID to find the child *FileContextInfo in the AppContext. + //0: not loaded + unsigned int fileID; + //Override reader to use when loading the asset. + //-> User can import files outside the bundle; + // instead of searching for the entry in the original bundle, + // this reader will be used instead (if non-nullptr). + //-> Note: If the child file still is loaded (fileID != 0 and valid), + // it may be useful to initiate a reload after setting this field; + // otherwise, the sub file will still use the previous reader, + // and the pOverrideReader change will be ignored when saving. + //-> Ignored if entryRemoved is set to true. + std::shared_ptr pOverrideReader; + + //Set if the file type readable in pOverrideReader is an .assets file. + //-> Ignored if fileID != 0 or if entryRemoved is set to true. + bool hasSerializedData; + + //Set if the entry should be considered removed. + //-> Ignored if the child file is still loaded (fileID != 0). + bool entryRemoved; + + //Set if a change in pOverrideReader or entryRemoved is considered new (unsaved). + //-> Ignored if the child file is still loaded (fileID != 0). + bool newChangeFlag; + + bool entryNameOverridden; + std::string newEntryName; + + inline BundleFileDirectoryInfo() + : fileID(0), hasSerializedData(false), entryRemoved(false), newChangeFlag(false), entryNameOverridden(false) + {} +}; +class BundleFileContextInfo : public FileContextInfo +{ + BundleFileContext *pContext; + + std::shared_mutex directoryMutex; + //File IDs and other info for entries of this bundle, in the same order as the directory list. + //-> Entries of this vector are never removed so as to preserve indices. + //Changes to the vector or its data needs to be protected by an exclusive lock on directoryLock. + //Reads need to be protected by a shared (or exclusive) lock on directoryLock. + std::vector directoryRefs; + + std::unique_ptr modificationsToApply; //May contain changes to apply when opening sub file contexts. + bool isDecompressed; + void CloseContext(); + //Assumes that pContext != nullptr. + //Takes its own exclusive lock on directoryRefs. + std::vector> makeEntryReplacers(class AppContext &appContext, bool resetChangedFlag); + UABE_Generic_API std::string getNewEntryName(size_t index, bool acquireLock); +public: + BundleFileContextInfo(BundleFileContext *pContext, unsigned int fileID, unsigned int parentFileID); + ~BundleFileContextInfo(); + IFileContext *getFileContext(); + void getChildFileIDs(std::vector &childFileIDs); + void onCloseChild(unsigned int childFileID); + bool hasNewChanges(class AppContext &appContext); + bool hasAnyChanges(class AppContext &appContext); + uint64_t write(class AppContext &appContext, IAssetsWriter *pWriter, uint64_t start, bool resetChangedFlag); + std::unique_ptr makeBundleReplacer(class AppContext &appContext, const char *oldName, const char *newName, uint32_t bundleIndex, bool resetChangedFlag); + inline BundleFileContext *getBundleFileContext() { return pContext; } + //Retrieves the bundle name portion in "archive:/"-style paths, e.g. "BuildPlayer-something". + //Returns an empty string if no such name could be determined. + UABE_Generic_API std::string getBundlePathName(); + inline std::string getNewEntryName(size_t index) + { + return getNewEntryName(index, true); + } + //Returns nullptr on error (e.g. index out of range, I/O error, ...), or if the entry has been deleted. + UABE_Generic_API std::shared_ptr makeEntryReader(size_t index, bool &readerIsModified); + + //Called once the bundle directory is deserialized. + UABE_Generic_API void onDirectoryReady(class AppContext &appContext); + + //Sets the override name for a bundle entry. Returns true on success. + UABE_Generic_API bool renameEntry(class AppContext &appContext, size_t index, std::string newEntryName); + //Sets the override reader for a bundle entry. Returns true on success. + UABE_Generic_API bool overrideEntryReader(class AppContext &appContext, size_t index, std::shared_ptr pReader, bool hasSerializedData); + //Sets the override reader and override name for a bundle entry. Returns true on success (returns false on partial success, i.e. reader but not name changed). + inline bool overrideEntryReader(class AppContext &appContext, size_t index, std::shared_ptr pReader, bool hasSerializedData, std::string newEntryName) + { + return overrideEntryReader(appContext, index, std::move(pReader), hasSerializedData) + && renameEntry(appContext, index, std::move(newEntryName)); + } + //Sets the remove flag of a bundle entry. Note: The effect only takes place once the bundle entry has closed. Returns true on success. + //Note: Does not affect any of the succeeding entry indices. + UABE_Generic_API bool removeEntry(class AppContext &appContext, size_t index); + //Adds a new entry and returns its index. Returns (size_t)-1 on failure. + UABE_Generic_API size_t addEntry(class AppContext &appContext, std::shared_ptr pReader, bool hasSerializedData, std::string entryName); + + //Checks whether an entry has been removed. Can return true even if the child file still is open. + UABE_Generic_API bool entryIsRemoved(size_t index); + //Checks whether an entry has changed (renamed, reader overridden, removed, added). Does not check for changes in the child FileContextInfo. + UABE_Generic_API bool entryHasChanged(size_t index); + + UABE_Generic_API size_t getEntryCount(); + +public: + class DecompressTask : public ITask + { + std::shared_ptr pFileContextInfo; //Maintain object lifetime. + + std::string name; + std::string outputPath; + class AppContext &appContext; + public: + EBundleFileDecompressStatus decompressStatus; + EBundleFileOpenStatus reopenStatus; + DecompressTask(class AppContext &appContext, std::shared_ptr &pContextInfo, std::string outputPath); + const std::string &getName(); + TaskResult execute(TaskProgressManager &progressManager); + inline std::shared_ptr &getFileContextInfo() + { + return pFileContextInfo; + } + }; + //Creates and enqueues a task that decompresses and then reopens the bundle file, replacing the BundleFileContext of this instance. + //Must be called from the main app thread. This instance or its BundleFileContext must not be used from other threads while the task is running. + UABE_Generic_API std::shared_ptr EnqueueDecompressTask(AppContext &appContext, std::shared_ptr &selfPointer, std::string outputPath); + + friend class AppContext; +}; + +class ResourcesFileContextInfo : public FileContextInfo +{ + ResourcesFileContext *pContext; + std::shared_mutex resourcesMutex; + std::list resources; + QWORD originalFileSize; + std::atomic_bool changedFlag; +public: + UABE_Generic_API ResourcesFileContextInfo(ResourcesFileContext *pContext, unsigned int fileID, unsigned int parentFileID); + ~ResourcesFileContextInfo(); + IFileContext *getFileContext(); + void getChildFileIDs(std::vector &childFileIDs); + void onCloseChild(unsigned int childFileID); + bool hasNewChanges(class AppContext &appContext); + bool hasAnyChanges(class AppContext &appContext); + uint64_t write(class AppContext &appContext, IAssetsWriter *pWriter, uint64_t start, bool resetChangedFlag); + std::unique_ptr makeBundleReplacer(class AppContext &appContext, const char *oldName, const char *newName, uint32_t bundleIndex, bool resetChangedFlag); + inline ResourcesFileContext *getResourcesFileContext() { return pContext; } + + UABE_Generic_API bool setByReplacer(class AppContext& appContext, BundleReplacer* pReplacer); + //Adds a new resource to this object and marks the file as changed unless size == 0. + //resourcesFilePos: The start position where the given resource is to be stored. + //Note: The reader reference will be stored in this object's resource list, possibly for the lifetime of this object. + // The reader will be queried as needed (for writing or by the reader from getResource(..)). + UABE_Generic_API void addResource(std::shared_ptr pReader, uint64_t readerOffs, uint64_t size, uint64_t &resourcesFilePos); + //Returns a reader for a given range in the resources file represented by this object. + //The reader is a snapshot of the resource data at about call-time, + // and includes all unsaved changes such as added resources. + //Set size to numeric_limits::max() to capture all the resource data. + //Returns nullptr if no resource data is in the given range. + //Note: The returned reader uses a mutex in Read, SetPosition, Tell, Seek calls. + // For proper multi threading, CreateView should be used, as the views each use an indepentent mutex. + UABE_Generic_API std::shared_ptr getResource(std::shared_ptr selfRef, uint64_t offs, uint64_t size); +}; + +class GenericFileContextInfo : public FileContextInfo +{ + GenericFileContext *pContext; + std::mutex replacementReaderMutex; + //The last entry of this vector is the current replacement reader. + //The vector ensures that each IAssetsReader indirectly returned through makeBundleReplacer + // remains valid as long as some GenericFileContextInfo is valid. + //TODO: Add some mechanism (e.g. a BundleReplacer wrapper) to free any outdated, no longer used readers + // -> e.g. have the BundleReplacer keep some shared_ptr, + // and only store the latest shared_ptr here. + std::vector replacementReaderHistory; + bool changedFlag; +public: + UABE_Generic_API GenericFileContextInfo(GenericFileContext *pContext, unsigned int fileID, unsigned int parentFileID); + ~GenericFileContextInfo(); + IFileContext *getFileContext(); + void getChildFileIDs(std::vector &childFileIDs); + void onCloseChild(unsigned int childFileID); + bool hasNewChanges(class AppContext &appContext); + bool hasAnyChanges(class AppContext &appContext); + uint64_t write(class AppContext &appContext, IAssetsWriter *pWriter, uint64_t start, bool resetChangedFlag); + std::unique_ptr makeBundleReplacer(class AppContext &appContext, const char *oldName, const char *newName, uint32_t bundleIndex, bool resetChangedFlag); + inline GenericFileContext *getGenericFileContext() { return pContext; } +}; + +//#include "AppContext.h" \ No newline at end of file diff --git a/UABE_Generic/FileModTree.cpp b/UABE_Generic/FileModTree.cpp new file mode 100644 index 0000000..e2ac071 --- /dev/null +++ b/UABE_Generic/FileModTree.cpp @@ -0,0 +1,304 @@ +#include "FileModTree.h" +#include "AppContext.h" +#include "../ModInstaller/InstallerDataFormat.h" +#include "../AssetsTools/InternalAssetsReplacer.h" +#include "../AssetsTools/InternalBundleReplacer.h" +#include + +VisibleFileEntry::VisibleFileEntry(AppContext &appContext, std::shared_ptr pContextInfo) + : treeViewEntry(0), pathNull(false) +{ + assert(pContextInfo->getFileContext() != nullptr); + + this->pathOrName.assign(pContextInfo->getFileContext()->getFilePath()); + this->fileType = pContextInfo->getFileContext()->getType(); + std::vector childFileIDs; + pContextInfo->getChildFileIDs(childFileIDs); + switch (pContextInfo->getFileContext()->getType()) + { + case FileContext_Bundle: + { + std::unique_ptr bundleReplacer = pContextInfo->makeBundleReplacer(appContext, "", "", 0, false); + assert(dynamic_cast(bundleReplacer.get()) != nullptr); + if (BundleEntryModifierFromBundle *pBundleModifier = dynamic_cast(bundleReplacer.get())) + { + struct { + std::shared_ptr pContextInfo; + void operator()(BundleReplacer *pBundleReplacer) { FreeBundleReplacer(pBundleReplacer); pContextInfo.reset(); } + } replacerOwnerDeleter; + std::shared_ptr bundleReplacerShared(bundleReplacer.release(), replacerOwnerDeleter); + constructFromReplacer(appContext, bundleReplacerShared, BundleReplacer_BundleEntryModifierFromBundle); + this->pathOrName.assign(pContextInfo->getFileContext()->getFilePath()); + this->pContextInfo = std::move(pContextInfo); + return; + } + } + break; + case FileContext_Assets: + assert(childFileIDs.empty()); + { + auto *pAssetsContextInfo = reinterpret_cast(pContextInfo.get()); + AssetsFileContext *pAssetsContext = pAssetsContextInfo->getAssetsFileContext(); + auto replacersVec = pAssetsContextInfo->getAllReplacers(); + + std::sort(replacersVec.begin(), replacersVec.end(), + [](const std::shared_ptr& a, const std::shared_ptr& b) { + bool aIsEntryModifier = (a->GetType() == AssetsReplacement_AddOrModify || a->GetType() == AssetsReplacement_Remove); + bool bIsEntryModifier = (b->GetType() == AssetsReplacement_AddOrModify || b->GetType() == AssetsReplacement_Remove); + if (!aIsEntryModifier && bIsEntryModifier) + return true; //Put non-entry modifiers before entry modifiers. + else if (!aIsEntryModifier || !bIsEntryModifier) + return false; //No particular order between non-entry modifiers. + return reinterpret_cast(a.get())->GetPathID() + < reinterpret_cast(b.get())->GetPathID(); + }); + + replacers.resize(replacersVec.size()); + for (size_t i = 0; i < replacersVec.size(); ++i) + replacers[i].pReplacer = std::move(replacersVec[i]); + } + break; + case FileContext_Resources: + assert(childFileIDs.empty()); + { + std::unique_ptr bundleReplacer = pContextInfo->makeBundleReplacer(appContext, "", "", (uint32_t)-1, false); + assert(dynamic_cast(bundleReplacer.get()) != nullptr); + if (BundleEntryModifierByResources* pBundleModifier = dynamic_cast(bundleReplacer.get())) + { + replacers.resize(1); + replacers[0].pReplacer.reset(bundleReplacer.release()); + } + else + { + throw std::invalid_argument("VisibleFileEntry: Bundle replacer generated from a ResourcesFileContextInfo is null or has an unexpected type!"); + } + } + break; + case FileContext_Generic: + assert(childFileIDs.empty()); + throw std::domain_error("VisibleFileEntry: FileContext_Generic is not supported as a base file!"); + break; + default: + throw std::domain_error("VisibleFileEntry constructed with an unknown file type!"); + } + this->pContextInfo = std::move(pContextInfo); +} +//shared_ptr deleter that keeps a shared_ptr reference +// on a parent BundleEntryModifierFromBundle or BundleEntryModifierFromAssets, +// which in turn frees all child replacers on destruction. +struct GenericReplacerOwnershipWrapper +{ + std::shared_ptr memoryOwner; + GenericReplacerOwnershipWrapper(std::shared_ptr owner) + : memoryOwner(std::move(owner)) {} + void operator()(GenericReplacer* pReplacer) {memoryOwner.reset();} +}; +//Generate a VisibleFileEntry from a BundleEntryModifierFromAssets, BundleEntryModifierFromBundle or BundleEntryModifierByResources. +//-> type: EBundleReplacers (InternalBundleReplacer.h) +VisibleFileEntry::VisibleFileEntry(class AppContext &appContext, std::shared_ptr &fromReplacer, unsigned int type) + : treeViewEntry(0), pathNull(true) +{ + constructFromReplacer(appContext, fromReplacer, type); +} +void VisibleFileEntry::constructFromReplacer(class AppContext &appContext, std::shared_ptr &fromReplacer, unsigned int type) +{ + const char *_origEntryName = fromReplacer->GetOriginalEntryName(); + this->pathOrName = (_origEntryName == nullptr) ? "" : _origEntryName; + this->pathNull = (_origEntryName == nullptr); + const char *_newEntryName = fromReplacer->GetEntryName(); + this->newName = (_newEntryName == nullptr) ? "" : _newEntryName; + switch ((EBundleReplacers)type) + { + case BundleReplacer_BundleEntryModifierFromAssets: + { + this->fileType = FileContext_Assets; + BundleEntryModifierFromAssets *pAssetsFileReplacer = reinterpret_cast(fromReplacer.get()); + size_t nReplacers = 0; + AssetsReplacer **ppReplacers = pAssetsFileReplacer->GetReplacers(nReplacers); + this->replacers.resize(nReplacers); + for (size_t i = 0; i < nReplacers; ++i) + this->replacers[i].pReplacer = std::shared_ptr(ppReplacers[i], GenericReplacerOwnershipWrapper(fromReplacer)); + } + break; + case BundleReplacer_BundleEntryModifierFromBundle: + { + this->fileType = FileContext_Bundle; + BundleEntryModifierFromBundle *pBundleFileReplacer = reinterpret_cast(fromReplacer.get()); + size_t nReplacers = 0; + BundleReplacer **ppReplacers = pBundleFileReplacer->GetReplacers(nReplacers); + for (size_t i = 0; i < nReplacers; ++i) + { + auto pChildReplacer = std::shared_ptr(ppReplacers[i], GenericReplacerOwnershipWrapper(fromReplacer)); + if (ppReplacers[i]->GetType() == BundleReplacement_AddOrModify) + { + if (dynamic_cast(ppReplacers[i]) != nullptr) + { + BundleEntryModifierFromAssets* pBundleModifierFromAssets = reinterpret_cast(ppReplacers[i]); + uint32_t subFileID = pBundleModifierFromAssets->GetFileID(); + this->subFiles.push_back(VisibleFileEntry(appContext, pChildReplacer, BundleReplacer_BundleEntryModifierFromAssets)); + if (subFileID != (uint32_t)-1) + { + FileContextInfo_ptr pChildContextInfo = appContext.getContextInfo(subFileID); + if (pChildContextInfo != nullptr) + this->subFiles.back().pContextInfo = pChildContextInfo; + } + } + else if (dynamic_cast(ppReplacers[i]) != nullptr) + { + this->subFiles.push_back(VisibleFileEntry(appContext, pChildReplacer, BundleReplacer_BundleEntryModifierFromBundle)); + } + else if (dynamic_cast(ppReplacers[i]) != nullptr + && ppReplacers[i]->RequiresEntryReader()) + { + //This resources replacer is based on an existing bundle entry, i.e. requires the original file reader to work. + // If !ppReplacers[i]->RequiresEntryReader(), the file may not exist (but it could, e.g. if all resources are replaced). + this->subFiles.push_back(VisibleFileEntry(appContext, pChildReplacer, BundleReplacer_BundleEntryModifierByResources)); + } + else + this->replacers.push_back(VisibleReplacerEntry(pChildReplacer)); + } + else + this->replacers.push_back(VisibleReplacerEntry(pChildReplacer)); + } + } + break; + case BundleReplacer_BundleEntryModifierByResources: + { + //Modified resource files just store the bundle replacer inside. + //-> Top-level resource files have a bundle replacer with empty names. + this->fileType = FileContext_Resources; + //BundleEntryModifierByResources* pResourcesReplacer = reinterpret_cast(fromReplacer.get()); + this->replacers.resize(1); + this->replacers[0].pReplacer = fromReplacer; + } + break; + default: + throw std::domain_error("VisibleFileEntry constructed with an unknown/unsupported replacer type!"); + } +} +VisibleFileEntry::VisibleFileEntry(class AppContext &appContext, InstallerPackageAssetsDesc &installerPackageDesc) + : treeViewEntry(0), pathNull(false) +{ + this->pathOrName.assign(installerPackageDesc.path); + switch (installerPackageDesc.type) + { + case InstallerPackageAssetsType::Assets: + { + this->fileType = FileContext_Assets; + this->replacers.resize(installerPackageDesc.replacers.size()); + for (size_t i = 0; i < installerPackageDesc.replacers.size(); ++i) + this->replacers[i].pReplacer = installerPackageDesc.replacers[i]; + } + break; + case InstallerPackageAssetsType::Bundle: + { + this->fileType = FileContext_Bundle; + for (size_t i = 0; i < installerPackageDesc.replacers.size(); ++i) + { + std::shared_ptr pReplacer = std::reinterpret_pointer_cast(installerPackageDesc.replacers[i]); + if (pReplacer->GetType() == BundleReplacement_AddOrModify) + { + if (dynamic_cast(pReplacer.get()) != nullptr) + { + this->subFiles.push_back(VisibleFileEntry(appContext, pReplacer, BundleReplacer_BundleEntryModifierFromAssets)); + } + else if (dynamic_cast(pReplacer.get()) != nullptr) + { + this->subFiles.push_back(VisibleFileEntry(appContext, pReplacer, BundleReplacer_BundleEntryModifierFromBundle)); + } + else if (dynamic_cast(pReplacer.get()) != nullptr + && pReplacer->RequiresEntryReader()) + { + this->subFiles.push_back(VisibleFileEntry(appContext, pReplacer, BundleReplacer_BundleEntryModifierByResources)); + } + else + this->replacers.push_back(VisibleReplacerEntry(std::move(pReplacer))); + } + else + this->replacers.push_back(VisibleReplacerEntry(std::move(pReplacer))); + } + } + break; + case InstallerPackageAssetsType::Resources: + { + this->fileType = FileContext_Resources; + if (installerPackageDesc.replacers.size() != 1 + || dynamic_cast(installerPackageDesc.replacers[0].get()) == nullptr) + { + throw std::invalid_argument("VisibleFileEntry: Resources installer package entry does not consist of a singular resources bundle replacer!"); + } + this->replacers.resize(1); + this->replacers[0].pReplacer = installerPackageDesc.replacers[0]; + } + break; + default: + throw std::domain_error("VisibleFileEntry constructed with an unknown/unsupported installer package entry type!"); + } +} + +std::shared_ptr VisibleFileEntry::produceBundleReplacer() +{ + struct { + std::vector> subOwnerships; + void operator()(AssetsReplacer *del) + { + FreeAssetsReplacer(del); + subOwnerships.clear(); + } + void operator()(BundleReplacer *del) + { + FreeBundleReplacer(del); + subOwnerships.clear(); + } + } bundleModifierDeleter; + switch (this->fileType) + { + case FileContext_Bundle: + { + std::vector childReplacers; + for (size_t i = 0; i < this->replacers.size(); ++i) + { + bundleModifierDeleter.subOwnerships.push_back(this->replacers[i].pReplacer); + childReplacers.push_back(reinterpret_cast(this->replacers[i].pReplacer.get())); + } + for (size_t i = 0; i < this->subFiles.size(); ++i) + { + std::shared_ptr pChildReplacer = this->subFiles[i].produceBundleReplacer(); + childReplacers.push_back(pChildReplacer.get()); + bundleModifierDeleter.subOwnerships.push_back(std::move(pChildReplacer)); + } + return std::shared_ptr( + MakeBundleEntryModifierFromBundle(this->pathNull ? nullptr : this->pathOrName.c_str(), + this->newName.c_str(), childReplacers.data(), childReplacers.size(), + (unsigned int)-1), bundleModifierDeleter); + } + break; + case FileContext_Assets: + { + std::vector childReplacers; + for (size_t i = 0; i < this->replacers.size(); ++i) + { + bundleModifierDeleter.subOwnerships.push_back(this->replacers[i].pReplacer); + childReplacers.push_back(reinterpret_cast(this->replacers[i].pReplacer.get())); + } + return std::shared_ptr( + MakeBundleEntryModifierFromAssets(this->pathNull ? nullptr : this->pathOrName.c_str(), + this->newName.c_str(), nullptr, + childReplacers.data(), childReplacers.size(), 0), bundleModifierDeleter); + } + break; + case FileContext_Resources: + { + if (this->replacers.size() != 1 + || dynamic_cast(this->replacers[0].pReplacer.get()) == nullptr) + { + throw std::invalid_argument("VisibleFileEntry: Resources file entry does not consist of a singular resources bundle replacer!"); + } + return std::reinterpret_pointer_cast(this->replacers[0].pReplacer); + } + break; + default: + throw std::domain_error("VisibleFileEntry::produceBundleReplacer - unsupported file type!"); + } +} + diff --git a/UABE_Generic/FileModTree.h b/UABE_Generic/FileModTree.h new file mode 100644 index 0000000..faf9cd9 --- /dev/null +++ b/UABE_Generic/FileModTree.h @@ -0,0 +1,223 @@ +#pragma once +#include "api.h" +#include "FileContext.h" +#include "../ModInstaller/InstallerDataFormat.h" +#include +#include +#include +#include +#include +#include +#include + +//Note: The UIEntryType members are set to nullptr by default. + +struct VisibleReplacerEntry +{ + //Optional, used to ensure no contents of replacers are freed, such as file readers. + //Do not use its state, as external threads could add replacers, etc. at any time. + std::shared_ptr pContextInfo; + + uintptr_t treeItem; + std::shared_ptr pReplacer; + inline VisibleReplacerEntry() : treeItem(NULL) {} + inline VisibleReplacerEntry(std::shared_ptr pReplacer, std::shared_ptr pContextInfo = nullptr) + : pContextInfo(std::move(pContextInfo)), pReplacer(std::move(pReplacer)), treeItem(NULL) + {} +}; +class VisibleFileEntry +{ + void constructFromReplacer(class AppContext &appContext, std::shared_ptr &fromReplacer, unsigned int type); + +public: + //Optional. Do not use its state, as external threads could add replacers, etc. at any time. + std::shared_ptr pContextInfo; + + //Determines the type of replacers (AssetsReplacer, BundleReplacer). + EFileContextType fileType; + //For assets: All AssetsReplacers for the file. + //For bundles: Certain BundleReplacers (e.g. for generic files) that are not based on an existing file. + // -> Note: The BundleReplacers may have a bundle list index other than -1, but it will not be saved anyway. + //For resources: A single entry containing a BundleEntryModifierByResources that represents the whole resources file. + std::vector replacers; + //For bundles: All sub files not represented by a BundleReplacer in replacers. + std::vector subFiles; + + //For base entries (not included in some parent's subFiles vector): Full file path. + //For child entries: Name of the bundle entry. + std::string pathOrName; bool pathNull; + //For bundle child entries: New name (as in the BundleReplacer). + std::string newName; + + uintptr_t treeViewEntry; + inline VisibleFileEntry() : fileType(FileContext_Generic), pathNull(true), treeViewEntry((uintptr_t)-1) {} + UABE_Generic_API VisibleFileEntry(class AppContext &appContext, std::shared_ptr contextInfo); + UABE_Generic_API VisibleFileEntry(class AppContext &appContext, InstallerPackageAssetsDesc &installerPackageDesc); + //Generate a VisibleFileEntry from a BundleEntryModifierFromAssets, BundleEntryModifierFromBundle or BundleEntryModifierByResources. + //-> type: EBundleReplacers (InternalBundleReplacer.h) + VisibleFileEntry(class AppContext& appContext, std::shared_ptr& fromReplacer, unsigned int type); + + //Merges a VisibleFileEntry (other) into this one. + //T: Callable bool (VisibleReplacerEntry& existing,const VisibleReplacerEntry& other) + // -> Returns true <-> mergeWith will set existing.pReplacer = other.pReplacer, and otherwise does not change existing. + // -> The callee takes care of removing or updating existing.treeItem before returning true. + template void mergeWith(VisibleFileEntry &other, T& resolveConflict) + { + assert(this->fileType == other.fileType); + if (this->fileType != other.fileType) + return; + switch (this->fileType) + { + case FileContext_Bundle: + { + //Should be better than Theta(N^2), disregarding the recursive calls. + std::unordered_map selfReplacersByName(this->replacers.size() * 2); + for (size_t iThis = 0; iThis < this->replacers.size(); ++iThis) + { + BundleReplacer *pReplacer = reinterpret_cast(this->replacers[iThis].pReplacer.get()); + const char *replacedName = pReplacer->GetOriginalEntryName(); + if (replacedName == nullptr) replacedName = pReplacer->GetEntryName(); + if (replacedName != nullptr) + selfReplacersByName[std::string(replacedName)] = iThis; + } + for (size_t iOther = 0; iOther < other.replacers.size(); ++iOther) + { + BundleReplacer *pReplacer = reinterpret_cast(other.replacers[iOther].pReplacer.get()); + const char *replacedName = pReplacer->GetOriginalEntryName(); + if (replacedName == nullptr) replacedName = pReplacer->GetEntryName(); + auto selfReplIt = selfReplacersByName.end(); + if (replacedName != nullptr) + selfReplIt = selfReplacersByName.find(std::string(replacedName)); + if (selfReplIt == selfReplacersByName.end()) + { + //Insert new from other. + this->replacers.push_back(other.replacers[iOther]); + this->replacers.back().treeItem = 0; + } + else if (true == resolveConflict(this->replacers[selfReplIt->second], other.replacers[iOther])) + { + //Replace by other. + this->replacers[selfReplIt->second].pContextInfo = other.replacers[iOther].pContextInfo; + this->replacers[selfReplIt->second].pReplacer = other.replacers[iOther].pReplacer; + } + } + + + std::unordered_map selfSubFilesByName(this->subFiles.size() * 2); + for (size_t iThis = 0; iThis < this->subFiles.size(); ++iThis) + selfSubFilesByName[this->subFiles[iThis].pathNull ? this->subFiles[iThis].newName : this->subFiles[iThis].pathOrName] = iThis; + for (size_t iOther = 0; iOther < other.subFiles.size(); ++iOther) + { + std::string otherPath = this->subFiles[iOther].pathNull ? this->subFiles[iOther].newName : this->subFiles[iOther].pathOrName; + auto selfSubIt = selfSubFilesByName.find(otherPath); + if (selfSubIt == selfSubFilesByName.end()) + { + //Deep copy other (no more recursive conflicts). + struct { + void operator()(VisibleFileEntry &entry) + { + for (size_t i = 0; i < entry.replacers.size(); ++i) + entry.replacers[i].treeItem = 0; + entry.treeViewEntry = 0; + for (size_t i = 0; i < entry.subFiles.size(); ++i) + (*this)(entry.subFiles[i]); + } + } nullUIData; + this->subFiles.push_back(other.subFiles[iOther]); + nullUIData(this->subFiles.back()); //Null any UI info that needs to be regenerated. + } + else + { + //Recursively merge own subFile with other subFile. + this->subFiles[selfSubIt->second].mergeWith(other.subFiles[iOther], resolveConflict); + } + } + } + break; + case FileContext_Assets: + { + //Roughly O(NlogN) + std::map selfReplacersByPathID; + size_t iOwnDependenciesReplacer = SIZE_MAX; + for (size_t iThis = 0; iThis < this->replacers.size(); ++iThis) + { + AssetsReplacer *pReplacer = reinterpret_cast(this->replacers[iThis].pReplacer.get()); + if (pReplacer->GetType() == AssetsReplacement_AddOrModify || pReplacer->GetType() == AssetsReplacement_Remove) + selfReplacersByPathID[reinterpret_cast(pReplacer)->GetPathID()] = iThis; + else if (pReplacer->GetType() == AssetsReplacement_Dependencies) + iOwnDependenciesReplacer = iThis; + } + for (size_t iOther = 0; iOther < other.replacers.size(); ++iOther) + { + AssetsReplacer *pReplacer = reinterpret_cast(other.replacers[iOther].pReplacer.get()); + bool add = false; + size_t replaceIndex = SIZE_MAX; + switch (pReplacer->GetType()) + { + case AssetsReplacement_AddOrModify: + case AssetsReplacement_Remove: + { + auto selfReplIt = selfReplacersByPathID.find(reinterpret_cast(pReplacer)->GetPathID()); + if (selfReplIt == selfReplacersByPathID.end()) + add = true; + else if (true == resolveConflict(this->replacers[selfReplIt->second], other.replacers[iOther])) + replaceIndex = selfReplIt->second; + } + break; + case AssetsReplacement_Dependencies: + { + if (iOwnDependenciesReplacer == SIZE_MAX) + add = true; + else + replaceIndex = iOwnDependenciesReplacer; + } + break; + } + if (add) + { + //Insert new from other. + this->replacers.push_back(other.replacers[iOther]); + this->replacers.back().treeItem = 0; + } + else if (replaceIndex != SIZE_MAX + && true == resolveConflict(this->replacers[replaceIndex], other.replacers[iOther])) + { + //Replace by other. + this->replacers[replaceIndex].pContextInfo = other.replacers[iOther].pContextInfo; + this->replacers[replaceIndex].pReplacer = other.replacers[iOther].pReplacer; + } + } + } + break; + case FileContext_Resources: + { + if (this->replacers.empty()) + { + assert(false); //Should never happen for Resources files. + if (other.replacers.size() == 1) + { + this->replacers.resize(1); + this->replacers[0].pContextInfo = other.replacers[0].pContextInfo; + this->replacers[0].pReplacer = other.replacers[0].pReplacer; + } + } + else if (this->replacers.size() == 1 && other.replacers.size() == 1) + { + if (true == resolveConflict(this->replacers[0], other.replacers[0])) + { + //Replace by other. + this->replacers[0].pContextInfo = other.replacers[0].pContextInfo; + this->replacers[0].pReplacer = other.replacers[0].pReplacer; + } + } + else + assert(false); //Should never happen for Resources files. + } + break; + default: + throw std::domain_error("VisibleFileEntry merging unknown file types!"); + } + } + + UABE_Generic_API std::shared_ptr produceBundleReplacer(); +}; diff --git a/UABE_Generic/IAssetBatchImportDesc.h b/UABE_Generic/IAssetBatchImportDesc.h new file mode 100644 index 0000000..3c3150c --- /dev/null +++ b/UABE_Generic/IAssetBatchImportDesc.h @@ -0,0 +1,39 @@ +#pragma once +#include +#include +#include + +#ifndef OUT +#define OUT +#define IN +#endif + +//Class implemented by plugins that use the AppContext::ShowAssetBatchImportDialog function. +//All const char* and std strings are UTF-8. const char* strings returned by the plugin must not be freed before the dialog has closed. +class IAssetBatchImportDesc +{ +public: + class AssetDesc + { + public: + const char *description; + const char* assetsFileName; + long long int pathID; + }; + //Returns a list of asset descriptions to show to the user. The indices into this list will be used for matchIndex. + virtual bool GetImportableAssetDescs(OUT std::vector& descList) = 0; + + //Returns one or multiple regex string(s) that match(es) any potentially importable file in a directory, including those not to be imported. + //The batch import dialog implementation uses std::regex (ECMAScript) and matches the full name. See https://www.regular-expressions.info/stdregex.html + virtual bool GetFilenameMatchStrings(OUT std::vector& regexList, OUT bool& checkSubDirs) = 0; + + //The return value specifies whether the file name matches any of the assets to import. If a match is found, its index is returned through matchIndex. + //capturingGroups contains the contents of the regex capturing groups matched in filename. + virtual bool GetFilenameMatchInfo(IN const char *filename, IN std::vector& capturingGroups, OUT size_t& matchIndex) = 0; + + //Sets the full file path for an asset. filepath is NULL for assets where no matching file was found. + virtual void SetInputFilepath(IN size_t matchIndex, IN const char* filepath) = 0; + + //Retrieves a potential file name override, returning true only if an override exists. Called by the dialog handler after ShowAssetSettings. + virtual bool HasFilenameOverride(IN size_t matchIndex, OUT std::string& filenameOverride, OUT bool& relativeToBasePath) = 0; +}; \ No newline at end of file diff --git a/UABE_Generic/IProgressIndicator.cpp b/UABE_Generic/IProgressIndicator.cpp new file mode 100644 index 0000000..cba41bf --- /dev/null +++ b/UABE_Generic/IProgressIndicator.cpp @@ -0,0 +1,4 @@ +#include "IProgressIndicator.h" + +IProgressIndicator::IProgressIndicator() {} +IProgressIndicator::~IProgressIndicator() {} diff --git a/UABE_Generic/IProgressIndicator.h b/UABE_Generic/IProgressIndicator.h new file mode 100644 index 0000000..99f967d --- /dev/null +++ b/UABE_Generic/IProgressIndicator.h @@ -0,0 +1,57 @@ +#pragma once +#include "api.h" +#include +#include + +class IProgressIndicator +{ +public: + UABE_Generic_API IProgressIndicator(); + UABE_Generic_API virtual ~IProgressIndicator(); + //Cancel status callback interface. + class ICancelCallback + { + public: + //Called when the progress indicator's cancel status changes. There is no guarantee which thread this is called from. + virtual void OnCancelEvent(bool cancel) = 0; + }; + + //Instructs the progress indicator to stay open or not to stay open (default) with an OK button instead of Cancel if something was written to the log. + virtual void SetDontCloseIfLog(bool dontclose = true) = 0; + + //Adds a new step and returns its index. The step with index 0 already exists before calling AddStep the first time. + virtual size_t AddStep(unsigned int range = 0) = 0; + //Sets the range of a step, which can represent the smallest unit by which the progress of this step can advance. + virtual bool SetStepRange(size_t idx, unsigned int range) = 0; + //Sets the progress of the current step, which should be a value from 0 to (including) range. + virtual bool SetStepStatus(unsigned int progress) = 0; + + //Jumps to a step and sets its progress. + virtual bool JumpToStep(size_t idx, unsigned int progress = 0) = 0; + //Goes to the next step, setting its progress to 0. Returns (size_t)-1 on failure, the new idx otherwise. + virtual size_t GoToNextStep() = 0; + + //Sets the progress indicator's window title. + virtual bool SetTitle(const std::string& title) = 0; + virtual bool SetTitle(const std::wstring& title) = 0; + //Sets the description of the progress indicator, usually referring to the current step. + virtual bool SetDescription(const std::string& desc) = 0; + virtual bool SetDescription(const std::wstring& desc) = 0; + + //Adds text to the log. + virtual bool AddLogText(const std::string& text) = 0; + virtual bool AddLogText(const std::wstring& text) = 0; + //Adds a line of text to the log. + inline bool AddLogLine(std::string line) { return AddLogText(line + "\r\n"); } + inline bool AddLogLine(std::wstring line) { return AddLogText(line + L"\r\n"); } + + //Enables or disables the cancel button. + virtual bool SetCancellable(bool cancellable) = 0; + + //Adds a cancel callback. Called by the window handler from another thread, or by SetCancelled. + virtual bool AddCancelCallback(std::unique_ptr pCallback) = 0; + //Retrieves the current cancel status. + virtual bool IsCancelled() = 0; + //Sets the current cancel status. + virtual bool SetCancelled(bool cancelled) = 0; +}; diff --git a/UABE_Generic/PluginManager.cpp b/UABE_Generic/PluginManager.cpp new file mode 100644 index 0000000..d3fec98 --- /dev/null +++ b/UABE_Generic/PluginManager.cpp @@ -0,0 +1,7 @@ +#include "PluginManager.h" + +IOptionProvider::~IOptionProvider() {} +IPluginDesc::~IPluginDesc() {} +IOptionRunner::~IOptionRunner() {} + +IAssetOptionProviderGeneric::IAssetOptionProviderGeneric() {} diff --git a/UABE_Generic/PluginManager.h b/UABE_Generic/PluginManager.h new file mode 100644 index 0000000..26e4d10 --- /dev/null +++ b/UABE_Generic/PluginManager.h @@ -0,0 +1,108 @@ +#pragma once +#include "api.h" +#include +#include +#include +#include +#include +#include + +//TODO: Incomplete (just a skeleton). + +class IOptionProvider +{ +public: + UABE_Generic_API virtual ~IOptionProvider(); +}; + +class IPluginDesc +{ +public: + UABE_Generic_API virtual ~IPluginDesc(); + UABE_Generic_API virtual std::string getName() = 0; + UABE_Generic_API virtual std::string getAuthor() = 0; + //New line: \n + UABE_Generic_API virtual std::string getDescriptionText() = 0; + //The IPluginDesc object should keep a reference to the returned options, as the caller may keep only std::weak_ptrs. + //Note: May be called early, e.g. before program UI initialization. + UABE_Generic_API virtual std::vector> getPluginOptions(class AppContext& appContext) = 0; +}; + +class PluginMapping +{ +public: + std::list> options; + std::list> descriptions; + + //Finds the next plugin option provider of a specific type. + //curOption: Last returned iterator of a getNextOptionProvider, or options.cbegin(). + //pProvider: Output variable for the found provider. Set to nullptr if no provider was found. + // -> The caller can (and probably should) stop iterating if pProvider == nullptr. + //Returns an iterator in options that points to the next element to check, or options.cend(). + template + requires std::derived_from + inline decltype(options)::const_iterator getNextOptionProvider( + decltype(options)::const_iterator curOption, + std::shared_ptr& pProvider) const + { + pProvider.reset(); + for (; curOption != options.end(); ++curOption) + { + pProvider = std::dynamic_pointer_cast(curOption->lock()); + if (pProvider != nullptr) + { + ++curOption; + break; + } + } + return curOption; + } +}; + +//Option runner base class for option providers. +class IOptionRunner +{ +public: + UABE_Generic_API virtual ~IOptionRunner(); + //Perform the operation. + //Tasks blocking the caller should be avoided, as it would make the rest of the application unresponsive. + // An exception to this are modal Win32 dialogs. + UABE_Generic_API virtual void operator()() = 0; +}; + +#include +class OptionRunnerByFn : public IOptionRunner +{ + std::function fn; +public: + inline OptionRunnerByFn(std::function _fn) + : fn(std::move(_fn)) + {} + inline void operator()() { fn(); } +}; + +#include "AppContext.h" +#include "AssetIterator.h" +#include "AssetPluginUtil.h" + +enum class EAssetOptionType +{ + Import, //Import or edit an asset, may also export + Export //Export an asset +}; + +//Provider for asset options: Apply an operation on a selection of assets. +class IAssetOptionProviderGeneric : public IOptionProvider +{ +public: + UABE_Generic_API IAssetOptionProviderGeneric(); + UABE_Generic_API virtual EAssetOptionType getType()=0; + //Determines whether this option provider applies to a given selection. + //Returns a runner object for the selection, or a null pointer otherwise. + //No operation should be applied unless the runner object's operator() is invoked. + //The plugin should set "optionName" to a short description on success. + UABE_Generic_API virtual std::unique_ptr prepareForSelection( + class AppContext& appContext, + std::vector selection, + std::string &optionName)=0; +}; diff --git a/UABE_Generic/TaskStatusTracker.cpp b/UABE_Generic/TaskStatusTracker.cpp new file mode 100644 index 0000000..adf5de2 --- /dev/null +++ b/UABE_Generic/TaskStatusTracker.cpp @@ -0,0 +1,230 @@ +#include "TaskStatusTracker.h" +#include + +TaskStatusTracker::TaskStatusTracker(AppContext& appContext) + : appContext(appContext) +{ + appContext.taskManager.addCallback(this); +} +TaskStatusTracker::~TaskStatusTracker() +{ + appContext.taskManager.removeCallback(this); +} + +void TaskStatusTracker::updateTotalProgress() +{ + if (taskList.empty()) + { + totalProgress = 0.0f; + this->mainOnTotalProgressUpdate(); + return; + } + float progressPerTask = 1.0f / (float)(taskList.size() - staleTaskRefs.size()); + totalProgress = std::transform_reduce(taskList.begin(), taskList.end(), 0.0f, + [](float a, float b) {return a + b; }, + [progressPerTask](const TaskStatusDesc& desc) { + if (desc.hasResult) + return 0.0f; + if (desc.range == 0) + return 0.0f; + if (desc.progress >= desc.range) + return 1.0f; + return progressPerTask * static_cast(static_cast(desc.progress) / static_cast(desc.range)); + }); + this->mainOnTotalProgressUpdate(); +} +void TaskStatusTracker::updateLatestProgressMessage() +{ + auto latestElemIt = std::max_element(taskList.begin(), taskList.end(), + [](const TaskStatusDesc& a, const TaskStatusDesc& b) { return a.progressDescNumber < b.progressDescNumber; }); + if (latestElemIt == taskList.end()) + { + latestProgressMessage.clear(); + this->mainOnProgressMessageUpdate(); + return; + } + latestProgressMessage = latestElemIt->curProgressDesc; + this->mainOnProgressMessageUpdate(); +} + +void TaskStatusTracker::OnAdd(std::shared_ptr& pTask) +{ + if (pTask == nullptr) + return; + appContext.postMainThreadCallback([this, pTask]() { + auto taskListIt = taskList.end(); + auto taskMapIt = taskByPtr.find(pTask.get()); + if (taskMapIt != taskByPtr.end()) + return; + taskListIt = taskList.insert(taskList.end(), TaskStatusDesc(pTask, taskList.end(), staleTaskRefs.end())); + taskListIt->selfRef = taskListIt; + taskListIt->name = pTask->getName(); + taskListIt->progress = 0; + taskListIt->range = 0; + taskByPtr.insert({ pTask.get(), taskListIt }); + this->updateTotalProgress(); + this->mainOnTaskAdd(taskListIt); + }); +} + +void TaskStatusTracker::OnProgress(std::shared_ptr& pTask, unsigned int progress, unsigned int range) +{ + if (pTask == nullptr) + return; + appContext.postMainThreadCallback([this, pTask, progress, range]() { + auto taskMapIt = taskByPtr.find(pTask.get()); + if (taskMapIt == taskByPtr.end()) + return; + auto taskListIt = taskMapIt->second; + taskListIt->progress = progress; + taskListIt->range = range; + this->updateTotalProgress(); + this->mainOnTaskProgressUpdate(taskListIt); + }); +} + +void TaskStatusTracker::OnProgressDesc(std::shared_ptr& pTask, const std::string& desc) +{ + if (pTask == nullptr) + return; + appContext.postMainThreadCallback([this, pTask, desc]() { + auto taskMapIt = taskByPtr.find(pTask.get()); + if (taskMapIt == taskByPtr.end()) + return; + auto taskListIt = taskMapIt->second; + taskListIt->curProgressDesc.assign(desc); + if (!desc.empty()) + { + if (this->progressDescCounter == std::numeric_limitsprogressDescCounter)>::max()) + { + this->progressDescCounter = 1; + for (TaskStatusDesc& desc : taskList) + desc.progressDescNumber = (desc.curProgressDesc.empty() ? 0 : 1); + } + taskListIt->progressDescNumber = ++this->progressDescCounter; + this->latestProgressMessage = desc; + this->mainOnProgressMessageUpdate(); + } + else + { + taskListIt->progressDescNumber = 0; + this->updateLatestProgressMessage(); + } + this->mainOnTaskProgressDescUpdate(taskListIt); + }); +} +void TaskStatusTracker::OnLogMessage(std::shared_ptr& pTask, const std::string& msg) +{ + if (pTask == nullptr) + return; + + appContext.postMainThreadCallback([this, pTask, msg]() { + auto taskMapIt = taskByPtr.find(pTask.get()); + if (taskMapIt == taskByPtr.end()) + return; + auto taskListIt = taskMapIt->second; + if (!msg.ends_with('\n')) + taskListIt->messages.emplace_back(msg + "\n"); + else + taskListIt->messages.emplace_back(msg); + this->mainOnTaskAddLogMessage(taskListIt); + }); +} +void TaskStatusTracker::OnCompletion(std::shared_ptr& pTask, TaskResult result) +{ + if (pTask == nullptr) + return; + appContext.postMainThreadCallback([this, pTask, result]() { + auto taskMapIt = taskByPtr.find(pTask.get()); + if (taskMapIt == taskByPtr.end()) + return; + auto taskListIt = taskMapIt->second; + taskByPtr.erase(taskMapIt); + assert(taskListIt->hasResult == false); + taskListIt->hasResult = true; + taskListIt->result = result; + this->lastTaskResult = result; + unsigned int progressDescNumber = taskListIt->progressDescNumber; + taskListIt->progressDescNumber = 0; + bool taskProgressDescUpdate = (!taskListIt->curProgressDesc.empty()); + taskListIt->curProgressDesc.clear(); + staleTaskRefs.push_back(taskListIt); + if (progressDescNumber == this->progressDescCounter) + this->updateLatestProgressMessage(); + else + this->mainOnProgressMessageUpdate(); //Let the tracker subclass update task counter labels, etc.. + this->updateTotalProgress(); + if (taskProgressDescUpdate) + this->mainOnTaskProgressDescUpdate(taskListIt); + if (taskListIt->cancelable) + { + taskListIt->cancelable = false; + this->mainOnTaskCancelableChange(taskListIt); + } + this->mainOnTaskCompletion(taskListIt); + }); +} +void TaskStatusTracker::OnCancelableChange(std::shared_ptr& pTask, bool cancelable) +{ + if (pTask == nullptr) + return; + appContext.postMainThreadCallback([this, pTask, cancelable]() { + auto taskMapIt = taskByPtr.find(pTask.get()); + if (taskMapIt == taskByPtr.end()) + return; + auto taskListIt = taskMapIt->second; + taskListIt->cancelable = cancelable; + this->mainOnTaskCancelableChange(taskListIt); + }); +} + +void TaskStatusTracker::preEraseElement(std::list::iterator listEntry) +{} + +void TaskStatusTracker::eraseStaleElements() +{ + if (erasingStaleElements) + { + repeatEraseStaleElements = true; + return; + } + erasingStaleElements = true; + auto staleListEndIt = staleTaskRefs.end(); + do { + repeatEraseStaleElements = false; + for (auto staleListIt = staleTaskRefs.begin(); staleListIt != staleTaskRefs.end();) + { + auto curStaleListIt = staleListIt; + ++staleListIt; + + typename decltype(taskList)::iterator taskListIt = *curStaleListIt; + if (taskListIt->eraseIfStale) + { + preEraseElement(taskListIt); + staleTaskRefs.erase(curStaleListIt); + taskList.erase(taskListIt); + } + } + } while (repeatEraseStaleElements); + erasingStaleElements = false; +} + +void TaskStatusTracker::mainOnTaskAdd(std::list::iterator listEntry) +{} +void TaskStatusTracker::mainOnTaskProgressUpdate(std::list::iterator listEntry) +{} +void TaskStatusTracker::mainOnTaskProgressDescUpdate(std::list::iterator listEntry) +{} +void TaskStatusTracker::mainOnTaskAddLogMessage(std::list::iterator listEntry) +{} +void TaskStatusTracker::mainOnTaskCompletion(std::list::iterator listEntry) +{ + eraseStaleElements(); +} +void TaskStatusTracker::mainOnTaskCancelableChange(std::list::iterator listEntry) +{} + +void TaskStatusTracker::mainOnTotalProgressUpdate() +{} +void TaskStatusTracker::mainOnProgressMessageUpdate() +{} diff --git a/UABE_Generic/TaskStatusTracker.h b/UABE_Generic/TaskStatusTracker.h new file mode 100644 index 0000000..b8d1e99 --- /dev/null +++ b/UABE_Generic/TaskStatusTracker.h @@ -0,0 +1,80 @@ +#pragma once +#include "AsyncTask.h" +#include "AppContext.h" +#include +#include +#include +#include +#include + +struct TaskStatusDesc +{ + std::list::iterator selfRef; + + std::weak_ptr wpTask; + std::string name; + unsigned int progress = 0, range = 100; + std::vector messages; + std::string curProgressDesc; + unsigned int progressDescNumber = 0; //Higher: more recent + + bool cancelable = false; + + bool hasResult = false; + //Invalid as long as !hasResult. + TaskResult result = 0; + + //Reference into staleTaskRefs (sorry for the type). + std::list::iterator>::iterator staleTaskSelfRef; + bool eraseIfStale = true; + + //Additional field not touched by the TaskStatusTracker base class. + uintptr_t auxData = 0; + + inline TaskStatusDesc(const std::shared_ptr& pTask, decltype(selfRef) selfRef, decltype(staleTaskSelfRef) staleTaskSelfRef) + : selfRef(selfRef), wpTask(pTask), staleTaskSelfRef(staleTaskSelfRef) + {} +}; +class TaskStatusTracker : public TaskProgressCallback +{ + void updateTotalProgress(); + void updateLatestProgressMessage(); + unsigned int progressDescCounter = 0; + bool erasingStaleElements = false, repeatEraseStaleElements = false; +protected: + class AppContext& appContext; + //Main task status list. + std::list taskList; + //Fast lookup for the On* main thread callback. + std::unordered_map taskByPtr; + //Main task status list. + std::list staleTaskRefs; + + float totalProgress = 0.0f; + std::string latestProgressMessage; + TaskResult lastTaskResult = 0; +public: + UABE_Generic_API TaskStatusTracker(class AppContext& appContext); + UABE_Generic_API ~TaskStatusTracker(); + + UABE_Generic_API void OnAdd(std::shared_ptr& pTask); + UABE_Generic_API void OnProgress(std::shared_ptr& pTask, unsigned int progress, unsigned int range); + UABE_Generic_API void OnProgressDesc(std::shared_ptr& pTask, const std::string& desc); + UABE_Generic_API void OnLogMessage(std::shared_ptr& pTask, const std::string& msg); + UABE_Generic_API void OnCompletion(std::shared_ptr& pTask, TaskResult result); + UABE_Generic_API void OnCancelableChange(std::shared_ptr& pTask, bool cancelable); + + UABE_Generic_API void eraseStaleElements(); + +public: + UABE_Generic_API virtual void preEraseElement(std::list::iterator listEntry); + UABE_Generic_API virtual void mainOnTaskAdd(std::list::iterator listEntry); + UABE_Generic_API virtual void mainOnTaskProgressUpdate(std::list::iterator listEntry); + UABE_Generic_API virtual void mainOnTaskProgressDescUpdate(std::list::iterator listEntry); + UABE_Generic_API virtual void mainOnTaskAddLogMessage(std::list::iterator listEntry); + UABE_Generic_API virtual void mainOnTaskCompletion(std::list::iterator listEntry); + UABE_Generic_API virtual void mainOnTaskCancelableChange(std::list::iterator listEntry); + + UABE_Generic_API virtual void mainOnTotalProgressUpdate(); + UABE_Generic_API virtual void mainOnProgressMessageUpdate(); +}; diff --git a/UABE_Generic/api.h b/UABE_Generic/api.h new file mode 100644 index 0000000..8c55420 --- /dev/null +++ b/UABE_Generic/api.h @@ -0,0 +1,6 @@ +#pragma once +#ifdef UABE_Generic_EXPORTS +#define UABE_Generic_API __declspec(dllexport) +#else +#define UABE_Generic_API __declspec(dllimport) +#endif diff --git a/UABE_Win32/AddAssetDialog.cpp b/UABE_Win32/AddAssetDialog.cpp new file mode 100644 index 0000000..38e2748 --- /dev/null +++ b/UABE_Win32/AddAssetDialog.cpp @@ -0,0 +1,728 @@ +#include "stdafx.h" +#include "AddAssetDialog.h" +#include "resource.h" +#include "../libStringConverter/convert.h" +#include "MonoBehaviourManager.h" +#include "CreateEmptyValueField.h" +#include +#include + +void AddAssetDialog::open() +{ + DialogBoxParam(appContext.getMainWindow().getHInstance(), + MAKEINTRESOURCE(IDD_ADDASSET), + appContext.getMainWindow().getWindow(), + DlgProc, (LPARAM)this); +} + +void AddAssetDialog::EnumScriptIndices_HandleMonoScript(AssetsFileContextInfo *pSourceFile, unsigned int scriptFileRefIdx, + long long int scriptPathID, std::vector &descriptors) +{ + bool exists = false; + for (size_t i = 0; i < descriptors.size(); i++) + { + if (descriptors[i].monoScriptFileIDRel == scriptFileRefIdx + && descriptors[i].monoScriptPathID == scriptPathID) + { + exists = true; + break; + } + } + if (!exists) + { + ScriptIdxDescriptor descriptor = {}; + descriptor.monoClassID = 0xFFFF; + descriptor.isNewClassID = false; + descriptor.monoScriptFileIDRel = scriptFileRefIdx; + descriptor.monoScriptPathID = scriptPathID; + descriptors.push_back(descriptor); + } +} + +unsigned int AddAssetDialog::findRelFileID(AssetsFileContextInfo *pSourceFile, unsigned int targetFileID) +{ + if (targetFileID == pSourceFile->getFileID()) + return 0; + + unsigned int targetFileIDRel = (unsigned int)-1; + auto refLock = pSourceFile->lockReferencesRead(); + const std::vector &sourceReferences = pSourceFile->getReferencesRead(refLock); + + auto targetFileIDRefIt = std::find(sourceReferences.begin(), sourceReferences.end(), targetFileID); + if (targetFileIDRefIt != sourceReferences.end()) + targetFileIDRel = (unsigned int)(std::distance(sourceReferences.begin(), targetFileIDRefIt) + 1); + + return targetFileIDRel; +} + +void AddAssetDialog::EnumScriptIndices_HandleMonoBehaviour(AssetsFileContextInfo *pSourceFile, + AssetIdentifier &behaviourAsset, + AssetTypeTemplateField *pBehaviourBase, std::vector &descriptors) +{ + if (!behaviourAsset.resolve(appContext)) + return; + IAssetsReader_ptr pReader = behaviourAsset.makeReader(); + if (pReader == nullptr) + return; + + AssetTypeInstance instance(1, &pBehaviourBase, behaviourAsset.getDataSize(), pReader.get(), behaviourAsset.isBigEndian()); + AssetTypeValueField *pBaseField = instance.GetBaseField(); + if (pBaseField == nullptr) + return; + + AssetTypeValue *pFileIDValue = pBaseField->Get("m_Script")->Get("m_FileID")->GetValue(); + AssetTypeValue *pPathIDValue = pBaseField->Get("m_Script")->Get("m_PathID")->GetValue(); + if (pFileIDValue == nullptr || pPathIDValue == nullptr) + return; + + unsigned int scriptFileID_RelBehaviour = pFileIDValue->AsUInt(); + unsigned int scriptFileID = behaviourAsset.pFile->resolveRelativeFileID(pFileIDValue->AsUInt()); + long long int scriptPathID = pPathIDValue->AsInt64(); + if (scriptFileID == 0 || scriptPathID == 0) + return; + + unsigned int scriptFileIDRel = findRelFileID(pSourceFile, scriptFileID); + if (scriptFileIDRel == (unsigned int)-1) + return; + + struct { + bool operator()(AssetsFileContextInfo *pSourceFile, AppContext &appContext, + AssetIdentifier &behaviourAsset, AssetTypeTemplateField *pPureBehaviourBase) + { + AssetTypeTemplateField fullBehaviourBase; + if (pSourceFile->MakeTemplateField(&fullBehaviourBase, appContext, + behaviourAsset.getClassID(appContext), behaviourAsset.getMonoScriptID(appContext), &behaviourAsset) + && fullBehaviourBase.children.size() > pPureBehaviourBase->children.size()) + { + return true; + } + return false; + } + } getExtendedTypeInfoPresent; + + bool exists = false; + for (size_t i = 0; i < descriptors.size(); i++) + { + if (descriptors[i].monoScriptFileIDRel == scriptFileIDRel + && descriptors[i].monoScriptPathID == scriptPathID) + { + if (behaviourAsset.fileID == pSourceFile->getFileID()) + { + if (descriptors[i].monoClassID == (decltype(descriptors[i].monoClassID))-1) + descriptors[i].extendedTypeInfoPresent = getExtendedTypeInfoPresent(pSourceFile, appContext, behaviourAsset, pBehaviourBase); + descriptors[i].monoClassID = behaviourAsset.getMonoScriptID(appContext); + } + exists = true; + break; + } + else if (descriptors[i].monoClassID == behaviourAsset.getMonoScriptID(appContext)) + { + exists = true; + break; + } + } + if (!exists) + { + ScriptIdxDescriptor descriptor = {}; + descriptor.monoClassID = behaviourAsset.getMonoScriptID(appContext); + descriptor.isNewClassID = false; + descriptor.monoScriptFileIDRel = scriptFileIDRel; + descriptor.monoScriptPathID = scriptPathID; +descriptor.extendedTypeInfoPresent = getExtendedTypeInfoPresent(pSourceFile, appContext, behaviourAsset, pBehaviourBase); + + descriptors.push_back(descriptor); + } +} + +int AddAssetDialog::GetTextExtent(HWND hComboBox, const TCHAR *text) +{ + HDC hListDC = GetDC(hComboBox); + HGDIOBJ hOrigObject = SelectObject(hListDC, GetWindowFont(hComboBox)); + RECT textRect = {}; + DrawText(hListDC, text, -1, &textRect, DT_SINGLELINE | DT_CALCRECT); + SelectObject(hListDC, hOrigObject); + ReleaseDC(hComboBox, hListDC); + + return textRect.right-textRect.left + 4; +} + +bool AddAssetDialog::EnumScriptIndices(AssetsFileContextInfo *pFile, std::vector &descriptors) +{ + descriptors.clear(); + + uint16_t maxScriptIndex = 0xFFFF; //max script index of fileID + const std::vector references = pFile->getReferences(); + + //Enumerate the MonoScript assets reachable from pFile (i.e. inside pFile or in a referenced file). + // -> Store the relative file ID and path ID. + // -> Retrieve the script index inside pFile, if a MonoBehavior asset exists for that script (otherwise 0xFFFF). + //Store the result in descriptors. + bool useLongPathID = false; + { + for (unsigned int i = 0; i <= references.size(); i++) + { + unsigned int targetFileID = 0; + if (i == 0) targetFileID = pFile->getFileID(); + else targetFileID = references[i-1]; + FileContextInfo_ptr pTargetFileAny; + if (targetFileID == 0 + || !(pTargetFileAny = appContext.getContextInfo(targetFileID)) + || pTargetFileAny->getFileContext() == nullptr + || pTargetFileAny->getFileContext()->getType() != FileContext_Assets) + continue; + AssetsFileContextInfo *pTargetFile = reinterpret_cast(pTargetFileAny.get()); + + int scriptClassId = pTargetFile->GetClassByName("MonoScript"); + int behaviourClassId = pTargetFile->GetClassByName("MonoBehaviour"); + + AssetTypeTemplateField behaviourBase; + if (behaviourClassId >= 0) + pTargetFile->MakeTemplateField(&behaviourBase, appContext, behaviourClassId); + + for (AssetIterator iter(pTargetFile); !iter.isEnd(); ++iter) + { + AssetIdentifier curAsset; + iter.get(curAsset); + curAsset.resolve(appContext); + if (curAsset.getClassID() == scriptClassId) + { + EnumScriptIndices_HandleMonoScript(pFile, i, (long long)curAsset.pathID, descriptors); + } + else if (targetFileID == pFile->getFileID() && curAsset.getMonoScriptID() != 0xFFFF) + { + //MonoBehaviours are used to retrieve the expected script index, which is different for each .assets file. + EnumScriptIndices_HandleMonoBehaviour(pFile, curAsset, &behaviourBase, descriptors); + if (curAsset.getMonoScriptID() > maxScriptIndex || maxScriptIndex == 0xFFFF) + maxScriptIndex = curAsset.getMonoScriptID(); + } + } + } + } + + //Cache the temporarily (but 'randomly') needed MonoScript deserialization templates. + //-> Retrievable through getScriptTemplate below. + std::unordered_map> scriptTemplatesByFileID; + //Ancient pre-C++11 lambda equivalent (TODO: modernize this). + struct _Lambda_GetScriptTemplate { + std::unordered_map> &scriptTemplatesByFileID; + AppContext &appContext; + _Lambda_GetScriptTemplate(decltype(scriptTemplatesByFileID) &scriptTemplatesByFileID, AppContext &appContext) + : scriptTemplatesByFileID(scriptTemplatesByFileID), appContext(appContext) + {} + AssetTypeTemplateField *operator()(unsigned int fileID) + { + auto entryIt = scriptTemplatesByFileID.find(fileID); + if (entryIt != scriptTemplatesByFileID.end()) + return entryIt->second.get(); + FileContextInfo_ptr pContextInfo = appContext.getContextInfo(fileID); + if (pContextInfo == nullptr + || pContextInfo->getFileContext() == nullptr + || pContextInfo->getFileContext()->getType() != FileContext_Assets) + return nullptr; + AssetsFileContextInfo *pFile = reinterpret_cast(pContextInfo.get()); + int32_t scriptClassID = pFile->GetClassByName("MonoScript"); + if (scriptClassID < 0) + return nullptr; + std::unique_ptr pTemplate(new AssetTypeTemplateField()); + if (pFile->MakeTemplateField(pTemplate.get(), appContext, scriptClassID) && pTemplate->children.size() > 0) + { + return (scriptTemplatesByFileID[fileID] = std::move(pTemplate)).get(); + } + return nullptr; + } + } getScriptTemplate(scriptTemplatesByFileID, appContext); + + //For each enumerated MonoScript: + // -> Retrieve the hash for the script properties + // -> Generate a human-facing identifier from the strings inside the MonoScript. + for (size_t i = 0; i < descriptors.size(); i++) + { + ScriptIdxDescriptor &descriptor = descriptors[i]; + descriptor.propertiesHash.qValue[0] = descriptor.propertiesHash.qValue[1] = 0; + if (descriptor.monoClassID == 0xFFFF) + { + //Assign the new script index. + //-> The user will only select one from the descriptor vector, + // i.e. the potential future script index is the same across all new ones. + descriptor.monoClassID = maxScriptIndex + 1; + descriptor.isNewClassID = true; + } + + unsigned int scriptFileID = 0; + if (descriptor.monoScriptFileIDRel == 0) scriptFileID = pFile->getFileID(); + else scriptFileID = references[descriptor.monoScriptFileIDRel-1]; + FileContextInfo_ptr pScriptFileAny; + if (scriptFileID == 0 + || !(pScriptFileAny = appContext.getContextInfo(scriptFileID)) + || pScriptFileAny->getFileContext() == nullptr + || pScriptFileAny->getFileContext()->getType() != FileContext_Assets) + continue; + AssetsFileContextInfo *pScriptFile = reinterpret_cast(pScriptFileAny.get()); + + int scriptClassId = pScriptFile->GetClassByName("MonoScript"); + AssetTypeTemplateField *pScriptTemplate = getScriptTemplate(scriptFileID); + if (pScriptTemplate == nullptr) + continue; + AssetIdentifier scriptAsset(std::shared_ptr(pScriptFileAny, pScriptFile), (pathid_t)descriptor.monoScriptPathID); + if (!scriptAsset.resolve(appContext)) + { + assert(false); + continue; + } + IAssetsReader_ptr pReader = scriptAsset.makeReader(appContext); + if (pReader == nullptr) + { + assert(false); + continue; + } + AssetTypeInstance scriptInstance = AssetTypeInstance(1, &pScriptTemplate, scriptAsset.getDataSize(), + pReader.get(), scriptAsset.isBigEndian()); + + AssetTypeValueField *pScriptBase = scriptInstance.GetBaseField(); + AssetTypeValueField *pScriptClassNameField; const char *scriptClassName; + AssetTypeValueField *pScriptNamespaceField; const char *scriptNamespace; + AssetTypeValueField *pScriptAssemblyNameField; const char *scriptAssemblyName; + if ((pScriptBase != NULL) && + (pScriptClassNameField = pScriptBase->Get("m_ClassName"))->GetValue() + && (scriptClassName = pScriptClassNameField->GetValue()->AsString()) && + (pScriptNamespaceField = pScriptBase->Get("m_Namespace"))->GetValue() + && (scriptNamespace = pScriptNamespaceField->GetValue()->AsString())) + { + AssetTypeValueField *pScriptPropertiesHashField = pScriptBase->Get("m_PropertiesHash"); + if (!pScriptPropertiesHashField->IsDummy() && pScriptPropertiesHashField->GetChildrenCount() == 16) + { + for (int j = 0; j < 16; j++) + { + AssetTypeValueField *pCurField = pScriptPropertiesHashField->Get(j); + AssetTypeValue *pCurByte = pCurField->GetValue(); + if (!pCurByte || pCurByte->GetType() != ValueType_UInt8) + { + assert(false); //Class database invalid / unsupported here? Not critical. + descriptor.propertiesHash.qValue[0] = descriptor.propertiesHash.qValue[1] = 0; + break; + } + else + descriptor.propertiesHash.bValue[j] = (uint8_t)pCurByte->AsUInt(); + } + } + std::string scriptDesc(scriptNamespace); + if (scriptDesc.size() > 0) + scriptDesc += "."; + scriptDesc += scriptClassName; + if ((pScriptAssemblyNameField = pScriptBase->Get("m_AssemblyName"))->GetValue() + && (scriptAssemblyName = pScriptAssemblyNameField->GetValue()->AsString())) + { + scriptDesc += " ("; + scriptDesc += scriptAssemblyName; + scriptDesc += ")"; + } + else + scriptAssemblyName = ""; + descriptor.scriptDescText = std::move(scriptDesc); + } + } + + return true; +} + +pathid_t AddAssetDialog::getFreePathID(AssetsFileContextInfo *pFile) +{ + pathid_t pathID = 1; + size_t numAssets = 0; + for (AssetIterator iter(pFile); !iter.isEnd(); ++iter) + { + AssetIdentifier asset; + iter.get(asset); + ++numAssets; + if (asset.pathID >= pathID) + pathID = asset.pathID + 1; + } + if (pathID == 0 || pathID == (unsigned long long)UINT_MAX+1) + { + std::vector allPathIDs; + allPathIDs.reserve(numAssets); + for (AssetIterator iter(pFile); !iter.isEnd(); ++iter) + { + AssetIdentifier asset; + iter.get(asset); + allPathIDs.push_back(asset.pathID); + assert(asset.pathID > 0); + } + std::sort(allPathIDs.begin(), allPathIDs.end()); + if (!allPathIDs.empty() && allPathIDs[0] > 1) + return 1; //Path ID 1 is empty. + for (size_t i = 1; i < allPathIDs.size(); ++i) + if (allPathIDs[i] - allPathIDs[i-1] > 1) + return allPathIDs[i-1] + 1; + } + else + return pathID; + return 0; +} + +INT_PTR CALLBACK AddAssetDialog::DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + AddAssetDialog *pThis = (AddAssetDialog*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + static const char *addAsset_MonoBehavMessage + = "Additional MonoBehaviour type information can possibly be retrieved in order to generate valid assets.\n" + "Do you want to do that now? Otherwise, the new asset may be invalid."; + int wmId, wmEvent; + UNREFERENCED_PARAMETER(lParam); + INT_PTR ret = (INT_PTR)FALSE; + switch (message) + { + case WM_CLOSE: + case WM_DESTROY: + //OnPluginListCancel(pMainWindow, hDlg); + break; + case WM_INITDIALOG: + { + SetWindowLongPtr(hDlg, GWLP_USERDATA, lParam); + pThis = (AddAssetDialog*)lParam; + pThis->scriptDescriptors.clear(); + + HWND hWndOptions = GetDlgItem(hDlg, IDC_COMBOOPTIONLIST); + HWND hWndFileId = GetDlgItem(hDlg, IDC_COMBOFILEID); + HWND hWndPathId = GetDlgItem(hDlg, IDC_EDITPATHID); + HWND hWndClassId = GetDlgItem(hDlg, IDC_EDITCLASSID); + HWND hWndMonoClassId = GetDlgItem(hDlg, IDC_EDITMONOCLASSID); + HWND hWndValidAsset = GetDlgItem(hDlg, IDC_CKVALIDASSET); + HWND hWndStaticScriptClass = GetDlgItem(hDlg, IDC_STATICSCRIPTCLASS); + HWND hWndScriptClass = GetDlgItem(hDlg, IDC_COMBOSCRIPTCLASS); + + COMBOBOXINFO scriptClassInfo = {}; + scriptClassInfo.cbSize = sizeof(COMBOBOXINFO); + if (GetComboBoxInfo(hWndScriptClass, &scriptClassInfo)) + { + //Allow horizontal scroll in the script combo box. + SetWindowLong(scriptClassInfo.hwndList, GWL_STYLE, GetWindowLong(scriptClassInfo.hwndList, GWL_STYLE) | WS_HSCROLL); + } + + ComboBox_AddString(hWndOptions, _T("Custom")); + ComboBox_AddString(hWndOptions, _T("MonoBehaviour")); + //Initialize the plugin list using an empty asset interface list (gather the supported plugins). + //InitPluginList(pMainWindow, hDlg, std::vector(), PluginAction_CREATE); + ComboBox_SetCurSel(hWndOptions, 0); + + ComboBox_ResetContent(hWndFileId); + int cbFileIdIdx = 0; + long long int pathId = 1; + auto &fileEntries = pThis->appContext.getMainWindow().getFileEntries(); + for (auto fileIt = fileEntries.begin(); fileIt != fileEntries.end(); ++fileIt) + { + if (fileIt->pContextInfo && + fileIt->pContextInfo->getFileContext() && + fileIt->pContextInfo->getFileContext()->getType() == FileContext_Assets) + { + char referenceFileIDTmp[32]; + sprintf_s(referenceFileIDTmp, "%u - ", fileIt->pContextInfo->getFileID()); + std::string targetName8 = std::string(referenceFileIDTmp) + fileIt->pContextInfo->getFileName(); + auto upTargetNameT = unique_MultiByteToTCHAR(targetName8.c_str()); + ComboBox_AddString(hWndFileId, upTargetNameT.get()); + ComboBox_SetItemData(hWndFileId, cbFileIdIdx, fileIt->pContextInfo->getFileID()); + if (cbFileIdIdx == 0) + { + AssetsFileContextInfo *pAssetsInfo = reinterpret_cast(fileIt->pContextInfo.get()); + pThis->EnumScriptIndices(pAssetsInfo, pThis->scriptDescriptors); + pathId = (long long int)pThis->getFreePathID(pAssetsInfo); + } + ++cbFileIdIdx; + if (cbFileIdIdx == INT_MAX) + break; + } + } + + ComboBox_ResetContent(hWndScriptClass); + int cbHorizExtent = 0; + for (size_t i = 0; i < pThis->scriptDescriptors.size(); ++i) + { + size_t descLenT = 0; + TCHAR *classDescT = _MultiByteToTCHAR(pThis->scriptDescriptors[i].scriptDescText.c_str(), descLenT); + int idx = ComboBox_AddString(hWndScriptClass, classDescT); + cbHorizExtent = std::max(cbHorizExtent, GetTextExtent(hWndScriptClass, classDescT)); + if (idx != CB_ERR) + ComboBox_SetItemData(hWndScriptClass, idx, i); + _FreeTCHAR(classDescT); + } + COMBOBOXINFO comboBoxInfo = {}; + comboBoxInfo.cbSize = sizeof(COMBOBOXINFO); + if (GetComboBoxInfo(hWndScriptClass, &comboBoxInfo)) + ListBox_SetHorizontalExtent(comboBoxInfo.hwndList, cbHorizExtent); + ComboBox_SetCurSel(hWndScriptClass, 0); + + ComboBox_SetCurSel(hWndFileId, 0); + + Edit_SetText(hWndClassId, TEXT("0")); + Edit_SetText(hWndMonoClassId, TEXT("-1")); + Button_SetCheck(hWndValidAsset, TRUE); + ShowWindow(hWndStaticScriptClass, SW_HIDE); + ShowWindow(hWndScriptClass, SW_HIDE); + + TCHAR numTmp[22]; + _stprintf_s(numTmp, TEXT("%lld"), pathId); + Edit_SetText(hWndPathId, numTmp); + } + return (INT_PTR)TRUE; + case WM_COMMAND: + wmId = LOWORD(wParam); + wmEvent = HIWORD(wParam); + switch (wmId) + { + case IDC_COMBOFILEID: + { + if (wmEvent == CBN_SELCHANGE) + { + int curSel = ComboBox_GetCurSel((HWND)lParam); + if (curSel != CB_ERR) + { + unsigned int fileId = (unsigned int)ComboBox_GetItemData((HWND)lParam, curSel); + HWND hWndOptions = GetDlgItem(hDlg, IDC_COMBOOPTIONLIST); + int selectedOption = ComboBox_GetCurSel(hWndOptions); + + HWND hWndFileId = GetDlgItem(hDlg, IDC_COMBOFILEID); + HWND hWndScriptClass = GetDlgItem(hDlg, IDC_COMBOSCRIPTCLASS); + HWND hWndMonoClassId = GetDlgItem(hDlg, IDC_EDITMONOCLASSID); + + ComboBox_ResetContent(hWndScriptClass); + FileContextInfo_ptr pContextInfo = pThis->appContext.getContextInfo(fileId); + if (pContextInfo != nullptr && + pContextInfo->getFileContext() && + pContextInfo->getFileContext()->getType() == FileContext_Assets) + { + AssetsFileContextInfo *pAssetsInfo = reinterpret_cast(pContextInfo.get()); + pThis->EnumScriptIndices(pAssetsInfo, pThis->scriptDescriptors); + int selIdx = ComboBox_SetCurSel(hWndScriptClass, 0); + if (selectedOption == 1 && selIdx != CB_ERR) + { + size_t listIdx = (size_t)ComboBox_GetItemData(hWndScriptClass, selIdx); + if (pThis->scriptDescriptors.size() > listIdx) + { + uint16_t newClassID = pThis->scriptDescriptors[listIdx].monoClassID; + TCHAR classIDBuf[8]; classIDBuf[7] = 0; + _stprintf_s(classIDBuf, TEXT("%u"), newClassID); + Edit_SetText(hWndMonoClassId, classIDBuf); + } + } + } + } + } + } + break; + case IDC_COMBOSCRIPTCLASS: + { + if (wmEvent == CBN_EDITCHANGE || wmEvent == CBN_EDITUPDATE) + { + //Show the dropdown list + DWORD rangePre = ComboBox_GetEditSel((HWND)lParam); + uint16_t selStartPre = LOWORD(rangePre); + + ComboBox_ShowDropdown((HWND)lParam, TRUE); + + DWORD rangePost = ComboBox_GetEditSel((HWND)lParam); + uint16_t selEndPost = HIWORD(rangePost); + ComboBox_SetEditSel((HWND)lParam, selStartPre, selEndPost); + + //Workaround, see https://stackoverflow.com/questions/1093067/why-combobox-hides-cursor-when-droppeddown-is-set + SendMessage((HWND)lParam, WM_SETCURSOR, 0, 0); + } + //if (wmEvent == CBN_SELCHANGE) + { + HWND hWndMonoClassId = GetDlgItem(hDlg, IDC_EDITMONOCLASSID); + HWND hWndOptions = GetDlgItem(hDlg, IDC_COMBOOPTIONLIST); + int selectedOption = ComboBox_GetCurSel(hWndOptions); + + if (selectedOption == 1) + { + unsigned int curScriptClass = (unsigned int)ComboBox_GetCurSel((HWND)lParam); + size_t listIdx = (curScriptClass != CB_ERR) ? (size_t)ComboBox_GetItemData((HWND)lParam, (int)curScriptClass) : (size_t)-1; + if (listIdx < pThis->scriptDescriptors.size()) + { + uint16_t newClassID = pThis->scriptDescriptors[listIdx].monoClassID; + TCHAR classIDBuf[8]; classIDBuf[7] = 0; + _stprintf_s(classIDBuf, TEXT("%u"), newClassID); + Edit_SetText(hWndMonoClassId, classIDBuf); + } + } + } + } + break; + case IDC_COMBOOPTIONLIST: + { + if (wmEvent == CBN_SELCHANGE) + { + HWND hWndStaticClassId = GetDlgItem(hDlg, IDC_STATICCLASS); + HWND hWndClassId = GetDlgItem(hDlg, IDC_EDITCLASSID); + HWND hWndMonoClassId = GetDlgItem(hDlg, IDC_EDITMONOCLASSID); + HWND hWndValidAsset = GetDlgItem(hDlg, IDC_CKVALIDASSET); + + HWND hWndStaticScriptClass = GetDlgItem(hDlg, IDC_STATICSCRIPTCLASS); + HWND hWndScriptClass = GetDlgItem(hDlg, IDC_COMBOSCRIPTCLASS); + + int curSel = ComboBox_GetCurSel((HWND)lParam); + EnableWindow(hWndClassId, (curSel <= 1) ? TRUE : FALSE); + EnableWindow(hWndMonoClassId, (curSel <= 1) ? TRUE : FALSE); + if (curSel == 1) Button_SetCheck(hWndValidAsset, BST_CHECKED); + EnableWindow(hWndValidAsset, (curSel < 1) ? TRUE : FALSE); + + ShowWindow(hWndStaticClassId, (curSel == 1) ? SW_HIDE : SW_SHOW); + ShowWindow(hWndClassId, (curSel == 1) ? SW_HIDE : SW_SHOW); + ShowWindow(hWndStaticScriptClass, (curSel == 1) ? SW_SHOW : SW_HIDE); + ShowWindow(hWndScriptClass, (curSel == 1) ? SW_SHOW : SW_HIDE); + + if (curSel == 1) + { + int curScriptClass = ComboBox_GetCurSel(hWndScriptClass); + size_t listIdx = (curScriptClass != CB_ERR) ? (size_t)ComboBox_GetItemData((HWND)lParam, (int)curScriptClass) : (size_t)-1; + if (listIdx < pThis->scriptDescriptors.size()) + { + uint16_t newClassID = pThis->scriptDescriptors[listIdx].monoClassID; + TCHAR classIDBuf[8]; classIDBuf[7] = 0; + _stprintf_s(classIDBuf, TEXT("%u"), newClassID); + Edit_SetText(hWndMonoClassId, classIDBuf); + } + } + else + Edit_SetText(hWndMonoClassId, TEXT("-1")); + } + } + break; + case IDOK: + { + TCHAR editText[100] = {0}; + wchar_t *editTextEnd = nullptr; + HWND hWndOptions = GetDlgItem(hDlg, IDC_COMBOOPTIONLIST); + HWND hWndScriptClass = GetDlgItem(hDlg, IDC_COMBOSCRIPTCLASS); + HWND hWndFileId = GetDlgItem(hDlg, IDC_COMBOFILEID); + HWND hWndPathId = GetDlgItem(hDlg, IDC_EDITPATHID); + HWND hWndClassId = GetDlgItem(hDlg, IDC_EDITCLASSID); + HWND hWndMonoClassId = GetDlgItem(hDlg, IDC_EDITMONOCLASSID); + HWND hWndValidAsset = GetDlgItem(hDlg, IDC_CKVALIDASSET); + + int selectedOption = ComboBox_GetCurSel(hWndOptions); + + int selectedFileIndex = ComboBox_GetCurSel(hWndFileId); + unsigned int fileId = (selectedFileIndex == CB_ERR) ? 0 : (unsigned int)ComboBox_GetItemData(hWndFileId, selectedFileIndex); + FileContextInfo_ptr pContextInfo = pThis->appContext.getContextInfo(fileId); + if (pContextInfo == nullptr + || pContextInfo->getFileContext() == nullptr + || pContextInfo->getFileContext()->getType() != FileContext_Assets) + { + MessageBox(hDlg, + TEXT("Unable to find the selected file!"), + TEXT("Asset Bundle Extractor"), MB_ICONERROR); + break; + } + AssetsFileContextInfo *pAssetsInfo = reinterpret_cast(pContextInfo.get()); + int monoScriptCBIdx = ComboBox_GetCurSel(hWndScriptClass); + size_t monoScriptIdx = (monoScriptCBIdx != CB_ERR) ? (size_t)ComboBox_GetItemData(hWndScriptClass, (int)monoScriptCBIdx) : (size_t)-1; + Edit_GetText(hWndPathId, editText, 100); + *_errno() = 0; + long long int pathId = _tcstoi64(editText, NULL, 0); + if (errno == ERANGE) + { + *_errno() = 0; + pathId = (long long int)_tcstoui64(editText, NULL, 0); + } + if (errno == ERANGE) + pathId = 0; + //long long int pathId = _tcstoi64(editText, NULL, 0); + Edit_GetText(hWndClassId, editText, 100); + *_errno() = 0; + int classId = _tcstol(editText, &editTextEnd, 0); + if (errno == ERANGE) + { + *_errno() = 0; + classId = (int)_tcstoul(editText, NULL, 0); + } + if (errno == ERANGE || editTextEnd == editText) + { + classId = 0; + size_t classnameLenMB = 0; + auto classnameMB = unique_TCHARToMultiByte(editText, classnameLenMB); + classId = pAssetsInfo->GetClassByName(classnameMB.get()); + if (classId < 0) //not found + classId = 0; + } + Edit_GetText(hWndMonoClassId, editText, 100); + *_errno() = 0; + int monoClassId = _tcstol(editText, NULL, 0); + if (errno == ERANGE) + { + *_errno() = 0; + monoClassId = (int)_tcstoul(editText, NULL, 0); + } + if (errno == ERANGE) + monoClassId = -1; + + if (selectedOption == 0 && classId < 0 && pAssetsInfo->getAssetsFileContext()->getAssetsFile()->header.format >= 0x10) + monoClassId = (-classId) - 1; //AssetsFileTable encodes this + if (selectedOption <= 1) + { + unsigned int relFileID_MonoScript = 0; + long long int pathID_MonoScript = 0; + Hash128 propertiesHash_MonoScript = {}; + if (selectedOption == 1) + { + if (monoScriptIdx < pThis->scriptDescriptors.size()) + { + relFileID_MonoScript = pThis->scriptDescriptors[monoScriptIdx].monoScriptFileIDRel; + pathID_MonoScript = pThis->scriptDescriptors[monoScriptIdx].monoScriptPathID; + propertiesHash_MonoScript = pThis->scriptDescriptors[monoScriptIdx].propertiesHash; + bool hasExtendedInfoAlready = pThis->scriptDescriptors[monoScriptIdx].extendedTypeInfoPresent; + + FileContextInfo_ptr pMonoScriptFile; + if (!hasExtendedInfoAlready + && (pMonoScriptFile = pThis->appContext.getContextInfo(pAssetsInfo->resolveRelativeFileID(relFileID_MonoScript))) != nullptr + && pMonoScriptFile->getFileContext() != nullptr + && pMonoScriptFile->getFileContext()->getType() == FileContext_Assets + && !reinterpret_cast(pMonoScriptFile.get())->hasAnyScriptDatabases()) + { + AssetsFileContextInfo *pMonoScriptAssetsFile = reinterpret_cast(pMonoScriptFile.get()); + switch (MessageBoxA(hDlg, addAsset_MonoBehavMessage, "Add Asset", MB_YESNO)) + { + case IDYES: + { + std::vector> filesToSearchScripts; + filesToSearchScripts.push_back(std::shared_ptr(pMonoScriptFile, pMonoScriptAssetsFile)); + GetAllScriptInformation(pThis->appContext, filesToSearchScripts); + } + break; + case IDNO: + break; + } + } + } + classId = -1 - (int)monoClassId; + } + bool hasReplacer = false; + if (Button_GetCheck(hWndValidAsset) == BST_CHECKED) + { + //Make a replacer with all fields set to 0. + AssetsEntryReplacer *pReplacer = MakeEmptyAssetReplacer( + pThis->appContext, std::shared_ptr(pContextInfo, pAssetsInfo), + pathId, classId, monoClassId, relFileID_MonoScript, pathID_MonoScript, propertiesHash_MonoScript); + if (pReplacer) + { + pAssetsInfo->addReplacer(std::shared_ptr(pReplacer, FreeAssetsReplacer), pThis->appContext); + hasReplacer = true; + } + } + } + //else if (fileId < dlg_AssetsFileListLen) + //{ + // //Set the asset interface. + // std::vector interfaces; + // interfaces.assign(1, CAssetInterface(pathId, fileId, "", 0, 0, 0, (uint16_t)-1, nullptr)); + // OverrideInterfaceList(pMainWindow, interfaces); + // //Run the plugin. + // RunPluginOption(pMainWindow, hDlg); + //} + } + case IDCANCEL: + EndDialog(hDlg, LOWORD(wParam)); + return (INT_PTR)TRUE; + } + break; + } + return (INT_PTR)FALSE; +} \ No newline at end of file diff --git a/UABE_Win32/AddAssetDialog.h b/UABE_Win32/AddAssetDialog.h new file mode 100644 index 0000000..3a294dc --- /dev/null +++ b/UABE_Win32/AddAssetDialog.h @@ -0,0 +1,40 @@ +#pragma once +#include "Win32AppContext.h" +#include "AssetListDialog.h" + + +class AddAssetDialog +{ + struct ScriptIdxDescriptor + { + long long int monoScriptPathID; + unsigned int monoScriptFileIDRel; + uint16_t monoClassID; bool isNewClassID; + Hash128 propertiesHash; //Assuming properties hash == 0 => need to calculate + std::string scriptDescText; + bool extendedTypeInfoPresent; //Set to true if the full type information for monoClassID is already present. + }; + + std::vector scriptDescriptors; + Win32AppContext &appContext; +public: + inline AddAssetDialog(Win32AppContext &appContext) + : appContext(appContext) + {} + void open(); +private: + static INT_PTR CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); + + static int GetTextExtent(HWND hComboBox, const TCHAR *text); + + unsigned int findRelFileID(AssetsFileContextInfo *pSourceFile, unsigned int targetFileID); + void EnumScriptIndices_HandleMonoScript(AssetsFileContextInfo *pSourceFile, unsigned int scriptFileRefIdx, + long long int scriptPathID, std::vector &descriptors); + void EnumScriptIndices_HandleMonoBehaviour(AssetsFileContextInfo *pSourceFile, + AssetIdentifier &behaviourAsset, + AssetTypeTemplateField *pBehaviourBase, std::vector &descriptors); + + bool EnumScriptIndices(AssetsFileContextInfo *pFile, std::vector &descriptors); + + pathid_t getFreePathID(AssetsFileContextInfo *pFile); +}; diff --git a/UABE_Win32/AssetBundleExtractor.rc b/UABE_Win32/AssetBundleExtractor.rc new file mode 100644 index 0000000000000000000000000000000000000000..b39fa948737482103d3767740abbc371629953bf GIT binary patch literal 67382 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 Oae{M=hje@~*Z&7z?8H0( literal 0 HcmV?d00001 diff --git a/UABE_Win32/AssetDependDialog.cpp b/UABE_Win32/AssetDependDialog.cpp new file mode 100644 index 0000000..05efd29 --- /dev/null +++ b/UABE_Win32/AssetDependDialog.cpp @@ -0,0 +1,710 @@ +#include "stdafx.h" +#include "resource.h" +#include "MainWindow2.h" +#include "AssetDependDialog.h" +#include "../libStringConverter/convert.h" +#include + +/* +TODO +- Add/Remove dependencies - should be possible with the existing AssetsFileContextInfo and replacers infrastructure. +*/ + +AssetDependDialog::~AssetDependDialog() +{ + pContext->getMainWindow().unregisterEventHandler(eventHandlerHandle); +} +AssetDependDialog::AssetDependDialog(class Win32AppContext *pContext, HWND hParentWnd) + : pContext(pContext), hParentWnd(hParentWnd), hDialog(NULL), + hCurEditPopup(NULL), iEditPopupItem(0), iEditPopupSubItem(0), + pCurFileEntry(nullptr) +{ + eventHandlerHandle = pContext->getMainWindow().registerEventHandler(this); +} + +void AssetDependDialog::addFileContext(const std::pair &fileContext) +{ + FileContextInfo *pContextInfo = fileContext.first->getContextInfoPtr(); + if (AssetsFileContextInfo *pAssetsInfo = dynamic_cast(pContextInfo)) + { + unsigned int fileID = pAssetsInfo->getFileID(); + auto entryIt = fileEntries.find(fileID); + assert(entryIt == fileEntries.end()); + if (entryIt == fileEntries.end()) + { + fileEntries.insert(std::make_pair(fileID, fileContext.first)); + if (pCurFileEntry == nullptr || pCurFileEntry->getContextInfoPtr() == nullptr + || pAssetsInfo->getFileID() < pCurFileEntry->getContextInfoPtr()->getFileID()) + { + pCurFileEntry = fileContext.first; + onUpdateCurrentFile(); + } + } + } + +} +void AssetDependDialog::removeFileContext(FileEntryUIInfo *pContext) +{ + FileContextInfo *pContextInfo = pContext->getContextInfoPtr(); + if (AssetsFileContextInfo *pAssetsInfo = dynamic_cast(pContextInfo)) + { + unsigned int fileID = pAssetsInfo->getFileID(); + auto entryIt = fileEntries.find(fileID); + assert(entryIt != fileEntries.end()); + assert(entryIt->second == pContext); + if (entryIt != fileEntries.end()) + fileEntries.erase(entryIt); + } + else + { + for (auto it = fileEntries.begin(); it != fileEntries.end(); ++it) + { + if (it->second == pContext) + { + fileEntries.erase(it); + break; + } + } + } + if (pContext == pCurFileEntry) + { + if (!fileEntries.empty()) + pCurFileEntry = fileEntries.begin()->second; + else + pCurFileEntry = nullptr; + onUpdateCurrentFile(); + } +} +EFileManipulateDialogType AssetDependDialog::getType() +{ + return FileManipulateDialog_AssetsDependencies; +} +HWND AssetDependDialog::getWindowHandle() +{ + return hDialog; +} +void AssetDependDialog::onHotkey(ULONG message, DWORD keyCode) //message : currently only WM_KEYDOWN; keyCode : VK_F3 for instance +{ + +} +bool AssetDependDialog::onCommand(WPARAM wParam, LPARAM lParam) //Called for unhandled WM_COMMAND messages. Returns true if this dialog has handled the request, false otherwise. +{ + return false; +} +void AssetDependDialog::onShow() +{ + if (!this->hDialog) + { + this->hDialog = CreateDialogParam(pContext->getMainWindow().getHInstance(), MAKEINTRESOURCE(IDD_ASSETSDEPEND2), hParentWnd, AssetDependProc, (LPARAM)this); + onUpdateCurrentFile(); + } +} +void AssetDependDialog::onHide() +{ + if (this->hDialog) + { + if (this->hCurEditPopup != NULL) + doCloseEditPopup(); + SendMessage(this->hDialog, WM_CLOSE, 0, 0); + } +} +bool AssetDependDialog::hasUnappliedChanges(bool *applyable) +{ + return false; +} +bool AssetDependDialog::applyChanges() +{ + return true; +} +bool AssetDependDialog::doesPreferNoAutoclose() +{ + return false; +} + +void AssetDependDialog::list_updateAssetPath(HWND hList, int iItem, const AssetsFileDependency *pDependency) +{ + auto upText = unique_MultiByteToTCHAR(pDependency->assetPath); + LVITEM item; + item.mask = LVIF_TEXT; + item.pszText = upText.get(); + item.cchTextMax = 256; + item.iItem = iItem; + item.iSubItem = 1; + ListView_SetItem(hList, &item); +} +void AssetDependDialog::list_updateTargetAsset(HWND hList, int iItem, unsigned int reference) +{ + LVITEM item; + item.mask = LVIF_TEXT; + item.cchTextMax = 256; + item.pszText = const_cast(TEXT("None")); + TCHAR *resolvedToText = nullptr; + if (reference != 0) + { + if (FileContextInfo_ptr pReferencedInfo = this->pContext->getContextInfo(reference)) + { + char referenceFileIDTmp[32]; + sprintf_s(referenceFileIDTmp, "%u - ", reference); + std::string resolvedToText8 = std::string(referenceFileIDTmp) + pReferencedInfo->getFileName(); + + size_t tmp; + resolvedToText = _MultiByteToTCHAR(resolvedToText8.c_str(), tmp); + item.pszText = resolvedToText; + } + } + item.iItem = iItem; + item.iSubItem = 2; + ListView_SetItem(hList, &item); + if (resolvedToText) + _FreeTCHAR(resolvedToText); +} + +void AssetDependDialog::onUpdateCurrentFile() +{ + HWND hList = GetDlgItem(hDialog, IDC_DEPENDLIST); + ListView_DeleteAllItems(hList); + if (pCurFileEntry != nullptr && pCurFileEntry->getContextInfoPtr() != nullptr) + { + auto* pContextInfo = static_cast(pCurFileEntry->getContextInfoPtr()); + auto refLock = pContextInfo->lockReferencesRead(); + const std::vector &references = pContextInfo->getReferencesRead(refLock); + const std::vector &dependencies = pContextInfo->getDependenciesRead(refLock); + + int listViewCount = 0; + for (size_t i = 0; i < dependencies.size(); i++) + { + const AssetsFileDependency &dependency = dependencies[i]; + if (dependency.type != 0) + continue; + TCHAR fileIDTmp[32]; + _stprintf_s(fileIDTmp, TEXT("%u"), i + 1); + LVITEM item; + item.mask = LVIF_TEXT | LVIF_PARAM; + item.lParam = (LPARAM)i; + item.iItem = listViewCount; + item.iSubItem = 0; + item.pszText = fileIDTmp; + ListView_InsertItem(hList, &item); + + list_updateAssetPath(hList, listViewCount, &dependency); + + list_updateTargetAsset(hList, listViewCount, references[i]); + + assert(listViewCount < INT_MAX); + listViewCount++; + } + } +} + +inline void doMoveWindow(HDWP &deferCtx, bool &retry, HWND hWnd, int x, int y, int w, int h) +{ + if (deferCtx) + { + deferCtx = DeferWindowPos(deferCtx, hWnd, HWND_TOP, x, y, w, h, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); + if (!deferCtx) + retry = true; + } + else + SetWindowPos(hWnd, HWND_TOP, x, y, w, h, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); +} +static void onResize(HWND hDlg, bool defer = true) +{ + //Add/Remove is not implemented + ShowWindow(GetDlgItem(hDlg, IDC_BTNADD), SW_HIDE); + ShowWindow(GetDlgItem(hDlg, IDC_BTNREMOVE), SW_HIDE); + + HDWP deferCtx = defer ? BeginDeferWindowPos(12) : NULL; + bool retry = false; + + RECT client = {}; + GetClientRect(hDlg, &client); + LONG clientWidth = client.right-client.left; + LONG clientHeight = client.bottom-client.top; + LONG x = 19; + LONG w = clientWidth - 16; + + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_STATICTITLE), x + 2, 10, w - 4, 15); + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_DEPENDLIST), x, 30, w - 15, clientHeight - 60 - 7); + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_BTNADD), x + 5, clientHeight - 30, 125, 25); + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_BTNREMOVE), x + 5 + 145, clientHeight - 30, 125, 25); + + if (defer) + { + if (retry || !EndDeferWindowPos(deferCtx)) + onResize(hDlg, false); + else + UpdateWindow(hDlg); + deferCtx = NULL; + } + else + UpdateWindow(hDlg); + +} + +void AssetDependDialog::doCloseEditPopup(bool applyChanges) +{ + if (applyChanges) + { + if (pCurFileEntry == nullptr || pCurFileEntry->getContextInfoPtr() == nullptr) + { + doCloseEditPopup(false); + return; + } + AssetsFileContextInfo *pAssetsFileInfo = static_cast(pCurFileEntry->getContextInfoPtr()); + + HWND hList = GetDlgItem(hDialog, IDC_DEPENDLIST); + LVITEM item; + item.mask = LVIF_PARAM; + item.lParam = (LPARAM)-1; + item.iItem = iEditPopupItem; + item.iSubItem = 0; + ListView_GetItem(hList, &item); + + size_t dependencyIdx = item.lParam; + auto refLock = pAssetsFileInfo->lockReferencesWrite(); + std::vector &references = pAssetsFileInfo->getReferencesWrite(refLock); + std::vector& dependencies = pAssetsFileInfo->getDependenciesWrite(refLock); + + assert(dependencyIdx < dependencies.size()); + assert(dependencies.size() == references.size()); + if (dependencyIdx >= dependencies.size() + || dependencyIdx >= references.size()) + { + refLock.unlock(); + doCloseEditPopup(false); + return; + } + + switch (iEditPopupSubItem) + { + case 1: //Dependency name/text + { + int nameLen = Edit_GetTextLength(hCurEditPopup); + if (nameLen > 0 && nameLen < INT_MAX - 2) + { + std::unique_ptr nameT(new TCHAR[nameLen + 2]); + nameT[0] = 0; + nameT[nameLen + 1] = 0; + Edit_GetText(hCurEditPopup, nameT.get(), nameLen + 1); + size_t name8Len = 0; + auto name8 = unique_TCHARToMultiByte(nameT.get(), name8Len); + AssetsFileDependency *pDependencyEntry = &dependencies[dependencyIdx]; + if (name8Len < sizeof(pDependencyEntry->assetPath) / sizeof(char)) + { + memcpy_s(pDependencyEntry->assetPath, sizeof(pDependencyEntry->assetPath), + name8.get(), (name8Len + 1) * sizeof(char)); + pAssetsFileInfo->setDependenciesChanged(); + list_updateAssetPath(hList, iEditPopupItem, pDependencyEntry); + } + } + refLock.unlock(); + pContext->OnUpdateDependencies(pAssetsFileInfo, dependencyIdx, dependencyIdx); + } + break; + case 2: //Dependency target + { + int iSelection = ComboBox_GetCurSel(hCurEditPopup); + if (iSelection < 0) + break; + unsigned int newReferenceTarget = 0; + if (iSelection == 0) + newReferenceTarget = 0; + else + { + size_t iComboBoxItem = 1; + auto &fileEntries = pContext->getMainWindow().getFileEntries(); + //Slightly janky. A mapping from combo box index to file ID would be better. + for (auto fileIt = fileEntries.begin(); fileIt != fileEntries.end(); ++fileIt) + { + if (fileIt->pContextInfo != pCurFileEntry->pContextInfo && + fileIt->pContextInfo && + fileIt->pContextInfo->getFileContext() && + fileIt->pContextInfo->getFileContext()->getType() == FileContext_Assets) + { + if (iComboBoxItem == iSelection) + { + newReferenceTarget = fileIt->pContextInfo->getFileID(); + break; + } + iComboBoxItem++; + } + } + } + unsigned int prevReferenceTarget = references[dependencyIdx]; + references[dependencyIdx] = newReferenceTarget; + + refLock.unlock(); + + if (prevReferenceTarget != 0 && newReferenceTarget != prevReferenceTarget) + { + //Remove the containerSources entry in the previously referenced AssetsFileContextInfo, if it exists. + FileContextInfo_ptr pOldTargetFileInfo = pContext->getContextInfo(prevReferenceTarget); + //May be null (if the referenced file was closed). + if (pOldTargetFileInfo && + pOldTargetFileInfo->getFileContext() && + pOldTargetFileInfo->getFileContext()->getType() == FileContext_Assets) + { + AssetsFileContextInfo *pTargetAssetsInfo = static_cast(pOldTargetFileInfo.get()); + std::vector &containerSources = pTargetAssetsInfo->getContainerSources(); + for (size_t i = 0; i < containerSources.size(); i++) + { + if (containerSources[i] == pAssetsFileInfo->getFileID()) + { + containerSources.erase(containerSources.begin() + i); + pContext->OnUpdateContainers(pTargetAssetsInfo); + break; + } + } + } + } + bool hasContainers; + { + AssetContainerList &containers = pAssetsFileInfo->lockContainersRead(); + hasContainers = (containers.getContainerCount() > 0); + pAssetsFileInfo->unlockContainersRead(); + } + if (newReferenceTarget != 0 && hasContainers) + { + //Add a containerSources entry in the newly referenced AssetsFileContextInfo, if there is a need for it. + FileContextInfo_ptr pNewTargetFileInfo = pContext->getContextInfo(newReferenceTarget); + assert(pNewTargetFileInfo && + pNewTargetFileInfo->getFileContext() && + pNewTargetFileInfo->getFileContext()->getType() == FileContext_Assets); + if (pNewTargetFileInfo && + pNewTargetFileInfo->getFileContext() && + pNewTargetFileInfo->getFileContext()->getType() == FileContext_Assets) + { + AssetsFileContextInfo *pTargetAssetsInfo = static_cast(pNewTargetFileInfo.get()); + std::vector &containerSources = pTargetAssetsInfo->getContainerSources(); + bool alreadyExists = false; + for (size_t i = 0; i < containerSources.size(); i++) + { + if (containerSources[i] == pAssetsFileInfo->getFileID()) + { + alreadyExists = true; + break; + } + } + if (!alreadyExists) + { + containerSources.push_back(pAssetsFileInfo->getFileID()); + pContext->OnUpdateContainers(pTargetAssetsInfo); + } + } + } + list_updateTargetAsset(hList, iEditPopupItem, newReferenceTarget); + pContext->OnUpdateDependencies(pAssetsFileInfo, dependencyIdx, dependencyIdx); + } + break; + default: + assert(false); + } + } + DestroyWindow(hCurEditPopup); + hCurEditPopup = NULL; + iEditPopupItem = iEditPopupSubItem = 0; +} + +void AssetDependDialog::onOpenEditPopup() +{ + if (pCurFileEntry == nullptr || pCurFileEntry->getContextInfoPtr() == nullptr) + { + doCloseEditPopup(false); + return; + } + auto pContextInfo = static_cast(pCurFileEntry->getContextInfoPtr()); + auto refLock = pContextInfo->lockReferencesRead(); + const std::vector& references = pContextInfo->getReferencesRead(refLock); + const std::vector& dependencies = pContextInfo->getDependenciesRead(refLock); + + HWND hList = GetDlgItem(hDialog, IDC_DEPENDLIST); + LVITEM item; + item.mask = LVIF_PARAM; + item.lParam = (LPARAM)-1; + item.iItem = iEditPopupItem; + item.iSubItem = 0; + ListView_GetItem(hList, &item); + + size_t dependencyIdx = item.lParam; + assert(dependencyIdx < dependencies.size()); + assert(dependencies.size() == references.size()); + if (dependencyIdx >= dependencies.size() + || dependencyIdx >= references.size()) + { + doCloseEditPopup(false); + return; + } + + switch (iEditPopupSubItem) + { + case 1: //Dependency name/text + { + auto pText = unique_MultiByteToTCHAR(dependencies[dependencyIdx].assetPath); + Edit_SetText(hCurEditPopup, pText.get()); + return; + } + break; + case 2: //Dependency target + { + ComboBox_AddString(hCurEditPopup, TEXT("None")); + size_t iComboBoxItem = 1; + size_t iSelComboBoxItem = 0; + auto &fileEntries = pContext->getMainWindow().getFileEntries(); + for (auto fileIt = fileEntries.begin(); fileIt != fileEntries.end(); ++fileIt) + { + //Do not list the current .assets file in the combo box. However, circular references are allowed. + if (fileIt->pContextInfo != pCurFileEntry->pContextInfo && + fileIt->pContextInfo && + fileIt->pContextInfo->getFileContext() && + fileIt->pContextInfo->getFileContext()->getType() == FileContext_Assets) + { + if (fileIt->pContextInfo->getFileID() == references[dependencyIdx]) + iSelComboBoxItem = iComboBoxItem; //Mark the currently set dependency as the selection of the combo box. + char referenceFileIDTmp[32]; + sprintf_s(referenceFileIDTmp, "%u - ", fileIt->pContextInfo->getFileID()); + std::string targetName8 = std::string(referenceFileIDTmp) + fileIt->pContextInfo->getFileName(); + auto upTargetNameT = unique_MultiByteToTCHAR(targetName8.c_str()); + ComboBox_AddString(hCurEditPopup, upTargetNameT.get()); + iComboBoxItem++; + } + } + ComboBox_SetCurSel(hCurEditPopup, iSelComboBoxItem); + return; + } + break; + default: + assert(false); + } + //Failure + doCloseEditPopup(false); +} + + +INT_PTR CALLBACK AssetDependDialog::AssetDependProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + INT_PTR ret = (INT_PTR)FALSE; + AssetDependDialog *pThis = (AssetDependDialog*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + switch (message) + { + case WM_DESTROY: + break; + case WM_NCDESTROY: + break; + case WM_CLOSE: + if (pThis) + pThis->hDialog = NULL; + DestroyWindow(hDlg); + ret = (INT_PTR)TRUE; + break; + case WM_INITDIALOG: + { + SetWindowLongPtr(hDlg, GWLP_USERDATA, lParam); + pThis = (AssetDependDialog*)lParam; + + //List control columns : Rel File ID; Dependency path; Chosen target file ID (for UABE) + HWND hList = GetDlgItem(hDlg, IDC_DEPENDLIST); + //Subclass the ListView to support item edit popups (via double click). + SetWindowSubclass(hList, AssetDependListViewProc, 0, reinterpret_cast(pThis)); + ListView_SetExtendedListViewStyle(hList, LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER); + LVCOLUMN column; + ZeroMemory(&column, sizeof(LVCOLUMN)); + column.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM; + column.cx = 70; + column.pszText = const_cast(TEXT("Rel. File ID")); + column.iSubItem = 0; + ListView_InsertColumn(hList, 0, &column); + column.cx = 250; + column.pszText = const_cast(TEXT("File Path")); + //column.iSubItem = 1; + ListView_InsertColumn(hList, 1, &column); + column.cx = 250; + column.pszText = const_cast(TEXT("Resolved to")); + //column.iSubItem = 2; + ListView_InsertColumn(hList, 2, &column); + + ShowWindow(hDlg, SW_SHOW); + PostMessage(hDlg, WM_SIZE, 0, 0); + ret = (INT_PTR)TRUE; + } + break; + case WM_SIZE: + onResize(hDlg); + break; + } + return ret; +} +LRESULT CALLBACK AssetDependDialog::AssetDependListViewProc(HWND hWnd, UINT message, + WPARAM wParam, LPARAM lParam, + uintptr_t uIdSubclass, DWORD_PTR dwRefData) +{ + AssetDependDialog *pThis = (AssetDependDialog*)dwRefData; + switch (message) + { + case WM_LBUTTONDBLCLK: + { + LVHITTESTINFO hitTestInfo = {}; + hitTestInfo.pt.x = GET_X_LPARAM(lParam); + hitTestInfo.pt.y = GET_Y_LPARAM(lParam); + if (ListView_SubItemHitTest(hWnd, &hitTestInfo) != -1 && hitTestInfo.iSubItem >= 1) + { + if (pThis->hCurEditPopup != NULL) + pThis->doCloseEditPopup(); + pThis->iEditPopupItem = hitTestInfo.iItem; + pThis->iEditPopupSubItem = hitTestInfo.iSubItem; + + RECT targetRect = {}; + ListView_GetSubItemRect(hWnd, hitTestInfo.iItem, hitTestInfo.iSubItem, LVIR_BOUNDS, &targetRect); + if (hitTestInfo.iSubItem == 2) + { + pThis->hCurEditPopup = + CreateWindow(WC_COMBOBOX, TEXT(""), CBS_DROPDOWNLIST | WS_VSCROLL | WS_CHILD | WS_VISIBLE, + targetRect.left, targetRect.top, targetRect.right - targetRect.left, targetRect.bottom - targetRect.top, + hWnd, GetMenu(hWnd), pThis->pContext->getMainWindow().getHInstance(), NULL); + } + else + { + pThis->hCurEditPopup = + CreateWindow(WC_EDIT, TEXT(""), ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE, + targetRect.left, targetRect.top, targetRect.right - targetRect.left, targetRect.bottom - targetRect.top, + hWnd, GetMenu(hWnd), pThis->pContext->getMainWindow().getHInstance(), NULL); + SendMessage(pThis->hCurEditPopup, EM_SETLIMITTEXT, sizeof(((AssetsFileDependency*)nullptr)->assetPath) / sizeof(char) - 1, 0); + } + SetWindowSubclass(pThis->hCurEditPopup, EditPopupProc, 0, reinterpret_cast(pThis)); + SendMessage(pThis->hCurEditPopup, WM_SETFONT, (WPARAM)(HFONT)SendMessage(hWnd, WM_GETFONT, 0, 0), FALSE); + SetFocus(pThis->hCurEditPopup); + pThis->onOpenEditPopup(); + } + } + break; + case WM_NCDESTROY: + RemoveWindowSubclass(hWnd, AssetDependListViewProc, uIdSubclass); + break; + } + return DefSubclassProc(hWnd, message, wParam, lParam); +} +LRESULT CALLBACK AssetDependDialog::EditPopupProc(HWND hWnd, UINT message, + WPARAM wParam, LPARAM lParam, + uintptr_t uIdSubclass, DWORD_PTR dwRefData) +{ + AssetDependDialog *pThis = (AssetDependDialog*)dwRefData; + switch (message) + { + case WM_KILLFOCUS: + //if (wParam == WA_INACTIVE) + { + if (pThis->hCurEditPopup != NULL) + pThis->doCloseEditPopup(); + } + break; + case WM_KEYDOWN: + if (LOWORD(wParam) == VK_ESCAPE || (pThis->iEditPopupSubItem != 2 && LOWORD(wParam) == VK_RETURN)) + { + if (pThis->hCurEditPopup != NULL) + pThis->doCloseEditPopup(); + } + break; + case WM_NCDESTROY: + RemoveWindowSubclass(hWnd, EditPopupProc, uIdSubclass); + break; + } + return DefSubclassProc(hWnd, message, wParam, lParam); +} + +void AssetDependDialog::onUpdateDependencies(AssetsFileContextInfo * pContextInfo, size_t from, size_t to) +{ + if (this->hDialog == NULL || this->pCurFileEntry == nullptr || pContextInfo != this->pCurFileEntry->getContextInfoPtr() + || pContextInfo == nullptr || from > UINT32_MAX) + return; + HWND hList = GetDlgItem(hDialog, IDC_DEPENDLIST); + auto refLock = pContextInfo->lockReferencesRead(); + const std::vector &references = pContextInfo->getReferencesRead(refLock); + const std::vector &dependencies = pContextInfo->getDependenciesRead(refLock); + + int nListItems = ListView_GetItemCount(hList); + + //Retrieve a list of all dependencies in range that are already in the list. + int iFirstAffectedItem = -1; + std::vector dependenciesInRange; + for (int i = 0; i < nListItems; i++) + { + LVITEM item; + item.mask = LVIF_PARAM; + item.lParam = (LPARAM)-1; + item.iItem = i; + item.iSubItem = 0; + ListView_GetItem(hList, &item); + if ((size_t)item.lParam > to) + break; + else if ((size_t)item.lParam >= from) + { + dependenciesInRange.push_back(item.lParam); + if (iFirstAffectedItem == -1) + iFirstAffectedItem = i; + } + } + bool requiresListRebuild = false; + { + //Check whether the dependencies that would now be put into the list already existed before. + //If not, a rebuild of the list will be required. + size_t iChangedItem = 0; + for (size_t i = from; i <= to && i < dependencies.size(); i++) + { + const AssetsFileDependency &dependency = dependencies[i]; + if (dependency.type != 0) + continue; + if (iChangedItem >= dependenciesInRange.size() + || dependenciesInRange[iChangedItem++] != i) + { + requiresListRebuild = true; + break; + } + } + if (iChangedItem != dependenciesInRange.size()) + requiresListRebuild = true; + } + if (this->hCurEditPopup != NULL) + { + //Retrieve the dependency index for the edit popup. + LVITEM item; + item.mask = LVIF_PARAM; + item.lParam = (LPARAM)-1; + item.iItem = iEditPopupItem; + item.iSubItem = 0; + ListView_GetItem(hList, &item); + + size_t dependencyIdx = item.lParam; + if (dependencyIdx >= from && (requiresListRebuild || dependencyIdx <= to)) + { + //Reset the popup since the contents to be edited may have changed (if !requiresListRebuild) + // or the list indices have shuffled (if requiresListRebuild). + doCloseEditPopup(false); + } + } + if (requiresListRebuild) + { + //Regenerate the whole list for simplicity. + onUpdateCurrentFile(); + } + else + { + //Update the asset paths and selected targets only, since no dependencies were added/removed. + //This is essential so the UI keeps being usable while dependencies are loading. + int iItem = iFirstAffectedItem; + for (DWORD i = static_cast(from); i <= to && i < dependencies.size(); i++) + { + const AssetsFileDependency &dependency = dependencies[i]; + if (dependency.type != 0) + continue; + + list_updateAssetPath(hList, iItem, &dependency); + + list_updateTargetAsset(hList, iItem, references[i]); + + assert(iItem < INT_MAX); + iItem++; + } + } +} + +void AssetDependDialog::onUpdateContainers(AssetsFileContextInfo *pFile) {} +void AssetDependDialog::onChangeAsset(AssetsFileContextInfo *pFile, pathid_t pathID, bool wasRemoved) {} \ No newline at end of file diff --git a/UABE_Win32/AssetDependDialog.h b/UABE_Win32/AssetDependDialog.h new file mode 100644 index 0000000..1f34557 --- /dev/null +++ b/UABE_Win32/AssetDependDialog.h @@ -0,0 +1,56 @@ +#pragma once +#include "MainWindow2.h" +#include "Win32AppContext.h" +#include + + +class AssetDependDialog : public IFileManipulateDialog, public MainWindowEventHandler +{ + MainWindowEventHandlerHandle eventHandlerHandle; + class Win32AppContext *pContext; + HWND hDialog; + HWND hParentWnd; + + HWND hCurEditPopup; + int iEditPopupItem; + int iEditPopupSubItem; + + FileEntryUIInfo *pCurFileEntry; + std::map fileEntries; +protected: + static INT_PTR CALLBACK AssetDependProc(HWND hWnd, UINT message, + WPARAM wParam, LPARAM lParam); + static LRESULT CALLBACK AssetDependListViewProc(HWND hWnd, UINT uMsg, + WPARAM wParam, LPARAM lParam, + uintptr_t uIdSubclass, DWORD_PTR dwRefData); + static LRESULT CALLBACK EditPopupProc(HWND hWnd, UINT uMsg, + WPARAM wParam, LPARAM lParam, + uintptr_t uIdSubclass, DWORD_PTR dwRefData); + + void list_updateAssetPath(HWND hList, int iItem, const AssetsFileDependency *pDependency); + void list_updateTargetAsset(HWND hList, int iItem, unsigned int reference); + void onUpdateCurrentFile(); + void doCloseEditPopup(bool applyChanges = true); + void onOpenEditPopup(); + +public: + //IFileManipulateDialog + AssetDependDialog(class Win32AppContext *pContext, HWND hParentWnd); + ~AssetDependDialog(); + void addFileContext(const std::pair &context); + void removeFileContext(FileEntryUIInfo *pContext); + EFileManipulateDialogType getType(); + HWND getWindowHandle(); + void onHotkey(ULONG message, DWORD keyCode); //message : currently only WM_KEYDOWN; keyCode : VK_F3 for instance + bool onCommand(WPARAM wParam, LPARAM lParam); //Called for unhandled WM_COMMAND messages. Returns true if this dialog has handled the request, false otherwise. + void onShow(); + void onHide(); + bool hasUnappliedChanges(bool *applyable=nullptr); + bool applyChanges(); + bool doesPreferNoAutoclose(); + + //MainWindowEventHandler + void onUpdateContainers(AssetsFileContextInfo *pFile); + void onChangeAsset(AssetsFileContextInfo *pFile, pathid_t pathID, bool wasRemoved); + void onUpdateDependencies(AssetsFileContextInfo *info, size_t from, size_t to); +}; \ No newline at end of file diff --git a/UABE_Win32/AssetListDialog.cpp b/UABE_Win32/AssetListDialog.cpp new file mode 100644 index 0000000..9bbeaf2 --- /dev/null +++ b/UABE_Win32/AssetListDialog.cpp @@ -0,0 +1,2764 @@ +#include "stdafx.h" +#include "resource.h" +#include "MainWindow2.h" +#include "Win32AppContext.h" +#include "AssetListDialog.h" +#include "AssetViewModifyDialog.h" +#include "FileDialog.h" +#include "ProgressDialog.h" +#include +#include +#include +#include "Win32PluginManager.h" +#include "../libStringConverter/convert.h" +#include //Note: For compiler support troubles, check out fmtlib https://github.com/fmtlib/fmt as a drop-in replacement. + +AssetModifyDialog::AssetModifyDialog() +{} +AssetModifyDialog::~AssetModifyDialog() +{} + +AssetListDialog::FileEntryCache::FileEntryCache(FileEntryUIInfo *pUIInfo, Win32AppContext *pContext) + : pContext(pContext), pUIInfo(pUIInfo), nUsers(0), lastUseTime(0) +{ + pContext->getMainWindow().addDisposableCacheElement(pUIInfo, this); + eventHandlerHandle = pContext->getMainWindow().registerEventHandler(this); +} +AssetListDialog::FileEntryCache::~FileEntryCache() +{ + pContext->getMainWindow().unregisterEventHandler(eventHandlerHandle); +} +size_t AssetListDialog::FileEntryCache::approxMemory() +{ + //Roughly approximate the memory usage. + size_t i = 0; + size_t ret = 0; + //Retrieve the size of the first four cache elements (chosen 'randomly' by iterating through the unordered map). + for (auto cacheIt = assetCache.begin(); cacheIt != assetCache.end(); ++cacheIt) + { + if (++i == 4) + break; + ret += cacheIt->second.getSize(); + } + ret *= ((assetCache.size() + 3) & ~size_t(3)) / 4; + ret += sizeof(FileEntryCache); + return ret; +} +time_t AssetListDialog::FileEntryCache::getLastUseTime() +{ + return lastUseTime; +} +bool AssetListDialog::FileEntryCache::isInUse() +{ + return nUsers > 0; +} +void AssetListDialog::FileEntryCache::onUpdateContainers(AssetsFileContextInfo *pFile) +{ + if (pFile == pUIInfo->getContextInfoPtr()) + { + //Invalidate all cache entries. + std::unordered_map tmp; + assetCache.swap(tmp); + } +} +void AssetListDialog::FileEntryCache::onChangeAsset(AssetsFileContextInfo *pFile, pathid_t pathID, bool wasRemoved) +{ + FileContextInfo *pContextInfo = pUIInfo->getContextInfoPtr(); + if (pContextInfo == nullptr) + { + //Any cached element is invalid + std::unordered_map tmp; + assetCache.swap(tmp); + } + else if (pFile->getFileID() == pContextInfo->getFileID()) + { + auto cacheIt = assetCache.find(pathID); + if (cacheIt != assetCache.end()) + { + assetCache.erase(cacheIt); //Cache entry needs an update + } + } +} + +AssetListDialog::~AssetListDialog() +{ + pContext->getMainWindow().unregisterEventHandler(eventHandlerHandle); +} +AssetListDialog::AssetListDialog(class Win32AppContext *pContext, HWND hParentWnd) + : pContext(pContext), hParentWnd(hParentWnd), hDialog(NULL), + cachedListEntryCount(0), cachedListEntryStartIdx(0), maxEntriesPerTick(100), ticksUntilCacheFreqUpdate(0) +{ + eventHandlerHandle = pContext->getMainWindow().registerEventHandler(this); + sorted = false; + sortOrderAscending = false; + iSortColumn = 0; + iFocusedItem = -1; + iLastTopItem = -1; + qpfrequency.QuadPart = 1; +} +EFileManipulateDialogType AssetListDialog::getType() +{ + return FileManipulateDialog_AssetList; +} +void AssetListDialog::addFileContext(const std::pair &fileContext) +{ + FileContextInfo *pContextInfo = fileContext.first->getContextInfoPtr(); + if (AssetsFileContextInfo *pAssetsInfo = dynamic_cast(pContextInfo)) + { + unsigned int fileID = pAssetsInfo->getFileID(); + auto entryIt = fileEntries.find(fileID); + assert(entryIt == fileEntries.end()); + if (entryIt == fileEntries.end()) + { + FileEntryCacheRef cachedRef = pContext->getMainWindow().findDisposableCacheElement(fileContext.first); + if (cachedRef.get() != nullptr) + { + cachedRef->nUsers++; + assert(cachedRef->nUsers >= 1); + fileEntries[fileID] = std::move(cachedRef); + } + else + { + FileEntryCache *pNewCache = new FileEntryCache(fileContext.first, pContext); + pNewCache->nUsers++; + assert(pNewCache->nUsers >= 1); + fileEntries[fileID] = FileEntryCacheRef(pNewCache); + } + AssetIdentifier identifier; + size_t start = listEntries.size(); + for (AssetIterator iter(pAssetsInfo); !iter.isEnd(); ++iter) + { + iter.get(identifier); + listEntries.push_back(ListEntry(fileID, identifier.pathID)); + } + if (hDialog) + ListView_SetItemCount(GetDlgItem(hDialog, IDC_ASSETLIST), (int)std::min(listEntries.size(), INT_MAX)); + if (sorted) + resort(); + if (!entryCachingScheduled) + { + entryCachingScheduled = true; + SetTimer(hDialog, (uintptr_t)1, 16, NULL); + } + } + } +} +void AssetListDialog::removeFileContext(FileEntryUIInfo *pEntryInfo) +{ + //No need to update the sort order. + unsigned int fileID = 0; + auto entryIt = fileEntries.end(); + if (pEntryInfo->pContextInfo != nullptr) + { + entryIt = fileEntries.find(pEntryInfo->pContextInfo->getFileID()); + } + else + { + for (auto curEntryIt = fileEntries.begin(); curEntryIt != fileEntries.end(); ++curEntryIt) + { + if (curEntryIt->second->pUIInfo == pEntryInfo) + { + entryIt = curEntryIt; + break; + } + } + } + if (entryIt != fileEntries.end()) + { + fileID = entryIt->first; + entryIt->second->lastUseTime = time(nullptr); + assert(entryIt->second->nUsers >= 1); + entryIt->second->nUsers--; + fileEntries.erase(entryIt); + } + HWND hAssetListView = NULL; + if (hDialog) + hAssetListView = GetDlgItem(hDialog, IDC_ASSETLIST); + if (fileID != 0) + { + { + auto deferredChangesIt = deferredChangesByFileID.find(fileID); + if (deferredChangesIt != deferredChangesByFileID.end()) + deferredChangesByFileID.erase(deferredChangesIt); + } + //Delete the list entries for this file. + //Assuming that listEntries only consists of trivially destructible types (allowed : std::tuple, not allowed : std::vector). + size_t moveOffset = 0; + if (hAssetListView) + ListView_SetItemCount(hAssetListView, 0); + //ListView_DeleteAllItems(hAssetListView); + for (size_t i = 0; i < listEntries.size() - moveOffset; i++) + { + while ((i + moveOffset) < listEntries.size() && listEntries[i + moveOffset].fileID == fileID) + { + //if (hAssetListView && (i + moveOffset) <= INT_MAX) + // ListView_DeleteItem(hAssetListView, (i + moveOffset)); + moveOffset++; + } + if (moveOffset > 0 && (i + moveOffset) < listEntries.size()) + listEntries[i] = std::move(listEntries[i + moveOffset]); + } + if (moveOffset > 0) + { + listEntries.erase(listEntries.begin() + (listEntries.size() - moveOffset), listEntries.end()); + } + if (cachedListEntryCount >= moveOffset) + cachedListEntryCount -= moveOffset; + else + cachedListEntryCount = 0; + } + if (hAssetListView) + ListView_SetItemCount(hAssetListView, static_cast(std::min(listEntries.size(), INT_MAX))); + + if (fileEntries.empty()) + this->pContext->getMainWindow().hideManipulateDialog(this); +} +HWND AssetListDialog::getWindowHandle() +{ + return hDialog; +} +//Called for unhandled WM_COMMAND messages. Returns true if this dialog has handled the request, false otherwise. +bool AssetListDialog::onCommand(WPARAM wParam, LPARAM lParam) +{ + int wmId = LOWORD(wParam); + if (wmId == IDM_FILE_SAVEALL || !this->hDialog) + { + bool applyAll = (wmId == IDM_FILE_SAVEALL); + if (wmId == IDM_FILE_SAVEALL) + wmId = IDM_FILE_APPLY; + bool ret = false; + for (auto dialogIt = this->modifyDialogs.begin(); dialogIt != this->modifyDialogs.end(); ++dialogIt) + { + if (*dialogIt) + { + if (applyAll) + { + (*dialogIt)->applyChanges(); + ret = true; + } + else + ret = ret || (*dialogIt)->onCommand(wParam, lParam); + } + } + if (hDialog) + ret = ret || (AssetListProc(hDialog, WM_COMMAND, wParam, lParam) == (INT_PTR)TRUE); + return ret; + } + else + { + HWND hTabsControl = GetDlgItem(this->hDialog, IDC_ASSETLISTMODIFYTABS); + int curTab = (int)SendMessage(hTabsControl, MC_MTM_GETCURSEL, 0, 0); + if (curTab != -1) + { + MC_MTITEM item = {}; + item.dwMask = MC_MTIF_PARAM; + if (SendMessage(hTabsControl, MC_MTM_GETITEM, (WPARAM)curTab, (LPARAM)&item) == TRUE) + { + if (item.lParam != 0) + { + AssetModifyDialog *pModifyDlg = reinterpret_cast(item.lParam); + if (wmId == IDM_FILE_APPLY) + { + pModifyDlg->applyChanges(); + return true; + } + return pModifyDlg->onCommand(wParam, lParam); + } + else + return AssetListProc(hDialog, WM_COMMAND, wParam, lParam) == (INT_PTR)TRUE; + } + } + } + return false; +} +//message : currently only WM_KEYDOWN; keyCode : VK_F3 for instance +void AssetListDialog::onHotkey(ULONG message, DWORD keyCode) +{ + if (message == WM_KEYDOWN && keyCode == VK_F3) + { + this->searchNext(); + } +} + +void AssetListDialog::onUpdateContainers(AssetsFileContextInfo *pFile) +{ + unsigned int fileID = pFile->getFileID(); + auto entryIt = fileEntries.find(pFile->getFileID()); + if (entryIt != fileEntries.end()) + { + FileEntryCache &entryCache = *entryIt->second.get(); + if (entryCache.assetCache.size() > 0 && entryCache.assetCache.size() <= cachedListEntryCount) + cachedListEntryCount -= entryCache.assetCache.size(); + else + { + //The amount of discarded cache elements is unknown, since the FileEntryCache may have cleared itself already. + cachedListEntryCount = 0; + } + if (hDialog) + { + PostMessage(GetDlgItem(hDialog, IDC_ASSETLIST), LVM_REDRAWITEMS, 0, (LPARAM)(int)std::min(listEntries.size(), INT_MAX)); + //ListView_RedrawItems(GetDlgItem(hDialog, IDC_ASSETLIST), 0, (int)std::min(listEntries.size(), INT_MAX)); + if (!windowUpdateScheduled) + { + windowUpdateScheduled = true; + SetTimer(hDialog, (uintptr_t)0, 50, NULL); + } + } + } +} + +void AssetListDialog::applyDeferredChanges() +{ + auto deferredChangesByFileID = std::move(this->deferredChangesByFileID); + size_t totalChanges = 0; + for (auto& changesVal : deferredChangesByFileID) + totalChanges += changesVal.second.size(); + if (totalChanges == 0) + return; + //Generate a hash table that lists all changes, + // since about listEntries.size()*3 steps are much less than O(listEntries.size()^2) steps + // which would occur if a change notification was posted for every single entry. + // (Could be even worse in theory, if several change notifications per asset occured between two calls). + auto hasher = [](const std::pair& entry) + { + static std::hash fidHasher; + static std::hash pidHasher; + size_t fidHash = fidHasher(entry.first); + size_t pidHash = pidHasher(entry.second); + return (fidHash << 1) ^ pidHash; + }; + std::unordered_map, bool/*wasRemoved*/, decltype(hasher)> changesLookup(totalChanges); + for (auto changesForFileIDIt = deferredChangesByFileID.begin(); changesForFileIDIt != deferredChangesByFileID.end(); ++changesForFileIDIt) + { + unsigned int fileID = changesForFileIDIt->first; + for (DeferredChangeDesc& changeDesc : changesForFileIDIt->second) + changesLookup.insert({ {fileID, changeDesc.pathID}, changeDesc.wasRemoved }); + } + + std::vector newListEntries; + newListEntries.reserve(this->listEntries.size()); + + size_t iChangedFirst = SIZE_MAX; + + std::vector> entriesToSort; + size_t numEntriesToSortWithOldIdx = 0; + size_t iCurOutIndexIfUnsorted = 0; + //First, fill newListEntries with all unchanged entries from this->listEntries. + for (size_t i = 0; i < this->listEntries.size(); i++) + { + bool keepEntry = true; + auto lookupEntryIt = changesLookup.find({ this->listEntries[i].fileID, this->listEntries[i].pathID }); + if (lookupEntryIt != changesLookup.end()) + { + if (lookupEntryIt->second) //wasRemoved + { + keepEntry = false; + if (cachedListEntryCount > i) + cachedListEntryCount--; + } + else + { + keepEntry = false; + entriesToSort.push_back({ this->listEntries[i], iCurOutIndexIfUnsorted }); + iCurOutIndexIfUnsorted++; + numEntriesToSortWithOldIdx++; + } + changesLookup.erase(lookupEntryIt); + } + if (keepEntry) + { + newListEntries.push_back(this->listEntries[i]); + iCurOutIndexIfUnsorted++; + } + else + { + if (iChangedFirst == SIZE_MAX) + iChangedFirst = i; + } + } + for (auto& newAssetLookupEntry : changesLookup) + entriesToSort.push_back({ ListEntry(newAssetLookupEntry.first.first, newAssetLookupEntry.first.second), SIZE_MAX }); + this->listEntries = std::move(newListEntries); + if (hDialog) + ListView_SetItemCount(GetDlgItem(hDialog, IDC_ASSETLIST), (int)std::min(listEntries.size(), INT_MAX)); + if (entriesToSort.size() > 100) + { + //Since inserting many items into a vector at random locations is slow (O(nlogn+n^2)), + // insert all at the end and then sort the whole vector if needed (O(n+nlogn)). + //Crude check to limit the worst case time overhead. + for (std::pair& listEntryToInsert : entriesToSort) + listEntries.push_back(listEntryToInsert.first); + if (hDialog) + ListView_SetItemCount(GetDlgItem(hDialog, IDC_ASSETLIST), (int)std::min(listEntries.size(), INT_MAX)); + if (sorted) + resort(); + } + else + { + //More user-friendly insertion method (more stable entry locations), + // but slower depending on the size of listEntries. + //Note: Each listEntryInsertSorted may also invoke ListView_RedrawItems. + for (std::pair& listEntryToInsert : entriesToSort) + { + listEntryInsertSorted(listEntryToInsert.first, listEntryToInsert.second); + } + } + if (hDialog) + ListView_SetItemCount(GetDlgItem(hDialog, IDC_ASSETLIST), (int)std::min(listEntries.size(), INT_MAX)); + if (hDialog && iChangedFirst < INT_MAX) + ListView_RedrawItems(GetDlgItem(hDialog, IDC_ASSETLIST), iChangedFirst, (int)std::min(listEntries.size(), INT_MAX)); +} +void AssetListDialog::onChangeAsset(AssetsFileContextInfo *pFile, pathid_t pathID, bool wasRemoved) +{ + //Called whenever a replacer was added, even if the dialog is hidden (as long as it has the file context). + unsigned int fileID = pFile->getFileID(); + this->deferredChangesByFileID[fileID].push_back(DeferredChangeDesc{ pathID, wasRemoved }); + //static_cast(pFile->getFileContext())->getAssetsFileTable()->getAssetInfo(pathID) + if (hDialog && !windowUpdateScheduled) + { + windowUpdateScheduled = true; + SetTimer(hDialog, (uintptr_t)0, 50, NULL); + } + if (hDialog && !selectionUpdateScheduled) + { + selectionUpdateScheduled = true; + SetTimer(hDialog, (uintptr_t)2, 50, NULL); + } +} +void AssetListDialog::onHide() +{ + if (pActiveModifyDialog) pActiveModifyDialog->onHide(); + EnableMenuItem(pContext->getMainWindow().getMenu(), IDM_VIEW_ADDASSET, MF_GRAYED); + EnableMenuItem(pContext->getMainWindow().getMenu(), IDM_FILE_APPLY, MF_GRAYED); + EnableMenuItem(pContext->getMainWindow().getMenu(), IDM_VIEW_SEARCHBYNAME, MF_GRAYED); + EnableMenuItem(pContext->getMainWindow().getMenu(), IDM_VIEW_CONTINUESEARCH, MF_GRAYED); + EnableMenuItem(pContext->getMainWindow().getMenu(), IDM_VIEW_GOTOASSET, MF_GRAYED); + //EnableMenuItem(pContext->getMainWindow().getMenu(), IDM_MODMAKER_CREATESTANDALONE, MF_GRAYED); + //EnableMenuItem(pContext->getMainWindow().getMenu(), IDM_MODMAKER_CREATEPACKAGE, MF_GRAYED); + if (this->hDialog) + { + if (this->hCurPopupMenu != NULL) + { + DestroyMenu(this->hCurPopupMenu); + this->hCurPopupMenu = NULL; + } + if (windowUpdateScheduled) + KillTimer(this->hDialog, (uintptr_t)0); + if (entryCachingScheduled) + KillTimer(this->hDialog, (uintptr_t)1); + if (selectionUpdateScheduled) + KillTimer(this->hDialog, (uintptr_t)2); + windowUpdateScheduled = false; + entryCachingScheduled = false; + selectionUpdateScheduled = false; + + HWND hTabsControl = GetDlgItem(this->hDialog, IDC_ASSETLISTMODIFYTABS); + int curTab = (int)SendMessage(hTabsControl, MC_MTM_GETCURSEL, 0, 0); + if (curTab != -1) + { + MC_MTITEM item = {}; + item.dwMask = MC_MTIF_PARAM; + if (SendMessage(hTabsControl, MC_MTM_GETITEM, (WPARAM)curTab, (LPARAM)&item) == TRUE) + { + if (item.lParam != 0) + reinterpret_cast(item.lParam)->onHide(); + } + } + + HWND hAssetList = GetDlgItem(this->hDialog, IDC_ASSETLIST); + this->iLastTopItem = ListView_GetTopIndex(hAssetList); + + SendMessage(this->hDialog, WM_CLOSE, 0, 0); + } +} +void AssetListDialog::onShow() +{ + if (!this->hDialog) + this->hDialog = CreateDialogParam(pContext->getMainWindow().getHInstance(), MAKEINTRESOURCE(IDD_ASSETSINFO), hParentWnd, AssetListProc, (LPARAM)this); + + EnableMenuItem(pContext->getMainWindow().getMenu(), IDM_VIEW_ADDASSET, MF_ENABLED); + EnableMenuItem(pContext->getMainWindow().getMenu(), IDM_FILE_APPLY, MF_ENABLED); + EnableMenuItem(pContext->getMainWindow().getMenu(), IDM_VIEW_SEARCHBYNAME, MF_ENABLED); + EnableMenuItem(pContext->getMainWindow().getMenu(), IDM_VIEW_CONTINUESEARCH, + this->searchQuery.empty() ? MF_GRAYED : MF_ENABLED); + EnableMenuItem(pContext->getMainWindow().getMenu(), IDM_VIEW_GOTOASSET, MF_ENABLED); + + if (this->hDialog) + { + this->selectionUpdateScheduled = true; + SetTimer(this->hDialog, (uintptr_t)2, 16, NULL); + } + //EnableMenuItem(pContext->getMainWindow().getMenu(), IDM_MODMAKER_CREATESTANDALONE, MF_ENABLED); + //EnableMenuItem(pContext->getMainWindow().getMenu(), IDM_MODMAKER_CREATEPACKAGE, MF_ENABLED); +} +bool AssetListDialog::hasUnappliedChanges(bool *applyable) +{ + bool ret = false; + //Only the modify dialogs can have unapplied changes; AssetListDialog doesn't store any relevant saveable state. + for (auto modifyDialogPtrIt = this->modifyDialogs.begin(); modifyDialogPtrIt != this->modifyDialogs.end(); ++modifyDialogPtrIt) + { + AssetModifyDialog *pDialog = modifyDialogPtrIt->get(); + bool curApplyable = false; + if (pDialog && pDialog->hasUnappliedChanges(&curApplyable)) + { + if (!applyable) + return true; + ret = true; + if (curApplyable) + { + if (applyable) *applyable = true; + return true; + } + } + } + return ret; +} +bool AssetListDialog::applyChanges() +{ + bool ret = true; + for (auto modifyDialogPtrIt = this->modifyDialogs.begin(); modifyDialogPtrIt != this->modifyDialogs.end(); ++modifyDialogPtrIt) + { + AssetModifyDialog *pDialog = modifyDialogPtrIt->get(); + if (pDialog && !pDialog->applyChanges()) + ret = false; //Still apply changes of any other open dialog. Need to check whether this is good behavior, or if it could . + } + return ret; +} +bool AssetListDialog::doesPreferNoAutoclose() +{ + //Do not autoclose when: + //1. At least one modify dialog is open. + if (!this->modifyDialogs.empty()) return true; + //2. More than one asset is selected. + if (this->lastNumSelections > 1) return true; + return false; +} + +struct SortPred_Base +{ + AssetListDialog *pDlg; + bool smallerThan; //ascending + inline SortPred_Base(AssetListDialog *pDlg, bool smallerThan) : pDlg(pDlg), smallerThan(smallerThan) {} +}; +struct SortPred_Name : SortPred_Base +{ + inline SortPred_Name(AssetListDialog *pDlg, bool smallerThan) : SortPred_Base(pDlg, smallerThan) {} + bool operator()(const AssetListDialog::ListEntry &a, const AssetListDialog::ListEntry &b) const + { + std::string *pNameA = pDlg->getName(a.fileID, a.pathID); + std::string *pNameB = pDlg->getName(b.fileID, b.pathID); + assert(pNameA && pNameB); //Should always be the case. + return (smallerThan ? ((*pNameA) < (*pNameB)) : ((*pNameA) > (*pNameB))); + } +}; +struct SortPred_ContainerName : SortPred_Base +{ + inline SortPred_ContainerName(AssetListDialog *pDlg, bool smallerThan) : SortPred_Base(pDlg, smallerThan) {} + bool operator()(const AssetListDialog::ListEntry &a, const AssetListDialog::ListEntry &b) const + { + std::string *pNameA = pDlg->getContainerName(a.fileID, a.pathID); + std::string *pNameB = pDlg->getContainerName(b.fileID, b.pathID); + assert(pNameA && pNameB); //Should always be the case. + return (smallerThan ? ((*pNameA) < (*pNameB)) : ((*pNameA) > (*pNameB))); + } +}; +struct SortPred_TypeName : SortPred_Base +{ + inline SortPred_TypeName(AssetListDialog *pDlg, bool smallerThan) : SortPred_Base(pDlg, smallerThan) {} + bool operator()(const AssetListDialog::ListEntry &a, const AssetListDialog::ListEntry &b) const + { + std::string *pNameA = pDlg->getTypeName(a.fileID, a.pathID); + std::string *pNameB = pDlg->getTypeName(b.fileID, b.pathID); + assert(pNameA && pNameB); //Should always be the case. + return (smallerThan ? ((*pNameA) < (*pNameB)) : ((*pNameA) > (*pNameB))); + } +}; +struct SortPred_FileID +{ + bool smallerThan; + inline SortPred_FileID(bool smallerThan) : smallerThan(smallerThan) {} + bool operator()(const AssetListDialog::ListEntry &a, const AssetListDialog::ListEntry &b) const + { + return (smallerThan ? (a.fileID < b.fileID) : (a.fileID > b.fileID)); + } +}; +struct SortPred_PathID +{ + bool smallerThan; + inline SortPred_PathID(bool smallerThan) : smallerThan(smallerThan) {} + bool operator()(const AssetListDialog::ListEntry &a, const AssetListDialog::ListEntry &b) const + { + return (smallerThan ? ((int64_t)a.pathID < (int64_t)b.pathID) : ((int64_t)a.pathID > (int64_t)b.pathID)); + } +}; +struct SortPred_Size : SortPred_Base +{ + inline SortPred_Size(AssetListDialog *pDlg, bool smallerThan) : SortPred_Base(pDlg, smallerThan) {} + bool operator()(const AssetListDialog::ListEntry &a, const AssetListDialog::ListEntry &b) const + { + auto sizeA = pDlg->getSize(a.fileID, a.pathID); + auto sizeB = pDlg->getSize(b.fileID, b.pathID); + return (smallerThan ? (sizeA < sizeB) : (sizeA > sizeB)); + } +}; +struct SortPred_Modified : SortPred_Base +{ + inline SortPred_Modified(AssetListDialog *pDlg, bool smallerThan) : SortPred_Base(pDlg, smallerThan) {} + bool operator()(const AssetListDialog::ListEntry &a, const AssetListDialog::ListEntry &b) const + { + bool modA = pDlg->getIsModified(a.fileID, a.pathID); + bool modB = pDlg->getIsModified(b.fileID, b.pathID); + return (smallerThan ? (!modA && modB) : (modA && !modB)); + } +}; + +template +static It upper_bound_or_target(It begin, It end, It target, const Ty& val, Pred pred) +{ + if (target != end) + { + if (!pred(*target, val) && !pred(val, *target)) //if (*target compares equal to val) + return target; + } + return std::upper_bound(begin, end, val, pred); +} +bool AssetListDialog::cacheAllAndShowProgress(bool allowCancel) +{ + applyDeferredChanges(); + bool ret = true; + AssetInfo* tmp; + size_t start = 0; + auto pProgressIndicator = std::make_shared(this->pContext->getMainWindow().getHInstance()); + bool indicatorStarted = false; + while (start < listEntries.size()) + { + if (pProgressIndicator && allowCancel && pProgressIndicator->IsCancelled()) + { + ret = false; + break; + } + if (start != 0 && pProgressIndicator) + { + if (!indicatorStarted) + { + if (!pProgressIndicator->Start(this->hDialog, pProgressIndicator, 2000)) + { + pProgressIndicator.reset(); + continue; + } + pProgressIndicator->SetStepRange(0, (unsigned int)std::min(INT_MAX, listEntries.size())); + pProgressIndicator->SetCancellable(allowCancel); + pProgressIndicator->SetTitle("Processing asset entries"); + } + pProgressIndicator->SetDescription(std::format("Processing asset {} / {}", start + 1, listEntries.size())); + pProgressIndicator->SetStepStatus((unsigned int)std::min(INT_MAX, start)); + } + cachedListEntryCount += cacheEntries(start, listEntries.size(), tmp, 431, &start); + } + if (pProgressIndicator != nullptr) + { + pProgressIndicator->End(); + pProgressIndicator->Free(); + } + return ret; +} +void AssetListDialog::listEntryInsertSorted(ListEntry newEntry, size_t targetIdx) +{ + if (targetIdx == (size_t)-1 || targetIdx > listEntries.size()) + targetIdx = listEntries.size(); + size_t newEntryIndex = listEntries.size(); + if (sorted) + { + if (iSortColumn != 3 && iSortColumn != 4) //Most predicates require cached info, FileID and PathID being the exceptions. + { + //Generate the cache info for the new entry before inserting it at the right place. + listEntries.push_back(newEntry); + this->cacheAllAndShowProgress(false); + listEntries.erase(listEntries.begin() + newEntryIndex); + } + auto targetIt = listEntries.begin() + targetIdx; + switch (iSortColumn) + { + case 0: //Name + newEntryIndex = std::distance(listEntries.begin(), + upper_bound_or_target(listEntries.begin(), listEntries.end(), targetIt, newEntry, SortPred_Name(this, sortOrderAscending))); + break; + case 1: //Container + newEntryIndex = std::distance(listEntries.begin(), + upper_bound_or_target(listEntries.begin(), listEntries.end(), targetIt, newEntry, SortPred_ContainerName(this, sortOrderAscending))); + break; + case 2: //Type + newEntryIndex = std::distance(listEntries.begin(), + upper_bound_or_target(listEntries.begin(), listEntries.end(), targetIt, newEntry, SortPred_TypeName(this, sortOrderAscending))); + break; + case 3: //File ID + newEntryIndex = std::distance(listEntries.begin(), + upper_bound_or_target(listEntries.begin(), listEntries.end(), targetIt, newEntry, SortPred_FileID(sortOrderAscending))); + break; + case 4: //Path ID + newEntryIndex = std::distance(listEntries.begin(), + upper_bound_or_target(listEntries.begin(), listEntries.end(), targetIt, newEntry, SortPred_PathID(sortOrderAscending))); + break; + case 5: //Size (Bytes) + newEntryIndex = std::distance(listEntries.begin(), + upper_bound_or_target(listEntries.begin(), listEntries.end(), targetIt, newEntry, SortPred_Size(this, sortOrderAscending))); + break; + case 6: //Modified + newEntryIndex = std::distance(listEntries.begin(), + upper_bound_or_target(listEntries.begin(), listEntries.end(), targetIt, newEntry, SortPred_Modified(this, sortOrderAscending))); + break; + default: + sorted = false; + break; + } + } + listEntries.insert(listEntries.begin() + newEntryIndex, std::move(newEntry)); + if (iFocusedItem >= 0 && (size_t)iFocusedItem >= newEntryIndex && iFocusedItem < INT_MAX) + { + iFocusedItem++; + } + if (hDialog && newEntryIndex <= INT_MAX) + { + HWND hAssetList = GetDlgItem(hDialog, IDC_ASSETLIST); + if (iFocusedItem != -1) + ListView_SetItemState(hAssetList, iFocusedItem, LVIS_FOCUSED, LVIS_FOCUSED); + ListView_RedrawItems(hAssetList, (int)newEntryIndex, (int)std::min(listEntries.size(), INT_MAX)); + if (!windowUpdateScheduled) + { + windowUpdateScheduled = true; + SetTimer(hDialog, (uintptr_t)0, 50, NULL); + } + } +} +void AssetListDialog::resort() +{ + unsigned int focusTarget_FileID = 0; pathid_t focusTarget_PathID = 0; + if (iFocusedItem >= 0 && (size_t)iFocusedItem < listEntries.size()) + { + focusTarget_FileID = listEntries[iFocusedItem].fileID; + focusTarget_PathID = listEntries[iFocusedItem].pathID; + } + if (iSortColumn != 3 && iSortColumn != 4) //Most predicates require cached info, FileID and PathID being the exceptions. + { + if (!this->cacheAllAndShowProgress(true)) + { + sorted = false; + return; + } + } + auto pProgressIndicator = std::make_shared(this->pContext->getMainWindow().getHInstance()); + if (pProgressIndicator->Start(this->hDialog, pProgressIndicator, 2000)) + { + pProgressIndicator->SetStepRange(0, 0); + pProgressIndicator->SetCancellable(false); + pProgressIndicator->SetTitle("Sorting the asset list"); + } + else + pProgressIndicator.reset(); + bool noChanges = false; + switch (iSortColumn) + { + case 0: //Name + std::stable_sort(listEntries.begin(), listEntries.end(), SortPred_Name(this, sortOrderAscending)); + break; + case 1: //Container + std::stable_sort(listEntries.begin(), listEntries.end(), SortPred_ContainerName(this, sortOrderAscending)); + break; + case 2: //Type + std::stable_sort(listEntries.begin(), listEntries.end(), SortPred_TypeName(this, sortOrderAscending)); + break; + case 3: //File ID + std::stable_sort(listEntries.begin(), listEntries.end(), SortPred_FileID(sortOrderAscending)); + break; + case 4: //Path ID + std::stable_sort(listEntries.begin(), listEntries.end(), SortPred_PathID(sortOrderAscending)); + break; + case 5: //Size (Bytes) + std::stable_sort(listEntries.begin(), listEntries.end(), SortPred_Size(this, sortOrderAscending)); + break; + case 6: //Modified + std::stable_sort(listEntries.begin(), listEntries.end(), SortPred_Modified(this, sortOrderAscending)); + break; + default: + noChanges = true; + break; + } + if (pProgressIndicator != nullptr) + { + pProgressIndicator->End(); + pProgressIndicator->Free(); + pProgressIndicator.reset(); + } + if (noChanges) + return; + + if (focusTarget_FileID != 0) + { + iFocusedItem = -1; + for (size_t i = 0; i < listEntries.size() && i <= INT_MAX; i++) + { + if (listEntries[i].fileID == focusTarget_FileID && listEntries[i].pathID == focusTarget_PathID) + { + iFocusedItem = (int)i; + } + } + assert(iFocusedItem != -1); + } + if (hDialog) + { + HWND hAssetList = GetDlgItem(hDialog, IDC_ASSETLIST); + if (iFocusedItem != -1) + { + ListView_SetItemState(hAssetList, iFocusedItem, LVIS_FOCUSED, LVIS_FOCUSED); + ListView_EnsureVisible(hAssetList, iFocusedItem, FALSE); + } + ListView_RedrawItems(hAssetList, 0, (int)std::min(listEntries.size(), INT_MAX)); + if (!windowUpdateScheduled) + { + windowUpdateScheduled = true; + SetTimer(hDialog, (uintptr_t)0, 50, NULL); + } + } + sorted = true; +} + +void AssetListDialog::getContainerInfo(AssetsFileContextInfo *pContextInfo, pathid_t pathID, + OUT std::string &baseName, OUT std::string &containerListName) +{ + unsigned int fileID = pContextInfo->getFileID(); + bool hasContainerBase = false; + uint64_t baseCount = 0; + uint64_t dependantCount = 0; + baseName.clear(); + containerListName.clear(); + std::string exampleContainerName; + { + AssetContainerList *pMainContainersList = pContextInfo->tryLockContainersRead(); + if (pMainContainersList) + { + std::vector mainContainers = pMainContainersList->getContainers(0, pathID); + if (mainContainers.size() > 0) + { + hasContainerBase = true; + exampleContainerName = mainContainers[0]->name; + baseCount += mainContainers.size(); + } + std::vector parentContainers = pMainContainersList->getParentContainers(0, pathID); + if (parentContainers.size() > 0) + { + dependantCount += parentContainers.size(); + if (exampleContainerName.empty()) + exampleContainerName = parentContainers[0]->name; + } + pContextInfo->unlockContainersRead(); + } + } + for (size_t i = 0; i < pContextInfo->getContainerSources().size(); i++) + { + FileContextInfo_ptr pDepContextInfo = this->pContext->getContextInfo(pContextInfo->getContainerSources()[i]); + if (pDepContextInfo != nullptr && + pDepContextInfo->getFileContext() && + pDepContextInfo->getFileContext()->getType() == FileContext_Assets) + { + AssetsFileContextInfo *pDepContextInfo_Assets = static_cast(pDepContextInfo.get()); + const std::vector referenceFileIDs = pDepContextInfo_Assets->getReferences(); + auto ownIDIt = std::find(referenceFileIDs.begin(), referenceFileIDs.end(), fileID); + if (ownIDIt != referenceFileIDs.end()) + { + size_t relFileID = std::distance(referenceFileIDs.begin(), ownIDIt) + 1; + assert(relFileID < UINT_MAX); + + AssetContainerList *pDepContainersList = pDepContextInfo_Assets->tryLockContainersRead(); + if (pDepContainersList) + { + std::vector mainContainers = pDepContainersList->getContainers((unsigned int)relFileID, pathID); + if (mainContainers.size() > 0) + { + if (!hasContainerBase || exampleContainerName.empty()) + exampleContainerName = mainContainers[0]->name; + hasContainerBase = true; + baseCount += mainContainers.size(); + } + std::vector parentContainers = pDepContainersList->getParentContainers((unsigned int)relFileID, pathID); + if (parentContainers.size() > 0) + { + dependantCount += parentContainers.size(); + if (exampleContainerName.empty()) + exampleContainerName = parentContainers[0]->name; + } + pDepContextInfo_Assets->unlockContainersRead(); + } + } + } + } + char nameExt[128]; nameExt[0] = 0; + if (hasContainerBase) + { + if (baseCount > 1) + sprintf_s(nameExt, " (Base, %llu other base containers, %llu dependants)", baseCount - 1, dependantCount); + else + sprintf_s(nameExt, " (Base, %llu dependants)", dependantCount); + } + else if (!exampleContainerName.empty()) + { + if (dependantCount > 1) + sprintf_s(nameExt, " (and %llu other dependant containers)", dependantCount - 1); + else + strcpy_s(nameExt, " (dependant container)"); + } + if (hasContainerBase) + { + auto slashIt = std::find(exampleContainerName.rbegin(), exampleContainerName.rend(), '/'); + if (slashIt != exampleContainerName.rend() && slashIt != exampleContainerName.rbegin()) + baseName.assign(slashIt.base(), exampleContainerName.end()); //.base() returns a forward iterator for the next item (in forward direction). + else + baseName = exampleContainerName; + } + containerListName = std::move(exampleContainerName); + if (nameExt[0] != 0) + containerListName += nameExt; +} +bool AssetListDialog::TryRetrieveAssetNameField(AssetsFileContextInfo *pContextInfo, AssetIdentifier &identifier, std::string &nameOut, + std::unordered_map &nameTypesCache) +{ + if (identifier.pAssetInfo && !identifier.pReplacer) + { + IAssetsReader_ptr pReader(pContextInfo->getAssetsFileContext()->createReaderView(), Free_AssetsReader); + + if (identifier.pAssetInfo->ReadName(pContextInfo->getAssetsFileContext()->getAssetsFile(), nameOut, pReader.get())) + return true; + } + bool ret = false; + { + NameTypeCacheValue *pNameTypeValue = nullptr; + NameTypeCacheKey key(pContextInfo->getFileID(), identifier.getClassID(), identifier.getMonoScriptID()); + auto typeFileCacheIt = nameTypesCache.find(key); + if (typeFileCacheIt != nameTypesCache.end()) + pNameTypeValue = &typeFileCacheIt->second; + else + { + NameTypeCacheValue newValue; + newValue.hasName = false; + newValue.nameChildIdx = 0; + if (pContextInfo->MakeTemplateField(&newValue.templateBase, *this->pContext, identifier.getClassID(), identifier.getMonoScriptID(), &identifier)) + { + AssetTypeTemplateField &templateBase = newValue.templateBase; + for (DWORD i = 0; i < templateBase.children.size(); i++) + { + if (templateBase.children[i].name == "m_Name" + && (templateBase.children[i].valueType == ValueType_String)) + { + newValue.hasName = true; + newValue.nameChildIdx = i; + break; + } + } + } + pNameTypeValue = &(nameTypesCache[key] = std::move(newValue)); + } + if (pNameTypeValue->hasName) + { + IAssetsReader_ptr pReader = identifier.makeReader(); + if (pReader) + { + AssetTypeTemplateField *pTemplateBase = &pNameTypeValue->templateBase; + AssetTypeValueField *pBase; + + uint32_t origChildCount = (uint32_t)pNameTypeValue->templateBase.children.size(); + std::vector childrenTmp = std::move(pNameTypeValue->templateBase.children); + pNameTypeValue->templateBase.children.assign(childrenTmp.begin(), childrenTmp.begin() + (pNameTypeValue->nameChildIdx + 1)); + bool bigEndian = false; + pContextInfo->getEndianness(bigEndian); + AssetTypeInstance instance(1, &pTemplateBase, identifier.getDataSize(), pReader.get(), bigEndian); + pBase = instance.GetBaseField(); + if (pBase) + { + AssetTypeValueField *pNameField = pBase->Get("m_Name"); + if (!pNameField->IsDummy() && pNameField->GetValue()) + { + nameOut.assign(pNameField->GetValue()->AsString()); + ret = true; + } + } + pNameTypeValue->templateBase.children = std::move(childrenTmp); + } + } + } + return ret; +} +size_t AssetListDialog::cacheEntries(size_t start, size_t end, AssetInfo *&pFirstEntry, size_t nMax, size_t *maxVisitedIndex) +{ + applyDeferredChanges(); + std::unordered_map nameTypesCache; + pFirstEntry = nullptr; + if (maxVisitedIndex) *maxVisitedIndex = start; + size_t newCachedEntries = 0; + if (start < listEntries.size()) + { + if (end > listEntries.size()) + end = listEntries.size(); + size_t i; + for (i = start; i < end && nMax > 0; i++) + { + unsigned int fileID = listEntries[i].fileID; + pathid_t pathID = listEntries[i].pathID; + auto fileEntryIt = fileEntries.find(fileID); + if (fileEntryIt != fileEntries.end()) + { + auto cacheEntryIt = fileEntryIt->second->assetCache.find(pathID); + if (cacheEntryIt == fileEntryIt->second->assetCache.end()) + { + FileEntryUIInfo *pUIInfo = fileEntryIt->second->pUIInfo; + AssetsFileContextInfo *pContextInfo = static_cast(pUIInfo->getContextInfoPtr()); + AssetIdentifier identifier(std::static_pointer_cast(pUIInfo->pContextInfo), pathID); + if (identifier.resolve(*this->pContext)) + { + AssetInfo info; + info.typeID = identifier.getClassID(); + info.monoScriptID = identifier.getMonoScriptID(); + info.isModified = (identifier.pReplacer != nullptr); + info.size = identifier.getDataSize(); + getContainerInfo(pContextInfo, pathID, info.name, info.containerName); + info.typeName = pContextInfo->GetClassName_(*this->pContext, info.typeID, info.monoScriptID, &identifier); + if (info.name.empty()) + TryRetrieveAssetNameField(pContextInfo, identifier, info.name, nameTypesCache); + if (info.typeName.empty()) + { + char sprntTmp[20]; + sprintf_s(sprntTmp, "0x%08X", info.typeID); + info.typeName.assign(sprntTmp); + } + auto newCacheEntryIt = fileEntryIt->second->assetCache.insert( + fileEntryIt->second->assetCache.begin(), + std::make_pair(pathID, std::move(info))); + if (!pFirstEntry) + pFirstEntry = &newCacheEntryIt->second; + newCachedEntries++; + nMax--; + } + } + else if (!pFirstEntry) + pFirstEntry = &cacheEntryIt->second; + } + } + if (maxVisitedIndex) *maxVisitedIndex = i; + } + return newCachedEntries; +} +void AssetListDialog::getEntryText(int iItem, int iSubItem, AssetInfo &entry, OUT std::unique_ptr &newTextBuf) +{ + newTextBuf.reset(); + TCHAR sprntTmp[64]; + int sprntSize = 0; + newTextBuf = nullptr; + switch (iSubItem) + { + case 0: //Name + { + if (entry.name.empty()) + sprntSize = _stprintf_s(sprntTmp, TEXT("%s"), TEXT("Unnamed asset")); + else + { + size_t nameLenT; + TCHAR *pNameT = _MultiByteToTCHAR(entry.name.c_str(), nameLenT); + if (pNameT) + { + newTextBuf.reset(new TCHAR[nameLenT + 1]); + memcpy(newTextBuf.get(), pNameT, (nameLenT + 1) * sizeof(TCHAR)); + _FreeTCHAR(pNameT); + } + } + } + break; + case 1: //Container + { + size_t nameLenT; + TCHAR *pNameT = _MultiByteToTCHAR(entry.containerName.c_str(), nameLenT); + if (pNameT) + { + newTextBuf.reset(new TCHAR[nameLenT + 1]); + memcpy(newTextBuf.get(), pNameT, (nameLenT + 1) * sizeof(TCHAR)); + _FreeTCHAR(pNameT); + } + } + break; + case 2: //Type + { + size_t nameLenT; + TCHAR *pNameT = _MultiByteToTCHAR(entry.typeName.c_str(), nameLenT); + if (pNameT) + { + newTextBuf.reset(new TCHAR[nameLenT + 1]); + memcpy(newTextBuf.get(), pNameT, (nameLenT + 1) * sizeof(TCHAR)); + _FreeTCHAR(pNameT); + } + } + break; + case 128: //Type (plus Hex typeID) + { + size_t nameLenT; + TCHAR *pNameT = _MultiByteToTCHAR(entry.typeName.c_str(), nameLenT); + if (pNameT) + { + int curSprntSize = _stprintf_s(sprntTmp, TEXT(" (0x%08X)"), entry.typeID); + if (curSprntSize < 0) curSprntSize = 0; + newTextBuf.reset(new TCHAR[nameLenT + curSprntSize + 1]); + memcpy(newTextBuf.get(), pNameT, (nameLenT + 1) * sizeof(TCHAR)); + memcpy(newTextBuf.get() + nameLenT, sprntTmp, (curSprntSize + 1) * sizeof(TCHAR)); + _FreeTCHAR(pNameT); + } + } + break; + case 3: //File ID + sprntSize = _stprintf_s(sprntTmp, TEXT("%u"), this->listEntries[iItem].fileID); + break; + case 4: //Path ID + sprntSize = _stprintf_s(sprntTmp, TEXT("%lld"), (int64_t)this->listEntries[iItem].pathID); + break; + case 5: //Size (Bytes) + sprntSize = _stprintf_s(sprntTmp, TEXT("%llu"), entry.size); + break; + case 6: //Modified + sprntTmp[0] = (entry.isModified ? TEXT('*') : 0); + sprntTmp[1] = 0; + sprntSize = (entry.isModified ? 1 : 0); + break; + } + if (sprntSize > 0) + { + newTextBuf.reset(new TCHAR[sprntSize + 1]); + memcpy(newTextBuf.get(), sprntTmp, (sprntSize + 1) * sizeof(TCHAR)); + } +} + +void AssetListDialog::updateSelectionDesc() +{ + size_t firstSelection = SIZE_MAX; + size_t lastSelection = SIZE_MAX; + size_t nSelections = 0; + for (size_t i = 0; i < this->listEntries.size(); i++) + { + if (this->listEntries[i].isSelected) + { + lastSelection = i; + nSelections++; + } + } + if (nSelections <= 1) + { + if (this->pActiveModifyDialog == nullptr) + { + ShowWindow(GetDlgItem(hDialog, IDC_NAMESTATIC), SW_SHOW); + ShowWindow(GetDlgItem(hDialog, IDC_EDITASSETNAME), SW_SHOW); + + ShowWindow(GetDlgItem(hDialog, IDC_PATHIDSTATIC), SW_SHOW); + ShowWindow(GetDlgItem(hDialog, IDC_EDITASSETPATHID), SW_SHOW); + + ShowWindow(GetDlgItem(hDialog, IDC_FILEIDSTATIC), SW_SHOW); + ShowWindow(GetDlgItem(hDialog, IDC_EDITASSETFILEID), SW_SHOW); + + ShowWindow(GetDlgItem(hDialog, IDC_TYPESTATIC), SW_SHOW); + ShowWindow(GetDlgItem(hDialog, IDC_EDITASSETTYPE), SW_SHOW); + + ShowWindow(GetDlgItem(hDialog, IDC_NUMSELSTATIC), SW_HIDE); + } + + if (lastSelection > INT_MAX) + nSelections = 0; + if (nSelections == 1) + { + AssetInfo *pEntry = nullptr; + this->cacheEntry(lastSelection, pEntry); + if (pEntry == nullptr) + nSelections = 0; + else + { + std::unique_ptr namePtr; + this->getEntryText(static_cast(lastSelection), 0, *pEntry, namePtr); //Name + Edit_SetText(GetDlgItem(hDialog, IDC_EDITASSETNAME), namePtr ? namePtr.get() : TEXT("")); + this->getEntryText(static_cast(lastSelection), 4, *pEntry, namePtr); //Path ID + Edit_SetText(GetDlgItem(hDialog, IDC_EDITASSETPATHID), namePtr ? namePtr.get() : TEXT("")); + this->getEntryText(static_cast(lastSelection), 3, *pEntry, namePtr); //File ID + Edit_SetText(GetDlgItem(hDialog, IDC_EDITASSETFILEID), namePtr ? namePtr.get() : TEXT("")); + this->getEntryText(static_cast(lastSelection), 128, *pEntry, namePtr); //Type Name + Hex ID + Edit_SetText(GetDlgItem(hDialog, IDC_EDITASSETTYPE), namePtr ? namePtr.get() : TEXT("")); + } + } + if (nSelections == 0) + { + Edit_SetText(GetDlgItem(hDialog, IDC_EDITASSETNAME), TEXT("")); + Edit_SetText(GetDlgItem(hDialog, IDC_EDITASSETPATHID), TEXT("")); + Edit_SetText(GetDlgItem(hDialog, IDC_EDITASSETFILEID), TEXT("")); + Edit_SetText(GetDlgItem(hDialog, IDC_EDITASSETTYPE), TEXT("")); + } + } + else //if (nSelections > 1) + { + if (this->pActiveModifyDialog == nullptr) + { + ShowWindow(GetDlgItem(hDialog, IDC_NAMESTATIC), SW_HIDE); + ShowWindow(GetDlgItem(hDialog, IDC_EDITASSETNAME), SW_HIDE); + + ShowWindow(GetDlgItem(hDialog, IDC_PATHIDSTATIC), SW_HIDE); + ShowWindow(GetDlgItem(hDialog, IDC_EDITASSETPATHID), SW_HIDE); + + ShowWindow(GetDlgItem(hDialog, IDC_FILEIDSTATIC), SW_HIDE); + ShowWindow(GetDlgItem(hDialog, IDC_EDITASSETFILEID), SW_HIDE); + + ShowWindow(GetDlgItem(hDialog, IDC_TYPESTATIC), SW_HIDE); + ShowWindow(GetDlgItem(hDialog, IDC_EDITASSETTYPE), SW_HIDE); + + ShowWindow(GetDlgItem(hDialog, IDC_NUMSELSTATIC), SW_SHOW); + } + + TCHAR sprntTmp[64]; + _stprintf_s(sprntTmp, TEXT("%llu selected assets"), (uint64_t)nSelections); + Edit_SetText(GetDlgItem(hDialog, IDC_NUMSELSTATIC), sprntTmp); + } + this->lastNumSelections = nSelections; +} + +void AssetListDialog::requestRemoveSelectedAssets() +{ + std::vector selections; + for (size_t i = 0; i < this->listEntries.size(); i++) + { + if (this->listEntries[i].isSelected) + { + selections.push_back(i); + } + } + if (selections.size() > 0 && + MessageBox( + hDialog, + TEXT("Are you sure you want to remove the selected asset(s)?\nThis will break any reference to the selection."), + TEXT("Warning"), + MB_YESNO) + == IDYES) + { + auto pProgressIndicator = std::make_shared(this->pContext->getMainWindow().getHInstance()); + if (pProgressIndicator->Start(this->hDialog, pProgressIndicator, 2000)) + { + pProgressIndicator->SetStepRange(0, (unsigned int)std::min(INT_MAX, listEntries.size())); + pProgressIndicator->SetCancellable(true); + pProgressIndicator->SetTitle("Removing assets"); + } + else + pProgressIndicator.reset(); + size_t _progressUpdateCounter = 431; + for (size_t i = 0; i < selections.size(); ++i) + { + if (pProgressIndicator && pProgressIndicator->IsCancelled()) + break; + if (pProgressIndicator && (_progressUpdateCounter++) == 431) + { + _progressUpdateCounter = 0; + pProgressIndicator->SetDescription(std::format("Removing asset {} / {}", i + 1, listEntries.size())); + pProgressIndicator->SetStepStatus((unsigned int)std::min(INT_MAX, i)); + } + size_t nListEntriesPre = this->listEntries.size(); + unsigned int fileID = this->listEntries[selections[i]].fileID; + pathid_t pathID = this->listEntries[selections[i]].pathID; + FileContextInfo_ptr pContextInfo = this->pContext->getContextInfo(fileID); + if (pContextInfo->getFileContext() != nullptr && pContextInfo->getFileContext()->getType() == FileContext_Assets) + { + AssetsFileContextInfo *pAssetsInfo = reinterpret_cast(pContextInfo.get()); + AssetIdentifier identifier(std::shared_ptr(pContextInfo, pAssetsInfo), pathID); + if (identifier.resolve(*this->pContext)) + pAssetsInfo->addReplacer(std::shared_ptr( + MakeAssetRemover(fileID, pathID, identifier.getClassID(), identifier.getMonoScriptID()), FreeAssetsReplacer), + *this->pContext); + } + //Check the assumption that the 'change asset' callbacks are not applied recursively (i.e. PostMessage, not SendMessage). + assert(this->listEntries.size() == nListEntriesPre); + } + if (pProgressIndicator != nullptr) + { + pProgressIndicator->End(); + pProgressIndicator->Free(); + } + ListView_SetItemCount(GetDlgItem(hDialog, IDC_ASSETLIST), (int)std::min(listEntries.size(), INT_MAX)); + } +} + +inline void doMoveWindow(HDWP &deferCtx, bool &retry, HWND hWnd, int x, int y, int w, int h) +{ + if (deferCtx) + { + deferCtx = DeferWindowPos(deferCtx, hWnd, HWND_TOP, x, y, w, h, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); + if (!deferCtx) + retry = true; + } + else + SetWindowPos(hWnd, HWND_TOP, x, y, w, h, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); +} +static void onResize(HWND hDlg, HWND hActiveTabWnd, bool defer = true) +{ + bool showTabs = false; + LONG tabY = 10; + if (SendMessage(GetDlgItem(hDlg, IDC_ASSETLISTMODIFYTABS), MC_MTM_GETITEMCOUNT, 0, 0) > 1) + { + showTabs = true; + tabY = 35; + } + + { + bool showAssetList = true; + ShowWindow(GetDlgItem(hDlg, IDC_ASSETLISTMODIFYTABS), showTabs ? SW_SHOW : SW_HIDE); + if (hActiveTabWnd != NULL) + { + ShowWindow(hActiveTabWnd, SW_SHOW); + showAssetList = false; + } + ShowWindow(GetDlgItem(hDlg, IDC_ASSETSSTATIC), showAssetList ? SW_SHOW : SW_HIDE); + ShowWindow(GetDlgItem(hDlg, IDC_ASSETLIST), showAssetList ? SW_SHOW : SW_HIDE); + ShowWindow(GetDlgItem(hDlg, IDC_VIEWDATA), showAssetList ? SW_SHOW : SW_HIDE); + ShowWindow(GetDlgItem(hDlg, IDC_EXPORTRAW), showAssetList ? SW_SHOW : SW_HIDE); + ShowWindow(GetDlgItem(hDlg, IDC_DUMPDATA), showAssetList ? SW_SHOW : SW_HIDE); + ShowWindow(GetDlgItem(hDlg, IDC_PLUGINS), showAssetList ? SW_SHOW : SW_HIDE); + ShowWindow(GetDlgItem(hDlg, IDC_IMPORTRAW), showAssetList ? SW_SHOW : SW_HIDE); + ShowWindow(GetDlgItem(hDlg, IDC_IMPORTDUMP), showAssetList ? SW_SHOW : SW_HIDE); + ShowWindow(GetDlgItem(hDlg, IDC_REMOVEASSET), showAssetList ? SW_SHOW : SW_HIDE); + if (!showAssetList) + { + ShowWindow(GetDlgItem(hDlg, IDC_NAMESTATIC), SW_HIDE); + ShowWindow(GetDlgItem(hDlg, IDC_EDITASSETNAME), SW_HIDE); + + ShowWindow(GetDlgItem(hDlg, IDC_PATHIDSTATIC), SW_HIDE); + ShowWindow(GetDlgItem(hDlg, IDC_EDITASSETPATHID), SW_HIDE); + + ShowWindow(GetDlgItem(hDlg, IDC_FILEIDSTATIC), SW_HIDE); + ShowWindow(GetDlgItem(hDlg, IDC_EDITASSETFILEID), SW_HIDE); + + ShowWindow(GetDlgItem(hDlg, IDC_TYPESTATIC), SW_HIDE); + ShowWindow(GetDlgItem(hDlg, IDC_EDITASSETTYPE), SW_HIDE); + + ShowWindow(GetDlgItem(hDlg, IDC_NUMSELSTATIC), SW_HIDE); + } + } + + + HDWP deferCtx = defer ? BeginDeferWindowPos(12) : NULL; + bool retry = false; + + RECT client = {}; + GetClientRect(hDlg, &client); + LONG clientWidth = client.right-client.left; + LONG clientHeight = client.bottom-client.top; + LONG rightPanelSize = std::min(200, (clientWidth / 3) - 56); + LONG rightPanelStart = clientWidth - (rightPanelSize + 16); + LONG leftPanelStart = 19; + LONG leftPanelSize = rightPanelStart - 7 - leftPanelStart; + //LONG rightPanelSize = std::min(200, clientWidth - ((2*clientWidth / 3) + 40)); + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_ASSETLISTMODIFYTABS), 0, 0, clientWidth, 25); + if (hActiveTabWnd != NULL) + doMoveWindow(deferCtx, retry, hActiveTabWnd, leftPanelStart, tabY, rightPanelStart + rightPanelSize - leftPanelStart, clientHeight - tabY); + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_ASSETSSTATIC), leftPanelStart + 2, tabY + 0, leftPanelSize - 2, 15); + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_ASSETLIST), leftPanelStart, tabY + 20, leftPanelSize, clientHeight - tabY - 20 - 7); + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_NUMSELSTATIC), rightPanelStart, tabY + 20, rightPanelSize, 15); + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_NAMESTATIC), rightPanelStart, tabY + 20, 50, 15); + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_EDITASSETNAME), rightPanelStart, tabY + 35, rightPanelSize, 20); + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_PATHIDSTATIC), rightPanelStart, tabY + 59, 50, 15); + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_EDITASSETPATHID), rightPanelStart, tabY + 74, rightPanelSize, 20); + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_FILEIDSTATIC), rightPanelStart, tabY + 98, 50, 15); + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_EDITASSETFILEID), rightPanelStart, tabY + 113, rightPanelSize, 20); + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_TYPESTATIC), rightPanelStart, tabY + 137, 50, 15); + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_EDITASSETTYPE), rightPanelStart, tabY + 152, rightPanelSize, 20); + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_VIEWDATA), rightPanelStart, tabY + 191, rightPanelSize, 25); + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_EXPORTRAW), rightPanelStart, tabY + 235, rightPanelSize, 25); + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_DUMPDATA), rightPanelStart, tabY + 279, rightPanelSize, 25); + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_PLUGINS), rightPanelStart, tabY + 323, rightPanelSize, 25); + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_IMPORTRAW), rightPanelStart, tabY + 367, rightPanelSize, 25); + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_IMPORTDUMP), rightPanelStart, tabY + 411, rightPanelSize, 25); + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_REMOVEASSET), rightPanelStart, tabY + 460, rightPanelSize, 25); + //doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDOK), (clientWidth / 2) - 38, clientHeight - 33, 76, 26); + + if (defer) + { + if (retry || !EndDeferWindowPos(deferCtx)) + onResize(hDlg, hActiveTabWnd, false); + else + UpdateWindow(hDlg); + deferCtx = NULL; + } + else + UpdateWindow(hDlg); + +} +void AssetListDialog::onCacheUpdateTick() +{ + KillTimer(hDialog, (uintptr_t)1); + applyDeferredChanges(); + if (!entryCachingScheduled || cachedListEntryCount >= listEntries.size()) + { + entryCachingScheduled = false; + } + else + { + bool updateTickFrequency = false; + if (ticksUntilCacheFreqUpdate-- == 0) + { + updateTickFrequency = true; + ticksUntilCacheFreqUpdate = 10; + } + LARGE_INTEGER preTimer; + if (updateTickFrequency) QueryPerformanceCounter(&preTimer); + + if (cachedListEntryStartIdx > listEntries.size()) cachedListEntryStartIdx = 0; + AssetInfo *tmp; + size_t newEntries = cacheEntries(cachedListEntryStartIdx, listEntries.size(), tmp, maxEntriesPerTick, &cachedListEntryStartIdx); + if (newEntries < std::min(maxEntriesPerTick, listEntries.size() - cachedListEntryCount)) + { + //The list probably was sorted after starting auto caching, or another dialog worked on the same file cache. + newEntries += cacheEntries(0, listEntries.size(), tmp, maxEntriesPerTick - newEntries, &cachedListEntryStartIdx); + cachedListEntryCount = cachedListEntryStartIdx; + } + else + cachedListEntryCount += newEntries; + if (newEntries == 0 && maxEntriesPerTick > 0) + cachedListEntryCount = listEntries.size(); + if (cachedListEntryCount > listEntries.size()) + { + assert(false); //cachedListEntryCount is inconsistent with listEntries + cachedListEntryCount = listEntries.size(); + } + if (updateTickFrequency) + { + LARGE_INTEGER postTimer; + QueryPerformanceCounter(&postTimer); + QWORD deltaMicro = ((postTimer.QuadPart - preTimer.QuadPart) * 1000000) / qpfrequency.QuadPart; + if (deltaMicro < 9000) + { + if (maxEntriesPerTick >= deltaMicro / 4) + { + //Calculating the "optimal" amount of ticks would run + // into precision issues and potentially division by zero. + //Controlled cultivation should be a healthy choice. + if (deltaMicro < 6500) + maxEntriesPerTick *= 2; + else + maxEntriesPerTick = (maxEntriesPerTick / 4) * 5; + if (maxEntriesPerTick < 10) + maxEntriesPerTick = 10; + } + else + { + //Try to approach the target of 10ms/tick by increasing the amount of entries per tick. + size_t targetMaxEntries = std::max(16, 10000 / (deltaMicro / maxEntriesPerTick)); + if (targetMaxEntries < maxEntriesPerTick) targetMaxEntries = maxEntriesPerTick + 8; + size_t delta = targetMaxEntries - maxEntriesPerTick; + //Contain the spread by limiting the growth rate. + maxEntriesPerTick += (delta / 4) * 3; + } + } + //else if (deltaMicro >= 9000 && deltaMicro < 13000) + //{growth rate near 1 for now} + else if (deltaMicro >= 13000) + { + if (maxEntriesPerTick <= 10) + { + //Probably some bottleneck in traversing the list if it was sorted after starting caching. + //Just disable auto caching in that case, since reducing the amount of entries further likely won't help. + entryCachingScheduled = false; + } + else if (maxEntriesPerTick >= deltaMicro / 4) + { + //Calculating the "optimal" amount of ticks would run + // into precision issues and potentially division by zero. + //Some numeric distancing should be a reasonable choice in this case. + maxEntriesPerTick = deltaMicro / 8; + } + else + { + //Try to approach the target of 10ms/tick by decreasing the amount of entries per tick. + size_t targetMaxEntries = std::max(10, 10000 / (deltaMicro / maxEntriesPerTick)); + if (targetMaxEntries >= maxEntriesPerTick) targetMaxEntries = maxEntriesPerTick - 8; + size_t delta = maxEntriesPerTick - targetMaxEntries; + if (delta < 4) + { + switch (delta) + { + case 1: break; + case 2: maxEntriesPerTick--; break; + case 3: maxEntriesPerTick--; break; + } + } + else + maxEntriesPerTick -= (delta / 4) * 3; + } + } + } + if (entryCachingScheduled) + SetTimer(hDialog, (uintptr_t)1, 16, NULL); + } +} +LRESULT CALLBACK AssetListDialog::ListViewSubclassProc(HWND hWnd, UINT message, + WPARAM wParam, LPARAM lParam, + uintptr_t uIdSubclass, DWORD_PTR dwRefData) +{ + AssetListDialog *pThis = (AssetListDialog*)dwRefData; + //switch (message) + //{ + //} + return DefSubclassProc(hWnd, message, wParam, lParam); +} +void AssetListDialog::searchNext() +{ + if (searchQuery.empty()) + return; + size_t iCur = (this->searchDirectionUp) ? this->listEntries.size() : 0; + ListEntrySelectionIterator selectionIter(*this); + if (!selectionIter.isEnd()) + { + iCur = (*selectionIter) + 1; + } + auto checkEntry = [this](size_t iCur) + { + if (this->listEntries[iCur].isSelected) + return false; + unsigned int fileID = this->listEntries[iCur].fileID; + pathid_t pathID = this->listEntries[iCur].pathID; + AssetInfo* pEntry = nullptr; + auto fileEntryIt = fileEntries.find(fileID); + if (fileEntryIt != fileEntries.end()) + { + auto cacheEntryIt = fileEntryIt->second->assetCache.find(pathID); + if (cacheEntryIt != fileEntryIt->second->assetCache.end()) + pEntry = &cacheEntryIt->second; + } + if (pEntry == nullptr) + { + this->cacheEntries( + iCur, + (iCur < SIZE_MAX - 10) ? std::min(iCur + 10, this->listEntries.size()) : (iCur + 1), + pEntry); + if (pEntry == nullptr) + return false; + } + if (std::regex_search(pEntry->name, this->searchRegex) || std::regex_search(pEntry->containerName, this->searchRegex)) + { + this->selectAsset(fileID, pathID); + return true; + } + return false; + }; + + auto pProgressIndicator = std::make_shared(this->pContext->getMainWindow().getHInstance()); + if (pProgressIndicator->Start(this->hDialog, pProgressIndicator, 2000)) + { + pProgressIndicator->SetCancellable(true); + pProgressIndicator->SetTitle("Searching for asset"); + } + else + pProgressIndicator.reset(); + + size_t _progressUpdateCounter = 431; + bool requestCancel = false; + auto updateProgress = [&pProgressIndicator, &_progressUpdateCounter, &requestCancel](size_t progress, size_t total) + { + if (pProgressIndicator && (_progressUpdateCounter++) == 431) + { + _progressUpdateCounter = 0; + pProgressIndicator->SetDescription(std::format("Processing asset {} / {}", progress + 1, total)); + pProgressIndicator->SetStepStatus((unsigned int)std::min(INT_MAX, progress)); + } + if (pProgressIndicator && pProgressIndicator->IsCancelled()) + requestCancel = true; + }; + + if (this->searchDirectionUp) + { + size_t iStart = iCur; + size_t total = iCur; + if (pProgressIndicator) + pProgressIndicator->SetStepRange(0, (unsigned int)std::min(INT_MAX, total)); + for (; !requestCancel && iCur > 0 && !checkEntry(iCur - 1); --iCur) + updateProgress(iStart - iCur, total); + } + else + { + size_t iStart = iCur; + size_t total = this->listEntries.size() - iCur; + if (pProgressIndicator) + pProgressIndicator->SetStepRange(0, (unsigned int)std::min(INT_MAX, total)); + for (; !requestCancel && iCur < this->listEntries.size() && !checkEntry(iCur); ++iCur) + updateProgress(iCur - iStart, total); + } + if (pProgressIndicator != nullptr) + { + pProgressIndicator->End(); + pProgressIndicator->Free(); + } + //TODO: Add some indication if no result was found. +} +INT_PTR CALLBACK AssetListDialog::GotoDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + AssetListDialog* pThis = (AssetListDialog*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + int wmId, wmEvent; + UNREFERENCED_PARAMETER(lParam); + switch (message) + { + case WM_CLOSE: + case WM_DESTROY: + break; + case WM_INITDIALOG: + { + SetWindowLongPtr(hDlg, GWLP_USERDATA, lParam); + pThis = (AssetListDialog*)lParam; + + unsigned int selectedFileID = 0; + pathid_t selectedPathID = 0; + ListEntrySelectionIterator selectionIter(*pThis); + if (!selectionIter.isEnd()) + { + size_t iSelection = *selectionIter; + selectedFileID = pThis->listEntries[iSelection].fileID; + selectedPathID = pThis->listEntries[iSelection].pathID; + } + + HWND hComboAssets = GetDlgItem(hDlg, IDC_COMBOASSETS); + pThis->gotoDlg_uiToFileIDMapping.clear(); + //Iterating through an unordered_map is not the most efficient, but good enough here. + for (std::pair& entryRef : pThis->fileEntries) + { + unsigned int fileID = entryRef.first; + pThis->gotoDlg_uiToFileIDMapping.push_back(fileID); + } + std::sort(pThis->gotoDlg_uiToFileIDMapping.begin(), pThis->gotoDlg_uiToFileIDMapping.end()); + int cbSelection = -1; + for (size_t i = 0; i < pThis->gotoDlg_uiToFileIDMapping.size(); ++i) + { + unsigned int fileID = pThis->gotoDlg_uiToFileIDMapping[i]; + auto pAssetsInfo = std::dynamic_pointer_cast( + pThis->pContext->getContextInfo(fileID)); + assert(pAssetsInfo != nullptr); + std::string desc = std::format("{} - {}", + fileID, + (pAssetsInfo != nullptr) ? pAssetsInfo->getFileName() : std::string("")); + auto tCbEntryText = unique_MultiByteToTCHAR(desc.c_str()); + ComboBox_AddString(hComboAssets, tCbEntryText.get()); + if (fileID == selectedFileID && i < std::numeric_limits::max()) + cbSelection = (int)i; + } + if (cbSelection != -1) + { + ComboBox_SetCurSel(hComboAssets, cbSelection); + std::basic_string pathIDText = std::to_wstring((int64_t)selectedPathID); + Edit_SetText(GetDlgItem(hDlg, IDC_EDITPATHID), pathIDText.c_str()); + } + return (INT_PTR)TRUE; + } + case WM_COMMAND: + wmId = LOWORD(wParam); + wmEvent = HIWORD(wParam); + switch (wmId) + { + case IDOK: + { + char numberTmp[25]; + GetWindowTextA(GetDlgItem(hDlg, IDC_EDITPATHID), numberTmp, 25); + numberTmp[24] = 0; + __int64 pathID; + if (numberTmp[0] == '-') + pathID = _strtoi64(numberTmp, NULL, 0); + else + pathID = (__int64)_strtoui64(numberTmp, NULL, 0); + + int fileIDSel = ComboBox_GetCurSel(GetDlgItem(hDlg, IDC_COMBOASSETS)); + if (fileIDSel < 0 || (unsigned int)fileIDSel >= pThis->gotoDlg_uiToFileIDMapping.size()) + return (INT_PTR)TRUE; + + unsigned int fileID = pThis->gotoDlg_uiToFileIDMapping[(unsigned int)fileIDSel]; + + pThis->selectAsset(fileID, (pathid_t)pathID); + } + case IDCANCEL: + EndDialog(hDlg, LOWORD(wParam)); + return (INT_PTR)TRUE; + } + break; + } + return (INT_PTR)FALSE; +} +INT_PTR CALLBACK AssetListDialog::SearchDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + AssetListDialog* pThis = (AssetListDialog*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + int wmId, wmEvent; + UNREFERENCED_PARAMETER(lParam); + switch (message) + { + case WM_CLOSE: + case WM_DESTROY: + break; + case WM_INITDIALOG: + { + SetWindowLongPtr(hDlg, GWLP_USERDATA, lParam); + pThis = (AssetListDialog*)lParam; + auto tQuery = unique_MultiByteToTCHAR(pThis->searchQuery.c_str()); + SetWindowText(GetDlgItem(hDlg, IDC_EDITQUERY), tQuery.get()); + Button_SetCheck(GetDlgItem(hDlg, IDC_CKCASESENS), pThis->searchCaseSensitive ? TRUE : FALSE); + Button_SetCheck(GetDlgItem(hDlg, pThis->searchDirectionUp ? IDC_RBUP : IDC_RBDOWN), TRUE); + return (INT_PTR)TRUE; + } + case WM_COMMAND: + wmId = LOWORD(wParam); + wmEvent = HIWORD(wParam); + switch (wmId) + { + case IDC_RBUP: + { + if (Button_GetCheck(GetDlgItem(hDlg, IDC_RBUP)) == BST_CHECKED) + Button_SetCheck(GetDlgItem(hDlg, IDC_RBDOWN), BST_UNCHECKED); + else if (Button_GetCheck(GetDlgItem(hDlg, IDC_RBDOWN)) != BST_CHECKED) + Button_SetCheck(GetDlgItem(hDlg, IDC_RBDOWN), BST_CHECKED); + break; + } + case IDC_RBDOWN: + { + if (Button_GetCheck(GetDlgItem(hDlg, IDC_RBDOWN)) == BST_CHECKED) + Button_SetCheck(GetDlgItem(hDlg, IDC_RBUP), BST_UNCHECKED); + else if (Button_GetCheck(GetDlgItem(hDlg, IDC_RBUP)) != BST_CHECKED) + Button_SetCheck(GetDlgItem(hDlg, IDC_RBUP), BST_CHECKED); + break; + } + case IDOK: + { + HWND hEditQuery = GetDlgItem(hDlg, IDC_EDITQUERY); + DWORD textLen = Edit_GetTextLength(hEditQuery); + if (textLen >= (DWORD)std::numeric_limits::max()) + break; + std::vector textBuf(textLen + 1); + GetWindowText(GetDlgItem(hDlg, IDC_EDITQUERY), textBuf.data(), (int)textBuf.size()); + textBuf.back() = 0; + auto queryU8 = unique_TCHARToMultiByte(textBuf.data()); + pThis->searchQuery.assign(queryU8.get()); + + pThis->searchCaseSensitive = + Button_GetCheck(GetDlgItem(hDlg, IDC_CKCASESENS)) ? true : false; + pThis->searchDirectionUp = + (Button_GetCheck(GetDlgItem(hDlg, IDC_RBUP)) == BST_CHECKED); + + std::string regexStr = ""; + for (size_t iChar = 0; iChar < pThis->searchQuery.size(); ++iChar) + { + switch (pThis->searchQuery[iChar]) + { + case '*': + regexStr += ".*"; + break; + case '(': case '[': case ']': case ')': + case '{': case '}': + case '\\': case '^': case '$': + case '.': case '|': case '?': case '+': + regexStr += '\\'; + default: + regexStr += pThis->searchQuery[iChar]; + break; + } + } + std::regex_constants::syntax_option_type regexOptions = std::regex_constants::optimize; + if (pThis->searchCaseSensitive) + regexOptions |= std::regex_constants::icase; + pThis->searchRegex = std::regex(regexStr, regexOptions); + + EndDialog(hDlg, 1); + return (INT_PTR)TRUE; + } + break; + case IDCANCEL: + EndDialog(hDlg, 0); + return (INT_PTR)TRUE; + } + break; + } + return (INT_PTR)FALSE; +} +INT_PTR CALLBACK AssetListDialog::AssetListProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + int wmId, wmEvent; + UNREFERENCED_PARAMETER(lParam); + INT_PTR ret = (INT_PTR)FALSE; + AssetListDialog *pThis = (AssetListDialog*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + switch (message) + { + case WM_CLOSE: + if (pThis) + pThis->hDialog = NULL; + DestroyWindow(hDlg); + ret = (INT_PTR)TRUE; + break; + case WM_INITDIALOG: + { + SetWindowLongPtr(hDlg, GWLP_USERDATA, lParam); + pThis = (AssetListDialog*)lParam; + pThis->hDialog = hDlg; + //pMainWindow->assetsInfoDialog.hHotkeyHook = SetWindowsHookEx(WH_GETMESSAGE, AssetsInfoKeyboardHookProc, NULL, GetCurrentThreadId()); + + pThis->selectionUpdateScheduled = false; + pThis->windowUpdateScheduled = false; + pThis->entryCachingScheduled = false; + HWND hAssetListView = GetDlgItem(hDlg, IDC_ASSETLIST); + //SetWindowSubclass(hAssetListView, ListViewSubclassProc, 0, reinterpret_cast(pThis)); + ShowWindow(hAssetListView, SW_HIDE); + + ShowWindow(hAssetListView, SW_SHOW); + ListView_SetItemCount(GetDlgItem(hDlg, IDC_ASSETLIST), (int)std::min(pThis->listEntries.size(), INT_MAX)); + + LVCOLUMN column; + ZeroMemory(&column, sizeof(LVCOLUMN)); + column.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM; + column.cx = 150; + column.pszText = const_cast(TEXT("Name")); + column.iSubItem = 0; + ListView_InsertColumn(hAssetListView, 0, &column); + column.cx = 150; + column.pszText = const_cast(TEXT("Container")); + //column.iSubItem = 1; + ListView_InsertColumn(hAssetListView, 1, &column); + column.cx = 60; + column.pszText = const_cast(TEXT("Type")); + //column.iSubItem = 2; + ListView_InsertColumn(hAssetListView, 2, &column); + column.cx = 50; + column.pszText = const_cast(TEXT("File ID")); + //column.iSubItem = 3; + ListView_InsertColumn(hAssetListView, 3, &column); + column.cx = 60; + column.pszText = const_cast(TEXT("Path ID")); + //column.iSubItem = 4; + ListView_InsertColumn(hAssetListView, 4, &column); + column.mask |= LVCF_FMT; + column.cx = 70; + column.fmt = LVCFMT_RIGHT; + column.pszText = const_cast(TEXT("Size (bytes)")); + //column.iSubItem = 5; + ListView_InsertColumn(hAssetListView, 5, &column); + column.fmt = LVCFMT_LEFT; + column.cx = 60; + column.pszText = const_cast(TEXT("Modified")); + ListView_InsertColumn(hAssetListView, 6, &column); + + ListView_SetCallbackMask(hAssetListView, LVIS_SELECTED); + + { + HWND hTabsControl = GetDlgItem(hDlg, IDC_ASSETLISTMODIFYTABS); + MC_MTITEMWIDTH widths; + widths.dwDefWidth = 0; + widths.dwMinWidth = 90; + SendMessage(hTabsControl, MC_MTM_SETITEMWIDTH, 0, (LPARAM) &widths); + + std::shared_ptr modifyDialogToSelect = pThis->pActiveModifyDialog; + + MC_MTITEM newItem = {}; + newItem.dwMask = MC_MTIF_TEXT | MC_MTIF_PARAM | MC_MTIF_CLOSEFLAG; + newItem.pszText = const_cast(TEXT("Asset list")); + newItem.lParam = 0; + newItem.bDisableClose = TRUE; + SendMessage(hTabsControl, MC_MTM_INSERTITEM, (WPARAM)0, (LPARAM)&newItem); + if (!pThis->modifyDialogs.empty()) + { + ShowWindow(hTabsControl, SW_SHOW); + size_t newTabIdx = 1; + + size_t tabIdxToSelect = 0; + for (auto dialogIt = pThis->modifyDialogs.begin(); dialogIt != pThis->modifyDialogs.end(); ++dialogIt) + { + if (modifyDialogToSelect != nullptr && dialogIt->get() == modifyDialogToSelect.get()) + tabIdxToSelect = newTabIdx; + std::string tabName8 = (*dialogIt != nullptr) ? dialogIt->get()->getTabName() : "-"; + auto upTabNameT = unique_MultiByteToTCHAR(tabName8.c_str()); + + newItem.dwMask = MC_MTIF_TEXT | MC_MTIF_PARAM; + newItem.pszText = upTabNameT.get(); + newItem.lParam = (LPARAM)dialogIt->get(); + SendMessage(hTabsControl, MC_MTM_INSERTITEM, (WPARAM)newTabIdx, (LPARAM)&newItem); + + newTabIdx++; + } + + SendMessage(hTabsControl, MC_MTM_SETCURSEL, (WPARAM)tabIdxToSelect, 0); //Also sends a SELCHANGE notification. + } + } + + + if (pThis->iLastTopItem >= 0 && pThis->iLastTopItem < pThis->listEntries.size()) + { + ListView_EnsureVisible(hAssetListView, pThis->iLastTopItem, FALSE); + } + + //Tooltip style for item text; Double buffering to prevent flickering while scrolling, resizing, etc. + ListView_SetExtendedListViewStyle(hAssetListView, LVS_EX_INFOTIP | LVS_EX_LABELTIP | LVS_EX_DOUBLEBUFFER | LVS_EX_ONECLICKACTIVATE | LVS_EX_UNDERLINEHOT); + + pThis->iFocusedItem = -1; + + if (pThis->listEntries.size() > 0) + { + pThis->entryCachingScheduled = true; + SetTimer(hDlg, (uintptr_t)1, 16, NULL); + } + + //Will not fail (on Win >= XP) according to MS docs. + QueryPerformanceFrequency(&pThis->qpfrequency); + pThis->ticksUntilCacheFreqUpdate = 1; + + ShowWindow(hDlg, SW_SHOW); + PostMessage(hDlg, WM_SIZE, 0, 0); + ret = (INT_PTR)TRUE; + } + break; + case WM_TIMER: + { + if (wParam == (uintptr_t)0 && pThis && pThis->windowUpdateScheduled) + { + pThis->windowUpdateScheduled = false; + KillTimer(hDlg, wParam); + UpdateWindow(GetDlgItem(hDlg, IDC_ASSETLIST)); + } + if (wParam == (uintptr_t)1 && pThis) + { + pThis->onCacheUpdateTick(); + } + if (wParam == (uintptr_t)2 && pThis->selectionUpdateScheduled) + { + pThis->selectionUpdateScheduled = false; + KillTimer(hDlg, wParam); + pThis->applyDeferredChanges(); + pThis->updateSelectionDesc(); + } + } + ret = 0; + break; + case WM_NOTIFY: + { + NMLISTVIEW *pNotifyLV = (NMLISTVIEW*)lParam; + switch (pNotifyLV->hdr.code) + { + case LVN_ITEMCHANGED: + { + NMLISTVIEW *pInfo = (NMLISTVIEW*)lParam; + if (pThis) + { + if ((pInfo->uOldState ^ pInfo->uNewState) & LVIS_SELECTED) + { + bool isSelected = (pInfo->uNewState & LVIS_SELECTED) ? true : false; + int iItem = pInfo->iItem; + if (iItem == -1) + { + for (size_t i = 0; i < pThis->listEntries.size(); i++) + { + pThis->listEntries[i].isSelected = isSelected; + } + } + else if (iItem >= 0 && iItem < pThis->listEntries.size()) + { + pThis->listEntries[iItem].isSelected = isSelected; + } + } + if (pInfo->uNewState & LVIS_FOCUSED) + { + pThis->iFocusedItem = pInfo->iItem; + } + } + } + pThis->updateSelectionDesc(); + break; + case LVN_ODSTATECHANGED: + { + NMLVODSTATECHANGE *pInfo = (NMLVODSTATECHANGE*)lParam; + if (pThis) + { + int from = pInfo->iFrom; + if (from < 0) + from = 0; + int to = pInfo->iTo; + if (to < 0 || to >= pThis->listEntries.size()) + to = (pThis->listEntries.size() > INT_MAX) ? INT_MAX : (int)(pThis->listEntries.size() - 1); + + if ((pInfo->uOldState ^ pInfo->uNewState) & LVIS_SELECTED) + { + bool isSelected = (pInfo->uNewState & LVIS_SELECTED) ? true : false; + for (int i = from; i <= to; i++) + { + pThis->listEntries[i].isSelected = isSelected; + } + } + if (pInfo->uNewState & LVIS_FOCUSED) + { + pThis->iFocusedItem = pInfo->iFrom; + } + } + } + pThis->updateSelectionDesc(); + break; + case LVN_GETINFOTIP: + { + NMLVGETINFOTIP *pInfo = (NMLVGETINFOTIP*)lParam; + int iItem = pInfo->iItem; + if (pThis && iItem >= 0 && iItem < pThis->listEntries.size()) + { + AssetInfo *pEntry = nullptr; + pThis->cacheEntry(iItem, pEntry); + if (pEntry) + { + assert(pThis->lvStringBuf_Tooltip.size() >= 2); + //Shift the string buffer list (removing the oldest if needed). + for (size_t i = pThis->lvStringBuf_Tooltip.size() - 1; i > 0; i--) + pThis->lvStringBuf_Tooltip[i] = std::move(pThis->lvStringBuf_Tooltip[i-1]); + pThis->lvStringBuf_Tooltip[0].reset(); + pInfo->cchTextMax = 0; + pInfo->pszText = const_cast(TEXT("")); + //Retrieve the entry text and store the string buffer. + std::unique_ptr newTextBuf; + pThis->getEntryText(iItem, pInfo->iSubItem, *pEntry, newTextBuf); + pThis->lvStringBuf_Tooltip[0].swap(newTextBuf); + if (pThis->lvStringBuf_Tooltip[0] != nullptr) + pInfo->pszText = pThis->lvStringBuf_Tooltip[0].get(); + } + } + } + break; + case LVN_GETDISPINFO: + { + NMLVDISPINFO *pInfo = (NMLVDISPINFO*)lParam; + int iItem = pInfo->item.iItem; + if (pThis && iItem >= 0 && iItem < pThis->listEntries.size()) + { + AssetInfo *pEntry = nullptr; + pThis->cacheEntry(iItem, pEntry); + if (pEntry) + { + UINT newMask = 0; + pInfo->item.lParam = (LPARAM)iItem; + newMask |= LVIF_PARAM; + pInfo->item.stateMask = LVIS_SELECTED; + if (pInfo->item.iSubItem == 0) + pInfo->item.state = (pThis->listEntries[iItem].isSelected ? LVIS_SELECTED : 0); + else + pInfo->item.state = 0; + newMask |= LVIF_STATE; + if (pInfo->item.mask & LVIF_TEXT) + { + newMask |= LVIF_TEXT; + assert(pThis->lvStringBuf_Ownerdata.size() >= 2); + //Shift the string buffer list (removing the oldest if needed). + for (size_t i = pThis->lvStringBuf_Ownerdata.size() - 1; i > 0; i--) + pThis->lvStringBuf_Ownerdata[i] = std::move(pThis->lvStringBuf_Ownerdata[i-1]); + pThis->lvStringBuf_Ownerdata[0].reset(); + //Retrieve the entry text and store the string buffer. + pInfo->item.cchTextMax = 0; + pInfo->item.pszText = const_cast(TEXT("")); + std::unique_ptr newTextBuf = nullptr; + pThis->getEntryText(iItem, pInfo->item.iSubItem, *pEntry, newTextBuf); + pThis->lvStringBuf_Ownerdata[0].swap(newTextBuf); + if (pThis->lvStringBuf_Ownerdata[0] != nullptr) + pInfo->item.pszText = pThis->lvStringBuf_Ownerdata[0].get(); + } + pInfo->item.mask = newMask; + + bool isSelected = pThis->listEntries[iItem].isSelected; + bool isFocused = (iItem == pThis->iFocusedItem); + //Workaround in case the ListView's selection states are not in sync. + //The GetDispInfo selection status is only used for visual purposes and does not touch the item state. + ListView_SetItemState(pInfo->hdr.hwndFrom, iItem, + ((isSelected) ? LVIS_SELECTED : 0) | ((isFocused) ? LVIS_FOCUSED : 0), //state + LVIS_SELECTED | ((isFocused) ? LVIS_FOCUSED : 0)); //mask; Only set LVIS_FOCUSED, don't reset. + } + } + } + ret = (INT_PTR)TRUE; + break; + case LVN_SETDISPINFO: + { + NMLVDISPINFO *pInfo = (NMLVDISPINFO*)lParam; + int iItem = pInfo->item.iItem; + if (pThis && iItem >= 0 && iItem < pThis->listEntries.size()) + { + if (pInfo->item.mask & LVIF_STATE) + { + if ((pInfo->item.stateMask & LVIS_FOCUSED) && (pInfo->item.state & LVIS_FOCUSED)) + { + pThis->iFocusedItem = iItem; + } + if (pInfo->item.stateMask & LVIS_SELECTED) + { + bool isSelected = (pInfo->item.state & LVIS_SELECTED) ? true : false; + if (isSelected != pThis->listEntries[iItem].isSelected) + { + pThis->listEntries[iItem].isSelected = isSelected; + pThis->updateSelectionDesc(); + } + } + } + } + } + break; + case LVN_ODCACHEHINT: + { + NMLVCACHEHINT *pInfo = (NMLVCACHEHINT*)lParam; + if (pThis && pInfo->iFrom >= 0 && pInfo->iTo >= pInfo->iFrom) + { + AssetInfo *pEntry = nullptr; + pThis->cachedListEntryCount += pThis->cacheEntries(pInfo->iFrom, pInfo->iTo + 1, pEntry); + } + } + ret = (INT_PTR)TRUE; + break; + case LVN_ODFINDITEM: + { + LVFINDINFO *pInfo = (LVFINDINFO*)lParam; + //TODO + } + SetWindowLongPtr(hDlg, DWLP_MSGRESULT, -1); + ret = (INT_PTR)TRUE; + break; + case LVN_COLUMNCLICK: + { + NMLISTVIEW *pInfo = (NMLISTVIEW*)lParam; + if (pThis) + { + pThis->sortOrderAscending ^= (pThis->sorted && pThis->iSortColumn == pInfo->iSubItem); + pThis->iSortColumn = pInfo->iSubItem; + pThis->sorted = false; + pThis->resort(); + } + } + break; + case LVN_ITEMACTIVATE: + { + NMITEMACTIVATE *pInfo = (NMITEMACTIVATE*)lParam; + if (pThis) + { + if ((pInfo->iItem >= 0 && pInfo->iItem < pThis->listEntries.size() && pThis->listEntries[pInfo->iItem].isSelected) + && !(pInfo->uKeyFlags & (LVKF_CONTROL | LVKF_SHIFT))) + { + int topIdx = ListView_GetTopIndex(pInfo->hdr.hwndFrom); + int bottomIdx = topIdx + ListView_GetCountPerPage(pInfo->hdr.hwndFrom) + 1; + + //Deselect all. Required when the Win32 dialog is closed and reopened and there are still selected items. + //Otherwise, the old selection would stay even when just clicking an item. + ListView_SetItemState(pInfo->hdr.hwndFrom, -1, 0, LVIS_SELECTED); + ListView_SetItemState(pInfo->hdr.hwndFrom, pInfo->iItem, LVIS_SELECTED, LVIS_SELECTED); + } + } + } + break; + case MC_MTN_CLOSEITEM: + if (((NMHDR*)lParam)->hwndFrom == GetDlgItem(hDlg, IDC_ASSETLISTMODIFYTABS)) + { + MC_NMMTCLOSEITEM *pNotification = (MC_NMMTCLOSEITEM*)lParam; + if (!pThis->preDeleteTab(pNotification)) + SetWindowLongPtr(hDlg, DWLP_MSGRESULT, TRUE); + return (INT_PTR)TRUE; //Prevent tab deletion. + } + break; + case MC_MTN_DELETEITEM: + if (((NMHDR*)lParam)->hwndFrom == GetDlgItem(hDlg, IDC_ASSETLISTMODIFYTABS)) + { + MC_NMMTDELETEITEM *pNotification = (MC_NMMTDELETEITEM*)lParam; + pThis->onDeleteTab(pNotification); + } + break; + case MC_MTN_SELCHANGE: + if (((NMHDR*)lParam)->hwndFrom == GetDlgItem(hDlg, IDC_ASSETLISTMODIFYTABS)) + { + MC_NMMTSELCHANGE *pNotification = (MC_NMMTSELCHANGE*)lParam; + pThis->onSwitchTabs(pNotification); + } + break; + case MC_MTN_DELETEALLITEMS: + if (((NMHDR*)lParam)->hwndFrom == GetDlgItem(hDlg, IDC_ASSETLISTMODIFYTABS)) + { + SetWindowLongPtr(hDlg, DWLP_MSGRESULT, TRUE); + return (INT_PTR)TRUE; //When the dialog closes due to a call, keep the internal tabs. + } + break; + } + } + break; + case WM_COMMAND: + wmId = LOWORD(wParam); + wmEvent = HIWORD(wParam); + switch (wmId) + { + case IDM_FILE_CLOSE: + //if (AskSaveAssetsInfo(pMainWindow, hDlg)) + { + if (pThis) + pThis->pContext->getMainWindow().hideManipulateDialog(pThis); + else + SendMessage(hDlg, WM_CLOSE, 0, 0); + } + return (INT_PTR)TRUE; + case IDM_FILE_APPLY: + //Already handled in onCommand + break; + case IDM_VIEW_CONTINUESEARCH: + pThis->searchNext(); + break; + case IDM_VIEW_SEARCHBYNAME: + { + std::shared_ptr pSelfRef = pThis->selfPtr.lock(); + if (!pSelfRef) { assert(false); break; } + if (DialogBoxParam(pThis->pContext->getMainWindow().getHInstance(), + MAKEINTRESOURCE(IDD_SEARCHASSET), + pThis->hParentWnd ? pThis->hParentWnd : hDlg, + SearchDlgProc, + (LPARAM)pThis) + == 1) + { + pThis->searchNext(); + EnableMenuItem(pThis->pContext->getMainWindow().getMenu(), IDM_VIEW_CONTINUESEARCH, MF_ENABLED); + } + } + break; + case IDM_VIEW_GOTOASSET: + { + std::shared_ptr pSelfRef = pThis->selfPtr.lock(); + if (!pSelfRef) { assert(false); break; } + DialogBoxParam(pThis->pContext->getMainWindow().getHInstance(), + MAKEINTRESOURCE(IDD_GOTOASSET), + pThis->hParentWnd ? pThis->hParentWnd : hDlg, + GotoDlgProc, + (LPARAM)pThis); + } + break; + case IDM_VIEW_CONTAINERS: + /*if (pMainWindow->assetsInfoDialog.hContainersDlg) + { + SetWindowPos(pMainWindow->assetsInfoDialog.hContainersDlg, hDlg, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); + } + else + { + pMainWindow->assetsInfoDialog.hContainersDlg = CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_VIEWCONTAINERS), NULL, ViewContainers, NULL); + if (pMainWindow->assetsInfoDialog.hContainersDlg) + { + //https://stackoverflow.com/questions/812686/can-a-window-be-always-on-top-of-just-one-other-window + SetWindowLongPtr(pMainWindow->assetsInfoDialog.hContainersDlg, GWLP_HWNDPARENT, (LONG_PTR)hDlg); + RECT targetRect = {}; + GetWindowRect(hDlg, &targetRect); + SetWindowPos(pMainWindow->assetsInfoDialog.hContainersDlg, hDlg, targetRect.left, targetRect.top, 0, 0, SWP_NOSIZE); + } + }*/ + break; + case IDC_VIEWDATA: + pThis->openViewDataTab(); + break; + case IDC_DUMPDATA: + pThis->onExportDumpButton(); + break; + case IDC_EXPORTRAW: + pThis->exportAssetsRaw(pThis->getSelectedAssets()); + break; + case IDC_PLUGINS: + pThis->onPluginButton(); + break; + case IDC_IMPORTRAW: + pThis->importAssetsRaw(pThis->getSelectedAssets()); + break; + case IDC_IMPORTDUMP: + pThis->importAssetsDump(pThis->getSelectedAssets()); + break; + case IDC_REMOVEASSET: + pThis->requestRemoveSelectedAssets(); + break; + } + break; + case WM_SIZE: + { + HWND hTabsControl = GetDlgItem(hDlg, IDC_ASSETLISTMODIFYTABS); + HWND hActiveTabWnd = NULL; + int curTab = (int)SendMessage(hTabsControl, MC_MTM_GETCURSEL, 0, 0); + if (curTab != -1) + { + MC_MTITEM item = {}; + item.dwMask = MC_MTIF_PARAM; + if (SendMessage(hTabsControl, MC_MTM_GETITEM, (WPARAM)curTab, (LPARAM)&item) == TRUE) + { + if (item.lParam != 0) + hActiveTabWnd = reinterpret_cast(item.lParam)->getWindowHandle(); + } + } + onResize(hDlg, hActiveTabWnd); + break; + } + } + return ret; +} + +void AssetListDialog::switchToListTab() +{ + HWND hTabsControl = GetDlgItem(this->hDialog, IDC_ASSETLISTMODIFYTABS); + int itemCount = static_cast(SendMessage(hTabsControl, MC_MTM_GETITEMCOUNT, 0, 0)); + for (int i = 0; i < itemCount; i++) + { + MC_MTITEM item; + item.dwMask = MC_MTIF_PARAM; + item.lParam = 0; + if (SendMessage(hTabsControl, MC_MTM_GETITEM, static_cast(i), reinterpret_cast(&item)) + && item.lParam == 0) + { + SendMessage(hTabsControl, MC_MTM_SETCURSEL, static_cast(i), 0); + HWND hAssetList = GetDlgItem(hDialog, IDC_ASSETLIST); + SetFocus(hAssetList); + break; + } + } +} +bool AssetListDialog::openViewDataTab(unsigned int fileID, pathid_t pathID) +{ + auto fileIDEntry = fileEntries.find(fileID); + if (fileIDEntry == fileEntries.end()) + { + //Try to add the file context to the list dialog. + this->pContext->getMainWindow().selectFileContext(fileID, true); + //If a proper dialog type was found for the file context, it should now be added via addFileContext(..). + fileIDEntry = fileEntries.find(fileID); + if (fileIDEntry == fileEntries.end()) + return false; + } + for (size_t i = 0; i < this->listEntries.size(); i++) + { + if (this->listEntries[i].fileID == fileID && this->listEntries[i].pathID == pathID) + { + this->openViewDataTab(i); + return true; + } + } + return false; +} +bool AssetListDialog::selectAsset(unsigned int fileID, pathid_t pathID) +{ + HWND hAssetList = GetDlgItem(hDialog, IDC_ASSETLIST); + auto fileIDEntry = fileEntries.find(fileID); + if (fileIDEntry == fileEntries.end()) + { + //Try to add the file context to the list dialog. + this->pContext->getMainWindow().selectFileContext(fileID, true); + //If a proper dialog type was found for the file context, it should now be added via addFileContext(..). + fileIDEntry = fileEntries.find(fileID); + if (fileIDEntry == fileEntries.end()) + return false; + } + size_t targetIndex = SIZE_MAX; + for (size_t i = 0; i < this->listEntries.size() && i < INT_MAX; i++) + { + if (this->listEntries[i].fileID == fileID && this->listEntries[i].pathID == pathID) + { + targetIndex = i; + break; + } + } + if (targetIndex == SIZE_MAX) + return false; + + ListView_SetItemState(hAssetList, -1, 0, LVIS_FOCUSED | LVIS_SELECTED); + ListView_SetItemState(hAssetList, static_cast(targetIndex), LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED); + ListView_EnsureVisible(hAssetList, static_cast(targetIndex), FALSE); + return true; +} + +void AssetListDialog::addModifyDialog(std::shared_ptr dialogTmp) +{ + HWND hTabsControl = GetDlgItem(this->hDialog, IDC_ASSETLISTMODIFYTABS); + AssetModifyDialog *pDialog = dialogTmp.get(); + + size_t newTabIdx = this->modifyDialogs.size() + 1; + pDialog->selfHandle = this->modifyDialogs.insert(this->modifyDialogs.end(), std::move(dialogTmp)); + + std::string tabName8 = pDialog->getTabName(); + auto upTabNameT = unique_MultiByteToTCHAR(tabName8.c_str()); + MC_MTITEM newItem = {}; + newItem.dwMask = MC_MTIF_TEXT | MC_MTIF_PARAM; + newItem.pszText = upTabNameT.get(); + newItem.lParam = (LPARAM)pDialog; + SendMessage(hTabsControl, MC_MTM_INSERTITEM, (WPARAM)newTabIdx, (LPARAM)&newItem); + SendMessage(hTabsControl, MC_MTM_SETCURSEL, (WPARAM)newTabIdx, 0); //Also sends a SELCHANGE notification. +} +void AssetListDialog::removeModifyDialog(AssetModifyDialog *pModifyDialog) +{ + HWND hTabsControl = (this->hDialog == NULL) ? NULL : GetDlgItem(this->hDialog, IDC_ASSETLISTMODIFYTABS); + if (this->hDialog == NULL || hTabsControl == NULL) + { + pModifyDialog->onDestroy(); + auto dialogSelfHandle = pModifyDialog->selfHandle; + pModifyDialog->selfHandle = this->modifyDialogs.end(); + this->modifyDialogs.erase(dialogSelfHandle); + return; + } + //Find the tab index in the control, since the user may have changed it by dragging. + int nTabs = (int)SendMessage(hTabsControl, MC_MTM_GETITEMCOUNT, 0, 0); + MC_MTITEM item = {}; + for (int i = 0; i < nTabs; i++) + { + item.dwMask = MC_MTIF_PARAM; + if (SendMessage(hTabsControl, MC_MTM_GETITEM, (WPARAM)i, (LPARAM)&item) == TRUE) + { + if (item.lParam == (LPARAM)pModifyDialog) + { + SendMessage(hTabsControl, MC_MTM_DELETEITEM, (WPARAM)i, 0); + break; + } + } + } +} +std::shared_ptr AssetListDialog::getModifyDialogRef(AssetModifyDialog *pDialog) +{ + if (pDialog->selfHandle == this->modifyDialogs.end()) + return std::shared_ptr(); + return *pDialog->selfHandle; +} + +bool AssetListDialog::preDeleteTab(MC_NMMTCLOSEITEM *pNotification) +{ + if (pNotification->lParam == 0) //Asset list tab + return false; + AssetModifyDialog *pModifyDialog = reinterpret_cast(pNotification->lParam); + bool changesApplyable = false; + if (pModifyDialog->hasUnappliedChanges(&changesApplyable)) + { + SendMessage(pNotification->hdr.hwndFrom, MC_MTM_SETCURSEL, (WPARAM)pNotification->iItem, 0); + if (changesApplyable) + { + switch (MessageBox(this->hDialog, + TEXT("This tab has unsaved changes.\nDo you want to apply the changes before closing the tab?"), + TEXT("Asset Bundle Extractor"), + MB_YESNOCANCEL | MB_ICONWARNING | MB_DEFBUTTON3)) + { + case IDYES: + if (pModifyDialog->applyChanges()) + return true; //Close tab (changes applied). + return false; //Don't close tab (changes not applied). + case IDNO: + return true; //Close tab without saving. + case IDCANCEL: + return false; //Don't close tab. + } + } + else if (IDYES == MessageBox(this->hDialog, + TEXT("This tab has unsaved changes.\nDo you want to close the tab anyway and discard any unsaved changes?"), + TEXT("Asset Bundle Extractor"), + MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2)) + { + return true; + } + return false; + } + return true; +} +void AssetListDialog::onDeleteTab(MC_NMMTDELETEITEM *pNotification) +{ + if (pNotification->lParam != 0) + { + AssetModifyDialog *pModifyDialog = reinterpret_cast(pNotification->lParam); + pModifyDialog->onHide(); + pModifyDialog->onDestroy(); + if (pModifyDialog == pActiveModifyDialog.get()) + pActiveModifyDialog.reset(); + + auto dialogSelfHandle = pModifyDialog->selfHandle; + pModifyDialog->selfHandle = this->modifyDialogs.end(); + this->modifyDialogs.erase(dialogSelfHandle); + + if (this->modifyDialogs.empty()) + { + //Hide the tab control. + PostMessage(this->hDialog, WM_SIZE, 0, 0); + } + } +} +void AssetListDialog::onSwitchTabs(MC_NMMTSELCHANGE *pNotification) +{ + HWND newDialogHandle = NULL; + bool runResize = false; + if (pNotification->lParamNew != pNotification->lParamOld) + { + runResize = true; + if (pNotification->lParamOld != 0) + { + AssetModifyDialog *pOldModifyDialog = reinterpret_cast(pNotification->lParamOld); + HWND oldDialogHandle = pOldModifyDialog->getWindowHandle(); + if (oldDialogHandle != NULL) + ShowWindow(oldDialogHandle, SW_HIDE); + pOldModifyDialog->onHide(); + } + if (pNotification->lParamNew != 0) + { + AssetModifyDialog *pNewModifyDialog = reinterpret_cast(pNotification->lParamNew); + for (auto tabListIt = modifyDialogs.begin(); tabListIt != modifyDialogs.end(); ++tabListIt) + { + if (tabListIt->get() == pNewModifyDialog) + pActiveModifyDialog = *tabListIt; + } + pNewModifyDialog->onShow(this->hDialog); + newDialogHandle = pNewModifyDialog->getWindowHandle(); + if (newDialogHandle != NULL) + ShowWindow(newDialogHandle, SW_SHOW); + } + else + { + pActiveModifyDialog = nullptr; + updateSelectionDesc(); + } + } + if (runResize) + onResize(this->hDialog, newDialogHandle); +} + +void AssetListDialog::onExportDumpButton() +{ + if (this->hCurPopupMenu != NULL) + { + DestroyMenu(this->hCurPopupMenu); + this->hCurPopupMenu = NULL; + } + std::vector selection = getSelectedAssets(); + if (selection.empty()) + return; + + UINT popupMenuFlags = TPM_RETURNCMD | TPM_NONOTIFY; + if (GetSystemMetrics(SM_MENUDROPALIGNMENT) != 0) + popupMenuFlags |= TPM_RIGHTALIGN | TPM_HORNEGANIMATION; + else + popupMenuFlags |= TPM_HORPOSANIMATION; + + this->hCurPopupMenu = CreatePopupMenu(); + if (this->hCurPopupMenu == NULL) + return; + AppendMenu(this->hCurPopupMenu, MF_STRING, 9000, TEXT("Dump as text file")); + AppendMenu(this->hCurPopupMenu, MF_STRING, 9001, TEXT("Dump as json file")); + POINT popupPos = {}; + RECT btnRect = {}; + if (GetWindowRect(GetDlgItem(this->hDialog, IDC_DUMPDATA), &btnRect)) + { + popupPos.x = btnRect.left; + popupPos.y = btnRect.bottom; + } + else + GetCursorPos(&popupPos); + uintptr_t selectedId = static_cast(TrackPopupMenuEx(this->hCurPopupMenu, popupMenuFlags, popupPos.x, popupPos.y, this->hDialog, NULL)); + switch (selectedId) + { + case 9000: + this->exportAssetsTextDump(std::move(selection)); + break; + case 9001: + this->exportAssetsJSONDump(std::move(selection)); + break; + } +} +void AssetListDialog::onPluginButton() +{ + if (this->hCurPopupMenu != NULL) + { + DestroyMenu(this->hCurPopupMenu); + this->hCurPopupMenu = NULL; + } + + std::vector selection = getSelectedAssets(); + if (selection.empty()) + return; + + const PluginMapping& plugins = this->pContext->getPlugins(); + auto citer = plugins.options.cbegin(); + std::shared_ptr pCurProvider; + std::vector>> viableOptions; + while (citer = plugins.getNextOptionProvider(citer, pCurProvider), pCurProvider != nullptr) + { + std::string optionName; + std::unique_ptr pRunner; + if (auto* pAssetListProvider = dynamic_cast(pCurProvider.get())) + { + pRunner = pAssetListProvider->prepareForSelection(*this->pContext, *this, selection, optionName); + } + else if (auto* pAssetGenericProvider = dynamic_cast(pCurProvider.get())) + { + pRunner = pAssetGenericProvider->prepareForSelection(*this->pContext, selection, optionName); + } + if (pRunner != nullptr) + { + viableOptions.push_back({ std::move(optionName), std::move(pRunner) }); + } + } + + UINT popupMenuFlags = TPM_RETURNCMD | TPM_NONOTIFY; + if (GetSystemMetrics(SM_MENUDROPALIGNMENT) != 0) + popupMenuFlags |= TPM_RIGHTALIGN | TPM_HORNEGANIMATION; + else + popupMenuFlags |= TPM_HORPOSANIMATION; + + POINT popupPos = {}; + RECT btnRect = {}; + if (GetWindowRect(GetDlgItem(this->hDialog, IDC_PLUGINS), &btnRect)) + { + popupPos.x = btnRect.left; + popupPos.y = btnRect.bottom; + } + else + GetCursorPos(&popupPos); + + if (viableOptions.empty()) + { + this->hCurPopupMenu = CreatePopupMenu(); + if (this->hCurPopupMenu != NULL) + { + AppendMenu(this->hCurPopupMenu, MF_STRING | MF_GRAYED, 102, TEXT("(no options found)")); + TrackPopupMenuEx(this->hCurPopupMenu, popupMenuFlags, popupPos.x, popupPos.y, this->hDialog, NULL); + DestroyMenu(this->hCurPopupMenu); + this->hCurPopupMenu = NULL; + } + return; + } + + size_t sel = ShowContextMenu(viableOptions.size(), [&viableOptions](size_t i) {return viableOptions[i].first.c_str(); }, + popupMenuFlags, popupPos.x, popupPos.y, this->hDialog, + this->hCurPopupMenu); + if (sel != (size_t)-1) + (*viableOptions[sel].second)(); //Let the plugin perform the action. +} + +void AssetListDialog::openViewDataTab(size_t selection) +{ + if (selection == SIZE_MAX) + { + for (size_t i = 0; i < this->listEntries.size(); i++) + { + if (this->listEntries[i].isSelected) + { + selection = i; + break; + } + } + if (selection == SIZE_MAX) + return; + } + AssetIdentifier identifier(this->listEntries[selection].fileID, this->listEntries[selection].pathID); + if (!identifier.resolve(*pContext)) + { + MessageBox(this->hDialog, TEXT("Unable to find the selected asset!"), TEXT("Asset Bundle Extractor"), MB_ICONERROR); + return; + } + AssetInfo *pFirstEntry = nullptr; + this->cacheEntry(selection, pFirstEntry); + std::shared_ptr pSubDialog = + std::make_shared(*this, *pContext, std::move(identifier), pFirstEntry ? pFirstEntry->name : std::string()); + if (pSubDialog->init(pSubDialog, this->hDialog)) + addModifyDialog(pSubDialog); +} + +AssetUtilDesc AssetListDialog::makeExportDescForSelection(size_t selection) { + AssetUtilDesc ret; + unsigned int fileID = this->listEntries[selection].fileID; + pathid_t pathID = this->listEntries[selection].pathID; + + auto fileIt = fileEntries.find(fileID); + if (fileIt != fileEntries.end()) + { + FileEntryCache& cache = *fileIt->second.get(); + FileContextInfo_ptr pContextInfo = cache.pUIInfo->getContextInfo(); + if (pContextInfo->getFileContext() && pContextInfo->getFileContext()->getType() == FileContext_Assets) + { + ret.assetsFileName = pContextInfo->getFileName(); + AssetInfo* pEntry = nullptr; + cacheEntry(selection, pEntry); + if (pEntry != nullptr) + ret.assetName = pEntry->name; + ret.asset = AssetIdentifier(std::reinterpret_pointer_cast(pContextInfo), pathID); + if (!ret.asset.resolve(*this->pContext)) + ret.asset = AssetIdentifier(); + } + } + return ret; +} + +AssetListDialog::ListEntrySelectionIterator::ListEntrySelectionIterator(AssetListDialog& dialog) + : dialog(dialog), selection(SIZE_MAX), nextSelection(SIZE_MAX) +{ + for (size_t i = 0; i < dialog.listEntries.size() && nextSelection == SIZE_MAX; i++) + { + if (dialog.listEntries[i].isSelected) + { + if (selection == SIZE_MAX) + selection = i; + else + nextSelection = i; + } + } +} +AssetListDialog::ListEntrySelectionIterator& AssetListDialog::ListEntrySelectionIterator::operator++() +{ + selection = nextSelection; + nextSelection = SIZE_MAX; + if (selection != SIZE_MAX) + { + for (size_t i = selection + 1; i < dialog.listEntries.size() && nextSelection == SIZE_MAX; i++) + { + if (dialog.listEntries[i].isSelected) + { + nextSelection = i; + } + } + } + return *this; +} + + +std::vector AssetListDialog::getSelectedAssets() +{ + ListEntrySelectionIterator selectionIter(*this); + std::vector selectedAssets; + while (!selectionIter.isEnd()) + { + selectedAssets.push_back(makeExportDescForSelection(*selectionIter)); + ++selectionIter; + } + return selectedAssets; +} + +template +requires std::invocable, std::vector> + && std::convertible_to, std::vector>, + std::shared_ptr> +void AssetListDialog::importAssetsBy(std::vector assets, + const TaskGenerator& taskGenerator, std::string _extension, std::string _extensionRegex, std::string _extensionFilter) +{ + std::vector _importFilePaths = this->pContext->QueryAssetImportLocation( + assets, std::move(_extension), std::move(_extensionRegex), std::move(_extensionFilter)); + if (_importFilePaths.size() == assets.size()) + { + std::shared_ptr pTask = taskGenerator(std::move(assets), std::move(_importFilePaths)); + this->pContext->taskManager.enqueue(pTask); + } +} + +template +requires std::invocable, std::string> +&& std::convertible_to, std::string>, std::shared_ptr> +void AssetListDialog::exportAssetsBy(std::vector assets, + const TaskGenerator& taskGenerator, std::string _extension, std::string _extensionFilter) +{ + std::string exportPath = this->pContext->QueryAssetExportLocation(assets, std::move(_extension), std::move(_extensionFilter)); + if (exportPath.empty()) + return; + std::shared_ptr pTask = taskGenerator(std::move(assets), std::move(exportPath)); + this->pContext->taskManager.enqueue(pTask); +} + +void AssetListDialog::exportAssetsTextDump(std::vector assets, bool stopOnError) +{ + return exportAssetsBy(std::move(assets), *this->pContext, ".txt", "*.txt|Text files:", "Export text dump", stopOnError); +} +void AssetListDialog::exportAssetsJSONDump(std::vector assets, bool stopOnError) +{ + return exportAssetsBy(std::move(assets), *this->pContext, ".json", "*.json|JSON text files:", "Export JSON dump", stopOnError); +} + +void AssetListDialog::importAssetsRaw(std::vector assets, bool stopOnError) +{ + return importAssetsBy(std::move(assets), *this->pContext, ".dat", "(?:\\.dat)?", "*.*|Raw Unity asset:", + "Import raw assets", stopOnError); +} +void AssetListDialog::importAssetsDump(std::vector assets, bool stopOnError) +{ + return importAssetsBy(std::move(assets), *this->pContext, ".json", "\\.(?:json|txt)", "*.txt|UABE text dump:*.json|UABE json dump:", + "Import asset dumps", stopOnError); +} diff --git a/UABE_Win32/AssetListDialog.h b/UABE_Win32/AssetListDialog.h new file mode 100644 index 0000000..d847d11 --- /dev/null +++ b/UABE_Win32/AssetListDialog.h @@ -0,0 +1,472 @@ +#pragma once +#include "api.h" +#include "MainWindow2.h" +#include "FileDialog.h" +#include "../libStringConverter/convert.h" +#include "../UABE_Generic/AssetPluginUtil.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class AssetModifyDialog +{ +private: + std::list>::iterator selfHandle; //Set and used internally by AssetListDialog. +public: + UABE_Win32_API AssetModifyDialog(); + UABE_Win32_API virtual ~AssetModifyDialog(); + //Called when the user requests to close the tab. + //Returns true if there are unsaved changes, false otherwise. + //If the function will return true and applyable is not null, + // *applyable will be set to true iff applyNow() is assumed to succeed without further interaction + // (e.g. all fields in the dialog have a valid value, ...). + //The caller uses this info to decide whether and how it should display a confirmation dialog before proceeding. + virtual bool hasUnappliedChanges(bool *applyable=nullptr)=0; + //Called when the user requests to apply the changes (e.g. selecting Apply, Save or Save All in the menu). + //Returns whether the changes have been applied; + // if true, the caller may continue closing the AssetModifyDialog. + // if false, the caller shall stop closing the AssetModifyDialog. + //Note: applyChanges() is expected to notify the user about errors (e.g. via MessageBox). + virtual bool applyChanges()=0; + virtual std::string getTabName()=0; + virtual HWND getWindowHandle()=0; + //Called for unhandled WM_COMMAND messages. Returns true if this dialog has handled the request, false otherwise. + virtual bool onCommand(WPARAM wParam, LPARAM lParam)=0; + virtual void onHotkey(ULONG message, DWORD keyCode)=0; //message : currently only WM_KEYDOWN; keyCode : VK_F3 for instance + //Called when the dialog is to be shown. The parent window will not change before the next onHide call. + virtual void onShow(HWND hParentWnd)=0; + //Called when the dialog is to be hidden, either because of a tab switch or while closing the tab. + virtual void onHide()=0; + //Called when the tab is about to be destroyed. + //Once this function is called, AssetListDialog::removeModifyDialog must not be used for this dialog. + virtual void onDestroy()=0; + + friend class AssetListDialog; +}; + +struct NameTypeCacheKey +{ + unsigned int fileID; + int32_t classID; + uint16_t monoTypeID; + inline NameTypeCacheKey() + : fileID(0), classID(0), monoTypeID(0xFFFF) + {} + inline NameTypeCacheKey(unsigned int fileID, int32_t classID, uint16_t monoTypeID) + : fileID(fileID), classID(classID), monoTypeID(monoTypeID) + {} + inline bool operator==(const NameTypeCacheKey& other) const + { + return fileID == other.fileID && + classID == other.classID && + monoTypeID == other.monoTypeID; + } +}; + +namespace std +{ + template<> + struct hash + { + inline std::size_t operator()(NameTypeCacheKey const& key) const + { + static std::hash fidHasher; + static std::hash cidHasher; + static std::hash mtidHasher; + size_t fidHash = fidHasher(key.fileID); + size_t cidHash = cidHasher(key.classID); + size_t mtidHash = mtidHasher(key.monoTypeID); + size_t seed = 0; + //Seed calculation stolen from boost (hash_combine) : + /* + Boost Software License - Version 1.0 - August 17th, 2003 + + Permission is hereby granted, free of charge, to any person or organization + obtaining a copy of the software and accompanying documentation covered by + this license (the "Software") to use, reproduce, display, distribute, + execute, and transmit the Software, and to prepare derivative works of the + Software, and to permit third-parties to whom the Software is furnished to + do so, all subject to the following: + + The copyright notices in the Software and this entire statement, including + the above license grant, this restriction and the following disclaimer, + must be included in all copies of the Software, in whole or in part, and + all derivative works of the Software, unless such copies or derivative + works are solely in the form of machine-executable object code generated by + a source language processor. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + */ + seed ^= fidHash + 0x9e3779b9 + (seed << 6) + (seed >> 2); + seed ^= cidHash + 0x9e3779b9 + (seed << 6) + (seed >> 2); + seed ^= mtidHash + 0x9e3779b9 + (seed << 6) + (seed >> 2); + return seed; + } + }; +} + +class AssetListDialog : public IFileManipulateDialog, public MainWindowEventHandler +{ + struct AssetInfo + { + std::string name; //(from the m_Name field) + std::string containerName; + std::string typeName; + uint64_t size; + uint32_t typeID; + uint16_t monoScriptID; + bool isModified; //Has a replacer. + inline size_t getSize() + { + //Rough approximation (overcounts or undercounts the std::string memory size). + return name.size() + containerName.size() + typeName.size() + sizeof(AssetInfo); + } + }; + //Caches AssetInfo elements for assets of a file. Registers itself on the FileEntryUIInfo pointer. + struct FileEntryCache : public MainWindowEventHandler, public UIDisposableCache + { + class Win32AppContext *pContext; + MainWindowEventHandlerHandle eventHandlerHandle; + size_t nUsers; + time_t lastUseTime; + + + FileEntryCache(FileEntryUIInfo *pUIInfo, Win32AppContext *pContext); + ~FileEntryCache(); + size_t approxMemory(); + time_t getLastUseTime(); + bool isInUse(); + + void onUpdateContainers(AssetsFileContextInfo *pFile); + void onChangeAsset(AssetsFileContextInfo *pFile, pathid_t pathID, bool wasRemoved); + + FileEntryUIInfo *pUIInfo; + std::unordered_map assetCache; + }; + typedef UIDisposableCacheRef FileEntryCacheRef; + inline AssetInfo *tryGetCacheEntry(unsigned int fileID, pathid_t pathID) + { + auto fileIt = fileEntries.find(fileID); + if (fileIt != fileEntries.end()) + { + FileEntryCache &cache = *fileIt->second.get(); + auto entryIt = cache.assetCache.find(pathID); + if (entryIt != cache.assetCache.end()) + { + return &entryIt->second; + } + } + return nullptr; + } + //Last determined number of selections; May not be precise at some points. + size_t lastNumSelections; + + UABE_Win32_API AssetUtilDesc makeExportDescForSelection(size_t selection); + //Simple iterator over the selected listEntry elements in an AssetListDialog. + //Iterating over all selected elements has linear time complexity in listEntry.size(). + //Note: Should not be used across window event handler calls, since the listEntries vector could change in between. + // Opening a new message handler (e.g. MessageBox, DialogBox) on the thread is not recommended for the same reason, + // unless the iterator object will no longer be queried or incremented. + class ListEntrySelectionIterator + { + AssetListDialog& dialog; + size_t selection; + size_t nextSelection; + public: + UABE_Win32_API ListEntrySelectionIterator(AssetListDialog& dialog); + inline bool hasNext() + { + return nextSelection != SIZE_MAX; + } + inline bool isEnd() + { + return selection == SIZE_MAX; + } + inline size_t operator*() + { + return selection; + } + UABE_Win32_API ListEntrySelectionIterator& operator++(); + inline ListEntrySelectionIterator operator++(int) + { + ListEntrySelectionIterator ret = *this; + ++ret; + return ret; + } + }; + +public: + struct ListEntry + { + unsigned int fileID; + pathid_t pathID; + bool isSelected; + ListEntry(unsigned int fileID, pathid_t pathID, bool isSelected = false) + : fileID(fileID), pathID(pathID), isSelected(isSelected) + {} + }; + inline std::string *getName(unsigned int fileID, pathid_t pathID) + { + if (AssetInfo *pInfo = tryGetCacheEntry(fileID, pathID)) return &pInfo->name; + return nullptr; + } + inline std::string *getContainerName(unsigned int fileID, pathid_t pathID) + { + if (AssetInfo *pInfo = tryGetCacheEntry(fileID, pathID)) return &pInfo->containerName; + return nullptr; + } + inline std::string *getTypeName(unsigned int fileID, pathid_t pathID) + { + if (AssetInfo *pInfo = tryGetCacheEntry(fileID, pathID)) return &pInfo->typeName; + return nullptr; + } + inline uint64_t getSize(unsigned int fileID, pathid_t pathID) + { + if (AssetInfo *pInfo = tryGetCacheEntry(fileID, pathID)) return pInfo->size; + return 0; + } + inline uint32_t getTypeID(unsigned int fileID, pathid_t pathID) + { + if (AssetInfo *pInfo = tryGetCacheEntry(fileID, pathID)) return pInfo->typeID; + return (uint32_t)INT32_MIN; + } + inline uint16_t getMonoScriptID(unsigned int fileID, pathid_t pathID) + { + if (AssetInfo *pInfo = tryGetCacheEntry(fileID, pathID)) return pInfo->monoScriptID; + return 0xFFFF; + } + inline bool getIsModified(unsigned int fileID, pathid_t pathID) + { + if (AssetInfo *pInfo = tryGetCacheEntry(fileID, pathID)) return pInfo->isModified; + return false; + } + + UABE_Win32_API void addModifyDialog(std::shared_ptr dialog); + UABE_Win32_API void removeModifyDialog(AssetModifyDialog *pDialog); + UABE_Win32_API std::shared_ptr getModifyDialogRef(AssetModifyDialog *pDialog); +private: + MainWindowEventHandlerHandle eventHandlerHandle; + class Win32AppContext *pContext; + HWND hDialog; bool windowUpdateScheduled; bool selectionUpdateScheduled; + HWND hParentWnd; + HMENU hCurPopupMenu = NULL; + std::unordered_map fileEntries; + std::vector listEntries; //The assets in the list control order. pFile, pathID + + std::list> modifyDialogs; + std::shared_ptr pActiveModifyDialog; + + size_t cachedListEntryStartIdx; + size_t cachedListEntryCount; + bool entryCachingScheduled; + LARGE_INTEGER qpfrequency; //QueryPerformanceFrequency + size_t maxEntriesPerTick, ticksUntilCacheFreqUpdate; + void onCacheUpdateTick(); + + std::vector gotoDlg_uiToFileIDMapping; + + std::string searchQuery; + bool searchDirectionUp = false; + bool searchCaseSensitive = false; + std::regex searchRegex; + void searchNext(); + + struct DeferredChangeDesc + { + pathid_t pathID; + bool wasRemoved; + }; + std::map> deferredChangesByFileID; + void applyDeferredChanges(); + + bool sorted; + bool sortOrderAscending; + int iSortColumn; + int iFocusedItem; + int iLastTopItem; + //Returns false if the user cancelled caching. + bool cacheAllAndShowProgress(bool allowCancel); + void listEntryInsertSorted(ListEntry newEntry, size_t targetIdx = (size_t)-1); + void resort(); + + void getContainerInfo(AssetsFileContextInfo *pContextInfo, pathid_t pathID, OUT std::string &baseName, OUT std::string &containerListName); + //Assumes that iItem has been bounds checked already (i.e. 0 <= iItem < listEntries.size()). + //newTextBuf will be created with new TCHAR[]. + void getEntryText(int iItem, int iSubItem, AssetInfo &entry, OUT std::unique_ptr &newTextBuf); + + //Updates the right half description depending on selection. + void updateSelectionDesc(); + + void requestRemoveSelectedAssets(); + + bool preDeleteTab(MC_NMMTCLOSEITEM *pNotification); + void onDeleteTab(MC_NMMTDELETEITEM *pNotification); + void onSwitchTabs(MC_NMMTSELCHANGE *pNotification); + + void openViewDataTab(size_t selection = SIZE_MAX); + void onExportDumpButton(); + void onPluginButton(); + + struct NameTypeCacheValue + { + DWORD nameChildIdx; + bool hasName; + AssetTypeTemplateField templateBase; + inline NameTypeCacheValue() + : nameChildIdx(0), hasName(false), templateBase() + {} + inline NameTypeCacheValue(DWORD nameChildIdx, AssetTypeTemplateField &&templateBase) + : nameChildIdx(nameChildIdx), templateBase(std::move(templateBase)) + {} + NameTypeCacheValue(const NameTypeCacheValue &other) = delete; + inline NameTypeCacheValue(NameTypeCacheValue &&other) + : nameChildIdx(other.nameChildIdx), hasName(other.hasName), templateBase(std::move(other.templateBase)) + { + other.hasName = false; + } + NameTypeCacheValue &operator=(const NameTypeCacheValue &other) = delete; + inline NameTypeCacheValue &operator=(NameTypeCacheValue &&other) + { + nameChildIdx = other.nameChildIdx; + hasName = other.hasName; + templateBase = std::move(other.templateBase); + other.hasName = false; + return (*this); + } + }; + bool TryRetrieveAssetNameField(AssetsFileContextInfo *pContextInfo, AssetIdentifier &identifier, std::string &nameOut, + std::unordered_map &nameTypesCache); + //Generates cache info for listEntries [start,end). + //pFirstEntry (out) : The cache info for listEntries[start] + //nMax : A limit on how many new items should be cached. + //maxVisitedIndex (out, optional) : Receives the index after the highest (i.e. the last) visited list entry. Useful for overridden nMax. + // Set to start if no element was visited. + //Returns the amount of new cached items. + size_t cacheEntries(size_t start, size_t end, AssetInfo *&pFirstEntry, size_t nMax = SIZE_MAX, size_t *maxVisitedIndex = nullptr); + inline void cacheEntry(size_t start, AssetInfo *&pFirstEntry) + { + cacheEntries(start, start+1, pFirstEntry); + } + std::array, 4> lvStringBuf_Ownerdata; + std::array, 4> lvStringBuf_Tooltip; + +protected: + static INT_PTR CALLBACK GotoDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); + static INT_PTR CALLBACK SearchDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); + static INT_PTR CALLBACK AssetListProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); + static LRESULT CALLBACK ListViewSubclassProc(HWND hWnd, UINT uMsg, + WPARAM wParam, LPARAM lParam, + uintptr_t uIdSubclass, DWORD_PTR dwRefData); +public: + ~AssetListDialog(); + AssetListDialog(class Win32AppContext *pContext, HWND hParentWnd); + EFileManipulateDialogType getType(); + void addFileContext(const std::pair &fileContext); + void removeFileContext(FileEntryUIInfo *pEntryInfo); + HWND getWindowHandle(); + void onHotkey(ULONG message, DWORD keyCode); //message : currently only WM_KEYDOWN; keyCode : VK_F3 for instance + bool onCommand(WPARAM wParam, LPARAM lParam); //Called for unhandled WM_COMMAND messages. Returns true if this dialog has handled the request, false otherwise. + + void onUpdateContainers(AssetsFileContextInfo *pFile); + void onChangeAsset(AssetsFileContextInfo *pFile, pathid_t pathID, bool wasRemoved); + + void onShow(); + void onHide(); + + bool hasUnappliedChanges(bool *applyable=nullptr); + bool applyChanges(); + bool doesPreferNoAutoclose(); + + UABE_Win32_API void switchToListTab(); + UABE_Win32_API bool openViewDataTab(unsigned int fileID, pathid_t pathID); + UABE_Win32_API bool selectAsset(unsigned int fileID, pathid_t pathID); + + UABE_Win32_API std::vector getSelectedAssets(); + + //Assumes all assets have resolved AssetIdentifiers already. + template + requires std::invocable, std::string> + && std::convertible_to, std::string>, std::shared_ptr> + UABE_Win32_API void exportAssetsBy(std::vector assets, + const TaskGenerator& taskGenerator, std::string extension, std::string extensionFilter); + + //Assumes all assets have resolved AssetIdentifiers already. + template + requires std::derived_from + && std::constructible_from, std::string, std::string, std::string, bool> + inline void exportAssetsBy(std::vector assets, + std::string extension, std::string extensionFilter, std::string taskName, bool stopOnError = false) + { + return exportAssetsBy(std::move(assets), + [&](std::vector assets, std::string baseDir) { + return std::static_pointer_cast( + std::make_shared(std::move(assets), taskName, extension, std::move(baseDir), stopOnError)); + }, extension, extensionFilter); + } + //Assumes all assets have resolved AssetIdentifiers already. + template + requires std::derived_from + && std::constructible_from, std::string, std::string, std::string, bool> + inline void exportAssetsBy(std::vector assets, + class Win32AppContext& appContext, std::string extension, std::string extensionFilter, + std::string taskName, bool stopOnError = false) + { + return exportAssetsBy(std::move(assets), + [&](std::vector assets, std::string baseDir) { + return std::static_pointer_cast( + std::make_shared(appContext, std::move(assets), taskName, extension, std::move(baseDir), stopOnError)); + }, extension, extensionFilter); + } + + //Assumes all assets have resolved AssetIdentifiers already. + inline void exportAssetsRaw(std::vector assets, bool stopOnError = false) + { + return exportAssetsBy(std::move(assets), ".dat", "*.*|All types:", "Export raw assets", stopOnError); + } + //Assumes all assets have resolved AssetIdentifiers already. + UABE_Win32_API void exportAssetsTextDump(std::vector assets, bool stopOnError = false); + //Assumes all assets have resolved AssetIdentifiers already. + UABE_Win32_API void exportAssetsJSONDump(std::vector assets, bool stopOnError = false); + + //Assumes all assets have resolved AssetIdentifiers already. + template + requires std::invocable, std::vector> + && std::convertible_to, std::vector>, + std::shared_ptr> + UABE_Win32_API void importAssetsBy(std::vector assets, + const TaskGenerator& taskGenerator, std::string extension, std::string extensionRegex, std::string extensionFilter); + + //Assumes all assets have resolved AssetIdentifiers already. + template + requires std::derived_from + && std::constructible_from, std::vector, std::string, bool> + inline void importAssetsBy(std::vector assets, + class Win32AppContext& appContext, std::string extension, std::string extensionRegex, std::string extensionFilter, + std::string taskName, bool stopOnError = false) + { + return importAssetsBy(std::move(assets), + [&appContext, &taskName, stopOnError](std::vector assets, std::vector paths) { + return std::static_pointer_cast( + std::make_shared(appContext, std::move(assets), std::move(paths), taskName, stopOnError)); + }, std::move(extension), std::move(extensionRegex), std::move(extensionFilter)); + } + + //Assumes all assets have resolved AssetIdentifiers already. + UABE_Win32_API void importAssetsRaw(std::vector assets, bool stopOnError = false); + //Assumes all assets have resolved AssetIdentifiers already. + UABE_Win32_API void importAssetsDump(std::vector assets, bool stopOnError = false); +}; + diff --git a/UABE_Win32/AssetViewModifyDialog.cpp b/UABE_Win32/AssetViewModifyDialog.cpp new file mode 100644 index 0000000..f3e4f04 --- /dev/null +++ b/UABE_Win32/AssetViewModifyDialog.cpp @@ -0,0 +1,2348 @@ +#include "stdafx.h" +#include "AssetViewModifyDialog.h" +#include "AsyncTask.h" +#include "resource.h" +#include "ProgressDialog.h" +#include "../libStringConverter/convert.h" +#include "CreateEmptyValueField.h" +#include "MonoBehaviourManager.h" +#include "Win32PluginManager.h" +#include +#include + +class AssetInstanceTask : public ITask +{ + std::shared_ptr pDialog; //Maintain object lifetime. + std::list::iterator itDeserializeDesc; + + std::string name; +public: + AssetInstanceTask(std::shared_ptr pDialog, std::list::iterator itDeserializeDesc) + : pDialog(pDialog), itDeserializeDesc(itDeserializeDesc) + { + assert(itDeserializeDesc->asset.pFile != nullptr); + name = "Deserialize asset : File ID " + std::to_string(static_cast(itDeserializeDesc->asset.pFile->getFileID())) + + ", Path ID " + std::to_string(itDeserializeDesc->asset.pathID); + } + const std::string &getName() + { + return name; + } + TaskResult execute(TaskProgressManager &progressManager) + { + progressManager.setProgress(0, 0); + progressManager.setProgressDesc("Generating the type template"); + bool missingScriptTypeInfo = false; + if (!itDeserializeDesc->asset.pFile->MakeTemplateField( + &itDeserializeDesc->templateBase, pDialog->appContext, + itDeserializeDesc->asset.getClassID(), itDeserializeDesc->asset.getMonoScriptID(), &itDeserializeDesc->asset, + missingScriptTypeInfo)) + { + progressManager.logMessage("Unable to generate the type template!"); + return -1; + } + uint64_t size = itDeserializeDesc->asset.getDataSize(); + IAssetsReader_ptr pReader = itDeserializeDesc->asset.makeReader(); + if (!pReader) + { + progressManager.logMessage("Unable to read the asset!"); + return -2; + } + progressManager.setProgressDesc("Deserializing the asset"); + AssetTypeTemplateField *pTemplateBase = &itDeserializeDesc->templateBase; + std::unique_ptr pAssetInstance( + new AssetTypeInstance(1, &pTemplateBase, size, pReader.get(), itDeserializeDesc->asset.isBigEndian())); + + AssetTypeValueField *pInstanceBase = pAssetInstance->GetBaseField(); + if (pInstanceBase) + { + itDeserializeDesc->pAssetInstance = std::move(pAssetInstance); + //Assuming the caller sets a memory fence (e.g. with EnterCriticalSection) before notifying the main thread. + return missingScriptTypeInfo ? 1 : 0; + } + else + { + progressManager.logMessage("Unable to deserialize the asset!"); + return -3; + } + } + friend class AssetViewModifyDialog; +}; + +AssetViewModifyDialog::AssetViewModifyDialog(AssetListDialog &assetListDialog, Win32AppContext &appContext, AssetIdentifier asset, std::string assetName) + : assetListDialog(assetListDialog), appContext(appContext), assetName(std::move(assetName)), + hDialog(NULL), isDestroyed(false), registeredCallbackCounter(0), ignoreExpandNotifications(false), + hCurPopupMenu(NULL), hCurEditPopup(NULL), hCurEditPopupUpDown(NULL), hEditPopupItem(NULL), pEditValueField(nullptr), pEditAssetDesc(nullptr), + testItemHasTextcallbackActive(false), testItemHasTextcallbackResult(false) +{ + assert(asset.pFile); + assert(asset.pReplacer || asset.pAssetInfo); + + findOrPrepareLoad(std::move(asset), MC_TLI_ROOT, nullptr); +} +AssetViewModifyDialog::~AssetViewModifyDialog() +{ + if (this->hDialog) + { + SendMessage(this->hDialog, WM_CLOSE, 0, 0); + //SetWindowLongPtr(this->hDialog, GWLP_USERDATA, 0); + } +} +HWND AssetViewModifyDialog::_getTreeHandle(HWND hDialog) +{ + return GetDlgItem(hDialog, IDC_TYPETREE); +} + +static void setTreeValueText(HWND hTree, MC_HTREELISTITEM hItem, AssetTypeValueField *pField) +{ + TCHAR sprntTmp[64]; sprntTmp[0] = 0; + TCHAR *convertedText = nullptr; + const TCHAR *text = nullptr; + if (AssetTypeValue *pValue = pField->GetValue()) + { + switch (pValue->GetType()) + { + case ValueType_Bool : + text = (pValue->AsBool() ? TEXT("true") : TEXT("false")); + break; + case ValueType_Int8 : + case ValueType_UInt8 : + case ValueType_Int16 : + case ValueType_UInt16 : + case ValueType_Int32 : + _stprintf_s(sprntTmp, TEXT("%d"), pValue->AsInt()); + text = sprntTmp; + break; + case ValueType_UInt32 : + _stprintf_s(sprntTmp, TEXT("%u"), pValue->AsUInt()); + text = sprntTmp; + break; + case ValueType_Int64 : + _stprintf_s(sprntTmp, TEXT("%lld"), pValue->AsInt64()); + text = sprntTmp; + break; + case ValueType_UInt64 : + _stprintf_s(sprntTmp, TEXT("%llu"), pValue->AsUInt64()); + text = sprntTmp; + break; + case ValueType_Float : + _stprintf_s(sprntTmp, TEXT("%f"), pValue->AsFloat()); + text = sprntTmp; + break; + case ValueType_Double : + _stprintf_s(sprntTmp, TEXT("%f"), pValue->AsDouble()); + text = sprntTmp; + break; + case ValueType_String : + if (pValue->AsString()) + { + std::string valueString = std::string("\"") + pValue->AsString() + "\""; + size_t sizeTmp; + convertedText = _MultiByteToTCHAR(valueString.c_str(), sizeTmp); + text = convertedText; + } + break; + case ValueType_Array : + _stprintf_s(sprntTmp, TEXT("[%u]"), pValue->AsArray()->size); + text = sprntTmp; + break; + case ValueType_ByteArray: + _stprintf_s(sprntTmp, TEXT("[%u]"), pValue->AsByteArray()->size); + text = sprntTmp; + break; + default: + break; + } + } + if (text != nullptr) + { + MC_TLSUBITEM subItem; + subItem.fMask = MC_TLSIF_TEXT; + subItem.iSubItem = 1; + subItem.cchTextMax = 0; + subItem.pszText = const_cast(text); + SendMessage(hTree, MC_TLM_SETSUBITEM, reinterpret_cast(hItem), reinterpret_cast(&subItem)); + } + if (convertedText) + { + _FreeTCHAR(convertedText); + } +} + +MC_HTREELISTITEM AssetViewModifyDialog::addTreeItems(AssetDeserializeDesc *pAssetDesc, MC_HTREELISTITEM hParent, LPARAM parentLParam, + AssetTypeValueField **pFields, size_t fieldCount, bool isPPtr, size_t startIdx) +{ + HWND hTree = GetDlgItem(this->hDialog, IDC_TYPETREE); + if (startIdx > 0 && SendMessage(hTree, MC_TLM_GETNEXTITEM, MC_TLGN_CHILD, (LPARAM)hParent) == NULL) + startIdx = 0; //Was never expanded. + + bool parentIsArray = false; + auto arrayMappingIt = pAssetDesc->arrayMappingsByArray.end(); + if (AssetTypeValueField *pParentField = reinterpret_cast(parentLParam)) + { + if (pParentField->GetValue() && pParentField->GetValue()->GetType() == ValueType_Array) + { + parentIsArray = true; + auto insertResult = pAssetDesc->arrayMappingsByArray.insert(std::make_pair(pParentField, AssetDeserializeDesc::ArrayMappings())); + arrayMappingIt = insertResult.first; + assert(fieldCount <= pParentField->GetValue()->AsArray()->size); + arrayMappingIt->second.treeItems.resize(pParentField->GetValue()->AsArray()->size); + if (insertResult.second) + { + arrayMappingIt->second.itemToIndexMap.rehash(static_cast(std::ceil( + static_cast(pParentField->GetValue()->AsArray()->size) / arrayMappingIt->second.itemToIndexMap.max_load_factor()))); + } + } + } + + std::shared_ptr pProgressIndicator = std::make_shared(this->appContext.getMainWindow().getHInstance()); + if (pProgressIndicator->Start(hDialog, pProgressIndicator, 2000)) + { + size_t nTotal = fieldCount - startIdx; + pProgressIndicator->SetStepRange(0, (nTotal > INT_MAX) ? INT_MAX : static_cast(nTotal)); + pProgressIndicator->SetCancellable(true); + pProgressIndicator->SetTitle("Adding fields to the View"); + } + else + { + pProgressIndicator.reset(); + } + + SendMessage(hTree, WM_SETREDRAW, FALSE, 0); + + MC_HTREELISTITEM hLastItem = NULL; + + MC_TLINSERTSTRUCT insert; + insert.hParent = hParent; + insert.hInsertAfter = MC_TLI_LAST; + insert.item.fMask = MC_TLIF_CHILDREN | MC_TLIF_PARAM | MC_TLIF_TEXT; + insert.item.cchTextMax = 0; + for (size_t i = startIdx; i < fieldCount; i++) + { + if (pProgressIndicator && pProgressIndicator->IsCancelled()) + break; + if (pProgressIndicator && i <= INT_MAX && (i % 101) == 0) + { + TCHAR descTmp[64]; + _stprintf_s(descTmp, TEXT("Adding field %llu/%llu"), + static_cast(i - startIdx), static_cast(fieldCount - startIdx)); + pProgressIndicator->SetDescription(descTmp); + pProgressIndicator->SetStepStatus(static_cast(i - startIdx)); + } + MC_HTREELISTITEM hCurParent = hParent; + if (parentIsArray) + { + insert.hParent = hParent; + insert.item.cChildren = 1; + insert.item.lParam = 0; + //TCHAR sprntTmp[64]; sprntTmp[0] = 0; + //_stprintf_s(sprntTmp, TEXT("[%llu]"), (unsigned long long)i); + //insert.item.pszText = sprntTmp; + insert.item.pszText = MC_LPSTR_TEXTCALLBACK; + hCurParent = reinterpret_cast(SendMessage(hTree, MC_TLM_INSERTITEM, 0, reinterpret_cast(&insert))); + assert(hCurParent != NULL); + if (hCurParent == NULL) + break; + arrayMappingIt->second.treeItems[i] = hCurParent; + arrayMappingIt->second.itemToIndexMap[hCurParent] = static_cast(i); + } + insert.hParent = hCurParent; + insert.item.cChildren = (pFields[i]->GetChildrenCount() > 0) ? 1 : 0; + insert.item.lParam = reinterpret_cast(pFields[i]); + auto pTypeNameT = unique_MultiByteToTCHAR(pFields[i]->GetType().c_str()); + auto pFieldNameT = unique_MultiByteToTCHAR(pFields[i]->GetName().c_str()); + std::basic_string fullName = std::basic_string(pTypeNameT.get()) + TEXT(" ") + pFieldNameT.get(); + //Requires a const_cast, since the item structure can also be used for text retrieval. + //Should be safe, since the text is only read for MC_TLM_INSERTITEM. + insert.item.pszText = const_cast(fullName.c_str()); + MC_HTREELISTITEM hItem = reinterpret_cast(SendMessage(hTree, MC_TLM_INSERTITEM, 0, reinterpret_cast(&insert))); + assert(hItem != NULL); + if (hItem == NULL) + break; + hLastItem = hItem; + setTreeValueText(hTree, hItem, pFields[i]); + } + if (isPPtr) + { + insert.item.cChildren = 1; + //Use a null parameter for special items. + // => The parent item is the PPtr. Retrieve via MC_TLM_GETNEXTITEM with MC_TLGN_PARENT. + insert.item.lParam = 0; + //Requires a const_cast, since the item structure can also be used for text retrieval. + //Should be safe, since the text is only read for MC_TLM_INSERTITEM. + insert.item.pszText = const_cast(TEXT("View asset")); + MC_HTREELISTITEM hItem = reinterpret_cast(SendMessage(hTree, MC_TLM_INSERTITEM, 0, reinterpret_cast(&insert))); + } + SendMessage(hTree, WM_SETREDRAW, TRUE, 0); + if (pProgressIndicator != nullptr) + { + pProgressIndicator->End(); + pProgressIndicator->Free(); + } + return hLastItem; +} + +void AssetViewModifyDialog::onCompletionMainThread(uintptr_t param1, uintptr_t param2) +{ + AssetViewModifyDialog *pThis = reinterpret_cast(param1); + auto ppInstanceTask = reinterpret_cast*>(param2); + if (--pThis->registeredCallbackCounter == 0) + pThis->appContext.taskManager.removeCallback(pThis); + AssetDeserializeDesc *pAssetDesc = &*(*ppInstanceTask)->itDeserializeDesc; + pAssetDesc->pLoadTask = nullptr; + assert(pAssetDesc->pParent != nullptr || pAssetDesc->parentItem == MC_TLI_ROOT); + bool closeAsset = false; + bool reloadAsset = false; + if (pAssetDesc->pendingClose) + { + //Remove this asset from the list, as the load task has now finished. + //Invalidates pAssetDesc + closeAsset = true; + } + else if (pAssetDesc->pAssetInstance == nullptr + || pAssetDesc->pAssetInstance->GetBaseField()->IsDummy()) + { + if (!pThis->isDestroyed && pThis->hDialog != NULL) + { + const TCHAR *errorMessage = nullptr; + switch (pAssetDesc->loadTaskResult) + { + case TaskResult_Canceled: + break; + case -1: + errorMessage = TEXT("Could not retrieve the type information for the asset view!"); + break; + case -2: + errorMessage = TEXT("Unable to read the asset for viewing!"); + break; + case -3: + default: + errorMessage = TEXT("Unable to deserialize the asset for viewing!"); + break; + } + if (errorMessage != nullptr) + MessageBox(pThis->hDialog, errorMessage, TEXT("Asset Bundle Extractor"), MB_ICONERROR); + } + closeAsset = true; + } + else if (pAssetDesc->loadTaskResult == 1 //i.e. is missing MonoBehaviour type information + && !pAssetDesc->monoBehaviourInfoAsked) + { + AssetTypeValueField *pBaseField = pAssetDesc->pAssetInstance->GetBaseField(); + AssetTypeValueField *pFileIDField = pBaseField->Get("m_Script")->Get("m_FileID"); + std::vector> typeAssets = { pAssetDesc->asset.pFile }; + bool foundScriptFile = false; + if (!pFileIDField->IsDummy() && pFileIDField->GetValue() != nullptr && pFileIDField->GetValue()->GetType() == ValueType_Int32) + { + unsigned int absScriptFileID = pAssetDesc->asset.pFile->resolveRelativeFileID((unsigned int)pFileIDField->GetValue()->AsInt()); + std::shared_ptr scriptDefFileInfo + = std::dynamic_pointer_cast(pThis->appContext.getContextInfo(absScriptFileID)); + if (scriptDefFileInfo != nullptr) + { + foundScriptFile = true; + if (scriptDefFileInfo->getFileID() != pAssetDesc->asset.pFile->getFileID()) + typeAssets.push_back(std::move(scriptDefFileInfo)); + } + } + if (foundScriptFile) + { + switch (MessageBox(pThis->hDialog, + TEXT("Class information needs to be extracted to show the complete asset data, and to not break the changed asset when saving.\n") + TEXT("Do you want to do this now?\n") + TEXT("Note: This currently does not work with il2cpp game builds."), + TEXT("Asset Bundle Extractor"), MB_ICONWARNING | MB_YESNOCANCEL)) + { + case IDYES: + pAssetDesc->monoBehaviourInfoAsked = true; + if (GetAllScriptInformation(pThis->appContext, typeAssets)) + reloadAsset = true; + else + closeAsset = true; + break; + case IDNO: + break; + case IDCANCEL: + default: + closeAsset = true; + break; + } + } + } + if (closeAsset) + { + pThis->loadedAssets.erase((*ppInstanceTask)->itDeserializeDesc); + if (!pThis->isDestroyed && pThis->loadedAssets.size() == 1) + { + assert(&pThis->loadedAssets.front() == pAssetDesc); + pThis->assetListDialog.removeModifyDialog(pThis); + pThis->isDestroyed = true; + } + } + else if (reloadAsset) + { + pAssetDesc->pLoadTask = nullptr; + pAssetDesc->pAssetInstance.reset(); + pThis->startLoadTask((*ppInstanceTask)->itDeserializeDesc, (*ppInstanceTask)->pDialog); + } + else + { + assert(pAssetDesc->pAssetInstance != nullptr); + AssetTypeValueField *pBaseField = pAssetDesc->pAssetInstance->GetBaseField(); + MC_HTREELISTITEM hBaseItem = pThis->addTreeItems(pAssetDesc, pAssetDesc->parentItem, 0, &pBaseField, 1, false); + pAssetDesc->baseItem = hBaseItem; + pThis->loadedAssetsByBaseItem.insert(std::make_pair(hBaseItem, (*ppInstanceTask)->itDeserializeDesc)); + } + delete ppInstanceTask; +} +typedef void(*CallbackProc)(uintptr_t,uintptr_t); + +void AssetViewModifyDialog::OnCompletion(std::shared_ptr& pTask, TaskResult result) +{ + //Not necessarily run from the main thread. + if (std::shared_ptr pInstanceTask = std::dynamic_pointer_cast(pTask)) + { + if (pInstanceTask->pDialog.get() != this) + return; + assert(pInstanceTask->itDeserializeDesc->pLoadTask == pTask.get()); + pInstanceTask->itDeserializeDesc->loadTaskResult = result; + + CallbackProc callback = onCompletionMainThread; + appContext.signalMainThread(AppContextMsg_DoMainThreadCallback, + new std::tuple( + callback, + reinterpret_cast(this), + reinterpret_cast(new std::shared_ptr(pInstanceTask)))); + } +} +bool AssetViewModifyDialog::init(std::shared_ptr &selfPtr, HWND hParentWnd) +{ + assert(selfPtr.get() == this); + assert(this->loadedAssets.size() == 1); + assert(this->loadedAssets.front().pLoadTask == nullptr && + this->loadedAssets.front().pAssetInstance == nullptr); + if (this->loadedAssets.size() != 1) + return false; + return startLoadTask(this->loadedAssets.begin(), selfPtr); +} +std::list::iterator + AssetViewModifyDialog::findOrPrepareLoad(AssetIdentifier asset, MC_HTREELISTITEM parentItem, AssetDeserializeDesc *pParentAsset, + AssetTypeValueField *pPPtrField) +{ + if (!asset.resolve(appContext)) + return loadedAssets.end(); + unsigned int fileID = asset.pFile->getFileID(); + pathid_t pathID = asset.pathID; + + auto mapIt = loadedAssetsByPPtr.find(AssetAbsPPtr(fileID, pathID)); + if (mapIt != loadedAssetsByPPtr.end()) + return mapIt->second; + + auto assetListEntryIt = loadedAssets.insert(loadedAssets.end(), AssetDeserializeDesc()); + assetListEntryIt->asset = std::move(asset); + assetListEntryIt->parentItem = parentItem; + assetListEntryIt->pParent = pParentAsset; + if (pParentAsset != nullptr) + { + assert(pPPtrField != nullptr); + pParentAsset->children.push_back(std::make_pair(pPPtrField, assetListEntryIt)); + } + else + assert(parentItem == MC_TLI_ROOT); + + loadedAssetsByPPtr.emplace(std::make_pair(AssetAbsPPtr(fileID, pathID), assetListEntryIt)); + if (pPPtrField != nullptr) + loadedAssetsByPPtrField.emplace(std::make_pair(pPPtrField, assetListEntryIt)); + return assetListEntryIt; +} +bool AssetViewModifyDialog::startLoadTask(std::list::iterator assetEntry, std::shared_ptr selfPtr) +{ + if (assetEntry->pLoadTask != nullptr || assetEntry->pAssetInstance) + return true; + if (this->isDestroyed) + return false; + if (!selfPtr) + { + selfPtr = std::static_pointer_cast(this->assetListDialog.getModifyDialogRef(this)); + if (!selfPtr) + return false; + } + + std::shared_ptr pLoadTask = std::make_shared(std::move(selfPtr), assetEntry); + assetEntry->pLoadTask = pLoadTask.get(); + + if (this->registeredCallbackCounter++ == 0) + appContext.taskManager.addCallback(this); + if (appContext.taskManager.enqueue(pLoadTask)) + return true; + if (--this->registeredCallbackCounter == 0) + appContext.taskManager.removeCallback(this); + + assetEntry->pLoadTask = nullptr; + return false; +} +AssetViewModifyDialog::AssetDeserializeDesc *AssetViewModifyDialog::getAssetDescForItem(MC_HTREELISTITEM item) +{ + HWND hTree = GetDlgItem(this->hDialog, IDC_TYPETREE); + do { + auto mapIt = this->loadedAssetsByBaseItem.find(item); + if (mapIt != this->loadedAssetsByBaseItem.end()) + return &*mapIt->second; + item = reinterpret_cast(SendMessage(hTree, MC_TLM_GETNEXTITEM, MC_TLGN_PARENT, reinterpret_cast(item))); + } while (item != NULL); + return nullptr; +} +//Called when the user requests to close the tab. +//Returns true if there are unsaved changes, false otherwise. +//If the function will return true and applyable is not null, +// *applyable will be set to true iff applyNow() is assumed to succeed without further interaction +// (e.g. all fields in the dialog have a valid value, ...). +//The caller uses this info to decide whether and how it should display a confirmation dialog before proceeding. +bool AssetViewModifyDialog::hasUnappliedChanges(bool *applyable) +{ + bool ret = false; + for (auto assetIt = this->loadedAssets.begin(); assetIt != this->loadedAssets.end(); ++assetIt) + { + if (assetIt->hasChanged && assetIt->pAssetInstance != nullptr + && assetIt->pAssetInstance->GetBaseField(0) != nullptr + && assetIt->asset.pFile != nullptr) + { + ret = true; + break; + } + } + if (ret && applyable) *applyable = true; + return ret; +} +template +bool AssetViewModifyDialog::applyChangesIn(ForwardIt assetBegin, ForwardIt assetEnd) +{ + //Check for conflicting changes, e.g. if the user changed the same asset in a different tab without closing this one. + bool hasConflictingChanges = false; + for (auto assetIt = assetBegin; assetIt != assetEnd; ++assetIt) + { + AssetDeserializeDesc &assetDesc = *assetIt; + if (assetDesc.hasChanged && assetDesc.pAssetInstance != nullptr + && assetDesc.pAssetInstance->GetBaseField(0) != nullptr + && assetDesc.asset.pFile != nullptr) + { + std::shared_ptr pCurrentReplacer = assetDesc.asset.pFile->getReplacer(assetDesc.asset.pathID); + if (pCurrentReplacer.get() != assetDesc.asset.pReplacer.get()) + { + hasConflictingChanges = true; + break; + } + } + } + if (hasConflictingChanges) + { + HWND hParent = this->hDialog; + if (hParent == NULL) + hParent = this->appContext.getMainWindow().getWindow(); + std::string message = std::string("Applying changes in the tab <") + this->getTabName() + + std::string("> will overwrite existing changes.\n Do you want to proceed anyway?"); + auto messageT = unique_MultiByteToTCHAR(message.c_str()); + switch (MessageBox(hParent, messageT.get(), TEXT("Asset Bundle Extractor"), MB_YESNO)) + { + case IDYES: + break; + case IDNO: + return false; + } + } + for (auto assetIt = assetBegin; assetIt != assetEnd; ++assetIt) + { + AssetDeserializeDesc &assetDesc = *assetIt; + if (assetDesc.hasChanged && assetDesc.pAssetInstance != nullptr + && assetDesc.pAssetInstance->GetBaseField(0) != nullptr + && assetDesc.asset.pFile != nullptr) + { + IAssetsWriterToMemory *pWriter = Create_AssetsWriterToMemory(); + QWORD size = assetDesc.pAssetInstance->GetBaseField(0)->Write(pWriter, 0, assetDesc.asset.isBigEndian()); + size_t bufferSize = 0; + void *buffer = nullptr; + if (pWriter->GetBuffer(buffer, bufferSize)) + { + AssetsEntryReplacer *pReplacer = MakeAssetModifierFromMemory( + assetDesc.asset.pFile->getFileID(), assetDesc.asset.pathID, + assetDesc.asset.getClassID(), assetDesc.asset.getMonoScriptID(), + buffer, bufferSize, Free_AssetsWriterToMemory_DynBuf); + assetDesc.asset.pReplacer.reset(pReplacer); + assetDesc.asset.pFile->addReplacer(assetDesc.asset.pReplacer, this->appContext); + assetDesc.hasChanged = false; + assert(size == bufferSize); + } + else + assert(false); + } + } + return true; +} +//std::vector::iterator>> +//Called when the user requests to apply the changes (e.g. selecting Apply, Save or Save All in the menu). +//Returns whether the changes have been applied; +// if true, the caller may continue closing the AssetModifyDialog. +// if false, the caller may stop closing the AssetModifyDialog. +//Note: applyChanges() is expected to notify the user about errors (e.g. via MessageBox). +bool AssetViewModifyDialog::applyChanges() +{ + return this->applyChangesIn(this->loadedAssets.begin(), this->loadedAssets.end()); +} +std::string AssetViewModifyDialog::getTabName() +{ + if (this->assetName.empty()) + { + if (this->loadedAssets.size() >= 1) + { + AssetIdentifier &asset = this->loadedAssets.front().asset; + return std::format("View Asset (FileID {}, PathID {})", + static_cast(asset.pFile->getFileID()), std::to_string(asset.pathID)); + } + return "View Asset"; + } + if (this->loadedAssets.size() >= 1) + { + AssetIdentifier& asset = this->loadedAssets.front().asset; + return std::format("View Asset \"{}\" (FileID {}, PathID {})", this->assetName, + static_cast(asset.pFile->getFileID()), std::to_string(asset.pathID)); + } + return "View Asset \"" + this->assetName + "\""; +} +HWND AssetViewModifyDialog::getWindowHandle() +{ + return hDialog; +} +//Called for unhandled WM_COMMAND messages. Returns true if this dialog has handled the request, false otherwise. +bool AssetViewModifyDialog::onCommand(WPARAM wParam, LPARAM lParam) +{ + int wmId = LOWORD(wParam); + return false; +} +void AssetViewModifyDialog::onHotkey(ULONG message, DWORD keyCode) //message : currently only WM_KEYDOWN; keyCode : VK_F3 for instance +{ + +} +void AssetViewModifyDialog::onShow(HWND hParentWnd) +{ + if (!this->hDialog) + { + this->hDialog = CreateDialogParam(appContext.getMainWindow().getHInstance(), MAKEINTRESOURCE(IDD_ASSETVIEW), hParentWnd, AssetViewProc, (LPARAM)this); + } + else + { + SetParent(this->hDialog, hParentWnd); + ShowWindow(this->hDialog, SW_SHOW); + } +} +//Called when the dialog is to be hidden, either because of a tab switch or while closing the tab. +void AssetViewModifyDialog::onHide() +{ + if (this->hDialog) + { + ShowWindow(this->hDialog, SW_HIDE); + SetParent(this->hDialog, NULL); + //SendMessage(this->hDialog, WM_CLOSE, 0, 0); + } +} +//Called when the tab is about to be destroyed. +void AssetViewModifyDialog::onDestroy() +{ + this->isDestroyed = true; +} + +INT_PTR CALLBACK AssetViewModifyDialog::AssetViewProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + INT_PTR ret = (INT_PTR)FALSE; + AssetViewModifyDialog *pThis = (AssetViewModifyDialog*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + switch (message) + { + case WM_CLOSE: + if (pThis) + pThis->hDialog = NULL; + DestroyWindow(hDlg); + ret = (INT_PTR)TRUE; + break; + case WM_INITDIALOG: + { + SetWindowLongPtr(hDlg, GWLP_USERDATA, lParam); + pThis = (AssetViewModifyDialog*)lParam; + pThis->testItemHasTextcallbackActive = false; + { + HWND hTree = GetDlgItem(hDlg, IDC_TYPETREE); + MC_TLCOLUMN col; + col.fMask = MC_TLCF_TEXT | MC_TLCF_WIDTH; + col.pszText = const_cast(_T("Field")); + col.cx = 540; + col.cchTextMax = static_cast(_tcslen(col.pszText)) + 1; + SendMessage(hTree, MC_TLM_INSERTCOLUMN, 0, (LPARAM)&col); + col.pszText = const_cast(_T("Value")); + col.cchTextMax = static_cast(_tcslen(col.pszText)) + 1; + col.cx = 160; + SendMessage(hTree, MC_TLM_INSERTCOLUMN, 1, (LPARAM)&col); + + SetWindowSubclass(hTree, AssetTreeListSubclassProc, 0, reinterpret_cast(pThis)); + } + PostMessage(hDlg, WM_SIZE, 0, 0); + } + break; + case WM_SIZE: + { + RECT client = {}; + GetClientRect(hDlg, &client); + LONG clientWidth = client.right-client.left; + LONG clientHeight = client.bottom-client.top; + SetWindowPos(GetDlgItem(hDlg, IDC_TYPETREE), HWND_TOP, 5, 5, clientWidth - 10, clientHeight - 10, + SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); + } + break; + case WM_DRAWITEM: + { + + } + break; + case WM_CONTEXTMENU: + if (reinterpret_cast(wParam) == GetDlgItem(hDlg, IDC_TYPETREE)) + { + HWND hTree = reinterpret_cast(wParam); + POINT screenClickPos; + screenClickPos.x = GET_X_LPARAM(lParam); //Screen area + screenClickPos.y = GET_Y_LPARAM(lParam); + bool foundHitItem = false; + MC_TLHITTESTINFO hitTestInfo = {}; + hitTestInfo.pt.x = screenClickPos.x; + hitTestInfo.pt.y = screenClickPos.y; + if (screenClickPos.x == -1 && screenClickPos.y == -1) + { + hitTestInfo.hItem = reinterpret_cast(SendMessage(hTree, MC_TLM_GETNEXTITEM, MC_TLGN_CARET, NULL)); + hitTestInfo.iSubItem = 0; + if (hitTestInfo.hItem != NULL) + { + RECT itemRect = {}; + itemRect.left = MC_TLIR_SELECTBOUNDS; + if (SendMessage(hTree, MC_TLM_GETITEMRECT, reinterpret_cast(hitTestInfo.hItem), reinterpret_cast(&itemRect))) + { + screenClickPos.x = itemRect.left + (itemRect.right - itemRect.left) / 2; + screenClickPos.y = itemRect.top + (itemRect.bottom - itemRect.top) / 2; + if (ClientToScreen(hTree, &screenClickPos)) + foundHitItem = true; + } + } + } + else + { + if (ScreenToClient(hTree, &hitTestInfo.pt) && + SendMessage(hTree, MC_TLM_HITTEST, 0, reinterpret_cast(&hitTestInfo)) != NULL) + { + //Select this item. + MC_TLITEM item; + item.fMask = MC_TLIF_STATE; + item.state = MC_TLIS_SELECTED; + item.stateMask = MC_TLIS_SELECTED; + SendMessage(hTree, MC_TLM_SETITEM, + reinterpret_cast(hitTestInfo.hItem), + reinterpret_cast(&item)); + foundHitItem = true; + } + } + if (foundHitItem) + { + assert(hitTestInfo.hItem != NULL); + if (pThis->hCurEditPopup != NULL) + pThis->doCloseEditPopup(); + + MC_TLITEM item; + item.fMask = MC_TLIF_PARAM; + item.lParam = 0; + if (!SendMessage(hTree, MC_TLM_GETITEM, + reinterpret_cast(hitTestInfo.hItem), + reinterpret_cast(&item))) + break; + if (item.lParam == 0 || hitTestInfo.hItem == (MC_HTREELISTITEM)SendMessage(hTree, MC_TLM_GETNEXTITEM, MC_TLGN_ROOT, NULL)) + { + //if (hitTestInfo.iSubItem != 0) + // break; + //Array index PPtr [View asset] node or base node + MC_HTREELISTITEM hParentItem = reinterpret_cast( + SendMessage(hTree, MC_TLM_GETNEXTITEM, MC_TLGN_PARENT, + reinterpret_cast(hitTestInfo.hItem))); + item.fMask = MC_TLIF_PARAM; + item.lParam = NULL; + if ((hParentItem != NULL) && (!SendMessage(hTree, MC_TLM_GETITEM, + reinterpret_cast(hParentItem), + reinterpret_cast(&item)) + || item.lParam == NULL)) + break; + AssetTypeValueField *pParentValueField = reinterpret_cast(item.lParam); + AssetDeserializeDesc *pAssetDesc = pThis->getAssetDescForItem(hParentItem ? hParentItem : hitTestInfo.hItem); + if (!pAssetDesc) + break; + bool isRootItem = (hParentItem == NULL); + + pThis->openContextMenuPopup(hTree, screenClickPos, + isRootItem ? MC_TLI_ROOT : hitTestInfo.hItem, nullptr, + hParentItem, pParentValueField, + pAssetDesc); + break; + } + else + { + //Normal nodes with a linked AssetTypeValueField. + AssetTypeValueField *pValueField = reinterpret_cast(item.lParam); + AssetDeserializeDesc *pAssetDesc = pThis->getAssetDescForItem(hitTestInfo.hItem); + if (!pAssetDesc) + break; + + pThis->openContextMenuPopup(hTree, screenClickPos, + hitTestInfo.hItem, pValueField, + NULL, nullptr, //Don't retrieve the parent information (since it is not needed) + pAssetDesc); + } + } + } + break; + case WM_NOTIFY: + { + NMHDR *pNotifyHeader = reinterpret_cast(lParam); + switch (pNotifyHeader->code) + { + case MC_TLN_GETDISPINFO: + if (pNotifyHeader->hwndFrom == GetDlgItem(hDlg, IDC_TYPETREE) && !pThis->ignoreExpandNotifications) + { + MC_NMTLDISPINFO *pNotify = reinterpret_cast(lParam); + if (pThis->testItemHasTextcallbackActive) + pThis->testItemHasTextcallbackResult = (pNotify->item.fMask & MC_TLIF_TEXT) ? true : false; + assert((pNotify->item.fMask & ~MC_TLIF_TEXT) == 0); + if (pNotify->item.fMask & MC_TLIF_TEXT) + { + MC_HTREELISTITEM hArrayItem = reinterpret_cast( + SendMessage(pNotifyHeader->hwndFrom, MC_TLM_GETNEXTITEM, MC_TLGN_PARENT, reinterpret_cast(pNotify->hItem))); + if (hArrayItem == NULL) + break; + MC_TLITEM item; + item.fMask = MC_TLIF_PARAM; + item.lParam = 0; + if (!SendMessage(pNotifyHeader->hwndFrom, MC_TLM_GETITEM, + reinterpret_cast(hArrayItem), + reinterpret_cast(&item)) || item.lParam == NULL) + break; + AssetDeserializeDesc *pAssetDesc = pThis->getAssetDescForItem(hArrayItem); + if (pAssetDesc == nullptr) + break; + auto mappingIt = pAssetDesc->arrayMappingsByArray.find(reinterpret_cast(item.lParam)); + if (mappingIt == pAssetDesc->arrayMappingsByArray.end()) + break; + auto indexMapIt = mappingIt->second.itemToIndexMap.find(pNotify->hItem); + if (indexMapIt == mappingIt->second.itemToIndexMap.end()) + break; + + //Get the text of an array index item. + _stprintf_s(pThis->itemTextcallbackBuf, TEXT("[%u]"), indexMapIt->second); + pNotify->item.pszText = pThis->itemTextcallbackBuf; + } + } + break; + case MC_TLN_EXPANDED: + if (pNotifyHeader->hwndFrom == GetDlgItem(hDlg, IDC_TYPETREE) && !pThis->ignoreExpandNotifications) + { + MC_NMTREELIST *pNotify = reinterpret_cast(lParam); + if (pNotify->action == MC_TLE_EXPAND) + { + //Check if the child tree items already exist + if (SendMessage(pNotify->hdr.hwndFrom, MC_TLM_GETNEXTITEM, MC_TLGN_CHILD, reinterpret_cast(pNotify->hItemNew)) != NULL) + break; + if (pNotify->lParamNew == 0) + { + if (pThis->testItemHasTextcallback(pNotify->hdr.hwndFrom, pNotify->hItemNew)) + { + //Currently, only array index items use MC_LPSTR_TEXTCALLBACK. + //Array index items should always have a child in the tree list. + assert(false); + break; + } + //Special case - PPtr "View asset" node + MC_HTREELISTITEM hPPtrNode = reinterpret_cast( + SendMessage(pNotify->hdr.hwndFrom, MC_TLM_GETNEXTITEM, MC_TLGN_PARENT, reinterpret_cast(pNotify->hItemNew))); + assert(hPPtrNode != NULL); + if (hPPtrNode == NULL) + break; + //Find the asset behind this PPtr. + AssetDeserializeDesc *pAssetDesc = pThis->getAssetDescForItem(hPPtrNode); + if (pAssetDesc == nullptr) + break; + MC_TLITEM item; + item.fMask = MC_TLIF_PARAM; + item.lParam = 0; + SendMessage(pNotify->hdr.hwndFrom, MC_TLM_GETITEM, + reinterpret_cast(hPPtrNode), + reinterpret_cast(&item)); + if (AssetTypeValueField *pPPtrField = reinterpret_cast(item.lParam)) + { + //Retrieve the relative File/Path ID. + AssetTypeValueField *pFileIDField = pPPtrField->Get("m_FileID"); + AssetTypeValueField *pPathIDField = pPPtrField->Get("m_PathID"); + if (pFileIDField->GetValue() != nullptr && pPathIDField->GetValue() != nullptr) + { + //Initialize the AssetDeserializeDesc structure and add it to the list, + // or find an existing structure for the target asset. + auto newAssetDescIt = pThis->findOrPrepareLoad(AssetIdentifier( + pAssetDesc->asset.pFile, //Reference source is the file containing the referring asset + pFileIDField->GetValue()->AsUInt(), //Relative File ID for the referred asset + pPathIDField->GetValue()->AsUInt64()), + pNotify->hItemNew, pAssetDesc, pPPtrField); + if (newAssetDescIt == pThis->loadedAssets.end()) + { + MessageBox(hDlg, TEXT("Unable to resolve the target asset."), TEXT("Asset Bundle Extractor"), MB_ICONERROR); + break; + } + if (newAssetDescIt->pLoadTask == nullptr && !newAssetDescIt->pAssetInstance) + { + //Enqueue a task to load the asset. + pThis->startLoadTask(newAssetDescIt); + } + else + { + //findOrPrepareLoad returned an existing asset that is either loading or has loaded already. + MC_HTREELISTITEM itemToFocus = newAssetDescIt->parentItem; + if (itemToFocus == MC_TLI_ROOT) + itemToFocus = reinterpret_cast( + SendMessage(pNotify->hdr.hwndFrom, MC_TLM_GETNEXTITEM, MC_TLGN_ROOT, NULL)); + if (itemToFocus == NULL) + break; + //Collapse the just expanded item again. + SendMessage(pNotify->hdr.hwndFrom, MC_TLM_EXPAND, MC_TLE_COLLAPSE, reinterpret_cast(pNotify->hItemNew)); + //Select and go to an existing "View asset" node for the target asset. + item.fMask = MC_TLIF_STATE; + item.state = MC_TLIS_SELECTED; + item.stateMask = MC_TLIS_SELECTED; + SendMessage(pNotify->hdr.hwndFrom, MC_TLM_SETITEM, + reinterpret_cast(itemToFocus), + reinterpret_cast(&item)); + SendMessage(pNotify->hdr.hwndFrom, MC_TLM_ENSUREVISIBLE, 0, reinterpret_cast(itemToFocus)); + } + } + } + } + else + { + AssetTypeValueField *pField = reinterpret_cast(pNotify->lParamNew); + + //Find the asset. + AssetDeserializeDesc *pAssetDesc = pThis->getAssetDescForItem(pNotify->hItemNew); + if (pAssetDesc == nullptr) + break; + + bool isPPtr = false; + const char *typeName = pField->GetType().c_str(); + if (typeName && !strncmp(typeName, "PPtr<", 5)) + isPPtr = true; + pThis->ignoreExpandNotifications = true; + //Collapse the just expanded item again to prevent scrollbar redraw. + SendMessage(pNotify->hdr.hwndFrom, MC_TLM_EXPAND, MC_TLE_COLLAPSE, reinterpret_cast(pNotify->hItemNew)); + + pThis->addTreeItems(pAssetDesc, pNotify->hItemNew, pNotify->lParamNew, pField->GetChildrenList(), pField->GetChildrenCount(), isPPtr); + + //Expand the item to redraw. + SendMessage(pNotify->hdr.hwndFrom, MC_TLM_EXPAND, MC_TLE_EXPAND, reinterpret_cast(pNotify->hItemNew)); + pThis->ignoreExpandNotifications = false; + } + } + } + break; + //case UDN_DELTAPOS: + // break; + } + } + break; + } + return ret; +} +bool AssetViewModifyDialog::testItemHasTextcallback(HWND hTree, MC_HTREELISTITEM hItem) +{ + MC_TLITEM item; + item.fMask = MC_TLIF_TEXT; + item.pszText = nullptr; + item.cchTextMax = 0; + this->testItemHasTextcallbackResult = false; + this->testItemHasTextcallbackActive = true; + SendMessage(hTree, MC_TLM_GETITEM, + reinterpret_cast(hItem), + reinterpret_cast(&item)); + this->testItemHasTextcallbackActive = false; + return this->testItemHasTextcallbackResult; +} +static void FreeFieldRecursively(AssetTypeValueField *pField, std::unordered_set &allocatedMemory) +{ + if (pField->GetValue() && pField->GetValue()->GetType() == ValueType_String) + { + auto memoryEntryIt = allocatedMemory.find(reinterpret_cast(pField->GetValue()->AsString())); + if (memoryEntryIt != allocatedMemory.end()) + { + delete[] *memoryEntryIt; + allocatedMemory.erase(memoryEntryIt); + } + } + for (DWORD i = 0; i < pField->GetChildrenCount(); i++) + { + FreeFieldRecursively(pField->Get(i), allocatedMemory); + } + AssetTypeValueField **pChildList = pField->GetChildrenList(); + auto fieldMemoryEntryIt = allocatedMemory.find(reinterpret_cast(pField)); + if (fieldMemoryEntryIt != allocatedMemory.end()) + { + delete[] *fieldMemoryEntryIt; + allocatedMemory.erase(fieldMemoryEntryIt); + } + auto childListMemoryEntryIt = allocatedMemory.find(reinterpret_cast(pChildList)); + if (childListMemoryEntryIt != allocatedMemory.end()) + { + delete[] *childListMemoryEntryIt; + allocatedMemory.erase(childListMemoryEntryIt); + } +} +std::vector::iterator>> +AssetViewModifyDialog::findAllSubtreeAssets( + std::list::iterator itBaseAssetDesc, + bool &hasUnappliedChanges, + AssetTypeValueField *pBasePPtrField) +{ + if (pBasePPtrField == nullptr && itBaseAssetDesc->pParent != nullptr) + { + bool foundSelf = false; + for (size_t k = 0; k < itBaseAssetDesc->pParent->children.size(); k++) + { + if (&*itBaseAssetDesc->pParent->children[k].second == &*itBaseAssetDesc) + { + pBasePPtrField = itBaseAssetDesc->pParent->children[k].first; + foundSelf = true; + break; + } + } + assert(foundSelf); + } + //Find all child assets of the asset. + hasUnappliedChanges = false; + std::vector::iterator>> + recursiveChildren(1, std::make_pair(pBasePPtrField, itBaseAssetDesc)); + std::vector idxStack(1, 0); //Initialize with {0} + AssetDeserializeDesc *pCurDesc = &*itBaseAssetDesc; + do { + assert(pCurDesc != nullptr); + size_t i = idxStack.back(); + idxStack.pop_back(); + if (pCurDesc->hasChanged) + hasUnappliedChanges = true; + if (i < pCurDesc->children.size()) + { + assert(pCurDesc->children[i].second->pParent == pCurDesc); + idxStack.push_back(i + 1); + idxStack.push_back(0); + recursiveChildren.push_back(pCurDesc->children[i]); + pCurDesc = &*pCurDesc->children[i].second; + } + else + pCurDesc = pCurDesc->pParent; + } while (!idxStack.empty()); + return recursiveChildren; +} +void AssetViewModifyDialog::closeAllSubtreeAssets(HWND hTree, + std::vector::iterator>> subtreeAssets, + AssetDeserializeDesc *pParentAsset) +{ + assert(pParentAsset->pLoadTask == nullptr); + for (size_t i = 0; i < subtreeAssets.size(); i++) + { + assert(subtreeAssets[i].second->pParent != nullptr); + if (subtreeAssets[i].second->pParent == pParentAsset) + { + //Delete the asset and its children from the tree list. + SendMessage(hTree, MC_TLM_DELETEITEM, MC_TLDI_NONOTIFY, reinterpret_cast(subtreeAssets[i].second->baseItem)); + assert(subtreeAssets[i].second->pLoadTask == nullptr); + bool foundSelf = false; + for (size_t k = 0; k < subtreeAssets[i].second->pParent->children.size(); k++) + { + if (&*subtreeAssets[i].second->pParent->children[k].second == &*subtreeAssets[i].second) + { + //Delete this asset from its parent's child list. + subtreeAssets[i].second->pParent->children.erase(subtreeAssets[i].second->pParent->children.begin() + k); + foundSelf = true; + break; + } + } + assert(foundSelf); + } + } + //Delete this asset from internal lists and maps. + for (size_t i = 0; i < subtreeAssets.size(); i++) + { + std::list::iterator itCurAsset = subtreeAssets[i].second; + auto curAssetByBaseItemIt = this->loadedAssetsByBaseItem.find(itCurAsset->baseItem); + if (curAssetByBaseItemIt != this->loadedAssetsByBaseItem.end()) + { + assert(itCurAsset->pLoadTask == nullptr); + assert(&*curAssetByBaseItemIt->second == &*itCurAsset); + this->loadedAssetsByBaseItem.erase(curAssetByBaseItemIt); + } + else + assert(itCurAsset->pLoadTask != nullptr); + + auto curAssetByPPtrIt = this->loadedAssetsByPPtr.find( + AssetAbsPPtr(itCurAsset->asset.pFile->getFileID(), + itCurAsset->asset.pathID)); + assert(curAssetByPPtrIt != this->loadedAssetsByPPtr.end()); + if (curAssetByPPtrIt != this->loadedAssetsByPPtr.end()) + { + assert(&*curAssetByPPtrIt->second == &*itCurAsset); + this->loadedAssetsByPPtr.erase(curAssetByPPtrIt); + } + + if (subtreeAssets[i].first != nullptr) + { + auto curAssetByPPtrFieldIt = this->loadedAssetsByPPtrField.find(subtreeAssets[i].first); + assert(curAssetByPPtrFieldIt != this->loadedAssetsByPPtrField.end()); + if (curAssetByPPtrFieldIt != this->loadedAssetsByPPtrField.end()) + { + assert(&*curAssetByPPtrFieldIt->second == &*itCurAsset); + this->loadedAssetsByPPtrField.erase(curAssetByPPtrFieldIt); + } + } + + if (itCurAsset->pLoadTask != nullptr) + { + //The asset will be deleted once the task is finished. + itCurAsset->pendingClose = true; + itCurAsset->pParent = nullptr; + } + else + { + //Delete the asset. + this->loadedAssets.erase(itCurAsset); + } + } +} +void AssetViewModifyDialog::doCloseEditPopup() +{ + bool updateValueText = false; + bool addTreeListChildren = false; + size_t firstChildToAdd = 0; + union { + uint64_t oldValue64u; + int64_t oldValue64s; + }; + bool hasOldIntValue = false; + HWND hTree = GetDlgItem(this->hDialog, IDC_TYPETREE); + if (this->hCurEditPopupUpDown != NULL) + { + BOOL gotValue = FALSE; + int value = static_cast(SendMessage(this->hCurEditPopupUpDown, UDM_GETPOS32, 0, reinterpret_cast(&gotValue))); + if (this->pEditValueField != nullptr && this->pEditValueField->GetValue() != nullptr) + { + switch (this->pEditValueField->GetValue()->GetType()) + { + case ValueType_Int8: + case ValueType_UInt8: + case ValueType_Int16: + case ValueType_UInt16: + assert(this->iEditPopupSubItem == 1); + if (this->iEditPopupSubItem != 1) + break; + oldValue64s = this->pEditValueField->GetValue()->AsInt(); + hasOldIntValue = true; + if (value != this->pEditValueField->GetValue()->AsInt()) + { + this->pEditValueField->GetValue()->Set(&value); + updateValueText = true; + } + break; + case ValueType_Array: + if (this->iEditPopupSubItem == 0 && value >= 0 && static_cast(value) < this->pEditValueField->GetValue()->AsArray()->size) + { + //Move array element. + this->moveArrayItem(hTree, this->pEditAssetDesc, this->pEditValueField, this->hEditPopupItem, static_cast(value)); + } + else if (this->iEditPopupSubItem == 1 && value > 0 && static_cast(value) > this->pEditValueField->GetValue()->AsArray()->size) + { + //Create one or several array elements. + DWORD oldSize = this->pEditValueField->GetValue()->AsArray()->size; + if (this->pEditValueField->GetTemplateField() == nullptr + || !this->pEditValueField->GetTemplateField()->isArray + || this->pEditValueField->GetTemplateField()->children.size() != 2) + { + MessageBox(this->hDialog, TEXT("Invalid array type information."), TEXT("Asset Bundle Extractor"), MB_ICONERROR); + break; + } + AssetTypeTemplateField *pArrayDataTemplate = &this->pEditValueField->GetTemplateField()->children[1]; + AssetTypeValueField **oldChildrenList = this->pEditValueField->GetChildrenList(); + std::vector> newAllocatedMemory; + //Allocate the new, larger child list. + bool failed = false; + uint8_t *newChildrenListMemory = new uint8_t[sizeof(AssetTypeValueField*) * value]; + AssetTypeValueField **newChildrenList = reinterpret_cast(newChildrenListMemory); + newAllocatedMemory.push_back(std::unique_ptr(newChildrenListMemory)); + //Copy the old info to the new list. + memcpy(newChildrenList, oldChildrenList, sizeof(AssetTypeValueField*) * oldSize); + for (DWORD i = oldSize; i < static_cast(value); i++) + { + //Generate new empty array elements. + newChildrenList[i] = CreateEmptyValueFieldFromTemplate(pArrayDataTemplate, newAllocatedMemory); + if (newChildrenList[i] == nullptr) + { + failed = true; + break; + } + } + if (failed) + { + MessageBox(this->hDialog, TEXT("Failed to create new array elements."), TEXT("Asset Bundle Extractor"), MB_ICONERROR); + break; + } + //Update the child list. + this->pEditValueField->SetChildrenList(newChildrenList, static_cast(value)); + updateValueText = true; + addTreeListChildren = true; + firstChildToAdd = this->pEditValueField->GetValue()->AsArray()->size; + this->pEditValueField->GetValue()->AsArray()->size = static_cast(value); + //Free the old child list memory (if possible). + auto oldListIt = this->pEditAssetDesc->instanceModificationBuffers.find(reinterpret_cast(oldChildrenList)); + if (oldListIt != this->pEditAssetDesc->instanceModificationBuffers.end()) + { + delete[] *oldListIt; + this->pEditAssetDesc->instanceModificationBuffers.erase(oldListIt); + } + //Add all new allocated memory to the set. + for (std::unique_ptr& pMem : newAllocatedMemory) + this->pEditAssetDesc->instanceModificationBuffers.insert(pMem.release()); + //Insert the new array elements to the tree list control. + addTreeListChildren = true; + firstChildToAdd = oldSize; + } + break; + } + } + } + else if (this->hCurEditPopup != NULL) + { + //Retrieve the value from the edit. + int textLen = Edit_GetTextLength(this->hCurEditPopup); + if (textLen >= 0 && textLen < INT_MAX) + { + std::unique_ptr textBuf(new TCHAR[textLen + 1]); + int nCharacters = Edit_GetText(this->hCurEditPopup, textBuf.get(), textLen + 1); + if (nCharacters <= textLen) + textBuf[nCharacters] = 0; + else + textBuf[textLen] = 0; + TCHAR *endPtr = nullptr; + switch (this->pEditValueField->GetValue()->GetType()) + { + case ValueType_Int32: + case ValueType_Int64: + { + assert(this->iEditPopupSubItem == 1); + if (this->iEditPopupSubItem != 1) + break; + int64_t value = _tcstoi64(textBuf.get(), &endPtr, 0); + if (endPtr == textBuf.get() || endPtr == nullptr) + break; + oldValue64s = this->pEditValueField->GetValue()->AsInt64(); + hasOldIntValue = true; + if (value != this->pEditValueField->GetValue()->AsInt64()) + { + this->pEditValueField->GetValue()->Set(&value); + updateValueText = true; + } + } + break; + case ValueType_UInt32: + case ValueType_UInt64: + { + assert(this->iEditPopupSubItem == 1); + if (this->iEditPopupSubItem != 1) + break; + uint64_t value = _tcstoui64(textBuf.get(), &endPtr, 0); + if (endPtr == textBuf.get() || endPtr == nullptr) + break; + oldValue64u = this->pEditValueField->GetValue()->AsUInt64(); + hasOldIntValue = true; + if (value != this->pEditValueField->GetValue()->AsUInt64()) + { + this->pEditValueField->GetValue()->Set(&value); + updateValueText = true; + } + } + break; + case ValueType_Float: + { + assert(this->iEditPopupSubItem == 1); + if (this->iEditPopupSubItem != 1) + break; + double valueD = _tcstod(textBuf.get(), &endPtr); + if (endPtr == textBuf.get() || endPtr == nullptr) + break; + float value = static_cast(valueD); + if (value != this->pEditValueField->GetValue()->AsFloat()) + { + this->pEditValueField->GetValue()->Set(&value); + updateValueText = true; + } + } + break; + case ValueType_Double: + { + assert(this->iEditPopupSubItem == 1); + if (this->iEditPopupSubItem != 1) + break; + double value = _tcstod(textBuf.get(), &endPtr); + if (endPtr == textBuf.get() || endPtr == nullptr) + break; + if (value != this->pEditValueField->GetValue()->AsDouble()) + { + this->pEditValueField->GetValue()->Set(&value); + updateValueText = true; + } + } + break; + case ValueType_String: + { + assert(this->iEditPopupSubItem == 1); + if (this->iEditPopupSubItem != 1) + break; + size_t newLen = 0; + auto pNewUTF8 = unique_TCHARToMultiByte(textBuf.get(), newLen); + char *oldString = this->pEditValueField->GetValue()->AsString(); + if ((oldString == nullptr && newLen == 0) + || !strcmp(oldString, pNewUTF8.get())) + break; + //Update the string value in the field, and manage the memory allocations. + setStringValue(this->pEditValueField, this->pEditAssetDesc, pNewUTF8.get(), newLen); + updateValueText = true; + } + break; + } + } + } + if (updateValueText + && (this->pEditValueField->GetName() == "m_FileID" + || this->pEditValueField->GetName() == "m_PathID") + && hasOldIntValue + ) + { + //Find the [view asset] tree item, delete its subtree (if it exists) and the corresponding one or several AssetDeserializeDesc* + HWND hTree = GetDlgItem(this->hDialog, IDC_TYPETREE); + MC_HTREELISTITEM hPPtrBaseItem = reinterpret_cast( + SendMessage(hTree, MC_TLM_GETNEXTITEM, MC_TLGN_PARENT, reinterpret_cast(this->hEditPopupItem))); + AssetTypeValueField *pPPtrBaseField = nullptr; + if (hPPtrBaseItem != NULL) + { + MC_TLITEM item; + item.fMask = MC_TLIF_PARAM; + item.lParam = 0; + if (SendMessage(hTree, MC_TLM_GETITEM, reinterpret_cast(hPPtrBaseItem), reinterpret_cast(&item))) + { + pPPtrBaseField = reinterpret_cast(item.lParam); + } + } + MC_HTREELISTITEM hCurItem = this->hEditPopupItem; + MC_HTREELISTITEM hChildItem = NULL; + auto assetIt = this->loadedAssetsByBaseItem.end(); + while ((hCurItem = reinterpret_cast(SendMessage(hTree, MC_TLM_GETNEXTITEM, MC_TLGN_NEXT, reinterpret_cast(hCurItem)))) + != NULL) + { + hChildItem = reinterpret_cast(SendMessage(hTree, MC_TLM_GETNEXTITEM, MC_TLGN_CHILD, reinterpret_cast(hCurItem))); + if (hChildItem != NULL) + { + assetIt = this->loadedAssetsByBaseItem.find(hChildItem); + if (assetIt != this->loadedAssetsByBaseItem.end()) + { + assert(assetIt->second->pParent == this->pEditAssetDesc); + if (assetIt->second->pParent == this->pEditAssetDesc) + break; + } + } + } + if (assetIt != this->loadedAssetsByBaseItem.end() && pPPtrBaseField != nullptr) + { + bool hasUnappliedChanges; + auto recursiveChildren = this->findAllSubtreeAssets(assetIt->second, hasUnappliedChanges, pPPtrBaseField); + bool cancelledDelete = false; + if (hasUnappliedChanges) + { + switch (MessageBox(this->hDialog, TEXT("There are unsaved changes in assets below the changed PPtr.\n") + TEXT("Do you want save these changes before proceeding?"), TEXT("Asset Bundle Extractor"), MB_ICONWARNING | MB_YESNOCANCEL)) + { + case IDYES: + assert(false); //TODO: Save changes + case IDNO: + break; + case IDCANCEL: + this->pEditValueField->GetValue()->Set(&oldValue64u); + updateValueText = false; + cancelledDelete = true; + break; + } + } + if (!cancelledDelete) + { + this->closeAllSubtreeAssets(hTree, recursiveChildren, this->pEditAssetDesc); + + SendMessage(hTree, MC_TLM_EXPAND, MC_TLE_COLLAPSE, reinterpret_cast(hCurItem)); + MC_TLITEM item; + item.fMask = MC_TLIF_CHILDREN; + item.cChildren = 1; + SendMessage(hTree, MC_TLM_SETITEM, reinterpret_cast(hCurItem), reinterpret_cast(&item)); + } + } + } + if (updateValueText) + { + setTreeValueText(hTree, this->hEditPopupItem, this->pEditValueField); + pEditAssetDesc->hasChanged = true; + if (addTreeListChildren) + { + //Add new children of pEditValueField to the tree list, starting with firstChildToAdd. + this->addTreeItems(this->pEditAssetDesc, this->hEditPopupItem, reinterpret_cast(this->pEditValueField), + this->pEditValueField->GetChildrenList(), this->pEditValueField->GetChildrenCount(), false, firstChildToAdd); + //Keep in mind that the user may have cancelled adding tree elements before (and with the new items). + // => The tree list may have gaps. + // Additional functionality, such as moving array entries, must be able to handle that. + } + } + if (this->hCurPopupMenu != NULL) + { + DestroyMenu(this->hCurPopupMenu); + this->hCurPopupMenu = NULL; + } + if (this->hCurEditPopupUpDown != NULL) + { + DestroyWindow(this->hCurEditPopupUpDown); + this->hCurEditPopupUpDown = NULL; + } + if (this->hCurEditPopup != NULL) + { + DestroyWindow(this->hCurEditPopup); + this->hCurEditPopup = NULL; + } + this->pEditAssetDesc = nullptr; + this->pEditValueField = nullptr; + this->hEditPopupItem = NULL; +} +bool AssetViewModifyDialog::setByteArrayValue(FieldInfo fieldInfo, std::unique_ptr data, size_t data_len) +{ + if (fieldInfo.pValueField == nullptr + || fieldInfo.pValueField->GetValue() == nullptr + || data_len > std::numeric_limits::max()) + return false; + auto mapIt = loadedAssetsByPPtr.find(fieldInfo.assetIDs); + if (mapIt == loadedAssetsByPPtr.end()) + return false; + AssetDeserializeDesc* pAssetDesc = &(*mapIt->second); + + AssetTypeByteArray* pOldByteArray = fieldInfo.pValueField->GetValue()->AsByteArray(); + uint8_t* pOldByteArrayData = pOldByteArray ? pOldByteArray->data : nullptr; + AssetTypeByteArray input; + input.data = data.release(); + input.size = (uint32_t)data_len; + (*fieldInfo.pValueField->GetValue()) = AssetTypeValue(ValueType_ByteArray, &input); + fieldInfo.pValueField->SetChildrenList(nullptr, 0); + + pAssetDesc->hasChanged = true; + + if (pOldByteArrayData != nullptr) + { + //Free the old string memory, if possible. + auto oldDataMemIt = pAssetDesc->instanceModificationBuffers.find(pOldByteArrayData); + if (oldDataMemIt != pAssetDesc->instanceModificationBuffers.end()) + { + delete[] *oldDataMemIt; + pAssetDesc->instanceModificationBuffers.erase(oldDataMemIt); + } + } + //Add the new memory to the set. + pAssetDesc->instanceModificationBuffers.insert(input.data); + + HWND hTree = getTreeHandle(); + if (hTree == NULL) + return true; //There is no need to update the tree if it doesn't exist. + + //Delete all child items (ByteArrays are shown without any child items in the tree list). + MC_HTREELISTITEM childItem = reinterpret_cast( + SendMessage(hTree, MC_TLM_GETNEXTITEM, MC_TLGN_CHILD, reinterpret_cast(fieldInfo.treeListHandle))); + while (childItem != NULL) + { + MC_HTREELISTITEM nextChildItem = reinterpret_cast( + SendMessage(hTree, MC_TLM_GETNEXTITEM, MC_TLGN_NEXT, reinterpret_cast(childItem))); + SendMessage(hTree, MC_TLM_DELETEITEM, MC_TLDI_NONOTIFY, reinterpret_cast(childItem)); + childItem = nextChildItem; + } + //Mark the new ByteArray item as having no children. + MC_TLITEM tlItem = {}; + tlItem.fMask = MC_TLIF_CHILDREN; + tlItem.cChildren = 0; + SendMessage(hTree, MC_TLM_SETITEM, reinterpret_cast(fieldInfo.treeListHandle), reinterpret_cast(&tlItem)); + + return true; +} +bool AssetViewModifyDialog::setStringValue(AssetTypeValueField* pField, AssetDeserializeDesc* pAssetDesc, const char* str, size_t str_len) +{ + if (pField->GetValue() == nullptr || pField->GetValue()->GetType() != ValueType_String) + return false; + char* oldString = pField->GetValue()->AsString(); + uint8_t* newStringValue = new uint8_t[(str_len + 1) * sizeof(str[0])]; + memcpy(newStringValue, str, (str_len + 1) * sizeof(str[0])); + pField->GetValue()->Set((char*)newStringValue, ValueType_String); + pAssetDesc->hasChanged = true; + + if (oldString != nullptr) + { + //Free the old string memory, if possible. + auto oldStringMemIt = pAssetDesc->instanceModificationBuffers.find(reinterpret_cast(oldString)); + if (oldStringMemIt != pAssetDesc->instanceModificationBuffers.end()) + { + delete[] *oldStringMemIt; + pAssetDesc->instanceModificationBuffers.erase(oldStringMemIt); + } + } + //Add the new memory to the set. + pAssetDesc->instanceModificationBuffers.insert(newStringValue); + return true; +} +bool AssetViewModifyDialog::setStringValue(AssetTypeValueField* pValueField, AssetAbsPPtr assetIDs, const char* str, size_t str_len) +{ + auto mapIt = loadedAssetsByPPtr.find(assetIDs); + if (mapIt == loadedAssetsByPPtr.end()) + return false; + return setStringValue(pValueField, &(*mapIt->second), str, str_len); +} +AssetViewModifyDialog::FieldInfo AssetViewModifyDialog::getNextChildFieldInfo(FieldInfo parentFieldInfo, FieldInfo prevFieldInfo) +{ + if (parentFieldInfo.pValueField == nullptr) + return FieldInfo(); //Make sure we don't accidentally follow a PPtr. + if (prevFieldInfo.pValueField == nullptr && parentFieldInfo.pValueField->GetValue() != nullptr) + { + if (parentFieldInfo.pValueField->GetValue()->GetType() == ValueType_Array + || parentFieldInfo.pValueField->GetValue()->GetType() == ValueType_ByteArray) + return FieldInfo(); //Not supported (yet). + } + HWND hTree = getTreeHandle(); + MC_HTREELISTITEM childItem = NULL; + if (prevFieldInfo.treeListHandle == NULL) + { + childItem = reinterpret_cast( + SendMessage(hTree, MC_TLM_GETNEXTITEM, MC_TLGN_CHILD, reinterpret_cast(parentFieldInfo.treeListHandle))); + } + else + { + childItem = reinterpret_cast( + SendMessage(hTree, MC_TLM_GETNEXTITEM, MC_TLGN_NEXT, reinterpret_cast(prevFieldInfo.treeListHandle))); + } + if (childItem == NULL) return FieldInfo(); + AssetTypeValueField* pChildValueField = nullptr; + MC_TLITEM item; + item.fMask = MC_TLIF_PARAM; + item.lParam = 0; + if (SendMessage(hTree, MC_TLM_GETITEM, reinterpret_cast(childItem), reinterpret_cast(&item))) + { + pChildValueField = reinterpret_cast(item.lParam); + } + return FieldInfo(pChildValueField, parentFieldInfo.assetIDs, childItem); +} +void AssetViewModifyDialog::updateValueFieldText(FieldInfo fieldInfo, bool markAsChanged) +{ + if (markAsChanged) + { + auto mapIt = loadedAssetsByPPtr.find(fieldInfo.assetIDs); + if (mapIt != loadedAssetsByPPtr.end()) + mapIt->second->hasChanged = true; + } + if (fieldInfo.treeListHandle == NULL) + return; + HWND hTree = GetDlgItem(this->hDialog, IDC_TYPETREE); + if (fieldInfo.pValueField != nullptr) + setTreeValueText(hTree, fieldInfo.treeListHandle, fieldInfo.pValueField); + + FieldInfo curChildField = FieldInfo(); + while ((curChildField = getNextChildFieldInfo(fieldInfo, curChildField)).treeListHandle != NULL) + { + updateValueFieldText(curChildField); + } +} +void AssetViewModifyDialog::openEditPopup(HWND hTree, MC_HTREELISTITEM hItem, AssetTypeValueField *pValueField, AssetDeserializeDesc *pAssetDesc, + int iSubItem) +{ + RECT targetRect = {}; + targetRect.top = iSubItem; + targetRect.left = MC_TLIR_BOUNDS; + if (iSubItem == 0) + { + targetRect.left = MC_TLIR_LABEL; + if (!SendMessage(hTree, MC_TLM_GETITEMRECT, + reinterpret_cast(hItem), reinterpret_cast(&targetRect))) + return; + RECT subRect = {}; + subRect.top = 1; + subRect.left = MC_TLIR_BOUNDS; + if (!SendMessage(hTree, MC_TLM_GETSUBITEMRECT, + reinterpret_cast(hItem), reinterpret_cast(&subRect))) + return; + targetRect.right = subRect.left; + } + else + { + if (!SendMessage(hTree, MC_TLM_GETSUBITEMRECT, + reinterpret_cast(hItem), reinterpret_cast(&targetRect))) + return; + } + + int limitLow = 0; + int limitHigh = 65535; + this->pEditAssetDesc = pAssetDesc; + this->pEditValueField = pValueField; + this->hEditPopupItem = hItem; + this->iEditPopupSubItem = iSubItem; + switch (pValueField->GetValue()->GetType()) + { + case ValueType_Bool: + { + assert(iSubItem == 1); + if (iSubItem != 1) + break; + //Swap immediately. + bool newValue = !pValueField->GetValue()->AsBool(); + pValueField->GetValue()->Set(&newValue); + pAssetDesc->hasChanged = true; + + MC_TLSUBITEM subItem; + subItem.fMask = MC_TLSIF_TEXT; + subItem.iSubItem = 1; + subItem.cchTextMax = 0; + subItem.pszText = const_cast(newValue ? TEXT("true") : TEXT("false")); + SendMessage(hTree, MC_TLM_SETSUBITEM, reinterpret_cast(hItem), reinterpret_cast(&subItem)); + } + break; + case ValueType_Int8: limitHigh >>= 1; limitLow -= 127; //For Int8: limitHigh = 65535 >> 9 = 127; limitLow = -127 + 32768 - 32768 = -127 + case ValueType_UInt8: limitHigh >>= 7; limitLow += 32768;//For UInt8: limitHigh = 65535 >> 8 = 255; limitLow = 32768 - 32768 = 0 + case ValueType_Int16: limitHigh >>= 1; limitLow -= 32768;//For Int16: limitHigh = 65535 >> 1 = 32767; limitLow = -32768 + case ValueType_UInt16: + { + assert(iSubItem == 1); + if (iSubItem != 1) + break; + this->hCurEditPopup = CreateWindowEx(WS_EX_CLIENTEDGE, + WC_EDIT, NULL, ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | ES_NUMBER | ES_LEFT, + targetRect.left, targetRect.top, targetRect.right - targetRect.left, targetRect.bottom - targetRect.top, + hTree, GetMenu(this->hDialog), this->appContext.getMainWindow().getHInstance(), NULL); + this->hCurEditPopupUpDown = CreateWindowEx(WS_EX_LTRREADING, + UPDOWN_CLASS, NULL, WS_CHILD | WS_VISIBLE | UDS_AUTOBUDDY | UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_HOTTRACK, + 0, 0, 0, 0, // Auto size the Up-Down Control + hTree, GetMenu(this->hDialog), this->appContext.getMainWindow().getHInstance(), NULL); + SendMessage(this->hCurEditPopupUpDown, UDM_SETRANGE32, static_cast(limitLow), static_cast(limitHigh)); + SendMessage(this->hCurEditPopupUpDown, UDM_SETPOS32, 0, static_cast(pValueField->GetValue()->AsInt())); + } + break; + case ValueType_Int32: + case ValueType_UInt32: + case ValueType_Int64: + case ValueType_UInt64: + { + assert(iSubItem == 1); + if (iSubItem != 1) + break; + this->hCurEditPopup = CreateWindowEx(WS_EX_CLIENTEDGE, + WC_EDIT, NULL, ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | ES_NUMBER | ES_LEFT, + targetRect.left, targetRect.top, targetRect.right - targetRect.left, targetRect.bottom - targetRect.top, + hTree, GetMenu(this->hDialog), this->appContext.getMainWindow().getHInstance(), NULL); + TCHAR valueText[32]; + if (pValueField->GetValue()->GetType() == ValueType_Int32 + || pValueField->GetValue()->GetType() == ValueType_Int64) + _stprintf_s(valueText, TEXT("%lld"), pValueField->GetValue()->AsInt64()); + else + _stprintf_s(valueText, TEXT("%llu"), pValueField->GetValue()->AsUInt64()); + Edit_SetText(this->hCurEditPopup, valueText); + } + break; + case ValueType_Float: + case ValueType_Double: + { + assert(iSubItem == 1); + if (iSubItem != 1) + break; + this->hCurEditPopup = CreateWindowEx(WS_EX_CLIENTEDGE, + WC_EDIT, NULL, ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | ES_LEFT, + targetRect.left, targetRect.top, targetRect.right - targetRect.left, targetRect.bottom - targetRect.top, + hTree, GetMenu(this->hDialog), this->appContext.getMainWindow().getHInstance(), NULL); + TCHAR valueText[64]; + _stprintf_s(valueText, TEXT("%f"), pValueField->GetValue()->AsDouble()); + Edit_SetText(this->hCurEditPopup, valueText); + } + break; + case ValueType_String: + if (pValueField->GetValue()->AsString()) + { + assert(iSubItem == 1); + if (iSubItem != 1) + break; + this->hCurEditPopup = CreateWindowEx(WS_EX_CLIENTEDGE, + WC_EDIT, NULL, ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | ES_LEFT, + targetRect.left, targetRect.top, targetRect.right - targetRect.left, targetRect.bottom - targetRect.top, + hTree, GetMenu(this->hDialog), this->appContext.getMainWindow().getHInstance(), NULL); + auto pText = unique_MultiByteToTCHAR(pValueField->GetValue()->AsString()); + Edit_SetText(this->hCurEditPopup, pText.get()); + } + break; + case ValueType_Array: + { + int32_t curVal = -1; + int32_t rangeMin = -1; + int32_t rangeMax = -1; + if (pValueField->GetValue()->AsArray()->size >= static_cast(INT_MAX)) + break; + if (iSubItem == 1) //Value + { + curVal = pValueField->GetValue()->AsArray()->size; + rangeMin = pValueField->GetValue()->AsArray()->size; + rangeMax = INT_MAX; + } + else if (iSubItem == 0) //hItem is an array item, while pValueField is the array base field. + { + assert(pValueField->GetValue()->AsArray()->size > 0); + if (pValueField->GetValue()->AsArray()->size <= 1) + break; + rangeMin = 0; + rangeMax = pValueField->GetValue()->AsArray()->size - 1; + auto arrayMappingIt = pAssetDesc->arrayMappingsByArray.find(pValueField); + if (arrayMappingIt == pAssetDesc->arrayMappingsByArray.end()) + break; + auto indexMapIt = arrayMappingIt->second.itemToIndexMap.find(hItem); + if (indexMapIt == arrayMappingIt->second.itemToIndexMap.end()) + break; + curVal = indexMapIt->second; + } + else + break; + this->hCurEditPopup = CreateWindowEx(WS_EX_CLIENTEDGE, + WC_EDIT, NULL, ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | ES_NUMBER | ES_LEFT, + targetRect.left, targetRect.top, targetRect.right - targetRect.left, targetRect.bottom - targetRect.top, + hTree, GetMenu(this->hDialog), this->appContext.getMainWindow().getHInstance(), NULL); + this->hCurEditPopupUpDown = CreateWindowEx(WS_EX_LTRREADING, + UPDOWN_CLASS, NULL, WS_CHILD | WS_VISIBLE | UDS_AUTOBUDDY | UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_HOTTRACK, + 0, 0, 0, 0, // Auto size the Up-Down Control + hTree, GetMenu(this->hDialog), this->appContext.getMainWindow().getHInstance(), NULL); + SendMessage(this->hCurEditPopupUpDown, UDM_SETRANGE32, rangeMin, rangeMax); + SendMessage(this->hCurEditPopupUpDown, UDM_SETPOS32, 0, curVal); + } + break; + case ValueType_ByteArray: + //Ignore (for now) + break; + } + if (this->hCurEditPopup == NULL) + return; + SetWindowSubclass(this->hCurEditPopup, EditPopupProc, 0, reinterpret_cast(this)); + SendMessage(this->hCurEditPopup, WM_SETFONT, (WPARAM)(HFONT)SendMessage(hTree, WM_GETFONT, 0, 0), FALSE); + SetFocus(this->hCurEditPopup); +} + +std::vector AssetViewModifyDialog::findAllPPtrs(AssetTypeValueField *pBaseField, AssetIdentifier &asset) +{ + std::vector ret; + if (pBaseField->IsDummy() || + asset.pFile == nullptr) + return ret; + //Somewhat complex PPtr search algorithm. + //The simplest way would be to (recursively) iterate through all AssetTypeValueFields. + //To make sure the overhead does not explode if the asset has huge, unrelated arrays, + // the corresponding type template is first searched + // and 'search instructions' are generated for the path to each PPtr. + //Finally, the 'interpreter' walks through these instructions, + // looping if an Array is on the path (or several). + struct PPtrSearchInstr + { + //Amount of visited fields to leave. + // - Before leaving an array, jump back to the instruction that entered the array if there are more elements. + uint32_t nLeave; + //Index of the next child field to visit. + uint32_t entryChildIdx; + PPtrSearchInstr() + : nLeave(0), entryChildIdx(0) + {} + PPtrSearchInstr(uint32_t nLeave, uint32_t entryChildIdx) + : nLeave(nLeave), entryChildIdx(entryChildIdx) + {} + }; + struct TemplateStackEntry + { + AssetTypeTemplateField *pTemplate; + size_t nextChildIdx; + TemplateStackEntry() + : pTemplate(nullptr), nextChildIdx(0) + {} + TemplateStackEntry(AssetTypeTemplateField *pTemplate, size_t nextChildIdx) + : pTemplate(pTemplate), nextChildIdx(nextChildIdx) + {} + }; + std::vector searchInstructions; + uint32_t curSearchInstructionDepth = 0; + uint32_t searchInstructionPendingLeaves = 0; + std::vector templateSearchStack; + templateSearchStack.emplace_back(TemplateStackEntry(pBaseField->GetTemplateField(), 0)); + while (!templateSearchStack.empty()) + { + TemplateStackEntry stackEntry = templateSearchStack.back(); + bool leaveEntry = false; + if (stackEntry.pTemplate->isArray) + { + if (stackEntry.nextChildIdx == 0 && stackEntry.pTemplate->children.size() == 2) + { + //Enter the first array element. + //The search instruction interpreter handles array iteration implicitly. + // It remembers the arrays it entered and the current array index. + // When it is instructed to leave fields, it jumps back if an array to be left has further elements. + templateSearchStack.back().nextChildIdx = 1; + templateSearchStack.push_back(TemplateStackEntry(&stackEntry.pTemplate->children[1], 0)); + } + else + leaveEntry = true; + } + else + { + if (stackEntry.nextChildIdx == 0 && //If it is a PPtr, its children aren't analyzed and therefore nextChildIdx always is 0. + !strncmp(stackEntry.pTemplate->type.c_str(), "PPtr<", 5)) + { + assert(curSearchInstructionDepth - searchInstructionPendingLeaves < templateSearchStack.size()); + //Add instructions to enter the PPtr. + for (size_t i = curSearchInstructionDepth - searchInstructionPendingLeaves; i < templateSearchStack.size() - 1; i++) + { + assert(templateSearchStack[i].nextChildIdx > 0); + //Put the pending leave counter in the instruction, and enter the next child. + searchInstructions.emplace_back( + PPtrSearchInstr(searchInstructionPendingLeaves, + (uint32_t)(templateSearchStack[i].nextChildIdx - 1)) + ); + curSearchInstructionDepth = curSearchInstructionDepth - searchInstructionPendingLeaves + 1; + searchInstructionPendingLeaves = 0; + } + //Leave the PPtr immediately, since there are no further PPtrs inside. + leaveEntry = true; + } + else if (stackEntry.nextChildIdx < stackEntry.pTemplate->children.size()) + { + assert(curSearchInstructionDepth - searchInstructionPendingLeaves <= templateSearchStack.size()); + //Enter the next child. + size_t nextChildIdx = stackEntry.nextChildIdx; + templateSearchStack.back().nextChildIdx++; + templateSearchStack.push_back(TemplateStackEntry(&stackEntry.pTemplate->children[nextChildIdx], 0)); + } + else + leaveEntry = true; + } + if (leaveEntry) + { + if (curSearchInstructionDepth - searchInstructionPendingLeaves >= templateSearchStack.size()) + { + //'Leave' this node, in case it is entered at the state of the latest search instruction. + assert(curSearchInstructionDepth - searchInstructionPendingLeaves == templateSearchStack.size()); + searchInstructionPendingLeaves++; + } + templateSearchStack.pop_back(); + } + } + //Add an end instruction that leaves all open nodes plus the base node. + // If the base node is a PPtr, the end instruction will be the only one. + // If the end instruction wasn't there, the PPtr would not be processed. + searchInstructions.emplace_back(PPtrSearchInstr(curSearchInstructionDepth + 1, 0)); + + struct ExecArrayDesc + { + size_t enterInstrIdx; //Instruction index that entered the first element of the array. + uint32_t execStackDepth; //Index in execStack where the array value field is located. + uint32_t curChildIdx; //Index of the current child. + ExecArrayDesc() + : execStackDepth(0), enterInstrIdx(0), curChildIdx(0) + {} + ExecArrayDesc(uint32_t execStackDepth, size_t enterInstrIdx) + : execStackDepth(execStackDepth), enterInstrIdx(enterInstrIdx), curChildIdx(0) + {} + }; + std::vector openArrays; + std::vector execStack; + execStack.push_back(pBaseField); + for (size_t ip = 0; ip < searchInstructions.size(); ip++) + { + assert(!execStack.empty()); + if (execStack.back()->GetType().starts_with("PPtr<")) + { + //Found a PPtr. + AssetTypeValueField *pPPtrBase = execStack.back(); + AssetTypeValueField *pFileIDField = pPPtrBase->Get("m_FileID"); + AssetTypeValueField *pPathIDField = pPPtrBase->Get("m_PathID"); + if (!pFileIDField->IsDummy() && pFileIDField->GetValue() != nullptr && + !pPathIDField->IsDummy() && pPathIDField->GetValue() != nullptr && + pPathIDField->GetValue()->AsUInt64() != 0) + { + //The PPtr is non-empty. + ret.push_back(pPPtrBase); + } + } + //Leave the specified amount of stack elements. + uint32_t nLeaveLeft = searchInstructions[ip].nLeave; + assert(nLeaveLeft <= execStack.size()); + if (nLeaveLeft > execStack.size()) + break; + //If an array element is left (<=> the leave counter shrinks the stack at least to the array itself, + // i.e. so that afterwards [execStack.size <= array.execStackDepth + 1] holds), + // check if there are further array elements. + while (!openArrays.empty() && execStack.size() - nLeaveLeft <= openArrays.back().execStackDepth + 1) + { + //Assert that an element of the outermost openArrays entry is in the stack. + //If that were not the case, the openArrays entry should have been closed already. + assert(execStack.size() > openArrays.back().execStackDepth); + if (execStack.size() <= openArrays.back().execStackDepth) + return ret; + + //Leave the array element but not the array itself. + uint32_t nCurLeave = static_cast(execStack.size() - openArrays.back().execStackDepth - 1); + execStack.erase(execStack.begin() + (execStack.size() - nCurLeave), execStack.end()); + //for (uint32_t iLeave = 0; iLeave < nCurLeave; iLeave++) + // execStack.pop_back(); + nLeaveLeft -= nCurLeave; + if (execStack[openArrays.back().execStackDepth]->GetChildrenCount() < openArrays.back().curChildIdx + 1) + { + //Continue with the instruction that entered the array element. + ip = openArrays.back().enterInstrIdx; + openArrays.back().curChildIdx++; + nLeaveLeft = 0; + break; + } + else + { + //Close the openArrays entry and then check for the next openArrays entry, if it exists. + openArrays.pop_back(); + } + } + execStack.erase(execStack.begin() + (execStack.size() - nLeaveLeft), execStack.end()); + if (execStack.empty()) + break; + assert(!execStack.back()->IsDummy()); + if (execStack.back()->IsDummy()) + break; + if (execStack.back()->GetTemplateField()->isArray) + { + //Open an array element. + if (openArrays.empty() || openArrays.back().execStackDepth != execStack.size() - 1) + { + //Create the openArrays entry when entering the first element. + openArrays.emplace_back(ExecArrayDesc(static_cast(execStack.size() - 1), ip)); + } + execStack.push_back(execStack.back()->Get(openArrays.back().curChildIdx)); + } + else + { + assert(execStack.back()->GetChildrenCount() > searchInstructions[ip].entryChildIdx); + if (execStack.back()->GetChildrenCount() <= searchInstructions[ip].entryChildIdx) + break; + execStack.push_back(execStack.back()->Get(searchInstructions[ip].entryChildIdx)); + } + } + + return ret; +} +bool AssetViewModifyDialog::moveArrayItem(HWND hTree, AssetDeserializeDesc *pAssetDesc, + AssetTypeValueField *pArrayField, MC_HTREELISTITEM hItem, uint32_t newIndex) +{ + auto arrayMappingIt = pAssetDesc->arrayMappingsByArray.find(pArrayField); + if (arrayMappingIt == pAssetDesc->arrayMappingsByArray.end()) + return false; + auto indexMapIt = arrayMappingIt->second.itemToIndexMap.find(hItem); + if (indexMapIt == arrayMappingIt->second.itemToIndexMap.end()) + return false; + if (static_cast(newIndex) >= arrayMappingIt->second.treeItems.size()) + newIndex = static_cast(arrayMappingIt->second.treeItems.size() - 1); + uint32_t oldIndex = indexMapIt->second; + + //Move the item in the array<=>item mappings. + arrayMappingIt->second.moveItem(hItem, newIndex); + //Move the item in the child list. + AssetTypeValueField **pChildrenList = pArrayField->GetChildrenList(); + AssetTypeValueField *pMovedField = pChildrenList[oldIndex]; + if (newIndex < oldIndex) + { + memmove(&pChildrenList[newIndex + 1], &pChildrenList[newIndex], + (oldIndex - newIndex) * sizeof(AssetTypeValueField*)); + } + else if (newIndex > oldIndex) + { + memmove(&pChildrenList[oldIndex], &pChildrenList[oldIndex + 1], + (newIndex - oldIndex) * sizeof(AssetTypeValueField*)); + } + pChildrenList[newIndex] = pMovedField; + //Move the item in the tree list control (after the item with the next smaller index, or as the first item). + MC_HTREELISTITEM afterItem = MC_TLI_FIRST; + for (size_t _i = newIndex; _i > 0; _i--) + { + size_t i = _i - 1; + if (arrayMappingIt->second.treeItems[i] != NULL) + { + afterItem = arrayMappingIt->second.treeItems[i]; + break; + } + } + SendMessage(hTree, MC_TLM_MOVEITEM, reinterpret_cast(hItem), reinterpret_cast(afterItem)); + pAssetDesc->hasChanged = true; + return true; +} +void AssetViewModifyDialog::openContextMenuPopup(HWND hTree, POINT clickPos, + MC_HTREELISTITEM hItem, AssetTypeValueField *pValueField, + MC_HTREELISTITEM hParentItem, AssetTypeValueField *pParentValueField, + AssetDeserializeDesc *pAssetDesc) +{ + static const uintptr_t ID_ARRAYENTRY_DELETE = 9001; + static const uintptr_t ID_ARRAYENTRY_MOVEUP = 9002; + static const uintptr_t ID_ARRAYENTRY_MOVEDOWN = 9003; + static const uintptr_t ID_ARRAYENTRY_MOVE = 9004; + static const uintptr_t ID_PPTRENTRY_NEWVIEWTAB = 9005; + static const uintptr_t ID_PPTRENTRY_SHOWINLIST = 9006; + if (pAssetDesc == nullptr || pAssetDesc->asset.pFile == nullptr) + return; + if (this->hCurPopupMenu != NULL) + { + DestroyMenu(this->hCurPopupMenu); + this->hCurPopupMenu = NULL; + } + UINT popupMenuFlags = TPM_RETURNCMD | TPM_NONOTIFY; + if (GetSystemMetrics(SM_MENUDROPALIGNMENT) != 0) + popupMenuFlags |= TPM_RIGHTALIGN | TPM_HORNEGANIMATION; + else + popupMenuFlags |= TPM_HORPOSANIMATION; + if (pValueField == nullptr) + { + if (hItem == NULL || (hItem != MC_TLI_ROOT && (hParentItem == NULL || pParentValueField == nullptr))) + return; + if (hItem == MC_TLI_ROOT + || (pParentValueField != nullptr + && pParentValueField->GetType().starts_with("PPtr<"))) + { + pathid_t pathID = 0; + unsigned int absFileID = 0; + if (hItem == MC_TLI_ROOT) + { + absFileID = pAssetDesc->asset.pFile->getFileID(); + pathID = pAssetDesc->asset.pathID; + } + else if (pParentValueField != nullptr) + { + AssetTypeValueField* pFileIDField = pParentValueField->Get("m_FileID"); + AssetTypeValueField* pPathIDField = pParentValueField->Get("m_PathID"); + if (pFileIDField->GetValue() == nullptr || pPathIDField->GetValue() == nullptr) + return; + unsigned int fileID = pFileIDField->GetValue()->AsUInt(); + pathID = pPathIDField->GetValue()->AsUInt64(); + absFileID = pAssetDesc->asset.pFile->resolveRelativeFileID(fileID); + } + if (absFileID == 0 || pathID == 0) + return; + //Handle PPtr '[View asset]' nodes. + this->hCurPopupMenu = CreatePopupMenu(); + if (this->hCurPopupMenu == NULL) + return; + AppendMenu(this->hCurPopupMenu, MF_STRING, ID_PPTRENTRY_NEWVIEWTAB, TEXT("Show in &new tab")); + AppendMenu(this->hCurPopupMenu, MF_STRING, ID_PPTRENTRY_SHOWINLIST, TEXT("Show in asset &list")); + uintptr_t selectedId = static_cast(TrackPopupMenuEx(this->hCurPopupMenu, popupMenuFlags, clickPos.x, clickPos.y, this->hDialog, NULL)); + switch (selectedId) + { + case ID_PPTRENTRY_NEWVIEWTAB: + if (!this->assetListDialog.openViewDataTab(absFileID, pathID)) + MessageBox(this->hDialog, TEXT("Unable to find the requested asset."), TEXT("Asset Bundle Extractor"), MB_ICONERROR); + break; + case ID_PPTRENTRY_SHOWINLIST: + if (this->assetListDialog.selectAsset(absFileID, pathID)) + this->assetListDialog.switchToListTab(); + else + MessageBox(this->hDialog, TEXT("Unable to find the requested asset."), TEXT("Asset Bundle Extractor"), MB_ICONERROR); + break; + } + } + else if (pParentValueField != nullptr && pParentValueField->GetValue() && pParentValueField->GetValue()->AsArray()) + { + //Handle Array index nodes. + auto arrayMappingIt = pAssetDesc->arrayMappingsByArray.find(pParentValueField); + if (arrayMappingIt == pAssetDesc->arrayMappingsByArray.end()) + return; + auto indexMapIt = arrayMappingIt->second.itemToIndexMap.find(hItem); + if (indexMapIt == arrayMappingIt->second.itemToIndexMap.end()) + return; + if (indexMapIt->second > pParentValueField->GetChildrenCount()) + return; + assert(pParentValueField->GetChildrenCount() == pParentValueField->GetValue()->AsArray()->size); + + this->hCurPopupMenu = CreatePopupMenu(); + if (this->hCurPopupMenu == NULL) + return; + AppendMenu(this->hCurPopupMenu, MF_STRING, ID_ARRAYENTRY_MOVEUP, TEXT("Move array item &up")); + AppendMenu(this->hCurPopupMenu, MF_STRING, ID_ARRAYENTRY_MOVE, TEXT("Move array item &to")); + AppendMenu(this->hCurPopupMenu, MF_STRING, ID_ARRAYENTRY_MOVEDOWN, TEXT("Move array item &down")); + AppendMenu(this->hCurPopupMenu, MF_STRING, ID_ARRAYENTRY_DELETE, TEXT("&Remove array item")); + uintptr_t selectedId = static_cast(TrackPopupMenuEx(this->hCurPopupMenu, popupMenuFlags, clickPos.x, clickPos.y, this->hDialog, NULL)); + //Find again to prevent any issues from message handling during TrackPopupMenuEx. + arrayMappingIt = pAssetDesc->arrayMappingsByArray.find(pParentValueField); + if (arrayMappingIt == pAssetDesc->arrayMappingsByArray.end()) + selectedId = 0; + else + { + indexMapIt = arrayMappingIt->second.itemToIndexMap.find(hItem); + if (indexMapIt == arrayMappingIt->second.itemToIndexMap.end()) + selectedId = 0; + } + switch (selectedId) + { + case ID_ARRAYENTRY_MOVE: + this->openEditPopup(hTree, hItem, pParentValueField, pAssetDesc, 0); + break; + case ID_ARRAYENTRY_MOVEUP: + case ID_ARRAYENTRY_MOVEDOWN: + { + uint32_t newIndex = indexMapIt->second; + if (selectedId == ID_ARRAYENTRY_MOVEUP) + newIndex = (newIndex > 0) ? (newIndex - 1) : 0; + else if (selectedId == ID_ARRAYENTRY_MOVEDOWN) + newIndex = (newIndex < pParentValueField->GetChildrenCount()-1) ? (newIndex + 1) : (pParentValueField->GetChildrenCount() - 1); + this->moveArrayItem(hTree, pAssetDesc, pParentValueField, hItem, newIndex); + } + break; + case ID_ARRAYENTRY_DELETE: + { + uint32_t oldIndex = indexMapIt->second; + AssetTypeValueField **pChildrenList = pParentValueField->GetChildrenList(); + AssetTypeValueField *pDeletedField = pChildrenList[oldIndex]; + + //1. Check for PPtrs below the deleted array entry, where an asset could be loaded in the subtree. + std::vector pptrFields = findAllPPtrs(pDeletedField, pAssetDesc->asset); + + //2. For each PPtr candidate, find all subtree assets to be closed (=> AssetDeserializeDesc::children) + bool hasUnappliedChanges = false; + std::vector::iterator>> closedSubtreeAssets; + for (size_t i = 0; i < pptrFields.size(); i++) + { + auto assetIt = this->loadedAssetsByPPtrField.find(pptrFields[i]); + if (assetIt != this->loadedAssetsByPPtrField.end()) + { + bool curHasUnappliedChanges; + auto recursiveChildren = this->findAllSubtreeAssets(assetIt->second, curHasUnappliedChanges, pptrFields[i]); + closedSubtreeAssets.insert(closedSubtreeAssets.end(), recursiveChildren.begin(), recursiveChildren.end()); + } + } + //3. If there are unsaved changes, ask. + bool cancelledDelete = false; + if (hasUnappliedChanges) + { + switch (MessageBox(this->hDialog, TEXT("There are unsaved changes in assets opened below a PPtr.\n") + TEXT("Do you want save these changes before proceeding?"), TEXT("Asset Bundle Extractor"), MB_ICONWARNING | MB_YESNOCANCEL)) + { + case IDYES: + { + std::vector> assetsToApply; + assetsToApply.reserve(closedSubtreeAssets.size()); + for (size_t i = 0; i < closedSubtreeAssets.size(); ++i) + assetsToApply.push_back(*closedSubtreeAssets[i].second); + if (!this->applyChangesIn(assetsToApply.begin(), assetsToApply.end())) + cancelledDelete = true; + } + break; + case IDNO: + break; + case IDCANCEL: + cancelledDelete = true; + break; + } + } + if (cancelledDelete) + break; + //4. Close the assets properly and remove them from all maps. + this->closeAllSubtreeAssets(hTree, closedSubtreeAssets, pAssetDesc); + + //5. Remove the array entry : + //5a. Remove the entry from the array entry<->index mapping. + arrayMappingIt->second.removeItem(hItem, oldIndex); + //5b. Remove the entry field from the child list, and decrease the child list size (keeping the old and thereby oversized list memory). + memmove(&pChildrenList[oldIndex], &pChildrenList[oldIndex + 1], + (pParentValueField->GetChildrenCount() - oldIndex - 1) * sizeof(AssetTypeValueField*)); + pParentValueField->SetChildrenList(pChildrenList, pParentValueField->GetChildrenCount() - 1); + pParentValueField->GetValue()->AsArray()->size = pParentValueField->GetChildrenCount(); + //5c. Delete the item from the tree list before freeing its memory. + SendMessage(hTree, MC_TLM_DELETEITEM, MC_TLDI_NONOTIFY, reinterpret_cast(hItem)); + //5d. Free any additionally allocated memory for the deleted field (i.e. if it was added as a new array element). + FreeFieldRecursively(pDeletedField, pAssetDesc->instanceModificationBuffers); + + //6. Update the array size text. + setTreeValueText(hTree, hParentItem, pParentValueField); + pAssetDesc->hasChanged = true; + + //7. Select a new item and redraw the following items so the displayed indices are updated. + RECT treeClientRect = {}; + GetClientRect(hTree, &treeClientRect); + int curItemY = -1; + int itemHeight = static_cast(SendMessage(hTree, MC_TLM_GETITEMHEIGHT, 0, 0)); + if (itemHeight <= 0) + break; + bool foundNextItem = false; + for (size_t i = oldIndex; i < pParentValueField->GetChildrenCount(); i++) + { + MC_HTREELISTITEM hCurItem = arrayMappingIt->second.treeItems[i]; + if (hCurItem == NULL) + continue; + MC_TLITEM item; + item.fMask = 0; + if (curItemY == -1) + { + RECT rect = {}; + rect.left = MC_TLIR_BOUNDS; + if (!SendMessage(hTree, MC_TLM_GETITEMRECT, reinterpret_cast(hCurItem), reinterpret_cast(&rect))) + break; + curItemY = rect.top; + //Select the next item. Visibility is ensured since the user selected the previous item at this location. + item.fMask = MC_TLIF_STATE; + item.state = MC_TLIS_SELECTED; + item.stateMask = MC_TLIS_SELECTED; + foundNextItem = true; + } + //Force an item redraw. + SendMessage(hTree, MC_TLM_SETITEM, reinterpret_cast(hCurItem), reinterpret_cast(&item)); + curItemY += itemHeight; + if (curItemY > treeClientRect.bottom) + break; + } + if (!foundNextItem) + { + //If we deleted the last visible item, select the new last item. + MC_TLITEM item; + item.fMask = MC_TLIF_STATE; + item.state = MC_TLIS_SELECTED; + item.stateMask = MC_TLIS_SELECTED; + for (size_t _i = oldIndex; _i > 0; _i--) + { + size_t i = _i - 1; + MC_HTREELISTITEM hCurItem = arrayMappingIt->second.treeItems[i]; + if (hCurItem != NULL) + { + SendMessage(hTree, MC_TLM_SETITEM, reinterpret_cast(hCurItem), reinterpret_cast(&item)); + SendMessage(hTree, MC_TLM_ENSUREVISIBLE, 0, reinterpret_cast(hCurItem)); + break; + } + } + } + } + break; + } + } + } + else + { + if (hItem == NULL) + return; + //Context menu behaviour for normal nodes defined by plugins. + const PluginMapping& plugins = appContext.getPlugins(); + auto citer = plugins.options.cbegin(); + std::shared_ptr pCurProvider; + std::vector>> viableOptions; + while (citer = plugins.getNextOptionProvider(citer, pCurProvider), pCurProvider != nullptr) + { + std::string optionName; + std::unique_ptr pRunner = pCurProvider->prepareForSelection( + appContext, *this, + FieldInfo(pValueField, + AssetAbsPPtr(pAssetDesc->asset.pFile->getFileID(), pAssetDesc->asset.pathID), + hItem), + optionName); + if (pRunner != nullptr) + { + viableOptions.push_back({ std::move(optionName), std::move(pRunner) }); + } + } + + size_t sel = ShowContextMenu(viableOptions.size(), [&viableOptions](size_t i) {return viableOptions[i].first.c_str(); }, + popupMenuFlags, clickPos.x, clickPos.y, this->hDialog, + this->hCurPopupMenu); + if (sel != (size_t)-1) + (*viableOptions[sel].second)(); //Let the plugin perform the action. + } + if (this->hCurPopupMenu != NULL) + { + DestroyMenu(this->hCurPopupMenu); + this->hCurPopupMenu = NULL; + } +} + +LRESULT CALLBACK AssetViewModifyDialog::AssetTreeListSubclassProc(HWND hWnd, UINT message, + WPARAM wParam, LPARAM lParam, + uintptr_t uIdSubclass, DWORD_PTR dwRefData) +{ + AssetViewModifyDialog *pThis = reinterpret_cast(dwRefData); + switch (message) + { + case WM_LBUTTONDBLCLK: + { + MC_TLHITTESTINFO hitTestInfo = {}; + hitTestInfo.pt.x = GET_X_LPARAM(lParam); //Client area + hitTestInfo.pt.y = GET_Y_LPARAM(lParam); + if (SendMessage(hWnd, MC_TLM_HITTEST, 0, reinterpret_cast(&hitTestInfo)) != NULL && hitTestInfo.iSubItem == 1) + { + assert(hitTestInfo.hItem != NULL); + if (pThis->hCurEditPopup != NULL) + pThis->doCloseEditPopup(); + + TCHAR textBuf[32]; textBuf[31] = 0; + MC_TLITEM item; + item.fMask = MC_TLIF_PARAM | MC_TLIF_TEXT; + item.cchTextMax = 31; + item.pszText = textBuf; + item.lParam = 0; + if (!SendMessage(hWnd, MC_TLM_GETITEM, + reinterpret_cast(hitTestInfo.hItem), + reinterpret_cast(&item))) + break; + if (item.lParam == 0) + break; + + AssetTypeValueField *pValueField = reinterpret_cast(item.lParam); + AssetDeserializeDesc *pAssetDesc = pThis->getAssetDescForItem(hitTestInfo.hItem); + if (!pAssetDesc) + break; + if (!pValueField->GetValue()) + break; + + pThis->openEditPopup(hWnd, hitTestInfo.hItem, pValueField, pAssetDesc); + return (LRESULT)0; + } + } + break; + case WM_NCDESTROY: + RemoveWindowSubclass(hWnd, AssetTreeListSubclassProc, uIdSubclass); + break; + } + return DefSubclassProc(hWnd, message, wParam, lParam); +} + +LRESULT CALLBACK AssetViewModifyDialog::EditPopupProc(HWND hWnd, UINT message, + WPARAM wParam, LPARAM lParam, + uintptr_t uIdSubclass, DWORD_PTR dwRefData) +{ + AssetViewModifyDialog *pThis = (AssetViewModifyDialog*)dwRefData; + switch (message) + { + case WM_KILLFOCUS: + //if (wParam == WA_INACTIVE) + { + if (pThis->hCurEditPopup != NULL) + pThis->doCloseEditPopup(); + } + break; + case WM_KEYDOWN: + if (LOWORD(wParam) == VK_ESCAPE || LOWORD(wParam) == VK_RETURN) + { + if (pThis->hCurEditPopup != NULL) + pThis->doCloseEditPopup(); + } + break; + case WM_NCDESTROY: + RemoveWindowSubclass(hWnd, EditPopupProc, uIdSubclass); + break; + } + return DefSubclassProc(hWnd, message, wParam, lParam); +} diff --git a/UABE_Win32/AssetViewModifyDialog.h b/UABE_Win32/AssetViewModifyDialog.h new file mode 100644 index 0000000..e8cdd52 --- /dev/null +++ b/UABE_Win32/AssetViewModifyDialog.h @@ -0,0 +1,352 @@ +#pragma once +#include "api.h" +#include "AssetListDialog.h" +#include "Win32AppContext.h" +#include "../AssetsTools/AssetTypeClass.h" +#include +#include +#include +#include +#include +#include + + +struct AssetAbsPPtr +{ + unsigned int fileID; + pathid_t pathID; + inline AssetAbsPPtr() + : fileID(0), pathID(0) + {} + inline AssetAbsPPtr(unsigned int fileID, pathid_t pathID) + : fileID(fileID), pathID(pathID) + {} + inline bool operator==(AssetAbsPPtr other) const + { + return fileID == other.fileID && pathID == other.pathID; + } +}; + +namespace std +{ + template<> + struct hash + { + std::size_t operator()(AssetAbsPPtr const& pptr) const + { + static std::hash uintHash; + static std::hash llHash; + size_t fidHash = uintHash(pptr.fileID); + size_t pidHash = llHash(pptr.pathID); + return (fidHash << 1) ^ pidHash; + } + }; +} + +class AssetViewModifyDialog : public AssetModifyDialog, public TaskProgressCallback +{ +public: + struct FieldInfo + { + AssetTypeValueField* pValueField = nullptr; + //Reference to the asset (file ID and path ID). + //Can be assumed not to change for any child value fields reachable from pValueField. + AssetAbsPPtr assetIDs = {}; + //Handle to the UI representation of this value field. + void* treeListHandle = nullptr; + inline FieldInfo() {} + inline FieldInfo(AssetTypeValueField* pValueField, AssetAbsPPtr assetIDs, MC_HTREELISTITEM treeListItem) + : pValueField(pValueField), assetIDs(assetIDs), treeListHandle((void*)treeListItem) + {} + }; +private: + AssetListDialog &assetListDialog; + Win32AppContext &appContext; + + bool isDestroyed; + size_t registeredCallbackCounter; //(is 0) => callback is not registered + struct AssetDeserializeDesc + { + AssetIdentifier asset; + AssetTypeTemplateField templateBase; + ITask *pLoadTask; + TaskResult loadTaskResult; + std::unique_ptr pAssetInstance; + //The tree list item above this asset's base item. + MC_HTREELISTITEM parentItem; + MC_HTREELISTITEM baseItem; + //Set if this asset has 'unsaved' changes not added to the IAssetsReplacer list. + bool hasChanged; + //Set if is non-null while closing the asset. + //If set, this entry should get removed from the loadedAssets list once the task is done. + bool pendingClose; + //Set if the user has already been asked about extracting the MonoBehaviour class information. + //-> Do not ask again in case the required type information still cannot be found. + bool monoBehaviourInfoAsked; + + //Assumption: All open assets form a tree through the child lists. The root element is the first asset. + // => Each AssetDeserializeDesc is child of exactly one other, or is the root (and no child). + + //Assets opened through PPtrs in this asset's tree. The first element of the pair is the 'PPtr' typed field. + std::vector::iterator>> children; + AssetDeserializeDesc *pParent; + + std::unordered_set instanceModificationBuffers; + struct ArrayMappings + { + std::vector treeItems; //Same size as the array. NULL indicates gaps. + std::unordered_map itemToIndexMap; + inline void insertItem(MC_HTREELISTITEM item, uint32_t index) + { + size_t oldSize = treeItems.size(); + treeItems.insert(treeItems.begin() + index, item); + if (index < oldSize) + { + for (auto it = itemToIndexMap.begin(); it != itemToIndexMap.end(); ++it) + { + if (it->second >= index) + it->second++; + } + } + itemToIndexMap.insert(std::make_pair(item, index)); + } + inline void removeItem(MC_HTREELISTITEM item, uint32_t index) + { + itemToIndexMap.erase(item); + treeItems.erase(treeItems.begin() + index); + for (auto it = itemToIndexMap.begin(); it != itemToIndexMap.end(); ++it) + { + if (it->second > index) + it->second--; + } + } + inline void swapItems(MC_HTREELISTITEM firstItem, uint32_t secondIndex) + { + MC_HTREELISTITEM secondItem = treeItems[secondIndex]; + auto firstItemIt = itemToIndexMap.find(firstItem); + uint32_t firstIndex = firstItemIt->second; + firstItemIt->second = secondIndex; + itemToIndexMap.find(secondItem)->second = firstIndex; + treeItems[firstIndex] = secondItem; + treeItems[secondIndex] = firstItem; + } + inline void moveItem(MC_HTREELISTITEM item, uint32_t newIndex) + { + auto itemIt = itemToIndexMap.find(item); + uint32_t oldIndex = itemIt->second; + treeItems.erase(treeItems.begin() + oldIndex); + for (auto it = itemToIndexMap.begin(); it != itemToIndexMap.end(); ++it) + { + if (it->second >= oldIndex) + it->second--; + if (it->second >= newIndex) + it->second++; + } + treeItems.insert(treeItems.begin() + newIndex, item); + itemIt->second = newIndex; + } + }; + std::unordered_map arrayMappingsByArray; + + inline AssetDeserializeDesc() + : pLoadTask(nullptr), loadTaskResult(-256), parentItem(NULL), baseItem(NULL), hasChanged(false), pendingClose(false), monoBehaviourInfoAsked(false), pParent(nullptr) + {} + inline ~AssetDeserializeDesc() + { + for (auto it = instanceModificationBuffers.begin(); it != instanceModificationBuffers.end(); ++it) + delete[] *it; + } + inline AssetDeserializeDesc(AssetDeserializeDesc &&other) + : asset(std::move(other.asset)), templateBase(std::move(other.templateBase)), + pLoadTask(other.pLoadTask), loadTaskResult(other.loadTaskResult), + pAssetInstance(std::move(other.pAssetInstance)), + parentItem(other.parentItem), baseItem(other.baseItem), + hasChanged(other.hasChanged), pendingClose(other.pendingClose), + monoBehaviourInfoAsked(other.monoBehaviourInfoAsked), + children(std::move(other.children)), pParent(other.pParent), + instanceModificationBuffers(std::move(other.instanceModificationBuffers)) + { + other.pLoadTask = nullptr; + } + inline AssetDeserializeDesc &operator=(AssetDeserializeDesc &&other) noexcept + { + this->asset = std::move(other.asset); + this->templateBase = std::move(other.templateBase); + this->pLoadTask = other.pLoadTask; + other.pLoadTask = nullptr; + this->loadTaskResult = other.loadTaskResult; + this->pAssetInstance = std::move(other.pAssetInstance); + this->parentItem = other.parentItem; + this->baseItem = other.baseItem; + this->hasChanged = other.hasChanged; + this->pendingClose = other.pendingClose; + this->monoBehaviourInfoAsked = other.monoBehaviourInfoAsked; + this->children = std::move(other.children); + this->instanceModificationBuffers = std::move(other.instanceModificationBuffers); + this->pParent = other.pParent; + } + }; + std::list loadedAssets; + std::unordered_map::iterator> loadedAssetsByPPtrField; + std::unordered_map::iterator> loadedAssetsByPPtr; + std::unordered_map::iterator> loadedAssetsByBaseItem; + + std::list::iterator findOrPrepareLoad(AssetIdentifier asset, MC_HTREELISTITEM parentItem, AssetDeserializeDesc *pParentAsset, + AssetTypeValueField *pPPtrField = nullptr); + bool startLoadTask(std::list::iterator assetEntry, + std::shared_ptr selfPtr = std::shared_ptr()); + AssetDeserializeDesc *getAssetDescForItem(MC_HTREELISTITEM item); + + std::string assetName; + HWND hDialog; + HWND hTree=NULL; + static HWND _getTreeHandle(HWND hDialog); + inline HWND getTreeHandle() + { + if (!hTree) hTree = _getTreeHandle(hDialog); + return hTree; + } + bool ignoreExpandNotifications; + + HMENU hCurPopupMenu; + HWND hCurEditPopup; + HWND hCurEditPopupUpDown; + MC_HTREELISTITEM hEditPopupItem; + AssetTypeValueField *pEditValueField; + int iEditPopupSubItem; + AssetDeserializeDesc *pEditAssetDesc; + + //Finds all non-empty PPtrs in the asset, starting with the provided base field. + // pBaseField: Base field for the search, corresponding with the given asset. Does not have to be the absolute base field for the asset. + // asset: The resolved AssetIdentifier, based on which the absolute PPtr IDs are resolved. + //Returns: All identified "PPtr<*" type fields with a m_FileID and m_PathID value, the latter being non-zero. + static std::vector findAllPPtrs(AssetTypeValueField *pBaseField, AssetIdentifier &asset); + //Finds all assets in the subtree of a given asset (including the asset itself). + //Additionally outputs, whether any of the assets have unapplied changes (-> hasUnappliedChanges). + std::vector::iterator>> findAllSubtreeAssets( + std::list::iterator itBaseAssetDesc, + bool &hasUnappliedChanges, + AssetTypeValueField *pBasePPtrField = nullptr); + void closeAllSubtreeAssets(HWND hTree, + std::vector::iterator>> subtreeAssets, + AssetDeserializeDesc *pParentAsset); + + void doCloseEditPopup(); + void openEditPopup(HWND hTree, MC_HTREELISTITEM hItem, AssetTypeValueField *pValueField, AssetDeserializeDesc *pAssetDesc, + int iSubItem = 1); + bool moveArrayItem(HWND hTree, AssetDeserializeDesc *pAssetDesc, + AssetTypeValueField *pArrayField, MC_HTREELISTITEM hItem, uint32_t newIndex); + void openContextMenuPopup(HWND hTree, POINT clickPos, + MC_HTREELISTITEM hItem, AssetTypeValueField *pValueField, + MC_HTREELISTITEM hParentItem, AssetTypeValueField *pParentValueField, + AssetDeserializeDesc *pAssetDesc); + + //Inner implementation for applyChanges, see below. + //Allows other lists than all assets in the view to be saved. + //ForwardIt: forward iterator that dereferences to AssetDeserializeDesc& (e.g. std::list::iterator). + template + bool applyChangesIn(ForwardIt assetBegin, ForwardIt assetEnd); + + bool testItemHasTextcallbackActive; + bool testItemHasTextcallbackResult; + bool testItemHasTextcallback(HWND hTree, MC_HTREELISTITEM hItem); + TCHAR itemTextcallbackBuf[32]; + + static INT_PTR CALLBACK AssetViewProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); + static LRESULT CALLBACK AssetTreeListSubclassProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, + uintptr_t uIdSubclass, DWORD_PTR dwRefData); + static LRESULT CALLBACK EditPopupProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, + uintptr_t uIdSubclass, DWORD_PTR dwRefData); + static void onCompletionMainThread(uintptr_t param1, uintptr_t param2); + + bool setStringValue(AssetTypeValueField* pField, AssetDeserializeDesc* pAssetDesc, const char* str, size_t str_len); +protected: + MC_HTREELISTITEM addTreeItems(AssetDeserializeDesc *pAssetDesc, MC_HTREELISTITEM hParent, LPARAM parentLParam, AssetTypeValueField **pFields, size_t fieldCount, bool isPPtr, size_t startIdx = 0); +public: + //asset: The resolved identifier of the target asset. + AssetViewModifyDialog(AssetListDialog &assetListDialog, Win32AppContext &appContext, AssetIdentifier asset, std::string assetName); + ~AssetViewModifyDialog(); + //hParentWnd : Parent window for MessageBox calls during init(..) + bool init(std::shared_ptr &selfPtr, HWND hParentWnd); + + //Called when the user requests to close the tab. + //Returns true if there are unsaved changes, false otherwise. + //If the function will return true and applyable is not null, + // *applyable will be set to true iff applyNow() is assumed to succeed without further interaction + // (e.g. all fields in the dialog have a valid value, ...). + //The caller uses this info to decide whether and how it should display a confirmation dialog before proceeding. + bool hasUnappliedChanges(bool *applyable=nullptr); + //Called when the user requests to apply the changes (e.g. selecting Apply, Save or Save All in the menu). + //Returns whether the changes have been applied; + // if true, the caller may continue closing the AssetModifyDialog. + // if false, the caller may stop closing the AssetModifyDialog. + //Note: applyChanges() is expected to notify the user about errors (e.g. via MessageBox). + bool applyChanges(); + std::string getTabName(); + HWND getWindowHandle(); + //Called for unhandled WM_COMMAND messages. Returns true if this dialog has handled the request, false otherwise. + bool onCommand(WPARAM wParam, LPARAM lParam); + void onHotkey(ULONG message, DWORD keyCode); //message : currently only WM_KEYDOWN; keyCode : VK_F3 for instance + //Called when the dialog is to be shown. The parent window will not change before the next onHide call. + void onShow(HWND hParentWnd); + //Called when the dialog is to be hidden, either because of a tab switch or while closing the tab. + void onHide(); + //Called when the tab is about to be destroyed. + //Once this function is called, AssetListDialog::removeModifyDialog must not be used for this dialog. + void onDestroy(); + + //TaskProgressCallback + void OnCompletion(std::shared_ptr &pTask, TaskResult result); + + //Set the value for a string field and manages the string memory properly. + UABE_Win32_API bool setStringValue(AssetTypeValueField* pValueField, AssetAbsPPtr assetIDs, const char *str, size_t str_len); + inline bool setStringValue(AssetTypeValueField* pValueField, AssetAbsPPtr assetIDs, const std::string& str) + { return setStringValue(pValueField, assetIDs, str.c_str(), str.size()); } + + //Set the value for a ByteArray field and manages the string memory properly. + UABE_Win32_API bool setByteArrayValue(FieldInfo fieldInfo, std::unique_ptr data, size_t data_len); + + //Update the shown value for a field and its children. + //Note: Array and ByteArray fields are NOT supported, + // and PPtr-referenced assets opened below the given field are not updated! + UABE_Win32_API void updateValueFieldText(FieldInfo fieldInfo, bool markAsChanged = true); + + //Retrieves the next child of the parent, given the previous child and the next child's index. + //idxTracker must be set to 0 by the caller before the first call, and not modified in between calls! + // Bypasses the seek inefficiency compared to getChildFieldInfo, if all childs are to be enumerated. + //Note: Array fields are NOT supported, and PPtr-referenced assets opened in the view are not followed! + UABE_Win32_API FieldInfo getNextChildFieldInfo(FieldInfo parentFieldInfo, FieldInfo prevFieldInfo); + + //Retrieves the child of the parent with the given index. + // Quite inefficient (linear search due to UI backend limitations). + //Note: Array fields are NOT supported, and PPtr-referenced assets opened in the view are not followed! + inline FieldInfo getChildFieldInfo(FieldInfo fieldInfo, size_t iChild) + { + FieldInfo ret; + for (size_t i = 0; i < iChild; ++i) + { + ret = getNextChildFieldInfo(fieldInfo, ret); + if (ret.pValueField == nullptr) + break; + } + return ret; + } + //Retrieves the child of the parent with the given name. + //Note: Array fields are NOT supported! + inline FieldInfo getChildFieldInfo(FieldInfo fieldInfo, const char* childFieldName) + { + AssetTypeValueField** childList = fieldInfo.pValueField->GetChildrenList(); + uint32_t childCount = fieldInfo.pValueField->GetChildrenCount(); + for (uint32_t i = 0; i < childCount; i++) + { + if (childList[i]->GetTemplateField() != NULL) + { + if (childList[i]->GetTemplateField()->name == childFieldName) + return getChildFieldInfo(fieldInfo, i); + } + } + return FieldInfo(); + } + + friend class AssetInstanceTask; +}; \ No newline at end of file diff --git a/UABE_Win32/BatchImportDialog.cpp b/UABE_Win32/BatchImportDialog.cpp new file mode 100644 index 0000000..dae40f0 --- /dev/null +++ b/UABE_Win32/BatchImportDialog.cpp @@ -0,0 +1,588 @@ +#include "stdafx.h" +#include + +#include "BatchImportDialog.h" +#include "../libStringConverter/convert.h" +#include "../UABE_Generic/AssetPluginUtil.h" +#include "resource.h" +#include + +bool CBatchImportDialog::SearchDirectory(const std::wstring &path, const std::string &relativePath, std::vector ®exs, bool searchSubDirs) +{ + WIN32_FIND_DATA findData; + HANDLE hFind = FindFirstFileW((path + L"\\*").c_str(), &findData); + if (hFind == INVALID_HANDLE_VALUE) + return false; + //bool ret = false; + do + { + if (0 == wcscmp(findData.cFileName, L".") || 0 == wcscmp(findData.cFileName, L"..")) + continue; + auto pFileName = unique_WideToMultiByte(findData.cFileName); + if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + if (searchSubDirs) + { + //ret |= + SearchDirectory(path + L"\\" + findData.cFileName, relativePath + "\\" + pFileName.get(), regexs, true); + } + } + else + { + for (size_t i = 0; i < regexs.size(); i++) + { + std::cmatch match; + if (!std::regex_match(pFileName.get(), match, regexs[0])) + continue; + std::vector capturingGroupsStr; + std::vector capturingGroups; + //Retrieve the capturing groups as UTF-8 char*. + if (match.size() > 1) + { + capturingGroupsStr.resize(match.size() - 1); + capturingGroups.resize(match.size() - 1); + for (size_t j = 0; (j+1) < match.size(); j++) + { + capturingGroupsStr[j] = match[j+1].str(); + capturingGroups[j] = capturingGroupsStr[j].c_str(); + } + } + + size_t matchIndex = (size_t)-1; + if (this->pDesc->GetFilenameMatchInfo(pFileName.get(), capturingGroups, matchIndex) && (matchIndex < this->assetInfo.size())) + { + std::vector &fileList = this->assetInfo[matchIndex].fileList; + size_t targetIndex = fileList.size(); + fileList.resize(fileList.size() + 1); + + fileList[targetIndex].isRelative = true; + fileList[targetIndex].path = (relativePath + "\\" + pFileName.get()); + + //ret = true; + break; + } + } + } + } while (FindNextFileW(hFind, &findData)); + FindClose(hFind); + return true; +} + +bool CBatchImportDialog::GenerateFileLists() +{ + std::vector regexStrings; bool checkSubdirs = false; + this->pDesc->GetFilenameMatchStrings(regexStrings, checkSubdirs); + if (regexStrings.size() == 0) return false; + + std::vector regexs(regexStrings.size()); + for (size_t i = 0; i < regexStrings.size(); i++) + { + regexs[i].assign(regexStrings[i], std::regex_constants::ECMAScript); + } + + size_t basePathWLen = 0; + wchar_t *basePathW = _MultiByteToWide(basePath.c_str(), basePathWLen); + if (!basePathW) return false; + + this->SearchDirectory(std::wstring(basePathW), std::string("."), regexs, checkSubdirs); + + _FreeWCHAR(basePathW); + return true; +} + +size_t CBatchImportDialog::GetCurAssetInfoIndex(unsigned int selection) +{ + HWND hAssetlist = GetDlgItem(this->hWnd, IDC_ASSETLIST); + if (hAssetlist != NULL) + { + if (selection == (unsigned int)-1) + selection = (unsigned int)ListView_GetNextItem(hAssetlist, -1, LVNI_SELECTED); + if ((selection >= 0) && (selection < assetInfo.size())) + { + LVITEM item; + memset(&item, 0, sizeof(LVITEM)); + item.iItem = selection; + item.iSubItem = 0; + item.mask = LVIF_PARAM; + ListView_GetItem(hAssetlist, &item); + size_t ret = (size_t)item.lParam; + if (ret >= assetInfo.size()) return (size_t)-1; + return ret; + } + } + return (size_t)-1; +} + +void CBatchImportDialog::UpdateDialogFileList(size_t assetInfoIndex) +{ + this->updatingAssetList = true; + + HWND hFileList = GetDlgItem(this->hWnd, IDC_FILELIST); + ListBox_ResetContent(hFileList); + + int extent = 0; + if (assetInfoIndex < this->assetInfo.size()) + { + std::vector &fileList = this->assetInfo[assetInfoIndex].fileList; + for (size_t i = 0; i < fileList.size(); i++) + { + size_t filePathTLen = 0; + TCHAR *filePathT = _MultiByteToTCHAR(fileList[i].path.c_str(), filePathTLen); + ListBox_AddString(hFileList, filePathT ? filePathT : L""); + + if (filePathT) + { + HDC hListDC = GetDC(hFileList); + HGDIOBJ hOrigObject = SelectObject(hListDC, GetWindowFont(hFileList)); + RECT textRect = {}; + DrawText(hListDC, filePathT, -1, &textRect, DT_SINGLELINE | DT_CALCRECT); + SelectObject(hListDC, hOrigObject); + ReleaseDC(hFileList, hListDC); + + extent = std::max(extent, textRect.right-textRect.left + 4); + } + + _FreeTCHAR(filePathT); + } + } + ListBox_SetHorizontalExtent(hFileList, extent); + + this->updatingAssetList = false; +} + +int CALLBACK CBatchImportDialog::AssetlistSortCallback(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) +{ + size_t assetDescIndex1 = (size_t)lParam1; + size_t assetDescIndex2 = (size_t)lParam2; + CBatchImportDialog *pThis = (CBatchImportDialog*)lParamSort; + if (assetDescIndex1 < pThis->assetInfo.size() && assetDescIndex2 < pThis->assetInfo.size()) + { + bool swap = pThis->dialogSortDirReverse; + int retGreater = swap ? 1 : -1; + int retSmaller = swap ? -1 : 1; + switch (pThis->dialogSortColumnIdx) + { + case 0: + { + std::string &s1 = pThis->assetInfo[assetDescIndex1].description; + std::string &s2 = pThis->assetInfo[assetDescIndex2].description; + + if ( s1 > s2 ) return retGreater; + if ( s1 < s2 ) return retSmaller; + return 0; + } + case 1: + { + std::string &s1 = pThis->assetInfo[assetDescIndex1].assetsFileName; + std::string &s2 = pThis->assetInfo[assetDescIndex2].assetsFileName; + + if ( s1 > s2 ) return retGreater; + if ( s1 < s2 ) return retSmaller; + return 0; + } + case 2: + { + long long int pathId1 = pThis->assetInfo[assetDescIndex1].pathId; + long long int pathId2 = pThis->assetInfo[assetDescIndex2].pathId; + + if ( pathId1 > pathId2 ) return retGreater; + if ( pathId1 < pathId2 ) return retSmaller; + return 0; + } + } + } + return 0; +} +INT_PTR CALLBACK CBatchImportDialog::WindowHandler(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + CBatchImportDialog* pThis = (CBatchImportDialog*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + INT_PTR ret = (INT_PTR)FALSE; + int wmId, wmEvent; + UNREFERENCED_PARAMETER(lParam); + switch (message) + { + case WM_CLOSE: + if (pThis && pThis->modeless) + { + pThis->hWnd = NULL; + DestroyWindow(hDlg); + } + ret = (INT_PTR)TRUE; + break; + case WM_DESTROY: + if (pThis) + { + pThis->assetInfo.clear(); + pThis->hWnd = NULL; + if (pThis->closeCallback) + { + pThis->closeCallback(false); + } + SetWindowLongPtr(hDlg, GWLP_USERDATA, NULL); + } + break; + case WM_INITDIALOG: + { + SetWindowLongPtr(hDlg, GWLP_USERDATA, lParam); + pThis = (CBatchImportDialog*)lParam; + pThis->hWnd = hDlg; + + pThis->dialogSortColumnIdx = 0; + pThis->dialogSortDirReverse = true; + pThis->updatingAssetList = false; + + HWND hAssetList = GetDlgItem(hDlg, IDC_ASSETLIST); + HWND hFileList = GetDlgItem(hDlg, IDC_FILELIST); + HWND hEditAssetButton = GetDlgItem(hDlg, IDC_EDITASSETBTN); + + { + LVCOLUMN column; + ZeroMemory(&column, sizeof(LVCOLUMN)); + column.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM; + column.cx = 180; + column.pszText = const_cast(TEXT("Description")); + ListView_InsertColumn(hAssetList, 0, &column); + column.cx = 140; + column.pszText = const_cast(TEXT("File")); + ListView_InsertColumn(hAssetList, 1, &column); + column.cx = 80; + column.pszText = const_cast(TEXT("Path ID")); + ListView_InsertColumn(hAssetList, 2, &column); + } + { + if (pThis->pDescWin32 == nullptr || !pThis->pDescWin32->ShowAssetSettings((size_t)-1, hDlg)) + ShowWindow(hEditAssetButton, SW_HIDE); + } + //Create a tooltip for the file list. + { + //https://msdn.microsoft.com/en-us/library/windows/desktop/hh298368(v=vs.85).aspx + HWND hWndTooltip = CreateWindowEx(NULL, TOOLTIPS_CLASS, NULL, + WS_POPUP |TTS_ALWAYSTIP | TTS_BALLOON, + CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, + hDlg, NULL, + pThis->hInstance, NULL); + if (hWndTooltip) + { + TOOLINFO toolInfo = {}; + toolInfo.cbSize = sizeof(toolInfo); + toolInfo.hwnd = hDlg; + toolInfo.uFlags = TTF_IDISHWND | TTF_SUBCLASS; + toolInfo.uId = (uintptr_t)hFileList; + toolInfo.lpszText = const_cast(TEXT("Double click an item to move it up and use it for import.")); + SendMessage(hWndTooltip, TTM_ADDTOOL, 0, (LPARAM)&toolInfo); + } + } + + std::vector newAssetDescs; + if (pThis->pDesc->GetImportableAssetDescs(newAssetDescs)) + { + pThis->assetInfo.resize(newAssetDescs.size()); + + if (pThis->GenerateFileLists()) + { + LVITEM item; + ZeroMemory(&item, sizeof(LVITEM)); + item.iSubItem = 0; + item.cchTextMax = 255; + for (size_t i = 0; i < std::min(INT_MAX, newAssetDescs.size()); i++) + { + const std::string &curDescription = newAssetDescs[i].description; + pThis->assetInfo[i].description = curDescription; + + size_t _strLen = 0; + TCHAR *tcName = _MultiByteToTCHAR(curDescription.c_str(), _strLen); + + item.mask = (tcName ? LVIF_TEXT : 0) | LVIF_PARAM; + item.lParam = i; + item.iItem = (int)i; + item.iSubItem = 0; + item.pszText = tcName; + + ListView_InsertItem(hAssetList, &item); + + _FreeTCHAR(tcName); + + const std::string &curAssetsFileName = newAssetDescs[i].assetsFileName; + pThis->assetInfo[i].assetsFileName = curAssetsFileName; + + TCHAR *tcAssetsFileName = _MultiByteToTCHAR(curAssetsFileName.c_str(), _strLen); + + item.mask = (tcAssetsFileName ? LVIF_TEXT : 0); + item.iSubItem = 1; + item.pszText = tcAssetsFileName; + ListView_SetItem(hAssetList, &item); + + _FreeTCHAR(tcAssetsFileName); + + pThis->assetInfo[i].pathId = newAssetDescs[i].pathID; + TCHAR pathIdBuf[32]; + _stprintf_s(pathIdBuf, _T("%lli"), newAssetDescs[i].pathID); + + item.mask = LVIF_TEXT; + item.iSubItem = 2; + item.pszText = pathIdBuf; + ListView_SetItem(hAssetList, &item); + } + + ListView_SetItemState(hAssetList, 0, 0, LVIS_SELECTED); + } + else + { + pThis->modeless ? DestroyWindow(hDlg) : EndDialog(hDlg, FALSE); + } + } + else + { + pThis->modeless ? DestroyWindow(hDlg) : EndDialog(hDlg, FALSE); + } + + ret = (INT_PTR)TRUE; + goto CASE_WM_SIZE; + } + case WM_NOTIFY: + if (pThis != nullptr) + { + NMLISTVIEW *pNotifyLV = (NMLISTVIEW*)lParam; + switch (pNotifyLV->hdr.code) + { + case LVN_ITEMCHANGED: + if (pNotifyLV->uNewState & LVIS_SELECTED) + { + if (!pThis->updatingAssetList) + pThis->UpdateDialogFileList(pThis->GetCurAssetInfoIndex((unsigned int)pNotifyLV->iItem)); + } + break; + case LVN_COLUMNCLICK: + if (pThis->dialogSortColumnIdx == pNotifyLV->iSubItem) + { + pThis->dialogSortDirReverse ^= true; + } + else + { + pThis->dialogSortColumnIdx = pNotifyLV->iSubItem; + pThis->dialogSortDirReverse = false; + } + + pThis->updatingAssetList = true; + ListView_SortItems(pNotifyLV->hdr.hwndFrom, AssetlistSortCallback, (LPARAM)pThis); + pThis->updatingAssetList = false; + + pThis->UpdateDialogFileList(pThis->GetCurAssetInfoIndex()); + break; + } + } + break; + case WM_COMMAND: + if (pThis != nullptr) + { + wmId = LOWORD(wParam); + wmEvent = HIWORD(wParam); + switch (wmId) + { + case IDC_FILELIST: + { + switch (wmEvent) + { + case LBN_DBLCLK: + { + HWND hFileList = (HWND)lParam; + unsigned int fileListSel = 0; + size_t curAssetInfoIndex = pThis->GetCurAssetInfoIndex(); + if (curAssetInfoIndex < pThis->assetInfo.size()) + { + fileListSel = (unsigned int)ListBox_GetCurSel(hFileList); + + std::vector &fileList = pThis->assetInfo[curAssetInfoIndex].fileList; + if (fileListSel > 0 && fileListSel < fileList.size()) + { + AssetInfo::FileListEntry entry = fileList[fileListSel]; + fileList.erase(fileList.begin() + fileListSel); + fileList.insert(fileList.begin(), entry); + fileListSel = 0; + } + } + pThis->UpdateDialogFileList(pThis->GetCurAssetInfoIndex()); + ListBox_SetCurSel(hFileList, (int)fileListSel); + } + } + } + break; + case IDC_EDITASSETBTN: + { + HWND hFileList = GetDlgItem(hDlg, IDC_FILELIST); + + size_t curAssetInfoIndex = pThis->GetCurAssetInfoIndex(); + if (curAssetInfoIndex < pThis->assetInfo.size()) + { + if (pThis->pDescWin32 != nullptr + && pThis->pDescWin32->ShowAssetSettings(curAssetInfoIndex, pThis->modeless ? pThis->hParentWnd : hDlg)) + { + AssetInfo::FileListEntry overrideEntry; + overrideEntry.isRelative = false; + if (pThis->pDesc->HasFilenameOverride(curAssetInfoIndex, overrideEntry.path, overrideEntry.isRelative)) + { + std::vector &fileList = pThis->assetInfo[curAssetInfoIndex].fileList; + bool entryExists = false; + for (size_t i = 0; i < fileList.size(); i++) + { + if (fileList[i].isRelative == overrideEntry.isRelative && !fileList[i].path.compare(overrideEntry.path)) + { + entryExists = true; + break; + } + } + if (!entryExists) + { + fileList.insert(fileList.begin(), overrideEntry); + pThis->UpdateDialogFileList(curAssetInfoIndex); + ListBox_SetCurSel(hFileList, (int)0); + } + } + } + } + } + break; + case IDOK: + { + for (size_t i = 0; i < pThis->assetInfo.size(); i++) + { + std::string fullFilePathStr; + const char *fullFilePathCStr = nullptr; + if (pThis->assetInfo[i].fileList.size() > 0) + { + if (pThis->assetInfo[i].fileList[0].isRelative) + { + if (!pThis->basePath.empty()) + { + fullFilePathStr = pThis->basePath; + fullFilePathStr += "\\"; + } + fullFilePathStr += pThis->assetInfo[i].fileList[0].path; + fullFilePathCStr = fullFilePathStr.c_str(); + } + else + fullFilePathCStr = pThis->assetInfo[i].fileList[0].path.c_str(); + } + pThis->pDesc->SetInputFilepath(i, fullFilePathCStr); + } + bool modeless = pThis->modeless; + if (pThis->closeCallback) + { + pThis->closeCallback(true); + } + modeless ? DestroyWindow(hDlg) : EndDialog(hDlg, TRUE); + } + return (INT_PTR)TRUE; + case IDCANCEL: + pThis->modeless ? DestroyWindow(hDlg) : EndDialog(hDlg, FALSE); + return (INT_PTR)TRUE; + } + } + break; + case WM_SIZE: + CASE_WM_SIZE: + { + RECT client; + GetClientRect(hDlg, &client); + LONG clientWidth = client.right-client.left; + LONG clientHeight = client.bottom-client.top; + MoveWindow(GetDlgItem(hDlg, IDC_ASSETSSTATIC), 19, 10, (3*clientWidth / 5) + 7, 15, true); + MoveWindow(GetDlgItem(hDlg, IDC_ASSETLIST), 19, 30, (3*clientWidth / 5) + 7, clientHeight - 72, true); + MoveWindow(GetDlgItem(hDlg, IDC_EDITASSETBTN), 19, clientHeight - 33, 75, 26, true); + MoveWindow(GetDlgItem(hDlg, IDC_FILESSTATIC), (3*clientWidth / 5) + 40, 10, (2*clientWidth / 5) - 56, 15, true); + MoveWindow(GetDlgItem(hDlg, IDC_FILELIST), (3*clientWidth / 5) + 40, 30, (2*clientWidth / 5) - 56, clientHeight - 72, true); + MoveWindow(GetDlgItem(hDlg, IDCANCEL), clientWidth - 91, clientHeight - 33, 75, 26, true); + MoveWindow(GetDlgItem(hDlg, IDOK), (3*clientWidth / 5) + 40, clientHeight - 33, 75, 26, true); + UpdateWindow(hDlg); + break; + } + } + return ret; +} + +CBatchImportDialog::CBatchImportDialog(HINSTANCE hInstance, + IAssetBatchImportDesc* pDesc, IWin32AssetBatchImportDesc* pDescWin32, + std::string _basePath) + : hParentWnd(hParentWnd), hWnd(NULL), modeless(false), hInstance(hInstance), + dialogSortColumnIdx(0), dialogSortDirReverse(false), updatingAssetList(false), + pDesc(pDesc), pDescWin32(pDescWin32), basePath(std::move(_basePath)) +{ +} +CBatchImportDialog::~CBatchImportDialog() +{ + if (this->hWnd) + SendMessage(this->hWnd, WM_CLOSE, 0, 0); +} +void CBatchImportDialog::Hide() +{ + if (this->hWnd) + { + ShowWindow(this->hWnd, SW_HIDE); + SetParent(this->hWnd, NULL); + } +} +bool CBatchImportDialog::ShowModal(HWND hParentWnd) +{ + this->hParentWnd = hParentWnd; + if (this->hWnd) + return false; + this->modeless = false; + return (DialogBoxParam(this->hInstance, MAKEINTRESOURCE(IDD_BATCHIMPORT), this->hParentWnd, WindowHandler, (LPARAM)this) == TRUE); +} +bool CBatchImportDialog::ShowModeless(HWND hParentWnd) +{ + this->hParentWnd = hParentWnd; + if (this->hWnd) + { + SetParent(this->hWnd, hParentWnd); + ShowWindow(this->hWnd, SW_SHOW); + return true; + } + else + { + //Modify the dialog style before creating the dialog + //-> Load the dialog resource in memory and change the style flags, + // then create the dialog using CreateDialogIndirectParam. + //https://docs.microsoft.com/en-us/windows/win32/dlgbox/dlgtemplateex + //https://devblogs.microsoft.com/oldnewthing/20040623-00/?p=38753 + struct _DLGTEMPLATEEX_HEADER { + uint16_t dlgVer; + uint16_t signature; + DWORD helpID; + DWORD exStyle; + DWORD style; + uint16_t cDlgItems; + short x; + short y; + short cx; + short cy; + }; + + bool ret = false; + std::vector modelessResource; + { + HRSRC hResource = FindResourceExW(hInstance, RT_DIALOG, MAKEINTRESOURCE(IDD_BATCHIMPORT), 0); + if (hResource == NULL) + return false; + HGLOBAL hLoadedResource = LoadResource(hInstance, hResource); + if (hLoadedResource == NULL) + return false; + std::unique_ptr _raii_hLoadedResource(hLoadedResource, FreeResource); + LPVOID pResourceData = LockResource(hLoadedResource); + if (pResourceData == NULL) + return false; + DWORD size = SizeofResource(hInstance, hResource); + modelessResource.assign((uint8_t*)pResourceData, (uint8_t*)pResourceData + size); + } + _DLGTEMPLATEEX_HEADER* pDlgTemplateHeader = reinterpret_cast<_DLGTEMPLATEEX_HEADER*>(modelessResource.data()); + if (modelessResource.size() < sizeof(_DLGTEMPLATEEX_HEADER) + || pDlgTemplateHeader->signature != 0xFFFF || pDlgTemplateHeader->dlgVer != 1) + return false; + pDlgTemplateHeader->style &= ~(WS_POPUP | WS_CAPTION | WS_THICKFRAME); + pDlgTemplateHeader->style |= WS_CHILD | WS_SYSMENU; + this->modeless = true; + this->hWnd = CreateDialogIndirectParam(this->hInstance, (DLGTEMPLATE*)pDlgTemplateHeader, this->hParentWnd, WindowHandler, (LPARAM)this); + return (this->hWnd != NULL); + } +} \ No newline at end of file diff --git a/UABE_Win32/BatchImportDialog.h b/UABE_Win32/BatchImportDialog.h new file mode 100644 index 0000000..ca79f0c --- /dev/null +++ b/UABE_Win32/BatchImportDialog.h @@ -0,0 +1,71 @@ +#pragma once +#include "api.h" +#include +#include +#include +#include +#include "../UABE_Generic/IAssetBatchImportDesc.h" +#include "Win32BatchImportDesc.h" +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include + +class CBatchImportDialog +{ + class AssetInfo + { + public: + class FileListEntry + { + public: + std::string path; + bool isRelative; //Path is relative to wcBasePath. + }; + std::string description; + std::string assetsFileName; + long long int pathId; + std::vector fileList; + }; + HWND hParentWnd; + HWND hWnd; bool modeless; + HINSTANCE hInstance; + int dialogSortColumnIdx; + bool dialogSortDirReverse; + bool updatingAssetList; + + IAssetBatchImportDesc *pDesc; + IWin32AssetBatchImportDesc* pDescWin32; + std::string basePath; + + std::vector assetInfo; + + std::function closeCallback; + + static int CALLBACK AssetlistSortCallback(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort); + static INT_PTR CALLBACK WindowHandler(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); + + //Returns -1 if no valid asset is selected. + size_t GetCurAssetInfoIndex(unsigned int selection = -1); + //Updates the file list. + void UpdateDialogFileList(size_t assetInfoIndex); + //Returns whether at least one file has been found. + bool SearchDirectory(const std::wstring &path, const std::string &relativePath, std::vector ®exs, bool searchSubDirs); + bool GenerateFileLists(); +public: + //The pointer to wcBasePath must not be freed before Show() has returned! + UABE_Win32_API CBatchImportDialog(HINSTANCE hInstance, + IAssetBatchImportDesc* pDesc, IWin32AssetBatchImportDesc* pDescWin32, + std::string basePath); + UABE_Win32_API ~CBatchImportDialog(); + UABE_Win32_API void Hide(); + UABE_Win32_API bool ShowModal(HWND hParentWnd); + UABE_Win32_API bool ShowModeless(HWND hParentWnd); + inline void SetCloseCallback(std::function callback) + { + this->closeCallback = callback; + } + inline HWND getWindowHandle() + { + return hWnd; + } +}; \ No newline at end of file diff --git a/UABE_Win32/BundleDialog.cpp b/UABE_Win32/BundleDialog.cpp new file mode 100644 index 0000000..93c1f03 --- /dev/null +++ b/UABE_Win32/BundleDialog.cpp @@ -0,0 +1,738 @@ +#include "stdafx.h" +#include "BundleDialog.h" +#include "MainWindow2.h" +#include "resource.h" +#include "../libStringConverter/convert.h" +#include "FileDialog.h" +#include + +BundleDialog::~BundleDialog() +{ + pContext->getMainWindow().unregisterEventHandler(eventHandlerHandle); +} +BundleDialog::BundleDialog(class Win32AppContext *pContext, HWND hParentWnd) + : pContext(pContext), hParentWnd(hParentWnd), hDialog(NULL), + hCurEditPopup(NULL), iEditPopupItem(0), iEditPopupSubItem(0), nSelected(0), + pCurFileEntry(nullptr) +{ + eventHandlerHandle = pContext->getMainWindow().registerEventHandler(this); +} + +void BundleDialog::addFileContext(const std::pair &fileContext) +{ + FileContextInfo *pContextInfo = fileContext.first->getContextInfoPtr(); + if (BundleFileContextInfo *pBundleInfo = dynamic_cast(pContextInfo)) + { + unsigned int fileID = pBundleInfo->getFileID(); + auto entryIt = fileEntries.find(fileID); + assert(entryIt == fileEntries.end()); + if (entryIt == fileEntries.end()) + { + fileEntries.insert(std::make_pair(fileID, fileContext.first)); + if (pCurFileEntry == nullptr || pCurFileEntry->getContextInfoPtr() == nullptr + || pBundleInfo->getFileID() < pCurFileEntry->getContextInfoPtr()->getFileID()) + { + pCurFileEntry = fileContext.first; + onUpdateCurrentFile(); + } + } + } + +} +void BundleDialog::removeFileContext(FileEntryUIInfo *pContext) +{ + FileContextInfo *pContextInfo = pContext->getContextInfoPtr(); + if (BundleFileContextInfo *pBundleInfo = dynamic_cast(pContextInfo)) + { + unsigned int fileID = pBundleInfo->getFileID(); + auto entryIt = fileEntries.find(fileID); + assert(entryIt != fileEntries.end()); + assert(entryIt->second == pContext); + if (entryIt != fileEntries.end()) + fileEntries.erase(entryIt); + } + else + { + for (auto it = fileEntries.begin(); it != fileEntries.end(); ++it) + { + if (it->second == pContext) + { + fileEntries.erase(it); + break; + } + } + } + if (pContext == pCurFileEntry) + { + if (!fileEntries.empty()) + pCurFileEntry = fileEntries.begin()->second; + else + pCurFileEntry = nullptr; + onUpdateCurrentFile(); + } +} +EFileManipulateDialogType BundleDialog::getType() +{ + return FileManipulateDialog_Bundle; +} +HWND BundleDialog::getWindowHandle() +{ + return hDialog; +} +void BundleDialog::onHotkey(ULONG message, DWORD keyCode) //message : currently only WM_KEYDOWN; keyCode : VK_F3 for instance +{ + +} +bool BundleDialog::onCommand(WPARAM wParam, LPARAM lParam) //Called for unhandled WM_COMMAND messages. Returns true if this dialog has handled the request, false otherwise. +{ + return false; +} +void BundleDialog::onShow() +{ + if (!this->hDialog) + { + this->hDialog = CreateDialogParam(pContext->getMainWindow().getHInstance(), MAKEINTRESOURCE(IDD_BUNDLEEDIT), hParentWnd, BundleDlgProc, (LPARAM)this); + onUpdateCurrentFile(); + } +} +void BundleDialog::onHide() +{ + if (this->hDialog) + { + if (this->hCurEditPopup != NULL) + doCloseEditPopup(); + SendMessage(this->hDialog, WM_CLOSE, 0, 0); + } +} +bool BundleDialog::hasUnappliedChanges(bool *applyable) +{ + return false; +} +bool BundleDialog::applyChanges() +{ + return true; +} +bool BundleDialog::doesPreferNoAutoclose() +{ + return false; +} + +static size_t getSelectedEntryIdx(HWND hList) +{ + int listItem = ListView_GetNextItem(hList, -1, LVNI_SELECTED); + if (listItem == -1) + return (size_t)-1; + + LVITEM item; + item.mask = LVIF_PARAM; + item.lParam = (LPARAM)-1; + item.iItem = listItem; + item.iSubItem = 0; + ListView_GetItem(hList, &item); + + return (size_t)item.lParam; +} +void BundleDialog::importItem(bool addNew) +{ + HWND hList = GetDlgItem(hDialog, IDC_ENTRYLIST); + if (pCurFileEntry == nullptr || pCurFileEntry->getContextInfoPtr() == nullptr) + return; + std::shared_ptr pBundleInfo( + pCurFileEntry->getContextInfo(), + static_cast(pCurFileEntry->getContextInfoPtr())); + + size_t entryIdx = (size_t)-1; + if (!addNew) + { + entryIdx = getSelectedEntryIdx(hList); + assert(entryIdx == (size_t)-1 || entryIdx < pBundleInfo->getEntryCount()); + if (entryIdx >= pBundleInfo->getEntryCount() || entryIdx > UINT_MAX) + return; + } + + WCHAR *filePathBuf = nullptr; + if (FAILED(ShowFileOpenDialog(this->pContext->getMainWindow().getWindow(), + &filePathBuf, L"*.*|All types:", nullptr, nullptr, TEXT("Import a bundle entry"), + UABE_FILEDIALOG_FILE_GUID))) + return; + + if (!addNew) + { + std::vector fileIDByEntry; + pBundleInfo->getChildFileIDs(fileIDByEntry); + assert(fileIDByEntry.size() > entryIdx); + if (fileIDByEntry[entryIdx] != 0 && !pContext->getMainWindow().CloseFile(fileIDByEntry[entryIdx])) + return; + } + + std::string fileName; + for (size_t _i = wcslen(filePathBuf); _i > 0; --_i) + { + size_t i = _i - 1; + if (filePathBuf[i] == L'/' || filePathBuf[i] == L'\\') + { + auto pFileName8 = unique_TCHARToMultiByte(&filePathBuf[i+1]); + fileName.assign(pFileName8.get()); + break; + } + } + + IAssetsReader *pReader = Create_AssetsReaderFromFile(filePathBuf, true, RWOpenFlags_Immediately); + FreeCOMFilePathBuf(&filePathBuf); + if (!pReader) + { + MessageBox(this->pContext->getMainWindow().getWindow(), + TEXT("Unable to open the file to import!"), + TEXT("Asset Bundle Extractor"), + MB_ICONERROR); + return; + } + std::shared_ptr pReader_shared(pReader, Free_AssetsReader); + bool isSerializedData = false; + { + std::unique_ptr pAssetsFile(new AssetsFile(pReader_shared.get())); + isSerializedData = pAssetsFile->VerifyAssetsFile(); + } + + if (addNew) + { + if (fileName.empty()) + fileName = std::string("New entry (") + std::to_string(pBundleInfo->getEntryCount()) + ")"; + entryIdx = pBundleInfo->addEntry(*this->pContext, pReader_shared, isSerializedData, fileName); + } + else + pBundleInfo->overrideEntryReader(*this->pContext, entryIdx, pReader_shared, isSerializedData); + + pContext->getMainWindow().loadBundleEntry(pBundleInfo, (unsigned int)entryIdx); +} +void BundleDialog::exportItem() +{ + HWND hList = GetDlgItem(hDialog, IDC_ENTRYLIST); + if (pCurFileEntry == nullptr || pCurFileEntry->getContextInfoPtr() == nullptr) + return; + BundleFileContextInfo *pBundleInfo = static_cast(pCurFileEntry->getContextInfoPtr()); + + size_t entryIdx = getSelectedEntryIdx(hList); + assert(entryIdx == (size_t)-1 || entryIdx < pBundleInfo->getEntryCount()); + if (entryIdx >= pBundleInfo->getEntryCount()) + return; + + std::string newName = pBundleInfo->getNewEntryName(entryIdx); + auto pNewNameT = unique_MultiByteToTCHAR(newName.c_str()); + WCHAR *filePathBuf = nullptr; + if (FAILED(ShowFileSaveDialog(this->pContext->getMainWindow().getWindow(), + &filePathBuf, L"*.*|All types:", nullptr, pNewNameT.get(), TEXT("Export a bundle entry"), + UABE_FILEDIALOG_FILE_GUID))) + return; + + //Note: Race condition possible (entries could be changed by some other thread). + //-> Reader could change after having retrieved the name. + // (doesn't appear to be relevant, and will not cause too bad behavior). + + bool isModified; + std::shared_ptr pReader = pBundleInfo->makeEntryReader(entryIdx, isModified); + QWORD size = 0; + if (pReader == nullptr || !pReader->Seek(AssetsSeek_End, 0) || !pReader->Tell(size) || !pReader->Seek(AssetsSeek_Begin, 0)) + { + MessageBox(this->pContext->getMainWindow().getWindow(), + TEXT("Unable to read the entry to export!"), + TEXT("Asset Bundle Extractor"), + MB_ICONERROR); + return; + } + + //Slight abuse of BundleReplacers to copy from a reader to a writer. Should be fine, though. + BundleReplacer *pReplacer = MakeBundleEntryModifier("", "", false, pReader, size, 0); + bool result = pReplacer->Init(nullptr, nullptr, 0, 0, nullptr); + if (!result) + { + MessageBox(this->pContext->getMainWindow().getWindow(), + TEXT("An internal error occured."), + TEXT("Asset Bundle Extractor"), + MB_ICONERROR); + return; + } + + IAssetsWriter *pWriter = Create_AssetsWriterToFile(filePathBuf, true, true, RWOpenFlags_Immediately); + if (pWriter == nullptr) + { + FreeBundleReplacer(pReplacer); + MessageBox(this->pContext->getMainWindow().getWindow(), + TEXT("Unable to open the output file!"), + TEXT("Asset Bundle Extractor"), + MB_ICONERROR); + return; + } + + QWORD written = pReplacer->Write(0, pWriter); + FreeBundleReplacer(pReplacer); + Free_AssetsWriter(pWriter); + assert(written <= size); + if (written < size) + { + MessageBox(this->pContext->getMainWindow().getWindow(), + TEXT("Failed writing the output file!"), + TEXT("Asset Bundle Extractor"), + MB_ICONERROR); + return; + } +} +void BundleDialog::removeItem() +{ + HWND hList = GetDlgItem(hDialog, IDC_ENTRYLIST); + if (pCurFileEntry == nullptr || pCurFileEntry->getContextInfoPtr() == nullptr) + return; + std::shared_ptr pBundleInfo( + pCurFileEntry->getContextInfo(), + static_cast(pCurFileEntry->getContextInfoPtr())); + + size_t entryIdx = getSelectedEntryIdx(hList); + assert(entryIdx == (size_t)-1 || entryIdx < pBundleInfo->getEntryCount()); + if (entryIdx >= pBundleInfo->getEntryCount()) + return; + + std::vector fileIDByEntry; + pBundleInfo->getChildFileIDs(fileIDByEntry); + if (fileIDByEntry[entryIdx] != 0 && !pContext->getMainWindow().CloseFile(fileIDByEntry[entryIdx])) + return; + + pBundleInfo->removeEntry(*this->pContext, entryIdx); +} + +static void list_updateBundleName(HWND hList, int iItem, const char *name) +{ + auto upText = unique_MultiByteToTCHAR(name); + LVITEM item; + item.mask = LVIF_TEXT; + item.pszText = upText.get(); + item.iItem = iItem; + item.iSubItem = 0; + ListView_SetItem(hList, &item); +} +static void list_updateBundleModified(HWND hList, int iItem, bool modified) +{ + LVITEM item; + item.mask = LVIF_TEXT; + item.pszText = const_cast(modified ? TEXT("*") : TEXT("")); + item.iItem = iItem; + item.iSubItem = 1; + ListView_SetItem(hList, &item); +} + +void BundleDialog::onUpdateCurrentFile() +{ + HWND hList = GetDlgItem(hDialog, IDC_ENTRYLIST); + ListView_DeleteAllItems(hList); + if (pCurFileEntry != nullptr && pCurFileEntry->getContextInfoPtr() != nullptr && pCurFileEntry->getContextInfoPtr()->getFileContext() != nullptr) + { + BundleFileContextInfo *pBundleInfo = static_cast(pCurFileEntry->getContextInfoPtr()); + //Note: Race condition possible (entries could be changed by some other thread). + //-> Index bounds are checked in the functions, and can only increase. + size_t numEntries = pBundleInfo->getEntryCount(); + int listViewCount = 0; + for (size_t i = 0; i < numEntries && i < INT_MAX; ++i) + { + bool hasChanged = pBundleInfo->entryHasChanged(i); + std::string newName = pBundleInfo->getNewEntryName(i); + if (!pBundleInfo->entryIsRemoved(i)) + { + LVITEM item; + item.mask = LVIF_PARAM; + item.lParam = (LPARAM)i; + item.iItem = listViewCount; + item.iSubItem = 0; + ListView_InsertItem(hList, &item); + + list_updateBundleName(hList, listViewCount, newName.c_str()); + list_updateBundleModified(hList, listViewCount, hasChanged); + + assert(listViewCount < INT_MAX); + listViewCount++; + } + } + } +} + +inline void doMoveWindow(HDWP &deferCtx, bool &retry, HWND hWnd, int x, int y, int w, int h) +{ + if (deferCtx) + { + deferCtx = DeferWindowPos(deferCtx, hWnd, HWND_TOP, x, y, w, h, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); + if (!deferCtx) + retry = true; + } + else + SetWindowPos(hWnd, HWND_TOP, x, y, w, h, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); +} +static void onResize(HWND hDlg, bool defer = true) +{ + HDWP deferCtx = defer ? BeginDeferWindowPos(12) : NULL; + bool retry = false; + + RECT client = {}; + GetClientRect(hDlg, &client); + int clientWidth = (int)(client.right-client.left); + int clientHeight = (int)(client.bottom-client.top); + int x = 19; + int w = clientWidth - 16; + + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_STATICTITLE), x + 2, 10, w - 4, 15); + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_ENTRYLIST), x, 30, w - 15, clientHeight - 60 - 7); + int btnDistance = std::max(4, w - 25) / 4; + int btnWidth = std::max(1, btnDistance - 10); + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_BTNADD), x + 5 + 0*btnDistance, clientHeight - 30, btnWidth, 25); + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_BTNIMPORT), x + 5 + 1*btnDistance, clientHeight - 30, btnWidth, 25); + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_BTNEXPORT), x + 5 + 2*btnDistance, clientHeight - 30, btnWidth, 25); + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_BTNREMOVE), x + 5 + 3*btnDistance, clientHeight - 30, btnWidth, 25); + + if (defer) + { + if (retry || !EndDeferWindowPos(deferCtx)) + onResize(hDlg, false); + else + UpdateWindow(hDlg); + deferCtx = NULL; + } + else + UpdateWindow(hDlg); + +} + +void BundleDialog::doCloseEditPopup(bool applyChanges) +{ + if (applyChanges) + { + if (pCurFileEntry == nullptr || pCurFileEntry->getContextInfoPtr() == nullptr) + { + doCloseEditPopup(false); + return; + } + BundleFileContextInfo *pBundleFileInfo = static_cast(pCurFileEntry->getContextInfoPtr()); + + HWND hList = GetDlgItem(hDialog, IDC_ENTRYLIST); + LVITEM item; + item.mask = LVIF_PARAM; + item.lParam = (LPARAM)-1; + item.iItem = iEditPopupItem; + item.iSubItem = 0; + ListView_GetItem(hList, &item); + + size_t entryIdx = (size_t)item.lParam; + assert(entryIdx < pBundleFileInfo->getEntryCount()); + if (entryIdx >= pBundleFileInfo->getEntryCount()) + { + doCloseEditPopup(false); + return; + } + + switch (iEditPopupSubItem) + { + case 0: //Entry name + { + int nameLen = Edit_GetTextLength(hCurEditPopup); + if (nameLen > 0 && nameLen < INT_MAX - 2) + { + std::unique_ptr nameT(new TCHAR[nameLen + 2]); + nameT[0] = 0; + nameT[nameLen + 1] = 0; + Edit_GetText(hCurEditPopup, nameT.get(), nameLen + 1); + size_t name8Len = 0; + auto name8 = unique_TCHARToMultiByte(nameT.get(), name8Len); + pBundleFileInfo->renameEntry(*this->pContext, entryIdx, std::string(name8.get())); + } + } + break; + default: + assert(false); + } + } + DestroyWindow(hCurEditPopup); + hCurEditPopup = NULL; + iEditPopupItem = iEditPopupSubItem = 0; +} + +void BundleDialog::onOpenEditPopup() +{ + if (pCurFileEntry == nullptr || pCurFileEntry->getContextInfoPtr() == nullptr) + { + doCloseEditPopup(false); + return; + } + BundleFileContextInfo *pBundleInfo = static_cast(pCurFileEntry->getContextInfoPtr()); + + HWND hList = GetDlgItem(hDialog, IDC_ENTRYLIST); + LVITEM item; + item.mask = LVIF_PARAM; + item.lParam = (LPARAM)-1; + item.iItem = iEditPopupItem; + item.iSubItem = 0; + ListView_GetItem(hList, &item); + + size_t entryIdx = (size_t)item.lParam; + assert(entryIdx < pBundleInfo->getEntryCount()); + if (entryIdx >= pBundleInfo->getEntryCount()) + { + doCloseEditPopup(false); + return; + } + + switch (iEditPopupSubItem) + { + case 0: //Dependency name/text + { + std::string name = pBundleInfo->getNewEntryName(entryIdx); + auto pText = unique_MultiByteToTCHAR(name.c_str()); + Edit_SetText(hCurEditPopup, pText.get()); + return; + } + break; + default: + assert(false); + } + //Failure + doCloseEditPopup(false); +} + +void BundleDialog::onChangeSelection() +{ + EnableWindow(GetDlgItem(hDialog, IDC_BTNIMPORT), (this->nSelected > 0) ? TRUE : FALSE); + EnableWindow(GetDlgItem(hDialog, IDC_BTNEXPORT), (this->nSelected > 0) ? TRUE : FALSE); + EnableWindow(GetDlgItem(hDialog, IDC_BTNREMOVE), (this->nSelected > 0) ? TRUE : FALSE); +} + +INT_PTR CALLBACK BundleDialog::BundleDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + INT_PTR ret = (INT_PTR)FALSE; + BundleDialog *pThis = (BundleDialog*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + switch (message) + { + case WM_DESTROY: + break; + case WM_NCDESTROY: + break; + case WM_CLOSE: + if (pThis) + pThis->hDialog = NULL; + DestroyWindow(hDlg); + ret = (INT_PTR)TRUE; + break; + case WM_INITDIALOG: + { + SetWindowLongPtr(hDlg, GWLP_USERDATA, lParam); + pThis = (BundleDialog*)lParam; + + pThis->nSelected = 0; + pThis->onChangeSelection(); + + HWND hList = GetDlgItem(hDlg, IDC_ENTRYLIST); + //Subclass the ListView to support item edit popups (via double click). + SetWindowSubclass(hList, BundleListViewProc, 0, reinterpret_cast(pThis)); + ListView_SetExtendedListViewStyle(hList, LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER); + LVCOLUMN column; + ZeroMemory(&column, sizeof(LVCOLUMN)); + column.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM; + column.cx = 250; + column.pszText = const_cast(TEXT("File Name")); + column.iSubItem = 0; + ListView_InsertColumn(hList, 0, &column); + column.cx = 85; + column.pszText = const_cast(TEXT("Has changed")); + //column.iSubItem = 1; + ListView_InsertColumn(hList, 1, &column); + + ShowWindow(hDlg, SW_SHOW); + PostMessage(hDlg, WM_SIZE, 0, 0); + ret = (INT_PTR)TRUE; + } + break; + case WM_SIZE: + onResize(hDlg); + break; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_BTNADD: + if (pThis) pThis->importItem(true); + ret = (INT_PTR)TRUE; + break; + case IDC_BTNIMPORT: + if (pThis) pThis->importItem(false); + ret = (INT_PTR)TRUE; + break; + case IDC_BTNEXPORT: + if (pThis) pThis->exportItem(); + ret = (INT_PTR)TRUE; + break; + case IDC_BTNREMOVE: + if (pThis) pThis->removeItem(); + ret = (INT_PTR)TRUE; + break; + } + break; + case WM_NOTIFY: + { + HWND hList = GetDlgItem(hDlg, IDC_ENTRYLIST); + NMLISTVIEW *pNotifyLV = (NMLISTVIEW*)lParam; + if (pNotifyLV->hdr.hwndFrom != hList) + break; + switch (pNotifyLV->hdr.code) + { + case LVN_ITEMCHANGED: + { + NMLISTVIEW *pInfo = (NMLISTVIEW*)lParam; + if (pThis) + { + if ((pInfo->uOldState ^ pInfo->uNewState) & LVIS_SELECTED) + { + bool isSelected = (pInfo->uNewState & LVIS_SELECTED) ? true : false; + int iItem = pInfo->iItem; + if (iItem == -1) + { + pThis->nSelected = (isSelected) ? ListView_GetItemCount(hList) : 0; + } + else + { + if (isSelected) + pThis->nSelected++; + else + pThis->nSelected--; + } + } + } + } + pThis->onChangeSelection(); + break; + } + } + break; + } + return ret; +} +LRESULT CALLBACK BundleDialog::BundleListViewProc(HWND hWnd, UINT message, + WPARAM wParam, LPARAM lParam, + uintptr_t uIdSubclass, DWORD_PTR dwRefData) +{ + BundleDialog *pThis = (BundleDialog*)dwRefData; + switch (message) + { + case WM_LBUTTONDBLCLK: + { + LVHITTESTINFO hitTestInfo = {}; + hitTestInfo.pt.x = GET_X_LPARAM(lParam); + hitTestInfo.pt.y = GET_Y_LPARAM(lParam); + if (ListView_SubItemHitTest(hWnd, &hitTestInfo) != -1 && hitTestInfo.iSubItem == 0) + { + if (pThis->hCurEditPopup != NULL) + pThis->doCloseEditPopup(); + pThis->iEditPopupItem = hitTestInfo.iItem; + pThis->iEditPopupSubItem = hitTestInfo.iSubItem; + + RECT targetRect = {}; + ListView_GetSubItemRect(hWnd, hitTestInfo.iItem, hitTestInfo.iSubItem, LVIR_BOUNDS, &targetRect); + if (hitTestInfo.iSubItem == 0) + { + pThis->hCurEditPopup = + CreateWindow(WC_EDIT, TEXT(""), ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE, + targetRect.left, targetRect.top, targetRect.right - targetRect.left, targetRect.bottom - targetRect.top, + hWnd, GetMenu(hWnd), pThis->pContext->getMainWindow().getHInstance(), NULL); + } + SetWindowSubclass(pThis->hCurEditPopup, EditPopupProc, 0, reinterpret_cast(pThis)); + SendMessage(pThis->hCurEditPopup, WM_SETFONT, (WPARAM)(HFONT)SendMessage(hWnd, WM_GETFONT, 0, 0), FALSE); + SetFocus(pThis->hCurEditPopup); + pThis->onOpenEditPopup(); + } + } + break; + case WM_NCDESTROY: + RemoveWindowSubclass(hWnd, BundleListViewProc, uIdSubclass); + break; + } + return DefSubclassProc(hWnd, message, wParam, lParam); +} +LRESULT CALLBACK BundleDialog::EditPopupProc(HWND hWnd, UINT message, + WPARAM wParam, LPARAM lParam, + uintptr_t uIdSubclass, DWORD_PTR dwRefData) +{ + BundleDialog *pThis = (BundleDialog*)dwRefData; + switch (message) + { + case WM_KILLFOCUS: + //if (wParam == WA_INACTIVE) + { + if (pThis->hCurEditPopup != NULL) + pThis->doCloseEditPopup(); + } + break; + case WM_KEYDOWN: + if (LOWORD(wParam) == VK_ESCAPE || (pThis->iEditPopupSubItem != 0 && LOWORD(wParam) == VK_RETURN)) + { + if (pThis->hCurEditPopup != NULL) + pThis->doCloseEditPopup(); + } + break; + case WM_NCDESTROY: + RemoveWindowSubclass(hWnd, EditPopupProc, uIdSubclass); + break; + } + return DefSubclassProc(hWnd, message, wParam, lParam); +} + +void BundleDialog::onUpdateBundleEntry(BundleFileContextInfo *info, size_t index) +{ + if (this->hDialog == NULL || this->pCurFileEntry == nullptr || info != this->pCurFileEntry->getContextInfoPtr() + || info == nullptr || info->getFileContext() == nullptr) + return; + HWND hList = GetDlgItem(hDialog, IDC_ENTRYLIST); + BundleFileContextInfo *pBundleInfo = static_cast(pCurFileEntry->getContextInfoPtr()); + + int nListItems = ListView_GetItemCount(hList); + + //Retrieve a list of all dependencies in range that are already in the list. + int iAffectedItem = -1; + for (int i = 0; i < nListItems; i++) + { + LVITEM item; + item.mask = LVIF_PARAM; + item.lParam = (LPARAM)-1; + item.iItem = i; + item.iSubItem = 0; + ListView_GetItem(hList, &item); + if ((size_t)item.lParam == index) + { + iAffectedItem = i; + break; + } + } + bool hasChanged = pBundleInfo->entryHasChanged(index); + std::string newName = pBundleInfo->getNewEntryName(index); + if (pBundleInfo->entryIsRemoved(index)) + { + if (iAffectedItem != -1) + { + if (hCurEditPopup != NULL && iEditPopupItem == iAffectedItem) + doCloseEditPopup(false); + ListView_DeleteItem(hList, iAffectedItem); + } + } + else if (iAffectedItem == -1) + { + //Add new element + LVITEM item; + item.mask = LVIF_PARAM; + item.lParam = (LPARAM)index; + item.iItem = nListItems; + item.iSubItem = 0; + ListView_InsertItem(hList, &item); + + list_updateBundleName(hList, nListItems, newName.c_str()); + list_updateBundleModified(hList, nListItems, hasChanged); + } + else + { + //Change existing element + if (hCurEditPopup != NULL && iEditPopupItem == iAffectedItem) + doCloseEditPopup(false); + list_updateBundleName(hList, iAffectedItem, newName.c_str()); + list_updateBundleModified(hList, iAffectedItem, hasChanged); + } +} diff --git a/UABE_Win32/BundleDialog.h b/UABE_Win32/BundleDialog.h new file mode 100644 index 0000000..5f052d6 --- /dev/null +++ b/UABE_Win32/BundleDialog.h @@ -0,0 +1,59 @@ +#pragma once +#include "MainWindow2.h" +#include "Win32AppContext.h" + +class BundleDialog : public IFileManipulateDialog, public MainWindowEventHandler +{ + MainWindowEventHandlerHandle eventHandlerHandle; + class Win32AppContext *pContext; + HWND hDialog; + HWND hParentWnd; + + HWND hCurEditPopup; + int iEditPopupItem; + int iEditPopupSubItem; + + //Tracks LVN_ITEMCHANGED notifications (may potentially reach 2 temporarily). + int nSelected; + + FileEntryUIInfo *pCurFileEntry; + std::map fileEntries; +protected: + static INT_PTR CALLBACK BundleDlgProc(HWND hWnd, UINT message, + WPARAM wParam, LPARAM lParam); + static LRESULT CALLBACK BundleListViewProc(HWND hWnd, UINT uMsg, + WPARAM wParam, LPARAM lParam, + uintptr_t uIdSubclass, DWORD_PTR dwRefData); + static LRESULT CALLBACK EditPopupProc(HWND hWnd, UINT uMsg, + WPARAM wParam, LPARAM lParam, + uintptr_t uIdSubclass, DWORD_PTR dwRefData); + + void onUpdateCurrentFile(); + void doCloseEditPopup(bool applyChanges = true); + void onOpenEditPopup(); + + void onChangeSelection(); + + void importItem(bool addNew); + void exportItem(); + void removeItem(); + +public: + //IFileManipulateDialog + BundleDialog(class Win32AppContext *pContext, HWND hParentWnd); + ~BundleDialog(); + void addFileContext(const std::pair &context); + void removeFileContext(FileEntryUIInfo *pContext); + EFileManipulateDialogType getType(); + HWND getWindowHandle(); + void onHotkey(ULONG message, DWORD keyCode); //message : currently only WM_KEYDOWN; keyCode : VK_F3 for instance + bool onCommand(WPARAM wParam, LPARAM lParam); //Called for unhandled WM_COMMAND messages. Returns true if this dialog has handled the request, false otherwise. + void onShow(); + void onHide(); + bool hasUnappliedChanges(bool *applyable=nullptr); + bool applyChanges(); + bool doesPreferNoAutoclose(); + + //MainWindowEventHandler + void onUpdateBundleEntry(BundleFileContextInfo *pFile, size_t index); +}; diff --git a/UABE_Win32/CMakeLists.txt b/UABE_Win32/CMakeLists.txt new file mode 100644 index 0000000..2aa9a9c --- /dev/null +++ b/UABE_Win32/CMakeLists.txt @@ -0,0 +1,6 @@ +add_library (UABE_Win32 SHARED AddAssetDialog.cpp AssetDependDialog.cpp AssetListDialog.cpp AssetViewModifyDialog.cpp BatchImportDialog.cpp BundleDialog.cpp FileDialog.cpp MainWindow2.cpp ModInstallerEditor2.cpp ModPackageLoader.cpp MonoBehaviourManager.cpp ProgressDialog.cpp SelectClassDbDialog.cpp TypeDatabaseEditor.cpp TypeDbPackageEditor.cpp Win32AppContext.cpp Win32ModTreeDialogBase.cpp AssetBundleExtractor.rc UABE_Win32.manifest "Win32BatchImportDesc.h" "api.h" "Win32BatchImportDesc.cpp" "Win32TaskStatusTracker.cpp" "SplitterControlHandler.cpp" "Win32PluginManager.cpp") +target_include_directories (UABE_Win32 PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${MCTRL_INCLUDE_DIR}) + +set_target_properties(UABE_Win32 PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") +target_link_libraries(UABE_Win32 PUBLIC UABE_Generic AssetsTools libStringConverter) +target_link_libraries(UABE_Win32 PRIVATE ModInstaller libCompression Comctl32.lib Shlwapi.lib mCtrl) diff --git a/UABE_Win32/FileDialog.cpp b/UABE_Win32/FileDialog.cpp new file mode 100644 index 0000000..2dc5d3f --- /dev/null +++ b/UABE_Win32/FileDialog.cpp @@ -0,0 +1,868 @@ +#include "stdafx.h" +#include "FileDialog.h" +#include "../libStringConverter/convert.h" +#include +#include +#pragma comment(lib, "Shell32.lib") + +//for Windows XP +bool testedFileDialog = false; +bool useLegacyFileDialog = false; + +int GetFileTypeFilterCount(const wchar_t *filter) +{ + int ret = 1; + size_t filterStringLen = wcslen(filter); + for (size_t i = 1; i < filterStringLen; i++) + { + if (filter[i] == L':') + { + if (filter[i-1] == L'\\') //allow usage of ':' in text if a backslash prepends it + continue; + if ((i+1) < filterStringLen) + ret++; + } + } + return ret; +} +//also modifies the filter string for backslashes +void GetNextFileFilterIndices( + wchar_t *filter, + size_t *pFilterStringLen, //don't always run wcslen(filter) + size_t curSpecStart, + bool &hasName, bool &hasNextSpec, + size_t &curNameStart, size_t &nextSpecStart) +{ + size_t filterStringLen = *pFilterStringLen; + hasName = false; hasNextSpec = false; + nextSpecStart = filterStringLen; + for (size_t _ch = curSpecStart; _ch < filterStringLen; _ch++) + { + if (!hasName && filter[_ch] == L'|') + { + curNameStart = _ch+1; + hasName = true; + } + if (filter[_ch] == L':') + { + if (filter[_ch-1] != L'\\') + { + nextSpecStart = _ch; + hasNextSpec = true; + break; + } + else + { + for (size_t _tmpch = _ch; _tmpch <= filterStringLen; _tmpch++) + { + filter[_tmpch-1] = filter[_tmpch]; + } + (*pFilterStringLen)--; // + filterStringLen--; // + nextSpecStart--; // + } + } + } +} + +#pragma region Fallback +struct LegacyFileFilter +{ + wchar_t *name; + wchar_t *spec; +}; +typedef BOOL(WINAPI *prot_GetOpenFileNameW)(LPOPENFILENAMEW lpofn); +prot_GetOpenFileNameW pGetOpenFileNameW = NULL; +typedef BOOL(WINAPI *prot_GetSaveFileNameW)(LPOPENFILENAMEW lpofn); +prot_GetSaveFileNameW pGetSaveFileNameW = NULL; + +typedef PIDLIST_ABSOLUTE(__stdcall *prot_SHBrowseForFolderW)(LPBROWSEINFOW lpbi); +prot_SHBrowseForFolderW pSHBrowseForFolderW = NULL; +typedef BOOL(__stdcall *prot_SHGetPathFromIDListW)(PCIDLIST_ABSOLUTE pidl, LPWSTR pszPath); +prot_SHGetPathFromIDListW pSHGetPathFromIDListW = NULL; +void InitFileDialogFallback() +{ + HMODULE hComdlg = GetModuleHandle(TEXT("Comdlg32.dll")); + HMODULE hShell32 = GetModuleHandle(TEXT("Shell32.dll")); + if (hComdlg == nullptr || hShell32 == nullptr) + return; + pGetOpenFileNameW = (prot_GetOpenFileNameW)GetProcAddress(hComdlg, "GetOpenFileNameW"); + pGetSaveFileNameW = (prot_GetSaveFileNameW)GetProcAddress(hComdlg, "GetSaveFileNameW"); + pSHBrowseForFolderW = (prot_SHBrowseForFolderW)GetProcAddress(hShell32, "SHBrowseForFolderW"); + pSHGetPathFromIDListW = (prot_SHGetPathFromIDListW)GetProcAddress(hShell32, "SHGetPathFromIDListW"); + useLegacyFileDialog = true; +} + +bool MakeFileTypeFilterFallback(const wchar_t *_filter, wchar_t **_buf, int *_filterCount) +{ + size_t filterStringLen = wcslen(_filter); + + wchar_t *filter = (wchar_t*)malloc(sizeof(wchar_t) * (filterStringLen+1)); + if (filter == NULL) + return false; + wcscpy_s(filter, filterStringLen+1, _filter); + + if (filterStringLen == 0 || filter[0] == L':') + { + *_buf = NULL; + *_filterCount = 0; + return true; + } + int filterCount = GetFileTypeFilterCount(filter); + + wchar_t *stringMap = (wchar_t*)malloc(sizeof(wchar_t) * (filterStringLen + 2 + filterCount)); + if (stringMap == NULL) + { + free(filter); + return false; + } + + size_t curStrMapIndex = 0; + + size_t curSpecStart = 0; + size_t curNameStart = 0; + for (int i = 0; i < filterCount; i++) + { + bool hasName = false; bool hasNextSpec = false; + size_t nextSpecStart = filterStringLen; + + GetNextFileFilterIndices(filter, &filterStringLen, curSpecStart, hasName, hasNextSpec, curNameStart, nextSpecStart); + + if (!hasName) curNameStart = curSpecStart; + + size_t specSize = (hasName ? (curNameStart-1) : nextSpecStart) - curSpecStart; + size_t nameSize = nextSpecStart - curNameStart; + + if (hasName) + { + wcsncpy_s(&stringMap[curStrMapIndex], nameSize+1, &filter[curNameStart], nameSize); + stringMap[curStrMapIndex+nameSize] = 0; + curStrMapIndex += (nameSize + 1); + } + else + { + stringMap[curStrMapIndex] = L' '; + stringMap[curStrMapIndex+1] = 0; + curStrMapIndex += 2; + } + + wcsncpy_s(&stringMap[curStrMapIndex], specSize+1, &filter[curSpecStart], specSize); + stringMap[curStrMapIndex+specSize] = 0; + curStrMapIndex += (specSize + 1); + + if ((i+1) < filterCount && !hasNextSpec) + filterCount = i+1; + if (!hasNextSpec) + break; + curSpecStart = nextSpecStart + 1; + } + stringMap[curStrMapIndex] = stringMap[curStrMapIndex+1] = 0; + + free(filter); + *_buf = stringMap; + *_filterCount = filterCount; + return true; +} +void FreeFileTypeFilterFallback(wchar_t **buf) +{ + if (buf != NULL && *buf != NULL) + { + free(*buf); + *buf = NULL; + } +} + +HRESULT MakeFileDialogStructureFallback(OPENFILENAMEW *out, + HWND hOwner, wchar_t **filePathBuf, wchar_t *pFilters, int filterCount, LPCWSTR defaultFile, + size_t pathBufferLen = MAX_PATH+1) +{ + size_t defaultFileNameLen = (defaultFile != NULL) ? (wcslen(defaultFile)+1) : 1; + if (pathBufferLen < defaultFileNameLen) + pathBufferLen = defaultFileNameLen; + *filePathBuf = (wchar_t*)malloc(sizeof(wchar_t) * pathBufferLen); + if (*filePathBuf == NULL) + return E_OUTOFMEMORY; + + if (defaultFile) + memcpy(*filePathBuf, defaultFile, defaultFileNameLen * sizeof(wchar_t)); + else + memset(*filePathBuf, 0, sizeof(wchar_t) * pathBufferLen);//(*filePathBuf)[0] = 0; + + ZeroMemory(out, sizeof(OPENFILENAMEW)); + out->lStructSize = sizeof(OPENFILENAMEW); + out->hwndOwner = hOwner; + out->lpstrFilter = pFilters; + out->nFilterIndex = 1; + out->lpstrFile = *filePathBuf; + out->nMaxFile = (DWORD)pathBufferLen; + out->Flags = OFN_EXPLORER | OFN_HIDEREADONLY; + //out->lpstrInitialDir = &fallback_nullChar; + + return NOERROR; +} +#define FNERR_BUFFERTOOSMALL 0x3003 +HRESULT ShowFileOpenDialogFallback(HWND hOwner, wchar_t **filePathBuf, const wchar_t *fileTypeFilter, UINT *pOutSelFilter, LPCTSTR defaultFile, LPCTSTR windowTitle) +{ + if (pGetOpenFileNameW == NULL) + { + MessageBox(hOwner, TEXT("Unable to open a file dialog (fallback method failed)!"), TEXT("Error"), MB_ICONERROR); + return E_NOTIMPL; + } + if (pOutSelFilter) + *pOutSelFilter = (UINT)-1; + wchar_t *pFilters; int filterCount; + if (!MakeFileTypeFilterFallback(fileTypeFilter, &pFilters, &filterCount)) + return E_OUTOFMEMORY; + OPENFILENAMEW fileNameStruct; + + HRESULT ret = NOERROR; + size_t pathBufferLen = MAX_PATH+1; + while ((ret = MakeFileDialogStructureFallback(&fileNameStruct, hOwner, filePathBuf, + pFilters, filterCount, defaultFile, pathBufferLen)) != E_OUTOFMEMORY) + { + fileNameStruct.Flags |= OFN_FILEMUSTEXIST; + if (windowTitle != NULL) + fileNameStruct.lpstrTitle = windowTitle; + if (!pGetOpenFileNameW(&fileNameStruct)) + { + free(*filePathBuf); + *filePathBuf = NULL; + DWORD error = CommDlgExtendedError(); + if (error == FNERR_BUFFERTOOSMALL) + { + pathBufferLen = *(unsigned short*)fileNameStruct.lpstrFile; + } + else + { + ret = E_FAIL; + break; + } + } + else + { + if (pOutSelFilter) + *pOutSelFilter = fileNameStruct.nFilterIndex; + break; + } + } + FreeFileTypeFilterFallback(&pFilters); + return ret; +} + +HRESULT ShowFileSaveDialogFallback(HWND hOwner, wchar_t **filePathBuf, const wchar_t *fileTypeFilter, UINT *pOutSelFilter, LPCWSTR defaultFile, LPCTSTR windowTitle) +{ + if (pGetSaveFileNameW == NULL) + { + MessageBox(hOwner, TEXT("Unable to open a file dialog (fallback method failed)!"), TEXT("Error"), MB_ICONERROR); + return E_NOTIMPL; + } + if (pOutSelFilter) + *pOutSelFilter = (UINT)-1; + wchar_t *pFilters; int filterCount; + if (!MakeFileTypeFilterFallback(fileTypeFilter, &pFilters, &filterCount)) + return E_OUTOFMEMORY; + OPENFILENAMEW fileNameStruct; + + HRESULT ret = NOERROR; + size_t pathBufferLen = MAX_PATH+1; + while ((ret = MakeFileDialogStructureFallback(&fileNameStruct, hOwner, filePathBuf, + pFilters, filterCount, defaultFile, pathBufferLen)) != E_OUTOFMEMORY) + { + fileNameStruct.Flags |= OFN_CREATEPROMPT; + if (windowTitle != NULL) + fileNameStruct.lpstrTitle = windowTitle; + if (!pGetSaveFileNameW(&fileNameStruct)) + { + free(*filePathBuf); + *filePathBuf = NULL; + DWORD error = CommDlgExtendedError(); + if (error == FNERR_BUFFERTOOSMALL) + { + pathBufferLen = *(unsigned short*)fileNameStruct.lpstrFile; + } + else + { + ret = E_FAIL; + break; + } + } + else + { + if (pOutSelFilter) + *pOutSelFilter = fileNameStruct.nFilterIndex; + break; + } + } + FreeFileTypeFilterFallback(&pFilters); + return ret; +} +HRESULT ShowFileOpenDialogMultiSelectFallback(HWND hOwner, WCHAR **filePathsBuf, const wchar_t *fileTypeFilter, UINT *pOutSelFilter, LPCTSTR defaultFile, LPCTSTR windowTitle) +{ + if (pGetOpenFileNameW == NULL) + { + MessageBox(hOwner, TEXT("Unable to open a file dialog (fallback method failed)!"), TEXT("Error"), MB_ICONERROR); + return E_NOTIMPL; + } + if (pOutSelFilter) + *pOutSelFilter = (UINT)-1; + wchar_t *pFilters; int filterCount; + if (!MakeFileTypeFilterFallback(fileTypeFilter, &pFilters, &filterCount)) + return E_OUTOFMEMORY; + OPENFILENAMEW fileNameStruct; + + HRESULT ret = NOERROR; + size_t pathBufferLen = (MAX_PATH+1)*16+1; + while ((ret = MakeFileDialogStructureFallback(&fileNameStruct, hOwner, filePathsBuf, pFilters, + filterCount, defaultFile, pathBufferLen)) != E_OUTOFMEMORY) + { + fileNameStruct.Flags |= OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT; + if (windowTitle != NULL) + fileNameStruct.lpstrTitle = windowTitle; + if (!pGetOpenFileNameW(&fileNameStruct)) + { + free(*filePathsBuf); + *filePathsBuf = NULL; + DWORD error = CommDlgExtendedError(); + if (error == FNERR_BUFFERTOOSMALL) + { + if (pathBufferLen > *(unsigned short*)fileNameStruct.lpstrFile) + pathBufferLen += (MAX_PATH+1)*16; + else + pathBufferLen = *(unsigned short*)fileNameStruct.lpstrFile; + } + else + { + ret = E_FAIL; + break; + } + } + else + { + if (pOutSelFilter) + *pOutSelFilter = fileNameStruct.nFilterIndex; + break; + } + } + if (ret == NOERROR && *filePathsBuf) + { + size_t requiredLen = 0; + size_t directoryLen = wcslen(*filePathsBuf); + if (directoryLen) + { + size_t curStrIndex = 0; size_t curStrLen; + while ((curStrLen = wcslen(&(*filePathsBuf)[directoryLen+1+curStrIndex]))) + { + curStrIndex += curStrLen + 1; + requiredLen += directoryLen + 1 + curStrLen + 1; + } + if (curStrIndex == 0) + { + FreeFileTypeFilterFallback(&pFilters); + return NOERROR; + } + } + WCHAR *wFilePathsList = (WCHAR*)malloc((requiredLen+1) * sizeof(WCHAR)); size_t curOutStrIndex = 0; + if (!wFilePathsList) + { + free(*filePathsBuf); + FreeFileTypeFilterFallback(&pFilters); + return E_OUTOFMEMORY; + } + size_t curStrIndex = 0; size_t curStrLen; + while ((curStrLen = wcslen(&(*filePathsBuf)[directoryLen+1+curStrIndex]))) + { + memcpy(&wFilePathsList[curOutStrIndex], *filePathsBuf, directoryLen * sizeof(WCHAR)); + wFilePathsList[curOutStrIndex + directoryLen] = L'\\'; + memcpy(&wFilePathsList[curOutStrIndex + directoryLen + 1], + &(*filePathsBuf)[directoryLen+1+curStrIndex], curStrLen * sizeof(WCHAR)); + wFilePathsList[curOutStrIndex + directoryLen + 1 + curStrLen] = L';'; + curStrIndex += curStrLen + 1; + curOutStrIndex += directoryLen + 1 + curStrLen + 1; + } + if (!curOutStrIndex) + wFilePathsList[0] = 0; + else + wFilePathsList[curOutStrIndex - 1] = 0; + free(*filePathsBuf); + *filePathsBuf = wFilePathsList; + } + FreeFileTypeFilterFallback(&pFilters); + return ret; +} + +BOOL ShowFolderSelectDialogFallback(HWND hOwner, WCHAR **folderPathBuf, LPCWSTR windowTitle) +{ + if (pSHBrowseForFolderW == NULL + || pSHGetPathFromIDListW == NULL) + { + MessageBox(hOwner, TEXT("Unable to open a file dialog (fallback method failed)!"), TEXT("Error"), MB_ICONERROR); + return FALSE; + } + LPITEMIDLIST pItemIDList = NULL; + BROWSEINFOW bi; memset(&bi, 0, sizeof(BROWSEINFOW)); + BOOL ret = FALSE; + + WCHAR *folderPath = (WCHAR*)malloc(MAX_PATH * sizeof(WCHAR)); + if (folderPath == NULL) + return FALSE; + *folderPathBuf = folderPath; + folderPath[0] = 0; + + bi.hwndOwner = hOwner; + bi.pszDisplayName = folderPath; + bi.pidlRoot = NULL; + bi.lpszTitle = windowTitle; + bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI; + + if ((pItemIDList = pSHBrowseForFolderW(&bi)) != NULL) + { + ret = pSHGetPathFromIDListW(pItemIDList, folderPath); + CoTaskMemFree(pItemIDList); + } + if (!ret) + { + *folderPathBuf = NULL; + free(folderPath); + } + + return ret; +} +#pragma endregion + +bool MakeFileTypeFilter(const wchar_t *_filter, COMDLG_FILTERSPEC **_buf, int *_filterCount) +{ + size_t filterStringLen = wcslen(_filter); + + wchar_t *filter = (wchar_t*)malloc(sizeof(wchar_t) * (filterStringLen+1)); + if (filter == NULL) + return false; + wcscpy_s(filter, filterStringLen+1, _filter); + + if (filterStringLen == 0 || filter[0] == L':') + { + *_buf = NULL; + *_filterCount = 0; + return true; + } + int filterCount = GetFileTypeFilterCount(filter); + + //create a buffer that can at least hold all filterspecs + all strings + uint8_t *filterMem = (uint8_t*)malloc(sizeof(COMDLG_FILTERSPEC) * (size_t)filterCount + 2 * (sizeof(wchar_t) * filterStringLen) + (size_t)filterCount); + if (filterMem == NULL) + { + free(filter); + return false; + } + + COMDLG_FILTERSPEC *filterArray = (COMDLG_FILTERSPEC*)filterMem; + wchar_t *stringMap = (wchar_t*)&filterMem[sizeof(COMDLG_FILTERSPEC) * (size_t)filterCount]; + + size_t curStrMapIndex = 0; + + size_t curSpecStart = 0; + size_t curNameStart = 0; + for (int i = 0; i < filterCount; i++) + { + bool hasName = false; bool hasNextSpec = false; + size_t nextSpecStart = filterStringLen; + + GetNextFileFilterIndices(filter, &filterStringLen, curSpecStart, hasName, hasNextSpec, curNameStart, nextSpecStart); + + if (!hasName) curNameStart = curSpecStart; + + size_t specSize = (hasName ? (curNameStart-1) : nextSpecStart) - curSpecStart; + size_t nameSize = nextSpecStart - curNameStart; + + filterArray[i].pszSpec = &stringMap[curStrMapIndex]; + wcsncpy_s(&stringMap[curStrMapIndex], specSize+1, &filter[curSpecStart], specSize); + stringMap[curStrMapIndex+specSize] = 0; + curStrMapIndex += (specSize + 1); + + if (hasName) + { + filterArray[i].pszName = &stringMap[curStrMapIndex]; + wcsncpy_s(&stringMap[curStrMapIndex], nameSize+1, &filter[curNameStart], nameSize); + stringMap[curStrMapIndex+nameSize] = 0; + curStrMapIndex += (nameSize + 1); + } + else + filterArray[i].pszName = filterArray[i].pszSpec; + + if ((i+1) < filterCount && !hasNextSpec) + filterCount = i+1; + if (!hasNextSpec) + break; + curSpecStart = nextSpecStart + 1; + } + + free(filter); + *_buf = filterArray; + *_filterCount = filterCount; + return true; +} +void FreeFileTypeFilter(COMDLG_FILTERSPEC **buf) +{ + if (buf != NULL && *buf != NULL) + { + free(*buf); + *buf = NULL; + } +} + +bool TestFileDialogCompatible() +{ + if (!testedFileDialog) + { + IFileDialog *pfd = NULL; + HRESULT hr = CoCreateInstance(CLSID_FileSaveDialog, + NULL, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&pfd)); + if (!SUCCEEDED(hr)) + InitFileDialogFallback(); + else + pfd->Release(); + testedFileDialog = true; + } + return !useLegacyFileDialog; +} + +HRESULT ShowFileOpenDialog(HWND hOwner, wchar_t **filePathBuf, const wchar_t *fileTypeFilter, + UINT *pOutSelFilter, LPCTSTR defaultFile, LPCTSTR windowTitle, + const GUID& guid) +{ + if (!TestFileDialogCompatible()) + return ShowFileOpenDialogFallback(hOwner, filePathBuf, fileTypeFilter, pOutSelFilter, defaultFile, windowTitle); + COMDLG_FILTERSPEC *pFilters; int filterCount; + if (!MakeFileTypeFilter(fileTypeFilter, &pFilters, &filterCount)) + return E_OUTOFMEMORY; + HRESULT ret = ShowFileDialog(hOwner, filePathBuf, CLSID_FileOpenDialog, pFilters, filterCount, pOutSelFilter, defaultFile, windowTitle, guid); + FreeFileTypeFilter(&pFilters); + return ret; +} + +HRESULT ShowFileOpenDialogMultiSelect(HWND hOwner, + WCHAR ***filePathsBuf, size_t *filePathCountBuf, + const wchar_t *fileTypeFilter, UINT *pOutSelFilter, + LPCTSTR defaultFile, LPCTSTR windowTitle, + const GUID& guid); +HRESULT ShowFileOpenDialogMultiSelect(HWND hOwner, std::vector &filePathsBuf, + const char *fileTypeFilter, UINT *pOutSelFilter, + const char* defaultFile, const char* windowTitle, + const GUID& guid) +{ + size_t strLenW; + TCHAR *defaultFileT = _MultiByteToTCHAR(defaultFile, strLenW); + TCHAR *windowTitleT = _MultiByteToTCHAR(windowTitle, strLenW); + TCHAR *fileTypeFilterW = _MultiByteToWide(fileTypeFilter, strLenW); + HRESULT result; + if (!TestFileDialogCompatible()) + { + WCHAR *filePathsW = nullptr; + result = ShowFileOpenDialogMultiSelectFallback(hOwner, &filePathsW, fileTypeFilterW, pOutSelFilter, defaultFileT, windowTitleT); + if (SUCCEEDED(result) && filePathsW[0] != 0) + { + size_t filePathsLenW = wcslen(filePathsW); + size_t filePathsLen = (size_t)WideCharToMultiByte(CP_UTF8, 0, filePathsW, (int)filePathsLenW, NULL, 0, NULL, NULL); + char *filePaths = new char[filePathsLen + 1]; + WideCharToMultiByte(CP_UTF8, 0, filePathsW, (int)filePathsLenW, filePaths, (int)filePathsLen, NULL, NULL); + filePaths[filePathsLen] = 0; + for (size_t i = 0, start = 0; i <= filePathsLen; i++) + { + if (filePaths[i] == L';' || filePaths[i] == 0) + { + filePaths[i] = 0; + filePathsBuf.push_back(&filePaths[start]); + start = i + 1; + } + } + if (filePathsBuf.size() == 0) + delete[] filePaths; + } + } + else + { + WCHAR **filePathsArray; size_t filePathsCount; + result = ShowFileOpenDialogMultiSelect(hOwner, &filePathsArray, &filePathsCount, fileTypeFilterW, pOutSelFilter, defaultFileT, windowTitleT, guid); + if (SUCCEEDED(result) && filePathsCount > 0) + { + size_t totalLen = 0; + filePathsBuf.resize(filePathsCount + 1); + for (size_t i = 0; i < filePathsCount; i++) + { + size_t pathLenW = wcslen(filePathsArray[i]); + if (pathLenW > INT_MAX) pathLenW = INT_MAX; + size_t curBufferLen = (size_t)(WideCharToMultiByte(CP_UTF8, 0, filePathsArray[i], (int)pathLenW, NULL, 0, NULL, NULL) + 1) * sizeof(char); + filePathsBuf[i] = (char*)totalLen; + totalLen += curBufferLen; + } + filePathsBuf[filePathsCount] = (char*)totalLen; + char *filePaths = new char[totalLen + 1]; + for (size_t i = 0; i < filePathsCount; i++) + { + size_t pathLenW = wcslen(filePathsArray[i]); + if (pathLenW > INT_MAX) pathLenW = INT_MAX; + + int outLen = (int)(filePathsBuf[i+1] - filePathsBuf[i]); + filePathsBuf[i] = &filePaths[(size_t)filePathsBuf[i]]; + WideCharToMultiByte(CP_UTF8, 0, filePathsArray[i], (int)pathLenW, filePathsBuf[i], outLen - 1, NULL, NULL); + filePathsBuf[i][outLen - 1] = 0; + } + filePathsBuf.resize(filePathsCount); + for (size_t i = 0; i < filePathsCount; i++) + FreeCOMFilePathBuf(&filePathsArray[i]); + delete[] filePathsArray; + } + } + _FreeWCHAR(fileTypeFilterW); + _FreeTCHAR(windowTitleT); + _FreeTCHAR(defaultFileT); + return result; +} +void FreeFilePathsMultiSelect(std::vector &filePathsBuf) +{ + if (filePathsBuf.size() > 0) + delete[] filePathsBuf[0]; + filePathsBuf.clear(); +} +HRESULT ShowFileOpenDialogMultiSelect(HWND hOwner, WCHAR **filePathsBuf, + const wchar_t *fileTypeFilter, UINT *pOutSelFilter, + LPCTSTR defaultFile, LPCTSTR windowTitle, + const GUID &guid) +{ + if (!TestFileDialogCompatible()) + return ShowFileOpenDialogMultiSelectFallback(hOwner, filePathsBuf, fileTypeFilter, pOutSelFilter, defaultFile, windowTitle); + WCHAR **filePathsArray; size_t filePathsCount; + *filePathsBuf = NULL; + HRESULT ret = ShowFileOpenDialogMultiSelect(hOwner, &filePathsArray, &filePathsCount, fileTypeFilter, pOutSelFilter, defaultFile, windowTitle, guid); + if (!SUCCEEDED(ret)) + return ret; + size_t resultSize = 0; + for (size_t i = 0; i < filePathsCount; i++) + resultSize += wcslen(filePathsArray[i]) + 1; + if (!resultSize) resultSize = 1; + *filePathsBuf = new WCHAR[resultSize]; + size_t resultOffset = 0; + for (size_t i = 0; i < filePathsCount; i++) + { + size_t curSize = (filePathsArray[i] != NULL) ? wcslen(filePathsArray[i]) : 0; + memcpy(&(*filePathsBuf)[resultOffset], filePathsArray[i], curSize * sizeof(WCHAR)); + (*filePathsBuf)[resultOffset + curSize] = L';'; + resultOffset += curSize + 1; + } + (*filePathsBuf)[resultSize - 1] = 0; + for (size_t i = 0; i < filePathsCount; i++) + FreeCOMFilePathBuf(&filePathsArray[i]); + delete[] filePathsArray; + return ret; +} +HRESULT ShowFileOpenDialogMultiSelect(HWND hOwner, + WCHAR ***filePathsBuf, size_t *filePathCountBuf, + const wchar_t *fileTypeFilter, UINT *pOutSelFilter, + LPCTSTR defaultFile, LPCTSTR windowTitle, + const GUID& guid) +{ + if (pOutSelFilter) + *pOutSelFilter = (UINT)-1; + COMDLG_FILTERSPEC *pFilters; int filterCount; + if (!MakeFileTypeFilter(fileTypeFilter, &pFilters, &filterCount)) + return E_OUTOFMEMORY; + *filePathsBuf = NULL; *filePathCountBuf = 0; + IFileOpenDialog *pfd = NULL; + HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog, + NULL, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&pfd)); + if (SUCCEEDED(hr)) + { + pfd->SetClientGuid(guid); + DWORD dwOptions; + hr = pfd->GetOptions(&dwOptions); + if (SUCCEEDED(hr) && SUCCEEDED((hr = pfd->SetOptions(dwOptions | FOS_ALLOWMULTISELECT)))) + { + if (windowTitle != NULL) + pfd->SetTitle(windowTitle); + hr = pfd->SetFileTypes(filterCount, pFilters); + if (SUCCEEDED(hr)) + { + hr = pfd->SetFileTypeIndex(0); + if (defaultFile != NULL) + pfd->SetFileName(defaultFile); + if (SUCCEEDED(hr)) + { + hr = pfd->Show(hOwner); + if (SUCCEEDED(hr)) + { + IShellItemArray *pFileItems; + hr = pfd->GetResults(&pFileItems); + if (SUCCEEDED(hr)) + { + DWORD numItems; + hr = pFileItems->GetCount(&numItems); + if (SUCCEEDED(hr)) + { + if (pOutSelFilter) + pfd->GetFileTypeIndex(pOutSelFilter); + *filePathsBuf = new WCHAR*[numItems](); *filePathCountBuf = numItems; + for (DWORD i = 0; i < numItems; i++) + { + (*filePathsBuf)[i] = NULL; + IShellItem *pFileItem = NULL; + if (SUCCEEDED(pFileItems->GetItemAt(i, &pFileItem))) + { + WCHAR *filePath; + hr = pFileItem->GetDisplayName(SIGDN_FILESYSPATH, &filePath); + if (SUCCEEDED(hr)) + { + (*filePathsBuf)[i] = filePath; + } + pFileItem->Release(); + } + } + hr = 0; + } + pFileItems->Release(); + } + } + } + } + } + pfd->Release(); + } + FreeFileTypeFilter(&pFilters); + return hr; +} + +HRESULT ShowFileSaveDialog(HWND hOwner, wchar_t **filePathBuf, + const wchar_t *fileTypeFilter, UINT *pOutSelFilter, + LPCTSTR defaultFile, LPCTSTR windowTitle, + const GUID &guid) +{ + if (!TestFileDialogCompatible()) + return ShowFileSaveDialogFallback(hOwner, filePathBuf, fileTypeFilter, pOutSelFilter, defaultFile, windowTitle); + COMDLG_FILTERSPEC *pFilters; int filterCount; + if (!MakeFileTypeFilter(fileTypeFilter, &pFilters, &filterCount)) + return E_OUTOFMEMORY; + HRESULT ret = ShowFileDialog(hOwner, filePathBuf, CLSID_FileSaveDialog, pFilters, filterCount, pOutSelFilter, defaultFile, windowTitle, guid); + FreeFileTypeFilter(&pFilters); + return ret; +} + +HRESULT ShowFileDialog(HWND hOwner, wchar_t **filePathBuf, + CLSID dialogClass, + const COMDLG_FILTERSPEC *pFilters, int filtersCount, UINT *pOutSelFilter, + LPCTSTR defaultFile, LPCTSTR windowTitle, + const GUID& guid) +{ + if (pOutSelFilter) + *pOutSelFilter = (UINT)-1; + if (!TestFileDialogCompatible()) + return E_NOTIMPL; + *filePathBuf = NULL; + IFileDialog *pfd = NULL; + HRESULT hr = CoCreateInstance(dialogClass, + NULL, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&pfd)); + if (SUCCEEDED(hr)) + { + pfd->SetClientGuid(guid); + if (windowTitle != NULL) + pfd->SetTitle(windowTitle); + hr = pfd->SetFileTypes(filtersCount, pFilters); + if (SUCCEEDED(hr)) + { + hr = pfd->SetFileTypeIndex(0); + if (defaultFile != NULL) + pfd->SetFileName(defaultFile); + if (SUCCEEDED(hr)) + { + hr = pfd->Show(hOwner); + if (SUCCEEDED(hr)) + { + IShellItem *pFileItem; + hr = pfd->GetResult(&pFileItem); + if (SUCCEEDED(hr)) + { + WCHAR *filePath; + hr = pFileItem->GetDisplayName(SIGDN_FILESYSPATH, &filePath); + if (SUCCEEDED(hr)) + { + if (pOutSelFilter) + pfd->GetFileTypeIndex(pOutSelFilter); + *filePathBuf = filePath; + } + pFileItem->Release(); + } + } + } + } + pfd->Release(); + } + return hr; +} +HRESULT ShowPickFolderDialog(HWND hOwner, wchar_t **folderPathBuf, CLSID dialogClass, LPCWSTR windowTitle, + const GUID& guid) +{ + if (!TestFileDialogCompatible()) + return E_NOTIMPL; + *folderPathBuf = NULL; + IFileDialog *pfd = NULL; + HRESULT hr = CoCreateInstance(dialogClass, + NULL, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&pfd)); + if (SUCCEEDED(hr)) + { + pfd->SetClientGuid(guid); + DWORD dwOptions; + if (SUCCEEDED(pfd->GetOptions(&dwOptions))) + { + pfd->SetOptions(dwOptions | FOS_PICKFOLDERS); + } + if (windowTitle != NULL) + pfd->SetTitle(windowTitle); + hr = pfd->Show(hOwner); + if (SUCCEEDED(hr)) + { + IShellItem *pFileItem; + hr = pfd->GetResult(&pFileItem); + if (SUCCEEDED(hr)) + { + WCHAR *filePath; + hr = pFileItem->GetDisplayName(SIGDN_FILESYSPATH, &filePath); + if (SUCCEEDED(hr)) + { + *folderPathBuf = filePath; + } + pFileItem->Release(); + } + } + pfd->Release(); + } + return hr; +} + +BOOL ShowFolderSelectDialog(HWND hOwner, WCHAR **folderPathBuf, LPCWSTR windowTitle, const GUID &guid) +{ + if (!TestFileDialogCompatible()) + return ShowFolderSelectDialogFallback(hOwner, folderPathBuf, windowTitle); + return SUCCEEDED(ShowPickFolderDialog(hOwner, folderPathBuf, CLSID_FileOpenDialog, windowTitle, guid)); +} + +void FreeCOMFilePathBuf(WCHAR **filePath) +{ + if (useLegacyFileDialog) + { + if (filePath != NULL && *filePath != NULL) + { + free(*filePath); + *filePath = NULL; + } + } + else if (filePath != NULL && *filePath != NULL) + { + CoTaskMemFree(*filePath); + *filePath = NULL; + } +} \ No newline at end of file diff --git a/UABE_Win32/FileDialog.h b/UABE_Win32/FileDialog.h new file mode 100644 index 0000000..047e967 --- /dev/null +++ b/UABE_Win32/FileDialog.h @@ -0,0 +1,40 @@ +#pragma once +#include "api.h" +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#include +#include +#include +#include + +static const GUID UABE_FILEDIALOG_DEFAULT_GUID = {0x832dbb4b, 0xf1bf, 0x8e37, 0x69, 0xc5, 0x41, 0x6e, 0x5f, 0x72, 0x67, 0xc0 }; + +UABE_Win32_API HRESULT ShowFileOpenDialog(HWND hOwner, wchar_t **filePathBuf, const wchar_t *fileTypeFilter, + UINT *pOutSelFilter = nullptr, LPCTSTR defaultFile = nullptr, LPCTSTR windowTitle = nullptr, + const GUID &guid = UABE_FILEDIALOG_DEFAULT_GUID); + +//free with delete[] filePath +UABE_Win32_API HRESULT ShowFileOpenDialogMultiSelect(HWND hOwner, WCHAR **filePathsBuf, + const wchar_t *fileTypeFilter, UINT *pOutSelFilter = nullptr, + LPCTSTR defaultFile = nullptr, LPCTSTR windowTitle = nullptr, + const GUID &guid = UABE_FILEDIALOG_DEFAULT_GUID); +//free with FreeFilePathsMultiSelect +UABE_Win32_API HRESULT ShowFileOpenDialogMultiSelect(HWND hOwner, std::vector &filePathsBuf, + const char *fileTypeFilter, UINT *pOutSelFilter = nullptr, + const char *defaultFile = nullptr, const char *windowTitle = nullptr, + const GUID& guid = UABE_FILEDIALOG_DEFAULT_GUID); +UABE_Win32_API void FreeFilePathsMultiSelect(std::vector &filePathsBuf); + +//free with FreeCOMFilePathBuf for each filePath, then delete[] filePaths +UABE_Win32_API HRESULT ShowFileSaveDialog(HWND hOwner, wchar_t **filePathBuf, const wchar_t *fileTypeFilter, + UINT *pOutSelFilter = nullptr, LPCTSTR defaultFile = nullptr, LPCTSTR windowTitle = nullptr, + const GUID &guid = UABE_FILEDIALOG_DEFAULT_GUID); +UABE_Win32_API HRESULT ShowFileDialog(HWND hOwner, wchar_t **filePathBuf, + CLSID dialogClass, + const COMDLG_FILTERSPEC *pFilters, int filtersCount, + UINT *pOutSelFilter = nullptr, LPCTSTR defaultFile = nullptr, LPCTSTR windowTitle = nullptr, + const GUID &guid = UABE_FILEDIALOG_DEFAULT_GUID); +UABE_Win32_API BOOL ShowFolderSelectDialog(HWND hOwner, WCHAR **folderPathBuf, LPCWSTR windowTitle = nullptr, + const GUID &guid = UABE_FILEDIALOG_DEFAULT_GUID); +UABE_Win32_API void FreeCOMFilePathBuf(WCHAR **filePath); diff --git a/UABE_Win32/MainWindow2.cpp b/UABE_Win32/MainWindow2.cpp new file mode 100644 index 0000000..a511a40 --- /dev/null +++ b/UABE_Win32/MainWindow2.cpp @@ -0,0 +1,2441 @@ +#include "stdafx.h" +#include "resource.h" +#include "MainWindow2.h" +#include "Win32AppContext.h" +#include "AssetListDialog.h" +#include "AssetDependDialog.h" +#include "BundleDialog.h" +#include "FileDialog.h" +#include "TypeDatabaseEditor.h" +#include "TypeDbPackageEditor.h" +#include "MonoBehaviourManager.h" +#include "ModInstallerEditor2.h" +#include "ModPackageLoader.h" +#include "AddAssetDialog.h" +#include "Win32TaskStatusTracker.h" +#include "../libStringConverter/convert.h" + +#include +#include +#include +#include +#include + +IFileManipulateDialogFactory::IFileManipulateDialogFactory() +{} +IFileManipulateDialogFactory::~IFileManipulateDialogFactory() +{} + +DefaultFileDialogFactory::DefaultFileDialogFactory(class Win32AppContext *pContext) + : pContext(pContext)//, pAssetListDialog(nullptr) +{} + +DefaultFileDialogFactory::~DefaultFileDialogFactory() +{ +} +std::shared_ptr DefaultFileDialogFactory::construct(EFileManipulateDialogType type, HWND hParent) +{ + switch (type) + { + case FileManipulateDialog_AssetList: + return std::make_shared(pContext, hParent); + case FileManipulateDialog_AssetsDependencies: + return std::make_shared(pContext, hParent); + case FileManipulateDialog_Bundle: + return std::make_shared(pContext, hParent); + default: + return nullptr; + } +} + + +FileManipulateDialogInfo::FileManipulateDialogInfo() + : pEntry(nullptr), hTreeItem(NULL), param(0) +{ + b_isFileManipulateDialogInfo = true; +} +FileManipulateDialogInfo::~FileManipulateDialogInfo() +{ +} + +FileEntryUIInfo::FileEntryUIInfo(MC_HTREELISTITEM hTreeItem, const std::string &fullName, bool isFilePath) : + failed(false), pending(true), pContextInfo(nullptr), shortNameIndex(0), + hTreeItem(hTreeItem), standardDialogsCount(0) +{ + b_isFileManipulateDialogInfo = false; + this->fullName.assign(fullName); + if (isFilePath) + { + const char *fullNameC = this->fullName.c_str(); + for (shortNameIndex = fullName.size(); shortNameIndex > 0; shortNameIndex--) + { + if (fullNameC[shortNameIndex-1] == '/' || fullNameC[shortNameIndex-1] == '\\') + break; + } + } +} +FileEntryUIInfo::~FileEntryUIInfo() +{ + //Note that the destructor is also called during emplace_back, i.e. after a move constructor call. + if (!pending) + setContextInfo(std::shared_ptr(nullptr)); +} + +UIDisposableCache::UIDisposableCache() + : nRefs(0) +{} +UIDisposableCache::~UIDisposableCache() +{} +void UIDisposableCache::incRef() +{ + assert(nRefs != UINT_MAX); + ++nRefs; +} +void UIDisposableCache::decRef() +{ + assert(nRefs != 0); + if (--nRefs == 0) + delete this; +} + + +void MainWindowEventHandler::onUpdateContainers(AssetsFileContextInfo *pFile) +{} +void MainWindowEventHandler::onChangeAsset(AssetsFileContextInfo *pFile, pathid_t pathID, bool wasRemoved) +{} +void MainWindowEventHandler::onUpdateDependencies(AssetsFileContextInfo *info, size_t from, size_t to) +{} +void MainWindowEventHandler::onUpdateBundleEntry(BundleFileContextInfo *pFile, size_t index) +{} + +static const HANDLE uabeDlgProp = (HANDLE)(uintptr_t)(GetCurrentProcessId() | 0x80000000); + +MainWindow2::MainWindow2(HINSTANCE hInstance) : + pContext(nullptr), hDlg(NULL), hMenu(NULL), + hInstance(hInstance), + hContainersDlg(NULL), activeManipDlgTab(0), hHotkeyHook(NULL), + mainPanelSplitter(0.3f, 0.15f, 0.8f), + fileTreeColumnRatio(0.8f), fileEntryCountersByType(), + ignoreTreeSelChanges(false), skipDeselectOnTabChange(false), + decompressTargetDir_cancel(false) +{ +} + +bool MainWindow2::Initialize() +{ + if (!pDialogFactory) + pDialogFactory.reset(new DefaultFileDialogFactory(this->pContext)); + WNDCLASSEX windowClass = {}; + windowClass.cbSize = sizeof(windowClass); + if (!GetClassInfoEx(hInstance, TEXT("msctls_progress32_dblclk"), &windowClass) + && GetClassInfoEx(NULL, TEXT("msctls_progress32"), &windowClass)) + { + windowClass.hInstance = hInstance; + windowClass.lpszMenuName = nullptr; + windowClass.lpszClassName = TEXT("msctls_progress32_dblclk"); + windowClass.style |= CS_DBLCLKS; + RegisterClassEx(&windowClass); + } + this->hDlg = CreateDialogParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DlgProc, (LPARAM)this); + return (this->hDlg != NULL); +} +int MainWindow2::HandleMessages() +{ + MSG msg = {}; + //HACCEL hAccelTable; + //hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_ASSETBUNDLEEXTRACTOR)); + + while (GetMessage(&msg, NULL, 0, 0)) + { + if (IsDialogMessage(this->hDlg, &msg))// || TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) + continue; + IFileManipulateDialog *pActiveManipDlg = this->getActiveManipDlg(); + if (pActiveManipDlg && IsDialogMessage(pActiveManipDlg->getWindowHandle(), &msg)) + continue; + Win32TaskStatusTracker* pStatusTracker = dynamic_cast(this->pStatusTracker.get()); + if (pStatusTracker && pStatusTracker->getDialog() != NULL && IsDialogMessage(pStatusTracker->getDialog(), &msg)) + continue; + //TranslateMessage(&msg); + DispatchMessage(&msg); + } + pStatusTracker.reset(); + return (int)msg.wParam; +} + +static MC_HTREELISTITEM insertEntry(HWND hTree, MC_HTREELISTITEM parent, const std::string &name) +{ + MC_TLINSERTSTRUCT insertStruct; + insertStruct.hParent = parent; + insertStruct.hInsertAfter = MC_TLI_LAST; + insertStruct.item.fMask = MC_TLIF_TEXT; + size_t nameLenT; + TCHAR *tcText = _MultiByteToTCHAR(name.c_str(), nameLenT); + insertStruct.item.pszText = tcText; + insertStruct.item.cchTextMax = (int)(nameLenT + 1); + + MC_HTREELISTITEM hItem = (MC_HTREELISTITEM)SendMessage(hTree, MC_TLM_INSERTITEM, 0, (LPARAM)&insertStruct); + _FreeTCHAR(tcText); + return hItem; +} +static MC_HTREELISTITEM insertPendingEntry(HWND hTree, MC_HTREELISTITEM parent, const std::string &fileName) +{ + return insertEntry(hTree, parent, "Pending : " + fileName); +} +static void updateEntryInfoRef(HWND hTree, MC_HTREELISTITEM hTreeItem, ITreeParameter &info) +{ + MC_TLITEM item; + item.fMask = MC_TLIF_PARAM; + item.lParam = (LPARAM)&info; + + SendMessage(hTree, MC_TLM_SETITEM, (WPARAM)hTreeItem, (LPARAM)&item); +} +static void updateEntryName(HWND hTree, MC_HTREELISTITEM hTreeItem, const std::string &newName) +{ + MC_TLITEM item; + item.fMask = MC_TLIF_TEXT; + size_t nameLenT; + TCHAR *tcText = _MultiByteToTCHAR(newName.c_str(), nameLenT); + item.pszText = tcText; + item.cchTextMax = (int)(nameLenT + 1); + + SendMessage(hTree, MC_TLM_SETITEM, (WPARAM)hTreeItem, (LPARAM)&item); + + _FreeTCHAR(tcText); +} +static void setEntryFileID(HWND hTree, MC_HTREELISTITEM hTreeItem, unsigned int fileID) +{ + TCHAR fileIDBuf[20]; fileIDBuf[19] = 0; + _stprintf_s(fileIDBuf, TEXT("%u"), fileID); + MC_TLSUBITEMW subitemInfo; + subitemInfo.fMask = MC_TLSIF_TEXT; + subitemInfo.iSubItem = 1; + subitemInfo.pszText = fileIDBuf; + subitemInfo.cchTextMax = sizeof(fileIDBuf) / sizeof(TCHAR); + SendMessage(hTree, MC_TLM_SETSUBITEM, (WPARAM)hTreeItem, (LPARAM)&subitemInfo); +} +static ITreeParameter *getEntryParam(HWND hTree, MC_HTREELISTITEM hTreeItem) +{ + MC_TLITEM item; + item.fMask = MC_TLIF_PARAM; + item.lParam = 0; + if (!SendMessage(hTree, MC_TLM_GETITEM, (WPARAM)hTreeItem, (LPARAM)&item)) + item.lParam = 0; + return (ITreeParameter*)item.lParam; +} +static bool getEntryParam_TreeItem(ITreeParameter *pTreeParameter, MC_HTREELISTITEM &hTreeItem) +{ + hTreeItem = nullptr; + if (pTreeParameter) + { + if (pTreeParameter->isFileEntryInfo()) + hTreeItem = pTreeParameter->asFileEntryInfo()->hTreeItem; + else if (pTreeParameter->isFileManipulateDialogInfo()) + hTreeItem = pTreeParameter->asFileManipulateDialogInfo()->hTreeItem; + else + return false; + } + else + return false; + return true; +} +static FileEntryUIInfo *getEntryParam_FileEntryInfo(ITreeParameter *pTreeParameter, bool &isPrimaryDialog) +{ + if (pTreeParameter) + { + isPrimaryDialog = false; + if (pTreeParameter->isFileEntryInfo()) + { + isPrimaryDialog = true; + return pTreeParameter->asFileEntryInfo(); + } + else if (pTreeParameter->isFileManipulateDialogInfo()) + { + FileManipulateDialogInfo *pInfo = pTreeParameter->asFileManipulateDialogInfo(); + FileEntryUIInfo *pEntry = pInfo->pEntry; + if (pInfo == &pEntry->standardDialogs[0]) + isPrimaryDialog = true; + return pEntry; + } + } + else + isPrimaryDialog = true; + return nullptr; +} +static void setHasChildren(HWND hTree, MC_HTREELISTITEM hTreeItem, bool hasChildren) +{ + MC_TLITEM item; + item.fMask = MC_TLIF_CHILDREN; + item.cChildren = hasChildren ? 1 : 0; + + SendMessage(hTree, MC_TLM_SETITEM, (WPARAM)hTreeItem, (LPARAM)&item); +} +bool getSelectItem(HWND hTree, MC_HTREELISTITEM hTreeItem) +{ + MC_TLITEM item; + item.fMask = MC_TLIF_STATE; + item.stateMask = TVIS_SELECTED; + item.state = 0; + if (SendMessage(hTree, MC_TLM_GETITEM, (WPARAM)hTreeItem, (LPARAM)&item)) + return ((item.state & TVIS_SELECTED) != 0); + return false; +} +static void setSelectItem(HWND hTree, MC_HTREELISTITEM hTreeItem, bool select) +{ + MC_TLITEM item; + item.fMask = MC_TLIF_STATE; + item.stateMask = TVIS_SELECTED; + item.state = select ? TVIS_SELECTED : 0; + + SendMessage(hTree, MC_TLM_SETITEM, (WPARAM)hTreeItem, (LPARAM)&item); +} +inline MC_HTREELISTITEM MCTreeList_GetNextSelection(HWND hTree, MC_HTREELISTITEM curItem = NULL) +{ + return (MC_HTREELISTITEM)SendMessage(hTree, MC_TLM_GETNEXTITEM, MC_TLGN_CARET, (LPARAM)curItem); +} +inline MC_HTREELISTITEM MCTreeList_DeleteItem(HWND hTree, MC_HTREELISTITEM item) +{ + return (MC_HTREELISTITEM)SendMessage(hTree, MC_TLM_DELETEITEM, 0, (LPARAM)item); +} +static void DeleteFileEntry_TreeItems(HWND hTree, FileEntryUIInfo *pEntryInfo) +{ + //Deleting the parent item also deletes the children. + MCTreeList_DeleteItem(hTree, pEntryInfo->hTreeItem); +} + +LRESULT CALLBACK MainWindow2::KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam) +{ + LRESULT nResult = 1; + if(nCode == HC_ACTION && wParam == PM_REMOVE) + { + MSG *p = (MSG*) lParam; + HWND hForeground = GetForegroundWindow(); + DWORD wndProcessId = 0; + GetWindowThreadProcessId(hForeground, &wndProcessId); + if(p->message == WM_KEYDOWN && hForeground != NULL + && GetProp(hForeground, TEXT("UABE")) == uabeDlgProp && wndProcessId == GetCurrentProcessId()) + { + MainWindow2 *pThis = (MainWindow2*)GetWindowLongPtr(hForeground, GWLP_USERDATA); + if (pThis->getActiveManipDlg() != nullptr) + { + PostMessage(pThis->hDlg, WM_APP+1, (DWORD)p->wParam, (LPARAM)p->message); + } + } + } + if(nCode < 0 || nResult) + return CallNextHookEx(NULL,nCode,wParam,lParam); + return nResult; +} +void MainWindow2::OnSelectClassDbDialogFinished() +{ + if (!fileEntriesPendingForDbSelection.empty()) + { + //Apply the user choice for the first pending file entry. + DbSelectionQueueEntry entry = this->fileEntriesPendingForDbSelection.front(); + this->fileEntriesPendingForDbSelection.pop_front(); + auto pClassDatabase = this->pSelectClassDbDialog->getClassDatabaseResult_Move(); + if (entry.pEntry != nullptr && pClassDatabase != nullptr) + { + AssetsFileContextInfo *pTargetEntry = dynamic_cast(entry.pEntry->pContextInfo.get()); + ClassDatabaseFile_sharedptr pClassDatabaseShared(std::move(pClassDatabase)); + if (this->pSelectClassDbDialog->isRememberForVersion()) + this->databaseFilesByEngineVersion[this->pSelectClassDbDialog->getEngineVersion()] = pClassDatabaseShared; + else + { + //Remove old engine version defaults. + auto databaseIt = this->databaseFilesByEngineVersion.find(this->pSelectClassDbDialog->getEngineVersion()); + if (databaseIt != this->databaseFilesByEngineVersion.end()) + this->databaseFilesByEngineVersion.erase(databaseIt); + } + if (this->pSelectClassDbDialog->isRememberForAll()) + this->defaultDatabaseFile = pClassDatabaseShared; + else + this->defaultDatabaseFile.reset(); //Remove the general default. + pTargetEntry->SetClassDatabase(std::move(pClassDatabaseShared)); + } + } + this->pSelectClassDbDialog.reset(); + //Take care of the remaining file entries waiting for database selection. + while (!this->fileEntriesPendingForDbSelection.empty()) + { + DbSelectionQueueEntry &entry = this->fileEntriesPendingForDbSelection.front(); + if (entry.pEntry == nullptr) + { + this->fileEntriesPendingForDbSelection.pop_front(); + continue; + } + AssetsFileContextInfo *pContextInfo = dynamic_cast(entry.pEntry->pContextInfo.get()); + if (this->TryFindClassDatabase(pContextInfo)) //If new defaults can solve this entry, there is no need to open a dialog. + this->fileEntriesPendingForDbSelection.pop_front(); + else + { + this->OpenClassDatabaseSelection(pContextInfo, entry.reason_DatabaseNotFound); + break; + } + } +} + +void MainWindow2::onGCTick() +{ + PROCESS_MEMORY_COUNTERS memCounters = {}; + memCounters.cb = sizeof(PROCESS_MEMORY_COUNTERS); + size_t memoryToFree = this->pContext->getGCMemoryLimit() / 2; + assert(memoryToFree > 0); + if (GetProcessMemoryInfo(GetCurrentProcess(), &memCounters, memCounters.cb) + && memCounters.PagefileUsage >= this->pContext->getGCMemoryLimit() + && memoryToFree > 0) + { + struct ErasableElementDesc + { + std::list> *pList; + std::list>::iterator it; + time_t lastUseTime; + }; + std::vector erasableElements; + unsigned int minAge = this->pContext->getGCMinAge(); + time_t now = time(nullptr); + for (auto cacheListIt = this->disposableCacheElements.begin(); + cacheListIt != this->disposableCacheElements.end(); + ++cacheListIt) + { + for (auto cacheElemIt = cacheListIt->second.begin(); + cacheElemIt != cacheListIt->second.end(); + ++cacheElemIt) + { + if (!cacheElemIt->get()->isInUse()) + { + time_t lastUseTime = cacheElemIt->get()->getLastUseTime(); + //Assuming time_t is in seconds since some reference time (which is the case for POSIX and Win32) + if (lastUseTime < now && now - lastUseTime >= minAge) + { + ErasableElementDesc desc = {&cacheListIt->second, cacheElemIt, lastUseTime}; + erasableElements.push_back(std::move(desc)); + } + } + } + } + struct + { + bool operator()(ErasableElementDesc &a, ErasableElementDesc &b) const + { + return a.lastUseTime < b.lastUseTime; + } + } useTimeComparator_Ascending; + //Sort the erasable elements by their use time, so that the least recently used element is first. + std::sort(erasableElements.begin(), erasableElements.end(), useTimeComparator_Ascending); + size_t estimatedFreedMemory = 0; + for (size_t i = 0; i < erasableElements.size() && estimatedFreedMemory < memoryToFree; i++) + { + estimatedFreedMemory += erasableElements[i].it->get()->approxMemory(); + erasableElements[i].pList->erase(erasableElements[i].it); + } + } +} +bool askUserApplyChangeBeforeInstaller(HWND hParent); +static INT_PTR CALLBACK AboutDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + UNREFERENCED_PARAMETER(lParam); + switch (message) + { + case WM_INITDIALOG: + return (INT_PTR)TRUE; + + case WM_COMMAND: + if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) + { + EndDialog(hDlg, LOWORD(wParam)); + return (INT_PTR)TRUE; + } + break; + } + return (INT_PTR)FALSE; +} +INT_PTR CALLBACK MainWindow2::DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + int wmId, wmEvent; + UNREFERENCED_PARAMETER(lParam); + INT_PTR ret = (INT_PTR)FALSE; + MainWindow2 *pThis = (MainWindow2*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + if (pThis && pThis->mainPanelSplitter.handleWin32Message(hDlg, message, wParam, lParam)) + { + if (pThis->mainPanelSplitter.shouldResize()) + pThis->onResize(); + return (message == WM_SETCURSOR) ? (INT_PTR)TRUE : (INT_PTR)0; + } + switch (message) + { + case WM_APP+0: //New messages available. + if (pThis && pThis->pContext) + { + pThis->pContext->handleMessages(); + } + ret = (INT_PTR)TRUE; + break; + case WM_APP+1: + if (pThis) + { + IFileManipulateDialog *pManipDlg = pThis->getActiveManipDlg(); + if (pManipDlg) + pManipDlg->onHotkey((ULONG)lParam, (DWORD)wParam); + } + ret = (INT_PTR)TRUE; + break; + case WM_APP+2: //SelectClassDbDialog finished + if (pThis && pThis->pSelectClassDbDialog) + { + pThis->OnSelectClassDbDialogFinished(); + } + ret = (INT_PTR)TRUE; + break; + case WM_CLOSE: + { + if (pThis->onCloseProgramCommand()) + { + DestroyWindow(hDlg); + ret = (INT_PTR)TRUE; + } + } + break; + case WM_DESTROY: + { + if (pThis) + { + SetWindowLongPtr(hDlg, GWLP_USERDATA, 0); + DestroyMenu(pThis->hMenu); + KillTimer(hDlg, (uintptr_t)0); + pThis->hDlg = NULL; + pThis->hMenu = NULL; + + if (pThis->hContainersDlg != NULL) + { + CloseWindow(pThis->hContainersDlg); + pThis->hContainersDlg = NULL; + } + //FreeAssetsInfo(pThis); + //FreeMonoBehaviourClassDbs(); + } + + PostQuitMessage(0); + } + break; + case WM_NCDESTROY: + { + RemoveProp(hDlg, TEXT("UABE")); + if (pThis) + UnhookWindowsHookEx(pThis->hHotkeyHook); + } + break; + case WM_TIMER: + { + if (wParam == (uintptr_t)0 && pThis) + { + pThis->onGCTick(); + } + } + break; + case WM_INITDIALOG: + { + SetWindowLongPtr(hDlg, GWLP_USERDATA, lParam); + SetProp(hDlg, TEXT("UABE"), uabeDlgProp); + pThis = (MainWindow2*)lParam; + pThis->pStatusTracker.reset(new Win32TaskStatusTracker(*pThis->pContext, GetDlgItem(hDlg, IDC_PROGMAIN), GetDlgItem(hDlg, IDC_SPROGDESC))); + + pThis->mainPanelSplitter.setSplitterWindow(GetDlgItem(hDlg, IDC_CONTENTSEPARATE)); + pThis->mainPanelSplitter.handleWin32Message(hDlg, message, wParam, lParam); + + pThis->hHotkeyHook = SetWindowsHookEx(WH_GETMESSAGE, KeyboardHookProc, NULL, GetCurrentThreadId()); + SetWindowSubclass(GetDlgItem(hDlg, IDC_PROGMAIN), ProgSubclassProc, 0, (DWORD_PTR)pThis); + + + pThis->hMenu = LoadMenu(pThis->hInstance, MAKEINTRESOURCE(IDC_MAINMENU)); + SetMenu(hDlg, pThis->hMenu); + EnableMenuItem(pThis->hMenu, IDM_FILE_APPLY, MF_GRAYED); + EnableMenuItem(pThis->hMenu, IDM_FILE_SAVE, MF_GRAYED); + EnableMenuItem(pThis->hMenu, IDM_FILE_SAVEALL, MF_GRAYED); + + { + HWND hTree = GetDlgItem(hDlg, IDC_TREEFILES); + MC_TLCOLUMN col; + col.fMask = MC_TLCF_TEXT; + col.pszText = const_cast(_T("Files and Components")); + col.cchTextMax = (int)(_tcslen(col.pszText) + 1); + SendMessage(hTree, MC_TLM_INSERTCOLUMN, 0, (LPARAM)&col); + col.pszText = const_cast(_T("File ID")); + col.cchTextMax = (int)(_tcslen(col.pszText) + 1); + SendMessage(hTree, MC_TLM_INSERTCOLUMN, 1, (LPARAM)&col); + //Enable child multiselect + SendMessage(hTree, MC_TLM_SETCUSTOMSTYLE, (1 << 0), 0); + } + { + HWND hTabsControl = GetDlgItem(hDlg, IDC_MANIPDLGTABS); + MC_MTITEMWIDTH widths; + widths.dwDefWidth = 0; + widths.dwMinWidth = 90; + SendMessage(hTabsControl, MC_MTM_SETITEMWIDTH, 0, (LPARAM) &widths); + SendMessage(hTabsControl, MC_MTM_SETCUSTOMSTYLE, (WPARAM) MC_MTCS_OPENBTN, 0); + } + + pThis->ignoreTreeSelChanges = false; + pThis->skipDeselectOnTabChange = false; + + ShowWindow(hDlg, SW_SHOW); + PostMessage(hDlg, WM_SIZE, 0, 0); + //Cache GC timer + SetTimer(hDlg, (uintptr_t)0, 2000, NULL); + ret = (INT_PTR)TRUE; + } + break; + case WM_SIZE: + if (pThis) + { + pThis->onResize(); + ret = (INT_PTR)TRUE; + } + break; + case WM_GETMINMAXINFO: + { + LPMINMAXINFO pMinMax = (LPMINMAXINFO)lParam; + pMinMax->ptMinTrackSize.x = 400; + pMinMax->ptMinTrackSize.y = 400; + ret = (INT_PTR)TRUE; + } + break; + case WM_NOTIFY: + if (pThis && pThis->pContext) + { + switch (((NMHDR*)lParam)->code) + { + case MC_TLN_SELCHANGED: + if (((NMHDR*)lParam)->hwndFrom == GetDlgItem(hDlg, IDC_TREEFILES)) + { + //Style MC_TLS_MULTISELECT makes the hItemOld/hItemNew fields meaningless, + // i.e. we need to iterate over the selection and compare with the old one manually. + if (!pThis->ignoreTreeSelChanges) + pThis->onChangeFileSelection(); + } + break; + case MC_MTN_OPENITEM: + if (((NMHDR*)lParam)->hwndFrom == GetDlgItem(hDlg, IDC_MANIPDLGTABS)) + { + pThis->doOpenTab(); + SetWindowLongPtr(hDlg, DWLP_MSGRESULT, TRUE); + return (INT_PTR)TRUE; //Prevent tab deletion. + } + break; + case MC_MTN_CLOSEITEM: + if (((NMHDR*)lParam)->hwndFrom == GetDlgItem(hDlg, IDC_MANIPDLGTABS)) + { + MC_NMMTCLOSEITEM *pNotification = (MC_NMMTCLOSEITEM*)lParam; + if (!pThis->preDeleteTab(pNotification)) + SetWindowLongPtr(hDlg, DWLP_MSGRESULT, TRUE); + return (INT_PTR)TRUE; //Prevent tab deletion. + } + break; + case MC_MTN_DELETEITEM: + if (((NMHDR*)lParam)->hwndFrom == GetDlgItem(hDlg, IDC_MANIPDLGTABS)) + { + MC_NMMTDELETEITEM *pNotification = (MC_NMMTDELETEITEM*)lParam; + pThis->onDeleteTab(pNotification); + } + break; + case MC_MTN_SELCHANGE: + if (((NMHDR*)lParam)->hwndFrom == GetDlgItem(hDlg, IDC_MANIPDLGTABS)) + { + MC_NMMTSELCHANGE *pNotification = (MC_NMMTSELCHANGE*)lParam; + pThis->onSwitchTabs(pNotification); + } + break; + case NM_RCLICK: + if (((NMHDR*)lParam)->hwndFrom == GetDlgItem(hDlg, IDC_MANIPDLGTABS)) + { + //TODO: Handle tab right click. + //Use MC_MTM_HITTEST to find out which tab is being clicked. + // -> MC_MTM_HITTEST expects the control client position of the mouse position (=> GetCursorPos(&pos) and ScreenToClient(((NMHDR*)lParam)->hwndFrom, &pos)). + } + break; + case MC_MTN_DELETEALLITEMS: + if (((NMHDR*)lParam)->hwndFrom == GetDlgItem(hDlg, IDC_MANIPDLGTABS)) + { + SetWindowLongPtr(hDlg, DWLP_MSGRESULT, TRUE); + return (INT_PTR)TRUE; + } + break; + } + } + break; + case WM_COMMAND: + wmId = LOWORD(wParam); + wmEvent = HIWORD(wParam); + if (pThis != nullptr && pThis->pContext != nullptr) + { + switch (wmId) + { + case IDM_VIEW_ADDASSET: + { + AddAssetDialog dlg(*pThis->pContext); + dlg.open(); + } + break; + case IDM_FILE_OPEN: + pThis->onOpenFileCommand(); + break; + case IDM_FILE_CLOSE: + pThis->onCloseFileCommand(); + break; + case IDM_EXIT: + PostMessage(hDlg, WM_CLOSE, 0, 0); + break; + case IDM_FILE_SAVE: + if (IFileManipulateDialog *pActiveManipDlg = pThis->getActiveManipDlg()) + pActiveManipDlg->onCommand(MAKEWPARAM(IDM_FILE_APPLY,wmEvent), lParam); + if (ManipDlgDesc *pCurTab = pThis->getActiveManipDlgDesc()) + { + for (size_t i = 0; i < pCurTab->selection.size(); i++) + { + if (pCurTab->selection[i] == nullptr) continue; + FileEntryUIInfo *pUIInfo = nullptr; + if (pCurTab->selection[i]->isFileManipulateDialogInfo()) + { + FileManipulateDialogInfo *pDlgInfo = pCurTab->selection[i]->asFileManipulateDialogInfo(); + pUIInfo = pDlgInfo->pEntry; + } + else if (pCurTab->selection[i]->isFileEntryInfo()) + pUIInfo = pCurTab->selection[i]->asFileEntryInfo(); + if (pUIInfo != nullptr) + pThis->onSaveFileRequest(pUIInfo); + } + } + break; + case IDM_FILE_SAVEALL: + { + for (size_t iTab = 0; iTab < pThis->manipDlgTabs.size(); iTab++) + { + if (pThis->manipDlgTabs[iTab].pCurManipDlg) + pThis->manipDlgTabs[iTab].pCurManipDlg->onCommand(wParam, lParam); + } + auto &fileEntryUIList = pThis->getFileEntries(); + for (auto curIt = fileEntryUIList.begin(); curIt != fileEntryUIList.end(); ++curIt) + { + pThis->onSaveFileRequest(&*curIt); + } + } + break; + case IDM_FILE_OPENUABESAVEFILE: + { + Win32ModPackageLoader loader(*pThis->pContext); + loader.open(); + } + break; + case IDM_VIEW_PROGRESS: + if (auto pStatusTracker = dynamic_cast(pThis->pStatusTracker.get())) + pStatusTracker->open(); + break; + case IDM_MODMAKER_CREATESTANDALONE: + case IDM_MODMAKER_CREATEPACKAGE: + { + //First ask the user to apply (or not apply) changes to be visible in the installer data. + bool choseApplyAllChanges = false; + for (size_t iTab = 0; iTab < pThis->manipDlgTabs.size(); iTab++) + { + ManipDlgDesc& desc = pThis->manipDlgTabs[iTab]; + if (desc.pCurManipDlg && desc.pCurManipDlg->hasUnappliedChanges()) + { + if (!choseApplyAllChanges && !askUserApplyChangeBeforeInstaller(hDlg)) + break; + choseApplyAllChanges = true; + desc.pCurManipDlg->applyChanges(); + } + } + Win32ModInstallerEditor editor(*pThis->pContext, pThis->pContext->contextInfo, + (wmId == IDM_MODMAKER_CREATESTANDALONE) ? ModDataSaveType_Installer : ModDataSaveType_PackageFile); + editor.open(); + } + break; + case IDM_TOOLS_EDITTYPEDATABASE: + OpenTypeDatabaseEditor(pThis->hInstance, pThis->hDlg); + break; + case IDM_TOOLS_EDITTYPEPACKAGE: + OpenTypeDbPackageEditor(pThis->hInstance, pThis->hDlg); + break; + case IDM_TOOLS_GETSCRIPTINFORMATION: + { + std::unordered_set addedFileIDs; + std::vector> assetsInfo; + //Add all selected .assets files and their direct dependencies. + //-> If a selected .assets file has a MonoBehavior, + // it may have a reference to another .assets file with the corresponding MonoScript. + if (ManipDlgDesc *pCurTab = pThis->getActiveManipDlgDesc()) + { + for (size_t i = 0; i < pCurTab->selection.size(); i++) + { + if (pCurTab->selection[i] == nullptr) continue; + FileEntryUIInfo *pUIInfo = nullptr; + if (pCurTab->selection[i]->isFileManipulateDialogInfo()) + { + FileManipulateDialogInfo *pDlgInfo = pCurTab->selection[i]->asFileManipulateDialogInfo(); + pUIInfo = pDlgInfo->pEntry; + } + else if (pCurTab->selection[i]->isFileEntryInfo()) + pUIInfo = pCurTab->selection[i]->asFileEntryInfo(); + //Retrieve the context info, and try to convert it to an AssetsFileContextInfo. + auto pFile = std::dynamic_pointer_cast(pUIInfo->getContextInfo()); + //If this is an AssetsFileContextInfo and not inserted yet, proceed. + if (pFile == nullptr || !addedFileIDs.insert(pFile->getFileID()).second) continue; + + const std::vector references = pFile->getReferences(); + for (size_t i = 0; i < references.size(); ++i) + { + //Same as above, but for the direct references. + auto pFile = std::dynamic_pointer_cast(pThis->pContext->getContextInfo(references[i])); + if (pFile == nullptr || !addedFileIDs.insert(pFile->getFileID()).second) continue; + assetsInfo.push_back(std::move(pFile)); + } + assetsInfo.push_back(std::move(pFile)); + } + } + GetAllScriptInformation(*pThis->pContext, assetsInfo); + } + break; + case IDC_CKBUNDLES: + case IDC_CKASSETS: + case IDC_CKRESOURCES: + case IDC_CKGENERICS: + case IDC_CKSELALL: + if (wmEvent == BN_CLICKED) + pThis->onClickSelectionCheckbox(wmId, Button_GetCheck((HWND)lParam)); + break; + case IDM_HELP_ABOUT: + DialogBox(pThis->hInstance, MAKEINTRESOURCE(IDD_ABOUTBOX), hDlg, AboutDlgProc); + break; + default: + //Let the manipulate dialog handle the command first. + //(NOTE: This should be done for any menu item that can be overridden) + if (IFileManipulateDialog *pActiveManipDlg = pThis->getActiveManipDlg()) + ret = (pActiveManipDlg->onCommand(wParam, lParam) ? 1 : 0); + break; + } + } + break; + } + return ret; +} +LRESULT CALLBACK MainWindow2::ProgSubclassProc(HWND hWnd, UINT message, + WPARAM wParam, LPARAM lParam, + uintptr_t uIdSubclass, DWORD_PTR dwRefData) +{ + MainWindow2* pThis = (MainWindow2*)dwRefData; + switch (message) + { + case WM_LBUTTONDBLCLK: + if (auto pStatusTracker = dynamic_cast(pThis->pStatusTracker.get())) + pStatusTracker->open(); + return (LRESULT)0; + } + return DefSubclassProc(hWnd, message, wParam, lParam); +} + +inline void doMoveWindow(HDWP &deferCtx, bool &retry, HWND hWnd, int x, int y, int w, int h) +{ + if (deferCtx) + { + deferCtx = DeferWindowPos(deferCtx, hWnd, HWND_TOP, x, y, w, h, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); + if (!deferCtx) + retry = true; + } + else + SetWindowPos(hWnd, HWND_TOP, x, y, w, h, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); +} +void MainWindow2::doOpenTab() +{ + HWND hTabsControl = GetDlgItem(hDlg, IDC_MANIPDLGTABS); + size_t newTabIdx = this->manipDlgTabs.size(); + this->manipDlgTabs.emplace_back(ManipDlgDesc()); + + MC_MTITEM newItem = {}; + newItem.dwMask = MC_MTIF_TEXT | MC_MTIF_PARAM; + TCHAR sprntTmp[36]; + _stprintf_s(sprntTmp, TEXT("Tab %u"), (unsigned int)this->manipDlgTabs.size()); //TODO: Proper names + newItem.pszText = sprntTmp; + newItem.lParam = (LPARAM)newTabIdx; + SendMessage(hTabsControl, MC_MTM_INSERTITEM, (WPARAM)newTabIdx, (LPARAM)&newItem); + SendMessage(hTabsControl, MC_MTM_SETCURSEL, (WPARAM)newTabIdx, 0); //Also sends a SELCHANGE notification. +} +bool MainWindow2::preDeleteTab(MC_NMMTCLOSEITEM *pNotification) +{ + HWND hTabsControl = pNotification->hdr.hwndFrom; + if (pNotification->iItem >= 0 && pNotification->iItem < this->manipDlgTabs.size()) + { + MC_MTITEM itemInfo = {}; + itemInfo.dwMask = MC_MTIF_PARAM; + //Retrieve the index of the tab in pThis->manipDlgTabs. + SendMessage(hTabsControl, MC_MTM_GETITEM, (WPARAM)pNotification->iItem, (LPARAM)&itemInfo); + size_t internalItemIdx = (size_t)itemInfo.lParam; + assert(internalItemIdx < this->manipDlgTabs.size()); + + if (internalItemIdx < this->manipDlgTabs.size() && this->manipDlgTabs[internalItemIdx].pCurManipDlg != nullptr) + { + IFileManipulateDialog *pDialog = this->manipDlgTabs[internalItemIdx].pCurManipDlg.get(); + bool changesApplyable = false; + if (pDialog->hasUnappliedChanges(&changesApplyable)) + { + SendMessage(hTabsControl, MC_MTM_SETCURSEL, (WPARAM)pNotification->iItem, 0); + if (changesApplyable) + { + switch (MessageBox(this->hDlg, + TEXT("This tab has unsaved changes.\nDo you want to apply the changes before closing the tab?"), + TEXT("Asset Bundle Extractor"), + MB_YESNOCANCEL | MB_ICONWARNING | MB_DEFBUTTON3)) + { + case IDYES: + if (pDialog->applyChanges()) + return true; //Close tab (changes applied). + return false; //Don't close tab (changes not applied). + case IDNO: + return true; //Close tab without saving. + case IDCANCEL: + return false; //Don't close tab. + } + } + else if (IDYES == MessageBox(this->hDlg, + TEXT("This tab has unsaved changes.\nDo you want to close the tab anyway and discard any unsaved changes?"), + TEXT("Asset Bundle Extractor"), + MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2)) + { + return true; + } + return false; + } + } + } + else + assert(false); + return true; +} +void MainWindow2::onDeleteTab(MC_NMMTDELETEITEM *pNotification) +{ + HWND hTabsControl = pNotification->hdr.hwndFrom; + if (pNotification->iItem >= 0 && pNotification->iItem < this->manipDlgTabs.size()) + { + MC_MTITEM itemInfo = {}; + itemInfo.dwMask = MC_MTIF_PARAM; + //Retrieve the index of the tab in pThis->manipDlgTabs. + SendMessage(hTabsControl, MC_MTM_GETITEM, (WPARAM)pNotification->iItem, (LPARAM)&itemInfo); + size_t internalItemIdx = (size_t)itemInfo.lParam; + assert(internalItemIdx < this->manipDlgTabs.size()); + //Fix the pThis->manipDlgTabs indices in the tab control user data to account for pThis->manipDlgTabs.erase(...). + for (size_t i = 0; i < this->manipDlgTabs.size(); i++) + { + if (i == pNotification->iItem) + continue; + itemInfo.dwMask = MC_MTIF_PARAM; + if (SendMessage(hTabsControl, MC_MTM_GETITEM, (WPARAM)i, (LPARAM)&itemInfo) + && ((size_t)itemInfo.lParam) > internalItemIdx) + { + itemInfo.dwMask = MC_MTIF_PARAM; + itemInfo.lParam--; + SendMessage(hTabsControl, MC_MTM_SETITEM, (WPARAM)i, (LPARAM)&itemInfo); + } + } + if (internalItemIdx == this->activeManipDlgTab) + { + //If there are at least two tabs, another tab should be selected by mCtrl at this point. + assert(this->manipDlgTabs.size() == 1); + if (this->activeManipDlgTab == internalItemIdx + && this->manipDlgTabs[internalItemIdx].pCurManipDlg != nullptr) + { + this->manipDlgTabs[internalItemIdx].pCurManipDlg->onHide(); //Hide the tab if necessary. + InvalidateRect(hDlg, NULL, TRUE); //TODO: Redraw only the manipulate dialog area. + } + } + this->manipDlgTabs.erase(this->manipDlgTabs.begin() + internalItemIdx); + if (this->activeManipDlgTab > internalItemIdx) + { + this->activeManipDlgTab--; + //The active tab must still be in range if there is at least one item. + assert(this->activeManipDlgTab >= 0); + assert(this->manipDlgTabs.empty() || this->activeManipDlgTab < this->manipDlgTabs.size()); + } + } + else + assert(false); +} +void MainWindow2::onSwitchTabs(MC_NMMTSELCHANGE *pNotification) +{ + //Handle tab selection change. + HWND hTree = GetDlgItem(hDlg, IDC_TREEFILES); + HWND hTabsControl = pNotification->hdr.hwndFrom; + assert(pNotification->iItemNew != pNotification->iItemOld); + bool redraw = false; + //Deselect the old tab. + if (pNotification->iItemOld >= 0 && pNotification->iItemOld < this->manipDlgTabs.size()) + { + MC_MTITEM itemInfo = {}; + itemInfo.dwMask = MC_MTIF_PARAM; + //Retrieve the index of the tab in pThis->manipDlgTabs. + SendMessage(hTabsControl, MC_MTM_GETITEM, (WPARAM)pNotification->iItemOld, (LPARAM)&itemInfo); + size_t internalOldItemIdx = (size_t)itemInfo.lParam; + assert(internalOldItemIdx < this->manipDlgTabs.size()); + + ManipDlgDesc *pOldTabDesc = &this->manipDlgTabs[internalOldItemIdx]; //this->getActiveManipDlgDesc(); + if (pOldTabDesc) + { + if (pOldTabDesc->pCurManipDlg) + { + pOldTabDesc->pCurManipDlg->onHide(); + InvalidateRect(hDlg, NULL, TRUE); //TODO: Redraw only the manipulate dialog area. + redraw = true; + } + if (!this->skipDeselectOnTabChange) + { + assert(internalOldItemIdx == this->activeManipDlgTab); + //Undo the file selections in the tree list. + this->ignoreTreeSelChanges = true; + for (size_t i = 0; i < pOldTabDesc->selection.size(); i++) + { + MC_HTREELISTITEM hTreeItem; + if (getEntryParam_TreeItem(pOldTabDesc->selection[i], hTreeItem)) + setSelectItem(hTree, hTreeItem, false); + } + this->ignoreTreeSelChanges = false; + this->activeManipDlgTab = SIZE_MAX; //Deselect + std::vector emptySelection; + this->doUpdateSelectionCheckboxes(emptySelection); + } + } + } + //Select the new tab. + if (pNotification->iItemNew >= 0 && pNotification->iItemNew < this->manipDlgTabs.size()) + { + MC_MTITEM itemInfo = {}; + itemInfo.dwMask = MC_MTIF_PARAM; + //Retrieve the index of the tab in pThis->manipDlgTabs. + SendMessage(hTabsControl, MC_MTM_GETITEM, (WPARAM)pNotification->iItemNew, (LPARAM)&itemInfo); + size_t internalNewItemIdx = (size_t)itemInfo.lParam; + assert(internalNewItemIdx < this->manipDlgTabs.size()); + + this->activeManipDlgTab = internalNewItemIdx; //Select + ManipDlgDesc *pNewTabDesc = this->getActiveManipDlgDesc(); + if (!this->skipDeselectOnTabChange) + { + //Redo the file selections in the tree list. + this->ignoreTreeSelChanges = true; + for (size_t i = 0; i < pNewTabDesc->selection.size(); i++) + { + MC_HTREELISTITEM hTreeItem; + if (getEntryParam_TreeItem(pNewTabDesc->selection[i], hTreeItem)) + setSelectItem(hTree, hTreeItem, true); + } + this->ignoreTreeSelChanges = false; + } + if (pNewTabDesc->pCurManipDlg) + { + pNewTabDesc->pCurManipDlg->onShow(); + this->onResize(); + redraw = true; + } + if (!this->skipDeselectOnTabChange) + this->doUpdateSelectionCheckboxes(pNewTabDesc->selection); + } + if (redraw) + InvalidateRect(hDlg, NULL, TRUE); //TODO: Redraw only the manipulate dialog area. +} +void MainWindow2::onResize(bool defer) +{ + RECT client = {}; + GetClientRect(hDlg, &client); + long width = client.right-client.left; + long height = client.bottom-client.top; + + HDWP deferCtx = defer ? BeginDeferWindowPos(11) : NULL; + bool retry = false; + + long fontHeight = 16; + long bottomPanelHeight = 25; + long bottomPanelTop = height - bottomPanelHeight; + long bottomLeftPanelLeft = 7; + long bottomLeftPanelWidth = std::min((width - bottomLeftPanelLeft) / 3, 125); + long bottomRightPanelLeft = bottomLeftPanelWidth + 7; + long bottomRightPanelWidth = width - bottomRightPanelLeft - 7; + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_PROGSEPARATE), -2, bottomPanelTop, 2 + bottomLeftPanelLeft + bottomLeftPanelWidth, bottomPanelHeight); + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_PROGMAIN), bottomLeftPanelLeft, bottomPanelTop + 3, bottomLeftPanelWidth - 7, bottomPanelHeight - 6); + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_PROGSEPARATE2), bottomRightPanelLeft, bottomPanelTop, (width + 2) - bottomRightPanelLeft, bottomPanelHeight); + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_SPROGDESC), bottomRightPanelLeft + 3, bottomPanelTop + 4, bottomRightPanelWidth - 3, fontHeight); + + long leftPanelTop = 4; + long leftPanelLeft = 7; + long leftPanelWidth = (long)(width * this->mainPanelSplitter.getLeftOrTopPanelRatio() - leftPanelLeft); + long leftPanelClientWidth = leftPanelWidth - 5; + long panelHeight = height - bottomPanelHeight; + { + long curCheckboxLeft = leftPanelLeft; + static const long checkboxHeight = 16; + static const long ckBundlesWidth = 60; + static const long ckAssetsWidth = 60; + static const long ckResourcesWidth = 76; + static const long ckGenericsWidth = 60; + + static const long ckSelAllWidth = 60; + + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_CKBUNDLES), curCheckboxLeft, leftPanelTop, std::min(ckBundlesWidth, leftPanelClientWidth - curCheckboxLeft), checkboxHeight); + curCheckboxLeft += ckBundlesWidth; + if ((curCheckboxLeft + ckAssetsWidth) > leftPanelClientWidth && curCheckboxLeft > leftPanelLeft) + { + leftPanelTop += checkboxHeight + 4; + curCheckboxLeft = leftPanelLeft; + } + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_CKASSETS), curCheckboxLeft, leftPanelTop, std::min(ckAssetsWidth, leftPanelClientWidth - curCheckboxLeft), checkboxHeight); + curCheckboxLeft += ckAssetsWidth; + if ((curCheckboxLeft + ckResourcesWidth) > leftPanelClientWidth && curCheckboxLeft > leftPanelLeft) + { + leftPanelTop += checkboxHeight + 4; + curCheckboxLeft = leftPanelLeft; + } + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_CKRESOURCES), curCheckboxLeft, leftPanelTop, std::min(ckResourcesWidth, leftPanelClientWidth - curCheckboxLeft), checkboxHeight); + curCheckboxLeft += ckResourcesWidth; + if ((curCheckboxLeft + ckGenericsWidth) > leftPanelClientWidth && curCheckboxLeft > leftPanelLeft) + { + leftPanelTop += checkboxHeight + 4; + curCheckboxLeft = leftPanelLeft; + } + + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_CKGENERICS), curCheckboxLeft, leftPanelTop, std::min(ckGenericsWidth, leftPanelClientWidth - curCheckboxLeft), checkboxHeight); + leftPanelTop += checkboxHeight + 4; + curCheckboxLeft = leftPanelLeft; + + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_CKSELALL), curCheckboxLeft, leftPanelTop, std::min(ckSelAllWidth, leftPanelClientWidth - curCheckboxLeft), checkboxHeight); + leftPanelTop += checkboxHeight + 4; + } + + HWND hTree = GetDlgItem(hDlg, IDC_TREEFILES); + { + //Calculate relative column sizes for the file tree list. + MC_TLCOLUMN col; + col.fMask = MC_TLCF_WIDTH; + col.cx = 1; + SendMessage(hTree, MC_TLM_GETCOLUMN, 0, (LPARAM)&col); + int cxTree = col.cx; + col.fMask = MC_TLCF_WIDTH; + col.cx = 1; + SendMessage(hTree, MC_TLM_GETCOLUMN, 1, (LPARAM)&col); + int cxFileID = col.cx; + RECT treeClientRect = {}; + GetClientRect(hTree, &treeClientRect); + LONG actualWidth = treeClientRect.right - treeClientRect.left - 1; + int totalWidth = cxTree + cxFileID; + if (cxTree > 0 && cxFileID > 0 && actualWidth > 0) + { + cxTree = (int)(cxTree * ((float)totalWidth / (float)actualWidth)); + cxFileID = (int)(cxFileID * ((float)totalWidth / (float)actualWidth)); + this->fileTreeColumnRatio = (float)cxTree / (float)actualWidth; + if (this->fileTreeColumnRatio < 0.75f) this->fileTreeColumnRatio = 0.75f; + else if (this->fileTreeColumnRatio > 0.95f) this->fileTreeColumnRatio = 0.95f; + } + } + long leftPanelHeight = (bottomPanelTop - 4) - leftPanelTop; + doMoveWindow(deferCtx, retry, hTree, leftPanelLeft, leftPanelTop, leftPanelClientWidth, leftPanelHeight); + + long contentPanelLeft = leftPanelLeft + leftPanelWidth, contentPanelTop = 10; + long contentPanelWidth = width - contentPanelLeft; + long contentPanelHeight = panelHeight - 20; + doMoveWindow(deferCtx, retry, GetDlgItem(hDlg, IDC_CONTENTSEPARATE), contentPanelLeft, -2, contentPanelWidth + 2, panelHeight + 2); + + HWND hTabsControl = GetDlgItem(hDlg, IDC_MANIPDLGTABS); + long tabsControlBottom = std::min(24, panelHeight); + doMoveWindow(deferCtx, retry, hTabsControl, contentPanelLeft + 1, 0, contentPanelWidth, tabsControlBottom - 0); + + if (defer) + { + if (retry || !EndDeferWindowPos(deferCtx)) + onResize(false); + deferCtx = NULL; + } + //For some reason, using the deferred method for the child dialog silently makes no window resize. + if (IFileManipulateDialog *pCurManipDlg = this->getActiveManipDlg()) + { + //GetWindowRect(hTabsControl, &tabsRect); + RECT tabsRect = {}; + tabsRect.left = 0; tabsRect.right = contentPanelWidth; + tabsRect.top = tabsControlBottom + 1; tabsRect.bottom = panelHeight; + //TabCtrl_AdjustRect(hTabsControl, FALSE, &tabsRect); + MoveWindow(pCurManipDlg->getWindowHandle(), + tabsRect.left, tabsRect.top, + tabsRect.right-tabsRect.left, tabsRect.bottom-tabsRect.top, TRUE); + } + + UpdateWindow(hDlg); + //InvalidateRect(GetDlgItem(hDlg, IDC_PROGMAIN), NULL, TRUE); + //InvalidateRect(GetDlgItem(hDlg, IDC_SPROGDESC), NULL, TRUE); + //InvalidateRect(GetDlgItem(hDlg, IDC_TREEFILES), NULL, TRUE); + //InvalidateRect(hDlg, NULL, TRUE); + + //Resize the file tree control's main column. + RECT treeClientRect = {}; + GetClientRect(hTree, &treeClientRect); + MC_TLCOLUMN col; + col.fMask = MC_TLCF_WIDTH; + col.cx = (int)((treeClientRect.right - treeClientRect.left - 1) * fileTreeColumnRatio); + SendMessage(hTree, MC_TLM_SETCOLUMN, 0, (LPARAM)&col); + col.cx = (int)((treeClientRect.right - treeClientRect.left - 1) * (1.0 - fileTreeColumnRatio)); + SendMessage(hTree, MC_TLM_SETCOLUMN, 1, (LPARAM)&col); +} +inline bool manipDlgIsCompatibleWith(IFileManipulateDialog *pDialog, ITreeParameter *newItem) +{ + bool tmp; + FileEntryUIInfo *pNewFileEntryInfo = getEntryParam_FileEntryInfo(newItem, tmp); + return (pNewFileEntryInfo + && newItem->isFileManipulateDialogInfo() + && newItem->asFileManipulateDialogInfo()->type == pDialog->getType()); +} + +inline bool getSelectionType(ITreeParameter *pSel, EFileContextType &type, bool onlyPrimaryDialogOrFileEntry) +{ + type = FileContext_COUNT; + bool tmp; + if (FileEntryUIInfo *pSelFileEntryInfo = getEntryParam_FileEntryInfo(pSel, tmp)) + { + if (!pSelFileEntryInfo->pending && pSelFileEntryInfo->pContextInfo + && pSelFileEntryInfo->pContextInfo->getFileContext() + && (!onlyPrimaryDialogOrFileEntry || + pSelFileEntryInfo->standardDialogsCount <= 0 || + &pSelFileEntryInfo->standardDialogs[0] == pSel)) + { + type = pSelFileEntryInfo->pContextInfo->getFileContext()->getType(); + if (type < (EFileContextType)0 || type >= FileContext_COUNT) + return false; + return true; + } + } + return false; +} +void MainWindow2::onClickSelectionCheckbox(unsigned int checkboxID, int checkState) +{ + HWND hTree = GetDlgItem(this->hDlg, IDC_TREEFILES); + + EFileContextType fileContextType; + switch (checkboxID) + { + case IDC_CKBUNDLES: + fileContextType = FileContext_Bundle; + break; + case IDC_CKASSETS: + fileContextType = FileContext_Assets; + break; + case IDC_CKRESOURCES: + fileContextType = FileContext_Resources; + break; + case IDC_CKGENERICS: + fileContextType = FileContext_Generic; + break; + case IDC_CKSELALL: + { + this->ignoreTreeSelChanges = true; + if (checkState == BST_CHECKED) + { + for (auto fileIt = fileEntries.begin(); fileIt != fileEntries.end(); ++fileIt) + { + if (fileIt->hTreeItem) + setSelectItem(hTree, fileIt->hTreeItem, true); + } + } + else + { + MC_HTREELISTITEM selection = NULL; + while ((selection = MCTreeList_GetNextSelection(hTree, selection)) != NULL) + { + setSelectItem(hTree, selection, false); + } + } + this->ignoreTreeSelChanges = false; + this->onChangeFileSelection(); + } + return; + default: + return; + } + bool selectionUpdated = false; + switch (checkState) + { + case BST_INDETERMINATE: + Button_SetCheck(GetDlgItem(hDlg, checkboxID), BST_UNCHECKED); + //Fall through to the unchecked case. + case BST_UNCHECKED: + if (ManipDlgDesc *pTab = getActiveManipDlgDesc()) + { + this->ignoreTreeSelChanges = true; + for (size_t i = 0; i < pTab->selection.size(); i++) + { + EFileContextType type; + if (getSelectionType(pTab->selection[i], type, false) && type == fileContextType) + { + bool tmp; + if (FileEntryUIInfo *pSelFileEntryInfo = getEntryParam_FileEntryInfo(pTab->selection[i], tmp)) + { + selectionUpdated = true; + setSelectItem(hTree, pSelFileEntryInfo->hTreeItem, false); + } + } + } + this->ignoreTreeSelChanges = false; + } + break; + case BST_CHECKED: + { + this->ignoreTreeSelChanges = true; + for (auto fileIt = fileEntries.begin(); fileIt != fileEntries.end(); ++fileIt) + { + EFileContextType type; + if (fileIt->hTreeItem && getSelectionType(&*fileIt, type, false) && type == fileContextType) + { + selectionUpdated = true; + setSelectItem(hTree, fileIt->hTreeItem, true); + } + } + this->ignoreTreeSelChanges = false; + } + break; + } + if (selectionUpdated) + this->onChangeFileSelection(); +} +void MainWindow2::doUpdateSelectionCheckboxes(const std::vector &selections) +{ + std::array selectionCountersByType = {}; + for (size_t i = 0; i < selections.size(); i++) + { + EFileContextType type; + if (getSelectionType(selections[i], type, true)) + selectionCountersByType[type]++; + } + + static const int entryTypeDlgItems[] = {IDC_CKBUNDLES, IDC_CKASSETS, IDC_CKRESOURCES, IDC_CKGENERICS}; + assert(fileEntryCountersByType.size() == selectionCountersByType.size()); + assert(fileEntryCountersByType.size() == sizeof(entryTypeDlgItems) / sizeof(int)); + size_t selectionSum = 0; + for (size_t i = 0; i < fileEntryCountersByType.size() + && i < selectionCountersByType.size() + && i < sizeof(entryTypeDlgItems) / sizeof(int); i++) + { + HWND hCheckbox = GetDlgItem(this->hDlg, entryTypeDlgItems[i]); + selectionSum += selectionCountersByType[i]; + if (selectionCountersByType[i] > 0) + { + assert(selectionCountersByType[i] <= fileEntryCountersByType[i]); + if (selectionCountersByType[i] >= fileEntryCountersByType[i]) + Button_SetCheck(hCheckbox, BST_CHECKED); + else + Button_SetCheck(hCheckbox, BST_INDETERMINATE); + } + else + { + Button_SetCheck(hCheckbox, BST_UNCHECKED); + } + } + HWND hSelAllCheckbox = GetDlgItem(this->hDlg, IDC_CKSELALL); + if (selectionSum > 0) + { + assert(selectionSum <= fileEntries.size()); + if (selectionSum >= fileEntries.size()) + Button_SetCheck(hSelAllCheckbox, BST_CHECKED); + else + Button_SetCheck(hSelAllCheckbox, BST_UNCHECKED); + } + else + Button_SetCheck(hSelAllCheckbox, BST_UNCHECKED); +} +void MainWindow2::selectFileContext(unsigned int fileID, bool preventOpenNewTab) +{ + HWND hTree = GetDlgItem(hDlg, IDC_TREEFILES); + FileContextInfo_ptr pContextInfo = this->pContext->getContextInfo(fileID); + if (pContextInfo == nullptr) + return; + auto fileUIEntryIt = this->fileEntriesByContextInfo.find(pContextInfo.get()); + if (fileUIEntryIt == this->fileEntriesByContextInfo.end()) + return; + ManipDlgDesc *pCurTabDesc = this->getActiveManipDlgDesc(); + FileManipulateDialogInfo *pDialogInfo = nullptr; + for (auto iter = fileUIEntryIt->second->getDialogsIterator(); !iter.end(); ++iter) + { + if (pCurTabDesc == nullptr || pCurTabDesc->pCurManipDlg == nullptr || + iter->type == pCurTabDesc->pCurManipDlg->getType()) + { + pDialogInfo = &*iter; + break; + } + } + if (pDialogInfo != nullptr && pDialogInfo->hTreeItem != NULL) + { + MC_TLITEM item; + item.fMask = MC_TLIF_STATE; + item.state = MC_TLIS_SELECTED; + item.stateMask = MC_TLIS_SELECTED; + this->oneshot_applySelectionToCurrentTab = preventOpenNewTab; + SendMessage(hTree, MC_TLM_SETITEM, reinterpret_cast(pDialogInfo->hTreeItem), reinterpret_cast(&item)); + this->oneshot_applySelectionToCurrentTab = false; + } +} +bool MainWindow2::loadBundleEntry(std::shared_ptr pBundleInfo, unsigned int bundleEntryIdx) +{ + HWND hTree = GetDlgItem(hDlg, IDC_TREEFILES); + auto fileUIEntryIt = this->fileEntriesByContextInfo.find(pBundleInfo.get()); + if (fileUIEntryIt == this->fileEntriesByContextInfo.end()) + return false; + std::shared_ptr pTask = pContext->CreateBundleEntryOpenTask(pBundleInfo, bundleEntryIdx); + if (!pTask) + { + if (!pBundleInfo->entryIsRemoved(bundleEntryIdx)) + MessageBox(this->hDlg, TEXT("Failed to read the bundle entry."), TEXT("UABE"), 16); + return false; + } + char entryNameBuf[32]; + std::string entryName = pBundleInfo->getNewEntryName(bundleEntryIdx); + if (entryName.empty()) + { + sprintf_s(entryNameBuf, "Entry %u", bundleEntryIdx); + entryName = entryNameBuf; + } + MC_HTREELISTITEM treeItem = insertPendingEntry(hTree, fileUIEntryIt->second->hTreeItem, entryName); + + fileEntries.emplace_back(FileEntryUIInfo(treeItem, entryName, false)); + fileEntries.back().myiter = --fileEntries.end(); + + updateEntryInfoRef(hTree, treeItem, fileEntries.back()); + updateEntryName(hTree, treeItem, std::string("Pending : ") + entryName); + pendingFileEntriesByTask.insert(std::make_pair(pTask.get(), &fileEntries.back())); + + if (!this->pContext->taskManager.enqueue(pTask)) + { + OnFileEntryLoadFailure(pTask.get(), std::string("Failed to enqueue the file open task.")); + return false; + } + return true; +} +void MainWindow2::onChangeFileSelection() +{ + HWND hTabsControl = GetDlgItem(hDlg, IDC_MANIPDLGTABS); + ManipDlgDesc *pCurTabDesc = this->getActiveManipDlgDesc(); + if (!pCurTabDesc && this->manipDlgTabs.size() > 0) + { + //Choose the last tab. + MC_MTITEM itemInfo = {}; + itemInfo.dwMask = MC_MTIF_PARAM; + //Retrieve the index of the tab in pThis->manipDlgTabs. + SendMessage(hTabsControl, MC_MTM_GETITEM, (WPARAM)(this->manipDlgTabs.size() - 1), (LPARAM)&itemInfo); + size_t internalItemIdx = (size_t)itemInfo.lParam; + assert(internalItemIdx < this->manipDlgTabs.size()); + if (internalItemIdx < this->manipDlgTabs.size()) + { + //Select the tab. + SendMessage(hTabsControl, MC_MTM_SETCURSEL, (WPARAM)(this->manipDlgTabs.size() - 1), (LPARAM)0); //wParam:idx + this->activeManipDlgTab = internalItemIdx; + pCurTabDesc = &this->manipDlgTabs[internalItemIdx]; + } + } + //May want to fine-tune the 'auto-close tab or don't' / 'auto-open new tab' behavior. + //-> What if the user selects just one additional file, while the tab returns true on hasUnappliedChanges or doesPreferNoAutoclose? + // Problem could be that new tabs are opened too easily when not desired. + // Could be quite cumbersome and error-prone to handle in detail. + if (!pCurTabDesc + || (pCurTabDesc->pCurManipDlg + && (pCurTabDesc->pCurManipDlg->hasUnappliedChanges() || pCurTabDesc->pCurManipDlg->doesPreferNoAutoclose())) + && !this->oneshot_applySelectionToCurrentTab) + { + size_t newTabIdx = this->manipDlgTabs.size(); + //Note: Manipulating manipDlgTabs invalidates pCurTabDesc. + this->manipDlgTabs.emplace_back(ManipDlgDesc()); + this->activeManipDlgTab = newTabIdx; + pCurTabDesc = &this->manipDlgTabs[newTabIdx]; + + this->skipDeselectOnTabChange = true; + + MC_MTITEM newItem = {}; + newItem.dwMask = MC_MTIF_TEXT | MC_MTIF_PARAM; + TCHAR sprntTmp[36]; + _stprintf_s(sprntTmp, TEXT("Tab %u"), (unsigned int)(newTabIdx + 1)); //TODO: Proper names + newItem.pszText = sprntTmp; + newItem.lParam = (LPARAM)newTabIdx; + SendMessage(hTabsControl, MC_MTM_INSERTITEM, (WPARAM)newTabIdx, (LPARAM)&newItem); + SendMessage(hTabsControl, MC_MTM_SETCURSEL, (WPARAM)newTabIdx, (LPARAM)0); //wParam:idx + + this->skipDeselectOnTabChange = false; + } + + HWND hTree = GetDlgItem(hDlg, IDC_TREEFILES); + std::vector newSelection; + MC_HTREELISTITEM selection = NULL; + while ((selection = MCTreeList_GetNextSelection(hTree, selection)) != NULL) + { + ITreeParameter *pCurParam = getEntryParam(hTree, selection); + newSelection.push_back(pCurParam); + } + bool selectionIsSaveable = false; + for (size_t i = 0; i < newSelection.size(); ++i) + { + bool tmp; + FileEntryUIInfo *pNewFileEntryInfo = getEntryParam_FileEntryInfo(newSelection[i], tmp); + if (pNewFileEntryInfo != nullptr + && pNewFileEntryInfo->getContextInfoPtr() != nullptr + && pNewFileEntryInfo->getContextInfoPtr()->getParentFileID() == 0 + && pNewFileEntryInfo->getContextInfoPtr()->hasAnyChanges(*this->pContext)) + { + selectionIsSaveable = true; + break; + } + } + EnableMenuItem(getMenu(), IDM_FILE_SAVE, selectionIsSaveable ? MF_ENABLED : MF_GRAYED); + doUpdateSelectionCheckboxes(newSelection); + if (pCurTabDesc->pCurManipDlg) + { + //Find the FileEntryUIInfo entries that were selected and those that were deselected. + //-> Add/remove these from the active manipulate dialog. + //Assuming the selections are both sorted by the tree view order. + + //Count the amount of selected dialog info entries for the current dialog. + size_t nShownSelections = 0; + + auto addFileContextsToDialog = [&newSelection, pCurTabDesc](size_t start, size_t limit, FileEntryUIInfo* pCurFileEntryInfo = nullptr) + { + size_t n = 0; + FileEntryUIInfo* pLastAddedFileEntry = nullptr; bool tmp; + for (size_t i = start; i < limit; i++) + { + ITreeParameter* newParam = newSelection[i]; + FileEntryUIInfo* pNewFileEntryInfo = getEntryParam_FileEntryInfo(newParam, tmp); + if (pNewFileEntryInfo + && pNewFileEntryInfo != pCurFileEntryInfo + && pNewFileEntryInfo != pLastAddedFileEntry + && newParam->isFileManipulateDialogInfo() + && newParam->asFileManipulateDialogInfo()->type == pCurTabDesc->pCurManipDlg->getType()) + { + //Add a new file context to this dialog. + FileManipulateDialogInfo* pNewDialogInfo = newParam->asFileManipulateDialogInfo(); + pCurTabDesc->pCurManipDlg->addFileContext( + std::make_pair(pNewFileEntryInfo, pNewDialogInfo->param)); + n++; + pLastAddedFileEntry = pNewFileEntryInfo; + } + } + return n; + }; + + size_t curIdx = pCurTabDesc->selection.size(), newIdx = newSelection.size(); + bool tmp; + FileEntryUIInfo *pLastFileEntryInfo = nullptr; + for (; curIdx > 0; curIdx--) + { + ITreeParameter *curParam = pCurTabDesc->selection[curIdx-1]; + FileEntryUIInfo *pCurFileEntryInfo = getEntryParam_FileEntryInfo(curParam, tmp); + if (!pCurFileEntryInfo || pCurFileEntryInfo == pLastFileEntryInfo + || !curParam->isFileManipulateDialogInfo() + || curParam->asFileManipulateDialogInfo()->type != pCurTabDesc->pCurManipDlg->getType()) + //Ignore entries that are removed/handled already and those with a different dialog type. + continue; + size_t addLimit = newIdx; + bool fileEntryInfoFound = false; + for (; newIdx > 0; newIdx--) + { + ITreeParameter *newParam = newSelection[newIdx-1]; + if (newParam == curParam) + { + //Found the same tree parameter (i.e. a tree view entry). + fileEntryInfoFound = true; + } + else if (FileEntryUIInfo *pNewFileEntryInfo = getEntryParam_FileEntryInfo(newParam, tmp)) + { + if (pNewFileEntryInfo->pending) + { + //Pending selected items can only be added later, once loaded. + //Set the selection entry to nullptr so it will not be treated as added. + newSelection[newIdx-1] = nullptr; + continue; + } + if (pNewFileEntryInfo == pLastFileEntryInfo) + { + if (fileEntryInfoFound) + break; //Went past the last item with the same file. + //Skip this tree entry in the new selections list, + // since one entry with this file was handled in the last iteration of the outer loop. + continue; + } + if (pCurFileEntryInfo && pNewFileEntryInfo == pCurFileEntryInfo) + { + if (newParam->isFileManipulateDialogInfo() + && newParam->asFileManipulateDialogInfo()->type == curParam->asFileManipulateDialogInfo()->type) + { + //New tree parameter is a dialog structure of the same type that refers to the same file. + fileEntryInfoFound = true; + } + } + else if (fileEntryInfoFound) + break; //Went past the last item with the same file. + } + } + if (!fileEntryInfoFound) + { + //Do not add any new elements (yet), since we can't tell which elements are new : + // the old selection is no proper anchor to determine what is new since it has been deleted. + newIdx = addLimit; + } + nShownSelections += addFileContextsToDialog(newIdx, addLimit, pCurFileEntryInfo); + if (!fileEntryInfoFound) + { + //Remove the file context from the dialog since no matching tree element is selected anymore. + pCurTabDesc->pCurManipDlg->removeFileContext(pCurFileEntryInfo); + //The dialog may have closed itself after removing a file context. + if (!pCurTabDesc->pCurManipDlg) + break; + } + else + nShownSelections++; //This entry still is shown. + pLastFileEntryInfo = pCurFileEntryInfo; + } + FileEntryUIInfo *pLastAddedFileEntry = nullptr; + if (pCurTabDesc->pCurManipDlg) + nShownSelections += addFileContextsToDialog(0, newIdx, pLastFileEntryInfo); + if (nShownSelections == 0) + { + //If none of the new selections apply to the current dialog, close the dialog. + if (pCurTabDesc->pCurManipDlg) + { + pCurTabDesc->pCurManipDlg->onHide(); + pCurTabDesc->pCurManipDlg.reset(); + InvalidateRect(hDlg, NULL, TRUE); //TODO: Redraw only the manipulate dialog area. + } + } + else + { + //If at least one file is shown in the dialog, there should be a matching selection. + assert(newSelection.size() > 0); + } + } + + if (!pCurTabDesc->pCurManipDlg) + { + if (newSelection.size() > 0) + { + std::set possibleDialogTypes; + for (size_t i = 0; i < newSelection.size(); i++) + { + ITreeParameter *curParam = newSelection[i]; + if (!curParam) continue; + if (FileManipulateDialogInfo *pDialogInfo = curParam->asFileManipulateDialogInfo()) + { + if (pDialogInfo->type != FileManipulateDialog_Other) + possibleDialogTypes.insert(pDialogInfo->type); + } + } + if (!possibleDialogTypes.empty()) + { + //TODO : Handle the case where several dialog types are possible for the selection. + //Option A : Display a notification dialog so the user selects only the entries they want. + //Option B : Display a selection for a dialog type to use. + EFileManipulateDialogType targetDialogType = *(possibleDialogTypes.begin()); + FileEntryUIInfo *pLastAddedFileEntry = nullptr; + for (size_t i = 0; i < newSelection.size(); i++) + { + ITreeParameter *curParam = newSelection[i]; + bool tmp; + FileEntryUIInfo *pCurFileEntry = getEntryParam_FileEntryInfo(curParam, tmp); + if (!pCurFileEntry || pCurFileEntry == pLastAddedFileEntry) + continue; //Only add one dialog info per file. + if (FileManipulateDialogInfo *pDialogInfo = curParam->asFileManipulateDialogInfo()) + { + if (pDialogInfo->type == targetDialogType) + { + if (!pCurTabDesc->pCurManipDlg) + { + //Create the dialog type if necessary. + //TODO: Move the factory to the MainWindow2 class instead. + if (!(pCurTabDesc->pCurManipDlg = this->pDialogFactory->construct( + pDialogInfo->type, GetDlgItem(hDlg, IDC_CONTENTSEPARATE))))//hTabsControl)))) + continue; + } + pCurTabDesc->pCurManipDlg->selfPtr = pCurTabDesc->pCurManipDlg; + pCurTabDesc->pCurManipDlg->addFileContext(std::make_pair(pCurFileEntry, pDialogInfo->param)); + } + } + pLastAddedFileEntry = pCurFileEntry; + } + } + } + pCurTabDesc->selection.swap(newSelection); + if (pCurTabDesc->pCurManipDlg) + { + //Show the new dialog and size it properly. + pCurTabDesc->pCurManipDlg->onShow(); + this->onResize(); + SetFocus(hTree); + } + } + else + { + pCurTabDesc->selection.swap(newSelection); + } +} +void MainWindow2::addPendingBaseFileEntry(ITask *pTask, const std::string &path) +{ + HWND hTree = GetDlgItem(this->hDlg, IDC_TREEFILES); + MC_HTREELISTITEM treeItem = insertPendingEntry(hTree, NULL, path); + + fileEntries.emplace_back(FileEntryUIInfo(treeItem, path, true)); + fileEntries.back().myiter = --fileEntries.end(); + + updateEntryInfoRef(hTree, treeItem, fileEntries.back()); + updateEntryName(hTree, treeItem, std::string("Pending : ") + fileEntries.back().getShortName()); + pendingFileEntriesByTask.insert(std::make_pair(pTask, &fileEntries.back())); +} +void MainWindow2::onOpenFileCommand() +{ + //assert(this->pContext); + std::vector filePaths; + HRESULT hr = ShowFileOpenDialogMultiSelect(this->hDlg, filePaths, + "*.*|All types:*.unity3d|Bundle file:*.assets|Assets file", NULL, NULL, + "Select the files to open", + UABE_FILEDIALOG_FILE_GUID); + if (SUCCEEDED(hr)) + { + HWND hTree = GetDlgItem(this->hDlg, IDC_TREEFILES); + for (size_t i = 0; i < filePaths.size(); i++) + { + std::string pathString(filePaths[i]); + std::shared_ptr pTask = this->pContext->CreateFileOpenTask(pathString); + if (!pTask) + MessageBox(this->hDlg, TEXT("Failed to open the file."), TEXT("UABE"), 16); + else + { + addPendingBaseFileEntry(pTask.get(), pathString); + + if (!this->pContext->taskManager.enqueue(pTask)) + OnFileEntryLoadFailure(pTask.get(), std::string("Failed to enqueue the file open task.")); + } + } + FreeFilePathsMultiSelect(filePaths); + } +} +void MainWindow2::OnFindClassDatabaseFailure(AssetsFileContextInfo *pAssetsFileInfo, ClassDatabasePackage &package) +{ + if (pAssetsFileInfo->getAssetsFileContext() && pAssetsFileInfo->getAssetsFileContext()->getAssetsFile()) + { + if (!TryFindClassDatabase(pAssetsFileInfo)) + { + auto entryIt = fileEntriesByContextInfo.find(pAssetsFileInfo); + if (entryIt != fileEntriesByContextInfo.end()) + { + fileEntriesPendingForDbSelection.push_back(DbSelectionQueueEntry(entryIt->second, true)); + if (pSelectClassDbDialog == nullptr) + { + OpenClassDatabaseSelection(pAssetsFileInfo, true); + } + } + } + } +} +bool MainWindow2::TryFindClassDatabase(AssetsFileContextInfo *pAssetsFileInfo) +{ + const char *targetVersion = pAssetsFileInfo->getAssetsFileContext()->getAssetsFile()->typeTree.unityVersion; + for (auto it = databaseFilesByEngineVersion.begin(); it != databaseFilesByEngineVersion.end(); ++it) + { + if (!it->first.compare(targetVersion)) + { + pAssetsFileInfo->SetClassDatabase(it->second); + return true; + } + } + if (defaultDatabaseFile != nullptr) + { + pAssetsFileInfo->SetClassDatabase(defaultDatabaseFile); + return true; + } + return false; +} +void MainWindow2::OpenClassDatabaseSelection(AssetsFileContextInfo *pAssetsFileInfo, bool reason_DatabaseNotFound) +{ + const char *targetVersion = pAssetsFileInfo->getAssetsFileContext()->getAssetsFile()->typeTree.unityVersion; + pSelectClassDbDialog.reset(new SelectClassDbDialog(hInstance, hDlg, pContext->classPackage)); + pSelectClassDbDialog->setAffectedFileName(pAssetsFileInfo->getFileName()); + pSelectClassDbDialog->setDialogReason(reason_DatabaseNotFound); + pSelectClassDbDialog->setEngineVersion(std::string(targetVersion)); + HWND hSelectDialogWnd = pSelectClassDbDialog->ShowModeless(WM_APP+2); + if (hSelectDialogWnd == NULL) pSelectClassDbDialog.reset(); + assert(hSelectDialogWnd != NULL); +} + +void MainWindow2::OnRemoveContextInfo(FileContextInfo *info) +{ + auto cacheIt = disposableCacheElements.find(info); + if (cacheIt != disposableCacheElements.end()) + disposableCacheElements.erase(cacheIt); + while (!fileEntriesPendingForDbSelection.empty() && fileEntriesPendingForDbSelection.front().pEntry == nullptr) + fileEntriesPendingForDbSelection.pop_front(); //Cleanup + fileEntriesByContextInfo.erase(info); + //info->decRef(); //Reference from FileEntryUIInfo +} +void MainWindow2::OnUpdateContainers(AssetsFileContextInfo *pFile) +{ + for (auto handlerIt = eventHandlers.begin(); handlerIt != eventHandlers.end(); ++handlerIt) + { + (*handlerIt)->onUpdateContainers(pFile); + } +} +void MainWindow2::OnUpdateDependencies(AssetsFileContextInfo *pFile, size_t from, size_t to) +{ + for (auto handlerIt = eventHandlers.begin(); handlerIt != eventHandlers.end(); ++handlerIt) + { + (*handlerIt)->onUpdateDependencies(pFile, from, to); + } +} +void MainWindow2::OnChangeAsset(AssetsFileContextInfo *pFile, pathid_t pathID, bool wasRemoved) +{ + for (auto handlerIt = eventHandlers.begin(); handlerIt != eventHandlers.end(); ++handlerIt) + { + (*handlerIt)->onChangeAsset(pFile, pathID, wasRemoved); + } +} +void MainWindow2::OnChangeBundleEntry(BundleFileContextInfo *pFile, size_t index) +{ + this->updateBundleEntryName(pFile, index, pFile->getNewEntryName(index)); + for (auto handlerIt = eventHandlers.begin(); handlerIt != eventHandlers.end(); ++handlerIt) + { + (*handlerIt)->onUpdateBundleEntry(pFile, index); + } +} +void MainWindow2::hideManipulateDialog(IFileManipulateDialog *pDialog) +{ + if (this->activeManipDlgTab < this->manipDlgTabs.size() && + this->manipDlgTabs[this->activeManipDlgTab].pCurManipDlg.get() == pDialog && + pDialog != nullptr) + { + pDialog->onHide(); + this->manipDlgTabs[this->activeManipDlgTab].pCurManipDlg = nullptr; + InvalidateRect(hDlg, NULL, TRUE); //TODO: Redraw only the manipulate dialog area. + } +} +void MainWindow2::CloseUIFileEntry(FileEntryUIInfo *info, HWND hTree) +{ + auto cacheIt = disposableCacheElements.find(info); + if (cacheIt != disposableCacheElements.end()) + disposableCacheElements.erase(cacheIt); + + if (hTree == NULL) + hTree = GetDlgItem(this->hDlg, IDC_TREEFILES); + + DeleteFileEntry_TreeItems(hTree, info); + for (size_t iTab = 0; iTab < this->manipDlgTabs.size(); iTab++) + { + ManipDlgDesc &desc = this->manipDlgTabs[iTab]; + for (size_t iSel = 0; iSel < desc.selection.size(); iSel++) + { + bool tmp; + ITreeParameter *pCurSelection = desc.selection[iSel]; + if (pCurSelection && getEntryParam_FileEntryInfo(pCurSelection, tmp) == info) + { + if (desc.pCurManipDlg) + desc.pCurManipDlg->removeFileContext(info); + desc.selection[iSel] = nullptr; + break; + } + } + } + + //Remove references from the class database selection queue. + for (auto it = fileEntriesPendingForDbSelection.begin(); it != fileEntriesPendingForDbSelection.end(); ++it) + { + if (it->pEntry == info) + it->pEntry = nullptr; + } + //Additionally, cancel the active cldb selection dialog if it refers to a closed file. + if (!fileEntriesPendingForDbSelection.empty() && fileEntriesPendingForDbSelection.front().pEntry == nullptr + && pSelectClassDbDialog != nullptr) + pSelectClassDbDialog->ForceCancel(); + + fileEntries.erase(info->myiter); +} +bool MainWindow2::fileHasUnappliedChanges(FileEntryUIInfo *pFileInfo) +{ + for (size_t iTab = 0; iTab < this->manipDlgTabs.size(); iTab++) + { + bool isSelectedInTab = false; + ManipDlgDesc &desc = this->manipDlgTabs[iTab]; + if (!desc.pCurManipDlg) + continue; + for (size_t iSel = 0; iSel < desc.selection.size(); iSel++) + { + bool tmp; + ITreeParameter *pCurSelection = desc.selection[iSel]; + if (pCurSelection && getEntryParam_FileEntryInfo(pCurSelection, tmp) == pFileInfo) + { + isSelectedInTab = true; + break; + } + } + if (isSelectedInTab && desc.pCurManipDlg && desc.pCurManipDlg->hasUnappliedChanges()) + return true; + } + return false; +} +bool MainWindow2::fileHasUnsavedChanges(FileEntryUIInfo *pFileInfo) +{ + return pFileInfo->pContextInfo && pFileInfo->pContextInfo->hasNewChanges(*pContext); +} +//Returns true if the user chose to proceed anyway. +static bool askUserApplyChangeBeforeInstaller(HWND hParent) +{ + return IDYES == MessageBox(hParent, + TEXT("There are unapplied changes in an open tab.\nDo you want to apply these changes before creating an installer?"), + TEXT("Asset Bundle Extractor"), MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2); +} +//Returns true if the user chose to proceed anyway. +static bool warnUserOnUnappliedFileChange(HWND hParent, bool onSelectedFile) +{ + return IDYES == MessageBox(hParent, + onSelectedFile ? + TEXT("A tab that uses this file has unapplied changes.\nDo you want to proceed anyway?") : + TEXT("A tab that uses an opened file has unapplied changes.\nDo you want to proceed anyway?"), + TEXT("Asset Bundle Extractor"), MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2); +} +//Returns true if the user chose to proceed anyway. +static bool warnUserOnUnsavedFileChange(HWND hParent, bool onSelectedFile) +{ + return IDYES == MessageBox(hParent, + onSelectedFile ? TEXT("The file has unapplied changes.\nDo you want to proceed anyway?") + : TEXT("A file has unapplied changes.\nDo you want to proceed anyway?"), + TEXT("Asset Bundle Extractor"), MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2); +} +bool MainWindow2::CloseFile(unsigned int fileID) +{ + FileContextInfo_ptr pContextInfo = this->pContext->getContextInfo(fileID); + if (pContextInfo == nullptr) + return false; + auto fileUIEntryIt = this->fileEntriesByContextInfo.find(pContextInfo.get()); + if (fileUIEntryIt == this->fileEntriesByContextInfo.end()) + return false; + return CloseFile(fileUIEntryIt->second); +} +bool MainWindow2::CloseFile(FileEntryUIInfo *info, HWND hTree) +{ + if (hTree == NULL) + hTree = GetDlgItem(this->hDlg, IDC_TREEFILES); + bool choseToProceedUnapplied = false, choseToProceedUnsaved = false; + if (!choseToProceedUnapplied && fileHasUnappliedChanges(info)) + { + if (warnUserOnUnappliedFileChange(this->hDlg, true)) + choseToProceedUnapplied = true; + else + return false; + } + if (!choseToProceedUnsaved && fileHasUnsavedChanges(info)) + { + if (warnUserOnUnsavedFileChange(this->hDlg, true)) + choseToProceedUnsaved = true; + else + return false; + } + std::list selectedEntries; + selectedEntries.push_back(info); + for (auto it = selectedEntries.begin(); it != selectedEntries.end(); ) + { + FileEntryUIInfo *pEntryInfo = *it; + if (pEntryInfo->pending) + { + bool taskRunning; + if (this->pContext->taskManager.cancel(pEntryInfo->getTask(), &taskRunning) && !taskRunning) + { + //The task did not start and therefore will not issue a finish callback. + pendingFileEntriesByTask.erase(pEntryInfo->getTask()); + delete pEntryInfo->getTask(); + + CloseUIFileEntry(pEntryInfo, hTree); + } + } + else + { + std::shared_ptr pFileContextInfo = pEntryInfo->getContextInfo(); + if (pFileContextInfo) + { + std::vector childFileIDs; + pFileContextInfo->getChildFileIDs(childFileIDs); + auto firstChildIt = it; + for (size_t k = 0; k < childFileIDs.size(); k++) + { + if (childFileIDs[k] == 0) + continue; + //TODO: Handle pending child entries properly + std::shared_ptr pCurChild = pContext->getContextInfo(childFileIDs[k]); + auto childEntryInfoIt = fileEntriesByContextInfo.find(pCurChild.get()); + if (childEntryInfoIt != fileEntriesByContextInfo.end()) + { + //Note: If there are >2 nesting levels for opened files, + // we may end up having some of the sub-files closed even if the user decides to cancel now. + if (!choseToProceedUnapplied && fileHasUnappliedChanges(childEntryInfoIt->second)) + { + if (warnUserOnUnappliedFileChange(this->hDlg, true)) + choseToProceedUnapplied = true; + else + return false; + } + if (!choseToProceedUnsaved && fileHasUnsavedChanges(childEntryInfoIt->second)) + { + if (warnUserOnUnsavedFileChange(this->hDlg, true)) + choseToProceedUnsaved = true; + else + return false; + } + + //Insert before firstChildIt and update firstChildIt. + firstChildIt = selectedEntries.insert(firstChildIt, childEntryInfoIt->second); + } + } + if (firstChildIt != it) + { + //If this entry has children, close those first. This entry will be revisited afterwards. + it = firstChildIt; + continue; + } + if (pFileContextInfo->getFileContext()) + { + EFileContextType type = pFileContextInfo->getFileContext()->getType(); + if (type >= (EFileContextType)0 && type < FileContext_COUNT) + { + assert(this->fileEntryCountersByType[type] > 0); + this->fileEntryCountersByType[type]--; + } + } + } + CloseUIFileEntry(pEntryInfo, hTree); + + if (pFileContextInfo) + { + pContext->RemoveContextInfo(pFileContextInfo.get()); + } + } + //No need to keep the old entry. + it = selectedEntries.erase(it); + } +#ifdef _DEBUG + assert(selectedEntries.empty()); +#endif + return true; +} +void MainWindow2::onCloseFileCommand() +{ + HWND hTree = GetDlgItem(this->hDlg, IDC_TREEFILES); + size_t nSelectionsLast = 0; + bool continueClose = false; + do + { + std::vector selections; + MC_HTREELISTITEM selection = NULL; + while ((selection = MCTreeList_GetNextSelection(hTree, selection)) != NULL) + selections.push_back(selection); + if (selections.empty()) + break; + + continueClose = false; + for (size_t i = 0; i < selections.size(); i++) + { + selection = selections[i]; + setSelectItem(hTree, selection, false); + + ITreeParameter *pEntryParam = getEntryParam(hTree, selection); + bool isCloseable; + FileEntryUIInfo *pEntryInfo = getEntryParam_FileEntryInfo(pEntryParam, isCloseable); + + if (pEntryInfo) + { + size_t oldFileCount = fileEntries.size(); + if (isCloseable && !CloseFile(pEntryInfo, hTree)) + return; + if (fileEntries.size() != oldFileCount) + { + continueClose = true; + break; + } + } + else + { + if (isCloseable) + { + MCTreeList_DeleteItem(hTree, selection); + continueClose = true; + break; + } + } + + } + } while (continueClose); +} +bool MainWindow2::onCloseProgramCommand() +{ + bool choseToProceedUnapplied = false, choseToProceedUnsaved = false; + for (FileEntryUIInfo& entryInfo : getFileEntries()) + { + if (!choseToProceedUnapplied && fileHasUnappliedChanges(&entryInfo)) + { + if (warnUserOnUnappliedFileChange(this->hDlg, false)) + choseToProceedUnapplied = true; + else + return false; + } + if (!choseToProceedUnsaved && fileHasUnsavedChanges(&entryInfo)) + { + if (warnUserOnUnsavedFileChange(this->hDlg, false)) + choseToProceedUnsaved = true; + else + return false; + } + } + return true; +} + +void MainWindow2::onSaveFileRequest(FileEntryUIInfo *pUIInfo) +{ + assert(pUIInfo != nullptr); + if (pUIInfo->pContextInfo && pUIInfo->pContextInfo->hasAnyChanges(*this->pContext)) + { + std::string defaultFilePath = pUIInfo->pContextInfo->getFileContext()->getFilePath(); + if (!defaultFilePath.empty()) + defaultFilePath += "-mod"; + if (pUIInfo->pContextInfo->getParentFileID() != 0) + { + //Ignore bundled files here. + } + else + { + size_t tmp; + TCHAR *defaultFilePathT = (defaultFilePath.empty() ? nullptr : _MultiByteToTCHAR(defaultFilePath.c_str(), tmp)); + WCHAR *saveFilePath = nullptr; + HRESULT hr = ShowFileSaveDialog(this->hDlg, &saveFilePath, + TEXT("*.*|File:"), nullptr, defaultFilePathT, + TEXT("Save changes"), + UABE_FILEDIALOG_FILE_GUID); + if (defaultFilePathT != nullptr) + _FreeTCHAR(defaultFilePathT); + if (SUCCEEDED(hr)) + { + IAssetsWriter *pWriter = Create_AssetsWriterToFile(saveFilePath, true, true, RWOpenFlags_Immediately); + FreeCOMFilePathBuf(&saveFilePath); + if (pWriter == nullptr) + MessageBox(this->hDlg, TEXT("Unable to open the selected file for writing!"), TEXT("Asset Bundle Extractor"), MB_ICONERROR); + else + { + uint64_t size = pUIInfo->pContextInfo->write(*this->pContext, pWriter, 0, true); + Free_AssetsWriter(pWriter); + switch (pUIInfo->pContextInfo->getFileContext()->getType()) + { + case FileContext_Assets: + case FileContext_Bundle: + if (size == 0) + MessageBox(this->hDlg, TEXT("An error occured while saving the file!"), TEXT("Asset Bundle Extractor"), MB_ICONERROR); + break; + } + } + } + } + } +} + +static std::string formatNameFor(FileContextInfo *pContextInfo, FileEntryUIInfo *pUIInfo) +{ + if (pContextInfo == nullptr || pContextInfo->getFileContext() == nullptr) + return std::string("Pending : ") + pUIInfo->getShortName(); + assert(!pUIInfo->pending); + assert(pUIInfo->pContextInfo.get() == pContextInfo); + const char *nameSuffix = ""; + switch (pContextInfo->getFileContext()->getType()) + { + case FileContext_Assets: + nameSuffix = " (Assets)"; + break; + case FileContext_Bundle: + nameSuffix = " (Bundle)"; + break; + case FileContext_Resources: + nameSuffix = " (Resources)"; + break; + case FileContext_Generic: + nameSuffix = " (Generic)"; + break; + } + return std::string(pUIInfo->getShortName()) + nameSuffix; +} + +void MainWindow2::updateBundleEntryName(BundleFileContextInfo *pBundleInfo, size_t bundleEntryIdx, std::string newName) +{ + std::vector childFileIDs; + pBundleInfo->getChildFileIDs(childFileIDs); + if (bundleEntryIdx >= childFileIDs.size()) + return; + FileEntryUIInfo *pEntry = nullptr; + FileContextInfo_ptr pChildInfo = nullptr; + if (childFileIDs[bundleEntryIdx] != 0) + { + pChildInfo = this->pContext->getContextInfo(childFileIDs[bundleEntryIdx]); + if (pChildInfo != nullptr) + { + auto uiEntryIt = fileEntriesByContextInfo.find(pChildInfo.get()); + if (uiEntryIt != fileEntriesByContextInfo.end()) + pEntry = uiEntryIt->second; + } + } + else + { + for (auto it = pendingFileEntriesByTask.begin(); it != pendingFileEntriesByTask.end(); ++it) + { + auto pFileOpenTask = dynamic_cast(it->first); + if (pFileOpenTask->parentFileID == pBundleInfo->getFileID() && pFileOpenTask->directoryEntryIdx == bundleEntryIdx) + { + pEntry = it->second; + break; + } + } + } + if (pEntry == nullptr) + return; + pEntry->updateName(std::move(newName)); + + HWND hTree = GetDlgItem(this->hDlg, IDC_TREEFILES); + updateEntryName(hTree, pEntry->hTreeItem, formatNameFor(pChildInfo.get(), pEntry)); +} + +bool MainWindow2::OnFileEntryLoadSuccess(ITask *pTask, std::shared_ptr &pContextInfo, TaskResult result) +{ + if (getMenu() != NULL) + EnableMenuItem(getMenu(), IDM_FILE_SAVEALL, MF_ENABLED); + auto entryIt = pendingFileEntriesByTask.find(pTask); + if (entryIt == pendingFileEntriesByTask.end() + && pTask != nullptr && pContextInfo != nullptr && pContextInfo->getFileContext() + && pContextInfo->getParentFileID() == 0) + { + this->addPendingBaseFileEntry(pTask, pContextInfo->getFileContext()->getFilePath()); + entryIt = pendingFileEntriesByTask.find(pTask); + } + if (entryIt != pendingFileEntriesByTask.end()) + { + FileEntryUIInfo &entry = *(entryIt->second); + pendingFileEntriesByTask.erase(pTask); + entry.failed = false; + entry.pending = false; + entry.pContextInfo = nullptr; + entry.setContextInfo(pContextInfo); + //entry.pContextInfo = pContextInfo; + //pContextInfo->incRef(); //Reference from FileEntryUIInfo + fileEntriesByContextInfo.insert(std::make_pair(pContextInfo.get(), &entry)); + + HWND hTree = GetDlgItem(this->hDlg, IDC_TREEFILES); + + setEntryFileID(hTree, entry.hTreeItem, pContextInfo->getFileID()); + IFileContext *pFileContext = pContextInfo->getFileContext(); + switch (pFileContext->getType()) + { + case FileContext_Assets: + { + EAssetsFileOpenStatus openStatus = static_cast(result); + fileEntryCountersByType[FileContext_Assets]++; + + + assert(entry.standardDialogsCount == 0); + assert(entry.standardDialogsCount < entry.standardDialogs.size()); + entry.standardDialogsCount = 0; + FileManipulateDialogInfo &assetsDialog = entry.standardDialogs[entry.standardDialogsCount++]; + assetsDialog.hTreeItem = entry.hTreeItem; + assetsDialog.pEntry = &entry; + assetsDialog.type = FileManipulateDialog_AssetList; + updateEntryInfoRef(hTree, assetsDialog.hTreeItem, assetsDialog); //Intentional so the tree item is linked to the dialog action. + + assert(entry.standardDialogsCount < entry.standardDialogs.size()); + FileManipulateDialogInfo &dependenciesDialog = entry.standardDialogs[entry.standardDialogsCount++]; + dependenciesDialog.hTreeItem = insertEntry(hTree, entry.hTreeItem, std::string("Dependencies")); + dependenciesDialog.pEntry = &entry; + dependenciesDialog.type = FileManipulateDialog_AssetsDependencies; + updateEntryInfoRef(hTree, dependenciesDialog.hTreeItem, dependenciesDialog); + + assert(entry.standardDialogsCount < entry.standardDialogs.size()); + FileManipulateDialogInfo &containersDialog = entry.standardDialogs[entry.standardDialogsCount++]; + containersDialog.hTreeItem = insertEntry(hTree, entry.hTreeItem, std::string("Containers")); + containersDialog.pEntry = &entry; + containersDialog.type = FileManipulateDialog_AssetsContainers; + updateEntryInfoRef(hTree, containersDialog.hTreeItem, containersDialog); + + assert(entry.standardDialogsCount < entry.standardDialogs.size()); + FileManipulateDialogInfo &altAssetsDialog = entry.standardDialogs[entry.standardDialogsCount++]; + altAssetsDialog.hTreeItem = insertEntry(hTree, entry.hTreeItem, std::string("Assets")); + altAssetsDialog.pEntry = &entry; + altAssetsDialog.type = FileManipulateDialog_AssetList; + updateEntryInfoRef(hTree, altAssetsDialog.hTreeItem, altAssetsDialog); + } + break; + case FileContext_Bundle: + { + EBundleFileOpenStatus openStatus = static_cast(result); + std::shared_ptr pBundleInfo = std::static_pointer_cast(pContextInfo); + + if (openStatus == BundleFileOpenStatus_CompressedDirectory || + openStatus == BundleFileOpenStatus_CompressedData) + { + entry.pending = true; + entry.pContextInfo = nullptr; + fileEntriesByContextInfo.erase(pContextInfo.get()); + + if (decompressTargetDir.empty()) + { + //Let the user select a decompression output directory. + WCHAR *folderPath = nullptr; + if (decompressTargetDir_cancel || + !ShowFolderSelectDialog(this->hDlg, &folderPath, L"Select a decompression output directory", UABE_FILEDIALOG_FILE_GUID)) + { + decompressTargetDir_cancel = (pendingFileEntriesByTask.empty()) ? false : true; + updateEntryName(hTree, entry.hTreeItem, std::string("Failed : ") + entry.getShortName() + " (Compressed Bundle)"); + entry.failed = true; + entry.pending = false; + entry.openLogText += "Decompression was cancelled\n"; + return false; //Remove the bundle from the AppContext. + } + auto folderPathUTF8 = unique_WideToMultiByte(folderPath); + decompressTargetDir_cancel = false; + decompressTargetDir.assign(folderPathUTF8.get()); + FreeCOMFilePathBuf(&folderPath); + } + updateEntryName(hTree, entry.hTreeItem, std::string(entry.getShortName()) + " (Compressed Bundle)"); + std::shared_ptr pDecompressTask = pBundleInfo->EnqueueDecompressTask(*pContext, pBundleInfo, + decompressTargetDir + "\\" + pBundleInfo->getFileName() + "-decompressed"); + if (pendingFileEntriesByTask.empty()) + decompressTargetDir.clear(); + if (pDecompressTask == nullptr) + { + entry.failed = true; + entry.pending = false; + updateEntryName(hTree, entry.hTreeItem, std::string("Failed : ") + entry.getShortName() + " (Compressed Bundle)"); + entry.openLogText += "Failed to enqueue decompression\n"; + return false; //Remove the bundle from the AppContext. + } + else + { + pendingFileEntriesByTask.insert(std::make_pair(pDecompressTask.get(), &entry)); + } + + return true; + } + + fileEntryCountersByType[FileContext_Bundle]++; + + BundleFileContext *pBundleContext = pBundleInfo->getBundleFileContext(); + if (pBundleInfo->getEntryCount() > 0) + setHasChildren(hTree, entry.hTreeItem, true); + assert(pBundleInfo->getEntryCount() <= UINT_MAX); + for (size_t i = 0; i < pBundleInfo->getEntryCount(); i++) + { + this->loadBundleEntry(pBundleInfo, (unsigned int)i); + } + + assert(entry.standardDialogsCount == 0); + assert(entry.standardDialogsCount < entry.standardDialogs.size()); + entry.standardDialogsCount = 0; + FileManipulateDialogInfo &bundleDialog = entry.standardDialogs[entry.standardDialogsCount++]; + bundleDialog.hTreeItem = entry.hTreeItem; + bundleDialog.pEntry = &entry; + bundleDialog.type = FileManipulateDialog_Bundle; + updateEntryInfoRef(hTree, bundleDialog.hTreeItem, bundleDialog); //Intentional so the tree item is linked to the dialog action. + } + break; + case FileContext_Resources: + { + EResourcesFileOpenStatus openStatus = static_cast(result); + fileEntryCountersByType[FileContext_Resources]++; + } + break; + case FileContext_Generic: + { + EGenericFileOpenStatus openStatus = static_cast(result); + fileEntryCountersByType[FileContext_Generic]++; + } + break; + default: + break; + } + updateEntryName(hTree, entry.hTreeItem, formatNameFor(pContextInfo.get(), &entry)); + if (pendingFileEntriesByTask.empty()) + { + //Forget the decompress target directory once all files are loaded. + decompressTargetDir_cancel = false; + decompressTargetDir.clear(); + } + if (getSelectItem(hTree, entry.hTreeItem)) + { + //If the item is selected and just finished loading, the manipulate dialog has to be created or notified. + this->onChangeFileSelection(); + } + else + { + //The selection checkboxes have to be updated in case a new file has loaded that is not selected. + // -> deselect IDC_CKSELALL, make type-specific check boxes indeterminate. + std::vector newSelection; + MC_HTREELISTITEM selection = NULL; + while ((selection = MCTreeList_GetNextSelection(hTree, selection)) != NULL) + { + ITreeParameter *pCurParam = getEntryParam(hTree, selection); + newSelection.push_back(pCurParam); + } + this->doUpdateSelectionCheckboxes(newSelection); + } + return true; + } + return false; +} +void MainWindow2::OnFileEntryLoadFailure(ITask *pTask, std::string logText) +{ + auto entryIt = pendingFileEntriesByTask.find(pTask); + if (entryIt != pendingFileEntriesByTask.end()) + { + FileEntryUIInfo &entry = *(entryIt->second); + pendingFileEntriesByTask.erase(pTask); + entry.failed = true; + entry.pending = false; + entry.pContextInfo = nullptr; + logText.swap(entry.openLogText); + + HWND hTree = GetDlgItem(this->hDlg, IDC_TREEFILES); + updateEntryName(hTree, entry.hTreeItem, std::string("Failed : ") + entry.getShortName()); + } +} +void MainWindow2::OnDecompressSuccess(BundleFileContextInfo::DecompressTask *pTask) +{ + std::shared_ptr pContextInfo = std::static_pointer_cast(pTask->getFileContextInfo()); + if (!OnFileEntryLoadSuccess(pTask, pContextInfo, BundleFileOpenStatus_OK)) + pContext->RemoveContextInfo(pContextInfo.get()); +} +void MainWindow2::OnDecompressFailure(BundleFileContextInfo::DecompressTask *pTask) +{ + auto entryIt = pendingFileEntriesByTask.find(pTask); + if (entryIt != pendingFileEntriesByTask.end()) + { + FileEntryUIInfo &entry = *(entryIt->second); + pendingFileEntriesByTask.erase(pTask); + entry.failed = true; + entry.pending = false; + entry.pContextInfo = nullptr; + + HWND hTree = GetDlgItem(this->hDlg, IDC_TREEFILES); + updateEntryName(hTree, entry.hTreeItem, std::string("Failed : ") + entry.getShortName() + " (Compressed Bundle)"); + } + pContext->RemoveContextInfo(pTask->getFileContextInfo().get()); +} + +MainWindow2::~MainWindow2(void) +{ + //Remove objects that may unregister main window event handlers during destruction + // before the event handlers list is cleared. + disposableCacheElements.clear(); + fileEntriesByContextInfo.clear(); + pendingFileEntriesByTask.clear(); + fileEntries.clear(); + manipDlgTabs.clear(); + + pDialogFactory.reset(); + + pStatusTracker.reset(); + + assert(eventHandlers.empty()); +} + +IFileManipulateDialog::IFileManipulateDialog() +{} +IFileManipulateDialog::~IFileManipulateDialog() +{} diff --git a/UABE_Win32/MainWindow2.h b/UABE_Win32/MainWindow2.h new file mode 100644 index 0000000..45c9389 --- /dev/null +++ b/UABE_Win32/MainWindow2.h @@ -0,0 +1,478 @@ +#pragma once +#include "api.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "FileContext.h" +#include "AppContext.h" +#include "SelectClassDbDialog.h" +#include "SplitterControlHandler.h" +#include "../UABE_Generic/TaskStatusTracker.h" + +//Note: If extensibility through plugins is required, UUIDs may need to be used instead. +enum EFileManipulateDialogType +{ + FileManipulateDialog_Other, + FileManipulateDialog_AssetList, + FileManipulateDialog_AssetsDependencies, + FileManipulateDialog_AssetsContainers, + FileManipulateDialog_Bundle, +}; + +class IFileManipulateDialog; +class IFileManipulateDialogFactory +{ +public: + UABE_Win32_API IFileManipulateDialogFactory(); + UABE_Win32_API virtual ~IFileManipulateDialogFactory(); + //Constructs the object. + virtual std::shared_ptr construct(EFileManipulateDialogType type, HWND hParent)=0; +}; +class DefaultFileDialogFactory : public IFileManipulateDialogFactory +{ + class Win32AppContext *pContext; + //std::shared_ptr pAssetListDialog; +public: + UABE_Win32_API ~DefaultFileDialogFactory(); + UABE_Win32_API DefaultFileDialogFactory(class Win32AppContext *pContext); + UABE_Win32_API std::shared_ptr construct(EFileManipulateDialogType type, HWND hParent); +}; + +class ITreeParameter +{ +public: + bool b_isFileManipulateDialogInfo; + inline class FileManipulateDialogInfo *asFileManipulateDialogInfo() + { + if (isFileManipulateDialogInfo()) + return (class FileManipulateDialogInfo*)this; + return nullptr; + } + inline class FileEntryUIInfo *asFileEntryInfo() + { + if (isFileEntryInfo()) + return (class FileEntryUIInfo*)this; + return nullptr; + } + inline bool isFileManipulateDialogInfo() const { return b_isFileManipulateDialogInfo; } + inline bool isFileEntryInfo() const { return !b_isFileManipulateDialogInfo; } +}; +class FileManipulateDialogInfo : public ITreeParameter +{ +public: + FileManipulateDialogInfo(); + ~FileManipulateDialogInfo(); + + class FileEntryUIInfo *pEntry; + MC_HTREELISTITEM hTreeItem; + EFileManipulateDialogType type; uintptr_t param; +}; +class FileEntryUIInfo : public ITreeParameter +{ + size_t shortNameIndex; +public: + //Pending file entry constructor + FileEntryUIInfo(MC_HTREELISTITEM hTreeItem, const std::string &fullName, bool isFilePath = true); + ~FileEntryUIInfo(); + //Iterator for the std::list this is stored in. Needs to be set manually after inserting the entry into the list! + std::list::iterator myiter; + bool failed; + bool pending; //If true, implies that pContextInfo is null and that there are no child entries in the tree. + //Note that hTreeItem may appear in the first standardDialogs entry. + MC_HTREELISTITEM hTreeItem; + std::shared_ptr pContextInfo; + ITask *pTask; + + //No duplicate hTreeItems are allowed. Only the first entry in standardDialogs may use the file entry's hTreeItem. + size_t standardDialogsCount; + std::array standardDialogs; + std::list subDialogs; + + std::string openLogText; + std::string fullName; +public: + class DialogsIterator + { + FileEntryUIInfo &entryInfo; + size_t standardDialogsI; + std::list::iterator iter; + public: + inline DialogsIterator(FileEntryUIInfo &entryInfo) + : entryInfo(entryInfo), standardDialogsI(0), iter(entryInfo.subDialogs.begin()) + {} + inline FileManipulateDialogInfo *operator->() { return &**this; } + inline FileManipulateDialogInfo &operator*() + { + if (standardDialogsI < entryInfo.standardDialogsCount) + return entryInfo.standardDialogs[standardDialogsI]; + return *iter; + } + inline DialogsIterator &operator++() + { + standardDialogsI++; + if (standardDialogsI > entryInfo.standardDialogsCount) + iter++; + return *this; + } + inline bool end() + { + return (standardDialogsI >= entryInfo.standardDialogsCount && iter == entryInfo.subDialogs.end()); + } + }; + inline const char *getShortName() + { + return &(fullName.c_str()[shortNameIndex]); + } + inline void setContextInfo(std::shared_ptr pContextInfo) + { + if (!pending) + this->pContextInfo = pContextInfo; + else + assert(false); + } + inline std::shared_ptr getContextInfo() + { + if (!pending) return pContextInfo; + return nullptr; + } + inline FileContextInfo *getContextInfoPtr() + { + if (!pending) return pContextInfo.get(); + return nullptr; + } + inline ITask *getTask() + { + if (pending) return pTask; + return nullptr; + } + inline DialogsIterator getDialogsIterator() + { + return DialogsIterator(*this); + } + //Updates the name for inner files (esp. bundle entries). + inline void updateName(std::string newName) + { + if (shortNameIndex == 0) + fullName = std::move(newName); + } +}; +//Resource only used by the UI thread (not thread safe). The resource can be generated at any time. +class UIDisposableCache +{ + unsigned int nRefs; +public: + UABE_Win32_API UIDisposableCache(); + UABE_Win32_API virtual ~UIDisposableCache(); + virtual size_t approxMemory() = 0; + //The timestamp of the last time this cache item was used. + //Only needs to be up to date if isInUse would return false. + virtual time_t getLastUseTime() = 0; + virtual bool isInUse() = 0; + //TODO: Last usage timestamp and bool 'isInUse' + UABE_Win32_API virtual void incRef(); + UABE_Win32_API virtual void decRef(); +}; +//Basic reference holder for UIDisposableCache and sub classes. +template +struct UIDisposableCacheRef +{ + T *pCache; +public: + inline UIDisposableCacheRef(T *pCache = nullptr) + : pCache(pCache) + { + if (pCache) pCache->incRef(); + } + inline ~UIDisposableCacheRef() + { + if (pCache) pCache->decRef(); + pCache = nullptr; + } + inline UIDisposableCacheRef(const UIDisposableCacheRef &other) + { + pCache = other.pCache; + if (pCache) pCache->incRef(); + } + inline UIDisposableCacheRef(UIDisposableCacheRef &&other) noexcept + { + pCache = other.pCache; + other.pCache = nullptr; + } + inline UIDisposableCacheRef &operator=(const UIDisposableCacheRef &other) + { + pCache = other.pCache; + if (pCache) pCache->incRef(); + return (*this); + } + inline T *operator->() + { + return pCache; + } + inline T *get() + { + return pCache; + } +}; +class MainWindowEventHandler +{ +public: + UABE_Win32_API virtual void onUpdateContainers(AssetsFileContextInfo *pFile); + UABE_Win32_API virtual void onChangeAsset(AssetsFileContextInfo *pFile, pathid_t pathID, bool wasRemoved); + UABE_Win32_API virtual void onUpdateDependencies(AssetsFileContextInfo *info, size_t from, size_t to); + UABE_Win32_API virtual void onUpdateBundleEntry(BundleFileContextInfo *pFile, size_t index); +}; +typedef std::list::iterator MainWindowEventHandlerHandle; +class MainWindow2 +{ + typedef void* HResource; + + class Win32AppContext *pContext; + + HWND hDlg; + HMENU hMenu; + HINSTANCE hInstance; + HHOOK hHotkeyHook; + + SplitterControlHandler mainPanelSplitter; + float fileTreeColumnRatio; + + HWND hContainersDlg; + struct ManipDlgDesc + { + std::shared_ptr pCurManipDlg; + std::vector selection; + //TODO: If a selected tree parameter is a pending file that is about to finish loading, either + // a) directly force a refresh of the selected elements list like with a selection change (if this is the current tab) + // or b) set the forceSelectionRefresh flag so the elements list gets refreshed once the user switches to this tab + // (This could also be done by noticing that an ITreeParameter is a FileEntryUIInfo and not a FileManipulateDialogInfo, + // which means that it is for a pending file). + //bool forceSelectionRefresh; + }; + std::vector manipDlgTabs; size_t activeManipDlgTab; + bool oneshot_applySelectionToCurrentTab; + inline ManipDlgDesc *getActiveManipDlgDesc() + { + if (activeManipDlgTab < manipDlgTabs.size()) + return &manipDlgTabs[activeManipDlgTab]; + return nullptr; + } + inline IFileManipulateDialog *getActiveManipDlg() + { + ManipDlgDesc *pDesc = getActiveManipDlgDesc(); + if (pDesc) + return pDesc->pCurManipDlg.get(); + return nullptr; + } + std::list fileEntries; + std::unordered_map fileEntriesByContextInfo; + std::unordered_map pendingFileEntriesByTask; + std::array fileEntryCountersByType; + + //Maps a resource (FileContextInfo*, FileEntryUIInfo*) to a disposable cache entry. + //Removal of the underlying resource will remove the cache entry from this map and decrease its reference counter. + //(TODO: Implement this behaviour, and use this functionality for the AssetListDialog + // -> Cache of asset list rows for an AssetsFileContextInfo object) + std::unordered_map>> disposableCacheElements; + std::list eventHandlers; + + bool decompressTargetDir_cancel; + std::string decompressTargetDir; + + std::unique_ptr pStatusTracker; +public: + MainWindow2(HINSTANCE hInstance); + ~MainWindow2(void); + + bool Initialize(); + int HandleMessages(); + + //void addManipulateDialog(IFileManipulateDialog *pDialog); + //void removeManipulateDialog(IFileManipulateDialog *pDialog); + UABE_Win32_API void hideManipulateDialog(class IFileManipulateDialog *pDialog); + + //Selects a file context. + //preventOpenNewTab: If set, the new selection will be applied to the current tab (if possible). + // If not set, a new tab may be opened depending on whether the user opened editors (among other factors). + UABE_Win32_API void selectFileContext(unsigned int fileID, bool preventOpenNewTab); + + //Loads a bundle entry. + UABE_Win32_API bool loadBundleEntry(std::shared_ptr pBundleInfo, unsigned int bundleEntryIdx); + + //Adds a disposable cache element to the list. + //Avoid adding too many (small) cache elements to limit the 'garbage collection' performance hit. + inline void addDisposableCacheElement(HResource hResource, UIDisposableCache *pElement) + { + disposableCacheElements[hResource].push_back(UIDisposableCacheRef<>(pElement)); + } + //Find a disposable cache element on the given resource where dynamic_cast succeeds with the type T. + //Returns a reference object with a null pointer on failure. + template + inline UIDisposableCacheRef findDisposableCacheElement(HResource hResource) + { + auto cacheIt = disposableCacheElements.find(hResource); + if (cacheIt != disposableCacheElements.end()) + { + //This obviously is not efficient for large amounts of cache entries for a resource. + for (auto elementIt = cacheIt->second.begin(); elementIt != cacheIt->second.end(); ++elementIt) + { + T *target = dynamic_cast(elementIt->get()); + if (target != nullptr) + return UIDisposableCacheRef(target); + } + } + return UIDisposableCacheRef(); + } + inline MainWindowEventHandlerHandle registerEventHandler(MainWindowEventHandler *pHandler) + { + return eventHandlers.insert(eventHandlers.end(), pHandler); + } + inline void unregisterEventHandler(MainWindowEventHandlerHandle hHandler) + { + eventHandlers.erase(hHandler); + } + + inline HMENU getMenu() { return hMenu; } + inline HINSTANCE getHInstance() { return hInstance; } + inline HWND getWindow() { return hDlg; } + + inline std::list &getFileEntries() + { + return fileEntries; + } + + //Closes the file and all children. Be careful when closing multiple files, as other FileContextInfo pointers (i.e. from child files) may become invalid. + //Returns false if the user aborted it due to unsaved changes. + UABE_Win32_API bool CloseFile(FileEntryUIInfo *info, HWND hTree = NULL); + //Closes the file and all children. Note that FileContextInfo pointers from child files may become invalid. + //Returns false if the user aborted it due to unsaved changes, or if the file wasn't opened. + UABE_Win32_API bool CloseFile(unsigned int fileID); + +protected: + bool OnFileEntryLoadSuccess(ITask *pTask, std::shared_ptr &pContextInfo, TaskResult result); + //Uses logText.swap() + void OnFileEntryLoadFailure(ITask *pTask, std::string logText); + void OnDecompressSuccess(BundleFileContextInfo::DecompressTask *pTask); + void OnDecompressFailure(BundleFileContextInfo::DecompressTask *pTask); + void OnFindClassDatabaseFailure(AssetsFileContextInfo *pAssetsFileInfo, ClassDatabasePackage &package); + void OnRemoveContextInfo(FileContextInfo *info); + void OnUpdateContainers(AssetsFileContextInfo *pFile); + void OnUpdateDependencies(AssetsFileContextInfo *info, size_t from, size_t to); + void OnChangeAsset(AssetsFileContextInfo *pFile, pathid_t pathID, bool wasRemoved); + void OnChangeBundleEntry(BundleFileContextInfo *pFile, size_t index); + + void CloseUIFileEntry(FileEntryUIInfo *info, HWND hTree = NULL); + + //Checks whether any tab potentially has unapplied changes for a given file. + //-> Returns true iff a tab has this file selected and has unapplied changes. + bool fileHasUnappliedChanges(FileEntryUIInfo *pFileInfo); + //Checks whether the file has any applied but unsaved changes. + bool fileHasUnsavedChanges(FileEntryUIInfo *pFileInfo); + + void updateBundleEntryName(BundleFileContextInfo *pBundleInfo, size_t bundleEntryIdx, std::string newName); + +private: + std::unique_ptr pSelectClassDbDialog; + std::shared_ptr defaultDatabaseFile; + std::map> databaseFilesByEngineVersion; + struct DbSelectionQueueEntry + { + FileEntryUIInfo *pEntry; + bool reason_DatabaseNotFound; + inline DbSelectionQueueEntry(FileEntryUIInfo *pEntry, bool reason_DatabaseNotFound) + : pEntry(pEntry), reason_DatabaseNotFound(reason_DatabaseNotFound) + {} + }; + std::deque fileEntriesPendingForDbSelection; + + void OnSelectClassDbDialogFinished(); + //Tries to find the class database based on defaults : databaseFilesByEngineVersion, defaultDatabaseFile. + bool TryFindClassDatabase(AssetsFileContextInfo *pAssetsFileInfo); + //Requires that the front entry of fileEntriesPendingForDbSelection matches the function parameters. + void OpenClassDatabaseSelection(AssetsFileContextInfo *pAssetsFileInfo, bool reason_DatabaseNotFound); + + std::unique_ptr pDialogFactory; +private: + static LRESULT CALLBACK KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam); + static INT_PTR CALLBACK DlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); + static LRESULT CALLBACK ProgSubclassProc(HWND hWnd, UINT message, + WPARAM wParam, LPARAM lParam, + uintptr_t uIdSubclass, DWORD_PTR dwRefData); + + inline bool onAppContextMessageAsync() + { + bool result = PostMessage(hDlg, WM_APP+0, 0, 0); + assert(result); + return result; + } + + void onResize(bool defer = true); + + bool ignoreTreeSelChanges, skipDeselectOnTabChange; + void onClickSelectionCheckbox(unsigned int checkboxID, int checkState); + void doUpdateSelectionCheckboxes(const std::vector &selections); + void onChangeFileSelection(); + void onOpenFileCommand(); + void addPendingBaseFileEntry(ITask *pTask, const std::string &path); + void onCloseFileCommand(); + bool onCloseProgramCommand(); + + void onSaveFileRequest(FileEntryUIInfo *pUIInfo); + + void doOpenTab(); + bool preDeleteTab(MC_NMMTCLOSEITEM *pNotification); + void onDeleteTab(MC_NMMTDELETEITEM *pNotification); + void onSwitchTabs(MC_NMMTSELCHANGE *pNotification); + + //Runs the garbage collector if necessary. + void onGCTick(); + + inline void setContext(class Win32AppContext *pContext) + { + this->pContext = pContext; + } + friend class Win32AppContext; +}; + +class IFileManipulateDialog +{ +protected: + std::weak_ptr selfPtr; + friend class MainWindow2; +public: + UABE_Win32_API IFileManipulateDialog(); + UABE_Win32_API virtual ~IFileManipulateDialog(); + virtual void addFileContext(const std::pair &context)=0; + virtual void removeFileContext(FileEntryUIInfo *pContext)=0; + virtual EFileManipulateDialogType getType()=0; + virtual HWND getWindowHandle()=0; + virtual void onHotkey(ULONG message, DWORD keyCode)=0; //message : currently only WM_KEYDOWN; keyCode : VK_F3 for instance + virtual bool onCommand(WPARAM wParam, LPARAM lParam)=0; //Called for unhandled WM_COMMAND messages. Returns true if this dialog has handled the request, false otherwise. + virtual void onShow()=0; + virtual void onHide()=0; + //Called when the user requests to close the tab. + //Returns true if there are unapplied changes, false otherwise. + //If the function will return true and applyable is not null, + // *applyable will be set to true iff applyNow() is assumed to succeed without further interaction + // (e.g. all fields in the dialog have a valid value, ...). + //The caller uses this info to decide whether and how it should display a confirmation dialog before proceeding. + virtual bool hasUnappliedChanges(bool *applyable=nullptr)=0; + //Called when the user requests to apply the changes (e.g. selecting Apply, Save or Save All in the menu). + //Returns whether the changes have been applied; + // if true, the caller may continue closing the IFileManipulateDialog. + // if false, the caller may stop closing the IFileManipulateDialog. + //Note: applyChanges() is expected to notify the user about errors (e.g. via MessageBox). + virtual bool applyChanges()=0; + //Returns whether the tab prefers not to be automatically closed due to user interaction (e.g. open sub-dialogs). + //-> The caller separately checks hasUnappliedChanges(), so unapplied changes need not be checked here. + //For instance, a pure 'info' dialog with little to no user interaction should return false (can be closed). + // Other dialogs may return true if the user has interacted to a certain degree (performed selections, ...). + virtual bool doesPreferNoAutoclose()=0; +}; \ No newline at end of file diff --git a/UABE_Win32/ModInstallerEditor2.cpp b/UABE_Win32/ModInstallerEditor2.cpp new file mode 100644 index 0000000..ef44385 --- /dev/null +++ b/UABE_Win32/ModInstallerEditor2.cpp @@ -0,0 +1,818 @@ +#include "stdafx.h" +#include "ModInstallerEditor2.h" +#include "resource.h" +#include "../ModInstaller/InstallerDataFormat.h" +#include "../ModInstaller/ModInstaller.h" +#include "../AssetsTools/InternalAssetsReplacer.h" +#include "../AssetsTools/InternalBundleReplacer.h" +#include "../libStringConverter/convert.h" +#include "FileDialog.h" +#include +#include +#include + +void Win32ModInstallerEditor::UpdateDisplayedRelPaths() +{ + HWND hEditBaseFolder = GetDlgItem(hDlg, IDC_EBASEFOLDER); + HWND hTreeChanges = GetDlgItem(hDlg, IDC_TREECHANGES); + if (!hEditBaseFolder || !hTreeChanges) + return; + size_t baseDirLen = (size_t)Edit_GetTextLength(hEditBaseFolder); + std::vector tBaseDir(baseDirLen + 1); + Edit_GetText(hEditBaseFolder, tBaseDir.data(), (int)(baseDirLen + 1)); + tBaseDir[baseDirLen] = 0; + + //PathRelativePathTo https://msdn.microsoft.com/en-us/library/bb773740(VS.85).aspx + for (size_t i = 0; i < visibleFiles.size(); i++) + { + assert(visibleFiles[i].treeViewEntry != NULL); + size_t filePathLen = 0; + auto tcFilePath = unique_MultiByteToTCHAR(visibleFiles[i].pathOrName.c_str(), filePathLen); + size_t newPathLen = baseDirLen + filePathLen + 1; if (newPathLen <= MAX_PATH) newPathLen = MAX_PATH + 1; + std::vector newPath(newPathLen); + if (!PathRelativePathTo(newPath.data(), tBaseDir.data(), FILE_ATTRIBUTE_DIRECTORY, tcFilePath.get(), FILE_ATTRIBUTE_NORMAL)) + memcpy(newPath.data(), tcFilePath.get(), (filePathLen + 1) * sizeof(TCHAR)); + + TVITEMEX itemex; + itemex.hItem = (HTREEITEM)visibleFiles[i].treeViewEntry; + itemex.mask = TVIF_HANDLE | TVIF_TEXT; + itemex.pszText = newPath.data(); + itemex.cchTextMax = (int)_tcslen(newPath.data()); + TreeView_SetItem(hTreeChanges, &itemex); + } +} +void Win32ModInstallerEditor::SelectAndLoadIcon() +{ + static const GUID UABE_FILEDIALOG_ICON_GUID = { 0x6ac81505, 0xed13, 0xdca2, 0x14, 0xf5, 0x70, 0xf2, 0x14, 0xab, 0xf6, 0x73 }; + HWND hIIcon = GetDlgItem(hDlg, IDC_IICON); + if (!hIIcon) + return; + WCHAR *pakPath = NULL; + if (SUCCEEDED(ShowFileOpenDialog(hDlg, &pakPath, L"*.ico|Icon file:", + nullptr, nullptr, nullptr, UABE_FILEDIALOG_ICON_GUID))) + { + IAssetsReader *pFileReader = Create_AssetsReaderFromFile(pakPath, true, RWOpenFlags_Immediately); + if (pFileReader) + { + pFileReader->Seek(AssetsSeek_End, 0); + QWORD _size = 0; + pFileReader->Tell(_size); + size_t size = (size_t)_size; + pFileReader->Seek(AssetsSeek_Begin, 0); + std::vector fileData(size, 0); + pFileReader->Read(size, fileData.data()); + Free_AssetsReader(pFileReader); + + HICON hIcon = (HICON)LoadImage(NULL, pakPath, IMAGE_ICON, 32, 32, LR_LOADFROMFILE); + FreeCOMFilePathBuf(&pakPath); + if (hIcon) + { + HICON hOldIcon = (HICON)SendMessage(hIIcon, STM_GETIMAGE, IMAGE_ICON, NULL); + SendMessage(hIIcon, STM_SETIMAGE, IMAGE_ICON, (LPARAM)hIcon); + if (hOldIcon) + DestroyIcon(hOldIcon); + this->iconData.swap(fileData); + } + } + FreeCOMFilePathBuf(&pakPath); + } +} + +void Win32ModInstallerEditor::SelectAndImportPackage() +{ + HWND hTreeChanges = GetDlgItem(hDlg, IDC_TREECHANGES); + HWND hEModName = GetDlgItem(hDlg, IDC_EMODNAME); + HWND hEAuthors = GetDlgItem(hDlg, IDC_EAUTHORS); + HWND hEDescription = GetDlgItem(hDlg, IDC_EDESCRIPTION); + if (!hTreeChanges || !hEModName || !hEAuthors || !hEDescription) + return; + WCHAR *pakPath = NULL; + if (FAILED(ShowFileOpenDialog(hDlg, &pakPath, L"*.emip|UABE Mod Installer Package:*.exe|UABE Installer:", + nullptr, nullptr, nullptr, UABE_FILEDIALOG_FILE_GUID))) + return; + std::shared_ptr pReader(Create_AssetsReaderFromFile(pakPath, true, RWOpenFlags_Immediately), Free_AssetsReader); + FreeCOMFilePathBuf(&pakPath); + if (!pReader) + return; + InstallerPackageFile loadedPackage = InstallerPackageFile(); + QWORD readPos = 0; + bool success = loadedPackage.Read(readPos, pReader); + if (!success) + { + size_t overlayOffset = GetPEOverlayOffset(pReader.get()); + if (overlayOffset != 0) + { + readPos = overlayOffset; + success = loadedPackage.Read(readPos, pReader); + } + } + if (!success) + { + MessageBox(appContext.getMainWindow().getWindow(), TEXT("Unable to understand the package file!\n")\ + TEXT("Make sure the selected file actually is a valid package file."), TEXT("ERROR"), 16); + return; + } + WCHAR *newBasePath = NULL; + if (ShowFolderSelectDialog(hDlg, &newBasePath, L"Select the base path", UABE_FILEDIALOG_FILE_GUID)) + { + size_t wBasePathLen = wcslen(newBasePath); + InstallerPackageAssetsDesc tempDesc; + for (size_t i = 0; i < loadedPackage.affectedAssets.size(); i++) + { + size_t wPathLen = 0; + WCHAR *wPath = _MultiByteToWide(loadedPackage.affectedAssets[i].path.c_str(), wPathLen); + std::vector combinedPathBuf(std::max(wBasePathLen + wPathLen + 16, MAX_PATH)); + bool changePath = PathCombine(combinedPathBuf.data(), newBasePath, wPath) != NULL; + _FreeWCHAR(wPath); + if (changePath) + { + tempDesc.type = loadedPackage.affectedAssets[i].type; + tempDesc.replacers.assign(loadedPackage.affectedAssets[i].replacers.begin(), + loadedPackage.affectedAssets[i].replacers.end()); + size_t mbCombinedPathLen = 0; + tempDesc.path = _WideToMultiByte(combinedPathBuf.data(), mbCombinedPathLen); + loadedPackage.affectedAssets[i] = tempDesc; + } + } + FreeCOMFilePathBuf(&newBasePath); + } + if (Edit_GetTextLength(hEModName) == 0) + { + auto tcTemp = unique_MultiByteToTCHAR(loadedPackage.modName.c_str()); + Edit_SetText(hEModName, tcTemp.get()); + } + if (Edit_GetTextLength(hEAuthors) == 0) + { + auto tcTemp = unique_MultiByteToTCHAR(loadedPackage.modCreators.c_str()); + Edit_SetText(hEAuthors, tcTemp.get()); + } + if (Edit_GetTextLength(hEDescription) == 0) + { + auto tcTemp = unique_MultiByteToTCHAR(loadedPackage.modDescription.c_str()); + Edit_SetText(hEDescription, tcTemp.get()); + } + + MergeInstallerData(loadedPackage); + UpdateModsTree(); + //UpdateDisplayedRelPaths(); +} + + +void Win32ModInstallerEditor::MergeInstallerData(InstallerPackageFile &newFile) +{ + //Note: Is O(n²), but should be fine since the amount of types n is relatively small. + for (size_t i = 0; i < newFile.addedTypes.classes.size(); i++) + { + bool alreadyExists = false; + for (size_t k = 0; k < typesToExport.classes.size(); k++) + { + if (newFile.addedTypes.classes[i].classId == typesToExport.classes[k].classId) + { + alreadyExists = true; + break; + } + } + if (!alreadyExists) + { + typesToExport.InsertFrom(&newFile.addedTypes, &newFile.addedTypes.classes[i]); + } + } + if (!newFile.affectedAssets.empty()) + this->changedFlag = true; + for (size_t i = newFile.affectedAssets.size(); i > 0; i--) + { + InstallerPackageAssetsDesc &newDesc = newFile.affectedAssets[i-1]; + VisibleFileEntry newEntry(this->appContext, newDesc); + bool merged = false; + for (size_t k = visibleFiles.size(); k > 0; k--) + { + VisibleFileEntry &existingEntry = visibleFiles[k-1]; + //TODO: Use C++17 std::filesystem::equivalent instead of string comparison. + if (newEntry.fileType == existingEntry.fileType && !newEntry.pathNull && newEntry.pathOrName == existingEntry.pathOrName) + { + auto resolveConflict = [&newEntry, this](VisibleReplacerEntry& existing, const VisibleReplacerEntry& other) + { + std::string message; + if (dynamic_cast(existing.pReplacer.get()) != nullptr) + { + auto* pExistingReplacer = reinterpret_cast(existing.pReplacer.get()); + assert(dynamic_cast(other.pReplacer.get()) != nullptr); + message = "There's a conflict between the resource replacers for " + newEntry.pathOrName + + ".\nShould the new replacer be used and the old one be removed?"; + } + else if (dynamic_cast(existing.pReplacer.get()) != nullptr) + { + BundleReplacer* pExistingReplacer = reinterpret_cast(existing.pReplacer.get()); + const char* name = pExistingReplacer->GetOriginalEntryName(); + if (name == nullptr) name = pExistingReplacer->GetEntryName(); + if (name == nullptr) name = ""; + assert(dynamic_cast(other.pReplacer.get()) != nullptr); + message = "There's a conflict between the bundle entry replacers for " + newEntry.pathOrName + + "/<...>/" + name + ".\nShould the new replacer be used and the old one be removed?"; + } + else if (dynamic_cast(existing.pReplacer.get()) != nullptr) + { + assert(dynamic_cast(other.pReplacer.get()) != nullptr); + assert(reinterpret_cast(existing.pReplacer.get())->GetPathID() + == reinterpret_cast(other.pReplacer.get())->GetPathID()); + message = "There's a conflict between the asset replacers for " + newEntry.pathOrName + + "/<...>/Path ID " + + std::to_string((int64_t)reinterpret_cast(existing.pReplacer.get())->GetPathID()) + + ".\nShould the new replacer be used and the old one be removed?"; + } + else if (dynamic_cast(existing.pReplacer.get()) != nullptr) + { + assert(dynamic_cast(other.pReplacer.get()) != nullptr); + message = "There's a conflict between the dependency replacers for " + newEntry.pathOrName + + ".\nShould the new replacer be used and the old one be removed?"; + } + else if (dynamic_cast(existing.pReplacer.get()) != nullptr) + { + assert(dynamic_cast(other.pReplacer.get()) != nullptr); + message = "There's a conflict between the replacers for " + newEntry.pathOrName + + ".\nShould the new replacer be used and the old one be removed?"; + } + assert(!message.empty()); + if (message.empty()) + return false; + auto tMessage = unique_MultiByteToTCHAR(message.c_str()); + if (IDYES == MessageBox(hDlg, tMessage.get(), TEXT("Mod Installer Editor"), MB_YESNO)) + { + HWND hTreeModifications = GetDlgItem(hDlg, IDC_TREECHANGES); + if (existing.treeItem != NULL && hTreeModifications != NULL) + { + TreeView_DeleteItem(hTreeModifications, existing.treeItem); + } + existing.treeItem = NULL; + return true; + } + return false; + }; + existingEntry.mergeWith(newEntry, resolveConflict); + merged = true; + break; + } + } + if (!merged) + { + this->visibleFiles.push_back(std::move(newEntry)); + } + } +} + +bool Win32ModInstallerEditor::removeChangesBy(VisibleFileEntry &file, HTREEITEM treeItem) +{ + for (size_t i = 0; i < file.replacers.size(); i++) + { + if ((HTREEITEM)file.replacers[i].treeItem == treeItem) + { + //Delete a single replacer. + TreeView_DeleteItem(hTreeModifications, treeItem); + file.replacers.erase(file.replacers.begin() + i); + return true; + } + } + for (size_t i = 0; i < file.subFiles.size(); ++i) + { + if ((HTREEITEM)file.subFiles[i].treeViewEntry == treeItem) + { + //Delete all changes in a sub file. + TreeView_DeleteItem(hTreeModifications, file.subFiles[i].treeViewEntry); + file.subFiles.erase(file.subFiles.begin() + i); + return true; + } + } + //Try in deeper levels. + for (size_t i = 0; i < file.subFiles.size(); ++i) + { + if (file.subFiles[i].treeViewEntry != NULL && removeChangesBy(file.subFiles[i], treeItem)) + return true; + } + return false; +} +void Win32ModInstallerEditor::RemoveChange(HTREEITEM treeItem) +{ + if (treeItem == NULL) + return; + if (treeItem == bundleBaseEntry || treeItem == assetsBaseEntry || treeItem == resourcesBaseEntry) + { + //Delete all changes in bundles / assets. + auto targetType = + (treeItem == bundleBaseEntry) ? FileContext_Bundle + : ((treeItem == assetsBaseEntry) ? FileContext_Assets : FileContext_Resources); + for (size_t _i = this->visibleFiles.size(); _i > 0; --_i) + { + size_t i = _i - 1; + if (this->visibleFiles[i].fileType == targetType + && this->visibleFiles[i].treeViewEntry != NULL) + { + this->changedFlag = true; + TreeView_DeleteItem(hTreeModifications, this->visibleFiles[i].treeViewEntry); + this->visibleFiles.erase(this->visibleFiles.begin() + i); + } + } + return; + } + for (size_t i = 0; i < this->visibleFiles.size(); ++i) + { + //Delete all changes in a base file. + if ((HTREEITEM)this->visibleFiles[i].treeViewEntry == treeItem) + { + this->changedFlag = true; + TreeView_DeleteItem(hTreeModifications, this->visibleFiles[i].treeViewEntry); + this->visibleFiles.erase(this->visibleFiles.begin() + i); + return; + } + } + //Look in all non-top levels (recursively). + for (size_t i = 0; i < this->visibleFiles.size(); ++i) + { + if (this->visibleFiles[i].treeViewEntry != NULL && removeChangesBy(this->visibleFiles[i], treeItem)) + { + this->changedFlag = true; + return; + } + } + //Tree item was not found, even though we were passed a non-NULL handle. + assert(false); +} + +//ModInstaller.dll +__declspec(dllimport) bool MakeInstaller(const TCHAR *installerDllPath, InstallerPackageFile *installerData, const TCHAR *outPath, const std::vector &iconData); + +static bool GenerateInstaller(HWND hDlg, InstallerPackageFile &packageFile, const std::wstring &filePath, + const std::vector &iconData) +{ + HMODULE hModInstaller = GetModuleHandle(TEXT("ModInstaller.dll")); + if (!hModInstaller) + MessageBox(hDlg, TEXT("Unable to locate ModInstaller.dll!"), TEXT("ERROR"), 16); + else + { + std::vector moduleFileNameBuf(257); + SetLastError(ERROR_SUCCESS); + while (true) + { + moduleFileNameBuf[moduleFileNameBuf.size() - 1] = 0; + DWORD written = GetModuleFileName(hModInstaller, moduleFileNameBuf.data(), (DWORD)(moduleFileNameBuf.size() - 1)); + //How too small buffer sizes are indicated : + //Win XP : wrote partial string without null-terminator + //Win Vista+ : ERROR_INSUFFICIENT_BUFFER set + if ((written > moduleFileNameBuf.size()) || + (GetLastError() == ERROR_INSUFFICIENT_BUFFER) || moduleFileNameBuf[written] != 0) + { + moduleFileNameBuf.resize(moduleFileNameBuf.size() + 1024); + } + else + { + moduleFileNameBuf.resize(written + 1); + moduleFileNameBuf[written] = 0; + break; + } + } + bool result = MakeInstaller(&moduleFileNameBuf[0], &packageFile, filePath.c_str(), iconData); + return result; + } + return false; +} + +bool Win32ModInstallerEditor::SaveChanges() +{ + InstallerPackageFile packageFile; + packageFile.addedTypes = this->typesToExport; + + HWND hEditModName = GetDlgItem(hDlg, IDC_EMODNAME); + HWND hEditAuthors = GetDlgItem(hDlg, IDC_EAUTHORS); + HWND hEditDescription = GetDlgItem(hDlg, IDC_EDESCRIPTION); + + { + size_t modNameLen = (size_t)Edit_GetTextLength(hEditModName); + std::vector tModName(modNameLen + 1); + Edit_GetText(hEditModName, tModName.data(), (int)(tModName.size())); + tModName[modNameLen] = 0; + auto mb = unique_TCHARToMultiByte(tModName.data()); + packageFile.modName = mb.get(); + } + { + size_t authorsLen = (size_t)Edit_GetTextLength(hEditAuthors); + std::vector tAuthors(authorsLen + 1); + Edit_GetText(hEditAuthors, tAuthors.data(), (int)(tAuthors.size())); + tAuthors[authorsLen] = 0; + auto mb = unique_TCHARToMultiByte(tAuthors.data()); + packageFile.modCreators = mb.get(); + } + { + size_t descriptionLen = (size_t)Edit_GetTextLength(hEditDescription); + std::vector tDescriptions(descriptionLen + 1); + Edit_GetText(hEditDescription, tDescriptions.data(), (int)(tDescriptions.size())); + tDescriptions[descriptionLen] = 0; + auto mb = unique_TCHARToMultiByte(tDescriptions.data()); + packageFile.modDescription = mb.get(); + } + //#ifdef __X64 + //if (this->saveType == ModDataSaveType_Installer) + //{ + // DWORD result = MessageBox( + // hDlg, + // TEXT("The installer will only be usable on 64bit systems. The 32bit release of UABE creates installers that work on both.\n")\ + // TEXT("Do you want to proceed? Press \"No\" to create an installer package that can be opened to create a 32bit installer."), + // TEXT("Asset Bundle Extractor"), + // MB_YESNOCANCEL); + // switch (result) + // { + // case IDYES: + // break; + // case IDNO: + // this->saveType = ModDataSaveType_PackageFile; + // break; + // case IDCANCEL: + // default: + // return false; + // } + //} + //#endif + WCHAR *filePathW = NULL; + if (FAILED(ShowFileSaveDialog(hDlg, &filePathW, + (this->saveType == ModDataSaveType_Installer) ? L"*.exe|Standalone Mod Installer:" + : L"*.emip|UABE Mod Installer Package:", + nullptr, nullptr, nullptr, UABE_FILEDIALOG_FILE_GUID))) + return false; + + std::wstring filePath = std::wstring(filePathW); + std::wstring fileExtension = (this->saveType == ModDataSaveType_Installer) ? L".exe" : L".emip"; + if (filePath.size() >= fileExtension.size() + && 0==filePath.compare(filePath.size() - fileExtension.size(), fileExtension.size(), fileExtension)) + {} + else + filePath += fileExtension; + FreeCOMFilePathBuf(&filePathW); + + HWND hEditBaseFolder = GetDlgItem(hDlg, IDC_EBASEFOLDER); + HWND hTreeChanges = GetDlgItem(hDlg, IDC_TREECHANGES); + if (!hEditBaseFolder || !hTreeChanges) + return false; + size_t baseDirLen = (size_t)Edit_GetTextLength(hEditBaseFolder); + packageFile.affectedAssets.resize(this->visibleFiles.size()); + std::vector pathBuffer; + + //Fill in the relative paths. + for (size_t i = 0; i < this->visibleFiles.size(); i++) + { + assert(!this->visibleFiles[i].pathOrName.empty() && !this->visibleFiles[i].pathNull); + assert(this->visibleFiles[i].treeViewEntry != NULL); + if (this->visibleFiles[i].pathOrName.empty() || this->visibleFiles[i].pathNull || this->visibleFiles[i].treeViewEntry == NULL) + continue; + size_t newPathLen = baseDirLen + this->visibleFiles[i].pathOrName.size() + 1; + if (newPathLen <= MAX_PATH) newPathLen = MAX_PATH + 1; + + //Retrieve the relative paths from the TreeView. + TVITEMEX itemex; + do { + if (newPathLen >= INT_MAX-1) newPathLen = INT_MAX-2; + if (pathBuffer.size() < newPathLen) pathBuffer.resize(newPathLen); + itemex.hItem = (HTREEITEM)this->visibleFiles[i].treeViewEntry; + itemex.mask = TVIF_HANDLE | TVIF_TEXT; + itemex.pszText = pathBuffer.data(); + itemex.cchTextMax = (int)pathBuffer.size(); + TreeView_GetItem(hTreeChanges, &itemex); + pathBuffer[pathBuffer.size() - 1] = 0; + newPathLen += MAX_PATH; + } while (newPathLen < INT_MAX-2 && _tcslen(itemex.pszText) >= pathBuffer.size() - 1); + + auto newPath8 = unique_TCHARToMultiByte(pathBuffer.data()); + packageFile.affectedAssets[i].path = newPath8.get(); + } + //Set the replacers. + for (size_t i = 0; i < this->visibleFiles.size(); i++) + { + InstallerPackageAssetsDesc &packageFileDesc = packageFile.affectedAssets[i]; + VisibleFileEntry &visibleFileDesc = this->visibleFiles[i]; + + packageFileDesc.replacers.reserve(visibleFileDesc.replacers.size() + visibleFileDesc.subFiles.size()); + for (size_t i = 0; i < visibleFileDesc.replacers.size(); ++i) + packageFileDesc.replacers.push_back(visibleFileDesc.replacers[i].pReplacer); + + switch (visibleFileDesc.fileType) + { + case FileContext_Assets: + packageFileDesc.type = InstallerPackageAssetsType::Assets; + assert(visibleFileDesc.subFiles.empty()); + break; + case FileContext_Bundle: + packageFileDesc.type = InstallerPackageAssetsType::Bundle; + for (size_t i = 0; i < visibleFileDesc.subFiles.size(); ++i) + packageFileDesc.replacers.push_back(visibleFileDesc.subFiles[i].produceBundleReplacer()); + break; + case FileContext_Resources: + packageFileDesc.type = InstallerPackageAssetsType::Resources; + assert(visibleFileDesc.subFiles.empty()); + assert(packageFileDesc.replacers.size() == 1); + break; + default: + assert(false); + } + } + //Free invalid file entries. + for (size_t _i = packageFile.affectedAssets.size(); _i > 0; --_i) + { + size_t i = _i - 1; + if (packageFile.affectedAssets[i].path.empty()) + packageFile.affectedAssets.erase(packageFile.affectedAssets.begin() + i); + } + + if (this->saveType == ModDataSaveType_Installer) + { + return GenerateInstaller(hDlg, packageFile, filePath, this->iconData); + } + else + { + bool success = false; + IAssetsWriter *pOutputWriter = Create_AssetsWriterToFile(filePath.c_str(), true, true, RWOpenFlags_Immediately); + + if (pOutputWriter) + { + QWORD filePos = 0; + if (!packageFile.Write(filePos, pOutputWriter)) + MessageBox(hDlg, TEXT("An error occured while writing the package file!"), TEXT("Asset Bundle Extractor"), MB_ICONERROR); + else + success = true; + Free_AssetsWriter(pOutputWriter); + } + else + MessageBox(hDlg, TEXT("Unable to open the output file!"), TEXT("Asset Bundle Extractor"), MB_ICONERROR); + return success; + } +} + +static void FreePostDialogProc(HWND hDlg) +{ + HWND hIIcon = GetDlgItem(hDlg, IDC_IICON); + if (hIIcon) + { + HICON hOldIcon = (HICON)SendMessage(hIIcon, STM_GETIMAGE, IMAGE_ICON, NULL); + if (hOldIcon) + DestroyIcon(hOldIcon); + } +} + +void Win32ModInstallerEditor::AskSave() +{ + if ((this->visibleFiles.empty() || !this->changedFlag) + && Edit_GetTextLength(GetDlgItem(hDlg, IDC_EMODNAME)) == 0 + && Edit_GetTextLength(GetDlgItem(hDlg, IDC_EAUTHORS)) == 0 + && Edit_GetTextLength(GetDlgItem(hDlg, IDC_EDESCRIPTION)) == 0) + { + EndDialog(hDlg, 0); + FreePostDialogProc(hDlg); + } + else if (this->saveType == ModDataSaveType_PackageFile //No question for saving to a package file needed. + || this->visibleFiles.empty() || !this->changedFlag) //Text fields changed. + { + switch (MessageBox(hDlg, + TEXT("Are you sure you want to discard the progress?"), + TEXT("Asset Bundle Extractor"), + MB_YESNO)) + { + case IDNO: + break; + case IDYES: + default: + EndDialog(hDlg, 0); + FreePostDialogProc(hDlg); + break; + } + } + else + { + switch (MessageBox(hDlg, + TEXT("Are you sure you want to discard the progress?\n")\ + TEXT("To use the changes inside UABE, you need to save them to an installer package first. Press \"No\" to do that."), + TEXT("Asset Bundle Extractor"), + MB_YESNOCANCEL)) + { + case IDNO: + this->saveType = ModDataSaveType_PackageFile; + if (!this->SaveChanges()) + break; + case IDYES: + EndDialog(hDlg, 0); + FreePostDialogProc(hDlg); + case IDCANCEL: + default: + break; + } + } +} + +INT_PTR CALLBACK Win32ModInstallerEditor::DialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + Win32ModInstallerEditor *pThis = reinterpret_cast(GetWindowLongPtr(hDlg, GWLP_USERDATA)); + int wmId, wmEvent; + UNREFERENCED_PARAMETER(lParam); + switch (message) + { + case WM_INITDIALOG: + { + SetWindowLongPtr(hDlg, GWLP_USERDATA, lParam); + pThis = reinterpret_cast(lParam); + pThis->hDlg = hDlg; + pThis->hTreeModifications = GetDlgItem(hDlg, IDC_TREECHANGES); + if (pThis->hTreeModifications == NULL) + { + EndDialog(hDlg, (INT_PTR)0); + break; + } + pThis->UpdateModsTree(); + SetWindowPos(hDlg, NULL, 0, 0, 510, 390, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); + pThis->iconData.clear(); + switch (pThis->saveType) + { + case ModDataSaveType_PackageFile: + ShowWindow(GetDlgItem(hDlg, IDC_SICON), SW_HIDE); + ShowWindow(GetDlgItem(hDlg, IDC_IICON), SW_HIDE); + ShowWindow(GetDlgItem(hDlg, IDC_BTNLOADICON), SW_HIDE); + SetWindowText(hDlg, TEXT("Create an installer package")); + break; + case ModDataSaveType_Installer: + { + HMODULE hModule = GetModuleHandle(NULL); + HRSRC hResource = FindResource(hModule, MAKEINTRESOURCE(IDI_ASSETBUNDLEEXTRACTOR), MAKEINTRESOURCE(3)); + if (hResource) + { + DWORD resourceSize = SizeofResource(hModule, hResource); + HGLOBAL resourceHandle = LoadResource(hModule, hResource); + if (resourceHandle) + { + PVOID pResource = LockResource(resourceHandle); + uint8_t *pBuf = reinterpret_cast(pResource); + size_t size = resourceSize; + pThis->iconData.assign(pBuf, pBuf + size); + } + } + HICON hIcon = (HICON)LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ASSETBUNDLEEXTRACTOR)); + HWND hIIcon = GetDlgItem(hDlg, IDC_IICON); + SendMessage(hIIcon, STM_SETIMAGE, IMAGE_ICON, (LPARAM)hIcon); + } + break; + } + } + return (INT_PTR)TRUE; + case WM_SIZE: + { + int width = LOWORD(lParam); int height = HIWORD(lParam); + SetWindowPos(GetDlgItem(hDlg, IDC_EMODNAME), NULL, 90, 23, width / 2 - 95, 23, SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(GetDlgItem(hDlg, IDC_EAUTHORS), NULL, 90, 54, width / 2 - 95, 23, SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(GetDlgItem(hDlg, IDC_EDESCRIPTION), NULL, 90, 93, width / 2 - 95, height - 146, SWP_NOZORDER | SWP_NOACTIVATE); + //if (userData->saveType != ModDataSaveType_Installer) + { + SetWindowPos(GetDlgItem(hDlg, IDC_IICON), NULL, 90, height - 50, 32, 32, SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(GetDlgItem(hDlg, IDC_BTNLOADICON), NULL, 130, height - 45, (width / 2) - 135, 21, SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(GetDlgItem(hDlg, IDC_SICON), NULL, 9, height - 55, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); + } + SetWindowPos(GetDlgItem(hDlg, IDC_SBASEFOLDER), NULL, width / 2 + 5, 23, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(GetDlgItem(hDlg, IDC_EBASEFOLDER), NULL, width / 2 + 5, 54, (int)((float)(width / 2 - 22) * 0.64), 23, SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(GetDlgItem(hDlg, IDC_BTNBASEFOLDER), NULL, (int)((float)width / 2.0 * 1.64), 55, (int)((float)(width / 2 - 22) * 0.36), 21, SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(GetDlgItem(hDlg, IDC_SCHANGES), NULL, width / 2 + 5, 84, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(GetDlgItem(hDlg, IDC_TREECHANGES), NULL, width / 2 + 5, 102, width / 2 - 13, height - 168, SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(GetDlgItem(hDlg, IDC_BTNIMPORTPAK), NULL, width / 2 + 5, height - 64, (int)(((float)width / 2.0 - 25.0) / 2.0), 21, SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(GetDlgItem(hDlg, IDC_BTNREMCHANGE), NULL, (int)((float)width * 0.75), height - 64, (int)(((float)width / 2.0 - 25.0) / 2.0), 21, SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(GetDlgItem(hDlg, IDOK), NULL, width / 2 + 5, height - 33, (int)(((float)width / 2.0 - 74.0) / 2.0), 21, SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(GetDlgItem(hDlg, IDCANCEL), NULL, (int)((float)width * 0.75 + 24.0), height - 33, (int)(((float)width / 2.0 - 74.0) / 2.0), 21, SWP_NOZORDER | SWP_NOACTIVATE); + } + return (INT_PTR)TRUE; + case WM_CLOSE: + pThis->AskSave(); + return (INT_PTR)TRUE; + case WM_DESTROY: + EndDialog(hDlg, LOWORD(wParam)); + FreePostDialogProc(hDlg); + break; + case WM_COMMAND: + wmId = LOWORD(wParam); + wmEvent = HIWORD(wParam); + switch (wmId) + { + case IDC_EBASEFOLDER: + if (!pThis) + break; + pThis->UpdateDisplayedRelPaths(); + return (INT_PTR)TRUE; + case IDC_BTNBASEFOLDER: + if (!pThis) + break; + { + HWND hEditBaseFolder = GetDlgItem(hDlg, IDC_EBASEFOLDER); + WCHAR *folderPath = NULL; + if (hEditBaseFolder && ShowFolderSelectDialog(hDlg, &folderPath, L"Select a base directory", UABE_FILEDIALOG_FILE_GUID)) + { + SetWindowTextW(hEditBaseFolder, folderPath); + FreeCOMFilePathBuf(&folderPath); + } + return (INT_PTR)TRUE; + } + case IDC_BTNLOADICON: + if (!pThis) + break; + pThis->SelectAndLoadIcon(); + return (INT_PTR)TRUE; + case IDC_BTNIMPORTPAK: + if (!pThis) + break; + pThis->SelectAndImportPackage(); + return (INT_PTR)TRUE; + case IDC_BTNREMCHANGE: + if (!pThis) + break; + { + HWND hTreeChanges = GetDlgItem(hDlg, IDC_TREECHANGES); + if (!hTreeChanges) + break; + HTREEITEM selection = TreeView_GetSelection(hTreeChanges); + pThis->RemoveChange(selection); + return (INT_PTR)TRUE; + } + case IDOK: + if (!pThis) + break; + if (pThis->SaveChanges()) + { + EndDialog(hDlg, LOWORD(wParam)); + FreePostDialogProc(hDlg); + } + return (INT_PTR)TRUE; + case IDCANCEL: + if (!pThis) + { + EndDialog(hDlg, LOWORD(wParam)); + FreePostDialogProc(hDlg); + break; + } + pThis->AskSave(); + return (INT_PTR)TRUE; + } + break; + } + return (INT_PTR)FALSE; +} + +Win32ModInstallerEditor::Win32ModInstallerEditor(Win32AppContext &appContext, + std::vector> &contextInfo, + EModDataSaveType saveType) + : Win32ModTreeDialogBase(appContext), saveType(saveType), changedFlag(false) +{ + for (size_t i = 0; i < contextInfo.size(); ++i) + { + if (contextInfo[i] != nullptr && contextInfo[i]->getFileContext() != nullptr && contextInfo[i]->getParentFileID() == 0 + && contextInfo[i]->hasAnyChanges(appContext)) + this->visibleFiles.push_back(VisibleFileEntry(appContext, contextInfo[i])); + } + + //Copy relevant Unity basic types, assuming that we only have one class database across all files. + //-> Assumption is not always correct. + struct { + void operator()(VisibleFileEntry &file, std::unordered_set &classIDs, std::shared_ptr &pFoundClassDatabase) + { + if (file.fileType == FileContext_Assets && file.pContextInfo != nullptr) + { + if (!pFoundClassDatabase) + pFoundClassDatabase = reinterpret_cast(file.pContextInfo.get())->GetClassDatabase(); + for (size_t i = 0; i < file.replacers.size(); ++i) + { + auto *pReplacer = reinterpret_cast(file.replacers[i].pReplacer.get()); + std::shared_ptr typeDbFile; ClassDatabaseType *pType; + if (pReplacer != nullptr + && pReplacer->GetType() == AssetsReplacement_AddOrModify) + { + AssetsEntryReplacer* pEntryReplacer = reinterpret_cast(pReplacer); + if (pEntryReplacer->GetClassID() >= 0 && !pEntryReplacer->GetTypeInfo(typeDbFile, pType)) + classIDs.insert(pEntryReplacer->GetClassID()); + } + } + } + for (size_t i = 0; i < file.subFiles.size(); ++i) + (*this)(file.subFiles[i], classIDs, pFoundClassDatabase); + } + } enumerateClassIDs; + std::shared_ptr pFoundClassDatabase; + std::unordered_set classIDs; + for (size_t i = 0; i < visibleFiles.size(); ++i) + enumerateClassIDs(visibleFiles[i], classIDs, pFoundClassDatabase); + if (pFoundClassDatabase != nullptr) + { + for (size_t i = 0; i < pFoundClassDatabase->classes.size(); i++) + { + if (classIDs.find(pFoundClassDatabase->classes[i].classId) != classIDs.end()) + { + typesToExport.InsertFrom(pFoundClassDatabase.get(), &pFoundClassDatabase->classes[i]); + } + } + } +} + +void Win32ModInstallerEditor::open() +{ + DialogBoxParam(appContext.getMainWindow().getHInstance(), + MAKEINTRESOURCE(IDD_MAKEINSTALLER), appContext.getMainWindow().getWindow(), + DialogProc, + (LPARAM)this); +} + diff --git a/UABE_Win32/ModInstallerEditor2.h b/UABE_Win32/ModInstallerEditor2.h new file mode 100644 index 0000000..2f78715 --- /dev/null +++ b/UABE_Win32/ModInstallerEditor2.h @@ -0,0 +1,41 @@ +#pragma once +#include "Win32AppContext.h" +#include "../ModInstaller/InstallerDataFormat.h" +#include "FileModTree.h" +#include "Win32ModTreeDialogBase.h" + +enum EModDataSaveType +{ + ModDataSaveType_Installer, + ModDataSaveType_PackageFile +}; + +class Win32ModInstallerEditor : public Win32ModTreeDialogBase +{ + EModDataSaveType saveType; + bool changedFlag; +protected: + std::vector iconData; + + ClassDatabaseFile typesToExport; + +private: + static INT_PTR CALLBACK DialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); + void UpdateDisplayedRelPaths(); + void SelectAndLoadIcon(); + void SelectAndImportPackage(); + void RemoveChange(HTREEITEM treeEntry); + bool SaveChanges(); + void AskSave(); + + bool removeChangesBy(VisibleFileEntry &file, HTREEITEM treeEntry); + + void MergeInstallerData(InstallerPackageFile &newFile); +public: + Win32ModInstallerEditor(Win32AppContext &appContext, + std::vector> &contextInfo, + EModDataSaveType saveType); + + void open(); + +}; diff --git a/UABE_Win32/ModPackageLoader.cpp b/UABE_Win32/ModPackageLoader.cpp new file mode 100644 index 0000000..09ed161 --- /dev/null +++ b/UABE_Win32/ModPackageLoader.cpp @@ -0,0 +1,398 @@ +#include "stdafx.h" +#include "ModPackageLoader.h" +#include "FileDialog.h" +#include "resource.h" +#include "../libStringConverter/convert.h" +#include "../ModInstaller/ModInstaller.h" +#include +#include + +Win32ModPackageLoader::Win32ModPackageLoader(Win32AppContext &appContext) + : Win32ModTreeDialogBase(appContext) +{} + +void Win32ModPackageLoader::open() +{ + WCHAR *packageFilePath = NULL; + if (FAILED(ShowFileOpenDialog(appContext.getMainWindow().getWindow(), &packageFilePath, + L"*.emip|UABE Mod Installer Package:*.exe|UABE Installer:", + nullptr, nullptr, nullptr, UABE_FILEDIALOG_FILE_GUID))) + return; + std::shared_ptr pPackageFileReader(Create_AssetsReaderFromFile(packageFilePath, true, RWOpenFlags_Immediately), Free_AssetsReader); + FreeCOMFilePathBuf(&packageFilePath); + if (!pPackageFileReader) + { + MessageBox(appContext.getMainWindow().getWindow(), TEXT("Unable to open the package file!"), TEXT("ERROR"), 16); + return; + } + bool closeReader = true; + InstallerPackageFile packageFile; + QWORD filePos = 0; + bool success = packageFile.Read(filePos, pPackageFileReader/*, true*/); + if (!success) + { + size_t overlayOffset = GetPEOverlayOffset(pPackageFileReader.get()); + if (overlayOffset != 0) + { + filePos = overlayOffset; + success = packageFile.Read(filePos, pPackageFileReader); + } + } + if (!success) + { + MessageBox(appContext.getMainWindow().getWindow(), TEXT("Unable to understand the package file!\n")\ + TEXT("Make sure the selected file actually is a valid package file."), TEXT("ERROR"), 16); + return; + } + + visibleFiles.clear(); + visibleFiles.reserve(packageFile.affectedAssets.size()); + for (size_t i = 0; i < packageFile.affectedAssets.size(); ++i) + { + visibleFiles.push_back(VisibleFileEntry(this->appContext, packageFile.affectedAssets[i])); + } + DialogBoxParam(appContext.getMainWindow().getHInstance(), + MAKEINTRESOURCE(IDD_LOADFROMPACKAGE), + appContext.getMainWindow().getWindow(), + DialogProc, (LPARAM)this); + + for (size_t i = 0; i < visibleFiles.size(); ++i) + { + bool basedOnExistingFile = true; + if (visibleFiles[i].fileType == FileContext_Resources) + { + //Allow new resource files to be created. + //-> Check if the resource replacer is based on an existing file. + // If not, allow CreateFileOpenTask to open a 'zero length' in-memory reader + // instead of the actual file. + assert(visibleFiles[i].replacers.size() == 1); + if (visibleFiles[i].replacers.size() == 1 + && !reinterpret_cast(visibleFiles[i].replacers[0].pReplacer.get())->RequiresEntryReader()) + basedOnExistingFile = false; + } + std::shared_ptr pTask = appContext.CreateFileOpenTask(visibleFiles[i].pathOrName, basedOnExistingFile); + if (pTask != nullptr) + { + appContext.OpenTask_SetModifications(pTask.get(), std::unique_ptr(new VisibleFileEntry(std::move(visibleFiles[i])))); + appContext.taskManager.enqueue(pTask); + } + } + visibleFiles.clear(); +} + +void Win32ModPackageLoader::FillModifiedFilesTree() +{ + UpdateModsTree(false); + + TVITEM item; + item.hItem = bundleBaseEntry; + item.mask = TVIF_HANDLE | TVIF_STATE; + item.stateMask = TVIS_STATEIMAGEMASK; + item.state = 0 << 12; + TreeView_SetItem(hTreeModifications, &item); + + item.hItem = assetsBaseEntry; + item.mask = TVIF_HANDLE | TVIF_STATE; + item.stateMask = TVIS_STATEIMAGEMASK; + item.state = 0 << 12; + TreeView_SetItem(hTreeModifications, &item); +} + +void Win32ModPackageLoader::OnChangeBaseFolderEdit() +{ + HWND hEditBaseFolder = GetDlgItem(hDlg, IDC_EBASEFOLDER); + if (!hEditBaseFolder) + return; + size_t textLen = (size_t)Edit_GetTextLength(hEditBaseFolder); + std::vector newBasePath(textLen+1); + Edit_GetText(hEditBaseFolder, newBasePath.data(), (int)(textLen + 1)); + newBasePath[textLen] = 0; + + size_t wBasePathLen = wcslen(newBasePath.data()); + for (size_t i = 0; i < this->visibleFiles.size(); i++) + { + size_t wPathLen = 0; + auto wPath = unique_MultiByteToWide(this->visibleFiles[i].pathOrName.c_str(), wPathLen); + std::vector combinedPathBuf(std::max(wBasePathLen + wPathLen + 16, MAX_PATH+1)); + //TODO: Use a long path compatible function. + bool changePath = PathCombine(combinedPathBuf.data(), newBasePath.data(), wPath.get()) != NULL; + if (changePath) + { + TVITEMEX item = {}; + item.hItem = (HTREEITEM)this->visibleFiles[i].treeViewEntry; + item.mask = TVIF_HANDLE | TVIF_TEXT; + item.pszText = combinedPathBuf.data(); + TreeView_SetItem(hTreeModifications, &item); + } + } +} + +void Win32ModPackageLoader::OnCheck(HTREEITEM item, bool isChecked) +{ + struct _Lambda_RecursiveCheck{ + HWND hTreeModifications; + bool isChecked; + _Lambda_RecursiveCheck(HWND hTreeModifications, bool isChecked) + : hTreeModifications(hTreeModifications), isChecked(isChecked) + {} + void operator()(VisibleFileEntry &file) + { + if (!file.treeViewEntry) + return; + TreeView_SetCheckState(hTreeModifications, (HTREEITEM)file.treeViewEntry, isChecked); + for (size_t i = 0; i < file.subFiles.size(); ++i) + (*this)(file.subFiles[i]); + } + bool operator()(HTREEITEM base, VisibleFileEntry &file) + { + if (!file.treeViewEntry) + return false; + if (file.treeViewEntry == (uintptr_t)base) + { + for (size_t i = 0; i < file.subFiles.size(); ++i) + (*this)(file.subFiles[i]); + return true; + } + else + { + for (size_t i = 0; i < file.subFiles.size(); ++i) + { + if ((*this)(base, file.subFiles[i])) + return true; + } + } + return false; + } + } recursiveCheck(hTreeModifications, isChecked); + if (item == assetsBaseEntry || item == NULL) + { + for (size_t i = 0; i < visibleFiles.size(); i++) + { + if (visibleFiles[i].fileType == FileContext_Assets) + recursiveCheck(visibleFiles[i]); + } + } + if (item == bundleBaseEntry || item == NULL) + { + for (size_t i = 0; i < visibleFiles.size(); i++) + { + if (visibleFiles[i].fileType == FileContext_Bundle) + recursiveCheck(visibleFiles[i]); + } + } + else + { + for (size_t i = 0; i < visibleFiles.size(); i++) + { + if (recursiveCheck(item, visibleFiles[i])) + break; + } + } +} +void Win32ModPackageLoader::RemoveUnchecked() +{ + struct _Lambda_RecursiveRemoveIfUnchecked { + HWND hTreeModifications; + _Lambda_RecursiveRemoveIfUnchecked(HWND hTreeModifications) + : hTreeModifications(hTreeModifications) + {} + bool operator()(VisibleFileEntry &file) + { + if (file.treeViewEntry == NULL || !TreeView_GetCheckState(hTreeModifications, file.treeViewEntry)) + return true; + for (size_t _i = file.subFiles.size(); _i > 0; --_i) + { + size_t i = _i - 1; + if ((*this)(file.subFiles[i])) + { + if (file.subFiles[i].treeViewEntry != NULL) + TreeView_DeleteItem(hTreeModifications, file.subFiles[i].treeViewEntry); + file.subFiles.erase(file.subFiles.begin() + i); + } + } + return false; + } + } recursiveRemoveIfUnchecked(hTreeModifications); + for (size_t _i = visibleFiles.size(); _i > 0; --_i) + { + size_t i = _i - 1; + if (recursiveRemoveIfUnchecked(visibleFiles[i])) + { + if (visibleFiles[i].treeViewEntry != NULL) + TreeView_DeleteItem(hTreeModifications, visibleFiles[i].treeViewEntry); + visibleFiles.erase(visibleFiles.begin() + i); + } + } +} +void Win32ModPackageLoader::OnClose() +{ + if (this->hTreeModifications == NULL) + return; + this->RemoveUnchecked(); + + HWND hEditBaseFolder = GetDlgItem(hDlg, IDC_EBASEFOLDER); + size_t baseDirLen = (size_t)Edit_GetTextLength(hEditBaseFolder); + std::vector tBaseDir(baseDirLen + 1); + Edit_GetText(hEditBaseFolder, tBaseDir.data(), (int)(baseDirLen + 1)); + tBaseDir[baseDirLen] = 0; + + std::vector pathBuffer; + for (size_t i = 0; i < this->visibleFiles.size(); i++) + { + TVITEM item; + item.mask = TVIF_HANDLE | TVIF_STATE; + item.hItem = (HTREEITEM)this->visibleFiles[i].treeViewEntry; + item.state = 0; + + size_t newPathLen = baseDirLen + this->visibleFiles[i].pathOrName.size() + 1; + if (newPathLen <= MAX_PATH) newPathLen = MAX_PATH + 1; + + //Retrieve the absolute paths from the TreeView. + TVITEMEX itemex; + do { + if (newPathLen >= INT_MAX-1) newPathLen = INT_MAX-2; + if (pathBuffer.size() < newPathLen) pathBuffer.resize(newPathLen); + itemex.hItem = (HTREEITEM)this->visibleFiles[i].treeViewEntry; + itemex.mask = TVIF_HANDLE | TVIF_TEXT; + itemex.pszText = pathBuffer.data(); + itemex.cchTextMax = (int)pathBuffer.size(); + TreeView_GetItem(hTreeModifications, &itemex); + pathBuffer[pathBuffer.size() - 1] = 0; + newPathLen += MAX_PATH; + } while (newPathLen < INT_MAX-2 && _tcslen(itemex.pszText) >= pathBuffer.size() - 1); + + if (pathBuffer[0] != 0) + { + //Put the absolute path in the file entries. + auto newPath8 = unique_TCHARToMultiByte(pathBuffer.data()); + this->visibleFiles[i].pathOrName = newPath8.get(); + } + } + + TreeView_DeleteAllItems(hTreeModifications); + HIMAGELIST hCBImageList = TreeView_GetImageList(hTreeModifications, TVSIL_STATE); + if (hCBImageList) + ImageList_Destroy(hCBImageList); +} + +INT_PTR CALLBACK Win32ModPackageLoader::DialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + Win32ModPackageLoader *pThis = reinterpret_cast(GetWindowLongPtr(hDlg, GWLP_USERDATA)); + int wmId, wmEvent; + UNREFERENCED_PARAMETER(lParam); + switch (message) + { + case WM_INITDIALOG: + { + SetWindowLongPtr(hDlg, GWLP_USERDATA, lParam); + pThis = reinterpret_cast(lParam); + pThis->hDlg = hDlg; + pThis->hTreeModifications = GetDlgItem(hDlg, IDC_TREECHANGES); + + DWORD dwStyle = GetWindowLong(pThis->hTreeModifications, GWL_STYLE); + dwStyle |= TVS_CHECKBOXES; + SetWindowLong(pThis->hTreeModifications, GWL_STYLE, dwStyle); + + ShowWindow(pThis->hTreeModifications, SW_HIDE); + pThis->FillModifiedFilesTree(); + ShowWindow(pThis->hTreeModifications, SW_SHOW); + SetWindowPos(hDlg, NULL, 0, 0, 255, 360, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); + } + return (INT_PTR)TRUE; + //Also see http://stackoverflow.com/questions/22441747/creating-treeview-with-nodes-and-checkboxes + case WM_NOTIFY: + if (pThis != nullptr) + { + NMHDR *pNotifHdr = (NMHDR*)lParam; + + if (pNotifHdr->idFrom != IDC_TREECHANGES) + break; + switch (pNotifHdr->code) + { + case TVN_KEYDOWN: + if (((NMTVKEYDOWN*)lParam)->wVKey == VK_SPACE) + { + HTREEITEM checkedItem = TreeView_GetSelection(pNotifHdr->hwndFrom); + bool isChecked = TreeView_GetCheckState(pNotifHdr->hwndFrom, checkedItem) == 0; //will be checked, isn't at the moment + pThis->OnCheck(checkedItem, isChecked); + return (INT_PTR)TRUE; + } + break; + case NM_CLICK: + { + TVHITTESTINFO hitTest = {}; + DWORD posval = GetMessagePos(); + hitTest.pt.x = GET_X_LPARAM(posval); + hitTest.pt.y = GET_Y_LPARAM(posval); + MapWindowPoints(NULL, pNotifHdr->hwndFrom, &hitTest.pt, 1); + TreeView_HitTest(pNotifHdr->hwndFrom, &hitTest); + if (hitTest.flags & TVHT_ONITEMSTATEICON) + { + bool isChecked = TreeView_GetCheckState(pNotifHdr->hwndFrom, hitTest.hItem) == 0; //will be checked, isn't at the moment + pThis->OnCheck(hitTest.hItem, isChecked); + return (INT_PTR)TRUE; + } + } + break; + default: + break; + } + break; + } + return (INT_PTR)FALSE; + case WM_SIZE: + { + int width = LOWORD(lParam); int height = HIWORD(lParam); + SetWindowPos(GetDlgItem(hDlg, IDC_SDESCRIPTION), NULL, 9, 11, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(GetDlgItem(hDlg, IDC_TREECHANGES), NULL, 9, 26, width - 18, height - 108, SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(GetDlgItem(hDlg, IDC_SBASEFOLDER), NULL, 9, height - 80, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(GetDlgItem(hDlg, IDC_EBASEFOLDER), NULL, 9, height - 63, (int)((float)(width - 23) * 0.69F), 23, SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(GetDlgItem(hDlg, IDC_BTNBASEFOLDER), NULL, (int)((float)width * 0.69F - 1.87F), height - 62, (int)((float)(width - 23) * 0.31F), 21, SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(GetDlgItem(hDlg, IDOK), NULL, 9, height - 33, width / 3, 21, SWP_NOZORDER | SWP_NOACTIVATE); + SetWindowPos(GetDlgItem(hDlg, IDCANCEL), NULL, width - 9 - width / 3, height - 33, width / 3, 21, SWP_NOZORDER | SWP_NOACTIVATE); + } + return (INT_PTR)TRUE; + case WM_CLOSE: + case WM_DESTROY: + if (pThis) pThis->OnClose(); + EndDialog(hDlg, LOWORD(wParam)); + return (INT_PTR)TRUE; + case WM_COMMAND: + wmId = LOWORD(wParam); + wmEvent = HIWORD(wParam); + switch (wmId) + { + case IDC_EBASEFOLDER: + if (!pThis) + break; + pThis->OnChangeBaseFolderEdit(); + return (INT_PTR)TRUE; + case IDC_BTNBASEFOLDER: + if (!pThis) + break; + { + HWND hEditBaseFolder = GetDlgItem(hDlg, IDC_EBASEFOLDER); + WCHAR *folderPath = NULL; + if (hEditBaseFolder && ShowFolderSelectDialog(hDlg, &folderPath, L"Select a base directory", UABE_FILEDIALOG_FILE_GUID)) + { + Edit_SetText(hEditBaseFolder, folderPath); + FreeCOMFilePathBuf(&folderPath); + } + return (INT_PTR)TRUE; + } + case IDOK: + if (pThis) pThis->OnClose(); + EndDialog(hDlg, (INT_PTR)0); + return (INT_PTR)TRUE; + case IDCANCEL: + { + if (pThis) + pThis->OnCheck(NULL, false); + if (pThis) pThis->OnClose(); + EndDialog(hDlg, (INT_PTR)0); + return (INT_PTR)TRUE; + } + } + } + return (INT_PTR)FALSE; +} diff --git a/UABE_Win32/ModPackageLoader.h b/UABE_Win32/ModPackageLoader.h new file mode 100644 index 0000000..c357e41 --- /dev/null +++ b/UABE_Win32/ModPackageLoader.h @@ -0,0 +1,21 @@ +#pragma once +#include "Win32AppContext.h" +#include "../ModInstaller/InstallerDataFormat.h" +#include "FileModTree.h" +#include "Win32ModTreeDialogBase.h" + +class Win32ModPackageLoader : public Win32ModTreeDialogBase +{ +protected: +private: + static INT_PTR CALLBACK DialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); + void FillModifiedFilesTree(); + void OnCheck(HTREEITEM item, bool isChecked); + void RemoveUnchecked(); + void OnClose(); + void OnChangeBaseFolderEdit(); +public: + Win32ModPackageLoader(Win32AppContext &appContext); + + void open(); +}; diff --git a/UABE_Win32/MonoBehaviourManager.cpp b/UABE_Win32/MonoBehaviourManager.cpp new file mode 100644 index 0000000..ca85207 --- /dev/null +++ b/UABE_Win32/MonoBehaviourManager.cpp @@ -0,0 +1,613 @@ +#include "stdafx.h" +#include "MonoBehaviourManager.h" +#include "Win32AppContext.h" +#include "FileDialog.h" +#include "../AssetsTools/ClassDatabaseFile.h" +#include "../AssetsTools/AssetsFileTable.h" +#include "../AssetsTools/EngineVersion.h" +#include "../libStringConverter/convert.h" +#include +#include "resource.h" +#include + +bool TryGetAssemblyFilePath(Win32AppContext &appContext, AssetsFileContextInfo &assetsFileInfo, const char *assemblyName, WCHAR *&path, bool allowUserDialog); +void ShowMonoBehaviourExportErrors(HINSTANCE hInstance, HWND hParent, std::vector &errorBuffer); + +static void AddAssemblyName(Win32AppContext &appContext, unsigned int assetsFileId, const char *scriptAssemblyName, std::vector &scannedAssemblies, std::vector> &assemblyNames) +{ + bool exists = false; + for (size_t l = 0; l < scannedAssemblies.size(); l++) + { + if (!strcmp(scannedAssemblies[l], scriptAssemblyName)) + { + exists = true; + break; + } + } + if (!exists) + { + size_t curNameLen = strlen(scriptAssemblyName); + for (size_t i = 0; i < curNameLen; i++) + { + //Prevent format string injection. + if (scriptAssemblyName[i] == ':' || scriptAssemblyName[i] == '|') + return; + } + char *curScannedName = new char[curNameLen+1]; + memcpy(curScannedName, scriptAssemblyName, curNameLen+1); + scannedAssemblies.push_back(curScannedName); + WCHAR *pName; + std::shared_ptr pAssetsInfo = std::dynamic_pointer_cast(appContext.getContextInfo(assetsFileId)); + if (pAssetsInfo && TryGetAssemblyFilePath(appContext, *pAssetsInfo, scriptAssemblyName, pName, true)) + { + EngineVersion version; + if (pAssetsInfo->getAssetsFileContext() && pAssetsInfo->getAssetsFileContext()->getAssetsFile()) + version = EngineVersion::parse(pAssetsInfo->getAssetsFileContext()->getAssetsFile()->typeTree.unityVersion); + assemblyNames.push_back({ version, pName }); + } + } +} + +bool GetAllScriptInformation(Win32AppContext &appContext, std::vector> &assetsInfo) +{ + std::vector assetsFilesWithMonoScript; + std::vector> assemblyNames; + std::vector scannedAssemblies; + bool useLongPathID = false; + for (size_t i = 0; i < assetsInfo.size(); i++) + { + AssetsFileContextInfo *pAssetsInfo = assetsInfo[i].get(); + bool isBigEndian = false; + if (!pAssetsInfo || !pAssetsInfo->getAssetsFileContext() || !pAssetsInfo->getAssetsFileContext()->getAssetsFile() + || !pAssetsInfo->getEndianness(isBigEndian)) + continue; + + useLongPathID = useLongPathID || (pAssetsInfo->getAssetsFileContext()->getAssetsFile()->header.format >= 0x0E); + int scriptClassId = pAssetsInfo->GetClassByName("MonoScript"); + int managerClassId = pAssetsInfo->GetClassByName("MonoManager"); + + AssetTypeTemplateField scriptBase; + if (scriptClassId >= 0) + pAssetsInfo->MakeTemplateField(&scriptBase, appContext, scriptClassId); + AssetTypeTemplateField managerBase; + if (managerClassId >= 0) + pAssetsInfo->MakeTemplateField(&managerBase, appContext, managerClassId); + + + if (scriptBase.children.size() > 0) + { + bool fileHasMonoScript = false; + AssetIdentifier identifier; + for (AssetIterator iter(pAssetsInfo); !iter.isEnd(); ++iter) + { + iter.get(identifier); + if (identifier.resolve(appContext) && identifier.getClassID() == scriptClassId) + { + IAssetsReader_ptr pReader = identifier.makeReader(); + if (!pReader) continue; + fileHasMonoScript = true; + AssetTypeTemplateField *pTemplate = &scriptBase; + AssetTypeInstance scriptInstance = AssetTypeInstance(1, &pTemplate, + identifier.getDataSize(), pReader.get(), isBigEndian); + + AssetTypeValueField *pScriptBase = scriptInstance.GetBaseField(); + AssetTypeValueField *pScriptAssemblyNameField; const char *scriptAssemblyName; + if ((pScriptBase != NULL) && + (pScriptAssemblyNameField = pScriptBase->Get("m_AssemblyName"))->GetValue() + && (scriptAssemblyName = pScriptAssemblyNameField->GetValue()->AsString())) + { + AddAssemblyName(appContext, pAssetsInfo->getFileID(), scriptAssemblyName, scannedAssemblies, assemblyNames); + } + } + } + if (fileHasMonoScript) + assetsFilesWithMonoScript.push_back(pAssetsInfo); + } + if (managerBase.children.size() > 0) + { + AssetIdentifier identifier; + for (AssetIterator iter(pAssetsInfo); !iter.isEnd(); ++iter) + { + iter.get(identifier); + if (identifier.resolve(appContext) && identifier.getClassID() == managerClassId) + { + IAssetsReader_ptr pReader = identifier.makeReader(); + if (!pReader) continue; + AssetTypeTemplateField *pTemplate = &managerBase; + AssetTypeInstance managerInstance = AssetTypeInstance(1, &pTemplate, + identifier.getDataSize(), pReader.get(), isBigEndian); + + AssetTypeValueField *pScriptBase = managerInstance.GetBaseField(); + AssetTypeValueField *pAssemblyNamesField; + if ((pScriptBase != NULL) && + (pAssemblyNamesField = pScriptBase->Get("m_AssemblyNames")->Get("Array"))->GetValue() && + pAssemblyNamesField->GetValue()->AsArray()) + { + for (unsigned int l = 0; l < pAssemblyNamesField->GetChildrenCount(); l++) + { + AssetTypeValue *pNameValue = pAssemblyNamesField->Get(l)->GetValue(); + char *scriptAssemblyName; + if (pNameValue && (scriptAssemblyName = pNameValue->AsString())) + { + AddAssemblyName(appContext, pAssetsInfo->getFileID(), scriptAssemblyName, scannedAssemblies, assemblyNames); + } + } + } + } + } + } + } + for (size_t i = 0; i < scannedAssemblies.size(); i++) + { + delete[] scannedAssemblies[i]; + } + + if (assemblyNames.size() > 0) + { + std::shared_ptr pClassDb = CreateMonoBehaviourClassDb(appContext, assemblyNames, useLongPathID, true); + for (size_t i = 0; i < assemblyNames.size(); i++) + { + delete[] assemblyNames[i].second; + } + if (pClassDb != nullptr) + { + if (assetsFilesWithMonoScript.empty()) + { + for (size_t i = 0; i < assetsInfo.size(); ++i) + assetsInfo[i]->appendScriptDatabase(pClassDb); + } + else + { + for (size_t i = 0; i < assetsFilesWithMonoScript.size(); ++i) + assetsFilesWithMonoScript[i]->appendScriptDatabase(pClassDb); + } + return true; + } + } + else + MessageBox(appContext.getMainWindow().getWindow(), TEXT("Unable to find any script assemblies!"), TEXT("Error"), 0); + return false; +} + +bool TryGetAssemblyFilePath(Win32AppContext &appContext, AssetsFileContextInfo &assetsFileInfo, const char *assemblyName, WCHAR *&path, bool allowUserDialog) +{ + if (!assetsFileInfo.getFileContext() + || !assetsFileInfo.getAssetsFileContext() + || !assetsFileInfo.getAssetsFileContext()->getAssetsFile()) + return false; + + if (!assemblyName) + return false; + size_t wcAssemblyNameLen; + auto wcAssemblyName = unique_MultiByteToWide(assemblyName, wcAssemblyNameLen); + if (!wcAssemblyName) + return false; + + std::wstring assemblyPath = std::wstring(); + std::string assetsBaseFolder = assetsFileInfo.getAssetsFileContext()->getFileDirectoryPath(); + if (!assetsBaseFolder.empty()) + { + size_t wcBaseFolderLen; + auto wcBaseFolder = unique_MultiByteToWide(assetsBaseFolder.c_str(), wcBaseFolderLen); + if (wcBaseFolder) + { + assemblyPath = std::wstring(wcBaseFolder.get()); + if (assemblyPath.size() > 10 && !assemblyPath.compare(assemblyPath.size() - 10, std::string::npos, L"\\Resources")) + assemblyPath += L"\\.."; + assemblyPath += L"\\Managed\\"; + assemblyPath += wcAssemblyName.get(); + } + } + bool foundFile = false; + if (assemblyPath.size() > 0) + { + IAssetsReader *pTempReader = Create_AssetsReaderFromFile(assemblyPath.c_str(), true, RWOpenFlags_Immediately); + if (pTempReader) + { + Free_AssetsReader(pTempReader); + foundFile = true; + } + } + if (!foundFile) + { + if (allowUserDialog) + { + std::vector wcAssemblyNameEscaped; wcAssemblyNameEscaped.reserve(wcAssemblyNameLen); + for (size_t i = 0; i < wcAssemblyNameLen; ++i) + { + if (wcAssemblyName[i] == L'|' || wcAssemblyName[i] == L':' || wcAssemblyName[i] == L'*' || wcAssemblyName[i] == 0) + { + wcAssemblyNameEscaped.clear(); + wcAssemblyNameEscaped.push_back(L'*'); + } + else + wcAssemblyNameEscaped.push_back(wcAssemblyName[i]); + } + WCHAR *filePathBuf = NULL; + HRESULT result = ShowFileOpenDialog( + appContext.getMainWindow().getWindow(), + &filePathBuf, + ( std::wstring(wcAssemblyNameEscaped.begin(), wcAssemblyNameEscaped.end()) + L"|Assembly file:" ).c_str(), + NULL, + wcAssemblyName.get(), + L"Open the Assembly file", + UABE_FILEDIALOG_FILE_GUID); + if (SUCCEEDED(result)) + { + assemblyPath = std::wstring(filePathBuf); + FreeCOMFilePathBuf(&filePathBuf); + foundFile = true; + } + } + } + + path = new WCHAR[assemblyPath.size() + 1]; + memcpy(path, assemblyPath.c_str(), (assemblyPath.size() + 1) * sizeof(WCHAR)); + + return foundFile; +} + +std::shared_ptr CreateMonoBehaviourClassDb(Win32AppContext &appContext, + std::vector> &assemblyFullNames, + bool useLongPathID, bool allowUserDialog) +{ + HANDLE stdoutReadPipe = INVALID_HANDLE_VALUE; HANDLE stdoutWritePipe = INVALID_HANDLE_VALUE; + HANDLE stderrReadPipe = INVALID_HANDLE_VALUE; HANDLE stderrWritePipe = INVALID_HANDLE_VALUE; + HANDLE stdinReadPipe = INVALID_HANDLE_VALUE; HANDLE stdinWritePipe = INVALID_HANDLE_VALUE; + SECURITY_ATTRIBUTES secAttributes = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE}; + if (!CreatePipe(&stdoutReadPipe, &stdoutWritePipe, &secAttributes, 0) || + !CreatePipe(&stderrReadPipe, &stderrWritePipe, &secAttributes, 0) || + !CreatePipe(&stdinReadPipe, &stdinWritePipe, &secAttributes, 0)) + { + if (stdinReadPipe != INVALID_HANDLE_VALUE) + { + CloseHandle(stdinReadPipe); + CloseHandle(stdinWritePipe); + } + if (stderrReadPipe != INVALID_HANDLE_VALUE) + { + CloseHandle(stderrReadPipe); + CloseHandle(stderrWritePipe); + } + if (allowUserDialog) + MessageBox(appContext.getMainWindow().getWindow(), TEXT("Unable to create stdout/stderr pipes for the child process!"), TEXT("Asset Bundle Extractor"), 16); + return std::shared_ptr(); + } + SetHandleInformation(stdoutReadPipe, HANDLE_FLAG_INHERIT, 0); + SetHandleInformation(stderrReadPipe, HANDLE_FLAG_INHERIT, 0); + SetHandleInformation(stdinWritePipe, HANDLE_FLAG_INHERIT, 0); + + std::wstring typeTreeGeneratorApp; + { + std::string baseDir = appContext.getBaseDir(); size_t baseDirWLen = 0; + auto baseDirW = unique_MultiByteToWide(baseDir.c_str(), baseDirWLen); + + typeTreeGeneratorApp = std::wstring(baseDirW.get(), baseDirW.get() + baseDirWLen) + L"\\Tools\\TypeTreeGenerator.exe"; + } + + std::vector commandLine; + { + std::wstring _commandLine = std::wstring(L"TypeTreeGenerator -stdout -stdin -longpathid ") + (useLongPathID ? L"1" : L"0"); + /*for (size_t i = 0; i < assemblyFullNames.size(); i++) + { + _commandLine = _commandLine + L" -f \"" + assemblyFullNames[i] + L"\""; + }*/ + commandLine = std::vector(); + commandLine.resize(_commandLine.size() + 1); //include null-terminator + memcpy(commandLine.data(), _commandLine.c_str(), (_commandLine.size() + 1) * sizeof(wchar_t)); + } + STARTUPINFO startInfo = {}; PROCESS_INFORMATION procInfo = {}; + startInfo.cb = sizeof(STARTUPINFO); + startInfo.dwFlags = STARTF_USESTDHANDLES; + startInfo.hStdError = stderrWritePipe; + startInfo.hStdOutput = stdoutWritePipe; + startInfo.hStdInput = stdinReadPipe; + + std::vector dataBuffer; + std::vector errorBuffer; + if (CreateProcess(typeTreeGeneratorApp.c_str(), commandLine.data(), NULL, NULL, TRUE, 0/*CREATE_NO_WINDOW*/, NULL, NULL, &startInfo, &procInfo)) + { + CloseHandle(procInfo.hThread); + std::wstring commands = std::wstring(); + for (size_t i = 0; i < assemblyFullNames.size(); i++) + { + commands += L"-ver"; + commands.push_back(0); + commands += std::to_wstring(assemblyFullNames[i].first.year); + commands.push_back(0); + commands += std::to_wstring(assemblyFullNames[i].first.release); + commands.push_back(0); + commands += L"-f"; + commands.push_back(0); + commands += assemblyFullNames[i].second; + commands.push_back(0); + } + if (commands.size() == 0) + commands.push_back(0); + commands.push_back(0); + DWORD written = 0; + WriteFile(stdinWritePipe, commands.data(), (DWORD)(commands.size() * sizeof(wchar_t)), &written, NULL); + bool repeatOnce = false; + while (true) + { + bool repeatOnce = false; + if (WaitForSingleObject(procInfo.hProcess, 100) != WAIT_TIMEOUT) + { + repeatOnce = true; + } + DWORD avail = 0; + if (PeekNamedPipe(stdoutReadPipe, NULL, 0, NULL, &avail, NULL) && avail > 0) + { + size_t targetIndex = dataBuffer.size(); + dataBuffer.resize(dataBuffer.size() + avail); + DWORD actuallyRead = 0; + ReadFile(stdoutReadPipe, &dataBuffer[targetIndex], avail, &actuallyRead, NULL); + dataBuffer.resize(targetIndex + actuallyRead); + } + avail = 0; + if (PeekNamedPipe(stderrReadPipe, NULL, 0, NULL, &avail, NULL) && avail > 0) + { + size_t targetIndex = errorBuffer.size(); + errorBuffer.resize(errorBuffer.size() + avail); + DWORD actuallyRead = 0; + ReadFile(stderrReadPipe, &errorBuffer[targetIndex], avail, &actuallyRead, NULL); + errorBuffer.resize(targetIndex + actuallyRead); + } + if (repeatOnce) + break; + } + TerminateProcess(procInfo.hProcess, 0); //Make sure it's really closed. + CloseHandle(procInfo.hProcess); + } + else + { + if (allowUserDialog) + { + char errorCodeBuf[128]; + sprintf_s(errorCodeBuf, "Unable to open Tools\\TypeTreeGenerator.exe (error %d)!", GetLastError()); + MessageBoxA(appContext.getMainWindow().getWindow(), errorCodeBuf, "Error", 16); + } + } + CloseHandle(stdoutReadPipe); + CloseHandle(stdoutWritePipe); + CloseHandle(stderrReadPipe); + CloseHandle(stderrWritePipe); + CloseHandle(stdinReadPipe); + CloseHandle(stdinWritePipe); + + std::shared_ptr pClassDatabaseFile = std::make_shared(); + bool success = false; + IAssetsReader *pReader = Create_AssetsReaderFromMemory(dataBuffer.data(), dataBuffer.size(), false); + if (pReader) + { + success = pClassDatabaseFile->Read(pReader); + Free_AssetsReader(pReader); + } + if (!success) + { + if (allowUserDialog) + { + std::wstring errorMessage = std::wstring(L"Unable to retrieve the script type database!"); + if (errorBuffer.size() > 0) + { + //Add a null terminator + errorBuffer.push_back(0); + size_t strLen = 0; + WCHAR *wideLog = _MultiByteToWide((char*)errorBuffer.data(), strLen); + if (wideLog) + { + errorMessage = errorMessage + L"\n" + wideLog; + _FreeWCHAR(wideLog); + } + } + MessageBoxW(appContext.getMainWindow().getWindow(), errorMessage.c_str(), L"Error", 16); + } + pClassDatabaseFile.reset(); + } + else + { + if (errorBuffer.size() > 0 && allowUserDialog) + { + ShowMonoBehaviourExportErrors(appContext.getMainWindow().getHInstance(), appContext.getMainWindow().getWindow(), errorBuffer); + } + } + return pClassDatabaseFile; +} + +class MonoBehavErrorDialogParam +{ +public: + struct ErrorDesc + { + //points to errorBuffer with byte lengths, UTF-16 + size_t headerText_bufferOffset; + size_t excText_bufferOffset; + unsigned int headerText_bufferLen; + unsigned int excText_bufferLen; + }; +public: + HWND hParentWnd; + std::vector &errorBuffer; + std::vector errorDescriptors; +public: + MonoBehavErrorDialogParam(HWND hParentWnd, std::vector &errorBuffer) + : hParentWnd(hParentWnd), errorBuffer(errorBuffer) + { + } +}; + +static void GetErrorString(MonoBehavErrorDialogParam *pParam, size_t offset, unsigned int byteLen, + std::vector &outString, bool pushback, bool nullchar = true) +{ + wchar_t *outBuf; + if (pushback) + { + size_t oldLen = outString.size(); + outString.resize(oldLen + (byteLen >> 1)); + outBuf = &outString.data()[oldLen]; + } + else + { + outString.resize((byteLen >> 1)); + outBuf = outString.data(); + } + memcpy(outBuf, &pParam->errorBuffer.data()[offset], byteLen); + if (nullchar) + outString.push_back(0); +} + +static void GetFullErrorString(MonoBehavErrorDialogParam *pParam, size_t errorIdx, + std::vector &outString, bool pushback, bool nullchar=true) +{ + MonoBehavErrorDialogParam::ErrorDesc &desc = pParam->errorDescriptors[errorIdx]; + GetErrorString(pParam, desc.headerText_bufferOffset, desc.headerText_bufferLen, outString, pushback, false); + outString.push_back(L' '); + outString.push_back(L':'); + outString.push_back(L'\r'); + outString.push_back(L'\n'); + GetErrorString(pParam, desc.excText_bufferOffset, desc.excText_bufferLen, outString, true, nullchar); +} + +static INT_PTR CALLBACK MonoBehavErrorDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + int wmId, wmEvent; bool all; + UNREFERENCED_PARAMETER(lParam); + switch (message) + { + case WM_INITDIALOG: + { + SetWindowLongPtr(hDlg, GWLP_USERDATA, lParam); + MonoBehavErrorDialogParam *pParam = (MonoBehavErrorDialogParam*)lParam; + HWND hErrorList = GetDlgItem(hDlg, IDC_ERRORLIST); + if (hErrorList == NULL) + return (INT_PTR)FALSE; + std::vector strBuffer; + for (size_t i = 0; i < pParam->errorDescriptors.size(); i++) + { + MonoBehavErrorDialogParam::ErrorDesc &desc = pParam->errorDescriptors[i]; + GetErrorString(pParam, desc.headerText_bufferOffset, desc.headerText_bufferLen, strBuffer, false); + ListBox_AddString(hErrorList, strBuffer.data()); + } + } + return (INT_PTR)TRUE; + + case WM_CLOSE: + case WM_DESTROY: + EndDialog(hDlg, LOWORD(wParam)); + return (INT_PTR)TRUE; + case WM_COMMAND: + wmId = LOWORD(wParam); + wmEvent = HIWORD(wParam); + all = true; + switch (wmId) + { + case IDC_BTNVIEW: + { + MonoBehavErrorDialogParam *pParam = (MonoBehavErrorDialogParam*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + HWND hErrorList = GetDlgItem(hDlg, IDC_ERRORLIST); + int selection = ListBox_GetCurSel(hErrorList); + if ((selection >= 0) && ((unsigned int)selection < pParam->errorDescriptors.size())) + { + std::vector messageBuffer; + GetFullErrorString(pParam, (size_t)selection, messageBuffer, false); + MessageBoxW(hDlg, messageBuffer.data(), L"MonoBehaviour error", 0); + } + } + break; + case IDC_BTNCOPY: + all = false; + case IDC_BTNCOPYALL: + { + MonoBehavErrorDialogParam *pParam = (MonoBehavErrorDialogParam*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + HWND hErrorList = GetDlgItem(hDlg, IDC_ERRORLIST); + int selection = ListBox_GetCurSel(hErrorList); + std::vector messageBuffer; + if (all) + { + for (size_t i = 0; i < pParam->errorDescriptors.size(); i++) + { + if (i > 0) + { + messageBuffer.push_back('\r'); + messageBuffer.push_back('\n'); + messageBuffer.push_back('\r'); + messageBuffer.push_back('\n'); + } + GetFullErrorString(pParam, i, messageBuffer, true, false); + } + messageBuffer.push_back(0); + } + else if ((selection >= 0) && ((unsigned int)selection < pParam->errorDescriptors.size())) + GetFullErrorString(pParam, (size_t)selection, messageBuffer, false); + HGLOBAL hClipboardMem = GlobalAlloc(GMEM_MOVEABLE, messageBuffer.size() * sizeof(wchar_t)); + void *pClipboardMem = nullptr; + bool success = false; + if ((hClipboardMem != nullptr) && ((pClipboardMem = GlobalLock(hClipboardMem)) != nullptr)) + { + memcpy(pClipboardMem, messageBuffer.data(), messageBuffer.size() * sizeof(wchar_t)); + GlobalUnlock(pClipboardMem); + if (OpenClipboard(pParam->hParentWnd)) + { + if (SetClipboardData(CF_UNICODETEXT, hClipboardMem)) + { + CloseClipboard(); + success = true; + } + else + { + CloseClipboard(); + MessageBox(hDlg, L"Unable to change the clipboard data!", L"ERROR", 16); + } + } + else + MessageBox(hDlg, L"Unable to open the clipboard!", L"ERROR", 16); + } + else + MessageBox(hDlg, L"Unable to allocate the global clipboard buffer!", L"ERROR", 16); + if (!success && hClipboardMem) + GlobalFree(hClipboardMem); + } + break; + case IDOK: + case IDCANCEL: + EndDialog(hDlg, LOWORD(wParam)); + return (INT_PTR)TRUE; + } + break; + } + return (INT_PTR)FALSE; +} + +void ShowMonoBehaviourExportErrors(HINSTANCE hInstance, HWND hParent, std::vector &errorBuffer) +{ + /* + errorBuffer format : any number of error entries, each of which is + + struct ErrorBufferEntry + { + unsigned int headerLen, excTextLen; + unsigned char header[headerLen]; //UTF-16 buffer + unsigned char excText[excTextLen]; //UTF-16 buffer + }; + */ + MonoBehavErrorDialogParam dlgParam(hParent, errorBuffer); + size_t bufferIndex = 0; + unsigned char *errorBufferRaw = errorBuffer.data(); + while ((bufferIndex + 8) < errorBuffer.size()) + { + MonoBehavErrorDialogParam::ErrorDesc desc; + desc.headerText_bufferLen = *(unsigned int*)(&errorBufferRaw[bufferIndex]); + desc.excText_bufferLen = *(unsigned int*)(&errorBufferRaw[bufferIndex + 4]); + desc.headerText_bufferOffset = bufferIndex + 8; + desc.excText_bufferOffset = desc.headerText_bufferOffset + desc.headerText_bufferLen; + if ((desc.excText_bufferOffset + desc.excText_bufferLen) <= errorBuffer.size() + && (desc.excText_bufferLen & 1) == 0 + && (desc.headerText_bufferLen & 1) == 0) + { + dlgParam.errorDescriptors.push_back(desc); + } + bufferIndex = desc.excText_bufferOffset + desc.excText_bufferLen; + } + if (dlgParam.errorDescriptors.size() > 0) + DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MONOBEHAVERROR), hParent, MonoBehavErrorDialogProc, (LPARAM)&dlgParam); +} \ No newline at end of file diff --git a/UABE_Win32/MonoBehaviourManager.h b/UABE_Win32/MonoBehaviourManager.h new file mode 100644 index 0000000..3e5c8a2 --- /dev/null +++ b/UABE_Win32/MonoBehaviourManager.h @@ -0,0 +1,15 @@ +#pragma once +//#include "AssetBundleExtractor.h" +//#include "AssetListManager.h" +#include "Win32AppContext.h" +#include "FileContextInfo.h" +#include "../AssetsTools/ClassDatabaseFile.h" +#include "../AssetsTools/EngineVersion.h" +#include +#include +#include //HWND + +bool GetAllScriptInformation(Win32AppContext &appContext, std::vector> &assetsInfo); +std::shared_ptr CreateMonoBehaviourClassDb(Win32AppContext &appContext, + std::vector> &assemblyFullNames, + bool useLongPathID, bool allowUserDialog); diff --git a/UABE_Win32/ProgressDialog.cpp b/UABE_Win32/ProgressDialog.cpp new file mode 100644 index 0000000..eb692ac --- /dev/null +++ b/UABE_Win32/ProgressDialog.cpp @@ -0,0 +1,579 @@ +#include "stdafx.h" +#include "resource.h" +#include "ProgressDialog.h" + +#include "../libStringConverter/convert.h" + +#include + +#include + +CProgressIndicator::CProgressIndicator(HINSTANCE hInstance) + : hInstance(hInstance) +{ + started = false; + dialogHasFocus = false; + dialogIsActive = false; + dontCloseIfLog = false; + logWasCalled = false; + totalRange = 0; + curStepBasePos = 0; + curStep = 0; + curStepProgress = 0; + hStartEvent = NULL; + hEndEvent = NULL; +} +CProgressIndicator::~CProgressIndicator() +{ + if (this->hStartEvent != NULL) + CloseHandle(this->hStartEvent); + if (this->hEndEvent != NULL) + CloseHandle(this->hEndEvent); +} +INT_PTR CALLBACK CProgressIndicator::WindowHandler(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + int wmId, wmEvent; + UNREFERENCED_PARAMETER(lParam); + INT_PTR ret = (INT_PTR)FALSE; + switch (message) + { + case WM_DESTROY: + { + CProgressIndicator *pThis = (CProgressIndicator*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + std::scoped_lock endLock(pThis->endMutex); + SetEvent(pThis->hEndEvent); + SetWindowLongPtr(hDlg, GWLP_USERDATA, 0); + pThis->selfRef.reset(); + + PostQuitMessage(0); + } + break; + case WM_TIMER: + { + CProgressIndicator *pThis = (CProgressIndicator*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + if (wParam == (WPARAM)pThis) + { + pThis->showDelayElapsed = true; + KillTimer(hDlg, wParam); + ShowWindow(hDlg, SW_SHOW); + } + } + break; + case WM_INITDIALOG: + { + SetWindowLongPtr(hDlg, GWLP_USERDATA, lParam); + CProgressIndicator *pThis = (CProgressIndicator*)lParam; + + HWND hWndOptions = GetDlgItem(hDlg, IDC_COMBOOPTIONLIST); + pThis->hDialog = hDlg; + + pThis->dialogHasFocus = (hDlg == GetFocus()); + + SetEvent(pThis->hStartEvent); + + if (pThis->showDelay > 0) + { + pThis->showDelayElapsed = false; + ShowWindow(hDlg, SW_HIDE); + SetTimer(hDlg, (uintptr_t)pThis, pThis->showDelay, NULL); + } + else + { + pThis->showDelayElapsed = true; + ShowWindow(hDlg, SW_SHOW); + } + + HWND hCancelButton = GetDlgItem(hDlg, IDCANCEL); + HWND hOKButton = GetDlgItem(hDlg, IDOK); + ShowWindow(hCancelButton, SW_SHOW); + ShowWindow(hOKButton, SW_HIDE); + + HWND hWndProgress = GetDlgItem(hDlg, IDC_PROG); + EnableWindow(hWndProgress, TRUE); + } + return (INT_PTR)TRUE; + case WM_COMMAND: + wmId = LOWORD(wParam); + wmEvent = HIWORD(wParam); + switch (wmId) + { + case IDOK: + { + CProgressIndicator *pThis = (CProgressIndicator*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + if (pThis->IsCancelled()) + { + SendMessage(hDlg, WM_APP+0, 0, 0); + } + } + return (INT_PTR)TRUE; + case IDCANCEL: + { + CProgressIndicator *pThis = (CProgressIndicator*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + if (pThis->cancellable) + { + pThis->SetCancellable(false); + pThis->forceUncancellable = false; + pThis->SetCancelled(true); + } + } + return (INT_PTR)TRUE; + } + break; + case WM_SETFOCUS: + { + CProgressIndicator *pThis = (CProgressIndicator*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + pThis->dialogHasFocus = true; + } + break; + case WM_KILLFOCUS: + { + CProgressIndicator *pThis = (CProgressIndicator*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + pThis->dialogHasFocus = false; + } + break; + case WM_ACTIVATE: + { + CProgressIndicator *pThis = (CProgressIndicator*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + pThis->dialogIsActive = (LOWORD(wParam) != WA_INACTIVE); + } + break; + case WM_CLOSE: + { + CProgressIndicator *pThis = (CProgressIndicator*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + if (pThis->cancellable) + pThis->SetCancelled(true); + } + break; + case WM_APP+0: + { + CProgressIndicator *pThis = (CProgressIndicator*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + if ((!pThis->showDelayElapsed || pThis->dialogHasFocus || pThis->dialogIsActive) && pThis->hParentWindow != NULL) + SetForegroundWindow(pThis->hParentWindow); + } + DestroyWindow(hDlg); + //EndDialog(hDlg, wParam); + return (INT_PTR)TRUE; + case WM_APP+1: //Cancelled/Ended but show an OK button first. + { + CProgressIndicator *pThis = (CProgressIndicator*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + + HWND hCancelButton = GetDlgItem(hDlg, IDCANCEL); + HWND hOKButton = GetDlgItem(hDlg, IDOK); + ShowWindow(hCancelButton, SW_HIDE); + ShowWindow(hOKButton, SW_SHOW); + + HWND hWndProgress = GetDlgItem(hDlg, IDC_PROG); + EnableWindow(hWndProgress, FALSE); + } + break; + } + return (INT_PTR)FALSE; +} +bool GetTargetWindowRect(IN HWND hParentWnd, IN HINSTANCE hInstance, IN LPCWSTR lpTemplateName, OUT RECT *pRect) +{ + struct _DLGTEMPLATEEX_HEADER{ + uint16_t dlgVer; + uint16_t signature; + DWORD helpID; + DWORD exStyle; + DWORD style; + uint16_t cDlgItems; + short x; + short y; + short cx; + short cy; + }; + TCHAR tempClassName[32]; + _stprintf_s(tempClassName, TEXT("UABE_PROGIND_TEMP%u"), GetCurrentThreadId()); + + HRSRC hResource = FindResourceExW(hInstance, RT_DIALOG, lpTemplateName, 0); + if (hResource == NULL) + return false; + HGLOBAL hLoadedResource = LoadResource(hInstance, hResource); + if (hLoadedResource == NULL) + return false; + bool ret = false; + LPVOID pResourceData = LockResource(hLoadedResource); + if (pResourceData != NULL) + { + DWORD size = SizeofResource(hInstance, hResource); + if (size >= sizeof(_DLGTEMPLATEEX_HEADER)) + { + _DLGTEMPLATEEX_HEADER *pTmpl = (_DLGTEMPLATEEX_HEADER*)pResourceData; + { + WNDCLASSW wc = {}; + wc.lpfnWndProc = DefWindowProcW; + wc.hInstance = hInstance; + wc.lpszClassName = tempClassName; + RegisterClass(&wc); + HWND hWnd = CreateWindowExW(WS_EX_NOPARENTNOTIFY, tempClassName, L"", hParentWnd ? WS_CHILD : WS_POPUP, + hParentWnd ? pTmpl->x : CW_USEDEFAULT, hParentWnd ? pTmpl->y : SW_HIDE, + pTmpl->cx, pTmpl->cy, hParentWnd, NULL, hInstance, NULL); + if (hWnd != NULL) + { + ret = GetWindowRect(hWnd, pRect) != FALSE; + DestroyWindow(hWnd); + } + UnregisterClass(tempClassName, hInstance); + } + } + UnlockResource(hLoadedResource); + } + FreeResource(hLoadedResource); + return ret; +} +DWORD WINAPI CProgressIndicator::WindowHandlerThread(PVOID param) +{ + CProgressIndicator *pThis = (CProgressIndicator*)param; + + RECT targetWindowRect = {}; + bool doSetTargetRect = GetTargetWindowRect(pThis->hParentWindow, pThis->hInstance, MAKEINTRESOURCE(IDD_PROGRESS), &targetWindowRect); + + pThis->dialogHasFocus = false; + pThis->dialogIsActive = false; + + HWND hDialog = CreateDialogParam(pThis->hInstance, MAKEINTRESOURCE(IDD_PROGRESS), NULL, WindowHandler, (LPARAM)pThis); + if (doSetTargetRect) + { + SetWindowPos(hDialog, NULL, + targetWindowRect.left, targetWindowRect.top, 0, 0, + SWP_NOOWNERZORDER | SWP_NOSIZE); + } + MSG msg; + while (GetMessage(&msg, NULL, 0, 0)) + { + DispatchMessage(&msg); + } + return 0; +} +bool CProgressIndicator::Start(HWND hParentWindow, std::shared_ptr _selfRef, unsigned int showDelay) +{ + if (!started) + { + if (!(this->hStartEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) + return false; + if (!(this->hEndEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) + { + CloseHandle(this->hStartEvent); + return false; + } + this->selfRef = std::move(_selfRef); + bool status = true; + this->hParentWindow = hParentWindow; + this->showDelay = showDelay; + this->logWasCalled = false; + HANDLE hThread = CreateThread(NULL, 0, WindowHandlerThread, this, 0, NULL); + if (hThread) + { + if (WaitForSingleObject(this->hStartEvent, INFINITE) != WAIT_OBJECT_0) + { + TerminateThread(hThread, 0); + SetEvent(this->hEndEvent); + status = false; + } + CloseHandle(hThread); + } + else + { + SetEvent(this->hEndEvent); + status = false; + } + CloseHandle(this->hStartEvent); + this->hStartEvent = NULL; + if (status) + { + if (this->stepRanges.size() == 0) + this->stepRanges.push_back(0); + UpdateRange(); + + cancelled = false; + cancellable = true; + forceUncancellable = false; + + started = true; + } + return status; + } + return true; +} +void CProgressIndicator::End() +{ + if (started) + { + if (!logWasCalled || !dontCloseIfLog) + { + started = false; + + HANDLE hEndEvent = this->hEndEvent; + SendMessage(this->hDialog, WM_APP+0, 0, 0); + WaitForSingleObject(hEndEvent, 100); + } + else + { + cancelled = true; + SendMessage(this->hDialog, WM_APP+1, 0, 0); + } + } +} +void CProgressIndicator::Free() +{ + std::unique_lock endLock(this->endMutex, std::defer_lock); + if (this->hEndEvent) + endLock.lock(); + if (!dontCloseIfLog || !logWasCalled || !started) + { + if (endLock.owns_lock()) + endLock.unlock(); + End(); + } +} +void CProgressIndicator::UpdateRange() +{ + HWND hWndProgress = GetDlgItem(this->hDialog, IDC_PROG); + if (totalRange == 0) + { + SetWindowLongPtr(hWndProgress, GWL_STYLE, + (GetWindowLongPtr(hWndProgress, GWL_STYLE) & (~(PBS_SMOOTH | PBS_SMOOTHREVERSE))) | PBS_MARQUEE); + SendMessage(hWndProgress, PBM_SETMARQUEE, (WPARAM)1, 0); + } + else + { + //Don't set the style if it's not necessary since setting the style resets the progress animation back to 0. + LONG_PTR oldStyle = GetWindowLongPtr(hWndProgress, GWL_STYLE); + if (oldStyle & PBS_MARQUEE || !(oldStyle & (PBS_SMOOTH | PBS_SMOOTHREVERSE))) + { + SetWindowLongPtr(hWndProgress, GWL_STYLE, (oldStyle & (~PBS_MARQUEE)) | PBS_SMOOTH | PBS_SMOOTHREVERSE); + } + + SendMessage(hWndProgress, PBM_SETRANGE32, (WPARAM)0, (LPARAM)totalRange); + + UpdateProgress(); + } +} +void CProgressIndicator::UpdateProgress() +{ + if (totalRange > 0) + { + HWND hWndProgress = GetDlgItem(this->hDialog, IDC_PROG); + SendMessage(hWndProgress, PBM_SETPOS, (WPARAM)(curStepBasePos + curStepProgress), 0); + } +} +void CProgressIndicator::SetDontCloseIfLog(bool dontclose) +{ + this->dontCloseIfLog = dontclose; +} +size_t CProgressIndicator::AddStep(unsigned int range) +{ + if (started) + { + size_t ret = stepRanges.size(); + stepRanges.push_back(range); + totalRange += range; + + UpdateRange(); + return ret; + } + return 0; +} +bool CProgressIndicator::SetStepRange(size_t idx, unsigned int range) +{ + if (started) + { + if (stepRanges.size() > idx) + { + unsigned int offset = range - stepRanges[idx]; + totalRange += offset; + stepRanges[idx] = range; + + UpdateRange(); + if (curStep > idx) + { + curStepBasePos += offset; + UpdateProgress(); + } + return true; + } + } + return false; +} +bool CProgressIndicator::SetStepStatus(unsigned int progress) +{ + if (started) + { + if (progress > stepRanges[curStep]) + progress = stepRanges[curStep]; + curStepProgress = progress; + UpdateProgress(); + return true; + } + return false; +} +bool CProgressIndicator::JumpToStep(size_t idx, unsigned int progress) +{ + if (started) + { + if (idx >= stepRanges.size()) + return false; + if (idx >= curStep) + { + for (size_t i = curStep; i < idx; i++) + curStepBasePos += stepRanges[i]; + } + else + { + for (size_t i = idx; i < curStep; i++) + curStepBasePos -= stepRanges[i]; + } + curStep = idx; + SetStepStatus(progress); + return true; + } + return false; +} +size_t CProgressIndicator::GoToNextStep() +{ + if (JumpToStep(curStep + 1)) + return curStep; + else + return (size_t)-1; +} + +bool CProgressIndicator::SetTitle(const std::string &title) +{ + if (started) + { + size_t strLen = 0; + wchar_t *wTitle = _MultiByteToWide(title.c_str(), strLen); + if (wTitle != nullptr) + { + SetWindowTextW(this->hDialog, wTitle); + _FreeWCHAR(wTitle); + return true; + } + } + return false; +} +bool CProgressIndicator::SetTitle(const std::wstring &title) +{ + if (started) + { + SetWindowTextW(this->hDialog, title.c_str()); + return true; + } + return false; +} + +bool CProgressIndicator::SetDescription(const std::string &desc) +{ + if (started) + { + size_t strLen = 0; + wchar_t *wDesc = _MultiByteToWide(desc.c_str(), strLen); + if (wDesc != nullptr) + { + HWND hWndDesc = GetDlgItem(this->hDialog, IDC_SDESC); + Static_SetText(hWndDesc, wDesc); + _FreeWCHAR(wDesc); + return true; + } + } + return false; +} +bool CProgressIndicator::SetDescription(const std::wstring &desc) +{ + if (started) + { + HWND hWndDesc = GetDlgItem(this->hDialog, IDC_SDESC); + Static_SetText(hWndDesc, desc.c_str()); + return true; + } + return false; +} + +bool CProgressIndicator::AddLogText(const std::string &text) +{ + if (started) + { + size_t strLen = 0; + wchar_t *wText = _MultiByteToWide(text.c_str(), strLen); + if (wText != nullptr) + { + bool ret = AddLogText(std::wstring(wText)); + _FreeWCHAR(wText); + return ret; + } + } + return false; +} +bool CProgressIndicator::AddLogText(const std::wstring &text) +{ + if (started) + { + HWND hWndStatus = GetDlgItem(this->hDialog, IDC_EDITSTATUS); + + int editLen = Edit_GetTextLength(hWndStatus); + int oldSelStart = editLen; + int oldSelEnd = editLen; + SendMessage(hWndStatus, EM_GETSEL, (WPARAM)&oldSelStart, (LPARAM)&oldSelEnd); + + Edit_SetSel(hWndStatus, editLen, editLen); + Edit_ReplaceSel(hWndStatus, text.c_str()); + + if ((oldSelEnd != editLen) || (oldSelStart != oldSelEnd)) + Edit_SetSel(hWndStatus, oldSelStart, oldSelEnd); + else + Edit_SetSel(hWndStatus, editLen + text.size(), editLen + text.size()); + + this->logWasCalled = true; + return true; + } + return false; +} + +bool CProgressIndicator::SetCancellable(bool cancellable) +{ + if (started) + { + HWND hWndCancel = GetDlgItem(this->hDialog, IDCANCEL); + Button_Enable(hWndCancel, cancellable ? TRUE : FALSE); + this->cancellable = cancellable; + this->forceUncancellable = true; + return true; + } + return false; +} +bool CProgressIndicator::AddCancelCallback(std::unique_ptr pCallback) +{ + if (started) + { + cancelCallbacks.push_back(std::move(pCallback)); + return true; + } + return false; +} +bool CProgressIndicator::IsCancelled() +{ + if (started) + { + return cancelled; + } + return false; +} +bool CProgressIndicator::SetCancelled(bool cancelled) +{ + if (started) + { + bool prevCancelled = this->cancelled; + this->cancelled = cancelled; + for (size_t i = 0; i < cancelCallbacks.size(); i++) + { + cancelCallbacks[i]->OnCancelEvent(cancelled); + } + if (prevCancelled && !cancelled && !cancellable && !forceUncancellable) + { + SetCancellable(true); //Reenable the button, which will get disabled when the user presses cancel. + } + return true; + } + return false; +} \ No newline at end of file diff --git a/UABE_Win32/ProgressDialog.h b/UABE_Win32/ProgressDialog.h new file mode 100644 index 0000000..a3de7c7 --- /dev/null +++ b/UABE_Win32/ProgressDialog.h @@ -0,0 +1,92 @@ +#pragma once +#include "api.h" +#include "../UABE_Generic/IProgressIndicator.h" +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#include +#include + +typedef void(_cdecl *cbFreeProgressIndicator)(class CProgressIndicator *pIndicator); +//Progress indicator that runs a message handler in a separate thread. +//Useful if longer operations need to run in the main thread +// and messages are not processed during that operation. +//Note that the main thread windows still end up labeled +// as 'not responding' depending on how long the operation is. +class CProgressIndicator : public IProgressIndicator +{ + HWND hDialog; + HWND hParentWindow; + HINSTANCE hInstance; + unsigned int showDelay; + bool started; + bool showDelayElapsed; + bool dialogHasFocus; + bool dialogIsActive; + + HANDLE hStartEvent; + HANDLE hEndEvent; + std::mutex endMutex; + + std::vector stepRanges; + unsigned int totalRange; + unsigned int curStepBasePos; + unsigned int curStepProgress; + size_t curStep; + + bool cancelled; + bool cancellable; + bool forceUncancellable; + bool dontCloseIfLog; + bool logWasCalled; + std::shared_ptr selfRef; + std::vector> cancelCallbacks; + + static INT_PTR CALLBACK WindowHandler(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); + static DWORD WINAPI WindowHandlerThread(PVOID param); +public: + UABE_Win32_API CProgressIndicator(HINSTANCE hInstance); + UABE_Win32_API ~CProgressIndicator(); + UABE_Win32_API bool Start(HWND hParentWindow, std::shared_ptr selfRef, unsigned int showDelay); + UABE_Win32_API void End(); + UABE_Win32_API void Free(); + UABE_Win32_API void UpdateRange(); + UABE_Win32_API void UpdateProgress(); + + //Instructs the progress indicator to stay open or not to stay open (default) with an OK button instead of Cancel if something was written to the log. + UABE_Win32_API void SetDontCloseIfLog(bool dontclose = true); + + //Adds a new step and returns its index. The step with index 0 already exists before calling AddStep the first time. + UABE_Win32_API size_t AddStep(unsigned int range = 0); + //Sets the range of a step. The progress bar range is set to match the range of all steps combined. + UABE_Win32_API bool SetStepRange(size_t idx, unsigned int range); + //Sets the progress of the current step, which should be a value from 0 to (including) range. + UABE_Win32_API bool SetStepStatus(unsigned int progress); + + //Jumps to a step and sets its progress. + UABE_Win32_API bool JumpToStep(size_t idx, unsigned int progress = 0); + //Goes to the next step, setting its progress to 0. + UABE_Win32_API size_t GoToNextStep(); + + //Sets the progress indicator's window title. + UABE_Win32_API bool SetTitle(const std::string &title); + UABE_Win32_API bool SetTitle(const std::wstring &title); + //Sets the description of the progress indicator, usually referring to the current step. + UABE_Win32_API bool SetDescription(const std::string &desc); + UABE_Win32_API bool SetDescription(const std::wstring &desc); + + //Adds text to the log. + UABE_Win32_API bool AddLogText(const std::string &text); + UABE_Win32_API bool AddLogText(const std::wstring &text); + + //Enables or disables the cancel button. + UABE_Win32_API bool SetCancellable(bool cancellable); + + //Adds a cancel callback. Called by the window handler from another thread, or by SetCancelled. + //pCallback must not be freed before destroying the progress indicator. + UABE_Win32_API bool AddCancelCallback(std::unique_ptr pCallback); + //Retrieves the current cancel status. + UABE_Win32_API bool IsCancelled(); + //Sets the current cancel status. + UABE_Win32_API bool SetCancelled(bool cancelled); +}; \ No newline at end of file diff --git a/UABE_Win32/SelectClassDbDialog.cpp b/UABE_Win32/SelectClassDbDialog.cpp new file mode 100644 index 0000000..4dd5db9 --- /dev/null +++ b/UABE_Win32/SelectClassDbDialog.cpp @@ -0,0 +1,202 @@ +#include "stdafx.h" +#include "resource.h" +#include "SelectClassDbDialog.h" +#include "Win32AppContext.h" +#include "FileDialog.h" +#include "../libStringConverter/convert.h" +#include + +typedef std::unique_ptr ClassDatabaseFile_ptr; +static void ClassDatabaseDeleter_Dummy(ClassDatabaseFile*) {} +static void ClassDatabaseDeleter_delete(ClassDatabaseFile *pFile) +{ + delete pFile; +} + +SelectClassDbDialog::SelectClassDbDialog(HINSTANCE hInstance, HWND hParentWnd, ClassDatabasePackage &classPackage) + : hInstance(hInstance), hParentWnd(hParentWnd), hDialog(NULL), + dialogReason_DbNotFound(true), + pClassDatabaseResult(nullptr, ClassDatabaseDeleter_Dummy), rememberForVersion(true), rememberForAll(false), + doneParentMessage(0), classPackage(classPackage) +{} + +INT_PTR CALLBACK SelectClassDbDialog::DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + INT_PTR ret = (INT_PTR)FALSE; + SelectClassDbDialog *pThis = (SelectClassDbDialog*)(GetWindowLongPtr(hDlg, GWLP_USERDATA)); + switch (message) + { + case WM_INITDIALOG: + { + SetWindowLongPtr(hDlg, GWLP_USERDATA, lParam); + pThis = (SelectClassDbDialog*)lParam; + + pThis->hDialog = hDlg; + pThis->pClassDatabaseResult.reset(); + + const char *engineVersion = pThis->version.c_str(); + std::string descText; + if (pThis->dialogReason_DbNotFound) + descText = "No type database matches the player version "; + else + descText = "The selected file has player version "; + descText += engineVersion; + descText += "."; + size_t _len; + TCHAR *descTextT = _MultiByteToTCHAR(descText.c_str(), _len); + Static_SetText(GetDlgItem(hDlg, IDC_DESCLABEL), descTextT); + _FreeTCHAR(descTextT); + + TCHAR *fileNameT = _MultiByteToTCHAR(pThis->fileName.c_str(), _len); + Static_SetText(GetDlgItem(hDlg, IDC_FILELABEL), fileNameT); + _FreeTCHAR(fileNameT); + + HWND hDatabaseList = GetDlgItem(hDlg, IDC_DBLIST); + for (DWORD i = 0; i < pThis->classPackage.header.fileCount; i++) + { + char *databaseName = pThis->classPackage.header.files[i].name; size_t _len; + TCHAR *databaseNameT = _MultiByteToTCHAR(databaseName, _len); + ListBox_AddString(hDatabaseList, databaseNameT); + _FreeTCHAR(databaseNameT); + } + + if (pThis->rememberForAll) + pThis->rememberForVersion = true; + Button_SetCheck(GetDlgItem(hDlg, IDC_CKREMEMBERVERSION), pThis->rememberForVersion ? BST_CHECKED : BST_UNCHECKED); + EnableWindow(GetDlgItem(hDlg, IDC_CKREMEMBERVERSION), pThis->rememberForAll ? FALSE : TRUE); + Button_SetCheck(GetDlgItem(hDlg, IDC_CKREMEMBERALL), pThis->rememberForAll ? BST_CHECKED : BST_UNCHECKED); + } + return (INT_PTR)TRUE; + case WM_APP+0: + { + if (!pThis) + break; + SetWindowLongPtr(hDlg, GWLP_USERDATA, 0); + if (pThis->doneParentMessage != 0 && pThis->hParentWnd != NULL) + PostMessageW(pThis->hParentWnd, pThis->doneParentMessage, reinterpret_cast(pThis), 0); + } + EndDialog(hDlg, 1); + return (INT_PTR)TRUE; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_CKREMEMBERALL: + { + bool isChecked = (Button_GetCheck((HWND)lParam) == BST_CHECKED); + if (isChecked) + Button_SetCheck(GetDlgItem(hDlg, IDC_CKREMEMBERVERSION), BST_CHECKED); + EnableWindow(GetDlgItem(hDlg, IDC_CKREMEMBERVERSION), isChecked ? FALSE : TRUE); + } + break; + case IDC_BTNLOAD: + { + HWND hLoadPathEdit = GetDlgItem(hDlg, IDC_ELOADPATH); + std::unique_ptr loadPathBuf; + int loadPathLen = Edit_GetTextLength(hLoadPathEdit); + if (loadPathLen > 0 && loadPathLen < INT_MAX) + { + std::unique_ptr loadPathBuf(new TCHAR[loadPathLen + 1]); + int actualLen = Edit_GetText(hLoadPathEdit, loadPathBuf.get(), loadPathLen + 1); + loadPathBuf[loadPathLen] = 0; + loadPathLen = actualLen; + } + + wchar_t *filePath; + HRESULT hr = ShowFileOpenDialog(hDlg, &filePath, L"*.dat|.dat files", nullptr, loadPathBuf.get(), + L"Select a class database file", + UABE_FILEDIALOG_CLDB_GUID); + if (SUCCEEDED(hr)) + { + SetWindowTextW(hLoadPathEdit, filePath); + FreeCOMFilePathBuf(&filePath); + } + } + break; + case IDOK: + { + if (!pThis) + break; + HWND hLoadPathEdit = GetDlgItem(hDlg, IDC_ELOADPATH); + std::unique_ptr loadPathBuf; + int loadPathLen = Edit_GetTextLength(hLoadPathEdit); + if (loadPathLen > 0 && loadPathLen < INT_MAX) + { + loadPathBuf.reset(new TCHAR[loadPathLen + 1]); + int actualLen = Edit_GetText(hLoadPathEdit, loadPathBuf.get(), loadPathLen + 1); + loadPathBuf[loadPathLen] = 0; + loadPathLen = actualLen; + } + if (loadPathLen > 0) + { + IAssetsReader_ptr pDatabaseReader(Create_AssetsReaderFromFile(loadPathBuf.get(), true, RWOpenFlags_Immediately), Free_AssetsReader); + if (pDatabaseReader != nullptr) + { + pThis->pClassDatabaseResult = ClassDatabaseFile_ptr(new ClassDatabaseFile(), ClassDatabaseDeleter_delete); + if (!pThis->pClassDatabaseResult->Read(pDatabaseReader.get())) + { + pThis->pClassDatabaseResult.reset(); + MessageBoxA(hDlg, "Unable to read or deserialize the given class database file!", "UABE", 16); + ret = (INT_PTR)TRUE; + break; + } + } + else + { + MessageBoxA(hDlg, "Unable to open the given class database file!", "UABE", 16); + ret = (INT_PTR)TRUE; + break; + } + } + else + { + HWND hVersionList = GetDlgItem(hDlg, IDC_DBLIST); + int selection = ListBox_GetCurSel(hVersionList); + if (selection >= 0 && (DWORD)selection < pThis->classPackage.header.fileCount) + { + ClassDatabaseFile *pSelectedDatabase = pThis->classPackage.files[selection]; + pThis->pClassDatabaseResult = ClassDatabaseFile_ptr(pSelectedDatabase, ClassDatabaseDeleter_Dummy); + } + } + } + case IDCANCEL: + { + if (!pThis) + break; + pThis->rememberForAll = (Button_GetCheck(GetDlgItem(hDlg, IDC_CKREMEMBERALL)) == BST_CHECKED); + pThis->rememberForVersion = pThis->rememberForAll || (Button_GetCheck(GetDlgItem(hDlg, IDC_CKREMEMBERVERSION)) == BST_CHECKED); + + SetWindowLongPtr(hDlg, GWLP_USERDATA, 0); + + if (pThis->doneParentMessage != 0 && pThis->hParentWnd != NULL) + PostMessageW(pThis->hParentWnd, pThis->doneParentMessage, reinterpret_cast(pThis), 0); + } + EndDialog(hDlg, 1); + return (INT_PTR)TRUE; + } + break; + } + return ret; +} + +bool SelectClassDbDialog::ShowModal() +{ + this->doneParentMessage = 0; + if (DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_SELECTTYPEDB), hParentWnd, DlgProc, (LPARAM)this) != 1) + return false; + return true; +} + +HWND SelectClassDbDialog::ShowModeless(UINT doneParentMessage) +{ + this->doneParentMessage = doneParentMessage; + this->hDialog = CreateDialogParam(hInstance, MAKEINTRESOURCE(IDD_SELECTTYPEDB), hParentWnd, DlgProc, (LPARAM)this); + return hDialog; +} + +void SelectClassDbDialog::ForceCancel(bool rememberForVersion, bool rememberForAll) +{ + this->rememberForVersion = rememberForVersion; + this->rememberForAll = rememberForAll; + this->pClassDatabaseResult.reset(); + PostMessage(this->hDialog, WM_APP+0, 0, 0); +} diff --git a/UABE_Win32/SelectClassDbDialog.h b/UABE_Win32/SelectClassDbDialog.h new file mode 100644 index 0000000..d4fffed --- /dev/null +++ b/UABE_Win32/SelectClassDbDialog.h @@ -0,0 +1,67 @@ +#pragma once +#include +#include +#include "../AssetsTools/ClassDatabaseFile.h" + +class SelectClassDbDialog +{ + HINSTANCE hInstance; + HWND hParentWnd; + HWND hDialog; + + std::string version; + std::string fileName; + bool dialogReason_DbNotFound; + + UINT doneParentMessage; + + std::unique_ptr pClassDatabaseResult; + bool rememberForVersion; + bool rememberForAll; + + ClassDatabasePackage &classPackage; + + static INT_PTR CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); + +public: + SelectClassDbDialog(HINSTANCE hInstance, HWND hParentWnd, ClassDatabasePackage &classPackage); + inline void setEngineVersion(std::string version) + { + this->version = std::move(version); + } + inline void setAffectedFileName(std::string fileName) + { + this->fileName = std::move(fileName); + } + //Specifies the first description line. + //databaseNotFound == true => "No type database matches the player version %s." + //databaseNotFound == false => "The selected file has player version %s." + inline void setDialogReason(bool databaseNotFound) + { + this->dialogReason_DbNotFound = databaseNotFound; + } + //Returns when the dialog is closed. Returns false if the dialog could not be opened + bool ShowModal(); + //Returns once the dialog has been created. The parent window receives a message when the dialog closes. + HWND ShowModeless(UINT doneParentMessage); + void ForceCancel(bool rememberForVersion = false, bool rememberForAll = false); + + inline std::unique_ptr getClassDatabaseResult_Move() + { + std::unique_ptr ret = std::move(pClassDatabaseResult); + pClassDatabaseResult.release(); //Should already be done by the copy assignment above. + return ret; + } + inline bool isRememberForVersion() + { + return rememberForVersion; + } + inline bool isRememberForAll() + { + return rememberForAll; + } + inline const std::string &getEngineVersion() + { + return this->version; + } +}; \ No newline at end of file diff --git a/UABE_Win32/SplitterControlHandler.cpp b/UABE_Win32/SplitterControlHandler.cpp new file mode 100644 index 0000000..5cb098a --- /dev/null +++ b/UABE_Win32/SplitterControlHandler.cpp @@ -0,0 +1,158 @@ +#include "stdafx.h" +#include "SplitterControlHandler.h" +#include +#include + +template <> +bool SplitterControlHandler::testCursorOverSplitter() +{ + //Horizontal check (x-axis). + POINT cursor; + RECT separateRect; + if (GetCursorPos(&cursor) && ScreenToClient(hContentSeparate, &cursor) + && GetClientRect(hContentSeparate, &separateRect)) + { + int x = cursor.x; + int y = cursor.y; + if (x >= -3 && x <= 3 && y >= 0 && y <= (separateRect.bottom - separateRect.top)) + { + return true; + } + } + return false; +} +template <> +bool SplitterControlHandler::testCursorOverSplitter() +{ + //Vertical check (y-axis). + POINT cursor; + RECT separateRect; + if (GetCursorPos(&cursor) && ScreenToClient(hContentSeparate, &cursor) + && GetClientRect(hContentSeparate, &separateRect)) + { + int x = cursor.x; + int y = cursor.y; + if (y >= -3 && y <= 3 && x >= 0 && x <= (separateRect.right - separateRect.left)) + { + return true; + } + } + return false; +} + +template <> +bool SplitterControlHandler::onMouseMove(HWND hParent, int x, int y) +{ + if (!this->splitterDown) + return false; + //Horizontal check (x-axis). + + LONG newSplitterX = x - this->splitterOffset; + + RECT clientRect; + if (GetClientRect(hParent, &clientRect) && (clientRect.right - clientRect.left) > 20) + { + LONG windowWidth = clientRect.right - clientRect.left; + if (newSplitterX < 5) newSplitterX = 5; + else if (newSplitterX > (windowWidth - 5)) newSplitterX = windowWidth - 5; + + float newRatio = (float)newSplitterX / (float)windowWidth; + if (newRatio < ratioMin) newRatio = ratioMin; + else if (newRatio > ratioMax) newRatio = ratioMax; + + this->leftOrTopPanelRatio = newRatio; + this->requestResize = true; + return true; + } + return false; +} + +template <> +bool SplitterControlHandler::onMouseMove(HWND hParent, int x, int y) +{ + if (!this->splitterDown) + return false; + //Vertical check (y-axis). + + LONG newSplitterY = y - this->splitterOffset; + + RECT clientRect; + if (GetClientRect(hParent, &clientRect) && (clientRect.bottom - clientRect.top) > 20) + { + LONG windowHeight = clientRect.bottom - clientRect.top; + if (newSplitterY < 5) newSplitterY = 5; + else if (newSplitterY > (windowHeight - 5)) newSplitterY = windowHeight - 5; + + float newRatio = (float)newSplitterY / (float)windowHeight; + if (newRatio < ratioMin) newRatio = ratioMin; + else if (newRatio > ratioMax) newRatio = ratioMax; + + this->leftOrTopPanelRatio = newRatio; + this->requestResize = true; + return true; + } + return false; +} + +template +bool SplitterControlHandler::handleWin32Message(HWND hParent, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + case WM_INITDIALOG: + this->splitterDown = false; + break; + case WM_MOUSEMOVE: + if (this->hContentSeparate != NULL) + return this->onMouseMove(hParent, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + break; + case WM_LBUTTONDOWN: + if (this->hContentSeparate != NULL && !this->splitterDown && testCursorOverSplitter()) + { + this->splitterDown = true; + SetCapture(hParent); + + int x = GET_X_LPARAM(lParam); + int y = GET_Y_LPARAM(lParam); + + //Get the x/y position of the splitter in hDlg client coordinates. + RECT separateRect = {}; + GetWindowRect(hContentSeparate, &separateRect); + ScreenToClient(hParent, &reinterpret_cast(&separateRect)[0]); + + if (horizontal) + this->splitterOffset = x - separateRect.left; + else + this->splitterOffset = y - separateRect.top; + return true; + } + break; + case WM_LBUTTONUP: + if (this->hContentSeparate != NULL && this->splitterDown) + { + this->splitterDown = false; + ReleaseCapture(); + return true; + } + break; + case WM_KILLFOCUS: + if (this->hContentSeparate != NULL && this->splitterDown) + { + this->splitterDown = false; + ReleaseCapture(); + //Let the caller also process this message. + } + break; + case WM_SETCURSOR: + if (this->hContentSeparate != NULL && testCursorOverSplitter()) + { + SetCursor(LoadCursor(NULL, horizontal ? IDC_SIZEWE : IDC_SIZENS)); + return true; + } + break; + } + return false; +} + +template class SplitterControlHandler; //vertical +template class SplitterControlHandler; //horizontal diff --git a/UABE_Win32/SplitterControlHandler.h b/UABE_Win32/SplitterControlHandler.h new file mode 100644 index 0000000..a4bdea0 --- /dev/null +++ b/UABE_Win32/SplitterControlHandler.h @@ -0,0 +1,44 @@ +#pragma once +#include "api.h" +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include + +//horizontal: Set to true if the splitter can be dragged across the x-axis; +// false if the splitter can be dragged across the y-axis. +template +class SplitterControlHandler +{ + HWND hContentSeparate = NULL; + bool requestResize = false; + float leftOrTopPanelRatio, ratioMin, ratioMax; + + bool splitterDown = false; + int splitterOffset = 0; + + bool testCursorOverSplitter(); + bool onMouseMove(HWND hParent, int x, int y); +public: + inline SplitterControlHandler(float leftOrTopPanelRatio, float ratioMin = 0.15f, float ratioMax = 0.8f) + : hContentSeparate(hContentSeparate), leftOrTopPanelRatio(leftOrTopPanelRatio), + ratioMin(ratioMin), ratioMax(ratioMax) + {} + inline void setSplitterWindow(HWND hContentSeparate) + { + this->hContentSeparate = hContentSeparate; + } + //Handles a window message, and returns true if the caller shouldn't process the message further. + UABE_Win32_API bool handleWin32Message(HWND hParent, UINT message, WPARAM wParam, LPARAM lParam); + //Retrieves the resize flag and clears it. + //Call after handleWin32Message to check whether the splitter position has changed. + inline bool shouldResize() + { + bool ret = requestResize; + requestResize = false; + return ret; + } + inline float getLeftOrTopPanelRatio() + { + return leftOrTopPanelRatio; + } +}; diff --git a/UABE_Win32/TypeDatabaseEditor.cpp b/UABE_Win32/TypeDatabaseEditor.cpp new file mode 100644 index 0000000..7defde7 --- /dev/null +++ b/UABE_Win32/TypeDatabaseEditor.cpp @@ -0,0 +1,1197 @@ +#include "stdafx.h" +#include "resource.h" +#include "TypeDatabaseEditor.h" +#include "Win32AppContext.h" + +#include "FileDialog.h" + +#include "../AssetsTools/ClassDatabaseFile.h" +#include "../AssetsTools/AssetsFileReader.h" + +#include + +INT_PTR CALLBACK AddTypeField(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); +INT_PTR CALLBACK TypeDbEditor(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); +INT_PTR CALLBACK TypeDbVersionEditor(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); +INT_PTR CALLBACK TypeEditor(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); + +struct TypeDbEditDialog +{ + HINSTANCE hInst = NULL; + int selectedTypeIndex = -1; + char **originalUnityVersionList = nullptr; + WCHAR *filePath = nullptr; + ClassDatabaseFile classDatabase; +} typeDbEditDialog; +struct TypeDbVersionEditDialog +{ + bool isCustomAlloc[256] = {}; +} typeDbVersionEditDialog; +struct TypeEditDialog +{ + ClassDatabaseType *pType = nullptr; + ClassDatabaseType workCopy; + int selectedFieldIndex = -1; + std::vector fields; +} typeEditDialog; +struct AddFieldDialog +{ + ClassDatabaseTypeField typeField; + bool success = false; +} addFieldDialog; +void OpenTypeDatabaseEditor(HINSTANCE hInstance, HWND hParent) +{ + typeDbEditDialog.hInst = hInstance; + typeDbEditDialog.originalUnityVersionList = NULL; + DialogBox(hInstance, MAKEINTRESOURCE(IDD_EDITTYPEDB), hParent, TypeDbEditor); +} + +static char *GetEditTextA(HWND hEdit) +{ + char *cNameBuf; + #ifdef _UNICODE + int wcTextLen = Edit_GetTextLength(hEdit); + WCHAR *wcNameBuf = (WCHAR*)malloc((wcTextLen+1) * sizeof(WCHAR)); + __checkoutofmemory(wcNameBuf==NULL); + Edit_GetText(hEdit, wcNameBuf, wcTextLen+1); + wcNameBuf[wcTextLen] = 0; + + int cTextLen = WideCharToMultiByte(CP_UTF8, 0, wcNameBuf, wcTextLen, NULL, 0, NULL, NULL); + cNameBuf = (char*)malloc((cTextLen+1) * sizeof(char)); + __checkoutofmemory(cNameBuf==NULL); + WideCharToMultiByte(CP_UTF8, 0, wcNameBuf, wcTextLen, cNameBuf, cTextLen, NULL, NULL); + cNameBuf[cTextLen] = 0; + free(wcNameBuf); + #else + int cTextLen = Edit_GetTextLength(hEdit); + cNameBuf = (char*)malloc(cTextLen+1); + __checkoutofmemory(cNameBuf==NULL); + Edit_GetText(hEdit, cNameBuf, cTextLen+1); + cNameBuf[cTextLen] = 0; + #endif + return cNameBuf; +} +static void SetEditTextA(HWND hEdit, const char *text) +{ + #ifdef _UNICODE + size_t textLen = strlen(text); + int wcharCount = MultiByteToWideChar(CP_UTF8, 0, text, (int)textLen, NULL, 0); + WCHAR *wcTextBuf = (WCHAR*)malloc((wcharCount+1) * sizeof(WCHAR)); + __checkoutofmemory(wcTextBuf==NULL); + MultiByteToWideChar(CP_UTF8, 0, text, (int)textLen, wcTextBuf, wcharCount); + wcTextBuf[wcharCount] = 0; + Edit_SetText(hEdit, wcTextBuf); + free(wcTextBuf); + #else + Edit_SetText(hNameEdit, text); + #endif +} + +INT_PTR CALLBACK TypeDbEditor(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + int wmId, wmEvent; + UNREFERENCED_PARAMETER(lParam); + switch (message) + { + case WM_INITDIALOG: + { + memset(typeDbVersionEditDialog.isCustomAlloc, 0, 256); + HWND hOptimizeFastCB = GetDlgItem(hDlg, IDC_CKOPTIMIZEFAST); + Button_SetCheck(hOptimizeFastCB, true); + HWND hOptimizeSlowCB = GetDlgItem(hDlg, IDC_CKOPTIMIZESLOW); + Button_SetCheck(hOptimizeSlowCB, false); + HWND hCompressLZ4CB = GetDlgItem(hDlg, IDC_CKCOMPRESSLZ4); + Button_SetCheck(hCompressLZ4CB, false); + HWND hCompressLZMACB = GetDlgItem(hDlg, IDC_CKCOMPRESSLZMA); + Button_SetCheck(hCompressLZMACB, true); + + WCHAR *filePathBuf; + HRESULT hr = ShowFileOpenDialog(hDlg, &filePathBuf, L"*.dat|Type database:", + nullptr, nullptr, nullptr, + UABE_FILEDIALOG_CLDB_GUID); + __checkoutofmemory(hr==E_OUTOFMEMORY); + if (SUCCEEDED(hr)) + { + IAssetsReader *pDbReader = Create_AssetsReaderFromFile(filePathBuf, true, RWOpenFlags_Immediately); + + if (pDbReader != NULL) + { + typeDbEditDialog.classDatabase.Read(pDbReader); + Free_AssetsReader(pDbReader); + //fclose(pDbFile); + typeDbEditDialog.filePath = filePathBuf; + + HWND hAssetlist = GetDlgItem(hDlg, IDC_TYPELIST); + for (DWORD i = 0; i < typeDbEditDialog.classDatabase.classes.size(); i++) + { + ClassDatabaseType *pType = &typeDbEditDialog.classDatabase.classes[i]; + const char *typeName = pType->name.GetString(&typeDbEditDialog.classDatabase); + if (typeName != NULL) + { +#ifdef _UNICODE + size_t typeNameLen = strlen(typeName); + int wcharCount = MultiByteToWideChar(CP_UTF8, 0, typeName, (int)typeNameLen, NULL, 0); + WCHAR *wcNameBuf = (WCHAR*)malloc((wcharCount+1) * sizeof(WCHAR)); + __checkoutofmemory(wcNameBuf==NULL); + MultiByteToWideChar(CP_UTF8, 0, typeName, (int)typeNameLen, wcNameBuf, wcharCount); + wcNameBuf[wcharCount] = 0; + ListBox_AddString(hAssetlist, wcNameBuf); + free(wcNameBuf); +#else + ListBox_AddString(hAssetlist, typeName); +#endif + } + else + ListBox_AddString(hAssetlist, TEXT("")); + } + typeDbEditDialog.selectedTypeIndex = -1; + ListBox_SetCurSel(hAssetlist, 0); + goto DoUpdateTypeList; + } + else + { + FreeCOMFilePathBuf(&filePathBuf); + MessageBox(hDlg, TEXT("Unable to open the file!"), TEXT("ERROR"), 16); + EndDialog(hDlg, LOWORD(wParam)); + } + } + else + EndDialog(hDlg, LOWORD(wParam)); + } + return (INT_PTR)TRUE; + + case WM_CLOSE: + case WM_DESTROY: + EndDialog(hDlg, LOWORD(wParam)); + goto Free_TypeDbEditorDialog; + case WM_COMMAND: + wmId = LOWORD(wParam); + wmEvent = HIWORD(wParam); + switch (wmId) + { + case IDC_CKCOMPRESSLZ4: + { + HWND hCompressLZ4CB = GetDlgItem(hDlg, IDC_CKCOMPRESSLZ4); + HWND hCompressLZMACB = GetDlgItem(hDlg, IDC_CKCOMPRESSLZMA); + if (Button_GetCheck(hCompressLZ4CB)) + Button_SetCheck(hCompressLZMACB, 0); + } + break; + case IDC_CKCOMPRESSLZMA: + { + HWND hCompressLZ4CB = GetDlgItem(hDlg, IDC_CKCOMPRESSLZ4); + HWND hCompressLZMACB = GetDlgItem(hDlg, IDC_CKCOMPRESSLZMA); + if (Button_GetCheck(hCompressLZMACB)) + Button_SetCheck(hCompressLZ4CB, 0); + } + break; + case IDC_CKOPTIMIZEFAST: + { + HWND hOptimizeFastCB = GetDlgItem(hDlg, IDC_CKOPTIMIZEFAST); + HWND hOptimizeSlowCB = GetDlgItem(hDlg, IDC_CKOPTIMIZESLOW); + if (Button_GetCheck(hOptimizeFastCB)) + Button_SetCheck(hOptimizeSlowCB, 0); + } + break; + case IDC_CKOPTIMIZESLOW: + { + HWND hOptimizeFastCB = GetDlgItem(hDlg, IDC_CKOPTIMIZEFAST); + HWND hOptimizeSlowCB = GetDlgItem(hDlg, IDC_CKOPTIMIZESLOW); + if (Button_GetCheck(hOptimizeSlowCB)) + Button_SetCheck(hOptimizeFastCB, 0); + } + break; + case IDOK: + { + HWND hOptimizeFastCB = GetDlgItem(hDlg, IDC_CKOPTIMIZEFAST); + HWND hOptimizeSlowCB = GetDlgItem(hDlg, IDC_CKOPTIMIZESLOW); + bool optimizePlacebo = Button_GetCheck(hOptimizeSlowCB)?true:false; + bool optimizeFast = (Button_GetCheck(hOptimizeFastCB)?true:false); + int optimize = optimizePlacebo ? 2 : (optimizeFast ? 1 : 0); + HWND hCompressLZ4CB = GetDlgItem(hDlg, IDC_CKCOMPRESSLZ4); + HWND hCompressLZMACB = GetDlgItem(hDlg, IDC_CKCOMPRESSLZMA); + DWORD compress = Button_GetCheck(hCompressLZ4CB) ? 1 : (Button_GetCheck(hCompressLZMACB) ? 2 : 0); + IAssetsWriter *pDbWriter = Create_AssetsWriterToFile(typeDbEditDialog.filePath, true, true, RWOpenFlags_Immediately); + //FILE *pDbFile = NULL; + //_wfopen_s(&pDbFile, typeDbEditDialog.filePath, L"wb"); + if (pDbWriter != NULL) + { + DialogBox(typeDbEditDialog.hInst, MAKEINTRESOURCE(IDD_EDITTYPEDBVERSION), hDlg, TypeDbVersionEditor); + typeDbEditDialog.classDatabase.Write(pDbWriter, 0, optimize, compress); + Free_AssetsWriter(pDbWriter); + //fclose(pDbFile); + } + else + { + MessageBox(hDlg, TEXT("Unable to open the file for writing!"), TEXT("ERROR"), 16); + break; + } + } + case IDCANCEL: + EndDialog(hDlg, LOWORD(wParam)); + goto Free_TypeDbEditorDialog; + case IDC_BTNTYPEEDIT: + { + if (typeDbEditDialog.selectedTypeIndex >= 0 && typeDbEditDialog.selectedTypeIndex < (int)typeDbEditDialog.classDatabase.classes.size()) + { + typeEditDialog.pType = &typeDbEditDialog.classDatabase.classes[typeDbEditDialog.selectedTypeIndex]; + DialogBox(typeDbEditDialog.hInst, MAKEINTRESOURCE(IDD_EDITTYPE), hDlg, TypeEditor); + } + } + break; + case IDC_BTNADD: + { + ClassDatabaseType newType; + newType.name.fromStringTable = false; + char *str = (char*)malloc(1); + __checkoutofmemory(str==NULL); + str[0] = 0; + newType.name.str.string = str; + newType.assemblyFileName.fromStringTable = false; + char *str2 = (char*)malloc(1); + __checkoutofmemory(str2==NULL); + str2[0] = 0; + newType.assemblyFileName.str.string = str2; + + newType.classId = -1; + newType.baseClass = 0; + typeDbEditDialog.classDatabase.classes.push_back(newType); + HWND hTypeList = GetDlgItem(hDlg, IDC_TYPELIST); + ListBox_AddString(hTypeList, TEXT("")); + ListBox_SetCurSel(hTypeList, typeDbEditDialog.classDatabase.classes.size()-1); + goto DoUpdateTypeList; + } + break; + case IDC_BTNREMOVE: + { + if (typeDbEditDialog.selectedTypeIndex >= 0 && typeDbEditDialog.selectedTypeIndex < (int)typeDbEditDialog.classDatabase.classes.size()) + { + int selection = typeDbEditDialog.selectedTypeIndex; + ClassDatabaseType *pType = &typeDbEditDialog.classDatabase.classes[typeDbEditDialog.selectedTypeIndex]; + if (!pType->name.fromStringTable) + free(const_cast(pType->name.str.string)); + if (!pType->assemblyFileName.fromStringTable) + free(const_cast(pType->assemblyFileName.str.string)); + for (size_t k = 0; k < pType->fields.size(); k++) + { + ClassDatabaseTypeField *pField = &pType->fields[k]; + if (!pField->fieldName.fromStringTable) + free(const_cast(pField->fieldName.str.string)); + if (!pField->typeName.fromStringTable) + free(const_cast(pField->typeName.str.string)); + } + typeDbEditDialog.classDatabase.classes.erase(typeDbEditDialog.classDatabase.classes.begin()+selection); + HWND hTypeList = GetDlgItem(hDlg, IDC_TYPELIST); + ListBox_DeleteString(hTypeList, selection); + if (selection < (int)typeDbEditDialog.classDatabase.classes.size()) + ListBox_SetCurSel(hTypeList, selection); + else if (selection > 0) + ListBox_SetCurSel(hTypeList, selection-1); + typeDbEditDialog.selectedTypeIndex = -1; + goto DoUpdateTypeList; + } + } + break; + case IDC_EDITNAME: + { + if (typeDbEditDialog.selectedTypeIndex >= 0 && typeDbEditDialog.selectedTypeIndex < (int)typeDbEditDialog.classDatabase.classes.size()) + { + ClassDatabaseType *pSelectedType = &typeDbEditDialog.classDatabase.classes[typeDbEditDialog.selectedTypeIndex]; + HWND hNameEdit = GetDlgItem(hDlg, IDC_EDITNAME); + HWND hAssetlist = GetDlgItem(hDlg, IDC_TYPELIST); + char *cNameBuf; + #ifdef _UNICODE + int wcTextLen = Edit_GetTextLength(hNameEdit); + WCHAR *wcNameBuf = (WCHAR*)malloc((wcTextLen+1) * sizeof(WCHAR)); + __checkoutofmemory(wcNameBuf==NULL); + Edit_GetText(hNameEdit, wcNameBuf, wcTextLen+1); + wcNameBuf[wcTextLen] = 0; + + ListBox_DeleteString(hAssetlist, typeDbEditDialog.selectedTypeIndex); + ListBox_InsertString(hAssetlist, typeDbEditDialog.selectedTypeIndex, wcNameBuf); + + int cTextLen = WideCharToMultiByte(CP_UTF8, 0, wcNameBuf, wcTextLen, NULL, 0, NULL, NULL); + cNameBuf = (char*)malloc((cTextLen+1) * sizeof(char)); + __checkoutofmemory(cNameBuf==NULL); + WideCharToMultiByte(CP_UTF8, 0, wcNameBuf, wcTextLen, cNameBuf, cTextLen, NULL, NULL); + cNameBuf[cTextLen] = 0; + free(wcNameBuf); + #else + int cTextLen = Edit_GetTextLength(hNameEdit); + cNameBuf = (char*)malloc(cTextLen+1); + __checkoutofmemory(cNameBuf==NULL); + Edit_GetText(hNameEdit, cNameBuf, cTextLen+1); + cNameBuf[cTextLen] = 0; + + ListBox_DeleteString(hAssetlist, typeDbEditDialog.selectedTypeIndex); + ListBox_InsertString(hAssetlist, typeDbEditDialog.selectedTypeIndex, cNameBuf); + #endif + ListBox_SetCurSel(hAssetlist, typeDbEditDialog.selectedTypeIndex); + if (!pSelectedType->name.fromStringTable) + free(const_cast(pSelectedType->name.str.string)); + pSelectedType->name.fromStringTable = false; + pSelectedType->name.str.string = cNameBuf; + } + } + break; + case IDC_EDITTYPEID: + { + if (typeDbEditDialog.selectedTypeIndex >= 0 && typeDbEditDialog.selectedTypeIndex < (int)typeDbEditDialog.classDatabase.classes.size()) + { + ClassDatabaseType *pSelectedType = &typeDbEditDialog.classDatabase.classes[typeDbEditDialog.selectedTypeIndex]; + + HWND hTypeIdEdit = GetDlgItem(hDlg, IDC_EDITTYPEID); + int tcTextLen = Edit_GetTextLength(hTypeIdEdit); + TCHAR *tcNameBuf = (WCHAR*)malloc((tcTextLen+1) * sizeof(WCHAR)); + __checkoutofmemory(tcNameBuf==NULL); + Edit_GetText(hTypeIdEdit, tcNameBuf, tcTextLen+1); + *_errno() = 0; + int typeId = _tcstol(tcNameBuf, NULL, 0); + if (errno == ERANGE) + { + *_errno() = 0; + typeId = (int)_tcstoul(tcNameBuf, NULL, 0); + } + if (errno != ERANGE) + { + pSelectedType->classId = typeId; + } + free(tcNameBuf); + + } + } + break; + case IDC_TYPELIST: + DoUpdateTypeList: + { + HWND hTypeList = GetDlgItem(hDlg, IDC_TYPELIST); + unsigned int selection = (unsigned int)ListBox_GetCurSel(hTypeList); + if ((selection != typeDbEditDialog.selectedTypeIndex) && selection < typeDbEditDialog.classDatabase.classes.size()) + { + typeDbEditDialog.selectedTypeIndex = (int)selection; + HWND hNameEdit = GetDlgItem(hDlg, IDC_EDITNAME); + HWND hTypeIdEdit = GetDlgItem(hDlg, IDC_EDITTYPEID); + ClassDatabaseType *pSelectedType = &typeDbEditDialog.classDatabase.classes[selection]; + const char *typeName = pSelectedType->name.GetString(&typeDbEditDialog.classDatabase); + if (typeName != NULL) + { + #ifdef _UNICODE + size_t typeNameLen = strlen(typeName); + int wcharCount = MultiByteToWideChar(CP_UTF8, 0, typeName, (int)typeNameLen, NULL, 0); + WCHAR *wcNameBuf = (WCHAR*)malloc((wcharCount+1) * sizeof(WCHAR)); + __checkoutofmemory(wcNameBuf==NULL); + MultiByteToWideChar(CP_UTF8, 0, typeName, (int)typeNameLen, wcNameBuf, wcharCount); + wcNameBuf[wcharCount] = 0; + Edit_SetText(hNameEdit, wcNameBuf); + free(wcNameBuf); + #else + Edit_SetText(hNameEdit, typeName); + #endif + } + TCHAR sprntTmp[12]; + _stprintf(sprntTmp, TEXT("0x%08X"), pSelectedType->classId); + Edit_SetText(hTypeIdEdit, sprntTmp); + } + } + break; + } + break; + } + return (INT_PTR)FALSE; +Free_TypeDbEditorDialog: + FreeCOMFilePathBuf(&typeDbEditDialog.filePath); + for (size_t i = 0; i < typeDbEditDialog.classDatabase.classes.size(); i++) + { + ClassDatabaseType *pType = &typeDbEditDialog.classDatabase.classes[i]; + if (!pType->name.fromStringTable) + free(const_cast(pType->name.str.string)); + if (!pType->assemblyFileName.fromStringTable) + free(const_cast(pType->assemblyFileName.str.string)); + for (size_t k = 0; k < pType->fields.size(); k++) + { + ClassDatabaseTypeField *pField = &pType->fields[k]; + if (!pField->fieldName.fromStringTable) + free(const_cast(pField->fieldName.str.string)); + if (!pField->typeName.fromStringTable) + free(const_cast(pField->typeName.str.string)); + } + } + for (int i = 0; i < typeDbEditDialog.classDatabase.header.unityVersionCount; i++) + { + if (typeDbVersionEditDialog.isCustomAlloc[i]) + { + free(typeDbEditDialog.classDatabase.header.pUnityVersions[i]); + } + } + if (typeDbEditDialog.originalUnityVersionList && typeDbEditDialog.classDatabase.header.pUnityVersions && + typeDbEditDialog.classDatabase.header.pUnityVersions != typeDbEditDialog.originalUnityVersionList) + { + free(typeDbEditDialog.classDatabase.header.pUnityVersions); + typeDbEditDialog.classDatabase.header.pUnityVersions = typeDbEditDialog.originalUnityVersionList; + } + typeDbEditDialog.classDatabase.~ClassDatabaseFile(); + return (INT_PTR)TRUE; +} + +INT_PTR CALLBACK TypeDbVersionEditor(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + int wmId, wmEvent; + UNREFERENCED_PARAMETER(lParam); + switch (message) + { + case WM_INITDIALOG: + { + memset(typeDbVersionEditDialog.isCustomAlloc, 0, 256 * sizeof(bool)); + typeDbEditDialog.originalUnityVersionList = NULL; + HWND hVersionList = GetDlgItem(hDlg, IDC_VERSIONLIST); + for (DWORD i = 0; i < typeDbEditDialog.classDatabase.header.unityVersionCount; i++) + { + const char *versionName = typeDbEditDialog.classDatabase.header.pUnityVersions[i]; + if (versionName != NULL) + { +#ifdef _UNICODE + size_t versionNameLen = strlen(versionName); + int wcharCount = MultiByteToWideChar(CP_UTF8, 0, versionName, (int)versionNameLen, NULL, 0); + WCHAR *wcNameBuf = (WCHAR*)malloc((wcharCount+1) * sizeof(WCHAR)); + __checkoutofmemory(wcNameBuf==NULL); + MultiByteToWideChar(CP_UTF8, 0, versionName, (int)versionNameLen, wcNameBuf, wcharCount); + wcNameBuf[wcharCount] = 0; + ListBox_AddString(hVersionList, wcNameBuf); + free(wcNameBuf); +#else + ListBox_AddString(hVersionList, versionName); +#endif + } + else + ListBox_AddString(hVersionList, TEXT("")); + } + ListBox_SetCurSel(hVersionList, 0); + } + return (INT_PTR)TRUE; + + case WM_CLOSE: + case WM_DESTROY: + EndDialog(hDlg, LOWORD(wParam)); + goto Free_TypeDbVersionEditorDialog; + case WM_COMMAND: + wmId = LOWORD(wParam); + wmEvent = HIWORD(wParam); + switch (wmId) + { + case IDOK: + { + EndDialog(hDlg, LOWORD(wParam)); + } + goto Free_TypeDbVersionEditorDialog; + case IDC_BTNREMOVE: + { + HWND hVersionList = GetDlgItem(hDlg, IDC_VERSIONLIST); + int selection = ListBox_GetCurSel(hVersionList); + if (selection >= 0 && selection < (int)typeDbEditDialog.classDatabase.header.unityVersionCount) + { + char **pNewUnityVersions = (char**)malloc(sizeof(char*) * (typeDbEditDialog.classDatabase.header.unityVersionCount - 1)); + __checkoutofmemory(pNewUnityVersions==NULL); + memcpy(pNewUnityVersions, + typeDbEditDialog.classDatabase.header.pUnityVersions, + selection * sizeof(char*)); + memcpy(&pNewUnityVersions[selection], + &typeDbEditDialog.classDatabase.header.pUnityVersions[selection+1], + (typeDbEditDialog.classDatabase.header.unityVersionCount - 1 - selection) * sizeof(char*)); + + memcpy(&typeDbVersionEditDialog.isCustomAlloc[selection], + &typeDbVersionEditDialog.isCustomAlloc[selection+1], + 255 - selection - 1); + + if (typeDbEditDialog.originalUnityVersionList == NULL) + typeDbEditDialog.originalUnityVersionList = typeDbEditDialog.classDatabase.header.pUnityVersions; + else if (typeDbEditDialog.originalUnityVersionList != typeDbEditDialog.classDatabase.header.pUnityVersions) + free(typeDbEditDialog.classDatabase.header.pUnityVersions); + typeDbEditDialog.classDatabase.header.pUnityVersions = pNewUnityVersions; + typeDbEditDialog.classDatabase.header.unityVersionCount--; + + ListBox_DeleteString(hVersionList, selection); + if (selection < (int)typeDbEditDialog.classDatabase.header.unityVersionCount) + ListBox_SetCurSel(hVersionList, selection); + else if (selection > 0) + ListBox_SetCurSel(hVersionList, selection-1); + } + } + break; + case IDC_BTNADD: + { + if (typeDbEditDialog.classDatabase.header.unityVersionCount >= 254) + break; + HWND hVersionEdit = GetDlgItem(hDlg, IDC_VERSIONEDIT); + HWND hVersionList = GetDlgItem(hDlg, IDC_VERSIONLIST); + //int selection = ListBox_GetCurSel(hVersionList); + //if (selection >= 0 && selection < (int)typeDbEditDialog.classDatabase.header.unityVersionCount) + { + char *cNameBuf; + #ifdef _UNICODE + int wcTextLen = Edit_GetTextLength(hVersionEdit); + WCHAR *wcNameBuf = (WCHAR*)malloc((wcTextLen+1) * sizeof(WCHAR)); + __checkoutofmemory(wcNameBuf==NULL); + Edit_GetText(hVersionEdit, wcNameBuf, wcTextLen+1); + wcNameBuf[wcTextLen] = 0; + + ListBox_AddString(hVersionList, wcNameBuf); + + int cTextLen = WideCharToMultiByte(CP_UTF8, 0, wcNameBuf, wcTextLen, NULL, 0, NULL, NULL); + cNameBuf = (char*)malloc((cTextLen+1) * sizeof(char)); + __checkoutofmemory(cNameBuf==NULL); + WideCharToMultiByte(CP_UTF8, 0, wcNameBuf, wcTextLen, cNameBuf, cTextLen, NULL, NULL); + cNameBuf[cTextLen] = 0; + free(wcNameBuf); + #else + int cTextLen = Edit_GetTextLength(hNameEdit); + cNameBuf = (char*)malloc(cTextLen+1); + __checkoutofmemory(cNameBuf==NULL); + Edit_GetText(hNameEdit, cNameBuf, cTextLen+1); + cNameBuf[cTextLen] = 0; + + ListBox_AddString(hVersionList, cNameBuf); + #endif + ListBox_SetCurSel(hVersionList, typeDbEditDialog.classDatabase.header.unityVersionCount); + char **pNewUnityVersions = (char**)malloc(sizeof(char*) * (typeDbEditDialog.classDatabase.header.unityVersionCount + 1)); + __checkoutofmemory(pNewUnityVersions==NULL); + memcpy(&pNewUnityVersions[0], + typeDbEditDialog.classDatabase.header.pUnityVersions, + typeDbEditDialog.classDatabase.header.unityVersionCount * sizeof(char*)); + pNewUnityVersions[typeDbEditDialog.classDatabase.header.unityVersionCount] = cNameBuf; + if (typeDbEditDialog.originalUnityVersionList == NULL) + typeDbEditDialog.originalUnityVersionList = typeDbEditDialog.classDatabase.header.pUnityVersions; + else if (typeDbEditDialog.originalUnityVersionList != typeDbEditDialog.classDatabase.header.pUnityVersions) + free(typeDbEditDialog.classDatabase.header.pUnityVersions); + typeDbEditDialog.classDatabase.header.pUnityVersions = pNewUnityVersions; + typeDbVersionEditDialog.isCustomAlloc[typeDbEditDialog.classDatabase.header.unityVersionCount] = true; + typeDbEditDialog.classDatabase.header.unityVersionCount++; + } + } + break; + } + break; + } + return (INT_PTR)FALSE; +Free_TypeDbVersionEditorDialog: + //nothing to free here, the TypeDbEditorDialog does all this + return (INT_PTR)TRUE; +} + +WCHAR *_TypeEditor_MakeListViewName(ClassDatabaseTypeField *pTypeField) +{ + const char *fieldName = pTypeField->fieldName.GetString(&typeDbEditDialog.classDatabase); + const char *typeName = pTypeField->typeName.GetString(&typeDbEditDialog.classDatabase); + int fieldNameMbLen = (int)strlen(fieldName); + int fieldNameWcLen = MultiByteToWideChar(CP_UTF8, 0, fieldName, fieldNameMbLen, NULL, 0); + int typeNameMbLen = (int)strlen(typeName); + int typeNameWcLen = MultiByteToWideChar(CP_UTF8, 0, typeName, typeNameMbLen, NULL, 0); + + WCHAR *treeViewText = (WCHAR*)malloc((typeNameWcLen + 1 + fieldNameWcLen + 1) * sizeof(WCHAR)); + __checkoutofmemory(treeViewText==NULL); + MultiByteToWideChar(CP_UTF8, 0, typeName, typeNameMbLen, treeViewText, typeNameWcLen); + treeViewText[typeNameWcLen] = L' '; + MultiByteToWideChar(CP_UTF8, 0, fieldName, fieldNameMbLen, &treeViewText[typeNameWcLen+1], fieldNameWcLen); + treeViewText[typeNameWcLen + 1 + fieldNameWcLen] = 0; + return treeViewText; +} +void _TypeEditor_BuildTreeView(HWND hFieldTree) +{ + size_t fieldCount = typeEditDialog.workCopy.fields.size(); + for (size_t i = 0; i < fieldCount; i++) + { + ClassDatabaseTypeField *pTypeField = &typeEditDialog.workCopy.fields[i]; + TVINSERTSTRUCT is;is.hParent = NULL; + for (size_t _k = i; _k > 0; _k--) + { + size_t k = _k - 1; + if (typeEditDialog.workCopy.fields[k].depth < pTypeField->depth) + { + is.hParent = typeEditDialog.fields[k]; + break; + } + } + + WCHAR *treeViewText = _TypeEditor_MakeListViewName(pTypeField); + + is.itemex.pszText = treeViewText; + is.itemex.cchTextMax = (int)wcslen(treeViewText)+1; + is.itemex.state = 0; + is.itemex.stateMask = 0xFF; + is.itemex.mask = TVIF_CHILDREN | TVIF_STATE | TVIF_TEXT; + //WCHAR *treeViewString = (WCHAR*) + if (i == 0) + is.hInsertAfter = TVI_ROOT; + else + is.hInsertAfter = typeEditDialog.fields[i-1]; + if (((i+1) < fieldCount) && (typeEditDialog.workCopy.fields[i+1].depth > pTypeField->depth)) + is.itemex.cChildren = 1; + else + is.itemex.cChildren = 0; + typeEditDialog.fields.push_back(TreeView_InsertItem(hFieldTree, &is)); + free(treeViewText); + } +} +INT_PTR CALLBACK TypeEditor(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + int wmId, wmEvent; + UNREFERENCED_PARAMETER(lParam); + switch (message) + { + case WM_INITDIALOG: + { + typeEditDialog.selectedFieldIndex = -1; + typeEditDialog.workCopy = ClassDatabaseType(*typeEditDialog.pType); + /*if (!typeEditDialog.workCopy.name.fromStringTable) + { + size_t strLen = strlen(typeEditDialog.workCopy.name.str.string); + char *nameCopy = (char*)malloc(strLen+1); + __checkoutofmemory(nameCopy==NULL); + memcpy(nameCopy, typeEditDialog.workCopy.name.str.string, strLen+1); + typeEditDialog.workCopy.name.str.string = nameCopy; + }*/ //the type's name can't be changed during the TypeEditor + HWND hFieldTree = GetDlgItem(hDlg, IDC_TYPETREE); + size_t fieldCount = typeEditDialog.workCopy.fields.size(); + for (size_t i = 0; i < fieldCount; i++) + { + ClassDatabaseTypeField *pTypeField = &typeEditDialog.workCopy.fields[i]; + if (!pTypeField->fieldName.fromStringTable) + { + size_t strLen = strlen(pTypeField->fieldName.str.string); + char *nameCopy = (char*)malloc(strLen+1); + __checkoutofmemory(nameCopy==NULL); + memcpy(nameCopy, pTypeField->fieldName.str.string, strLen+1); + pTypeField->fieldName.str.string = nameCopy; + } + if (!pTypeField->typeName.fromStringTable) + { + size_t strLen = strlen(pTypeField->typeName.str.string); + char *nameCopy = (char*)malloc(strLen+1); + __checkoutofmemory(nameCopy==NULL); + memcpy(nameCopy, pTypeField->typeName.str.string, strLen+1); + pTypeField->typeName.str.string = nameCopy; + } + } + _TypeEditor_BuildTreeView(hFieldTree); + } + return (INT_PTR)TRUE; + + case WM_CLOSE: + case WM_DESTROY: + //free the copied strings + for (size_t i = 0; i < typeEditDialog.workCopy.fields.size(); i++) + { + ClassDatabaseTypeField *pCopiedTypeField = &typeEditDialog.workCopy.fields[i]; + if (!pCopiedTypeField->fieldName.fromStringTable) + free(const_cast(pCopiedTypeField->fieldName.str.string)); + if (!pCopiedTypeField->typeName.fromStringTable) + free(const_cast(pCopiedTypeField->typeName.str.string)); + //typeEditDialog.workCopy.fields.erase(typeEditDialog.workCopy.fields.begin()+i); + //i--; + } + if (typeEditDialog.workCopy.fields.size() > 0) + typeEditDialog.workCopy.fields.clear(); //to make sure that the memory only is freed once! + if (typeEditDialog.fields.size() > 0) + typeEditDialog.fields.clear(); //to make sure that the memory only is freed once! + EndDialog(hDlg, LOWORD(wParam)); + goto Free_TypeEditorDialog; + case WM_COMMAND: + wmId = LOWORD(wParam); + wmEvent = HIWORD(wParam); + switch (wmId) + { + case IDOK: + { + //free the original strings and replace the field's data with the work copy's field data + for (size_t i = 0; i < typeEditDialog.pType->fields.size(); i++) + { + ClassDatabaseTypeField *pOriginalTypeField = &typeEditDialog.pType->fields[i]; + if (!pOriginalTypeField->fieldName.fromStringTable) + free(const_cast(pOriginalTypeField->fieldName.str.string)); + if (!pOriginalTypeField->typeName.fromStringTable) + free(const_cast(pOriginalTypeField->typeName.str.string)); + } + typeEditDialog.pType->fields.erase(typeEditDialog.pType->fields.begin(), typeEditDialog.pType->fields.end()); + //typeEditDialog.pType->fields.clear(); + typeEditDialog.pType->fields.reserve(typeEditDialog.workCopy.fields.size()); + for (size_t i = 0; i < typeEditDialog.workCopy.fields.size(); i++) + { + ClassDatabaseTypeField *pCopiedTypeField = &typeEditDialog.workCopy.fields[i]; + typeEditDialog.pType->fields.push_back(*pCopiedTypeField); + } + typeEditDialog.workCopy.fields.clear(); + typeEditDialog.fields.clear(); + EndDialog(hDlg, LOWORD(wParam)); + goto Free_TypeEditorDialog; + } + case IDCANCEL: + { + //free the copied strings + for (size_t i = 0; i < typeEditDialog.workCopy.fields.size(); i++) + { + ClassDatabaseTypeField *pCopiedTypeField = &typeEditDialog.workCopy.fields[i]; + if (!pCopiedTypeField->fieldName.fromStringTable) + free(const_cast(pCopiedTypeField->fieldName.str.string)); + if (!pCopiedTypeField->typeName.fromStringTable) + free(const_cast(pCopiedTypeField->typeName.str.string)); + } + typeEditDialog.workCopy.fields.erase(typeEditDialog.workCopy.fields.begin(),typeEditDialog.workCopy.fields.end()); + typeEditDialog.fields.erase(typeEditDialog.fields.begin(),typeEditDialog.fields.end()); + EndDialog(hDlg, LOWORD(wParam)); + goto Free_TypeEditorDialog; + } + case IDC_BTNADD: + { + HWND hFieldTree = GetDlgItem(hDlg, IDC_TYPETREE); + if (typeEditDialog.selectedFieldIndex < (int)typeEditDialog.fields.size()) + { + //initialize the template field + addFieldDialog.typeField.fieldName.fromStringTable = false; + addFieldDialog.typeField.typeName.fromStringTable = false; + char *str = (char*)malloc(1); + __checkoutofmemory(str==NULL); + str[0] = 0; + addFieldDialog.typeField.fieldName.str.string = str; + str = (char*)malloc(1); + __checkoutofmemory(str==NULL); + str[0] = 0; + addFieldDialog.typeField.typeName.str.string = str; + addFieldDialog.typeField.depth = 0; + addFieldDialog.typeField.flags2 = 0; + addFieldDialog.typeField.isArray = 0; + addFieldDialog.typeField.size = 0; + addFieldDialog.success = false; + + if (typeEditDialog.selectedFieldIndex == -1 && typeEditDialog.fields.size() == 0) + { + DialogBox(typeDbEditDialog.hInst, MAKEINTRESOURCE(IDD_ADDFIELD), hDlg, AddTypeField); + if (addFieldDialog.success) + { + typeEditDialog.workCopy.fields.push_back(addFieldDialog.typeField); + _TypeEditor_BuildTreeView(hFieldTree); + } + } + else if (typeEditDialog.selectedFieldIndex >= 0) + { + ClassDatabaseTypeField *pField = &typeEditDialog.workCopy.fields[typeEditDialog.selectedFieldIndex]; + addFieldDialog.typeField.depth = pField->depth; + DialogBox(typeDbEditDialog.hInst, MAKEINTRESOURCE(IDD_ADDFIELD), hDlg, AddTypeField); + if (addFieldDialog.success) //if not, AddTypeField frees the strings + { + int targetIndex = typeEditDialog.selectedFieldIndex+1; + TVINSERTSTRUCT is; + if (addFieldDialog.typeField.depth > pField->depth) //is the new field a child of the selected one? + { + //make the selected field expandable if it isn't already + is.itemex.hItem = typeEditDialog.fields[typeEditDialog.selectedFieldIndex]; + is.itemex.mask = TVIF_HANDLE | TVIF_CHILDREN; + if (TreeView_GetItem(hFieldTree, &is.itemex) && is.itemex.cChildren != 1) + { + is.itemex.cChildren = 1; + is.itemex.mask = TVIF_HANDLE | TVIF_CHILDREN; + TreeView_SetItem(hFieldTree, &is.itemex); + } + //set the parent field + is.hParent = typeEditDialog.fields[typeEditDialog.selectedFieldIndex]; + } + else + { + targetIndex = -1; + for (int i = typeEditDialog.selectedFieldIndex+1; i < (int)typeEditDialog.fields.size(); i++) + { + if (typeEditDialog.workCopy.fields[i].depth <= pField->depth) + { + targetIndex = i; + break; + } + } + if (targetIndex == -1) + targetIndex = (int)typeEditDialog.fields.size(); + is.hParent = NULL; + for (int i = typeEditDialog.selectedFieldIndex-1; i >= 0; i--) + { + if (typeEditDialog.workCopy.fields[i].depth < pField->depth) + { + is.hParent = typeEditDialog.fields[i]; + break; + } + } + } + if (true) + //if ((addFieldDialog.typeField.depth > pField->depth) && (typeEditDialog.fields.size() > (targetIndex+1)) && + // (typeEditDialog.workCopy.fields[targetIndex+1].depth == addFieldDialog.typeField.depth)) + { + //rebuild the tree view because it would otherwise append the new field after the parent's last field + typeEditDialog.workCopy.fields.insert( + typeEditDialog.workCopy.fields.begin()+targetIndex, + addFieldDialog.typeField); + + //make a backup of the states (rebuilding sets it to 0) + uint8_t *stateList = (uint8_t*)malloc(typeEditDialog.fields.size() * sizeof(uint8_t)); + __checkoutofmemory(stateList==NULL); + for (size_t i = 0; i < typeEditDialog.fields.size(); i++) + stateList[i] = TreeView_GetItemState(hFieldTree, typeEditDialog.fields[i], 0x000D); //all item states except TVIS_SELECTED + + TreeView_DeleteAllItems(hFieldTree); + typeEditDialog.fields.clear(); + _TypeEditor_BuildTreeView(hFieldTree); + + //backup the expanded state + int _i = 0; + for (size_t i = 0; i < typeEditDialog.fields.size(); i++) + { + if (i == targetIndex) + continue; + TreeView_SetItemState(hFieldTree, typeEditDialog.fields[i], stateList[_i], 0x000D); + _i++; + } + free(stateList); + + TreeView_SelectItem(hFieldTree, typeEditDialog.fields[targetIndex]); + } + else + { + typeEditDialog.workCopy.fields.insert( + typeEditDialog.workCopy.fields.begin()+targetIndex, + addFieldDialog.typeField); + + WCHAR *treeViewText = _TypeEditor_MakeListViewName(&addFieldDialog.typeField); + + //prepare the ListView + is.itemex.pszText = treeViewText; + is.itemex.cchTextMax = (int)wcslen(treeViewText); + is.itemex.state = 0; + is.itemex.stateMask = 0xFF; + is.itemex.mask = TVIF_CHILDREN | TVIF_STATE | TVIF_TEXT; + is.hInsertAfter = typeEditDialog.fields[targetIndex-1]; + is.itemex.cChildren = 0; + HTREEITEM treeItem = TreeView_InsertItem(hFieldTree, &is); + free(treeViewText); + typeEditDialog.fields.insert( + typeEditDialog.fields.begin()+targetIndex, + treeItem); + TreeView_SelectItem(hFieldTree, treeItem); + } + } + } + else + { + free(const_cast(addFieldDialog.typeField.typeName.str.string)); + free(const_cast(addFieldDialog.typeField.fieldName.str.string)); + } + } + } + break; + case IDC_BTNREMOVE: + { + HWND hFieldTree = GetDlgItem(hDlg, IDC_TYPETREE); + if (typeEditDialog.selectedFieldIndex >= 0 && typeEditDialog.selectedFieldIndex < (int)typeEditDialog.fields.size()) + { + ClassDatabaseTypeField *pSelField = &typeEditDialog.workCopy.fields[typeEditDialog.selectedFieldIndex]; + uint8_t oldDepth = pSelField->depth; //after removing an item, it is better not to use pSelField anymore + int oldSel = typeEditDialog.selectedFieldIndex; + typeEditDialog.selectedFieldIndex = -1; + bool forceRemove = true; + for (int i = oldSel; i < (int)typeEditDialog.fields.size(); i++) + { + ClassDatabaseTypeField *pField = &typeEditDialog.workCopy.fields[i]; + //if the current field is (no child of)/(not) the field to delete, select it and break + if (!forceRemove && (pField->depth <= oldDepth)) + { + TreeView_SelectItem(hFieldTree, typeEditDialog.fields[i]); + typeEditDialog.selectedFieldIndex = i; + break; + } + forceRemove = false; + if (!pField->fieldName.fromStringTable) + free(const_cast(pField->fieldName.str.string)); + if (!pField->typeName.fromStringTable) + free(const_cast(pField->typeName.str.string)); + TreeView_DeleteItem(hFieldTree, typeEditDialog.fields[i]); + typeEditDialog.workCopy.fields.erase(typeEditDialog.workCopy.fields.begin()+i); + typeEditDialog.fields.erase(typeEditDialog.fields.begin()+i); + i--; + } + //if the last field was the field to delete or a child of it, select the field before it + if ((typeEditDialog.selectedFieldIndex == -1) && typeEditDialog.fields.size() > 0) + { + typeEditDialog.selectedFieldIndex = (int)typeEditDialog.fields.size()-1; + TreeView_SelectItem(hFieldTree, typeEditDialog.fields[typeEditDialog.selectedFieldIndex]); + } + } + } + break; + case IDC_EDITNAME: + { + if (typeEditDialog.selectedFieldIndex >= 0 && typeEditDialog.selectedFieldIndex < (int)typeEditDialog.fields.size()) + { + ClassDatabaseTypeField *pSelField = &typeEditDialog.workCopy.fields[typeEditDialog.selectedFieldIndex]; + HWND hNameEdit = GetDlgItem(hDlg, IDC_EDITNAME); + char *cNameBuf = GetEditTextA(hNameEdit); + if (!pSelField->fieldName.fromStringTable) + free(const_cast(pSelField->fieldName.str.string)); + pSelField->fieldName.fromStringTable = false; + pSelField->fieldName.str.string = cNameBuf; + + HWND hFieldTree = GetDlgItem(hDlg, IDC_TYPETREE); + WCHAR *treeViewText = _TypeEditor_MakeListViewName(pSelField); + TVITEMEX itemex; + itemex.hItem = typeEditDialog.fields[typeEditDialog.selectedFieldIndex]; + itemex.mask = TVIF_HANDLE | TVIF_TEXT; + itemex.pszText = treeViewText; + itemex.cchTextMax = (int)wcslen(treeViewText); + TreeView_SetItem(hFieldTree, &itemex); + free(treeViewText); + } + } + break; + case IDC_EDITTYPE: + { + if (typeEditDialog.selectedFieldIndex >= 0 && typeEditDialog.selectedFieldIndex < (int)typeEditDialog.fields.size()) + { + ClassDatabaseTypeField *pSelField = &typeEditDialog.workCopy.fields[typeEditDialog.selectedFieldIndex]; + HWND hTypeEdit = GetDlgItem(hDlg, IDC_EDITTYPE); + char *cNameBuf = GetEditTextA(hTypeEdit); + if (!pSelField->typeName.fromStringTable && pSelField->typeName.str.string != NULL) + free(const_cast(pSelField->typeName.str.string)); + pSelField->typeName.fromStringTable = false; + pSelField->typeName.str.string = cNameBuf; + + HWND hFieldTree = GetDlgItem(hDlg, IDC_TYPETREE); + WCHAR *treeViewText = _TypeEditor_MakeListViewName(pSelField); + TVITEMEX itemex; + itemex.hItem = typeEditDialog.fields[typeEditDialog.selectedFieldIndex]; + itemex.mask = TVIF_HANDLE | TVIF_TEXT; + itemex.pszText = treeViewText; + itemex.cchTextMax = (int)wcslen(treeViewText); + TreeView_SetItem(hFieldTree, &itemex); + free(treeViewText); + } + } + break; + case IDC_EDITSIZE: + { + if (typeEditDialog.selectedFieldIndex >= 0 && typeEditDialog.selectedFieldIndex < (int)typeEditDialog.fields.size()) + { + ClassDatabaseTypeField *pSelField = &typeEditDialog.workCopy.fields[typeEditDialog.selectedFieldIndex]; + HWND hSizeEdit = GetDlgItem(hDlg, IDC_EDITSIZE); + int tcTextLen = Edit_GetTextLength(hSizeEdit); + TCHAR *tcNameBuf = (WCHAR*)malloc((tcTextLen+1) * sizeof(WCHAR)); + __checkoutofmemory(tcNameBuf==NULL); + Edit_GetText(hSizeEdit, tcNameBuf, tcTextLen+1); + *_errno() = 0; + int size = _tcstol(tcNameBuf, NULL, 0); + if (errno == ERANGE) + { + *_errno() = 0; + size = (int)_tcstoul(tcNameBuf, NULL, 0); + } + if (errno != ERANGE) + { + pSelField->size = size; + } + free(tcNameBuf); + } + } + break; + case IDC_EDITVERSION: + { + if (typeEditDialog.selectedFieldIndex >= 0 && typeEditDialog.selectedFieldIndex < (int)typeEditDialog.fields.size()) + { + ClassDatabaseTypeField *pSelField = &typeEditDialog.workCopy.fields[typeEditDialog.selectedFieldIndex]; + HWND hVersionEdit = GetDlgItem(hDlg, IDC_EDITVERSION); + int tcTextLen = Edit_GetTextLength(hVersionEdit); + TCHAR *tcNameBuf = (WCHAR*)malloc((tcTextLen+1) * sizeof(WCHAR)); + __checkoutofmemory(tcNameBuf==NULL); + Edit_GetText(hVersionEdit, tcNameBuf, tcTextLen+1); + *_errno() = 0; + int version = _tcstol(tcNameBuf, NULL, 0); + if (errno == ERANGE) + { + *_errno() = 0; + version = (int)_tcstoul(tcNameBuf, NULL, 0); + } + if (errno != ERANGE) + { + pSelField->version = (uint16_t)version; + } + free(tcNameBuf); + } + } + break; + case IDC_CHECKARRAY: + if (typeEditDialog.selectedFieldIndex >= 0 && typeEditDialog.selectedFieldIndex < (int)typeEditDialog.fields.size()) + { + ClassDatabaseTypeField *pSelField = &typeEditDialog.workCopy.fields[typeEditDialog.selectedFieldIndex]; + HWND hArrayCb = GetDlgItem(hDlg, IDC_CHECKARRAY); + if (Button_GetCheck(hArrayCb) == BST_CHECKED) + pSelField->isArray = 1; + else + pSelField->isArray = 0; + } + break; + case IDC_CHECKALIGN: + if (typeEditDialog.selectedFieldIndex >= 0 && typeEditDialog.selectedFieldIndex < (int)typeEditDialog.fields.size()) + { + ClassDatabaseTypeField *pSelField = &typeEditDialog.workCopy.fields[typeEditDialog.selectedFieldIndex]; + HWND hAlignCb = GetDlgItem(hDlg, IDC_CHECKALIGN); + if (Button_GetCheck(hAlignCb) == BST_CHECKED) + pSelField->flags2 = pSelField->flags2 | 0x4000; + else + pSelField->flags2 = pSelField->flags2 & (~0x4000); + } + break; + } + break; + case WM_NOTIFY: + { + switch (((LPNMHDR)lParam)->code) + { + case TVN_SELCHANGED: + { + LPNMTREEVIEW info = ((LPNMTREEVIEW)lParam); + if (info->hdr.idFrom == IDC_TYPETREE) + { + //DoUpdateFieldList: + HWND hFieldList = GetDlgItem(hDlg, IDC_TYPETREE); + HTREEITEM selTreeItem = TreeView_GetSelection(hFieldList);//info->itemNew.hItem + int selection = -1; + for (int i = 0; i < (int)typeEditDialog.fields.size(); i++) + { + if (typeEditDialog.fields[i] == selTreeItem) + { + selection = i; + break; + } + } + if ((selection != typeEditDialog.selectedFieldIndex) && (selection < (int)typeEditDialog.fields.size()) && (selection >= 0)) + { + typeEditDialog.selectedFieldIndex = selection; + HWND hFieldTypeEdit = GetDlgItem(hDlg, IDC_EDITTYPE); + HWND hFieldNameEdit = GetDlgItem(hDlg, IDC_EDITNAME); + HWND hSizeEdit = GetDlgItem(hDlg, IDC_EDITSIZE); + HWND hVersionEdit = GetDlgItem(hDlg, IDC_EDITVERSION); + HWND hArrayCb = GetDlgItem(hDlg, IDC_CHECKARRAY); + HWND hAlignCb = GetDlgItem(hDlg, IDC_CHECKALIGN); + ClassDatabaseTypeField *pSelField = &typeEditDialog.workCopy.fields[selection]; + + const char *fieldType = pSelField->typeName.GetString(&typeDbEditDialog.classDatabase); + SetEditTextA(hFieldTypeEdit, (fieldType == NULL) ? "" : fieldType); + const char *fieldName = pSelField->fieldName.GetString(&typeDbEditDialog.classDatabase); + SetEditTextA(hFieldNameEdit, (fieldName == NULL) ? "" : fieldName); + + TCHAR sprntTmp[12]; + _stprintf(sprntTmp, TEXT("%d"), (int)pSelField->size); + Edit_SetText(hSizeEdit, sprntTmp); + + _stprintf(sprntTmp, TEXT("%d"), (int)pSelField->version); + Edit_SetText(hVersionEdit, sprntTmp); + + if (pSelField->isArray & 1) + Button_SetCheck(hArrayCb, TRUE); + else + Button_SetCheck(hArrayCb, FALSE); + + if (pSelField->flags2 & 0x4000) + Button_SetCheck(hAlignCb, TRUE); + else + Button_SetCheck(hAlignCb, FALSE); + } + } + } + } + } + break; + } + return (INT_PTR)FALSE; +Free_TypeEditorDialog: + //typeEditDialog.workCopy.~ClassDatabaseType(); + return (INT_PTR)TRUE; +} + +INT_PTR CALLBACK AddTypeField(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ int wmId, wmEvent; + UNREFERENCED_PARAMETER(lParam); + switch (message) + { + case WM_INITDIALOG: + { + addFieldDialog.success = false; + if (addFieldDialog.typeField.depth == 0) + { + HWND hIsChildCB = GetDlgItem(hDlg, IDC_CHECKISCHILD); + Button_SetCheck(hIsChildCB, BST_CHECKED); + EnableWindow(hIsChildCB, FALSE); + } + } + return (INT_PTR)TRUE; + + case WM_CLOSE: + case WM_DESTROY: + break; + case WM_COMMAND: + wmId = LOWORD(wParam); + wmEvent = HIWORD(wParam); + switch (wmId) + { + case IDOK: + { + HWND hIsChildCB = GetDlgItem(hDlg, IDC_CHECKISCHILD); + HWND hTypeEdit = GetDlgItem(hDlg, IDC_EDITTYPE); + HWND hNameEdit = GetDlgItem(hDlg, IDC_EDITNAME); + HWND hSizeEdit = GetDlgItem(hDlg, IDC_EDITSIZE); + HWND hArrayCB = GetDlgItem(hDlg, IDC_CHECKARRAY); + HWND hAlignCB = GetDlgItem(hDlg, IDC_CHECKALIGN); + + //is child + if (Button_GetCheck(hIsChildCB) == BST_CHECKED) + addFieldDialog.typeField.depth++; + else if (addFieldDialog.typeField.depth == 0) + break; //Shouldn't (normally) happen, since the button is supposed to be stuck to 'checked' in this case. + //is array + addFieldDialog.typeField.isArray = (Button_GetCheck(hArrayCB)==BST_CHECKED) ? 1 : 0; + //align + if (Button_GetCheck(hAlignCB)==BST_CHECKED) + addFieldDialog.typeField.flags2 = addFieldDialog.typeField.flags2 | 0x4000; + else + addFieldDialog.typeField.flags2 = addFieldDialog.typeField.flags2 & (~0x4000); + //size + int tcTextLen = Edit_GetTextLength(hSizeEdit); + TCHAR *tcNameBuf = (WCHAR*)malloc((tcTextLen+1) * sizeof(WCHAR)); + __checkoutofmemory(tcNameBuf==NULL); + Edit_GetText(hSizeEdit, tcNameBuf, tcTextLen+1); + int size = _tcstol(tcNameBuf, NULL, 0); + if (errno != ERANGE) + addFieldDialog.typeField.size = (DWORD)size; + else + addFieldDialog.typeField.size = (DWORD)-1; + //version + addFieldDialog.typeField.version = 1; + + char *cTypeBuf = GetEditTextA(hTypeEdit); + if (!addFieldDialog.typeField.typeName.fromStringTable && addFieldDialog.typeField.typeName.str.string != NULL) + free(const_cast(addFieldDialog.typeField.typeName.str.string)); + addFieldDialog.typeField.typeName.fromStringTable = false; + addFieldDialog.typeField.typeName.str.string = cTypeBuf; + char *cNameBuf = GetEditTextA(hNameEdit); + if (!addFieldDialog.typeField.fieldName.fromStringTable && addFieldDialog.typeField.typeName.str.string != NULL) + free(const_cast(addFieldDialog.typeField.fieldName.str.string)); + addFieldDialog.typeField.fieldName.fromStringTable = false; + addFieldDialog.typeField.fieldName.str.string = cNameBuf; + + addFieldDialog.success = true; + EndDialog(hDlg, LOWORD(wParam)); + return (INT_PTR)TRUE; + } + case IDCANCEL: + //free the strings allocated by the caller + if (!addFieldDialog.typeField.typeName.fromStringTable && addFieldDialog.typeField.typeName.str.string != NULL) + free(const_cast(addFieldDialog.typeField.typeName.str.string)); + if (!addFieldDialog.typeField.fieldName.fromStringTable && addFieldDialog.typeField.typeName.str.string != NULL) + free(const_cast(addFieldDialog.typeField.fieldName.str.string)); + EndDialog(hDlg, LOWORD(wParam)); + return (INT_PTR)TRUE; + } + break; + } + return (INT_PTR)FALSE; +} \ No newline at end of file diff --git a/UABE_Win32/TypeDatabaseEditor.h b/UABE_Win32/TypeDatabaseEditor.h new file mode 100644 index 0000000..380cf4e --- /dev/null +++ b/UABE_Win32/TypeDatabaseEditor.h @@ -0,0 +1,3 @@ +#pragma once + +void OpenTypeDatabaseEditor(HINSTANCE hInstance, HWND hParent); \ No newline at end of file diff --git a/UABE_Win32/TypeDbPackageEditor.cpp b/UABE_Win32/TypeDbPackageEditor.cpp new file mode 100644 index 0000000..281b174 --- /dev/null +++ b/UABE_Win32/TypeDbPackageEditor.cpp @@ -0,0 +1,419 @@ +#include "stdafx.h" +#include "resource.h" +#include "TypeDbPackageEditor.h" + +#include "Win32AppContext.h" +#include "FileDialog.h" + +#include "../AssetsTools/ClassDatabaseFile.h" +#include "../AssetsTools/AssetsFileReader.h" + +#include + +INT_PTR CALLBACK TypeDbPackageEditor(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); + +struct TypePkEditDialog +{ + HINSTANCE hInst = NULL; + int selectedDbIndex = -1; + WCHAR *filePath = nullptr; + ClassDatabasePackage classPackage; +} typePkEditDialog; +void OpenTypeDbPackageEditor(HINSTANCE hInstance, HWND hParent) +{ + typePkEditDialog.hInst = hInstance; + DialogBox(hInstance, MAKEINTRESOURCE(IDD_EDITTYPEPAK), hParent, TypeDbPackageEditor); +} + +static char *GetEditTextA(HWND hEdit) +{ + char *cNameBuf; + #ifdef _UNICODE + int wcTextLen = Edit_GetTextLength(hEdit); + WCHAR *wcNameBuf = (WCHAR*)malloc((wcTextLen+1) * sizeof(WCHAR)); + __checkoutofmemory(wcNameBuf==NULL); + Edit_GetText(hEdit, wcNameBuf, wcTextLen+1); + wcNameBuf[wcTextLen] = 0; + + int cTextLen = WideCharToMultiByte(CP_UTF8, 0, wcNameBuf, wcTextLen, NULL, 0, NULL, NULL); + cNameBuf = (char*)malloc((cTextLen+1) * sizeof(char)); + __checkoutofmemory(cNameBuf==NULL); + WideCharToMultiByte(CP_UTF8, 0, wcNameBuf, wcTextLen, cNameBuf, cTextLen, NULL, NULL); + cNameBuf[cTextLen] = 0; + free(wcNameBuf); + #else + int cTextLen = Edit_GetTextLength(hEdit); + cNameBuf = (char*)malloc(cTextLen+1); + __checkoutofmemory(cNameBuf==NULL); + Edit_GetText(hEdit, cNameBuf, cTextLen+1); + cNameBuf[cTextLen] = 0; + #endif + return cNameBuf; +} +static void SetEditTextA(HWND hEdit, char *text) +{ + #ifdef _UNICODE + size_t textLen = strlen(text); + int wcharCount = MultiByteToWideChar(CP_UTF8, 0, text, (int)textLen, NULL, 0); + WCHAR *wcTextBuf = (WCHAR*)malloc((wcharCount+1) * sizeof(WCHAR)); + __checkoutofmemory(wcTextBuf==NULL); + MultiByteToWideChar(CP_UTF8, 0, text, (int)textLen, wcTextBuf, wcharCount); + wcTextBuf[wcharCount] = 0; + Edit_SetText(hEdit, wcTextBuf); + free(wcTextBuf); + #else + Edit_SetText(hNameEdit, text); + #endif +} + +INT_PTR CALLBACK TypeDbPackageEditor(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + int wmId, wmEvent; + int moveDirection = 0; //only used for the Up/Down buttons + UNREFERENCED_PARAMETER(lParam); + switch (message) + { + case WM_INITDIALOG: + { + HWND hOptimizeFastCB = GetDlgItem(hDlg, IDC_CKOPTIMIZEFAST); + Button_SetCheck(hOptimizeFastCB, true); + HWND hOptimizeSlowCB = GetDlgItem(hDlg, IDC_CKOPTIMIZESLOW); + Button_SetCheck(hOptimizeSlowCB, false); + HWND hCompressLZ4CB = GetDlgItem(hDlg, IDC_CKCOMPRESSLZ4); + Button_SetCheck(hCompressLZ4CB, false); + HWND hCompressLZMACB = GetDlgItem(hDlg, IDC_CKCOMPRESSLZMA); + Button_SetCheck(hCompressLZMACB, true); + + WCHAR *filePathBuf; + HRESULT hr = ShowFileOpenDialog(hDlg, &filePathBuf, L"*.tpk|Type database package:", + nullptr, nullptr, nullptr, + UABE_FILEDIALOG_CLDB_GUID); + __checkoutofmemory(hr==E_OUTOFMEMORY); + if (SUCCEEDED(hr)) + { + IAssetsReader *pDbReader = Create_AssetsReaderFromFile(filePathBuf, true, RWOpenFlags_Immediately); + //FILE *pDbFile = NULL; + //_wfopen_s(&pDbFile, filePathBuf, L"rb"); + if (pDbReader != NULL) + { + if (!typePkEditDialog.classPackage.Read(pDbReader)) + { + //treat it like an empty package + memset(&typePkEditDialog.classPackage.header, 0, sizeof(ClassDatabasePackageHeader)); + typePkEditDialog.classPackage.files = NULL; + typePkEditDialog.classPackage.stringTable = NULL; + } + Free_AssetsReader(pDbReader); + //fclose(pDbFile); + typePkEditDialog.filePath = filePathBuf; + + HWND hDblist = GetDlgItem(hDlg, IDC_DBLIST); + for (DWORD i = 0; i < (DWORD)typePkEditDialog.classPackage.header.files.size(); i++) + { + ClassDatabaseFileRef *pFileRef = &typePkEditDialog.classPackage.header.files[i]; + const char *dbName = pFileRef->name; + if (dbName != NULL) + { +#ifdef _UNICODE + size_t dbNameLen = strlen(dbName); + int wcharCount = MultiByteToWideChar(CP_UTF8, 0, dbName, (int)dbNameLen, NULL, 0); + WCHAR *wcNameBuf = (WCHAR*)malloc((wcharCount+1) * sizeof(WCHAR)); + __checkoutofmemory(wcNameBuf==NULL); + MultiByteToWideChar(CP_UTF8, 0, dbName, (int)dbNameLen, wcNameBuf, wcharCount); + wcNameBuf[wcharCount] = 0; + ListBox_AddString(hDblist, wcNameBuf); + free(wcNameBuf); +#else + ListBox_AddString(hDblist, typeName); +#endif + } + else + ListBox_AddString(hDblist, TEXT("")); + } + typePkEditDialog.selectedDbIndex = -1; + ListBox_SetCurSel(hDblist, 0); + goto DoUpdateDbList; + } + else + { + FreeCOMFilePathBuf(&filePathBuf); + MessageBox(hDlg, TEXT("Unable to open the file!"), TEXT("ERROR"), 16); + EndDialog(hDlg, LOWORD(wParam)); + } + } + else + EndDialog(hDlg, LOWORD(wParam)); + } + return (INT_PTR)TRUE; + + case WM_CLOSE: + case WM_DESTROY: + EndDialog(hDlg, LOWORD(wParam)); + goto Free_TypePkEditorDialog; + case WM_COMMAND: + wmId = LOWORD(wParam); + wmEvent = HIWORD(wParam); + switch (wmId) + { + case IDC_CKCOMPRESSLZ4: + { + HWND hCompressLZ4CB = GetDlgItem(hDlg, IDC_CKCOMPRESSLZ4); + HWND hCompressLZMACB = GetDlgItem(hDlg, IDC_CKCOMPRESSLZMA); + if (Button_GetCheck(hCompressLZ4CB)) + Button_SetCheck(hCompressLZMACB, 0); + } + break; + case IDC_CKCOMPRESSLZMA: + { + HWND hCompressLZ4CB = GetDlgItem(hDlg, IDC_CKCOMPRESSLZ4); + HWND hCompressLZMACB = GetDlgItem(hDlg, IDC_CKCOMPRESSLZMA); + if (Button_GetCheck(hCompressLZMACB)) + Button_SetCheck(hCompressLZ4CB, 0); + } + break; + case IDC_CKOPTIMIZEFAST: + { + HWND hOptimizeFastCB = GetDlgItem(hDlg, IDC_CKOPTIMIZEFAST); + HWND hOptimizeSlowCB = GetDlgItem(hDlg, IDC_CKOPTIMIZESLOW); + if (Button_GetCheck(hOptimizeFastCB)) + Button_SetCheck(hOptimizeSlowCB, 0); + } + break; + case IDC_CKOPTIMIZESLOW: + { + HWND hOptimizeFastCB = GetDlgItem(hDlg, IDC_CKOPTIMIZEFAST); + HWND hOptimizeSlowCB = GetDlgItem(hDlg, IDC_CKOPTIMIZESLOW); + if (Button_GetCheck(hOptimizeSlowCB)) + Button_SetCheck(hOptimizeFastCB, 0); + } + break; + case IDOK: + { + HWND hOptimizeFastCB = GetDlgItem(hDlg, IDC_CKOPTIMIZEFAST); + HWND hOptimizeSlowCB = GetDlgItem(hDlg, IDC_CKOPTIMIZESLOW); + bool optimizePlacebo = Button_GetCheck(hOptimizeSlowCB)?true:false; + bool optimizeFast = (Button_GetCheck(hOptimizeFastCB)?true:false); + int optimize = optimizePlacebo ? 2 : (optimizeFast ? 1 : 0); + HWND hCompressLZ4CB = GetDlgItem(hDlg, IDC_CKCOMPRESSLZ4); + HWND hCompressLZMACB = GetDlgItem(hDlg, IDC_CKCOMPRESSLZMA); + DWORD compress = (Button_GetCheck(hCompressLZ4CB) ? 1 : (Button_GetCheck(hCompressLZMACB) ? 2 : 0)) | 0x80; + IAssetsWriter *pDbWriter = Create_AssetsWriterToFile(typePkEditDialog.filePath, true, true, RWOpenFlags_Immediately); + //FILE *pDbFile = NULL; + //_wfopen_s(&pDbFile, typePkEditDialog.filePath, L"wb"); + if (pDbWriter != NULL) + { + typePkEditDialog.classPackage.Write(pDbWriter, 0, optimize, compress); + Free_AssetsWriter(pDbWriter); + //fclose(pDbFile); + } + else + { + MessageBox(hDlg, TEXT("Unable to open the file for writing!"), TEXT("ERROR"), 16); + break; + } + } + case IDCANCEL: + EndDialog(hDlg, LOWORD(wParam)); + goto Free_TypePkEditorDialog; + case IDC_BTNDBEXPORT: + { + if (typePkEditDialog.selectedDbIndex >= 0 && + typePkEditDialog.selectedDbIndex < (int)typePkEditDialog.classPackage.header.fileCount) + { + + WCHAR *filePathBuf; + HRESULT hr = ShowFileSaveDialog(hDlg, &filePathBuf, L"*.dat|Type database:", + nullptr, nullptr, nullptr, + UABE_FILEDIALOG_CLDB_GUID); + __checkoutofmemory(hr==E_OUTOFMEMORY); + if (SUCCEEDED(hr)) + { + IAssetsWriter *pDbWriter = Create_AssetsWriterToFile(filePathBuf, true, true, RWOpenFlags_Immediately); + //FILE *pDbFile = NULL; + //_wfopen_s(&pDbFile, filePathBuf, L"wb"); + FreeCOMFilePathBuf(&filePathBuf); + if (pDbWriter != NULL) + { + typePkEditDialog.classPackage.files[typePkEditDialog.selectedDbIndex]->Write( + pDbWriter, 0 + ); + Free_AssetsWriter(pDbWriter); + //fclose(pDbFile); + } + else + MessageBox(hDlg, TEXT("Unable to open the file for writing!"), TEXT("ERROR"), 16); + } + //typeEditDialog.pType = &typeDbEditDialog.classDatabase.classes[typeDbEditDialog.selectedTypeIndex]; + //DialogBox(typeDbEditDialog.hInst, MAKEINTRESOURCE(IDD_EDITTYPE), hDlg, TypeEditor); + } + } + break; + case IDC_BTNDBIMPORT: + { + WCHAR *filePathBuf; + HRESULT hr = ShowFileOpenDialog(hDlg, &filePathBuf, L"*.dat|Type database:", + nullptr, nullptr, nullptr, + UABE_FILEDIALOG_CLDB_GUID); + __checkoutofmemory(hr==E_OUTOFMEMORY); + if (SUCCEEDED(hr)) + { + IAssetsReader *pDbReader = Create_AssetsReaderFromFile(filePathBuf, true, RWOpenFlags_Immediately); + //FILE *pDbFile = NULL; + //_wfopen_s(&pDbFile, filePathBuf, L"rb"); + FreeCOMFilePathBuf(&filePathBuf); + if (pDbReader != NULL) + { + if (typePkEditDialog.classPackage.ImportFile(pDbReader)) + { + HWND hDbList = GetDlgItem(hDlg, IDC_DBLIST); + ListBox_AddString(hDbList, TEXT("")); + ListBox_SetCurSel(hDbList, typePkEditDialog.classPackage.header.fileCount - 1); + } + else + MessageBox(hDlg, TEXT("Unable to import the type database!"), TEXT("ERROR"), 16); + Free_AssetsReader(pDbReader); + //fclose(pDbFile); + } + else + MessageBox(hDlg, TEXT("Unable to open the file for reading!"), TEXT("ERROR"), 16); + } + goto DoUpdateDbList; + } + break; + case IDC_BTNREMOVE: + { + if (typePkEditDialog.selectedDbIndex >= 0 && + typePkEditDialog.selectedDbIndex < (int)typePkEditDialog.classPackage.header.fileCount) + { + int selection = typePkEditDialog.selectedDbIndex; + HWND hDbList = GetDlgItem(hDlg, IDC_DBLIST); + if (typePkEditDialog.classPackage.RemoveFile((DWORD)selection)) + { + ListBox_DeleteString(hDbList, selection); + typePkEditDialog.selectedDbIndex = -1; + } + if (selection < (int)typePkEditDialog.classPackage.header.fileCount) + ListBox_SetCurSel(hDbList, selection); + else if (selection > 0) + ListBox_SetCurSel(hDbList, selection-1); + goto DoUpdateDbList; + } + } + break; + case IDC_EDITNAME: + { + if (typePkEditDialog.selectedDbIndex >= 0 && + typePkEditDialog.selectedDbIndex < (int)typePkEditDialog.classPackage.header.fileCount) + { + ClassDatabaseFileRef *pSelectedRef = &typePkEditDialog.classPackage.header.files[typePkEditDialog.selectedDbIndex]; + HWND hNameEdit = GetDlgItem(hDlg, IDC_EDITNAME); + HWND hDblist = GetDlgItem(hDlg, IDC_DBLIST); + char *cNameBuf; + #ifdef _UNICODE + int wcTextLen = Edit_GetTextLength(hNameEdit); + WCHAR *wcNameBuf = (WCHAR*)malloc((wcTextLen+1) * sizeof(WCHAR)); + __checkoutofmemory(wcNameBuf==NULL); + Edit_GetText(hNameEdit, wcNameBuf, wcTextLen+1); + wcNameBuf[wcTextLen] = 0; + + ListBox_DeleteString(hDblist, typePkEditDialog.selectedDbIndex); + ListBox_InsertString(hDblist, typePkEditDialog.selectedDbIndex, wcNameBuf); + + int cTextLen = WideCharToMultiByte(CP_UTF8, 0, wcNameBuf, wcTextLen, NULL, 0, NULL, NULL); + cNameBuf = (char*)malloc((cTextLen+1) * sizeof(char)); + __checkoutofmemory(cNameBuf==NULL); + WideCharToMultiByte(CP_UTF8, 0, wcNameBuf, wcTextLen, cNameBuf, cTextLen, NULL, NULL); + cNameBuf[cTextLen] = 0; + free(wcNameBuf); + #else + int cTextLen = Edit_GetTextLength(hNameEdit); + cNameBuf = (char*)malloc(cTextLen+1); + __checkoutofmemory(cNameBuf==NULL); + Edit_GetText(hNameEdit, cNameBuf, cTextLen+1); + cNameBuf[cTextLen] = 0; + + ListBox_DeleteString(hAssetlist, typeDbEditDialog.selectedTypeIndex); + ListBox_InsertString(hAssetlist, typeDbEditDialog.selectedTypeIndex, cNameBuf); + #endif + strncpy(pSelectedRef->name, cNameBuf, 15); + pSelectedRef->name[15] = 0; + ListBox_SetCurSel(hDblist, typePkEditDialog.selectedDbIndex); + free(cNameBuf); + } + } + break; + case IDC_DBLIST: + DoUpdateDbList: + { + HWND hDbList = GetDlgItem(hDlg, IDC_DBLIST); + unsigned int selection = (unsigned int)ListBox_GetCurSel(hDbList); + if ((selection != typePkEditDialog.selectedDbIndex) && + selection < typePkEditDialog.classPackage.header.fileCount) + { + typePkEditDialog.selectedDbIndex = (int)selection; + HWND hNameEdit = GetDlgItem(hDlg, IDC_EDITNAME); + ClassDatabaseFileRef *pSelectedRef = &typePkEditDialog.classPackage.header.files[typePkEditDialog.selectedDbIndex]; + const char *dbName = pSelectedRef->name; + { + #ifdef _UNICODE + size_t dbNameLen = strlen(pSelectedRef->name); + int wcharCount = MultiByteToWideChar(CP_UTF8, 0, dbName, (int)dbNameLen, NULL, 0); + WCHAR *wcNameBuf = (WCHAR*)malloc((wcharCount+1) * sizeof(WCHAR)); + __checkoutofmemory(wcNameBuf==NULL); + MultiByteToWideChar(CP_UTF8, 0, dbName, (int)dbNameLen, wcNameBuf, wcharCount); + wcNameBuf[wcharCount] = 0; + Edit_SetText(hNameEdit, wcNameBuf); + free(wcNameBuf); + #else + Edit_SetText(hNameEdit, dbName); + #endif + } + } + } + break; + case IDC_BTNMOVEUP: + { + moveDirection = -1; + GOTO_HANDLEMOVEBTN: + if ((typePkEditDialog.selectedDbIndex + moveDirection) >= 0 && + (typePkEditDialog.selectedDbIndex + moveDirection) < (int)typePkEditDialog.classPackage.header.fileCount) + { + ClassDatabaseFileRef *pSelectedRef = &typePkEditDialog.classPackage.header.files[typePkEditDialog.selectedDbIndex]; + HWND hDblist = GetDlgItem(hDlg, IDC_DBLIST); + + int tTextLen = ListBox_GetTextLen(hDblist, typePkEditDialog.selectedDbIndex); + TCHAR *tNameBuf = (WCHAR*)malloc((tTextLen+1) * sizeof(TCHAR)); + __checkoutofmemory(tNameBuf==NULL); + ListBox_GetText(hDblist, typePkEditDialog.selectedDbIndex, tNameBuf); + tNameBuf[tTextLen] = 0; + + ListBox_DeleteString(hDblist, typePkEditDialog.selectedDbIndex); + ListBox_InsertString(hDblist, typePkEditDialog.selectedDbIndex + moveDirection, tNameBuf); + ClassDatabaseFileRef tmpRef = typePkEditDialog.classPackage.header.files[(DWORD)typePkEditDialog.selectedDbIndex]; + typePkEditDialog.classPackage.header.files.erase( + typePkEditDialog.classPackage.header.files.begin() + (DWORD)typePkEditDialog.selectedDbIndex); + typePkEditDialog.classPackage.header.files.insert( + typePkEditDialog.classPackage.header.files.begin() + (DWORD)(typePkEditDialog.selectedDbIndex + moveDirection), tmpRef); + + free(tNameBuf); + + ListBox_SetCurSel(hDblist, typePkEditDialog.selectedDbIndex + moveDirection); + typePkEditDialog.selectedDbIndex = typePkEditDialog.selectedDbIndex + moveDirection; + } + } + break; + case IDC_BTNMOVEDOWN: + { + moveDirection = 1; + goto GOTO_HANDLEMOVEBTN; + } + break; + } + break; + } + return (INT_PTR)FALSE; +Free_TypePkEditorDialog: + typePkEditDialog.classPackage.Clear(); + FreeCOMFilePathBuf(&typePkEditDialog.filePath); + return (INT_PTR)TRUE; +} \ No newline at end of file diff --git a/UABE_Win32/TypeDbPackageEditor.h b/UABE_Win32/TypeDbPackageEditor.h new file mode 100644 index 0000000..884566d --- /dev/null +++ b/UABE_Win32/TypeDbPackageEditor.h @@ -0,0 +1,3 @@ +#pragma once + +void OpenTypeDbPackageEditor(HINSTANCE hInstance, HWND hParent); \ No newline at end of file diff --git a/UABE_Win32/UABE_Win32.manifest b/UABE_Win32/UABE_Win32.manifest new file mode 100644 index 0000000..37751c2 --- /dev/null +++ b/UABE_Win32/UABE_Win32.manifest @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + diff --git a/UABE_Win32/Win32AppContext.cpp b/UABE_Win32/Win32AppContext.cpp new file mode 100644 index 0000000..3e3cd23 --- /dev/null +++ b/UABE_Win32/Win32AppContext.cpp @@ -0,0 +1,306 @@ +#include "stdafx.h" +#include "Win32AppContext.h" +#include +#include +#include +#include "BatchImportDialog.h" +#include "Win32PluginManager.h" + +Win32AppContext::Win32AppContext(HINSTANCE hInstance, const std::string &baseDir) + : mainWindow(hInstance), baseDir(baseDir), handlingMessages(false), messagePosted(false), + gcMemoryLimit(0), gcMinAge(0) +{ + InitializeCriticalSection(&this->messageMutex); + mainWindow.setContext(this); +} +Win32AppContext::~Win32AppContext() +{ + DeleteCriticalSection(&this->messageMutex); +} + +void Win32AppContext::signalMainThread(EAppContextMsg message, void *args) +{ + bool notifyMainThread = true; + EnterCriticalSection(&this->messageMutex); + if (!this->messageQueue.empty() && messagePosted) + notifyMainThread = false; //No need to send another notification. + this->messageQueue.push_back(std::pair(message, args)); + if (mainWindow.hDlg != NULL) + { + if (notifyMainThread) + messagePosted = mainWindow.onAppContextMessageAsync() || messagePosted; + } + else + { + //The program is probably exiting, waiting for the last tasks to complete. + //Since the main thread is inactive, it is sufficient to call handleMessages in a critical section. + //(Note: Enter-/LeaveCriticalSection allows recursion) + handleMessages(); + } + LeaveCriticalSection(&this->messageMutex); +} +void Win32AppContext::handleMessages() +{ + std::vector> messageQueue; + EnterCriticalSection(&this->messageMutex); + if (handlingMessages) + { + //May be called in parallel only if the window is destructed (by signalMainThread). + LeaveCriticalSection(&this->messageMutex); + return; + } + messagePosted = false; + handlingMessages = true; + + while (this->messageQueue.size() > 0) { + this->messageQueue.swap(messageQueue); + LeaveCriticalSection(&this->messageMutex); + for (size_t i = 0; i < messageQueue.size(); ++i) + { + EAppContextMsg message = messageQueue[i].first; + void *args = messageQueue[i].second; + this->processMessage(message, args); + } + messageQueue.clear(); + EnterCriticalSection(&this->messageMutex); + } + handlingMessages = false; + LeaveCriticalSection(&this->messageMutex); +} +bool Win32AppContext::processMessage(EAppContextMsg message, void *args) +{ + //switch ((EWin32AppContextMsg)message) + //{ + // default: + return AppContext::processMessage(message, args); + //} +} + +void Win32AppContext::OnUpdateContainers(AssetsFileContextInfo *info) +{ + mainWindow.OnUpdateContainers(info); +} +void Win32AppContext::OnChangeAsset(AssetsFileContextInfo *pFile, pathid_t pathID, bool wasRemoved) +{ + AppContext::OnChangeAsset(pFile, pathID, wasRemoved); + mainWindow.OnChangeAsset(pFile, pathID, wasRemoved); +} +void Win32AppContext::OnChangeBundleEntry(BundleFileContextInfo *pFile, size_t index) +{ + AppContext::OnChangeBundleEntry(pFile, index); + mainWindow.OnChangeBundleEntry(pFile, index); +} +void Win32AppContext::OnUpdateDependencies(AssetsFileContextInfo *info, size_t from, size_t to) +{ + AppContext::OnUpdateDependencies(info, from, to); + mainWindow.OnUpdateDependencies(info, from, to); +} //from/to: indices for info->references +std::shared_ptr Win32AppContext::OnFileOpenAsBundle(std::shared_ptr pTask, BundleFileContext *pContext, + EBundleFileOpenStatus openStatus, unsigned int parentFileID, unsigned int directoryEntryIdx) +{ + std::shared_ptr pInfo = AppContext::OnFileOpenAsBundle(pTask, pContext, openStatus, parentFileID, directoryEntryIdx); + if (pInfo == nullptr) + return nullptr; + BundleFileContextInfo *pInfo_Bundle = reinterpret_cast(pInfo.get()); + if (openStatus != BundleFileOpenStatus_CompressedDirectory) + pInfo_Bundle->onDirectoryReady(*this); + if (!mainWindow.OnFileEntryLoadSuccess(pTask.get(), pInfo, static_cast(openStatus))) + RemoveContextInfo(pInfo.get()); + return pInfo; +} +std::shared_ptr Win32AppContext::OnFileOpenAsAssets(std::shared_ptr pTask, AssetsFileContext *pContext, + EAssetsFileOpenStatus openStatus, unsigned int parentFileID, unsigned int directoryEntryIdx) +{ + std::shared_ptr pInfo = AppContext::OnFileOpenAsAssets(pTask, pContext, openStatus, parentFileID, directoryEntryIdx); + if (pInfo == nullptr) + return nullptr; + AssetsFileContextInfo *pInfo_Assets = reinterpret_cast(pInfo.get()); + if (mainWindow.OnFileEntryLoadSuccess(pTask.get(), pInfo, static_cast(openStatus))) + { + if (!pInfo_Assets->FindClassDatabase(classPackage)) + mainWindow.OnFindClassDatabaseFailure(pInfo_Assets, classPackage); + pInfo_Assets->EnqueueContainersTask(*this, std::shared_ptr(pInfo, pInfo_Assets)); + } + else + RemoveContextInfo(pInfo.get()); + return pInfo; +} +std::shared_ptr Win32AppContext::OnFileOpenAsResources(std::shared_ptr pTask, ResourcesFileContext *pContext, + unsigned int parentFileID, unsigned int directoryEntryIdx) +{ + std::shared_ptr pInfo = AppContext::OnFileOpenAsResources(pTask, pContext, parentFileID, directoryEntryIdx); + if (pInfo == nullptr) + return nullptr; + if (!mainWindow.OnFileEntryLoadSuccess(pTask.get(), pInfo, static_cast(0))) + RemoveContextInfo(pInfo.get()); + return pInfo; +} +std::shared_ptr Win32AppContext::OnFileOpenAsGeneric(std::shared_ptr pTask, GenericFileContext *pContext, unsigned int parentFileID, unsigned int directoryEntryIdx) +{ + std::shared_ptr pInfo = AppContext::OnFileOpenAsGeneric(pTask, pContext, parentFileID, directoryEntryIdx); + if (pInfo == nullptr) + return nullptr; + if (!mainWindow.OnFileEntryLoadSuccess(pTask.get(), pInfo, static_cast(0))) + RemoveContextInfo(pInfo.get()); + return pInfo; +} +void Win32AppContext::OnFileOpenFail(std::shared_ptr pTask, std::string &logText) +{ + mainWindow.OnFileEntryLoadFailure(pTask.get(), logText); +} +void Win32AppContext::OnDecompressBundle(BundleFileContextInfo::DecompressTask *pTask, TaskResult result) +{ + if (result >= 0) + mainWindow.OnDecompressSuccess(pTask); + else + mainWindow.OnDecompressFailure(pTask); +} +void Win32AppContext::RemoveContextInfo(FileContextInfo *info) +{ + AppContext::RemoveContextInfo(info); + mainWindow.OnRemoveContextInfo(info); +} + +void Win32AppContext::LoadSettings() +{ + gcMinAge = 15; +#ifdef __X64 + gcMemoryLimit = 2 * 1024 * 1024 * 1024ULL; //2GiB default +#else + gcMemoryLimit = 1 * 1024 * 1024 * 1024; //1GiB default +#endif + autoDetectDependencies = true; + //TODO: Load from settings file. +} + +int Win32AppContext::Run(size_t argc, char **argv) +{ + LoadSettings(); + std::string loadErrorMessage; + if (!this->LoadClassDatabasePackage(baseDir, loadErrorMessage)) + { + if (loadErrorMessage.size() == 0) + loadErrorMessage = "Unable to load the class database package file (classdata.tpk)."; + MessageBoxA(NULL, loadErrorMessage.c_str(), "UABE", 16); + } + taskManager.setMaxThreads(4); + mcTreeList_Initialize(); + mcMditab_Initialize(); + + INITCOMMONCONTROLSEX init; + init.dwSize = sizeof(init); + init.dwICC = ICC_LISTVIEW_CLASSES | ICC_TREEVIEW_CLASSES | ICC_BAR_CLASSES | ICC_TAB_CLASSES | ICC_PROGRESS_CLASS | ICC_STANDARD_CLASSES | ICC_UPDOWN_CLASS; + InitCommonControlsEx(&init); + + mainWindow.Initialize(); + loadAllPlugins(*this, this->plugins, this->getBaseDir() + "./Plugins"); + int ret = mainWindow.HandleMessages(); + mcTreeList_Terminate(); + //Wait for all tasks to complete. + taskManager.setMaxThreads(0); + return ret; +} + +bool Win32AppContext::ShowAssetBatchImportDialog(IAssetBatchImportDesc* pDesc, std::string _basePath) +{ + CBatchImportDialog dialog(mainWindow.getHInstance(), pDesc, nullptr, std::move(_basePath)); + return dialog.ShowModal(mainWindow.getWindow()); +} + +bool Win32AppContext::ShowAssetBatchImportDialog(IAssetBatchImportDesc* pDesc, IWin32AssetBatchImportDesc* pDescWin32, std::string _basePath) +{ + CBatchImportDialog dialog(mainWindow.getHInstance(), pDesc, pDescWin32, std::move(_basePath)); + return dialog.ShowModal(mainWindow.getWindow()); +} + +std::string Win32AppContext::QueryAssetExportLocation(const std::vector& assets, + const std::string &extension, const std::string &extensionFilter) +{ + if (assets.empty()) + return ""; + if (assets.size() > 1) + { + WCHAR* folderPathW = nullptr; + if (!ShowFolderSelectDialog(this->getMainWindow().getWindow(), &folderPathW, L"Select an output directory", UABE_FILEDIALOG_EXPIMPASSET_GUID)) + return ""; + auto pFolderPath8 = unique_WideToMultiByte(folderPathW); + FreeCOMFilePathBuf(&folderPathW); + + return std::string(pFolderPath8.get()); + } + else + { + const AssetUtilDesc& assetToExport = assets[0]; + if (assetToExport.asset.pathID == 0) + { + MessageBox(this->getMainWindow().getWindow(), + TEXT("Unable to resolve the selected asset!"), + TEXT("Asset Bundle Extractor"), + MB_ICONERROR); + return ""; + } + std::unordered_map _tmp; + std::string exportPath = assetToExport.makeExportFilePath(_tmp, extension); + auto pExportPathT = unique_MultiByteToTCHAR(exportPath.c_str()); + + auto pExtensionFilterW = unique_MultiByteToWide(extensionFilter.c_str()); + WCHAR* filePathW = nullptr; + if (FAILED(ShowFileSaveDialog(this->getMainWindow().getWindow(), &filePathW, pExtensionFilterW.get(), nullptr, + pExportPathT.get(), TEXT("Export an asset"), + UABE_FILEDIALOG_EXPIMPASSET_GUID))) + return ""; + auto pFilePath8 = unique_WideToMultiByte(filePathW); + FreeCOMFilePathBuf(&filePathW); + + return std::string(pFilePath8.get()); + } +} + +std::vector Win32AppContext::QueryAssetImportLocation(std::vector& assets, + std::string extension, std::string _extensionRegex, std::string extensionFilter) +{ + + if (assets.empty()) + return {}; + CWin32GenericBatchImportDialogDesc importDesc(assets, std::move(_extensionRegex), extensionFilter); + if (importDesc.getElements().size() > 1) + { + WCHAR* folderPathW = nullptr; + if (!ShowFolderSelectDialog(this->getMainWindow().getWindow(), &folderPathW, L"Select an input directory", UABE_FILEDIALOG_EXPIMPASSET_GUID)) + return {}; + auto pFolderPath8 = unique_WideToMultiByte(folderPathW); + FreeCOMFilePathBuf(&folderPathW); + bool doImport = this->ShowAssetBatchImportDialog(&importDesc, &importDesc, std::string(pFolderPath8.get())); + if (!doImport) + return {}; + } + else + { + const AssetUtilDesc& assetToImport = importDesc.getElements().front(); + if (assetToImport.asset.pathID == 0) + { + MessageBox(this->getMainWindow().getWindow(), + TEXT("Unable to resolve the selected asset!"), + TEXT("Asset Bundle Extractor"), + MB_ICONERROR); + return {}; + } + std::unordered_map _tmp; + std::string exportPath = assetToImport.makeExportFilePath(_tmp, extension); + auto pExportPathT = unique_MultiByteToTCHAR(exportPath.c_str()); + + auto pExtensionFilterW = unique_MultiByteToWide(extensionFilter.c_str()); + WCHAR* filePathW = nullptr; + if (FAILED(ShowFileOpenDialog(this->getMainWindow().getWindow(), &filePathW, pExtensionFilterW.get(), nullptr, + pExportPathT.get(), TEXT("Import an asset"), + UABE_FILEDIALOG_EXPIMPASSET_GUID))) + return {}; + auto pFilePath8 = unique_WideToMultiByte(filePathW); + FreeCOMFilePathBuf(&filePathW); + + importDesc.importFilePaths[0].assign(pFilePath8.get()); + } + std::vector importFilePaths = importDesc.getImportFilePaths(); + assets = importDesc.clearAndGetElements(); + return importFilePaths; +} diff --git a/UABE_Win32/Win32AppContext.h b/UABE_Win32/Win32AppContext.h new file mode 100644 index 0000000..80cb9d7 --- /dev/null +++ b/UABE_Win32/Win32AppContext.h @@ -0,0 +1,82 @@ +#pragma once +#include "api.h" +#include "AppContext.h" +#include "MainWindow2.h" +#include "Win32BatchImportDesc.h" + +static const GUID UABE_FILEDIALOG_FILE_GUID = { 0x832dbb4b, 0xf1bf, 0x8e37, 0x69, 0xc5, 0x41, 0x6e, 0x5f, 0x72, 0x67, 0xc1 }; +static const GUID UABE_FILEDIALOG_EXPIMPASSET_GUID = { 0x832dbb4b, 0xf1bf, 0x8e37, 0x69, 0xc5, 0x41, 0x6e, 0x5f, 0x72, 0x67, 0xc2 }; +static const GUID UABE_FILEDIALOG_CLDB_GUID = { 0x832dbb4b, 0xf1bf, 0x8e37, 0x69, 0xc5, 0x41, 0x6e, 0x5f, 0x72, 0x67, 0xc3 }; + +enum EWin32AppContextMsg +{ + Win32AppContextMsg_COUNT=AppContextMsg_COUNT +}; + +class MainWindow2; +class Win32AppContext : public AppContext +{ + MainWindow2 mainWindow; + std::string baseDir; + + size_t gcMemoryLimit; + unsigned int gcMinAge; + void LoadSettings(); + + bool handlingMessages; + CRITICAL_SECTION messageMutex; + std::vector> messageQueue; + bool messagePosted; + + void handleMessages(); + bool processMessage(EAppContextMsg message, void *args); + + std::shared_ptr OnFileOpenAsBundle(std::shared_ptr pTask, BundleFileContext *pContext, EBundleFileOpenStatus openStatus, unsigned int parentFileID, unsigned int directoryEntryIdx); + std::shared_ptr OnFileOpenAsAssets(std::shared_ptr pTask, AssetsFileContext *pContext, EAssetsFileOpenStatus openStatus, unsigned int parentFileID, unsigned int directoryEntryIdx); + std::shared_ptr OnFileOpenAsResources(std::shared_ptr pTask, ResourcesFileContext *pContext, unsigned int parentFileID, unsigned int directoryEntryIdx); + std::shared_ptr OnFileOpenAsGeneric(std::shared_ptr pTask, GenericFileContext *pContext, unsigned int parentFileID, unsigned int directoryEntryIdx); + void OnFileOpenFail(std::shared_ptr pTask, std::string &logText); + public: UABE_Win32_API void OnUpdateContainers(AssetsFileContextInfo *info); + UABE_Win32_API void OnUpdateDependencies(AssetsFileContextInfo *info, size_t from, size_t to); + protected: + void OnDecompressBundle(BundleFileContextInfo::DecompressTask *pTask, TaskResult result); + void OnChangeAsset(AssetsFileContextInfo *pFile, pathid_t pathID, bool wasRemoved); + void OnChangeBundleEntry(BundleFileContextInfo *pFile, size_t index); + + void RemoveContextInfo(FileContextInfo *info); +public: + UABE_Win32_API void signalMainThread(EAppContextMsg message, void *args); + //Process memory threshold that triggers cache 'garbage collection' (see MainWindow2::disposableCacheElements). + inline size_t getGCMemoryLimit() { return gcMemoryLimit; } + //Minimum age of a resource to be eligible for 'garbage collection'. + inline unsigned int getGCMinAge() { return gcMinAge; } + inline MainWindow2 &getMainWindow() { return mainWindow; } + inline std::string getBaseDir() { return baseDir; } + + UABE_Win32_API bool ShowAssetBatchImportDialog(IAssetBatchImportDesc* pDesc, std::string basePath); + UABE_Win32_API bool ShowAssetBatchImportDialog(IAssetBatchImportDesc* pDesc, IWin32AssetBatchImportDesc* pDescWin32, std::string basePath); + + //Asks the user to provide an export file or directory path. + // -> If assets.size() == 0, returns an empty string. + // -> If assets.size() == 1, the user is asked to select one output file path. + // -> If assets.size() > 1, the user is asked to select an output directory. + //Assumes that all AssetIdentifiers in assets are resolved. + UABE_Win32_API std::string QueryAssetExportLocation(const std::vector& assets, + const std::string &extension, const std::string &extensionFilter); + //Asks the user to provide an import file or directory path. An empty return value signals cancelling the action. + //The user may possibly change the set of assets to actually import. + //The returned vector indices correspond to the indices in the (potentially modified) assets vector. + // -> If assets.size() == 0, returns an empty vector. + // -> If assets.size() == 1, the user is asked to select one file from the filesystem. + // -> If assets.size() > 1, the user is asked to select an input directory and a batch import dialog is shown. + //Assumes all assets have resolved AssetIdentifiers already. + UABE_Win32_API std::vector QueryAssetImportLocation(std::vector& assets, + std::string extension, std::string extensionRegex, std::string extensionFilter); + + UABE_Win32_API Win32AppContext(HINSTANCE hInstance, const std::string &baseDir); + UABE_Win32_API ~Win32AppContext(); + + UABE_Win32_API int Run(size_t argc, char **argv); + friend class MainWindow2; +}; + diff --git a/UABE_Win32/Win32BatchImportDesc.cpp b/UABE_Win32/Win32BatchImportDesc.cpp new file mode 100644 index 0000000..a0f8524 --- /dev/null +++ b/UABE_Win32/Win32BatchImportDesc.cpp @@ -0,0 +1,29 @@ +#include "stdafx.h" +#include "Win32BatchImportDesc.h" +#include "Win32AppContext.h" +#include "FileDialog.h" +#include "../libStringConverter/convert.h" + +bool CWin32GenericBatchImportDialogDesc::ShowAssetSettings(IN size_t matchIndex, IN HWND hParentWindow) +{ + if (matchIndex == (size_t)-1) + return true; + if (matchIndex >= importFilePathOverrides.size()) + return false; + + importFilePathOverrides[matchIndex].clear(); + + auto pFileTypeFilterW = unique_MultiByteToWide(fileTypeFilter.c_str()); + wchar_t* filePathBufW = nullptr; + HRESULT hr = ShowFileOpenDialog(hParentWindow, &filePathBufW, pFileTypeFilterW.get(), + nullptr, nullptr, nullptr, + UABE_FILEDIALOG_EXPIMPASSET_GUID); + if (SUCCEEDED(hr)) + { + auto pFilePath8 = unique_WideToMultiByte(filePathBufW); + importFilePathOverrides[matchIndex].assign(pFilePath8.get()); + FreeCOMFilePathBuf(&filePathBufW); + } + + return true; +} \ No newline at end of file diff --git a/UABE_Win32/Win32BatchImportDesc.h b/UABE_Win32/Win32BatchImportDesc.h new file mode 100644 index 0000000..b0e9ea9 --- /dev/null +++ b/UABE_Win32/Win32BatchImportDesc.h @@ -0,0 +1,34 @@ +#pragma once +#include "../UABE_Generic/IAssetBatchImportDesc.h" +#include "../UABE_Generic/AssetPluginUtil.h" +#include "api.h" +#include +#include +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include + +//Class implemented by plugins that use the Win32AppContext::ShowAssetBatchImportDialog function specifically. +//-> The plugin would pass a companion IAssetBatchImportDesc object (-> UABE_Generic) to the function. +//All const char* and std strings are UTF-8. const char* strings returned by the plugin must not be freed before the dialog has closed. +class IWin32AssetBatchImportDesc +{ +public: + //Shows a settings dialog to specify precise settings of a single asset. Returns whether such a dialog is supported if matchIndex==(size_t)-1. + //matchIndex is defined as in the companion IAssetBatchImportDesc object. + UABE_Win32_API virtual bool ShowAssetSettings(IN size_t matchIndex, IN HWND hParentWindow) = 0; +}; + +class CWin32GenericBatchImportDialogDesc : public CGenericBatchImportDialogDesc, public IWin32AssetBatchImportDesc +{ + std::string fileTypeFilter; +public: + //fileTypeFilter: File type filter as for the FileDialog.h functions, e.g. "*.*|Raw Unity asset:". + inline CWin32GenericBatchImportDialogDesc(std::vector _elements, const std::string& extensionRegex, + std::string _fileTypeFilter) + : CGenericBatchImportDialogDesc(std::move(_elements), extensionRegex), + fileTypeFilter(std::move(_fileTypeFilter)) + {} + + UABE_Win32_API bool ShowAssetSettings(IN size_t matchIndex, IN HWND hParentWindow); +}; diff --git a/UABE_Win32/Win32ModTreeDialogBase.cpp b/UABE_Win32/Win32ModTreeDialogBase.cpp new file mode 100644 index 0000000..44399af --- /dev/null +++ b/UABE_Win32/Win32ModTreeDialogBase.cpp @@ -0,0 +1,261 @@ +#include "stdafx.h" +#include "Win32ModTreeDialogBase.h" +#include "../libStringConverter/convert.h" +#include +#include + +static std::string MakeAssetsReplacerDescription(AssetsReplacer *pReplacer) +{ + switch (pReplacer->GetType()) + { + case AssetsReplacement_AddOrModify: + return std::string("Add/Replace PathID ") + std::to_string((int64_t)reinterpret_cast(pReplacer)->GetPathID()); + case AssetsReplacement_Remove: + return std::string("Remove PathID ") + std::to_string((int64_t)reinterpret_cast(pReplacer)->GetPathID()); + case AssetsReplacement_Dependencies: + return std::string("Modify dependencies"); + default: + assert(false); + return std::string("(??)"); + } +} +void Win32ModTreeDialogBase::addTreeViewNode_AssetsReplacer(HTREEITEM hParent, VisibleReplacerEntry &entry) +{ + TVINSERTSTRUCT insert = {}; + insert.hParent = hParent; + insert.itemex.state = 0; + insert.itemex.stateMask = 0xFF; + insert.itemex.hwnd = NULL; + insert.itemex.hItem = NULL; + insert.itemex.mask = TVIF_CHILDREN | TVIF_STATE | TVIF_TEXT; + insert.hInsertAfter = TVI_FIRST; + std::string replacerDesc = MakeAssetsReplacerDescription(reinterpret_cast(entry.pReplacer.get())); + auto replacerDescT = unique_MultiByteToTCHAR(replacerDesc.c_str()); + insert.itemex.pszText = replacerDescT.get(); + insert.itemex.cchTextMax = 0; + insert.itemex.cChildren = 0; + entry.treeItem = (uintptr_t)TreeView_InsertItem(hTreeModifications, &insert); +} +static std::string MakeBundleReplacerDescription(BundleReplacer *pReplacer) +{ + const char *_origName = pReplacer->GetOriginalEntryName(); + const char *_newName = pReplacer->GetEntryName(); + std::string origName(_origName ? _origName : ""); + std::string newName(_newName ? _newName : ""); + switch (pReplacer->GetType()) + { + case BundleReplacement_Rename: + return std::string("Rename ") + origName + " to " + newName; + case BundleReplacement_AddOrModify: + if (newName.empty()) + return std::string("Modify and rename ") + origName; + if (origName.empty()) + return std::string("Modify/create ") + newName; + return std::string("Modify and rename ") + origName + " to " + newName; + case BundleReplacement_Remove: + return std::string("Remove ") + origName; + default: + assert(false); + return std::string("(??) ") + origName; + } +} +void Win32ModTreeDialogBase::addTreeViewNode_BundleReplacer(HTREEITEM hParent, VisibleReplacerEntry &entry) +{ + TVINSERTSTRUCT insert = {}; + insert.hParent = hParent; + insert.itemex.state = 0; + insert.itemex.stateMask = 0xFF; + insert.itemex.hwnd = NULL; + insert.itemex.hItem = NULL; + insert.itemex.mask = TVIF_CHILDREN | TVIF_STATE | TVIF_TEXT; + insert.hInsertAfter = TVI_FIRST; + std::string replacerDesc = MakeBundleReplacerDescription(reinterpret_cast(entry.pReplacer.get())); + auto replacerDescT = unique_MultiByteToTCHAR(replacerDesc.c_str()); + insert.itemex.pszText = replacerDescT.get(); + insert.itemex.cchTextMax = 0; + insert.itemex.cChildren = 0; + entry.treeItem = (uintptr_t)TreeView_InsertItem(hTreeModifications, &insert); +} +void Win32ModTreeDialogBase::addTreeViewNode_ResourcesReplacer(HTREEITEM hParent, VisibleReplacerEntry& entry) +{ + BundleReplacer* pReplacer = reinterpret_cast(entry.pReplacer.get()); + auto* pResourcesReplacer = dynamic_cast(pReplacer); + if (pResourcesReplacer == nullptr) + { + assert(false); + return; + } + TVINSERTSTRUCT insert = {}; + insert.hParent = hParent; + insert.itemex.state = 0; + insert.itemex.stateMask = 0xFF; + insert.itemex.hwnd = NULL; + insert.itemex.hItem = NULL; + insert.itemex.mask = TVIF_CHILDREN | TVIF_STATE | TVIF_TEXT; + insert.hInsertAfter = TVI_FIRST; + insert.itemex.pszText = const_cast( + pResourcesReplacer->RequiresEntryReader() ? TEXT("Modify") : TEXT("Add or replace") + ); + insert.itemex.cchTextMax = 0; + insert.itemex.cChildren = 0; + entry.treeItem = (uintptr_t)TreeView_InsertItem(hTreeModifications, &insert); +} +void Win32ModTreeDialogBase::updateTreeViewNode_File(HTREEITEM hParent, VisibleFileEntry &file, bool showReplacers) +{ + if (file.treeViewEntry == NULL) + { + size_t pathLen; + auto tcPath = unique_MultiByteToTCHAR(file.pathNull ? file.newName.c_str() : file.pathOrName.c_str(), pathLen); + TVINSERTSTRUCT insert = {}; + insert.hParent = NULL; + insert.itemex.state = 0; + insert.itemex.stateMask = 0xFF; + insert.itemex.hwnd = NULL; + insert.itemex.hItem = NULL; + insert.itemex.mask = TVIF_CHILDREN | TVIF_STATE | TVIF_TEXT; + insert.itemex.pszText = tcPath.get(); + insert.itemex.cchTextMax = (int)((pathLen + 1) & 0x7FFFFFFF); + insert.hInsertAfter = TVI_FIRST; + insert.hParent = hParent; + insert.itemex.cChildren = ((showReplacers && file.replacers.size() > 0) || (file.fileType == FileContext_Bundle && file.subFiles.size() > 0)) + ? 1 + : 0; + file.treeViewEntry = (uintptr_t)TreeView_InsertItem(hTreeModifications, &insert); + } + switch (file.fileType) + { + case FileContext_Bundle: + for (size_t _i = file.subFiles.size(); _i > 0; --_i) + { + size_t i = _i - 1; + updateTreeViewNode_File((HTREEITEM)file.treeViewEntry, file.subFiles[i], showReplacers); + } + if (showReplacers) + for (size_t _i = file.replacers.size(); _i > 0; --_i) + { + size_t i = _i - 1; + if (file.replacers[i].treeItem == NULL) + addTreeViewNode_BundleReplacer((HTREEITEM)file.treeViewEntry, file.replacers[i]); + } + break; + case FileContext_Assets: + if (showReplacers) + for (size_t _i = file.replacers.size(); _i > 0; --_i) + { + size_t i = _i - 1; + if (file.replacers[i].treeItem == NULL) + addTreeViewNode_AssetsReplacer((HTREEITEM)file.treeViewEntry, file.replacers[i]); + } + break; + case FileContext_Resources: + if (showReplacers && file.replacers.size() == 1 && file.replacers[0].treeItem == NULL) + { + addTreeViewNode_ResourcesReplacer((HTREEITEM)file.treeViewEntry, file.replacers[0]); + } + break; + default: + assert(false); + break; + } +} + +void Win32ModTreeDialogBase::UpdateModsTree(bool showReplacers) +{ + size_t numBundle = 0, numAssets = 0, numResources = 0; + for (size_t i = 0; i < this->visibleFiles.size(); i++) + { + switch (this->visibleFiles[i].fileType) + { + case FileContext_Bundle: + numBundle++; + break; + case FileContext_Assets: + numAssets++; + break; + case FileContext_Resources: + numResources++; + break; + default: + assert(false); + } + } + + TVINSERTSTRUCT insert = {}; + insert.hParent = NULL; + insert.itemex.state = 0; + insert.itemex.stateMask = 0xFF; + insert.itemex.hwnd = NULL; + if (this->bundleBaseEntry == NULL) + { + insert.itemex.hItem = NULL; + insert.itemex.mask = TVIF_CHILDREN | TVIF_STATE | TVIF_TEXT; + insert.itemex.pszText = const_cast(TEXT("Affected bundles")); + insert.hInsertAfter = TVI_FIRST; + insert.itemex.cchTextMax = (int)_tcslen(insert.itemex.pszText); + insert.itemex.cChildren = numBundle ? 1 : 0; + this->bundleBaseEntry = TreeView_InsertItem(hTreeModifications, &insert); + } + else + { + insert.itemex.hItem = this->bundleBaseEntry; + insert.itemex.mask = TVIF_CHILDREN; + insert.itemex.cChildren = numBundle ? 1 : 0; + TreeView_SetItem(hTreeModifications, &insert.itemex); + } + + if (this->assetsBaseEntry == NULL) + { + insert.itemex.hItem = NULL; + insert.itemex.mask = TVIF_CHILDREN | TVIF_STATE | TVIF_TEXT; + insert.itemex.pszText = const_cast(TEXT("Affected assets files")); + insert.hInsertAfter = TVI_LAST; + insert.itemex.cchTextMax = (int)_tcslen(insert.itemex.pszText); + insert.itemex.cChildren = numAssets ? 1 : 0; + this->assetsBaseEntry = TreeView_InsertItem(hTreeModifications, &insert); + } + else + { + insert.itemex.hItem = this->assetsBaseEntry; + insert.itemex.mask = TVIF_CHILDREN; + insert.itemex.cChildren = numAssets ? 1 : 0; + TreeView_SetItem(hTreeModifications, &insert.itemex); + } + + if (this->resourcesBaseEntry == NULL) + { + insert.itemex.hItem = NULL; + insert.itemex.mask = TVIF_CHILDREN | TVIF_STATE | TVIF_TEXT; + insert.itemex.pszText = const_cast(TEXT("Affected resource files")); + insert.hInsertAfter = TVI_LAST; + insert.itemex.cchTextMax = (int)_tcslen(insert.itemex.pszText); + insert.itemex.cChildren = numResources ? 1 : 0; + this->resourcesBaseEntry = TreeView_InsertItem(hTreeModifications, &insert); + } + else + { + insert.itemex.hItem = this->resourcesBaseEntry; + insert.itemex.mask = TVIF_CHILDREN; + insert.itemex.cChildren = numResources ? 1 : 0; + TreeView_SetItem(hTreeModifications, &insert.itemex); + } + + + for (size_t _i = this->visibleFiles.size(); _i > 0; --_i) + { + size_t i = _i - 1; + switch (this->visibleFiles[i].fileType) + { + case FileContext_Bundle: + updateTreeViewNode_File(this->bundleBaseEntry, this->visibleFiles[i], showReplacers); + break; + case FileContext_Assets: + updateTreeViewNode_File(this->assetsBaseEntry, this->visibleFiles[i], showReplacers); + break; + case FileContext_Resources: + updateTreeViewNode_File(this->resourcesBaseEntry, this->visibleFiles[i], showReplacers); + break; + default: + assert(false); + } + } +} diff --git a/UABE_Win32/Win32ModTreeDialogBase.h b/UABE_Win32/Win32ModTreeDialogBase.h new file mode 100644 index 0000000..77fd938 --- /dev/null +++ b/UABE_Win32/Win32ModTreeDialogBase.h @@ -0,0 +1,27 @@ +#pragma once +#include "Win32AppContext.h" +#include "../ModInstaller/InstallerDataFormat.h" +#include "FileModTree.h" +#include + + +class Win32ModTreeDialogBase +{ +protected: + Win32AppContext &appContext; + std::vector visibleFiles; + + HWND hDlg; + HWND hTreeModifications; + HTREEITEM bundleBaseEntry, assetsBaseEntry, resourcesBaseEntry; + + void updateTreeViewNode_File(HTREEITEM hParent, VisibleFileEntry &file, bool showReplacers); + void addTreeViewNode_AssetsReplacer(HTREEITEM hParent, VisibleReplacerEntry &entry); + void addTreeViewNode_BundleReplacer(HTREEITEM hParent, VisibleReplacerEntry &entry); + void addTreeViewNode_ResourcesReplacer(HTREEITEM hParent, VisibleReplacerEntry& entry); + void UpdateModsTree(bool showReplacers = true); + + inline Win32ModTreeDialogBase(Win32AppContext &appContext) + : appContext(appContext), hDlg(NULL), hTreeModifications(NULL), bundleBaseEntry(NULL), assetsBaseEntry(NULL), resourcesBaseEntry(NULL) + {} +}; diff --git a/UABE_Win32/Win32PluginManager.cpp b/UABE_Win32/Win32PluginManager.cpp new file mode 100644 index 0000000..23b02ab --- /dev/null +++ b/UABE_Win32/Win32PluginManager.cpp @@ -0,0 +1,105 @@ +#include "stdafx.h" +#include "Win32PluginManager.h" +#include +#include "../UABE_Generic/AppContext.h" +#include "../UABE_Generic/FileContextInfo.h" +#include + +IAssetListTabOptionProvider::IAssetListTabOptionProvider() {} +IAssetViewEntryOptionProvider::IAssetViewEntryOptionProvider() {} + +void loadPlugin(Win32AppContext& appContext, PluginMapping& outMapping, const std::filesystem::path& path) +{ + HMODULE hModule = LoadLibrary(path.c_str()); + if (hModule == NULL) + return; + auto pGetDescCallback = reinterpret_cast(GetProcAddress(hModule, "GetUABEPluginDesc1")); + IPluginDesc* pPluginDesc = nullptr; + if (pGetDescCallback == nullptr || (pPluginDesc = pGetDescCallback(sizeof(AppContext), sizeof(BundleFileContextInfo))) == nullptr) + { + FreeLibrary(hModule); + return; + } + outMapping.descriptions.emplace_back(pPluginDesc); + for (std::shared_ptr &pProvider : pPluginDesc->getPluginOptions(appContext)) + outMapping.options.emplace_back(pProvider); + //TODO: Keep the HMODULE somewhere, so it can be unloaded during runtime. +} + +void loadAllPlugins(Win32AppContext& appContext, PluginMapping& outMapping, const std::string& pluginsDir) +{ + //Create a path object, treating pluginsDir as UTF-8 (C++20; older equivalent: std::filesystem::u8path(pluginsDir)). + std::filesystem::path pluginsDirPath( + reinterpret_cast(&pluginsDir.data()[0]), + reinterpret_cast(&pluginsDir.data()[pluginsDir.size()])); + //Ignore errors accessing the directory. + std::error_code errc; + for (const auto& dirEntry : std::filesystem::directory_iterator(pluginsDirPath.make_preferred(), errc)) + { + auto extension = dirEntry.path().extension().string(); + const std::string expectedExtension(".bep"); + if (dirEntry.is_regular_file() + && std::equal(extension.begin(), extension.end(), + expectedExtension.begin(), expectedExtension.end(), + [](unsigned char is, unsigned char exp) { return std::tolower(is) == exp; })) + { + loadPlugin(appContext, outMapping, dirEntry.path()); + } + } +} + +size_t ShowContextMenu(size_t numEntries, std::function entryNameGetter, + UINT popupMenuFlags, LONG x, LONG y, HWND hParent, + HMENU& hCurPopupMenu, size_t nMaxEntriesOnScreen) +{ + auto displayPopupMenu = [numEntries, &entryNameGetter, popupMenuFlags, x, y, hParent, &hCurPopupMenu](size_t rangeMin, size_t rangeMax) { + assert(rangeMax <= numEntries); + if (rangeMax <= rangeMin || rangeMax > numEntries) + return (uintptr_t)0; + if (hCurPopupMenu != NULL) + DestroyMenu(hCurPopupMenu); + hCurPopupMenu = CreatePopupMenu(); + if (hCurPopupMenu == NULL) + return (uintptr_t)0; + if (rangeMin > 0) + AppendMenu(hCurPopupMenu, MF_STRING, 100, TEXT("...")); + for (size_t i = 0; i < rangeMax - rangeMin; ++i) + { + auto pOptionNameT = unique_MultiByteToTCHAR(entryNameGetter(i + rangeMin)); + AppendMenu(hCurPopupMenu, MF_STRING, 9000 + i, pOptionNameT.get()); + } + if (rangeMax < numEntries) + AppendMenu(hCurPopupMenu, MF_STRING, 101, TEXT("...")); + return static_cast(TrackPopupMenuEx(hCurPopupMenu, popupMenuFlags, x, y, hParent, NULL)); + }; + size_t rangeMin = 0; + size_t rangeMax = std::min(numEntries, nMaxEntriesOnScreen); + size_t ret = (size_t)-1; + while (uintptr_t selectedId = displayPopupMenu(rangeMin, rangeMax)) + { + if (selectedId == 100) //Up + { + rangeMin = std::max(rangeMin, nMaxEntriesOnScreen) - nMaxEntriesOnScreen; + rangeMax = std::min(numEntries, rangeMin + nMaxEntriesOnScreen); + } + else if (selectedId == 101) //Down + { + rangeMax = std::min(numEntries, rangeMax + nMaxEntriesOnScreen); + rangeMin = std::max(rangeMax, nMaxEntriesOnScreen) - nMaxEntriesOnScreen; + } + else if (selectedId >= 9000 && selectedId < (9000 + (rangeMax - rangeMin))) + { + size_t iOption = static_cast(selectedId - 9000) + rangeMin; + assert(iOption < numEntries); + if (iOption < numEntries) + ret = iOption; + break; + } + } + if (hCurPopupMenu != NULL) + { + DestroyMenu(hCurPopupMenu); + hCurPopupMenu = NULL; + } + return ret; +} diff --git a/UABE_Win32/Win32PluginManager.h b/UABE_Win32/Win32PluginManager.h new file mode 100644 index 0000000..e69a76e --- /dev/null +++ b/UABE_Win32/Win32PluginManager.h @@ -0,0 +1,61 @@ +#pragma once +#include "api.h" +#include "../UABE_Generic/PluginManager.h" +#include "Win32AppContext.h" +#include "AssetListDialog.h" +#include + +//Provider class intended for plugins that create a AssetModifyDialog tab in an AssetListDialog +class IAssetListTabOptionProvider : public IOptionProvider +{ +public: + UABE_Win32_API IAssetListTabOptionProvider(); + UABE_Win32_API virtual EAssetOptionType getType() = 0; + //Determines whether this option provider applies to a given selection. + //Returns a runner object for the selection, or a null pointer otherwise. + //No operation should be applied unless the runner object's operator() is invoked. + //The plugin should set "optionName" to a short description on success. + UABE_Win32_API virtual std::unique_ptr prepareForSelection( + class Win32AppContext& appContext, class AssetListDialog& listDialog, + std::vector selection, + std::string& optionName) = 0; +}; + +//Function provided by plugins. Called by the plugin loader to determine the supported actions. +//The sizeof_* parameters are set by the loader to sizeof(AppContext) and sizeof(BundleFileContextInfo), respectively. +// The plugin should check the sizes and return nullptr on a mismatch. +//The returned pointer must be created via the new operator. If nullptr is returned, the plugin will be unloaded again. +//The function should be exported with a .def file as GetUABEPluginDesc1. +typedef IPluginDesc* (_cdecl* UABEGetPluginDescCallback1)(size_t sizeof_AppContext, size_t sizeof_BundleFileContextInfo); + + +void loadAllPlugins(class Win32AppContext& appContext, PluginMapping& outMapping, const std::string& pluginsDir); + +#include "AssetViewModifyDialog.h" +//Provider class for context menu options in the "View Asset" dialog. +class IAssetViewEntryOptionProvider : public IOptionProvider +{ +public: + UABE_Win32_API IAssetViewEntryOptionProvider(); + UABE_Win32_API virtual EAssetOptionType getType() = 0; + //Determines whether this option provider applies to a given selection. + //Returns a runner object for the selection, or a null pointer otherwise. + //No operation should be applied unless the runner object's operator() is invoked. + //The plugin should set "optionName" to a short description on success. + //The runner object must not use the fieldInfo elements concurrently, + // and the validity of fieldInfo elements is only guaranteed up until the end of the runner call. + UABE_Win32_API virtual std::unique_ptr prepareForSelection( + class Win32AppContext& appContext, class AssetViewModifyDialog& assetViewDialog, + AssetViewModifyDialog::FieldInfo fieldInfo, + std::string& optionName) = 0; +}; + +//Shows a context menu with an arbitrary amount of entries (of at least one). +//hCurPopupMenu: Reference to a variable that holds the current popup menu. +// -> If hCurPopupMenu is set prior to calling ShowContextMenu, it will be destroyed first. +// -> Could be used by the caller in window handlers while the popup menu window loop is running. +// -> Closes the menu before returning. +//Returns the selected index, or (size_t)-1 if no proper selection was made. +size_t ShowContextMenu(size_t numEntries, std::function entryNameGetter, + UINT popupMenuFlags, LONG x, LONG y, HWND hParent, + HMENU& hCurPopupMenu, size_t nMaxEntriesOnScreen = 30); diff --git a/UABE_Win32/Win32TaskStatusTracker.cpp b/UABE_Win32/Win32TaskStatusTracker.cpp new file mode 100644 index 0000000..7d27887 --- /dev/null +++ b/UABE_Win32/Win32TaskStatusTracker.cpp @@ -0,0 +1,801 @@ +#include "stdafx.h" +#include "Win32TaskStatusTracker.h" +#include +#include "resource.h" +#include +#include "../libStringConverter/convert.h" + +static int GetTextExtent(HWND hListBox, const TCHAR* text) +{ + HDC hListDC = GetDC(hListBox); + HGDIOBJ hOrigObject = SelectObject(hListDC, GetWindowFont(hListBox)); + RECT textRect = {}; + DrawText(hListDC, text, -1, &textRect, DT_SINGLELINE | DT_CALCRECT); + SelectObject(hListDC, hOrigObject); + ReleaseDC(hListBox, hListDC); + + return textRect.right - textRect.left + 4; +} + +static std::string processLogEntry(const std::string& text) +{ + //Convert "\n" (LF) line ends to "\r\n" (CRLF). + std::string outText; + size_t curLineEnd = SIZE_MAX; size_t prevLineEnd = 0; + while (curLineEnd = text.find_first_of('\n', curLineEnd + 1), curLineEnd != std::string::npos) + { + outText += text.substr(prevLineEnd, curLineEnd - prevLineEnd); + if (curLineEnd == 0 || text[curLineEnd - 1] != '\r') + outText += '\r'; + prevLineEnd = curLineEnd; + } + outText += text.substr(prevLineEnd); + return outText; +} +bool Win32TaskStatusTracker::dlgAddLogText(const std::string& text) +{ + if (hDlg == NULL) + return false; + HWND hWndStatus = GetDlgItem(hDlg, IDC_EDITSTATUS); + + int editLen = Edit_GetTextLength(hWndStatus); + assert(editLen >= 0); + if (editLen < 0) + return false; + int oldSelStart = editLen; + int oldSelEnd = editLen; + SendMessage(hWndStatus, EM_GETSEL, (WPARAM)&oldSelStart, (LPARAM)&oldSelEnd); + + std::string outText = processLogEntry(text); + size_t outTextTLen = 0; + auto pOutTextT = unique_MultiByteToTCHAR(outText.c_str(), outTextTLen); + if (outTextTLen > INT_MAX || (INT_MAX - outTextTLen) < editLen) + return false; + + Edit_SetSel(hWndStatus, editLen, editLen); + Edit_ReplaceSel(hWndStatus, pOutTextT.get()); + + if ((oldSelEnd != editLen) || (oldSelStart != oldSelEnd)) + Edit_SetSel(hWndStatus, oldSelStart, oldSelEnd); + else + Edit_SetSel(hWndStatus, -1, -1); + + return true; +} +bool Win32TaskStatusTracker::dlgPutLogText(TaskStatusDesc* pDesc) +{ + if (hDlg == NULL) + return false; + HWND hWndStatus = GetDlgItem(hDlg, IDC_EDITSTATUS); + + std::basic_string fullLog; + for (const std::string& message : pDesc->messages) + { + std::string text = processLogEntry(message); + size_t textTLen = 0; + auto pTextT = unique_MultiByteToTCHAR(text.c_str(), textTLen); + fullLog.insert(fullLog.end(), pTextT.get(), pTextT.get() + textTLen); + } + if (fullLog.size() >= INT_MAX) + { + Edit_SetText(hWndStatus, TEXT("")); + return false; + } + Edit_SetText(hWndStatus, fullLog.c_str()); + Edit_SetSel(hWndStatus, -1, -1); + numShownLogEntries = pDesc->messages.size(); + return true; +} +inline auto getListCtrls(HWND hDlg) +{ + return std::array{ GetDlgItem(hDlg, IDC_LISTRUNNING), GetDlgItem(hDlg, IDC_LISTCOMPLETE) }; +} +bool Win32TaskStatusTracker::dlgFindListCtrlFor(TaskStatusDesc* pDesc, HWND& hList, int& listIdx) +{ + assert(pDesc != nullptr); + hList = NULL; + listIdx = 0; + if (hDlg == NULL || pDesc == nullptr) + return false; + + auto listCtrls = getListCtrls(hDlg); + + for (size_t i = 0; i < listCtrls.size(); ++i) + { + HWND hListCtrl = listCtrls[i]; + assert(hListCtrl != NULL); + if (hListCtrl == NULL) + continue; + int iItem = (int)dlgGetListIdx(pDesc); + + int numItems = ListBox_GetCount(hListCtrl); + if (numItems >= 0 && numItems > iItem + && (ListBox_GetItemData(hListCtrl, iItem) == (LRESULT)pDesc)) + { + hList = hListCtrl; + listIdx = iItem; + return true; + } + } + return false; +} +void Win32TaskStatusTracker::dlgSwitchShownTask(TaskStatusDesc* pDesc) +{ + if (hDlg == NULL) + return; + if (processingSelection) + return; + HWND hListComplete = GetDlgItem(hDlg, IDC_LISTCOMPLETE); + bool hasAnyCompleteItems = (ListBox_GetCount(hListComplete) > 0); + EnableWindow(GetDlgItem(hDlg, IDC_CLEARALL), hasAnyCompleteItems ? TRUE : FALSE); + + auto listCtrls = getListCtrls(hDlg); + if (pDesc == nullptr) + { + processingSelection = true; + for (size_t i = 0; i < listCtrls.size(); ++i) + ListBox_SetCurSel(listCtrls[i], -1); + processingSelection = false; + numShownLogEntries = 0; + ShowWindow(GetDlgItem(hDlg, IDC_EDITSTATUS), SW_HIDE); + ShowWindow(GetDlgItem(hDlg, IDC_PROG), SW_HIDE); + ShowWindow(GetDlgItem(hDlg, IDC_SDESC), SW_HIDE); + EnableWindow(GetDlgItem(hDlg, IDC_CLEAR), FALSE); + EnableWindow(GetDlgItem(hDlg, IDC_CANCELTASK), FALSE); + return; + } + HWND hListCtrl = NULL; int listIdx = 0; + if (!dlgFindListCtrlFor(pDesc, hListCtrl, listIdx)) + return; + if (hListCtrl == GetDlgItem(hDlg, IDC_LISTCOMPLETE)) + pDesc->eraseIfStale = false; + processingSelection = true; + for (size_t i = 0; i < listCtrls.size(); ++i) + { + if (listCtrls[i] != hListCtrl) + ListBox_SetCurSel(listCtrls[i], -1); + } + ListBox_SetCurSel(hListCtrl, listIdx); + processingSelection = false; + dlgPutLogText(pDesc); + mainOnTaskProgressUpdate(pDesc->selfRef); + mainOnTaskProgressDescUpdate(pDesc->selfRef); + ShowWindow(GetDlgItem(hDlg, IDC_EDITSTATUS), SW_SHOW); + ShowWindow(GetDlgItem(hDlg, IDC_PROG), SW_SHOW); + ShowWindow(GetDlgItem(hDlg, IDC_SDESC), SW_SHOW); + EnableWindow(GetDlgItem(hDlg, IDC_CLEAR), (hListCtrl == hListComplete) ? TRUE : FALSE); + EnableWindow(GetDlgItem(hDlg, IDC_CANCELTASK), pDesc->cancelable ? TRUE : FALSE); +} +static TaskStatusDesc* listCtrlGetItem(HWND hListCtrl, int listIdx) +{ + LRESULT itemData = ListBox_GetItemData(hListCtrl, listIdx); + assert(itemData != LB_ERR); + if (itemData == LB_ERR) + return nullptr; + return reinterpret_cast(itemData); +} +void Win32TaskStatusTracker::dlgHideTask(TaskStatusDesc* pDesc, bool eraseStale) +{ + HWND hListCtrl = NULL; int listIdx = 0; + if (!dlgFindListCtrlFor(pDesc, hListCtrl, listIdx)) + return; + HWND hListComplete = GetDlgItem(hDlg, IDC_LISTCOMPLETE); + pDesc->eraseIfStale = true; + bool wasSelected = (ListBox_GetCurSel(hListCtrl) == listIdx); + processingSelection = true; + + auto& taskNameExtents = (hListCtrl == hListComplete) ? completeTaskNameExtents : runningTaskNameExtents; + assert(ListBox_GetCount(hListCtrl) == taskNameExtents.size()); + assert(ListBox_GetCount(hListCtrl) > listIdx); + ListBox_DeleteString(hListCtrl, listIdx); + for (int i = listIdx; i < ListBox_GetCount(hListCtrl); ++i) + { + TaskStatusDesc* pDesc = reinterpret_cast(ListBox_GetItemData(hListCtrl, i)); + if (pDesc == nullptr) + continue; + assert(pDesc->auxData == i + 1); + pDesc->auxData = (uintptr_t)i; + } + + assert(hListCtrl == hListComplete || hListCtrl == GetDlgItem(hDlg, IDC_LISTRUNNING)); + if (listIdx < taskNameExtents.size()) + taskNameExtents.erase(taskNameExtents.begin() + listIdx); + auto maxIt = std::max_element(taskNameExtents.begin(), taskNameExtents.end()); + int extent = (maxIt == taskNameExtents.end()) ? 10 : *maxIt; + ListBox_SetHorizontalExtent(hListComplete, extent); + + processingSelection = false; + + bool hasAnyCompleteItems = (ListBox_GetCount(hListComplete) > 0); + EnableWindow(GetDlgItem(hDlg, IDC_CLEARALL), hasAnyCompleteItems ? TRUE : FALSE); + + if (wasSelected) + { + auto listCtrls = getListCtrls(hDlg); + bool foundItem = false; + for (size_t i = 0; i < listCtrls.size(); ++i) + { + if (ListBox_GetCount(listCtrls[i]) > 0) + { + foundItem = true; + EnableWindow(GetDlgItem(hDlg, IDC_CLEARALL), TRUE); + ListBox_SetCurSel(listCtrls[i], 0); + if (TaskStatusDesc* pItemDesc = listCtrlGetItem(listCtrls[i], 0)) + { + this->dlgSwitchShownTask(pItemDesc); + pItemDesc->eraseIfStale = false; + } + else + this->dlgSwitchShownTask(nullptr); + break; + } + } + if (!foundItem) + { + EnableWindow(GetDlgItem(hDlg, IDC_CLEARALL), FALSE); + dlgSwitchShownTask(nullptr); + } + } + if (eraseStale) + eraseStaleElements(); +} +void Win32TaskStatusTracker::onResize(bool defer) +{ + RECT client = {}; + GetClientRect(hDlg, &client); + long width = client.right - client.left; + long height = client.bottom - client.top; + + HDWP deferCtx = defer ? BeginDeferWindowPos(12) : NULL; + bool retry = false; + std::vector invalidateRects; + auto doMoveWindow = [&deferCtx, &retry, &invalidateRects](HWND hWnd, int x, int y, int w, int h, bool invalidate = false) + { + if (invalidate) + invalidateRects.emplace_back((LONG)x, (LONG)y, (LONG)x + w, (LONG)y + h); + if (deferCtx) + { + deferCtx = DeferWindowPos(deferCtx, hWnd, HWND_TOP, x, y, w, h, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); + if (!deferCtx) + retry = true; + } + else + SetWindowPos(hWnd, HWND_TOP, x, y, w, h, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); + }; + + long fontHeight = 16; + long bottomDistance = 7, topDistance = 4; + long leftDistance = 7, rightDistance = 7; + + long panelTop = topDistance; + long leftPanelLeft = leftDistance; + long leftPanelWidth = (long)(width * this->mainPanelSplitter.getLeftOrTopPanelRatio() - leftPanelLeft); + long panelHeight = height - bottomDistance - topDistance; + + long buttonHeight = 25, progbarHeight = 25; + long buttonWidth = 80; + + { + long taskLabelListsHeight = (panelHeight - buttonHeight - 3); + long runningLabelListTop = panelTop; + long runningLabelListHeight = taskLabelListsHeight / 2 + (taskLabelListsHeight % 2); + doMoveWindow(GetDlgItem(hDlg, IDC_SRUNNINGTASKS), leftPanelLeft, runningLabelListTop, leftPanelWidth - 3, fontHeight, true); + doMoveWindow(GetDlgItem(hDlg, IDC_LISTRUNNING), leftPanelLeft, runningLabelListTop + fontHeight + 4, leftPanelWidth - 3, runningLabelListHeight - fontHeight - 8); + long completeLabelListTop = runningLabelListTop + runningLabelListHeight; + long completeLabelListHeight = taskLabelListsHeight / 2; + doMoveWindow(GetDlgItem(hDlg, IDC_SCOMPLETETASKS), leftPanelLeft, completeLabelListTop, leftPanelWidth - 3, fontHeight, true); + doMoveWindow(GetDlgItem(hDlg, IDC_LISTCOMPLETE), leftPanelLeft, completeLabelListTop + fontHeight + 4, leftPanelWidth - 3, completeLabelListHeight - fontHeight - 8); + } + + doMoveWindow(GetDlgItem(hDlg, IDC_CLEARALL), leftPanelLeft, height - bottomDistance - buttonHeight, buttonWidth, buttonHeight); + doMoveWindow(GetDlgItem(hDlg, IDC_CLEAR), leftPanelLeft + leftPanelWidth - 3 - buttonWidth, height - bottomDistance - buttonHeight, buttonWidth, buttonHeight, true); + + long rightPanelLeft = leftPanelLeft + leftPanelWidth + 3; + long rightPanelWidth = width - rightDistance - rightPanelLeft; + + doMoveWindow(GetDlgItem(hDlg, IDC_SDESC), rightPanelLeft, panelTop, rightPanelWidth, fontHeight, true); + doMoveWindow(GetDlgItem(hDlg, IDC_PROG), rightPanelLeft, panelTop + fontHeight + 4, rightPanelWidth, progbarHeight); + long editstatusTop = panelTop + fontHeight + 4 + progbarHeight + 8; + long editstatusHeight = panelHeight - (editstatusTop - panelTop) - buttonHeight - 3; + doMoveWindow(GetDlgItem(hDlg, IDC_EDITSTATUS), rightPanelLeft, editstatusTop, rightPanelWidth, editstatusHeight); + + doMoveWindow(GetDlgItem(hDlg, IDC_CANCELTASK), rightPanelLeft, height - bottomDistance - buttonHeight, buttonWidth, buttonHeight, true); + doMoveWindow(GetDlgItem(hDlg, IDOK), std::max(rightPanelLeft, rightPanelLeft + rightPanelWidth - buttonWidth), + height - bottomDistance - buttonHeight, buttonWidth, buttonHeight, true); + + long contentSeparateLeft = leftPanelLeft + leftPanelWidth, contentPanelTop = panelTop; + long contentSeparateHeight = panelHeight; + doMoveWindow(GetDlgItem(hDlg, IDC_CONTENTSEPARATE), contentSeparateLeft, -2, 3, height + 2); + + if (defer) + { + if (retry || !EndDeferWindowPos(deferCtx)) + { + invalidateRects.clear(); + onResize(false); + } + deferCtx = NULL; + } + + UpdateWindow(hDlg); + //Workaround for now (broken labels and buttons occur when resizing). + for (RECT &rect : invalidateRects) + InvalidateRect(hDlg, &rect, FALSE); +} +INT_PTR CALLBACK Win32TaskStatusTracker::DlgHandler(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + INT_PTR ret = (INT_PTR)FALSE; + Win32TaskStatusTracker* pThis = (Win32TaskStatusTracker*)GetWindowLongPtr(hDlg, GWLP_USERDATA); + if (pThis && pThis->mainPanelSplitter.handleWin32Message(hDlg, message, wParam, lParam)) + { + if (pThis->mainPanelSplitter.shouldResize()) + pThis->onResize(); + return (message == WM_SETCURSOR) ? (INT_PTR)TRUE : (INT_PTR)0; + } + + switch (message) + { + case WM_DESTROY: + break; + case WM_NCDESTROY: + break; + case WM_CLOSE: + if (pThis) + pThis->hDlg = NULL; + DestroyWindow(hDlg); + ret = (INT_PTR)TRUE; + break; + case WM_INITDIALOG: + { + SetWindowLongPtr(hDlg, GWLP_USERDATA, lParam); + pThis = (Win32TaskStatusTracker*)lParam; + pThis->mainPanelSplitter.setSplitterWindow(GetDlgItem(hDlg, IDC_CONTENTSEPARATE)); + pThis->mainPanelSplitter.handleWin32Message(hDlg, message, wParam, lParam); + pThis->hDlg = hDlg; + + pThis->processingSelection = false; + pThis->numShownLogEntries = 0; + + ShowWindow(GetDlgItem(hDlg, IDC_EDITSTATUS), SW_HIDE); + ShowWindow(GetDlgItem(hDlg, IDC_PROG), SW_HIDE); + ShowWindow(GetDlgItem(hDlg, IDC_SDESC), SW_HIDE); + EnableWindow(GetDlgItem(hDlg, IDC_CLEARALL), FALSE); + EnableWindow(GetDlgItem(hDlg, IDC_CLEAR), FALSE); + EnableWindow(GetDlgItem(hDlg, IDC_CANCELTASK), FALSE); + for (auto it = pThis->taskList.begin(); it != pThis->taskList.end(); ++it) + { + if (it->hasResult) + it->eraseIfStale = (it->result >= 0); + else + it->eraseIfStale = true; + } + pThis->eraseStaleElements(); + for (auto it = pThis->taskList.begin(); it != pThis->taskList.end(); ++it) + { + if (it->hasResult) + pThis->handleTaskCompletion(it, false); + else + pThis->mainOnTaskAdd(it); + } + HWND hListRunning = GetDlgItem(hDlg, IDC_LISTRUNNING); + if (ListBox_GetCount(hListRunning) > 0) + { + if (TaskStatusDesc *pDesc = listCtrlGetItem(hListRunning, 0)) + pThis->dlgSwitchShownTask(pDesc); + } + + ShowWindow(hDlg, SW_SHOW); + PostMessage(hDlg, WM_SIZE, 0, 0); + ret = (INT_PTR)TRUE; + } + break; + case WM_SIZE: + if (pThis) + { + pThis->onResize(); + ret = (INT_PTR)TRUE; + } + break; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_CLEARALL: + if (pThis) + { + for (std::list::iterator staleTaskRef : pThis->staleTaskRefs) + { + staleTaskRef->eraseIfStale = true; + pThis->dlgHideTask(&*staleTaskRef, false); + } + pThis->eraseStaleElements(); + pThis->mainOnProgressMessageUpdate(); + } + ret = (INT_PTR)TRUE; + break; + case IDC_CLEAR: + if (pThis) + { + HWND hListComplete = GetDlgItem(hDlg, IDC_LISTCOMPLETE); + + int iItem = ListBox_GetCurSel(hListComplete); + if (iItem != LB_ERR) + { + if (TaskStatusDesc* pDesc = listCtrlGetItem(hListComplete, iItem)) + pThis->dlgHideTask(pDesc); + pThis->mainOnProgressMessageUpdate(); + } + } + ret = (INT_PTR)TRUE; + break; + case IDC_CANCELTASK: + if (pThis) + { + HWND hListRunning = GetDlgItem(hDlg, IDC_LISTRUNNING); + int iItem = ListBox_GetCurSel(hListRunning); + if (iItem != LB_ERR) + { + TaskStatusDesc* pDesc = listCtrlGetItem(hListRunning, iItem); + std::shared_ptr pTask; + if (pDesc && (pTask = pDesc->wpTask.lock())) + { + //The cancel button will be disabled in response to the "OnCancelableChange" event. + pThis->appContext.taskManager.cancel(pTask.get()); + } + } + } + ret = (INT_PTR)TRUE; + break; + case IDCANCEL: + case IDOK: + SendMessage(hDlg, WM_CLOSE, 0, 0); + ret = (INT_PTR)TRUE; + break; + case IDC_LISTRUNNING: + case IDC_LISTCOMPLETE: + { + HWND hList = (HWND)lParam; + if (HIWORD(wParam) == LBN_SELCHANGE) + { + auto listCtrls = getListCtrls(hDlg); + if (std::find(listCtrls.begin(), listCtrls.end(), hList) == listCtrls.end()) + break; //Only looking for the task list views. + TaskStatusDesc* pDesc = listCtrlGetItem(hList, ListBox_GetCurSel(hList)); + pThis->dlgSwitchShownTask(pDesc); + } + } + break; + } + break; + } + return ret; +} + +Win32TaskStatusTracker::Win32TaskStatusTracker(class Win32AppContext& appContext, HWND hMainWndProgress, HWND hMainWndStatusText) + : TaskStatusTracker(appContext), hMainWndProgress(hMainWndProgress), hMainWndStatusText(hMainWndStatusText), mainPanelSplitter(0.3f, 0.2f, 0.8f) +{ + mainOnTotalProgressUpdate(); + mainOnProgressMessageUpdate(); +} +void Win32TaskStatusTracker::open() +{ + if (hDlg != NULL) + return; + runningTaskNameExtents.clear(); + completeTaskNameExtents.clear(); + Win32AppContext& appContext = reinterpret_cast(this->appContext); + hDlg = CreateDialogParam(appContext.getMainWindow().getHInstance(), MAKEINTRESOURCE(IDD_PROGRESS2), + appContext.getMainWindow().getWindow(), DlgHandler, (LPARAM)this); +} +void Win32TaskStatusTracker::close() +{ + if (hDlg == NULL) + return; + SendMessage(hDlg, WM_CLOSE, 0, 0); +} + +void Win32TaskStatusTracker::preEraseElement(std::list::iterator listEntry) +{ + dlgHideTask(&*listEntry); +} +void Win32TaskStatusTracker::mainOnTaskAdd(std::list::iterator listEntry) +{ + if (hDlg == NULL) + return; + HWND hListRunning = GetDlgItem(hDlg, IDC_LISTRUNNING); + auto nameT = unique_MultiByteToTCHAR(listEntry->name.c_str()); + int iItem = ListBox_GetCount(hListRunning); + ListBox_InsertString(hListRunning, iItem, nameT.get()); + ListBox_SetItemData(hListRunning, iItem, (LPARAM)&*listEntry); + listEntry->auxData = (uintptr_t)iItem; + + runningTaskNameExtents.push_back(GetTextExtent(hListRunning, nameT.get())); + assert(runningTaskNameExtents.size() == ListBox_GetCount(hListRunning)); + ListBox_SetHorizontalExtent(hListRunning, std::max(runningTaskNameExtents.back(), ListBox_GetHorizontalExtent(hListRunning))); + + EnableWindow(GetDlgItem(hDlg, IDC_CLEARALL), TRUE); + bool hasAnySelection = false; + auto listCtrls = getListCtrls(hDlg); + for (size_t i = 0; i < listCtrls.size(); ++i) + { + if (ListBox_GetCurSel(listCtrls[i]) != LB_ERR) + { + hasAnySelection = true; + break; + } + } + if (!hasAnySelection) + { + ListBox_SetCurSel(hListRunning, iItem); + this->dlgSwitchShownTask(&*listEntry); + } +} +void Win32TaskStatusTracker::mainOnTaskProgressUpdate(std::list::iterator listEntry) +{ + if (hDlg == NULL) + return; + HWND hListCtrl = NULL; int listIdx = 0; + if (!dlgFindListCtrlFor(&*listEntry, hListCtrl, listIdx)) + return; + if (ListBox_GetCurSel(hListCtrl) != listIdx) + return; + HWND hWndProgress = GetDlgItem(hDlg, IDC_PROG); + int oldRange = (int)SendMessage(hWndProgress, PBM_GETRANGE, (WPARAM)FALSE, (LPARAM)NULL); + int newRange = (int)std::min(listEntry->range, INT_MAX); + int progress = (int)std::min(listEntry->progress, INT_MAX); + if (listEntry->hasResult) + { + if (newRange == 0) + newRange = 1; + progress = newRange; + } + if (listEntry->hasResult && listEntry->result < 0) + SendMessage(hWndProgress, PBM_SETSTATE, (WPARAM)PBST_ERROR, (LPARAM)0); + else + SendMessage(hWndProgress, PBM_SETSTATE, (WPARAM)PBST_NORMAL, (LPARAM)0); + if (newRange == 0) + { + SetWindowLongPtr(hWndProgress, GWL_STYLE, + (GetWindowLongPtr(hWndProgress, GWL_STYLE) & (~(PBS_SMOOTH | PBS_SMOOTHREVERSE))) | PBS_MARQUEE); + SendMessage(hWndProgress, PBM_SETMARQUEE, (WPARAM)1, 0); + } + else + { + //Don't set the style if it's not necessary since setting the style resets the progress animation back to 0. + LONG_PTR oldStyle = GetWindowLongPtr(hWndProgress, GWL_STYLE); + if (oldStyle & PBS_MARQUEE || !(oldStyle & (PBS_SMOOTH | PBS_SMOOTHREVERSE))) + { + SetWindowLongPtr(hWndProgress, GWL_STYLE, (oldStyle & (~PBS_MARQUEE)) | PBS_SMOOTH | PBS_SMOOTHREVERSE); + } + if (oldRange != newRange) + SendMessage(hWndProgress, PBM_SETRANGE32, (WPARAM)0, (LPARAM)newRange); + } + if (newRange > 0) + { + SendMessage(hWndProgress, PBM_SETPOS, (WPARAM)std::min(progress, INT_MAX), (LPARAM)0); + } +} +void Win32TaskStatusTracker::mainOnTaskProgressDescUpdate(std::list::iterator listEntry) +{ + if (hDlg == NULL) + return; + HWND hListCtrl = NULL; int listIdx = 0; + if (!dlgFindListCtrlFor(&*listEntry, hListCtrl, listIdx)) + return; + if (ListBox_GetCurSel(hListCtrl) == listIdx) + { + HWND hWndDesc = GetDlgItem(hDlg, IDC_SDESC); + if (listEntry->hasResult) + { + std::string desc; + if (listEntry->result == TaskResult_Canceled) + desc.assign("Canceled: "); + else if (listEntry->result < 0) + desc.assign("Failed: "); + else + desc.assign("Succeeded: "); + desc += listEntry->name; + auto pDescT = unique_MultiByteToTCHAR(desc.c_str()); + Static_SetText(hWndDesc, pDescT.get()); + } + else + { + auto pDescT = unique_MultiByteToTCHAR(listEntry->curProgressDesc.c_str()); + Static_SetText(hWndDesc, pDescT.get()); + } + } +} +void Win32TaskStatusTracker::mainOnTaskAddLogMessage(std::list::iterator listEntry) +{ + if (hDlg == NULL) + return; + HWND hListCtrl = NULL; int listIdx = 0; + if (!dlgFindListCtrlFor(&*listEntry, hListCtrl, listIdx)) + return; + if (ListBox_GetCurSel(hListCtrl) == listIdx) + { + assert(listEntry->messages.size() >= numShownLogEntries); + if (numShownLogEntries >= listEntry->messages.size()) + return; + for (size_t i = numShownLogEntries; i < listEntry->messages.size(); ++i) + dlgAddLogText(listEntry->messages[i]); + numShownLogEntries = listEntry->messages.size(); + } +} +void Win32TaskStatusTracker::handleTaskCompletion(std::list::iterator listEntry, bool eraseStaleEntries) +{ + if (listEntry->hasResult && listEntry->result < 0) + listEntry->eraseIfStale = false; + if (hDlg == NULL) + return; + mainOnTaskProgressUpdate(listEntry); + HWND hListCtrl = NULL; int listIdx = 0; + bool wasSelected = false; + HWND hListRunning = GetDlgItem(hDlg, IDC_LISTRUNNING); + HWND hListComplete = GetDlgItem(hDlg, IDC_LISTCOMPLETE); + if (dlgFindListCtrlFor(&*listEntry, hListCtrl, listIdx) && hListCtrl == hListRunning) + { + processingSelection = true; + wasSelected = ListBox_GetCurSel(hListCtrl) == listIdx; + if (wasSelected) + ListBox_SetCurSel(hListCtrl, -1); + + assert(ListBox_GetCount(hListCtrl) == runningTaskNameExtents.size()); + assert(ListBox_GetCount(hListCtrl) > listIdx); + ListBox_DeleteString(hListCtrl, listIdx); + for (int i = listIdx; i < ListBox_GetCount(hListCtrl); ++i) + { + TaskStatusDesc *pDesc = reinterpret_cast(ListBox_GetItemData(hListCtrl, i)); + if (pDesc == nullptr) + continue; + assert(pDesc->auxData == i + 1); + pDesc->auxData = (uintptr_t)i; + } + listEntry->auxData = (uintptr_t)-1; + + assert(hListCtrl == hListRunning); + if (listIdx < runningTaskNameExtents.size()) + runningTaskNameExtents.erase(runningTaskNameExtents.begin() + listIdx); + auto maxIt = std::max_element(runningTaskNameExtents.begin(), runningTaskNameExtents.end()); + int extent = (maxIt == runningTaskNameExtents.end()) ? 10 : *maxIt; + ListBox_SetHorizontalExtent(hListRunning, extent); + + processingSelection = false; + } + + processingSelection = true; + auto nameT = unique_MultiByteToTCHAR(listEntry->name.c_str()); + + int iItem = ListBox_GetCount(hListComplete); + ListBox_InsertString(hListComplete, iItem, nameT.get()); + ListBox_SetItemData(hListComplete, iItem, (LPARAM)&*listEntry); + listEntry->auxData = (uintptr_t)iItem; + + completeTaskNameExtents.push_back(GetTextExtent(hListComplete, nameT.get())); + assert(completeTaskNameExtents.size() == ListBox_GetCount(hListComplete)); + ListBox_SetHorizontalExtent(hListComplete, std::max(completeTaskNameExtents.back(), ListBox_GetHorizontalExtent(hListComplete))); + + if (wasSelected) + { + ListBox_SetCurSel(hListComplete, iItem); + EnableWindow(GetDlgItem(hDlg, IDC_CLEARALL), TRUE); + EnableWindow(GetDlgItem(hDlg, IDC_CLEAR), TRUE); + } + processingSelection = false; + if (wasSelected && !listEntry->messages.empty()) + listEntry->eraseIfStale = false; + + bool hasAnySelection = wasSelected; + auto listCtrls = getListCtrls(hDlg); + for (size_t i = 0; i < listCtrls.size() && !hasAnySelection; ++i) + { + if (ListBox_GetCurSel(listCtrls[i]) != LB_ERR) + { + hasAnySelection = true; + } + } + if (!hasAnySelection) + { + if (!listEntry->messages.empty() || !listEntry->eraseIfStale) + { + ListBox_SetCurSel(hListComplete, iItem); + this->dlgSwitchShownTask(&*listEntry); + listEntry->eraseIfStale = false; + } + else + this->dlgSwitchShownTask(nullptr); + } + if (eraseStaleEntries) + eraseStaleElements(); +} +void Win32TaskStatusTracker::mainOnTaskCompletion(std::list::iterator listEntry) +{ + handleTaskCompletion(listEntry, true); +} +void Win32TaskStatusTracker::mainOnTaskCancelableChange(std::list::iterator listEntry) +{ + if (hDlg == NULL) + return; + HWND hListCtrl = NULL; int listIdx = 0; + if (!dlgFindListCtrlFor(&*listEntry, hListCtrl, listIdx)) + return; + if (ListBox_GetCurSel(hListCtrl) == listIdx) + { + EnableWindow(GetDlgItem(hDlg, IDC_CANCELTASK), (listEntry->cancelable) ? TRUE : FALSE); + } +} + +void Win32TaskStatusTracker::mainOnTotalProgressUpdate() +{ + if (hMainWndProgress == NULL) + return; + + //Don't set the style if it's not necessary since setting the style resets the progress animation back to 0. + LONG_PTR oldStyle = GetWindowLongPtr(hMainWndProgress, GWL_STYLE); + + size_t numTasks = taskList.size() - staleTaskRefs.size(); + if (numTasks == 0) + { + SendMessage(hMainWndProgress, PBM_SETMARQUEE, (WPARAM)FALSE, (LPARAM)0); + if (oldStyle & PBS_MARQUEE) + { + SetWindowLongPtr(hMainWndProgress, GWL_STYLE, (oldStyle & (~PBS_MARQUEE)) | PBS_SMOOTH | PBS_SMOOTHREVERSE); + } + + if (lastTaskResult < 0 && lastTaskResult != TaskResult_Canceled) + SendMessage(hMainWndProgress, PBM_SETSTATE, (WPARAM)PBST_ERROR, (LPARAM)0); + else + SendMessage(hMainWndProgress, PBM_SETSTATE, (WPARAM)PBST_NORMAL, (LPARAM)0); + SendMessage(hMainWndProgress, PBM_SETRANGE32, (WPARAM)0, (LPARAM)1); + SendMessage(hMainWndProgress, PBM_SETPOS, (WPARAM)0, (LPARAM)0); + } + else + { + SendMessage(hMainWndProgress, PBM_SETSTATE, (WPARAM)PBST_NORMAL, (LPARAM)0); + + unsigned int range = 10000; + unsigned int progress = std::min(static_cast(totalProgress * range), range); + SendMessage(hMainWndProgress, PBM_SETMARQUEE, (WPARAM)FALSE, (LPARAM)0); + if (oldStyle & PBS_MARQUEE) + { + SetWindowLongPtr(hMainWndProgress, GWL_STYLE, (oldStyle & (~PBS_MARQUEE)) | PBS_SMOOTH | PBS_SMOOTHREVERSE); + } + + SendMessage(hMainWndProgress, PBM_SETRANGE32, (WPARAM)0, (LPARAM)range); + SendMessage(hMainWndProgress, PBM_SETPOS, (WPARAM)progress, (LPARAM)0); + } +} +void Win32TaskStatusTracker::mainOnProgressMessageUpdate() +{ + if (hMainWndStatusText == NULL) + return; + + size_t numTasks = taskList.size() - staleTaskRefs.size(); + if (numTasks == 0) + { + size_t numFailedTasks = 0; + for (std::list::iterator staleTaskRef : this->staleTaskRefs) + { + if (staleTaskRef->hasResult && staleTaskRef->result < 0) + ++numFailedTasks; + } + if (numFailedTasks == 0) + Static_SetText(hMainWndStatusText, TEXT("Ready")); + else + { + std::basic_string failMsg = std::format( + TEXT("Ready - {} task{} completed with errors, see the Task Progress tracker"), + numFailedTasks, (numFailedTasks > 1) ? TEXT("s") : TEXT("")); + Static_SetText(hMainWndStatusText, failMsg.c_str()); + } + } + else + { + auto numThreads = appContext.taskManager.getNumThreadsWorking(); + std::string fullDesc = std::format("({} task{}, {} thread{} working) {}", + numTasks, (numTasks!=1) ? "s" : "", + numThreads, (numThreads!=1) ? "s" : "", + latestProgressMessage); + auto lastTaskDescT = unique_MultiByteToTCHAR(fullDesc.c_str()); + Static_SetText(hMainWndStatusText, lastTaskDescT.get()); + } +} diff --git a/UABE_Win32/Win32TaskStatusTracker.h b/UABE_Win32/Win32TaskStatusTracker.h new file mode 100644 index 0000000..dadca72 --- /dev/null +++ b/UABE_Win32/Win32TaskStatusTracker.h @@ -0,0 +1,52 @@ +#pragma once +#include "../UABE_Generic/TaskStatusTracker.h" +#include "Win32AppContext.h" +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#include "SplitterControlHandler.h" +#include + +class Win32TaskStatusTracker : public TaskStatusTracker +{ + HWND hDlg = NULL; + HWND hMainWndProgress = NULL; + HWND hMainWndStatusText = NULL; + + SplitterControlHandler mainPanelSplitter; + + std::vector runningTaskNameExtents; + std::vector completeTaskNameExtents; + + bool processingSelection = false; + size_t numShownLogEntries = 0; + bool dlgAddLogText(const std::string& text); + bool dlgPutLogText(TaskStatusDesc* pDesc); + bool dlgFindListCtrlFor(TaskStatusDesc* pDesc, HWND& hList, int& listIdx); + void dlgSwitchShownTask(TaskStatusDesc* pDesc); + void dlgHideTask(TaskStatusDesc* pDesc, bool eraseStale = true); + inline size_t dlgGetListIdx(TaskStatusDesc* pDesc) + { + return (size_t)pDesc->auxData; + } + static INT_PTR CALLBACK DlgHandler(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); + void onResize(bool defer = true); + void handleTaskCompletion(std::list::iterator listEntry, bool eraseStaleEntries = false); + +public: + inline HWND getDialog() { return hDlg; } + Win32TaskStatusTracker(class Win32AppContext& appContext, HWND hMainWndProgress, HWND hMainWndStatusText); + void open(); + void close(); + + void preEraseElement(std::list::iterator listEntry); + void mainOnTaskAdd(std::list::iterator listEntry); + void mainOnTaskProgressUpdate(std::list::iterator listEntry); + void mainOnTaskProgressDescUpdate(std::list::iterator listEntry); + void mainOnTaskAddLogMessage(std::list::iterator listEntry); + void mainOnTaskCompletion(std::list::iterator listEntry); + void mainOnTaskCancelableChange(std::list::iterator listEntry); + + void mainOnTotalProgressUpdate(); + void mainOnProgressMessageUpdate(); +}; diff --git a/UABE_Win32/api.h b/UABE_Win32/api.h new file mode 100644 index 0000000..e3e29b8 --- /dev/null +++ b/UABE_Win32/api.h @@ -0,0 +1,6 @@ +#pragma once +#ifdef UABE_Win32_EXPORTS +#define UABE_Win32_API __declspec(dllexport) +#else +#define UABE_Win32_API __declspec(dllimport) +#endif diff --git a/UABE_Win32/resource.h b/UABE_Win32/resource.h new file mode 100644 index 0000000000000000000000000000000000000000..a33c37d7e554042a7d29dc2aa1bd60ea6016bc45 GIT binary patch literal 27332 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 + +#include +#include +#include +#include + +#include +#include +#include + +#ifndef __checkoutofmemory +#define __assert(exp,msg) if(!(exp)){MessageBoxA(NULL,msg,"Error",16);ExitProcess(E_OUTOFMEMORY);} +#define __checkoutofmemory(exp) __assert(!(exp),"Out of memory!") +#endif diff --git a/UABE_Win32/targetver.h b/UABE_Win32/targetver.h new file mode 100644 index 0000000..05ba7d4 --- /dev/null +++ b/UABE_Win32/targetver.h @@ -0,0 +1,3 @@ +#pragma once + +#include diff --git a/classdata.tpk b/classdata.tpk new file mode 100644 index 0000000000000000000000000000000000000000..88dbbd0e1c10950bbe5c9f0b69d0394843f3df02 GIT binary patch literal 72945 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

2|#FP>}-ZxGYG}mn7EqJ;B!M4QD-U3 zk6zN{M*;_6d1h=P%3QrWp>y2JQ7h_?4icv-gz<6dF<#-zHevv0OS980zaFbW%=5w+ zk5C+Eia=_GspKRP42jH(OnpS}GJ{rLI92pC3YCYlR@24$ZuU=cp-*SiwdAOPXfj}X z4Tu~@bVD0R5@Xo421B6IBv=v3OU881hP%4aBB10f4W)@`f-gU37nU(G;fDNc1oA66 zLu54$N+=|N;R4LYv1k(PF>lEzd6~|X&gP#SLW&IaG&&89)#tcX2*w5d{;hY#S$K2@ zZy41bLZ^T~lb6z21mF<_(!tNto{@^Q>k>flC4KNt#`PG+MQ2y4si=A@ZYD6-kY=M9J4e(a=P883io0a!zCZYB(C;AT|T>gYqYSoT2+? zS8#@aPce%IkTJ~%u-byu18u*Y|PJ4g4YQq&oUHvL-_@VOX1tdLGal$aF z_I#ql#h`0<2HhK<(BR__*wy8*>zfOuFG{y`YReFeQ6`gQFgOflFwWLoyEkT-Vp<`- zSt}HvE5jMP>R}eSr&=liru#^v8)S2kyoH=ut#CyL_xT8Y6Up1r%&Q80U>UC>+Uxt-*R%8z)bgP^%Glz( z^5)V^p8d56?r)Ss@^Xpx=zW8!eYO0~xN+}Gw7nRx&Tm89mKfs9Tu zz>(L!V`OCjdK|>GV3+-URkv>!k{_N<6f}2G&6-C^X_}DeNo>hAOk}&h+bf(4`-WTh z!3$F#`h!w(i!Ni92`uh_tBnC_RZ1L3R@tg5ft z`;)X;#zJLwJhKl3zqK?m8(!})Q}65)Jg0Dc*1-K}QcAZ9o+x>X{fQ`R@4A}D{Bwa) z+eh08tu^~r{ItX3cD%qjc>jSbJMHxeN=;ZWJVaLNc-bQoO*tTu@A#}f2K;~$l&Ezw za3yc7#bT1V0}SOYg$uWTo@!ZNG3Y@2e8nY#KTWG;AD4<*g&G$2FY|S1$RgKOB zr_9YjjEO7yFpvrgG|1ths$EdGv8x7Na~D6Tz~F!7Gd>r>^FH9U`{{AdqQ?(hmB=zc zDEy)k1t^Mo`md*qR}r3d4Idy{%^#{@Fqx)u=n+s*Vc{>3 zUX)H_+E<-~&m2C%#htKFVY=7-2#4+EgQdOTlENNWhqm?gf5`aL7WvDN4XBLAEiI&p zxnpeyU~>?Pr9lzfr}xoZ>EDjbrSMZuEmnkTD2}uRDyqFC0j-cHBin1unObSyIE#@# zW|R_b4?LIgg{XJxYwv_F7Dc7vzt!)=#(X=@uori&_p2{}#`J}5mY<_`N8MX$YZ<0K z!`9l*+?j7X$WHU94k}3}RaU6L?d&-aUI>jA6w#MThIz}vwW5P&f28+#4POszG`2&u z-&2Gr9O0Khz@sFC*30k^Lmu{S#s$F^L)o1oV%}4GsfbMm(M?}bV$@1%kTgysviC!A ze*3`Fckn~O)d83^YJ1YnI|L+VVA(nYiU5_S#GPOp>gNEL#@+0L$@^H>;XtWx+5>Z5 zvxyd=6mA}%fkF1YpyThbB=g8lVRX=0<0r+fX@%~?+fac2QWq}!Q3i+Fs`_Gn%k#CsnsQ!_C^5WDQWpGZZzO)hE%FH}n_+mo>xdz1TkH*6} z8g(E3$%Aewik*(3Ph7MGPPFSgB|SU&)PK;JJt~w1JSN+OyXhX^o}J+j%x2II1KEd{ zwh)%RV--0^PTTgIDOn!cN|GWV;;C95cqX&d#8Hal%l-O5yDRCV*1O_Y+q_j)SGGBw zD;I28l0~lEdKvUkaWB^8CEnlE6G3LYFKSxm-0&q28&9DwTXz9Jf*sEPVD{Wfq58i9 z>%;X4aF2?_rlG8WoEt?UbL{~#&9$34$(IS3(&5s`#))jA6pe=CHbi#<{0Lba^Y907N13`Q=GXWVQvt-AGrSRcS{&~@g zJ{wFkQ`-qs7Pe2RW4>N~54tvhCT6$jbZmA|W33vxF3&$Tgr5C?R%zS*_SG5ZIH5(N z8{GT*Ry<5F0P=$goQ}JWl<%y7iZ&Q={Ihm&)}#wdUz1s}i)P;0b25S${Mv;upz1af zStO5Z&|ZDNL@H88>zDT}6zp}NggL|-yvNd98r5RrxftUB0O;z#? zqP!MSB%MBL_tvw_-Qh+9A<(V{p44dkHQ03FJ(o(uOFm^R`Q~F@F$yI>&YvnWR2O*j zeMcynE3%gUhHMr!X2zwFk!+IqGpg#e^*%AwlB`xYQg5%* z<%R0EOufd-0W}jVzZQ=o+R*pV?jFjv=TX5blBPlcWWlg_;h3qYgCU=IDsR<9_Hl0| zwAK;||LPAvktfP;;!CQ3209TDvkIr&^`lRJ>2Y%Fy+EJa0n&1J@q#)18IAGP-MzvH zNGv#WVyx2dKgEFx1y9ql;l2^{8%c68f0SoP3The!GU6NkDX*5HITU3rb@Hm*n_muu zEK2N~vT*lhO#_22Jd0X=7G`f)SQ%4mSVM~NQY4Z`C5VipRw-jg z5n4&7CybF`~ya*)>nFn zGCqQ`%YzCn4D_}&Xz(?0sK-0fX=Fn0nTtBmm=CSy-4e$W*yw1{lY* zi!{GWEy>7n)Hfr7Y~kWH0MJf#+VcyLr@N*zI{IUdQtGXtV$< zwN4Z2ED&8U031#q3iqY9Ku^5L5B*-I_EgloHvW%%H~o9)-C9dGqp%)OC?$IW%ysG# z^_2-kH|H%1fit7U4Om|C``PO+7C%*om!|%^kl`}UE)zpb9RpMMEb!XGsTjKLLSak= zT6g#(2%&#DnQ@r>Q|*MR{jYaVQmX>s;k^Ka%ZaBHC)5a-t9~cWy(~q2SMv2QOqJj_ z-_*fXT(7h?yP65vEjBmfm{k0jAol&roVY7bSb;NeBliJB&?jWjU1gK=9|+lP^dhrH zlhXh!!-XwZGf*mD9_aVs*y(2=h@1j1}7bAk;6a=yYSm*U$o>NcAD# zpRN7D_-Jl^(@Dm}Qf^!0S&U9fToZyrrZ#G)s#U$K*as*^e(2}uzr*`>v>wK!yDC`; z`pn1^mU<0kt3bZvVpAarT=8!YA`!aRxQs{uvMy2$+=S|Et!ZxDo zBpJ^)%lROZT|UGm-3`&dDr`dp!62Q-7U3(2+jqp+CL@-+PJT9=y1Sxr%Qj#J`;*-O z*yk2^`ZVIB&Zm;dAySt@IQMPeQe1pEC(O|egqUIY&w}9@oE}*QxskUp1y-A;;~*!C z6Xx3XP8onB$`?#TwDzTSG-mVYyNmw^bI*hJiMa^H{N4T4sIU8jTfp(12|L^QJm(b^2C~K0-eD4>x{@s z$<%kCm3`wr@qY)KlXL+?j}J~5BZyjgl`NhgpWO4xJ`>hXq;uQ8>K|qH13F6vj;nyR za@EEEMB%xwN>a@@W=nd^dYA@ZmlHuzozv8P0+3G}hXZ#SJkswmYeJyiwde6ZsQ!Pv$u zT&f&NB*AYQXJw<-OWizVLWdi>Ok}rCLZAuXQ1s7Ar@=Z`Ecz~^xN+MNk#WUGTG34u@!8+(V3n z;#Z@N-z!-lZcC9r%!8j8^s;=(w4S!}J$ZL=c!_KeLm5#xD;GLz0woAc^$9V8r45@= z6her7BJ?xx-}8R?oVmzAc584?=mNw(Csx2w2ttJf*3riv$L(Mu4~3?z{}<%nxC(sr z3z4Bor;Wo6ynMz`gY#9GXyPoNu1f*P zxBj@Qs;9?}>exK=aRUoZ+`2E|{S3XfeaXs!(hntf39*vvH`A@fZnhF9QJJN`{URc+ z|I6M9mfR;MU;lz6B}}uwo>0x=6t(QStofM1wM^DD`_UG(=sNen(jsonrUHUcMIBAQ zM$v`qKo?$u4~eu5*d?0%efC3RrEwYE%PASW_Y#6w-|vR+!`y4uQ33p2@>Y>}Gil6- zYZlA|%h5y9vWJ4HW$@{fOi3;P0;&$zQ7vqTqD^x!*&O~7wle!GLKTw4X&i% zA$hg0Q;BHmNm(b!chQJ+>+X$CJgF9kxBGgE=b0>w*vtbf!U2rU*lfc74#TogtMq+ICw{WNcOYT7^2 zkogWLz)}(uP`bugrsa*A+)jY51geen9pMz1uxH;Yy9#G3$V`4x{usZnv`EF8aN*ZJ+T;)=&3@25=&%@B ze|SuW-tV`kbqlXq-knFUbPCEYEvREGRqMs=b%66B+NCEz4*Rq7Ed&Zj-b2@~zYv5l z$igQ57L&!$lIsE7RkP3oG|0$cIe5Z8lHo83J<)h+31p!)?%=QI=Un~M>sD(7Nj z1e7zCPG1QS`UcEO$$UAqyLnM3ZgvZ5bpE%nXuD?i%9GswXUH|?VHz~o@nV5k7=uWG zh#s+NgS@?L&`UsQVwCXi7(`aXuqu_4^rw%xmyAoMSYk8?^NB!{V}P8mK+G%ie;GM+R}ZSPYx9sDnejXED~Ti1YpO>ReLa&>n! zOf;ZDTx*M-E?WwV9AWA~*vGwFWe4tZ{WUKNu{mfbIUdWzi75@6TG#!fae%%MxqHj9 z6%LwW+6a3CQN4{8qh{GrsK3cuU%eIajNqs_6wn8bs;`R-0>sjvUFBXh}FZKV( z7y2L~i(s<;8%OA*W8=|~^#Bet=Z(AWTM-Hvr*UoEBfCr(G(9XRHeE-J_y?m+1SwRD;2*5VQ8k91Q{Po2j}NSH+-eV9Y7@ATL{WgpvklwOeE|4 z1ssjW)8Y54JoQ6}UObmt03>7Y&+2A=k15bfbm39s`)XmSf-d`ORVO8}`ycwJJ>YY! z8H-d;$(ARE@{m#|>o`%lfwU0@5mtY{-1&z0UOLAfyR<8XnHZm;K{UA{n*ev%L;izQ z3Ya|4RaOqPL-pzXVfDjMBh<+7M5}@lqS+SSH@UK-7xv*gCdvbj^(=o?l%w>BOi$!OBF^Zj467HsM$BrG) za}JSmU3o-GVax|Ci=*t?5XK5}P}o;t5>nApO-PI@R!EUI>_*xbU#aJ*huY9KlS(y; z#uY(d+92<$9c^|0I|$YALqX*b*8Jo;%+i95?Ch`k_y3vryli9TTW_SXzn;@l#7{M< z!G7w&uxoLG1pk}Z(LAQ>3&};eQZo)4M_;GQT*VC#o#;U4g3fB-NMktM?YOq>@k0M` z0yXwiJ7_2W=%*oEt_gh#| z5v{iv?=61kNJr<=n=np5@=0 zcUi(&+#y2IxU`&@{8A+Zo3!lKHY)qKXdj(8iJRM>iz2uifUBqTJY-|yMVE+FC|3*w z&lk(z;kh-f%dQf`^NBc`?wB2aiANv$0pK*X<(`0Oes0s1ZsFRzD^|bC=?*Cc}fzRG4jyLwef@1j_=a^d>Gr@qFb0#4D2l6 z-&t8|4;Lm739KKSEk73s9MDEoi$ObQ3cY+M4&_vt)Doci=iKcYJI?1g0NhM{$~hwY zFd-^(T3#)miSPRklSTIKRo(Os0CIsaPEo6JU*gH=KeZ=x;8FveE4HW$2WGKXKYI8P zXgvnNZI9N1Yv=|h1;br&Apx5vfH)3GRH{VX4pZ5DK3{PRF7z*5PjbW8#d#!!@g4@6$C+DNRIG8Y?D#~hD)M9IhC^$fgJBRPGVpI`+= zDQi!}@nle?hmEwL9)keY79AcAM}>HXoI2>5P`bo@g7dFSsz$l;!FK{YG`=Rg@#8+E z@N2Xm@4|5L4N-5RGK3(7Bw)4@V>Vr8~lqEv{Fcc1!>-#Vzm(VmyrEUB3dxM?>v z;@kNM)Zh)?Awim9XlX!b5jN-xZg$FO&v5F9=0j9T?Suoyo=|qF?F9c-hwk??m+pJK z?YKF63;f0(~w)z;zM4{4N5u?zlsvp3{B8rkffSekYr; zf#C8P^@PW7(X|{_4h)TLGcKvD1fADpD)K2_wu+q&bfkXO`FXYlcJoyuCz5ZwEaiwcln=TjE%cwJ+$U|8G?wr6lA7 znH*i^ZQro{{pKwa;pJ{|92S#3+^s*LKVxK6t)gYn{aWqyCi8R~2P^?p{zX5GAs=kh zNj$RcYA0nBMy$_$Hd~?wP{?0+oP*Vk#XKAFq=Zc`!#)Ps1R%R{Pe&i7y;%)$EwymS zKIyRLYwitn=dinqXc>Z05MBczF%v@5#|o2k(7is?p85yPKyrezWLNcEN_U(z!=Hij zkf_I6O~{Q9!e}YimMa(jJ!x4_G{1U48fbFc{X+Pd5(gFCer+-r@WpbAAz3`}H<{hA zG0$a8GN5{xp{hmH&ygkR72X53W$%rt+ic>2-e%p;k$xs&YdL!|Jhnx^?CA;th2I0(eb6{(ukya`87#Lg^ zsP?HO!pSQ9|BDC3o%co?!ARG?-5MmECSb9Hi(T4WZ%YTHC;bw6GFd+%+8h=JQ=?jM zJ!#jJSPeSuZ6!PEEgwhAT5{3W!Vq-2s(N?R&?)sqx7fsz8JH9L z_5v`zdP#QN1Adya0TMOYK5VZm5_+v%cXkd87*aflE4o*H13sz$IW;igl$lm6@t%M4 z70b_W1i6>Bc)#BR8b)NHsbW$V?ej?WX^!@XuyT{YLp%8R&Zw@&gvqjSg>ENpiJ#mz zONGHoWcf+77lGAPIq}M)%JJ#fLX{@5b|OpkVGDPq>~tDA8k{hxG4=gW{H*05R*qDk z-a?@K2nz;lD9R$ht!o^9VkI=My7gaEo2y)^fU51Ipihyr)WDpQFy?z;8!m(9prx%4 zrh_YsOHU-Ro01=_DK}FCFB+OpO3lM4(x>RTqCZMg*|(ta|hLt zdj=p*98W6HNUVhPiGUPAwWTRU`^Tn8cx6rGUvsqfqnEzJf7y01LK5O>Hl$PN-f;aN z;s$#I#j?5rRn64m3$DS3N?^9SIY5W|-8xo_IF$m)8(tcah7=6&MW_6=$ip*gp;EZ# z#N2?v>X!E3Bx!o^#D$GTynYX@jHp`49T~b6x$c0!mvbY+2_cBOZP@&S52R3K4F?&p z*5v0l8sU$`xq#zmo(RYh#ie{wcGO|vRrd&acl5a)$Dn{x^4>Gj$zoCknW0`(hiXLy zrUqVqTN$CSFw<`=GyviH6!BwN5?eS1bR+9>Li54GR07k^=*5D^Dt_vf= zE(ZU0j|$Ri>7(D9)av@qT}-}}<`-H?ayj)^gO_7REgA4OhS5{+MEsoDOY&E6;Uua_ zn|R`5(b&;m;mEqNsGA)x{jU_%pkq<*!G@{@3{y0TCTJ7J%TTk7chzsQH4pCa$NR#3 z1CT8HpZEm|Rv=^$bc(hEiMwspOH{{kwI?^yz8PAL8Ybwt{T2@Z7NyW5I8f!|pA+6P zuZg^M>qMx#7mOtGvVl910dCt>wX4*v$J6^w)N&}&*f{d_Tz`*O!KO%I(Q-9NSqUfj z5eC0@RW1jrk&1?%kOZ#08KWkXAWXL1=Z_poEw10m!jf2BSbPT^f%^N}JQVOXF30SC zz1Ow@id%~`T#$X7vq~L1ao-83;6F8K1y!jzj+7FHQYL}^PhaAR8~q)(N%MNojHV9L z>cta7#pF#_z@X#@U^p>(-&vQII!Cc*>Q3hNhbfr(@O>s)p2mxjh9KXmJP%IA&hl>N|E-K7wt_StMOTL~(gc zdzrRo+2@j4I{P}&5{tf>^Jqio`8~j&D#LMupXFcumQbP@xPyOddhuyj%6Z8VT`!66 zgJkfIiHD$&X!A0MoounB+dBx-Llc<@Z~+S+Pi(W~(WLr62B(L(`P2})O!t7V%p^$Ix zhs66|O%L_gR?H5KZ2dNGfAWkH3h~-_=j6NBm3Pu0zl-|lPDU}Q9wN|+t;vcFl-78E z$PliL=Q-o@MI2@o2Jszx$4|E^sq|<_MryQqzva^uhc>MAOs+bHI#RB2acll08Y#ie z?+0*L`0%-BZmP!@RIW1JjQou)nB?YOeYnL$F@_Z8MfYC!?cx_qs zR#$2qFd+uem%8(bQc`^V>0V=6i`)k2$UjkC`9dfyE>;b;kfDq#!f&>}MQ6D3sL#Uej($#y+^5y^(Cib=o6<;}w z)wNf~zTqi4ehYRQ!f{-4aPNN^HQ?1G{y09Q(xXTdF9f(h@SuM(;)`~gyfVBzj^C@> z4z}4zj^HDm6fy4ay}}9tskI#vG(UlgqN1iwC`%~2+Fd28K%WQ+Yh==+*v`R~y7?tXpTWaBy%$%#K`iNf8kiMIi$ zVMuM80_iBzN4B6-YaT&POQBZ5Vb&(Az`e6&Gkdb`?tTBm%8jh<%jD_hI}8f)-%o`^ zl|pgX_IJQ{8Wx2G(=+;Sx;~cXJ3EEVG6x>SXAaWgQg+?61s(59v$?(Y)(0w6Hht-8 zHFUY;?4=wtH27 z5uju_w^5)kZ4+vvhGU0veR}w7jd}%#6A!>t zJg5ySd&#buhM70pJ)rGK#!^JofBhf^(E<bEYbB`A z`w5q$wY)GkALMxrQ7Ck&XBsOtGc|;o-SL;mPFn*tAk>C<+Ql0ncDSkpe-;{faU@|~ z8W$W<4kqk7ikwaY0neT8gB(Zjc$Q++vsgH%clT!6H#xkURR{jwhCX;V|EulEk%gq3 zFaY8kizJHKSd=9NF77jGk)Xb_OhrMQQAL3$B>v0MGg4q<`I1R)*W0rTgfSR7QQvaf zb}*dvfX9n^NdN^RvDiuKfR?T;IsM?(&6!CEua_)+0gK?_$_4G0Q)j{yz4q7neWd7{ zm2b4WS=wdhi{jYUOvJ+J|E+Lz!v6E{3_&?RJ)1u4P*w%J<2LtIl=cWl7XvXb2Z=S3B5+ebwINrq!Xt&sh_)x}|OVBXERn#w0;2+Ep zO01eVRctZ2D8#&TI4ah{i`B8fL|t0OU5M@{hTZlpMit9?gZLjKzabObS9&2Udc%Sr z+twbJ(}5>azy5zpc$MTE0;rXqqSI4G2D567u3MRUyK8T2nxwXXRRo{c z{m*bpJeOR?8i-o1EQAd?FU=B0k?hxDLT<3iGvl8KY1xhJGqhy;;1h&k>gv|-GVXUy%`g&W&HlBVH@!pVbRP6@@J6c~^ zd-OGKl}HOn=RT^9g^F|AF-`^+>T%9nlV{?O4hYQPj%PK!I_MP0mT%HoV?DU%D!sw? z%}m-n$%0Be$NvB-2ezA1;9_k{TF$USKwD*mMH2o}!_J)OR{X}{G(~TaFzVryf;ruR z%j$*q7h?|Fdy*A|V_WSbHpJ!hNcPR~`h280N{X|ySAsFqt3MU#S{!*IT%DE!9F1Z1 zq?rGu?={#YJ~UPCLh}XY%uCfd$|AcWDg~v?hs0XbpE;bb`!Yy&8@53XopON-6JXSt z%;6b~qKFV+JK4cfTLUQFmG zkL0{tPSyqzmVzvF2!_->3E>U2>h+R^bd|rav_PS0A+KMR1;cBiDpn&o|z7J~F#B`dN-VNH(gSsM#Q)X6| z9@3h|A9Uuqvc$hS;;$)@omEy^N@{+0-sLezW+Ll=g-4}|O>E%;!vx376@E|kQ`yH` zlTLW@jS356L!TbdW@T9TKtT1$w_mL=aPIq$8PXQx$c!8KFRL*wlcH8KTVujFb&9;u z)8ocKzTqyc@!CKwPJ+<6Gk+Gs?8+Hf@I?3EA7ZjT(gZJg&R2aWnFE4r1Yg_iG@mt$ zDl6ZRIU2|RntgAI(NmPdq zNwJ=rpIhbSqS&fPAu6--ZM}6ksWOP299AgPrCx8R;Tb$$S!4C`tAC})VfZmpXkBK? zkDcnJ+IyLhwDs9IUqvo~g~)MT)3t>$R4^2twk<%D4`l38N4p4-HGrQY7Yu%-y`^U6 zKsPZ5pJ4W|*SDrx;Dh+@U;o5o5`HN0&fD6)IPw9+&t2bdYlhE0#{Yb^MdP(Zyr*o= zA(sQW;N$-hio7GotBAUch;z1Dn>M3a!LR`tCYJvkirNbJUUA^@Q+4d@d?xyH<@Q|P zz{Rip@XFq`$-1e|Ax_$HQ@l$VD<9Ux92{_Nu&zOm%Gh*3_0PPqBX%_gqrut)kb(;J zLa;{_oL32Gl8en3?7D>)ur1|Hhv9T~8A2TPEFG2@mr+}p6rd|-uBxabE+mUQaYIQJ zEOQz>mCS-3y{TbU4W*`negE^=Q<*p@8$tSZ5wsNUipNQ6h3VklQvN6w@!@mGGz?ZM zS#PIPSGb9N9c$=|C<^7`fNKtTC^u)Q!XpEc0e2|oINwY_obt8{N zIXgdjBx^SNCOO)6)bjkmVkS50saL16+vUA}Tg~T;0CHB~~oAWgkHaPu27#Os4asXng{Xxn0aS>dQFz`cR7vP%8zD*NQE zu&COY*J|jmN=<>Ky=88bOfCuS2b0jy_H)0FFrPGq=6ohW2S^(=YlkwH_P~ej3iF2C z=bcnpb1zY2v)ChVVm2O0uwes_uZA@*|s$ryYKa@F{VbS%4mM%{7jd0E7nc<5q^)6R{onX zPV=KDsn6MBwSIwe`6tawkPy$<$l7R%#-7My>-3HGSYoiw2Kr$KZvX1YAmnM-*3s+i8-*qtk0f&g2#D+Q!ry~6ed}*J@1Z+@j{>kW?S5ZmGqz2 z{2UHWg8-@Mnr4lA4k)G9t8FDzvO1hv(I4U)j~%rbDa!Bi4zLQ}mDsNH&J%8CQ*@qX zqC3RRiJSlAoOt%6K!TT%eghWY0-Sd%>eoK>znsw6g6#v=by2nqdE3jMU$;~ zUqmO5L49T!D2%xwRd> z;$LV5!~0J=I5gdwl!CWD8hLT!i!QQ7>XnA}gFRM0F@iwZXx(eO zq_#B)4r`w=?A0aYGN1p}j0C~4ZhQ_bM*CrSx+rJ_bT+F8|T$Nr0(c&T!ok}#k9h!l03imB0kqL8_; zlM7yb5h(SKh``s^;KC(DJGl(Tu)*_Sjo|Tiba|xaPh>|MgZux=b$e#lvK0}jHr3c! zkUZ4u>~V}PT_OKp4)uI+ZnZSfv^bjTtpdv}Chr!U9*9a=86wg+IkG-f7|JW? z*|4ns2^UK%Nk%f7`J}e4cQ<^Ui^(>4UzWzGHqc@pZ>wAHgpoXlR2QE>0*$KJ5-!G? z{oEBKR8+@2K)ro>D=h{bruzGpTJ1(Gy7BWhHA}ka{@L^mD??miI{if(wb5=~yVu25 zpZD`9U#1z|T6Rzy0J9VvWue$G&Reh;7hk^uf4}V)!uSc!X%^ZmzhxLGBo*JCiI z#p6XGI=^y(93rrukVs{5Pq|*JIB5B`zA+8ZC<<$K;Z481i@*7ZA#CDEa^~U@9u17IVr+)kverl4ERRcIQ-NUdCbnBW2&OzKQ07 zBE2ItK(5ilPyl=ufGxxyG8o@PprCO$YJ!7E3nW4CjKQ`fPK65Q!KDkIrUOtIcDVvq zzRM`SAf>&NCr?qpt9+Wk5}NkF5j{$tIgbO z+~F+VZosxGAs>9*e8KB2r7A%-&l`Ux4Fi?C{`rh!~&0(&^;~g3P=W|{XcM?^|!OxRm z3TnK%{%EOdP0Q}We+F&3>E6gc*sGZHYwOHK8hh*67yK8p!n|XCG=u)GuVaQ|sxeS# z+Vyn_9t}4|b_NrCjSsy@+E197L->|!I3eg|$T!#3>2GBcUEZe#hhzYkd;mvRK(n8< zV%C}p(3)W1gbVM6ffACfzYk(HF1iGR|3O^}MN6x8GqwMeFEzH!rc3QBRO%#6b0Pc~ z($gF`ha3YhAzRWQuf+t|b#EzQ-IKapS{DS5p(UQLY)%4BaH5^Py(Xc^OaFg{vR?rJ_D&om9k$TVEK5hoAp*L$RDoPtvu z2A#Br;UQ>K85=zA;zo7+;UwZ(wpMq@u`inRq4XI;$HHcj3b(Nml^u`wZ>ZCYV#N{a zOhk6WpS*SGXx~-f48p}9(i%+8ZD=BviChn%FFo+6XRAy?%j8=t zb!F6HDUsRr6_+*;W;n;K?K-J=wfP!*p!IkF%9Dn1i^3Y{)BQla`ap!N+Kv~+<8mHI4jnJ){p+|v_=M=`)#-r+d~QH>T;`G8z`^7dJhGZ2gEGUdxC&&Yn94paf} z-d$ubbd9#38}8TLOfQI(Fa}nT|IOOhOc3V-kao~%7M2FzmPOINm#F9F|>1s8(qDv3LLk za$%-Ufi=n}0{t!O-*SjCL-6=}Qhvx=#Sv7|GT)6USRphMr9MHHMSIurx|KXZ;aNaq zUd(6a7p5xKkRW}+UD2Hf=g^0(;+gN|Mom(lg>4R&KTPtTSkY=xu-Hc*)cU^@`RU8? zkTGo>3~n+a#7EZXOP>c4Z@s(oeVLvH@^bRYTBrKVQ5o$BI>5Zcq~<-GFD2R$%iAK} z6Ib@VwrBIpk)#pz%|Z(9XBYmn?g&WxJb2&7fnhL)MVN|yDv|Ohb@v&-2ADbEkm7c< z7VtXvHT0a3km!b+IS@!l2V2DNH)q7FS(G8gzbqD$AyxmU=48{r3fa7qe=es zRcfbZ(b25kzcUw`J!J53fjn{Ogv-rK3O{;E>(-%YdK*)BGIKhxTu>x1sf67A<+e`4 z>j!^EkE}7sVq|#TEx?_3p-|nt)SlfjI;H!R4NWSZjjbpDhSL;Bz{@0IM$09 zlQi_cxMzU~G6*+B&q6M*#^Ei+?X*2*o8cY8)XQB^tLQTj-3nik7Hd=0h0Lys{VW{B zm2R?bT8$j7zLmQf3`uN*fmW6lTD)On>LC)rtvYoXNRwYlGC&t# zDfNl%Z&0tTLbPv$XBLzO72o^hu8^!3501CQ5BqH1tA&&9lFQkc<{hhUdtil>Ej!=^ z$f|JJJzUWJ-0EuA0bmd-kxAot*M`Q-z;>T=2YJ%sn#RZIR|Q>k1?hCm0GYy0wwAXT z*A}Qsrv2K6U;9z0ce|zj+mSI7#G<_y>g4&|?j9@GB-dS%Vg#QA_IL>$ez%oJwOifh zNC23lszt{BhxX(B*vl<1m}VA2TD-rM*ha4n?5<5eJRzPpB{V!gIN}$Nugc(A5-Dr4 zIS17kf6gTbX80G^Cu~W3GXZ7gs)4q0))~GoiYEcPabvOsy4U1=Ff8Zl#l(|ouOkg_ z{d=<@HR|>Meir7k#VzaeT|E!NsTBx36N>1?p zyyyjE4YMAX>lfP0zqy3rp!G2M>=*Rw+3WG&lRI=+w}p%mq6oGPL_6vsE4gSm3OFp( ztfb;V^qlcfgxXyv1$j4XMdOBT6U+?)C?mCzkQ}Zu4`cX=wDb!4=U;>`sp1^v> zGBc5NFh^#>Zid~#>1M0^m3`18W?VC8asR-PS8A%=D&_tsIkUc98HMA_%#{BnIxg1X z2w|6C2Him^?jO|)F~Ptp05_^E-f2=TddqN(S|*I+5FWqeAUsBIS1*jxv&M(5w;&Db z98d7lo)Jxkz5A_oyCqYj0~{>~x6mU_y@`ejZY|y6YD#;&I%2q2mX7b+`${91@ITC% zM2vpk@6?3Mo8^gBP@KBt)!P62#f_P=g479OXrfE<>^IPGmirabgMRrfwpXAE~?pKoUB4PF7ga*L(#QrDAV*1o0&goW~*`E02g+K-lY2k$|qQy;vVl#?hZ` zIhY%-EVOmzmm@N>B>dnj{$696+<9m~v;9mNV89%-FC3vy{BnBQCmnzZ@XbA!B@D9VDACs(0sHSCDVzqYkBA7A&e1c1FcAGs`L;6@3=Fa zzkx9GyQN^IXbN=cU#Hd|RRJ0*`;j@WJG0StTo`mO!r`NaLVRC1zFJ>`#iVT<^dG;J zOBKvr$l=+3Mpk&kSk_UjL69@D7Sm|Bu?uGq7yHFphaSO0VAku84Z!Ls;uRGs3)4&A z#DNJOH_Mm=`K0TFuEIQzEG_e` zIo0@y+r72y(iE(wS?i@YC2u&hR|VcX1h2Yp-+-?WYkOWu;vIjEB5P#z{Bn#&4vHZr z(RJNwQaWLoJI4hVn$&ujp0~ZsNlWRnamXV^JB+&17QL9~{L|BH44^TPhG6N}o2xdJ~ zAmc2YDe6ACBzeW$e?U1bRN9n`5koKD&_bo7$sIOt#x8+OG$(J_76Op0K3ymfI*^n@ z629NP;{Ps^7m4r}`^ihq+cOC{bx&|=j?Faov`)t&y1d+P7`*xb)#6(kR94gJ7twCE@4i>p$?}^pRe9Jr2 z`bv6e1`-ot?*ia3d!2MlOnF;&A*_K*fSNw%AIDoJmP?WyM{-zlZvhaKD8~{KB3zr| z;uzAUe{Jp1Gm{D(#6ld+ad1dTJ;G9-sJQ*lWz#298tzWhRTkJP2@m1FIi$%4n?gPA z>_izPP&-8+d`cYAg|0d?E0QwFB#Ee=ZHS{GJQS!UrgA(fO6Xm+DYS9!1e(`XsKK%W z-vYrnw_WWC&entrk60^DD|r|=VXzQ)OMkKS$G#SO$ckH&YNi!b-vrpa+2-MXTM#`# z|LUMzdogmaQ%oca`cYPTmh5H0rj}#G@ab5ajL0;&<0nVP>^S>)S{rp05CGRMVX7t= z762^rTOnuT;&cn`19mLuaE}JIV98ryIlz$@ z@Cb#fhh^09GD9*S^-OvA6PCBduCOZ3Sw87dPNiNd4R^)dTxg4G!@PcLGtg!G9vYPiVy8_kGc!&&tJgeTS~-+Hld~iH3ykBq^BhFLFwJjsPFwiGJ1DMK>wa8 z-`mn^F8IFLo^3rs|{zw?p*8rqWQZBMwHJdSEi;AuchOuE%fIG=m(6)#8=)GUs@swrXfjB;)% z-^N^%fSU9v8H*Z!2iqCetqyQS+ zYOl>t_d)Q;My%=~E*7Bm*X;+$0bKiDCTak!L2TNdx*>L5cPb?HpT^ohh>SZowAMQB zyy;+p`I&uG%|U=rYH<-jXTtZ6I-H_eZ%=rTGUWI@gDlk|1-exhg!tOuhOs zK&TtqBU0bj0^YAKB|}qK-kV{@LkQfvQ`ho9gXc;_9Gj;%d?$EsPwE%0?kUN3i zt6ebW+1Fsty5j_U-n3OOBogbpEy9eCxBp{+|aqon;>SSW^ zZ{60sz`J`HFbr3SIu`a<7UeC#edbfHhckK->y81qL^u#h2|kY{gH`yI1Vm-iU;$tfrUSHlgvn0x?xJFInOaoVn&Y^quIYq zbrEsFx?`_=CcjUFsBd?fhq8mxRI!TbFkaN}K=$9zB%;aQ1KO-d5wc)3=tRvO(*aYf z5^pA)gA(B*+uVwLnchg)l-<1F6xI$50ssH==R~KV2#faG>~qcxFXRd%(A^eR{4Pbu zeJ=!f_g)+B!R0E6Z6k|{JC>S7g3rev=ul@OV(ufJr*`Z|uHb`PurKL&E&G69Y8IaJI50_2)HnjXZkV zP$#E_myZZgWe^3G#Wt~$OfgP8hTg&#A1uRe!zH>_th|Nx39dV7c98nM~ZIdv?%ONODFOJL+*h zQzS7C+GY4hmo~1AFmwHK?Fy*c@O!|d#YW4=MAL|t*nX~Ac18SW&Pt+ZAn>DhmV{1(T}XtL77 zG%1@Vy|ax5S@LD7t>(w34a*rr(-7qvX1YjrW~#f$mGe6x=xTRhMb8X3X82RoBsMyT|)7xwVePT9ie`y{>T> z_YSE&p??1ML3R@&Xp;@tgD-V%+XT`zTl+uf9OHQ{d8SanbJQvBsnS#bVB2%49;*1_U!c>0UbuG&N;iDg!7arLyl~x|%XZq{PAu9u>puXWrui*cUcb?_~-hukwh9 zW7#Cq_2%ibZw7?ed&ftcnCTzb)fvRmpSUa0!_@yU%55bW_Wb14;V^T3B)8#2H+4(2 z#_SC@7=n%X68CV~Zo1khD*m|#G=x8}<`FZ|M+GAZ9T|R~NUck}>edWiF0my|;;TjD zU@(J^!9Soq<*}?-*v4=9m6+IjOyTk7zQ`l_$FK|zl-RjqBnAn{xt0{EizmaBZGGXr zqhQy_nX2{>J5)Vp?ZLH<6DZ`FQ-SYH^nQ<&Kw?MboHb?nIn7BOu7b_>rjhBcbF0W!k2J>o~m50PQs_* z+9SSK=AG0A-fa9qAEJq{*VPPX`6bxzGG-=r9?oYzt8NaL*xq$LMvu@L_(&b#5)ke| z8@xplj!Fmhel-dUz_2*G5czZics(rm)+lytsM@5pQ+n(hd!)t%nAUo?$@pIJ7zlaG zzR@D9mI!H@ly2|nMhQZ96uZj;3xSkTH0`VXzFgc{DwA?r&?YgvOTN0hs87a2<^(MP zJnyHYb2axM=UFzt0wm)IrW!Bqm)CfrN$u*O8?E3ZlX*ENt%CGmAfRUoh^dWDpWa4G zX85_JYlsm@HqlSr)F9TJkS362Ek*r=IAjzgT+voj49bPykHmuJ#dnX_l3C062_I>4 z0hykVZD=xOof!+8+CZqR_>bgi-jzcooHD3K2Ha7j<6(el0V0!HM zRO#Im`}^-KrV3F*hPbM(&??&bpD-}70`${pQ*VZv{*9lUv${!sS*QU3~{+17xaf2Y?7MeNH?hrZ4!HF9`x7=-AoBauBjNie) z1ebg8{)(rkOZ|9gWwor3MATMpvINm*s^ywQOizNosmdxI6txk?RiJaE36uMBnYsRQ zI&W?@x}=Hbq=ZbXfs}%6nuM;1i0z*9njoT3)=sz)HU`*iQU=pj!3NtpA!naQ`1zto zK0C&p5B;Mo2n0LSi+s7GP&*;Mmf}U63EF|!;_|n$WX-Y$l{-W3eQ_@KuUn42$$US5P&ojS1X<3&h3bj@E3f{YnC)g)7nZdVa?IXW z-sF4Wo#eu(9|GAsK^=#&4?V79DGsCy`0^ZYMOjih=*Q9Mz zJ|EVjF+)>5itmtM67AAg1W5Q_EnE>=I83X=zov&~cGXLMsInC4e?F$l2n|%9PAQc< zAM}eM)*UgxT({lU!2oF4?W+U-8 z7z%eDlg3p1!>2JNtKSe-+tq*MaGyzDB#hfsx&zrM`t;le{t`hHMTZX74s=|g2ttNf z@z;VmdiKi51wQyL$1>2Fw}(xzXU6DT^I`Yo?svtg$t3q|Jxh zm$&XX2_bGB#E*gP^XGkW_2!xN6r(U-2BNon0clC&L{@}hipmL4SisI0D-dJaD$XYp zPD5VZM8gJHy41NyEQ5UMqhqiWy?p>fK)k=walDN3OHN+!fGsPd)MAr^aBUVKtcCCv znsRr`<2BvX`|m9to5sMYk^4-4j>E;bvm0tH3$41!B(`%WqCPzNY;5DJW9TS^9#K+v zBqI3CW$3bNo~hEh!@W8Xt;jw{k5J!Cf)L`~mle+{umHFErvBTnv9HCGjFqcX*x8=) z^(WpV?@;#Ai#6DLP{X>QVR(H}v@^TPd%;agb`45z$Okz%;`?2%_+iNx9Pktp45dx3 zopH`a;MhCJDYk3VV`CdqE*;T7-u^)ST@@_$KGK}T?tf&#y{U1z1Izi_KpuPxUi{>{ z-1E~kE^ud4{gccJS_IN={?nSE_vmsIHYy`m9g{NCLF}KON~HI6@4eP)Qv1qi$o>Hm z);E7yDIe~UMA#5;|jNyYwLzI#%2l8oJ|iquVY^*s361Avg*k{GS|N2T8` zoq5!xxTRZ+g5ZwXft%i}=D|=4^XJFcC;?cnH5uSl@`_T@MRT$jC+dBxc?+-)#EUG; z*zc(exf&BZ2+oFC7DHeWQFqg6C^iW-bb{!NeG7nHq~W zo}cmh&WV*^jdLB~^zW2o(&x|%E&mP3zRX}lhOY>^($dzn_g^HZZBa$gfKOe}??t(# zgd4Yt_Pn^%+1XGNC#zS;u4^51--#mqr7mu^hNH6pLE0Q6BVN% zHSrECeXZp4aYv1TL_PQxX!Z`LOq#xYc+^y1{8&kzJXu0O6_wZ?3*u_eFEjvXu|+sb zE~Y&nRq@o4dO9A@%GpUL2uS6XVE|j=Rmb7HLeMaAjlNbf%-eXpG3N`UxO$|CiAoD z{d*TfX&)o_`|!ddHVrmyL5MSd-}5gV1T@Sd6OVClnjcJ^=**u6Cac55(#lbv!Y;m3 z^3)il7KaFh_oJy`VWuv#lH*_f?VsvF>mCJa1c z-j}L4;n|s3JiKxxG8cx_H@ywI;-bNL^vV21eXjjC;HfIsh`(9-roRGeN(N-REm=9J z7?3Ok*JzV+Q%_wAgQ4dKb1=o>+kECNjTdS~NYoMX`$3X~G_fwi+ZhiWnD1Sf#ZR;1 zJk+_rgNozgwL$_DYt5LOR4#m6^J2uXuz+nguicWH4i1oC(+#-MC*{Y_N~E!_q|&fF ze2E&RU?)swZGA6RaOae`Ddt>tF4!eRR#$21Xr^-59oVv1YXLFTIp7$EyYzhRxRl$cpKu+vPk zf~2<<41rnib+B^GQ=mJ=Z(gQYJLA0E1%u<#;FD9hvNfYHY@68I%M&$fv|SQr6T+g} zaS!P?&?b2H3VxT=8S|-2Us*@URL9<-juNw5Osp`9Be48V${rL|U+^|2bwHPl$l)Bz z<+S<=L>2!J=Nbf2kpNVxYt(%&{;S96Ajpwg;3zei^B44XYv)g5Vnc}HV1TtLsaTb` z4<;&N1V*JQO2VUX4E;%n-WPkD;*=krwz6bEA3-AX!G z+(WBm=%^1Jps~p-;VhJMH`G}&zcWP|fvbQPK<)ca6GV5oga=$p@K`-Op|BO!Nze|F z%2O9VVLwmVS1?V^{<>tAl@2HnYV6DR0dmfJkbTDxye-KzOzp^Hrf#OXN)RK_Im(B@ zkuwv`cz|GgX;nQ%GJ)c2N)4?Rzi5#CKkX|6!8g)%r18MvAf&WYp zmq1rY5njnA>{|`WlzTVzOMR1(`}BaW>6uF{cmsO`8FUvH*hq>B4E7{LG?k zZ&d*{@?-YtQh?HAj=zEs6>!DSgvbKtKj#E5PC&C*c2T=}GFOCk$&C#sr2WQJWjZYts#7()~D zMFX1t?c1maV>5^<@ok0N9ao8* z<02{v-o(TBlC)0ZOmKGjxN(-uxPrzhx^Q6I|eK_lPn48G#lM-1HeE zC67j%Xd<~%C>NTRA=E?7W4=qu)NtjL#v6e?+s?dHeweYKxPHU8_WqFChN+UUztFDj z)e$W7|C7Vvb4c7t&p@1AX^BzfEG8Bx_c7(~@;Mxb6X}$}a@;`6)+^{yUJh+~*7X^o zB4z=iAu|Fh*1&7bGz9QG&q$fOea-D(dSBs&5W3eLXz zP)hP6j$7{i=9O*p*amV{Fbgz{;@3GSKe=?&)5= z8UPk~e*%+!%_~_XA~9SWuDF^p2^n>8GlLIiHg2DfxS`A6Pi%mZ6xzUhco-5=R{S~) z%%X?|O9Cal0AAq`F4D_q$nPZ5r~bIgSZRR%{&F9#cc;ZhWnUx;UU3*_t;l&TRj(Xx z)o1{GU|zE$Fvy9lGJo`%uj$RnS(C?o&Xl+O{p!(Uk|q+TNQ*>e?29&8DMe2H(JQ(j z1F2c0(LFb%OB8t830kn;?^Y9b6w;NJpbEKx+tgxPU@hpyXZgwH;Gld36&h8f+f+bD z)k#Ou$`cYxIZD(L;9AKb4B*m+N0vquR?|~w#0d>lW^Nli*%asj7TO1=E%$cs3Ck8_ zR5&bqZ(KPYpHF+2p{;ww4CIS~Mz_;Ng4#hC5L9g-R{mjzQc=r!-B|y?65qg*^?Egk zKN}gkz6Je60DczNW(aQX)b){-6OHI@MI?#FRCosYY%4s;lxXb*uJzETZLSIJwaCCnUhY;F-Y~ZZCa`6@+!4Yl z7o=MRiDc~~yg9S(|5ggKJv!CG@do4UTMh$yKfL*BDdZdgISGbXSgFctvCK&K#3dJI zeArH(gp-+8XhCT?%m(m*ED{V^fvT92t2hL_Et^fM`QHNXONhoNlbE=~Bl1NLF0cQ9 z;|^TFQ^M4!yeFkEBvFCB?ZYA^nbvpLVHv_Pz*!nq8H`Gm6}aqZ{`kuY(K)#l5T5k; z$Q-Q=iM(m8r(?7{s0pRKXB;i8o^i?wz#;w`eN=6ny21fDS@lUSd8VJt6z?7e8o&g$ z((2)E36H+Nj+$@~-Q{op-@ev>FKIN)2BS<00;ff)G2h3_}Cd!C< zLx{H+A!B65_j_ox$#q70QiKU}crSmV?jt*9Qn`c69vLg^FYZ|HDp+tMv4b3gHKfNmH=|Q3v4sOM8^cu(9#f7-vh-{!T|3{Bj zYswS1vZNy9lbbibcS39jzj^)AyUIzJa_?OxT|H*V>GN6Ct6Kf#B_3OX`^B!Kytf&Q zIYK3T_`FM&w&E#x8O!$ll1ztdB;5GzJ>+)c>Y*e!=H{0QiV8T+5CaXU^;G9eI>{${ z*yI>`tMVL$sO?$48xNT}c_~fY%H!O6D5Cx7-|$E3ayzIV?GI6#*FXh#qgE6ToxfD7 zivzF1zXOig5qqqI0H)vrAGP7h$!&wxK5vr>huErp^X;;AzI*T?J)UCoPk-3DJQds2Xc# znryJ0nu4JmHHzoH(aiqEY)#dJeMo8t=W#$B&^4vHo$yiLf6Y!BXF$JZt?r*_x zO-BsTi5c|%cr`-W3~HSTB|f$F%v@6knD^B?O!*rBCOx9QD26PDIx(Qz|UPILnfJQapb!)2yAlbOjl{x2!Uv#rp zvwI79**IYu6(wnJu7fbXbDFbynRGX9A1?7|-Dmq{vAvdTL<>@Y?sU;FY!kfQvvpVx z+7hje*wi6#EABoDv3h6lD@OB6uY?SREL>jte3Juu z8=TrBqY;^A1RbkuX*ikQDSt>w*Z8uI7tXz57@Xz#hE9akmjI96KjBV}VmehtHJ0XX zFEbqnTU1g1xIg})p^N?i?zOdO=okY*qs%t)apD*SaBBYa*q%yn_3XR)h$Br*h|X}{ z$Vv%JtO2=y)jVbHiSmrQz}I$<1eg&e^*JkpO*-7*lj8eal!s5x{KXtS&sT#iCG?4& z|6}aY_DpPS{$y2DR!;eX{i&A5NUEVVDR?A>>)%Zvg6MrsdRb}=I0vaK)p-h70n`*{Bs+g#!FVh|8vTzFp(1*Zb>7_*_^!P$jqai;IBHI-Z8b6ou*ve>K zrXFKBM#S|^`VT8sYVh`g1<&wyjK10ypB7tq4gvifi(tcWSoY+{7=E-*82HY~+NRz` z69XkiSm8DFK)F7pwkF)JOg7Fhu?o^~Nadj9W(mXk+G=$pR;u-)9X4ul8h^3HkLu)C z3tRoQ^e)Z~tWv!U|1|~P@1oB*jU0u7z6&?NWi}N}+%01dDv78b`>Z>ii(133saTR} zWh}h@4UNA+o%yR32uJ?VUF~?<@^N-f$x*)7qF;D0s&{;bEzj%@_j#Ya;j>~p&t%S?` znC9j3XCx=3cA?f(auR*Pr6GKHKTjNLu8f`q1vAeDg2|gsep%OIEh^L@Z?u_fU=paJ za9GlM79q_jyZbOAaND$CVtsjoe<>`iLuTuCh$fk48;iUKB5r~ay=w<5)L-pLasSs0 z=@|dpZ5^S#o?**!LF6C{Q*l5jnbuLK96)k99I$pW)OEF&*T35vXpYkR;2*GaJ9MMSX`Z%4bSkn8AD zm^=wgCh@%fIhPsKp^zS+n=T2q$wtGPf5JFAAC1Fy+k=B&_Ph_A`{N;k4?Qx+Jbvm+ zML{UA{n;l(*L5+$v+n|CIJzeUcw}l|+nG!p#ci+}oK&Xma`ij_Zg7t6n44MIX-w=p zNuWvuF>H+7_j^9uT01hkcC1pOjn@MxAiNd{|I05*6p(QJEbkx1AR=iBkviL0G{PPh zhs-M)WbZ}r*uft_cnz0#Zq>6Qi_f*^F2vm%491VFs0If%(28Daxk0%LtJ)XFm9IIH z(y487%gv~S1r#GqJSPJ*A^S61#)!ZtH-THIK>b3jvXN~vcP&BHK*HrTGq$2xq$AS9 zEFxNYk>r6=s#qPx6psi|8_dCMrydI|T2V4Pvt~Z6V37vIoCaYdy??1w>KWGeTRTsU z`HtLK`XZ%14D9V6k)L%XzDBtUZIzsv!PJwpM35`JDbS)OSeWoVOD9Ti6se{P6c-Ct z>0l6q?g<{|1gZsC6fA$%&NfH}M37syDgtFO0|wMQ+)t%uhfp;Y#VAa+bJu!JK9;?J zjwiaC!n{?v<&k`GZb>{-R^I@vzK0P4ORH1!W4}v{xGwOu}%Yvt$C0RFjtq@M_d*^7x`vr#!>tQ^hu}b5f5NpEK303l$z0)^Mxy6WRSO@-kU~}UiXe;s++F7`!!gUs3 z%J|Dp>nEMgMCNe`o|3rYu?wIczo7FMigkTA1dO$!cZC9BY0NSJe?EdGupU05pJxJz z`h^)5Em*QbQA%5rR`$|N_m5I8(E5kJD*yRC%K@jEW%f z{HDum0OYfAAQpxDTzd2qc9SZad-%Dz+irmg6Cg#FyJaJm$Q0`S(!ap6g_+Um7?>lG!+&)0V$?9o2fy7(J9G839qoMG z@75-Les6_Ja%q&2hhEPN#Rr|)jzE$lF;Okwnwg}U$EAfuGOzse9vA%lRU7xaez*?3 zS~DGUodm>TTx7#jg`a8S?;Of%x#arP5>~u}rs`o%gR+JABr~-WzkE}Lg<#3pzn z3~Wu|e;w%lbk#{(5Zw0)r``uIqW)auY@vLom9OL-T{Ke_{%1kitSxWILxPm&Fw&hF z*KXp$pwaM-ezmkC1#UH$5^JJe?aobvI5af5$nS(4+87?XH7$cqHVDz+UyK#@m_IHEEy(t9O0fV8U^V zHCn1y@)FO*?)QoP8E+{a_Mp!ReRxkU|K{5`)VTzaNmUhv9Tmcp-VLfKZrX z$3A|hjl&ROaW3ZtGe?L?&;tkcGeq#nBTdMIg~yP?Xntd6jQfy!-0my0_wAbL)K@_c zOx7)J2AOt~{sv+WQt%!8zSqL&ZT8Uc($tlvf5&2A=qTtQs`qnaMiyEZ`Papijys&^ zi>r=wjWT0G{g&?xEXjS2I`y-5z058T9ouOyx2F#}Aj&3cGfH}Imhio< ze0or!zxHghwI#mE+s;75OKU$o$>!!c_gqB4IaVaA-3a=xCXkJ_zkKo#!Z~LavJ^4n zLja}Jo*>t?ow;EAc(RF(dpzL5KWl|djaBK)joc`u+ak+VB=efqyPe>#g7RD&v9~U{Jy-m!bUW6q!_PJC`8W>i^BH8oO+kf)SH%0uN0%G`Lx_Vs#>chzf075ii$va2j;IUjWSM&B`KXt&dAnjeBhFfo z1wW-FRl7XF7vP?h1*qxx4^xNtdUZP3GgeL7Y>txvR#Imfr-fz2;RA^O3h%%Ez!4DZ z0bfi`arqh6b3jUoNR>kmTkjTYkp=bdL0C2?5q6Geu>M^H2qZ=P8SzThG4Ej`4CTiO zvhgVd>e9;>A-NYww=#iczQ6iIHoz1q=Rxr+X5s#6@=tXmE_(rhX`jm~w$J(+n3LZV z`g8`e%lXl6!tZiESZ4CjaZ`5%yN?z-P>Q2rCEIilQIiN{phURZ6mtDOA-d5|j_5Um zXul6&lWBLobTKBArtn=)p#gruEK+jr8Qz7paoB?Wo{f=JiPr?G^T`V!MsF3L&zd9T z!ICR}F-t3?a_R;TX4zVX@b2R%)T<`qCM`?HB)Q@W*M%1Fg5Jo9rxcE)GQ(jjF~WWr z<}g~^#`z&?_aOMeeXrqV(mD^I`ZI=41VF7dn!M0jj zJ*KR&A)vmK_uPdQ`EDxrbx~ubg6#A-d$A99)34Y1S6K+#U0E|6y%Q^TEhv=Yxfmce z=1%nwXYv#H#wq4>Q)4FjgAoBoTO8`YwPb;k-N8>^51ESxY#le=)Cb8{wdz;`CZkw$}PkquIm4+`2|~& zh7K{plkWk~K_A=~j$r;0Ox?$sX+-yFZcqz;3?!5_9Ly?lrX7Wv4)7Iw7TQR3zZTkL~Jmrs|Otu&!ctDRhF%N)0II(C9=ejb4ATK;ByM;0Y#gM9u}-^txko2Uf@c*pP4!zq@>QKaA^?fl=;GtfvRi>?)@md zYgJVE3kk^u`3+_*h(O0{Je4m(dB&9ZM~FvTi=sdj*Ns-Jbj=0R#Gsj`t6W&5yvMUX zA5}{)))JNFjwqFiO&K{NdNWv9g&rf94_IS=;^rJq?L-=sI!>NSCj3n@zReWgiPBd? zs}&kWd=}lpj{OLlXPKtl0M?E}k`7(OojRf}5M0?TYTu&RL~^ojptl2M7XBx;?x(W)3O8`77qBRx%#b~F=jcBkdCL4 z^MGR5-}1D(@>biN?ON07#6xOm9N}Hg{^PHC-V9O6F<(jgz-O%nt->mT1Z!KlWM$lg ztF=*pBHfWv53Pa}nA>+!D7v>RZ6&zqZ%NH0+uoqSbL zl~rymS-08Id4U}3K!CHd?|$P{<|w_9Pe7#GKGJ%(CWh$!!*iu7-vbc_B2L%nrvux> zZ69t>RRU6DDGqfdwiU#Pv@!-|%@)A-sMBk&n|ijOhfKGPS%w~&$F(GKUFQDqpsQL<#Xw)HEAzd&3}1w8jV0?zWGiWbUrAsq0@)q$|;&DDg=IRD7XNnNc34?r*gH^HD?a0*J9)@-g?IIw41^ zC>A;p-fMLKd2tjD=1?LQhkDq8o?Tbo>d@)sh-pC|SVjPR#bqD$ko|b)AWWy%+{xY( zF3JL7veuUR|ATI-_javAv@%?IJsP=I|C9gsF?g-D8BjHE2|rq-^Uf1(eDtZeRuU6# zKIC2m8whz@s}kj{a$Iyx@9(UNlD67@GBjpbmyQBu(|7GCY=}dIT+gdatjpNig+^ zKjR$rVa33960;V|*Qx{y1W|o-`}8v_>03NI+8?P>=u`4Ue?VcqzQUPc+ElIzn7`CR zT-7?k5Yd?STWvJGP9Zpz!zHX4iY16E1q&#+VviEw>;6K$drf%{D5)c$MS{u>I-jg@ zYc8=xFLBX%Gj9nt7-zXVJ%eIwldsT$SDhPSw~A74#4-QquD`2eyu^G|cY3*NpO=3D z{u(+MK^*G4_^`Q2(xmeBExs(deaPWw7Yi_Hp8(vtxIr5ts{7%@=bO~2f`fN3S#us! zP^Y7ac+aunQ%M+s9l?ap%y{y=z3VR0m@*W}=qG6>UcOK|9TgDLGZ^3z2?I5mvKBZU zvG&$rOF(ABQCc6q;y}?&`K*S^A3RbdjNO$;)RNJKf1!3qOi#mF0}$&oLOXkQ1Q+is||clw(d zdglne_hTi-euMnu>kXXS2jyRQB7U}rdZz|4A+&BiNMEK~Mg)Nr(TN15yjJyE(cgs! zzfo6L{i`oj7=j?2Olmj4N#I6Koh-{Rt+$fh)fY2I1HfpOr%$O5l^O)Us|Wf|Rr6qz z+VOq&I#e;d+&z*=rSIO5m-<6paxf%p4ZE<_BlQB(0%kx)*|j0+;|ZF2*3VAx&o*6W z6;vR&_A*#xR6DWn1BQ95M4wxUGQTvfV|8D6hWw`*D+5HD0jaIwjjqA6C$;{tpW5Q> z`^wQ8&tELVTE?g-^UIyD#@~q5^Uf)#O#x)LOuT^r;GJ_^)RB+pYxI_-v$2BFhrWq& zMBfygLmY#za2wRRK)!gI+pLvSsWB1G*ET@~O1wPv$BY$<{N`p;BO^+O) zv}&`4xoG(ygu0ObOIs0CzR0+A)6#SviM z>)jc@cMF_;HSk^mA&##f;;3CpkE2mtQ{0x7$O8|tdO$SR
yc{=pP%$n0;Ddd(e zho+^i4SaK{lwxMF*UY+NPTBx^F@r#S|m zk5`rd48StF{q0|J;_ePN#_!Q4t_8cvQc3Fj03?)`y|H>A1+P@KuP0zno}yuLogmFq z%N_wA>vQpRj*nSZVY7b`_gg1!;|up$v%E}dB#IAj}0hIEonT))|i@FDdiQ7*V`Nv!wz zw2)212?<-OHd2qr*aN=#84m1e2dPn@myRS6$ZzHJr0yzphe4jyN3S2+$$1KWT)em;Z^jiK~ zIIVUD)F>*N_1W34>j;rl${dR%c9$z>gz-Z40xD(#x1p=sey49bTVy$?@mbQ6>{jGD zL{WW;CFz9WO1H%@xbEG_X+$)lDOt#{qMf9d+L{XD2`VN;pQjA?e%DYitli(G|ajHvOYQbay#2ntU0Twl%2t?b@^|m!2w(C^1rQf3cXP7oM&Ks)NZbjw-u{vT&((oXlF_JBJ1UENF~Kz>9@h`vJLPqHCsVp2*2t+dZ!mlvi%$+O|T{^&pRyUi)O^@fGr(Wk;XLeb2M_oz=5e!dJHd;^Ysn81^ z(cdIiAZQGf?7N}syGH~?EpUl^d8*PS8@xBd&fXls2q!;lt?Z59v5{wGJ44caH?^MN zmL4E5Dw$$g`|Mc@4XlD>oUjWm-K~u{x+;dVGN>*X*^$ee?Ay+m z#wKmT{EKq|B{6G9chuCy0#`?bR8gM|bf8RUm9b=sCRPS?IIQ}<_{Av_&75dX>Gx}f z>5Q_9g^CHRI~ybvucumV#uE@!fh~~5I1B3L$5OWZ&|CzE$G!$q=`V0DdZh>O2Ph{O zxf=#(aaCb#s2O4}?DS!;ZKT9=5eHds8Cj+3phQ*k+0xu9o8H?N&X{My(lL>HaYw`_ zuVIbDJNEP7NOT2h?<^8k#efT(%KC1|LzwkRMi7iYVOw$T66Kl0+g7)scI+EgZ%W~7 zd*~@KiQbAKF^%MKiIRO@A$t|wjn>F5ZOlsDXV&WVef$1Cg}&RWf2l~k@!C&$g{H}H za@NL6d)5$)(^vExtMPLlY7|)zoFY#e6c#T5nwOE@-+X4}hbKVq-eKW}gaQM#*?tpq znC$9qmYF2$kNJ<~i5Wa##AX1G)=YQa@x7oVN6^}K^f?iunPyj)xUQ~Y8g`H&_lF_7 zRCaENT~_butpEBBQ1BKpLReSmpUyBAb@G^0f1({n@xMat$4&u@aU~fv`JGsGgcUu4 z0iYzY+0vmFhlYMJEwGpNwZK3;tbA>h!EF>xSGUu!`*CXj1@h=@^)KL*q$}q1i-?mq zvW0J(R5QN`%s}~;UaR0i-9uN#XSVS?BC7Cw2$z$fYHK^z*T|xMU6?jF*u|VnIA8Rh zgT^uK*>?RaRGBxVX6uYJ^V~_YNc)3;$j|1);?5Sn7B@Ef}{T z(P8k^2}mh0<>7%#TI&(XVs9NHoQw%odv6(QMtcriENzd)hv<>r7$@nY&Ybnu>@VSh zv{=083G9a7riDHv@^V4f@C9L}th?(A$e($*qulOnHCP_$`HZt~OX=uJ-6nI0Go{6{J@IJ@ z%{>D?!@JHE1$lkSB-L`DAXhk-wH#0rGfb;P2-M+*6DoS?gy#6c2EKwqd-y0%5YJ_{ z_EPu*R_DLVW}nv`jl)wdIf0JME65|qL#I158NF%gt9n*q`u+uwO?Oz&hh=^dDjpEF zeYQ7W?ro^&zN)zLCw?}dW=!_LTKt}*Af0ae_&g2Akvc%|1nQbz6~43H{Mq_4@hp}4 zX5g_ZF`}T|k`CIO5Tdb|VZKLK?nsfkxfhn5ux414Z5YMjYWCJ zEjwNUffy>-ulnT*L()7iM{+IDk&i($uSl#>K#1>AL0*t#9DIai4RmV;(s$P zp%OOwrql0IXSdZ_s8`#qpYUZzCb0HP@60ttBeE-Ddl4Ee*}3iZD(sRj!>k8_hgcpN&pHU|!nmwNRJMcr^7aJo*Nx2{;fN%|NZ}p)mK) zJwr;D$>0AW*_>uGKhPnhW&g&93rHOqoBv@^L6njcO(cEDD2EA%XTW$4DK$z0K z1SH1TN-i`<5!9)I^^IpT9QW^n(FASYhuAF(A@QG;T|@gI26?`-**Ih9OwynD>anO7 z@AsOgpOUM+Uhn8;A)(9%y!fYevyfnHs_?@9BZN85QKYG>k_H%> z-RTKN6EwvBr7EedH14KJvK&6dwboyYZ};gzM^E3Bly2cVO_MOH4d)=|@!pj7$5S1` z(x`3Kmf^wD5Za{JUm|EiIiGs}F3^ON7LdBnL0o8Ku zj=>nqTnb>tPd3BXO-G->eLFwOaSDdKR!|3KK?PJ)<(g7>n)auW(T#!RbpJ2;jo-?iilLrdHh8 z!kaG{4YVWKDL{tZK5BhAR8&n+;_R+!&z^ zWi5BD#wcfAuExSq;4j#f^cwJDL2uMD^6j}^){7|)ugP!x|6(ZlU{`dP#Lyb)_gHOL z&9U#mmg>2E#lTPQuHOvqi{4?HM>fc#YYq+TgQX)n?9(Pf9nE`HDLZhacgwCk`f-ud zL{mi?6h&UYoD!&=-i?-BmJDSyFV2N=!b9B3B~3y{AFTX|yI_WdjaFf^Q2`8&H!1-8 zVHt*wXv+!5v?@qrw^CbJr%jP~xc(ppwSfaF2J}VK6-(+C2=B=^HfKuL*C##C1)=&n zB74hH@VVotP5#b$x~6^+hyX=P)~2^xTP#yahgn2ZBvBTV{C)=fR=Ob^LCp+`wjf;d zOk%}KQjW9%Fr6%r=kw{Bh+2?o?5PdHS=?JCDw0@&45{8kw{QHu%)=rlVp>WMHML|u zkM0#A(wlI( zNFxySC@tS&~U&Wkw!)X&Yha`HBc zeg_Ul4s36Ho#LTf`j@?(HrNdZcZT7-J{@!{g0!H!h}jqAy!l)wLVu_FJUYFIrpKZhTfW~0BJ{U&=DmXOyX=wBw#&`aY7`U$J z?(&r6XZpjGq6F3ai|9RWn%wXEYiwyrBG4nW!=E1lk+pYX+-Y-wD;0*l#E0|26+a zkO9-a_6`_|dC=Q`vY`K1@F`p9@F@&3&B^zk`Aj2+7faeugU$f}G0}=Sx)wSx-6V`a zGRF!m2YFH?@_T2R?xbtwq{XA!0J?xvep%iqw{!GYw4L%8iqr91r4W=ADgbcn==dix zLLq*uceeIR#@ij*8kw}DZ>1!S(x(ujX5+g@8}Gqr=eRt1f%-jufAR1u88=1uM!}FkyVY(!>&ivo+>8ppMh5Fm5{IXyg$&t zn_W}1dFYKTXTaE!@?dJsB3AZG@1n~4@S=kQ|L`vJ&AWup=KX-CQyEm1Eiusv3|ygF zddR1tQ98b|e!=W&$!fq=s~MB!J|ZWSnXpAGhV~I_Mh755avq0z^*D)5*)h-xaBy5z zPbpgSsObwdM;i}Cx|Fa3uxN`jM>3CoXhK|TR_Ea)ODn#V|yc{u=7;a2b| z3nM%I*jb~LPOR|}_^-Wma>s-p?lZbJ)eqSz0)si@VLMub_k?h(*5%=j@&r~mlgJH! zkDEEFRvAEuhHCo8Vhj28n#+U;XtJkHSIdrZ!9qC`a9_20YeqP^G0t+m2^v&^BW93ue%p@A=E z5>xhXyUY7@=iSt76d6ZY7rd-Tkd<+r_&*^olD4vanUBj@8%jNZAhjvZ>A0t(P0x=2 ztkvx5VxQnY2657)Q6$3qN}jUgg=IArkY(et2A)$OEM0Nahn7jWpk95c23zAP!eEtRckeY&7-eZ3sBhu=sFsm)!afWhc(f67~1RtH8J{wzHzf&kG?(G8={s}8fAudT9h2%d#68yG-Hp3gb+ z(Kx${2@|8mN)cQQo8|*8g%9B zbLS11DUbA%Fr$3x4)QpxcQkLauN;z`_ZY!Fx0OY`ZH>DuSS* zq#-pcN6VFHWDEzH$1U8%*lk^XeGP~u*7STd?HMsp2Fl&yx@orL_`v~2xi@wrE~fhv zN-pV}IiIKMoBm|XtOI~#`lO5qJqI+PGJIfW8w?m5!;N(&fD;b?PUx7SKk1fSkS-7>WI4nnB+x*G(qod_7d=}awAG}Ghe;kP9fEkx*{Z< znvR3Ajj+*%Rv^gJhW_D;3mYV=VdM?NIuS?*4O_sO!MW}sl_qkw!@&!z;G_Z%TGRQc z4I{OuN(MUg4m>dU!EZQ)RRkM70tZmNz~@4I9oza)o7DLcg9|SPv$sevs(P!fLuOtQBT(x{ zkQHf3n>?42!VxYqAd&`lxO@+BJ*7Vy_uk4jk%;_#nHGTbER0>DP0oa2_fA`(mdbLJ z-cVvXp5~pDZS#>-SsQH!7qAbf)Y((?LeHAT-k}of;YKey%}(iZlJiwKeg+9J<$RGTkcp2<}Q) zmYfg#-AkTkq0c9n0QU$5#a9XaeLUu4jU7_gx5N2)EIpRQW`s)GBsK#r9RzkxE@s|8 zipNSx8)i#Fa|xNv|169@H#?)FP*#)>Qch7rwRzcAH|Sn5gR?k?Jme&jcgyc7PE}H; zZk{AjoanBNgvU8^XpCM5Z#8h4?aZV<#DL1Z`rA)q)a6_~nash@`;MQ&rV)ZVE0H@j*o>T_5Ng!zJbbPOyR6V+2mEr8QX5& z_MFzNUHR{8b7@a{j^Y}-w3=gKKLOv1eHE?StaDCWny~!{YyksELL2D%(ZC%>ZW2GFJG~|W z>Aw)d`RBcc-KGW-WL#V9aRl5)$b`0eTX!QP7NuDUvhbeX)kFJ4xj2CCsqbBaBNbw^ z4O;K>NoF>UZWh&`rI?=2J%eZZTj-ueIt!}j2x;4SV=6MJ+WWomi1RXZ>luyx0Ln?B zyleH9LVJ*7lS!gV+!}Cnr>FhyxFhsU^Q)|zDCDvfIF+1yvNz}sP;L94Bo9&lPy|V#W?j|ASlD&s*(?&UieHEK`dGF* z)G%I|$-dKl6T%NXHf12~P^tC)8IH9zeEIV{4P>iuooyp(MMcPW)TvSoCBnR$H2hU1 zpRxAH-ngd7UQo=&Ehv)A*3bE9%!HPkHnjVWe1u)@vEPpsMl7jCv}09e0E?O&=~*PR z)qS{}TJq%wZ47Y6rT2#={31&b8OZ2SF-@6`*8@pFwmb%w82MQ#eR@_ucc~i-feXp9 z(?5Gh;^C_eXHUeIM2o0mM>o6~v0-K!N>=o`u7#58j&rp;-^3a{9?>jhB_Eyfnurfg z^RGhkG|@NgDdEo;73(#JgRUz}^zMPe$65tzh%8PP7tyTa3o7iyP2&^v5^Q=;_ z--0woxj+f_nE{r49!PNuQy&=0{ic)+bxl}Yh8t_J!NvhR*e{N`5e|)8T(CH8_T%d4 zB5hkn@MxnebH;ga0aEEy7)RCA+gGuJRx`fqF>Y=uq1M+|+IVxoXW&6sNrTIlg>>WcX0cS9P}q-atYFq&w7- z4e=}4Ym`f&hnIuOIj+$UP`XQbqk>cPIr>xWY1`n~V3W}I)^aXis`Z#w0JW?kmbl5n zfHW!bYaj)?V8sBDmUccVlz+P>rN5XDf z1UWNkdXXQN)K(I~T)`j^Yz}IsrquD?75(t;;%Q-Zu%G{8o$#IHi)g=h{E3ImiqgX& z;3rP4o|4eDIF4&#>mZNPkLA7)LbncYha=xvk4^S}7!`F(6C2iQ$&_J}4l`ZOm{2`s zgl#*-f?ovjbx$$8&X2Od(Sng^$4@1o5?!qR$nKU};GQe^agKp6x2+z~>Yg3D$t6G= zl_@IPnLVS-Q$D&UgVj(<>xCnlONu=gh6e{4YBNbQz+uP_DNa6Inq*||219moLgdwI z{XZ<_HIm_i03mq82mM`I5)be*t8iOqlpO_F2i=}qls)|_Q`txh`*(dziTvSP0cu%4 z=TN%aoK?C7lTNIb6c;n-tsGNg`@1MiFJ@v5k#CK*Nrw?cgeS>Zhsu)^G*W^inzQVy zC)rz850-Y(L7$E}_LYd5{%X=K@0i#bSp@ko!NA4SgjW^1_%sZcxVh_rV}nd z&Gjl<@Oqe9+Zoo-!$xvvQg(Et;ItWVYmcnJ6OWlVo!7GqA<Lenxg*0KqEV)qSrePC__d%X#&A5!`5XDTo zY+d3n)?8RaqsI0L{oZ`CceU`&-fLe7ZD*%4?3rN}pmcMJcnw2q?Xi**u53lwP7yI8 zaE`=J3J<0Un^=1~P@l)8U>0$Xu6YGiQZtlr2-uF_{`-+cO zz{;f!(Yl>2KR>KC>`~t99C=3X9aQ)v=SB4^ezVLDiztPCdFvoij%`|5##!sgiA>{r zlEB581>Pk%$dbEjLsFMH)h8)rY36t{7!4WWM7j6FU(RW?Pnl_lW)uwjo@O?={xh;O zrd6YOEWP@;g1)Vh8Ar&#zE zR!}taAE|g_S^bH*f{~SIepipn2AU?3qiL}gp;|KSVY;`O11i>AfM{t8LdM~N#|9^A zU{%irWPzY!KYt!)fH(Oscd`bNf|Spq#$Jpqmo$_yzHU#P?}WEg2FW-C(RQ3-&&t#f zp#cl;h`&=@gB)OF2YQe<5^6ZXCyaTzi^ZT&AfJFO6CyK&e$sjCbiEMqNw4@sVoiCJ zot0iiFmi8ls4sKStQmGv3Yqs zV$cYIQ_L~Voy$`v_!KU(AO;J6-HGOBFtAHaAMf&n2B8;sZp(@NoSOW8ELK^KT?FJH z9Q%0iU_xqAjY)~wNakW_&7IvYY=>7Ti>U0jAh+|JXqjl$OK1dpN)^Rq%gOl=| ztNqPYf}EJ3+5MS6V>%ZUM-cgY*O5!zwpWr|M_W#UVW=$lp>9)>2`5#|d*1LZdBSQs zEaN+;6jeVe`=)a1#WjF; zX0XSovSZWCEll7HvYgQ8+I=N-T~rPG%3is{GwObCQO5f|XFK>r0aOmWFq2m97}lk| z4*~qs`cY1m9Hoc+OJKdY*o0T7Qpa)EDtE9)$9Fc6p2?bA zcocw#iW`!b?d;?5ux?m;nO#R{7X;mkLwz-Q4t@1W6TIzAAGM#A*GEmDCA!gf_I46Q z)4Uhn^n*d1Kx?~Ta4~}@OC7AqRhkii#kJBIOd>k>G(#U&?b^7hGye@NrAG{r*fy=c zUu%$K4SHuN5c2|s%q8%j#E(^Ze?MGU7vr+{c7P~b{W-O7zA7v}IQ;Be4k?w)+h1H> zjn;FFB3d)C+wq>8USzVdPXQGfjez*FoC9NCCQvX(pl5OJH-AO{U0YGM;Z z9ey+eY~|YqkNqZ($%9EM_$)`v>L< zj9ANcHTu64Q_{QtA5+{3XPsl~F@S`)gxPJyvL<{pY};-jdJs2{~nqq|$-=&|!9& z%!o;`Re*v}DO=MsJTFIkJ)u{mpa3S*Tf78W**V?!gj-mb(>E?;t6xv6ZW2lzpVQ7P zZRG2V2`Ap$oC!GPD~8D*CiYtXP#CW=4I967k6e_>V@u4l<)8)DY?!NGxV$cN(pQt& z9Y{^p)OZ?*HAHeY+zCGCrBP+}b7en~8an=}%S%!<ekq({ zFDDi8ar&a1bqyBB3)vw`kP<4}a-kTY4P0@dm*-C;7b204-XcWe6*7FS$5@GG)e4D@ z^IU%D;dYMX7|msOy-R=xHLGhlm2R@8suTe!^W?5HY3NWgUmRTZhLTc2TWQD3T8FM2+I)$x!BgksBPZ8Js5rq#5bbnOd`o&~L1vq_XvW~Blq;3ud+Q$Kmw zU?qq5tI=T6jI1?|1B(hk$GX4QHTV^M3}qLTSep{0+YddwOkI&NX&!D@B8BJKL4IeQ z)t|uWvUR2c<;GmBY~CyM83XAehG5F)tKBPVh{9iEUt^o+FEVzAPkt>c$69qhrN_97 znVMFZoD-|uJrRXwLabscrx*)nWCJ|kZ*>)^mg%QEpCZ7`x1Y$aUF9kg`x)HVz#?ZF zf7udW3nLu>G5yxTix`AKo{l{N>LgGHFUco{D#f^|wZFEil{YCA2%SWJ;&I6KHRBP9U}kQ#d(e2wmDJ{En^RbucmK0qGgbNfgI3Qjkbr z*yTRV->;qGNK}S?+M(%*CCsxp11?Yu$m0-FwdYt?JsMxd^B%t+CA5-csFDC4~)JUAU znlOq!sBv!bME7L;a_-92t#HaT%86H-o39r%SBlSPI# zk_|~siihs)KggR`$8aOLHQmmS*k{H_Bimko^G%ld9WMEyl}f~QV5@x|pe_gdE;8ed zo7&*@+=7I8ELgHmCyYv@#+*QM3ZRW0ap8kgBf|aU-=A3WdtFWVh~`i%MN)9r#vm=)hnNSXC%EpaO;G${9raVe(Q7S#kDpfc9>Q{J) zS=5cmaKP+Gi%uH_2v}v)^~86In>XzI%qxS+jiqbt9mH?hs(>#zx}lQ+bVcUFpNl7j zcDhN~r8~S3b}sf(D9gJb>YfYnK+ci#WI_<|1CX-p|ABif%?+dTMrO1g))R{KwNfVZ zcmG#ed_r+r<i z!MrhIj(>STNWtgpU-%YWqYa4hWU^rcu~gdBkqu4rrVavB-U z_q>gU+vL*SCgt4c9lQRuVoDPl?*v)4s?b7n7sq!w;suhxeHDVk_b0<>A| zv!9}L3=Y=Id}HB&+YD1|FNddm*+VTTdv?%URsQgU;g|W9`}7Cx!_us-CsdIn$N}2w zwCsQ}fyxi&(0E*pE|{Wati5_xNZo9E|Ae6AT6DR9v>c}rH|2LzIji>I9pQoLjc*7# zJAxVov&!4{f+f5Gz9stSM3l1$$ExYNq^}?!pnzqJq7gUaFz#X@dIT^5k6d_sWb>AI zfc4Fq5Le_SinnHe z(xkL|ijI4efi7#Rnztv%)+N?6`u#MD5L=c?PsAq}6QSWVXT6nw@9)!C&wgnyupzqo zmRqG|+&kmQ*}4aV);O?s4)8rOZ%;ACr4Ew~b0q6^hWwOd)d%^l>&9*bn#ttS;oujQ zLN*40lQd>~`v&3+Sv{z)D}6+?vM&w+1M+D|WR6mY!_USE+!{rkZv9R_4mI+X#89B@ zTT_C3k0FWRx$m{E6Z^+0AHjA4`nBN;fN~h6GU5O`FdJmf2jED#6rJX2g94Yw#d?E= zjE<4_ClT`wf-u}O^m!2rLxuD1uMR4>17%WNOJWm2_lNe3B&HFAkZeR47lL0%rK?8Y zn!OUW{paR;xB3EhuOz70=A^M~y@Bxfq)2>>YFfY+439L=?goiRV*v$M)Y$2htYCw= zGt7_ERvTGWko;C)+d9lHFLgq3gGGj1>k;SjX^{(pE=Kd67kC;~O*vN(a9n#r(%T5r zsdYs7%a9Qw(p^^5OKNJ@N_BH_GoXyV-u|(BNzs5hL<}$T&&6q1#(&EVY_Mv==-f=@ l06;3au|7oM>5oD1RBOzPCtIGoT_E*qAj~$C{w}Q`S8nHGSB3xp literal 0 HcmV?d00001 diff --git a/depend.cmake b/depend.cmake new file mode 100644 index 0000000..3c69adb --- /dev/null +++ b/depend.cmake @@ -0,0 +1,321 @@ +include(FetchContent) + +# ASTC Encoder + +FetchContent_Declare( + astcenc + GIT_REPOSITORY https://github.com/ARM-software/astc-encoder + GIT_TAG 7e2a81ed5abc202c6f06be9302d193ba44a765c9 #3.5 + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/_deps/astcenc-build" + SUBBUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/_deps/astcenc-subbuild" + SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/astcenc-src" +) +set(ISA_SSE41 ON CACHE BOOL "") +set(CLI OFF CACHE BOOL "") +if (NOT astcenc_POPULATED) + FetchContent_Populate( + astcenc + ) +endif (NOT astcenc_POPULATED) +find_package(Git REQUIRED) +execute_process(COMMAND ${GIT_EXECUTABLE} apply "${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/astcenc-x86-32-popcntu64-patch-7e2a81ed5abc202c6f06be9302d193ba44a765c9.patch" + WORKING_DIRECTORY ${astcenc_SOURCE_DIR} + ERROR_QUIET) +add_subdirectory(${astcenc_SOURCE_DIR} ${astcenc_BINARY_DIR}) + +# Crunch (Unity fork, compatible with the formats used in Unity 2017.3 onwards) + +FetchContent_Declare( + crunch-unity + GIT_REPOSITORY https://github.com/Unity-Technologies/crunch + GIT_TAG 8708900eca8ec609d279270e72936258f81ddfb7 + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/_deps/crunch-unity-build" + SUBBUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/_deps/crunch-unity-subbuild" + SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/crunch-unity-src" +) +if (NOT crunch-unity_POPULATED) + FetchContent_Populate(crunch-unity) +endif (NOT crunch-unity_POPULATED) +find_package(Git REQUIRED) +execute_process(COMMAND ${GIT_EXECUTABLE} apply "${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/crunch-unity-patch-8708900eca8ec609d279270e72936258f81ddfb7.patch" + WORKING_DIRECTORY ${crunch-unity_SOURCE_DIR} + ERROR_QUIET) +#Check if the patch is applied. +execute_process(COMMAND ${GIT_EXECUTABLE} apply --reverse --check "${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/crunch-unity-patch-8708900eca8ec609d279270e72936258f81ddfb7.patch" + WORKING_DIRECTORY ${crunch-unity_SOURCE_DIR}) + +file(GLOB crnlib-unity_SOURCES_EXCLUDE "${crunch-unity_SOURCE_DIR}/crnlib/lzham*.cpp") +file(GLOB crnlib-unity_SOURCES "${crunch-unity_SOURCE_DIR}/crnlib/*.cpp") +list(REMOVE_ITEM crnlib-unity_SOURCES ${crnlib-unity_SOURCES_EXCLUDE}) +add_library (crnlib-unity STATIC ${crnlib-unity_SOURCES}) +target_include_directories (crnlib-unity PUBLIC "${crunch-unity_SOURCE_DIR}/inc") + +# Crunch (older version, compatible with the formats used in Unity 5 .. 2017.2) + +FetchContent_Declare( + crunch-legacy + GIT_REPOSITORY https://github.com/BinomialLLC/crunch + GIT_TAG 671a0648c8a440b4397f1d96ea5cf5700f830417 + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/_deps/crunch-legacy-build" + SUBBUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/_deps/crunch-legacy-subbuild" + SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/crunch-legacy-src" +) +if (NOT crunch-legacy_POPULATED) + FetchContent_Populate(crunch-legacy) +endif (NOT crunch-legacy_POPULATED) +find_package(Git REQUIRED) +execute_process(COMMAND ${GIT_EXECUTABLE} apply "${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/crunch-legacy-patch-671a0648c8a440b4397f1d96ea5cf5700f830417.patch" + WORKING_DIRECTORY ${crunch-legacy_SOURCE_DIR} + ERROR_QUIET) +#Check if the patch is applied. +execute_process(COMMAND ${GIT_EXECUTABLE} apply --reverse --check "${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/crunch-legacy-patch-671a0648c8a440b4397f1d96ea5cf5700f830417.patch" + WORKING_DIRECTORY ${crunch-legacy_SOURCE_DIR}) + +file(GLOB crnlib-legacy_SOURCES_EXCLUDE "${crunch-legacy_SOURCE_DIR}/crnlib/lzham*.cpp") +file(GLOB crnlib-legacy_SOURCES ${crunch-legacy_SOURCE_DIR}/crnlib/*.cpp) +list(REMOVE_ITEM crnlib-legacy_SOURCES ${crnlib-legacy_SOURCES_EXCLUDE}) +add_library (crnlib-legacy STATIC ${crnlib-legacy_SOURCES}) +target_include_directories (crnlib-legacy PUBLIC "${crunch-legacy_SOURCE_DIR}/inc") + +# Squish (Official repo appears to be https://sourceforge.net/projects/libsquish/ , previously on Google Code) + +FetchContent_Declare( + libsquish + URL "${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/libsquish-1.15.tgz" + URL_HASH SHA256=628796EEBA608866183A61D080D46967C9DDA6723BC0A3EC52324C85D2147269 + SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/libsquish-src" +) +if (NOT libsquish_POPULATED) + FetchContent_Populate(libsquish) +endif (NOT libsquish_POPULATED) +set(BUILD_SQUISH_WITH_OPENMP OFF CACHE BOOL "") +add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/libsquish-src" "${CMAKE_CURRENT_BINARY_DIR}/_deps/libsquish-build") +set(LIBSQUISH_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/libsquish-src") + +# ISPC Texture Compressor +# Requires the ISPC compiler (https://github.com/ispc/ispc) to generate some object files. +# Official Windows Binaries: https://github.com/ispc/ispc/releases/download/v1.17.0/ispc-v1.17.0-windows.zip +# Binaries exist for other platforms, assuming Windows for now. Could also be compiled from scratch instead. + +FetchContent_Declare( + ispc_texcomp + GIT_REPOSITORY https://github.com/GameTechDev/ISPCTextureCompressor + GIT_TAG 14d998c02b71c356ff3a1ec1adc9243a517bbf38 + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/_deps/ispc_texcomp-build" + SUBBUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/_deps/ispc_texcomp-subbuild" + SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/ispc_texcomp-src" +) +set(ISA_SSE41 ON CACHE BOOL "") +set(CLI OFF CACHE BOOL "") +if (NOT ispc_texcomp_POPULATED) + FetchContent_Populate(ispc_texcomp) +endif (NOT ispc_texcomp_POPULATED) +# Fetch the ISPC compiler binaries +FetchContent_Declare( + ispc_compiler_binaries + URL https://github.com/ispc/ispc/releases/download/v1.17.0/ispc-v1.17.0-windows.zip + URL_HASH SHA256=E9A7CC98F69357482985BCBF69FA006632CEE7B3606069B4D5E16DC62092D660 + SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/ispc_compiler" +) +if (NOT ispc_compiler_binaries_POPULATED) + FetchContent_Populate(ispc_compiler_binaries) +endif (NOT ispc_compiler_binaries_POPULATED) + +# Custom command to invoke the ISPC compiler. +if (CMAKE_SIZEOF_VOID_P EQUAL 8) + set(ISPC_COMPILER_COMMAND_ARCH "") +elseif (CMAKE_SIZEOF_VOID_P EQUAL 4) + set(ISPC_COMPILER_COMMAND_ARCH "--arch=x86") +endif () +add_custom_command( + OUTPUT + ${ispc_texcomp_BINARY_DIR}/kernel.obj + ${ispc_texcomp_BINARY_DIR}/kernel_sse4.obj + ${ispc_texcomp_BINARY_DIR}/kernel_avx.obj + ${ispc_texcomp_BINARY_DIR}/kernel_ispc.h + + ${ispc_texcomp_BINARY_DIR}/kernel_astc.obj + ${ispc_texcomp_BINARY_DIR}/kernel_astc_sse4.obj + ${ispc_texcomp_BINARY_DIR}/kernel_astc_avx.obj + ${ispc_texcomp_BINARY_DIR}/kernel_astc_ispc.h + COMMAND + "${ispc_compiler_binaries_SOURCE_DIR}/bin/ispc.exe" -O2 "${ispc_texcomp_SOURCE_DIR}/ispc_texcomp/kernel.ispc" + -o "${ispc_texcomp_BINARY_DIR}/kernel.obj" + -h "${ispc_texcomp_BINARY_DIR}/kernel_ispc.h" + ${ISPC_COMPILER_COMMAND_ARCH} + --target=sse4,avx + --opt=fast-math + COMMAND + "${ispc_compiler_binaries_SOURCE_DIR}/bin/ispc.exe" -O2 "${ispc_texcomp_SOURCE_DIR}/ispc_texcomp/kernel_astc.ispc" + -o "${ispc_texcomp_BINARY_DIR}/kernel_astc.obj" + -h "${ispc_texcomp_BINARY_DIR}/kernel_astc_ispc.h" + ${ISPC_COMPILER_COMMAND_ARCH} + --target=sse4,avx + --opt=fast-math + WORKING_DIRECTORY "${ispc_texcomp_BINARY_DIR}" +) +# Create the actual ispc_texcomp library. +add_library (ispc_texcomp SHARED + ${ispc_texcomp_SOURCE_DIR}/ispc_texcomp/ispc_texcomp.cpp + ${ispc_texcomp_SOURCE_DIR}/ispc_texcomp/ispc_texcomp_astc.cpp + ${ispc_texcomp_SOURCE_DIR}/ispc_texcomp/ispc_texcomp.def + ${ispc_texcomp_BINARY_DIR}/kernel.obj + ${ispc_texcomp_BINARY_DIR}/kernel_sse4.obj + ${ispc_texcomp_BINARY_DIR}/kernel_avx.obj + ${ispc_texcomp_BINARY_DIR}/kernel_astc.obj + ${ispc_texcomp_BINARY_DIR}/kernel_astc_sse4.obj + ${ispc_texcomp_BINARY_DIR}/kernel_astc_avx.obj +) +target_include_directories (ispc_texcomp PUBLIC "${ispc_texcomp_SOURCE_DIR}/ispc_texcomp") +target_include_directories (ispc_texcomp PRIVATE "${ispc_texcomp_BINARY_DIR}") +set_target_properties(ispc_texcomp PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + +# texgenpack (only used for decompression) and dependencies + +FetchContent_Declare( + pthreads4w + GIT_REPOSITORY https://github.com/jwinarske/pthreads4w + GIT_TAG 02fecc211d626f28e05ecbb0c10f739bd36d6442 #2.10.0 RC + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/_deps/pthreads4w-build" + SUBBUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/_deps/pthreads4w-subbuild" + SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/pthreads4w-src" +) +if (NOT pthreads4w_POPULATED) + FetchContent_Populate(pthreads4w) +endif (NOT pthreads4w_POPULATED) +FetchContent_Declare( + libfgen + GIT_REPOSITORY https://github.com/hglm/libfgen + GIT_TAG 071e5130f5286850eafe8de65f51e05604a02929 + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/_deps/libfgen-build" + SUBBUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/_deps/libfgen-subbuild" + SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/libfgen-src" +) +if (NOT libfgen_POPULATED) + FetchContent_Populate(libfgen) +endif (NOT libfgen_POPULATED) +FetchContent_Declare( + texgenpack + GIT_REPOSITORY https://github.com/hglm/texgenpack + GIT_TAG cf548ef583ca9592a55ea217b0ec43a2e25b9cbe #0.96 + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/_deps/texgenpack-build" + SUBBUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/_deps/texgenpack-subbuild" + SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/texgenpack-src" +) +if (NOT texgenpack_POPULATED) + FetchContent_Populate(texgenpack) +endif (NOT texgenpack_POPULATED) +find_package(Git REQUIRED) +execute_process(COMMAND ${GIT_EXECUTABLE} apply "${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/texgenpack-patch-cf548ef583ca9592a55ea217b0ec43a2e25b9cbe.patch" + WORKING_DIRECTORY ${texgenpack_SOURCE_DIR} + ERROR_QUIET) +execute_process(COMMAND ${GIT_EXECUTABLE} apply "${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/libfgen-patch-071e5130f5286850eafe8de65f51e05604a02929.patch" + WORKING_DIRECTORY ${libfgen_SOURCE_DIR} + ERROR_QUIET) +execute_process(COMMAND ${GIT_EXECUTABLE} apply "${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/pthreads4w-patch-02fecc211d626f28e05ecbb0c10f739bd36d6442.patch" + WORKING_DIRECTORY ${pthreads4w_SOURCE_DIR} + ERROR_QUIET) +# Verify that the patches have been applied. +execute_process(COMMAND ${GIT_EXECUTABLE} apply --reverse --check "${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/texgenpack-patch-cf548ef583ca9592a55ea217b0ec43a2e25b9cbe.patch" + WORKING_DIRECTORY ${texgenpack_SOURCE_DIR}) +execute_process(COMMAND ${GIT_EXECUTABLE} apply --reverse --check "${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/libfgen-patch-071e5130f5286850eafe8de65f51e05604a02929.patch" + WORKING_DIRECTORY ${libfgen_SOURCE_DIR}) +execute_process(COMMAND ${GIT_EXECUTABLE} apply --reverse --check "${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/pthreads4w-patch-02fecc211d626f28e05ecbb0c10f739bd36d6442.patch" + WORKING_DIRECTORY ${pthreads4w_SOURCE_DIR}) + +add_library (pthreads4w STATIC "${pthreads4w_SOURCE_DIR}/pthread.c") +target_include_directories (pthreads4w PUBLIC "${pthreads4w_SOURCE_DIR}") +target_compile_definitions(pthreads4w PRIVATE PTW32_STATIC_LIB HAVE_CONFIG_H) +add_library (libfgen STATIC + "${libfgen_SOURCE_DIR}/bitstring.c" "${libfgen_SOURCE_DIR}/cache.c" "${libfgen_SOURCE_DIR}/crossover.c" + "${libfgen_SOURCE_DIR}/decode.c" "${libfgen_SOURCE_DIR}/error.c" "${libfgen_SOURCE_DIR}/ffit.c" + "${libfgen_SOURCE_DIR}/ga.c" "${libfgen_SOURCE_DIR}/gray.c" "${libfgen_SOURCE_DIR}/migration.c" + "${libfgen_SOURCE_DIR}/mutation.c" "${libfgen_SOURCE_DIR}/parameters.c" "${libfgen_SOURCE_DIR}/population.c" + "${libfgen_SOURCE_DIR}/pso.c" "${libfgen_SOURCE_DIR}/random.c" "${libfgen_SOURCE_DIR}/seed.c" + "${libfgen_SOURCE_DIR}/selection.c" "${libfgen_SOURCE_DIR}/steady_state.c" +) +target_include_directories (libfgen PUBLIC "${libfgen_SOURCE_DIR}") +target_link_libraries(libfgen PUBLIC pthreads4w) +add_library (texgenpack SHARED + "${texgenpack_SOURCE_DIR}/astc.c" "${texgenpack_SOURCE_DIR}/bptc.c" "${texgenpack_SOURCE_DIR}/calibrate.c" + "${texgenpack_SOURCE_DIR}/compare.c" "${texgenpack_SOURCE_DIR}/compress.c" "${texgenpack_SOURCE_DIR}/dxtc.c" + "${texgenpack_SOURCE_DIR}/etc2.c" "${texgenpack_SOURCE_DIR}/file.c" "${texgenpack_SOURCE_DIR}/half_float.c" + "${texgenpack_SOURCE_DIR}/image.c" "${texgenpack_SOURCE_DIR}/mipmap.c" "${texgenpack_SOURCE_DIR}/rgtc.c" + "${texgenpack_SOURCE_DIR}/texgenpack.c" "${texgenpack_SOURCE_DIR}/texture.c" +) +target_include_directories (texgenpack PUBLIC "${texgenpack_SOURCE_DIR}") +target_link_libraries(texgenpack PRIVATE libfgen pthreads4w) +target_compile_definitions(texgenpack PRIVATE TEXGENPACK_EXPORTS) +set_target_properties(texgenpack PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + +# mCtrl, with patches for UABE (customized TreeList and Mditab controls) +# Note: The patches disable some features of mCtrl to save miniscule amounts of space. + +set(MCTRL_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) +set(MCTRL_BUILD_TESTS OFF CACHE BOOL "" FORCE) +FetchContent_Declare( + mctrl + GIT_REPOSITORY https://github.com/mity/mctrl + GIT_TAG 42334bfbfffbb1530e69213199e775e54edbad21 #release-0.11.5 + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/_deps/mctrl-build" + SUBBUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/_deps/mctrl-subbuild" + SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/mctrl-src" +) +if (NOT mctrl_POPULATED) + FetchContent_Populate(mctrl) +endif (NOT mctrl_POPULATED) +find_package(Git REQUIRED) +execute_process(COMMAND ${GIT_EXECUTABLE} apply "${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/mctrl-patch-42334bfbfffbb1530e69213199e775e54edbad21.patch" + WORKING_DIRECTORY ${mctrl_SOURCE_DIR} + ERROR_QUIET) +# Verify that the patch has been applied. +execute_process(COMMAND ${GIT_EXECUTABLE} apply --reverse --check "${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/mctrl-patch-42334bfbfffbb1530e69213199e775e54edbad21.patch" + WORKING_DIRECTORY ${mctrl_SOURCE_DIR}) +add_subdirectory(${mctrl_SOURCE_DIR} ${mctrl_BINARY_DIR}) +set(MCTRL_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/mctrl-src/include") + +# jsmn + +FetchContent_Declare( + jsmn + GIT_REPOSITORY https://github.com/zserge/jsmn + GIT_TAG 25647e692c7906b96ffd2b05ca54c097948e879c + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/_deps/jsmn-build" + SUBBUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/_deps/jsmn-subbuild" + SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/jsmn-src" +) +if (NOT jsmn_POPULATED) + FetchContent_Populate(jsmn) +endif (NOT jsmn_POPULATED) +add_library (jsmn INTERFACE) +target_include_directories (jsmn INTERFACE "${jsmn_SOURCE_DIR}") + +# assimp + +FetchContent_Declare( + assimp + GIT_REPOSITORY https://github.com/assimp/assimp + GIT_TAG 80799bdbf90ce626475635815ee18537718a05b1 #4.1.0 + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/_deps/assimp-build" + SUBBUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/_deps/assimp-subbuild" + SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/assimp-src" +) +if (NOT assimp_POPULATED) + FetchContent_Populate(assimp) +endif (NOT assimp_POPULATED) +find_package(Git REQUIRED) +execute_process(COMMAND ${GIT_EXECUTABLE} apply "${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/assimp-patch-80799bdbf90ce626475635815ee18537718a05b1.patch" + WORKING_DIRECTORY ${assimp_SOURCE_DIR} + ERROR_QUIET) +# Verify that the patch has been applied. +execute_process(COMMAND ${GIT_EXECUTABLE} apply --reverse --check "${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/assimp-patch-80799bdbf90ce626475635815ee18537718a05b1.patch" + WORKING_DIRECTORY ${assimp_SOURCE_DIR}) +set(BUILD_SHARED_LIBS OFF CACHE BOOL "") +set(ASSIMP_BUILD_ASSIMP_TOOLS OFF CACHE BOOL "") +set(ASSIMP_BUILD_TESTS OFF CACHE BOOL "") +set(ASSIMP_BUILD_ALL_IMPORTERS_BY_DEFAULT OFF CACHE BOOL "") +set(ASSIMP_BUILD_COLLADA_IMPORTER ON CACHE BOOL "" FORCE) +set(ASSIMP_BUILD_3MF_IMPORTER ON CACHE BOOL "" FORCE) # Required to prevent linker errors. Something apparently uses this importer. +add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/fetchcontent/assimp-src" "${CMAKE_CURRENT_BINARY_DIR}/_deps/assimp-build") +set(ASSIMP_INCLUDE_DIR "${assimp_SOURCE_DIR}/include" "${assimp_BINARY_DIR}/include" "${assimp_SOURCE_DIR}/code") +SET(CMAKE_DEBUG_POSTFIX "" CACHE STRING "" FORCE) diff --git a/fetchcontent/assimp-patch-80799bdbf90ce626475635815ee18537718a05b1.patch b/fetchcontent/assimp-patch-80799bdbf90ce626475635815ee18537718a05b1.patch new file mode 100644 index 0000000..a764b42 --- /dev/null +++ b/fetchcontent/assimp-patch-80799bdbf90ce626475635815ee18537718a05b1.patch @@ -0,0 +1,531 @@ +diff --git a/code/ColladaExporter.cpp b/code/ColladaExporter.cpp +index 4a10d5845..11e9b2ff2 100644 +--- a/code/ColladaExporter.cpp ++++ b/code/ColladaExporter.cpp +@@ -108,6 +108,8 @@ ColladaExporter::ColladaExporter( const aiScene* pScene, IOSystem* pIOSystem, co + // set up strings + endstr = "\n"; + ++ mFoundSkeletonRootNodeID = "skeleton_root"; ++ + // start writing the file + WriteFile(); + } +@@ -803,15 +805,76 @@ void ColladaExporter::WriteControllerLibrary() + PushTag(); + + for( size_t a = 0; a < mScene->mNumMeshes; ++a) +- WriteController( a); ++ { ++ WriteMorphController( a); ++ WriteSkinController( a); ++ } + + PopTag(); + mOutput << startstr << "" << endstr; + } + ++// ------------------------------------------------------------------------------------------------ ++// Writes a morph controller of the given mesh ++//MOD by DerPopo ++void ColladaExporter::WriteMorphController( size_t pIndex) ++{ ++ const aiMesh* mesh = mScene->mMeshes[pIndex]; ++ const std::string idstr = GetMeshId( pIndex); ++ const std::string idstrEscaped = XMLEscape(idstr); ++ ++ if ( mesh->mNumFaces == 0 || mesh->mNumVertices == 0 ) ++ return; ++ ++ if ( mesh->mNumAnimMeshes == 0 ) ++ return; ++ ++ const char *methodStr = "NORMALIZED"; ++ if (mesh->mMethod == aiMorphingMethod_MORPH_RELATIVE) ++ methodStr = "RELATIVE"; ++ ++ mOutput << startstr << ""<< endstr; ++ PushTag(); ++ ++ mOutput << startstr << "" << endstr; ++ PushTag(); ++ ++ std::string *morphTargets = new std::string[mesh->mNumAnimMeshes]; ++ for (unsigned int i = 0; i < mesh->mNumAnimMeshes; ++i) ++ { ++ const std::string idstrAnim = GetAnimMeshId(pIndex, i); ++ morphTargets[i] = XMLEscape(idstrAnim); ++ } ++ WriteIDREFArray( idstr + "-morph_targets", IDREFType_MorphTarget, morphTargets, mesh->mNumAnimMeshes ); ++ delete[] morphTargets; ++ ++ float *morphWeights = new float[mesh->mNumAnimMeshes]; ++ for (unsigned int i = 0; i < mesh->mNumAnimMeshes; ++i) ++ { ++ morphWeights[i] = mesh->mAnimMeshes[i]->mWeight; ++ } ++ WriteFloatArray( idstr + "-morph_weights", FloatType_Weight, (ai_real*)morphWeights, mesh->mNumAnimMeshes ); ++ delete[] morphWeights; ++ ++ mOutput << startstr << "" << endstr; ++ PushTag(); ++ mOutput << startstr << "" << endstr; ++ mOutput << startstr << "" << endstr; ++ PopTag(); ++ mOutput << startstr << "" << endstr; ++ ++ ++ PopTag(); ++ mOutput << startstr << "" << endstr; ++ ++ PopTag(); ++ mOutput << startstr << "" << endstr; ++} ++ + // ------------------------------------------------------------------------------------------------ + // Writes a skin controller of the given mesh +-void ColladaExporter::WriteController( size_t pIndex) ++void ColladaExporter::WriteSkinController( size_t pIndex) + { + const aiMesh* mesh = mScene->mMeshes[pIndex]; + const std::string idstr = GetMeshId( pIndex); +@@ -987,12 +1050,21 @@ void ColladaExporter::WriteGeometry( size_t pIndex) + const aiMesh* mesh = mScene->mMeshes[pIndex]; + const std::string idstr = GetMeshId( pIndex); + const std::string idstrEscaped = XMLEscape(idstr); ++ //MOD by DerPopo ++ const std::string namestr = (mesh->mName.length > 0) ? mesh->mName.C_Str() : idstr; ++ //MOD by DerPopo ++ const std::string namestrEscaped = XMLEscape(namestr); + + if ( mesh->mNumFaces == 0 || mesh->mNumVertices == 0 ) + return; + + // opening tag +- mOutput << startstr << "" << endstr; ++ //mOutput << startstr << "" << endstr; ++ //MOD by DerPopo ++ if ( mesh->mNumAnimMeshes == 0) ++ mOutput << startstr << "" << endstr; ++ else ++ mOutput << startstr << "" << endstr; + PushTag(); + + mOutput << startstr << "" << endstr; +@@ -1003,6 +1075,13 @@ void ColladaExporter::WriteGeometry( size_t pIndex) + // Normals, if any + if( mesh->HasNormals() ) + WriteFloatArray( idstr + "-normals", FloatType_Vector, (ai_real*) mesh->mNormals, mesh->mNumVertices); ++ // (Bi-)Tangents, if any ++ //MOD by DerPopo ++ if( mesh->HasTangentsAndBitangents() ) ++ { ++ WriteFloatArray( idstr + "-tangents", FloatType_Vector, (ai_real*) mesh->mTangents, mesh->mNumVertices); ++ WriteFloatArray( idstr + "-bitangents", FloatType_Vector, (ai_real*) mesh->mBitangents, mesh->mNumVertices); ++ } + + // texture coords + for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) +@@ -1015,7 +1094,7 @@ void ColladaExporter::WriteGeometry( size_t pIndex) + } + + // vertex colors +- for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) ++ for( size_t a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a) + { + if( mesh->HasVertexColors(static_cast(a)) ) + WriteFloatArray( idstr + "-color" + to_string(a), FloatType_Color, (ai_real*) mesh->mColors[a], mesh->mNumVertices); +@@ -1045,7 +1124,13 @@ void ColladaExporter::WriteGeometry( size_t pIndex) + PushTag(); + mOutput << startstr << "" << endstr; + if( mesh->HasNormals() ) +- mOutput << startstr << "" << endstr; ++ mOutput << startstr << "" << endstr; ++ //MOD by DerPopo ++ if( mesh->HasTangentsAndBitangents() ) ++ { ++ mOutput << startstr << "" << endstr; ++ mOutput << startstr << "" << endstr; ++ } + for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a ) + { + if( mesh->HasTextureCoords(static_cast(a)) ) +@@ -1080,6 +1165,12 @@ void ColladaExporter::WriteGeometry( size_t pIndex) + mOutput << startstr << "" << endstr; + if( mesh->HasNormals() ) + mOutput << startstr << "" << endstr; ++ //MOD by DerPopo ++ if( mesh->HasTangentsAndBitangents() ) ++ { ++ mOutput << startstr << "" << endstr; ++ mOutput << startstr << "" << endstr; ++ } + for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a ) + { + if( mesh->HasTextureCoords(static_cast(a)) ) +@@ -1112,6 +1203,190 @@ void ColladaExporter::WriteGeometry( size_t pIndex) + mOutput << startstr << "" << endstr; + } + ++ // closing tags ++ PopTag(); ++ mOutput << startstr << "" << endstr; ++ PopTag(); ++ mOutput << startstr << "" << endstr; ++ ++ for (unsigned int i = 0; i < mesh->mNumAnimMeshes; i++) ++ { ++ WriteMorphGeometry(pIndex, i); ++ } ++} ++ ++// ------------------------------------------------------------------------------------------------ ++// Writes the given anim mesh ++//MOD by DerPopo ++void ColladaExporter::WriteMorphGeometry( size_t pIndexMesh, size_t pIndexAnimMesh) ++{ ++ const aiMesh* mesh = mScene->mMeshes[pIndexMesh]; ++ const aiAnimMesh* animMesh = mesh->mAnimMeshes[pIndexAnimMesh]; ++ ++ const std::string idstr = GetAnimMeshId( pIndexMesh, pIndexAnimMesh); ++ const std::string idstrEscaped = XMLEscape(idstr); ++ //const std::string baseidstr = GetMeshId( pIndexMesh); ++ //const std::string baseidstrEscaped = XMLEscape(baseidstr); ++ const std::string namestr = ++ (animMesh->mName.length > 0) ++ ? animMesh->mName.C_Str() ++ : ((mesh->mName.length > 0) ++ ? (std::string(mesh->mName.C_Str()) + "-morph" + to_string(pIndexAnimMesh)) ++ : idstr); ++ const std::string namestrEscaped = XMLEscape(namestr); ++ ++ if (animMesh->mNumVertices != mesh->mNumVertices) return; ++ ++ // opening tag ++ mOutput << startstr << "" << endstr; ++ PushTag(); ++ ++ mOutput << startstr << "" << endstr; ++ PushTag(); ++ ++ // Positions, if any ++ //TODO : Check if not having vertex positions in a morph mesh works fine with all importers. The standard does allow it. ++ if ( animMesh->HasPositions() ) ++ WriteFloatArray( idstr + "-positions", FloatType_Vector, (ai_real*) animMesh->mVertices, animMesh->mNumVertices); ++ // Normals, if any ++ if( animMesh->HasNormals() ) ++ WriteFloatArray( idstr + "-normals", FloatType_Vector, (ai_real*) animMesh->mNormals, animMesh->mNumVertices); ++ // (Bi-)Tangents, if any ++ if( animMesh->HasTangentsAndBitangents() ) ++ { ++ WriteFloatArray( idstr + "-tangents", FloatType_Vector, (ai_real*) animMesh->mTangents, animMesh->mNumVertices); ++ WriteFloatArray( idstr + "-bitangents", FloatType_Vector, (ai_real*) animMesh->mBitangents, animMesh->mNumVertices); ++ } ++ ++ // texture coords ++ for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) ++ { ++ if( mesh->HasTextureCoords(static_cast(a)) && animMesh->HasTextureCoords(static_cast(a)) ) ++ { ++ WriteFloatArray( idstr + "-tex" + to_string(a), mesh->mNumUVComponents[a] == 3 ? FloatType_TexCoord3 : FloatType_TexCoord2, ++ (ai_real*) animMesh->mTextureCoords[a], animMesh->mNumVertices); ++ } ++ } ++ ++ // vertex colors ++ for( size_t a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a) ++ { ++ if( mesh->HasVertexColors(static_cast(a)) && animMesh->HasVertexColors(static_cast(a)) ) ++ WriteFloatArray( idstr + "-color" + to_string(a), FloatType_Color, (ai_real*) animMesh->mColors[a], animMesh->mNumVertices); ++ } ++ ++ // assemble vertex structure ++ ++ //TODO : Check if skipping the vertices tag is allowed! ++ if ( animMesh->HasPositions() ) ++ { ++ // Only write input for POSITION since we will write other as shared inputs in polygon definition ++ mOutput << startstr << "" << endstr; ++ PushTag(); ++ mOutput << startstr << "" << endstr; ++ PopTag(); ++ mOutput << startstr << "" << endstr; ++ } ++ ++ // count the number of lines, triangles and polygon meshes ++ int countLines = 0; ++ int countPoly = 0; ++ for( size_t a = 0; a < mesh->mNumFaces; ++a ) ++ { ++ if (mesh->mFaces[a].mNumIndices == 2) countLines++; ++ else if (mesh->mFaces[a].mNumIndices >= 3) countPoly++; ++ } ++ ++ // lines ++ if (countLines) ++ { ++ mOutput << startstr << "" << endstr; ++ PushTag(); ++ //TODO : Check if skipping the vertex input is allowed! ++ if( animMesh->HasPositions() ) ++ mOutput << startstr << "" << endstr; ++ if( animMesh->HasNormals() ) ++ mOutput << startstr << "" << endstr; ++ if( animMesh->HasTangentsAndBitangents() ) ++ { ++ mOutput << startstr << "" << endstr; ++ mOutput << startstr << "" << endstr; ++ } ++ for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a ) ++ { ++ if( mesh->HasTextureCoords(static_cast(a)) && animMesh->HasTextureCoords(static_cast(a)) ) ++ mOutput << startstr << "" << endstr; ++ } ++ for( size_t a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a ) ++ { ++ if( mesh->HasVertexColors(static_cast(a)) && animMesh->HasVertexColors(static_cast(a)) ) ++ mOutput << startstr << "" << endstr; ++ } ++ ++ //TODO : Check if the faces are a requirement! ++ /*mOutput << startstr << "

"; ++ for( size_t a = 0; a < mesh->mNumFaces; ++a ) ++ { ++ const aiFace& face = mesh->mFaces[a]; ++ if (face.mNumIndices != 2) continue; ++ for( size_t b = 0; b < face.mNumIndices; ++b ) ++ mOutput << face.mIndices[b] << " "; ++ } ++ mOutput << "

" << endstr;*/ ++ PopTag(); ++ mOutput << startstr << "
" << endstr; ++ } ++ ++ // triangle - don't use it, because compatibility problems ++ ++ // polygons ++ if (countPoly) ++ { ++ mOutput << startstr << "" << endstr; ++ PushTag(); ++ //TODO : Check if skipping the vertex input is allowed! ++ if( animMesh->HasPositions() ) ++ mOutput << startstr << "" << endstr; ++ if( animMesh->HasNormals() ) ++ mOutput << startstr << "" << endstr; ++ if( animMesh->HasTangentsAndBitangents() ) ++ { ++ mOutput << startstr << "" << endstr; ++ mOutput << startstr << "" << endstr; ++ } ++ for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a ) ++ { ++ if( mesh->HasTextureCoords(static_cast(a)) && animMesh->HasTextureCoords(static_cast(a)) ) ++ mOutput << startstr << "" << endstr; ++ } ++ for( size_t a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a ) ++ { ++ if( mesh->HasVertexColors(static_cast(a)) && animMesh->HasVertexColors(static_cast(a)) ) ++ mOutput << startstr << "" << endstr; ++ } ++ ++ //TODO : Check if the faces are a requirement! ++ /*mOutput << startstr << ""; ++ for( size_t a = 0; a < mesh->mNumFaces; ++a ) ++ { ++ if (mesh->mFaces[a].mNumIndices < 3) continue; ++ mOutput << mesh->mFaces[a].mNumIndices << " "; ++ } ++ mOutput << "" << endstr; ++ ++ mOutput << startstr << "

"; ++ for( size_t a = 0; a < mesh->mNumFaces; ++a ) ++ { ++ const aiFace& face = mesh->mFaces[a]; ++ if (face.mNumIndices < 3) continue; ++ for( size_t b = 0; b < face.mNumIndices; ++b ) ++ mOutput << face.mIndices[b] << " "; ++ } ++ mOutput << "

" << endstr;*/ ++ PopTag(); ++ mOutput << startstr << "
" << endstr; ++ } ++ + // closing tags + PopTag(); + mOutput << startstr << "
" << endstr; +@@ -1225,6 +1500,51 @@ void ColladaExporter::WriteFloatArray( const std::string& pIdString, FloatDataTy + mOutput << startstr << "" << endstr; + } + ++// ------------------------------------------------------------------------------------------------ ++// Writes a IDREF array ++//MOD by DerPopo ++void ColladaExporter::WriteIDREFArray( const std::string& pIdString, IDREFDataType pType, const std::string* pData, size_t pElementCount) ++{ ++ // make a switch for more types ++ if (pType != IDREFType_MorphTarget) ++ return; ++ ++ std::string arrayId = pIdString + "-array"; ++ ++ mOutput << startstr << "" << endstr; ++ PushTag(); ++ ++ // source array ++ mOutput << startstr << " "; ++ PushTag(); ++ ++ for( size_t a = 0; a < pElementCount; ++a ) ++ mOutput << pData[a] << " "; ++ ++ mOutput << "" << endstr; ++ PopTag(); ++ ++ // the usual Collada fun. Let's bloat it even more! ++ mOutput << startstr << "" << endstr; ++ PushTag(); ++ mOutput << startstr << "" << endstr; ++ PushTag(); ++ ++ switch( pType ) ++ { ++ case IDREFType_MorphTarget: ++ mOutput << startstr << "" << endstr; ++ break; ++ } ++ ++ PopTag(); ++ mOutput << startstr << "" << endstr; ++ PopTag(); ++ mOutput << startstr << "" << endstr; ++ PopTag(); ++ mOutput << startstr << "" << endstr; ++} ++ + // ------------------------------------------------------------------------------------------------ + // Writes the scene library + void ColladaExporter::WriteSceneLibrary() +diff --git a/code/ColladaExporter.h b/code/ColladaExporter.h +index e7a4a9b5d..c406e08eb 100644 +--- a/code/ColladaExporter.h ++++ b/code/ColladaExporter.h +@@ -105,8 +105,13 @@ protected: + /// Writes the controller library + void WriteControllerLibrary(); + ++ /// Writes a morph controller of the given mesh ++ //MOD by DerPopo ++ void WriteMorphController( size_t pIndex); ++ + /// Writes a skin controller of the given mesh +- void WriteController( size_t pIndex); ++ //MOD by DerPopo (rename) ++ void WriteSkinController( size_t pIndex); + + /// Writes the geometry library + void WriteGeometryLibrary(); +@@ -114,12 +119,22 @@ protected: + /// Writes the given mesh + void WriteGeometry( size_t pIndex); + ++ /// Writes the given anim mesh ++ //MOD by DerPopo ++ void WriteMorphGeometry( size_t pIndexMesh, size_t pIndexAnimMesh); ++ + //enum FloatDataType { FloatType_Vector, FloatType_TexCoord2, FloatType_TexCoord3, FloatType_Color, FloatType_Mat4x4, FloatType_Weight }; + // customized to add animation related type + enum FloatDataType { FloatType_Vector, FloatType_TexCoord2, FloatType_TexCoord3, FloatType_Color, FloatType_Mat4x4, FloatType_Weight, FloatType_Time }; + + /// Writes a float array of the given type + void WriteFloatArray( const std::string& pIdString, FloatDataType pType, const ai_real* pData, size_t pElementCount); ++ ++ /// Types of IDREF accessors ++ enum IDREFDataType { IDREFType_MorphTarget }; ++ /// Writes a IDREF array ++ //MOD by DerPopo ++ void WriteIDREFArray( const std::string& pIdString, IDREFDataType pType, const std::string* pData, size_t pElementCount); + + /// Writes the scene library + void WriteSceneLibrary(); +@@ -127,7 +142,7 @@ protected: + // customized, Writes the animation library + void WriteAnimationsLibrary(); + void WriteAnimationLibrary( size_t pIndex); +- std::string mFoundSkeletonRootNodeID = "skeleton_root"; // will be replaced by found node id in the WriteNode call. ++ std::string mFoundSkeletonRootNodeID; // will be replaced by found node id in the WriteNode call. + + /// Recursively writes the given node + void WriteNode( const aiScene* scene, aiNode* pNode); +@@ -145,6 +160,12 @@ protected: + return std::string( "meshId" ) + to_string(pIndex); + } + ++ /// Creates a mesh ID for the given anim mesh ++ //MOD by DerPopo ++ std::string GetAnimMeshId( size_t pIndexMesh, size_t pIndexAnimMesh) const { ++ return std::string( "meshId" ) + to_string(pIndexMesh) + std::string( "-morph" ) + to_string(pIndexAnimMesh); ++ } ++ + public: + /// Stringstream to write all output into + std::stringstream mOutput; +diff --git a/code/CreateAnimMesh.cpp b/code/CreateAnimMesh.cpp +index 094a414bf..ecfaf975a 100644 +--- a/code/CreateAnimMesh.cpp ++++ b/code/CreateAnimMesh.cpp +@@ -47,6 +47,8 @@ namespace Assimp { + aiAnimMesh *aiCreateAnimMesh(const aiMesh *mesh) + { + aiAnimMesh *animesh = new aiAnimMesh; ++ //MOD by DerPopo ++ animesh->mName = mesh->mName; + animesh->mVertices = NULL; + animesh->mNormals = NULL; + animesh->mTangents = NULL; +diff --git a/code/MMDPmxParser.cpp b/code/MMDPmxParser.cpp +index 970cbc31e..36fda4fe6 100644 +--- a/code/MMDPmxParser.cpp ++++ b/code/MMDPmxParser.cpp +@@ -39,6 +39,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ---------------------------------------------------------------------- + */ + #include ++#include + #include "MMDPmxParser.h" + #include "../contrib/utf8cpp/source/utf8.h" + #include "Exceptional.h" +diff --git a/contrib/zip/src/miniz.h b/contrib/zip/src/miniz.h +index 916fb1ff8..f59bb693c 100644 +--- a/contrib/zip/src/miniz.h ++++ b/contrib/zip/src/miniz.h +@@ -4426,7 +4426,10 @@ mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + MZ_FILE *pSrc_file = NULL; +- ++ ++ if ((int)level_and_flags < 0) ++ level_and_flags = MZ_DEFAULT_LEVEL; ++ level = level_and_flags & 0xF; + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) + return MZ_FALSE; + +diff --git a/include/assimp/mesh.h b/include/assimp/mesh.h +index c8648c778..d82dd660e 100644 +--- a/include/assimp/mesh.h ++++ b/include/assimp/mesh.h +@@ -383,6 +383,12 @@ struct aiAnimMesh + */ + float mWeight; + ++ //MOD by DerPopo ++ /** Name of the anim mesh. Anim meshes can be named, but this is not a ++ * requirement and leaving this field empty is totally fine. ++ **/ ++ C_STRUCT aiString mName; ++ + #ifdef __cplusplus + + aiAnimMesh() diff --git a/fetchcontent/astcenc-x86-32-popcntu64-patch-7e2a81ed5abc202c6f06be9302d193ba44a765c9.patch b/fetchcontent/astcenc-x86-32-popcntu64-patch-7e2a81ed5abc202c6f06be9302d193ba44a765c9.patch new file mode 100644 index 0000000..aa28d32 --- /dev/null +++ b/fetchcontent/astcenc-x86-32-popcntu64-patch-7e2a81ed5abc202c6f06be9302d193ba44a765c9.patch @@ -0,0 +1,12 @@ +diff --git a/Source/cmake_core.cmake b/Source/cmake_core.cmake +index 2ba1886..e6a35c3 100644 +--- a/Source/cmake_core.cmake ++++ b/Source/cmake_core.cmake +@@ -214,7 +214,6 @@ macro(astcenc_set_properties NAME) + ASTCENC_NEON=0 + ASTCENC_SSE=41 + ASTCENC_AVX=0 +- ASTCENC_POPCNT=1 + ASTCENC_F16C=0) + endif() + diff --git a/fetchcontent/crunch-legacy-patch-671a0648c8a440b4397f1d96ea5cf5700f830417.patch b/fetchcontent/crunch-legacy-patch-671a0648c8a440b4397f1d96ea5cf5700f830417.patch new file mode 100644 index 0000000..6df89be --- /dev/null +++ b/fetchcontent/crunch-legacy-patch-671a0648c8a440b4397f1d96ea5cf5700f830417.patch @@ -0,0 +1,39 @@ +diff --git a/crnlib/crn_threading_pthreads.cpp b/crnlib/crn_threading_pthreads.cpp +index 9ab08f8..4890a71 100644 +--- a/crnlib/crn_threading_pthreads.cpp ++++ b/crnlib/crn_threading_pthreads.cpp +@@ -34,6 +34,8 @@ namespace crnlib + #else + g_number_of_processors = 1; + #endif ++ // Don't use more threads than supported. ++ g_number_of_processors = math::minimum(g_number_of_processors, task_pool::cMaxThreads); + } + + crn_thread_id_t crn_get_current_thread_id() +diff --git a/crnlib/crn_threading_win32.cpp b/crnlib/crn_threading_win32.cpp +index ccef52a..e9b513f 100644 +--- a/crnlib/crn_threading_win32.cpp ++++ b/crnlib/crn_threading_win32.cpp +@@ -15,6 +15,8 @@ namespace crnlib + GetSystemInfo(&g_system_info); + + g_number_of_processors = math::maximum(1U, g_system_info.dwNumberOfProcessors); ++ // Don't use more threads than supported. ++ g_number_of_processors = math::minimum(g_number_of_processors, task_pool::cMaxThreads); + } + + crn_thread_id_t crn_get_current_thread_id() +diff --git a/crnlib/crn_timer.h b/crnlib/crn_timer.h +index 4894a70..73536e5 100644 +--- a/crnlib/crn_timer.h ++++ b/crnlib/crn_timer.h +@@ -49,7 +49,7 @@ namespace crnlib + timer m_tm; + + public: +- inline timed_scope(char* pName = "timed_scope") : m_pName(pName) { m_tm.start(); } ++ inline timed_scope(const char* pName = "timed_scope") : m_pName(pName) { m_tm.start(); } + + inline double get_elapsed_secs() const { return m_tm.get_elapsed_secs(); } + inline double get_elapsed_ms() const { return m_tm.get_elapsed_ms(); } diff --git a/fetchcontent/crunch-unity-patch-8708900eca8ec609d279270e72936258f81ddfb7.patch b/fetchcontent/crunch-unity-patch-8708900eca8ec609d279270e72936258f81ddfb7.patch new file mode 100644 index 0000000..7ec3ebb --- /dev/null +++ b/fetchcontent/crunch-unity-patch-8708900eca8ec609d279270e72936258f81ddfb7.patch @@ -0,0 +1,26 @@ +diff --git a/crnlib/crn_threading_pthreads.cpp b/crnlib/crn_threading_pthreads.cpp +index 9b1b812..3bc7fc0 100644 +--- a/crnlib/crn_threading_pthreads.cpp ++++ b/crnlib/crn_threading_pthreads.cpp +@@ -32,6 +32,8 @@ void crn_threading_init() { + #else + g_number_of_processors = 1; + #endif ++ // Don't use more threads than supported. ++ g_number_of_processors = math::minimum(g_number_of_processors, task_pool::cMaxThreads); + } + + crn_thread_id_t crn_get_current_thread_id() { +diff --git a/crnlib/crn_threading_win32.cpp b/crnlib/crn_threading_win32.cpp +index d1730a3..b8e84c7 100644 +--- a/crnlib/crn_threading_win32.cpp ++++ b/crnlib/crn_threading_win32.cpp +@@ -13,6 +13,8 @@ void crn_threading_init() { + GetSystemInfo(&g_system_info); + + g_number_of_processors = math::maximum(1U, g_system_info.dwNumberOfProcessors); ++ // Don't use more threads than supported. ++ g_number_of_processors = math::minimum(g_number_of_processors, task_pool::cMaxThreads); + } + + crn_thread_id_t crn_get_current_thread_id() { diff --git a/fetchcontent/libfgen-patch-071e5130f5286850eafe8de65f51e05604a02929.patch b/fetchcontent/libfgen-patch-071e5130f5286850eafe8de65f51e05604a02929.patch new file mode 100644 index 0000000..d0bdf0c --- /dev/null +++ b/fetchcontent/libfgen-patch-071e5130f5286850eafe8de65f51e05604a02929.patch @@ -0,0 +1,63 @@ +diff --git a/mutation.c b/mutation.c +index 92ec9fb..e6c4d0f 100644 +--- a/mutation.c ++++ b/mutation.c +@@ -74,7 +74,7 @@ static double Gamma + + double y = x; + int n = 0; +- bool arg_was_less_than_one = (y < 1.0); ++ unsigned char arg_was_less_than_one = (y < 1.0); + + // Add or subtract integers as necessary to bring y into (1,2) + // Will correct for this below +@@ -84,7 +84,7 @@ static double Gamma + } + else + { +- n = static_cast (floor(y)) - 1; // will use n later ++ n = (int)(floor(y)) - 1; // will use n later + y -= n; + } + +diff --git a/random.c b/random.c +index 4666fbf..1b7ba17 100644 +--- a/random.c ++++ b/random.c +@@ -281,8 +281,8 @@ double fgen_random_d(FgenRNG *rng, double range) + // Scaling the 2^32 integers to [0, 1) (which has good precision in the + // double format) maintains precision when adding up the lower and higher + // order components. +- return ((double)Random32(rng) * (1.0d / pow(2.0d, 32)) + +- (double)Random32(rng) * (1.0d / pow(2.0d, 64))) * range; ++ return ((double)Random32(rng) * (1.0 / pow(2.0, 32)) + ++ (double)Random32(rng) * (1.0 / pow(2.0, 64))) * range; + } + + /** +@@ -307,12 +307,12 @@ float fgen_random_f_very_low_precision(FgenRNG *rng, float range) + + float fgen_random_d_low_precision(FgenRNG *rng, float range) + { +- return (double)Random32(rng) * (1.0f / pow(2.0d, 32)) * range; ++ return (double)Random32(rng) * ((float)1.0 / pow((double)2.0, 32)) * range; + } + + float fgen_random_d_high_precision(FgenRNG *rng, float range) + { +- if (range <= 1.00001d) ++ if (range <= (double)1.00001) + // When range <= 1.0, the standard high-precision function is already + // optimal. + return fgen_random_d(rng, range); +@@ -324,8 +324,8 @@ float fgen_random_d_high_precision(FgenRNG *rng, float range) + // That is, exp(scale * r0 + scale * r1 * 2^32) <= high_value, + // <-> scale * r0 + scale * r1 * 2^32 <= log(high_value) + // so that scale = log(high_value) / pow(2.0d, 32). +- const double scale_factor0 = log(high_value) / pow(2.0d, 32); +- const double scale_factor1 = log(high_value) / pow(2.0d, 64); ++ const double scale_factor0 = log(high_value) / pow((double)2.0, 32); ++ const double scale_factor1 = log(high_value) / pow((double)2.0, 64); + return log( + exp((double)Random32(rng) * scale_factor0) * + exp((double)Random32(rng) * scale_factor1) diff --git a/fetchcontent/libsquish-1.15.tgz b/fetchcontent/libsquish-1.15.tgz new file mode 100644 index 0000000000000000000000000000000000000000..466ecfab4494db5d5958994464fa74aaecc23269 GIT binary patch literal 59199 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

vB+ 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

u!v1g`>Q;cSw8`hR+}1% z7?s67BZNy~-e4gVxt?jT^6JZV{~lOPV5=)3qYLO|9+ooIt744jB{@50>6|!xYMitWka16yhMi#0Uds!F0WdosAlqOxs zAFoPJ)<*ofAD=AJyTu%+AlhFXNz;aMds$GZw1-LM-~aWtnU(1|Q1KFMufS)C_CF;n zULFpv!lXs3;J`9$AsCGW0*#yujY_5TruFh&MK&DS5XzDxWK>~yx6><&q?T8cANIUH zBvF+<)@Q%roezL$?=F6?>+wx?##(40IAM9mZfi4rttj$cpGT=8-n>4mVjcy%0i)*< zS1(L?_0SRd`}xO^E3-l9;qp60k!M=wvm<_d+3a7ap|Lx2+aZpV``izQFbou7g>{?=$gcg(eN%;CZ=+0O;y4RjM zbG9UlNXxk@PvP9Cp9DPKm3J_R$^B=uu*+%}2~>uqSf&R^Y7C>#?kOOv-{VNduD>CUIlN?h$@sv%#x(9+$bSaM}? z!}9fdKBNQlkzc&(x9$89;sl&N+KCHL9*zm%U(*Nh5k%4JNZp4bV$bDv>RNfT>*MW~ z{QNku6W^HQ;O2JC@|=L5@`3h3Z+6}fInfdELZ7LluIQ5`N9i=0e)^nY1paEoUW}{c z-I#4W?kA2ri_419oAr2f(KHsDAh`pHb3X<1&8fOfKCv7>hQ zNv`EfL8)$g@SLhl6Fya1<*>j$kqj5%`(m>sfV#7PONjw7Z}3;8M7~I(Uu-V(h=+oG zur(xwcytEt&^ndp9LyUb8XLtncPn-}HcP)l zIlcK7dl&m6LM{{T)$wC%P4fZX^80D$m5b_wdt65RXL?_@2kZEtMQN(r&q}k?9f;Y} zbs{LIxx%`91b`Dhn)lZ?A)XOOs|9skVhOJOjm4}L$ z#C%b>B5!$hL!NCtker@3L!O|+ljEil15F84#-y(jY)y@Py_}l1dA)e_Y-QPWK*2AE z!xcOEctFw_k8hrob6r}4-^7_H#3PjM)rLT9$LYs~{D!nunvN;P?V$u14Ax{%K5sTV zvoA+f4~7G>9IZXC45`*I@(RGe{Adl&*e7xl);JK3Dk3$w^$#@w)mat|Ql9gzqfpLv zeY;fsK&|DO#zu}3jcd-?0%bQLJJ%1Tp8+oD7~yusrYNEIgoW?72i_`TEiikt zO7+hS=J6QipQEzha3~Eq0CCuu-8H970Q|e=Nu323>bS(l9#k8Xi-`-~3Sp@8(SFIx zme^6O^{2;NT`xjjaIzit4K1FIj+;T@()ublB&02O?z6#n%{?)(w(5jI>3 z_jZ3arGhVyMYLa8d;Hflz_ZQde=cC^t~~%nVNGdpS1{=FKWhTWUDRI?$CdTghp_SX z*LFhi|7-aFQ2sB#|M#g#v86+jhYJ|A@c@hjC&-U={rzyfF1OG96U^<8*pGsQ&(MuO(Nf z{u{H + #include +-#include +-#include ++//#include ++//#include + #include +-#include +-#include +-#include +-#include +-#include ++//#include ++//#include ++//#include ++//#include ++//#include + #include + #include + #include +diff --git a/lib/hsluv-c/src/hsluv.c b/lib/hsluv-c/src/hsluv.c +index 7c09b38..268d80e 100644 +--- a/lib/hsluv-c/src/hsluv.c ++++ b/lib/hsluv-c/src/hsluv.c +@@ -30,7 +30,7 @@ + + #include + #include +- ++#include + + typedef struct Triplet_tag Triplet; + struct Triplet_tag { +@@ -210,7 +210,9 @@ y2l(double y) + if(y <= epsilon) + return y * kappa; + else +- return 116.0 * cbrt(y) - 16.0; ++ { ++ return 116.0 * cbrt(y) - 16.0; ++ } + } + + static double +@@ -246,6 +248,7 @@ xyz2luv(Triplet* in_out) + static void + luv2xyz(Triplet* in_out) + { ++ double var_u, var_v, y, x, z; + if(in_out->a <= 0.00000001) { + /* Black will create a divide-by-zero error. */ + in_out->a = 0.0; +@@ -254,11 +257,11 @@ luv2xyz(Triplet* in_out) + return; + } + +- double var_u = in_out->b / (13.0 * in_out->a) + ref_u; +- double var_v = in_out->c / (13.0 * in_out->a) + ref_v; +- double y = l2y(in_out->a); +- double x = -(9.0 * y * var_u) / ((var_u - 4.0) * var_v - var_u * var_v); +- double z = (9.0 * y - (15.0 * var_v * y) - (var_v * x)) / (3.0 * var_v); ++ var_u = in_out->b / (13.0 * in_out->a) + ref_u; ++ var_v = in_out->c / (13.0 * in_out->a) + ref_v; ++ y = l2y(in_out->a); ++ x = -(9.0 * y * var_u) / ((var_u - 4.0) * var_v - var_u * var_v); ++ z = (9.0 * y - (15.0 * var_v * y) - (var_v * x)) / (3.0 * var_v); + in_out->a = x; + in_out->b = y; + in_out->c = z; +diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt +index ca7fd1b..5d61412 100644 +--- a/src/CMakeLists.txt ++++ b/src/CMakeLists.txt +@@ -8,8 +8,8 @@ add_library(mCtrl SHARED + ../include/mCtrl/_common.h + ../include/mCtrl/_defs.h + anim.c anim.h +- button.c button.h ../include/mCtrl/button.h +- chart.c chart.h ../include/mCtrl/chart.h ++ #button.c button.h ../include/mCtrl/button.h ++ #chart.c chart.h ../include/mCtrl/chart.h + color.c color.h + compat.c compat.h + debug.c debug.h +@@ -17,14 +17,14 @@ add_library(mCtrl SHARED + doublebuffer.c doublebuffer.h + dsa.c dsa.h + dwm.c dwm.h +- expand.c expand.h ../include/mCtrl/expand.h ++ #expand.c expand.h ../include/mCtrl/expand.h + generic.c generic.h +- grid.c grid.h ../include/mCtrl/grid.h +- html.c html.h ../include/mCtrl/html.h +- imgview.c imgview.h ../include/mCtrl/imgview.h ++ #grid.c grid.h ../include/mCtrl/grid.h ++ #html.c html.h ../include/mCtrl/html.h ++ #imgview.c imgview.h ../include/mCtrl/imgview.h + labeledit.c labeledit.h + mditab.c mditab.h ../include/mCtrl/mditab.h +- menubar.c menubar.h ../include/mCtrl/menubar.h ++ #menubar.c menubar.h ../include/mCtrl/menubar.h + misc.c misc.h + module.c module.h + mousedrag.c mousedrag.h +diff --git a/src/dsa.c b/src/dsa.c +index 342fa67..8930afc 100644 +--- a/src/dsa.c ++++ b/src/dsa.c +@@ -108,11 +108,12 @@ dsa_insert_raw(dsa_t* dsa, WORD index) + if(dsa->size >= dsa->capacity) { + BYTE* buffer; + size_t sz = (size_t)dsa->size * (size_t)dsa->item_size; +- +- if(sz > DSA_BIGBUFFER_SIZE) { +- sz += DSA_BIGBUFFER_BOOKKEEPING_PADDING + dsa->item_size - 1; +- sz %= dsa->item_size; +- } ++ ++ //The BIGBUFFER optimizations lead to buffers too small to cover the requested amount of items, hence heap overflows occur once they kick in. ++ //if(sz > DSA_BIGBUFFER_SIZE) { ++ // sz += DSA_BIGBUFFER_BOOKKEEPING_PADDING + dsa->item_size - 1; ++ // sz %= dsa->item_size; ++ //} + + /* Make the buffer about twice as large, but round up to power of two. */ + sz = 2 * sz; +@@ -126,10 +127,10 @@ dsa_insert_raw(dsa_t* dsa, WORD index) + /* Make sure at least 4 items fit inside. */ + sz = MC_MAX(sz, 4 * dsa->item_size); + +- if(sz > DSA_BIGBUFFER_SIZE) { +- sz -= DSA_BIGBUFFER_BOOKKEEPING_PADDING; +- sz %= dsa->item_size; +- } ++ //if(sz > DSA_BIGBUFFER_SIZE) { ++ // sz -= DSA_BIGBUFFER_BOOKKEEPING_PADDING; ++ // sz %= dsa->item_size; ++ //} + + DSA_TRACE("dsa_insert_raw: Capacity growing: %d -> %d", + (int) dsa->capacity, (int) (sz / dsa->item_size)); +@@ -200,20 +201,20 @@ dsa_remove(dsa_t* dsa, WORD index, dsa_dtor_t dtor_func) + BYTE* buffer; + + sz = (size_t)dsa->capacity * (size_t)dsa->item_size; +- if(sz > DSA_BIGBUFFER_SIZE) { +- sz += DSA_BIGBUFFER_BOOKKEEPING_PADDING + dsa->item_size - 1; +- sz %= dsa->item_size; +- } ++ //if(sz > DSA_BIGBUFFER_SIZE) { ++ // sz += DSA_BIGBUFFER_BOOKKEEPING_PADDING + dsa->item_size - 1; ++ // sz %= dsa->item_size; ++ //} + + sz = sz / 2; + + /* Make sure at least 4 items fit inside. */ + sz = MC_MAX(sz, 4 * dsa->item_size); + +- if(sz > DSA_BIGBUFFER_SIZE) { +- sz -= DSA_BIGBUFFER_BOOKKEEPING_PADDING; +- sz %= dsa->item_size; +- } ++ //if(sz > DSA_BIGBUFFER_SIZE) { ++ // sz -= DSA_BIGBUFFER_BOOKKEEPING_PADDING; ++ // sz %= dsa->item_size; ++ //} + + MC_ASSERT((size_t)dsa->size * (size_t)dsa->item_size < sz); + +diff --git a/src/mCtrl.def b/src/mCtrl.def +index 0ef9bfd..5815a8c 100644 +--- a/src/mCtrl.def ++++ b/src/mCtrl.def +@@ -9,10 +9,10 @@ EXPORTS + mcBufferedPaintRenderAnimation + mcBufferedPaintStopAllAnimations + mcBufferedPaintUnInit +- mcButton_Initialize +- mcButton_Terminate +- mcChart_Initialize +- mcChart_Terminate ++ ;mcButton_Initialize ++ ;mcButton_Terminate ++ ;mcChart_Initialize ++ ;mcChart_Terminate + mcCloseThemeData + mcCreateDialogIndirectParamA + mcCreateDialogIndirectParamW +@@ -34,8 +34,8 @@ EXPORTS + mcEndBufferedAnimation + mcEndBufferedPaint + mcEndPanningFeedback +- mcExpand_Initialize +- mcExpand_Terminate ++ ;mcExpand_Initialize ++ ;mcExpand_Terminate + mcGetBufferedPaintBits + mcGetBufferedPaintDC + mcGetBufferedPaintTargetDC +@@ -73,16 +73,16 @@ EXPORTS + mcGetThemeTextMetrics + mcGetThemeTransitionDuration + mcGetWindowTheme +- mcGrid_Initialize +- mcGrid_Terminate ++ ;mcGrid_Initialize ++ ;mcGrid_Terminate + mcHitTestThemeBackground +- mcHtml_Initialize +- mcHtml_Terminate +- mcImgView_Initialize +- mcImgView_Terminate ++ ;mcHtml_Initialize ++ ;mcHtml_Terminate ++ ;mcImgView_Initialize ++ ;mcImgView_Terminate + mcIsAppThemed + mcIsCompositionActive +- mcIsMenubarMessage ++ ;mcIsMenubarMessage + mcIsThemeActive + mcIsThemeBackgroundPartiallyTransparent + mcIsThemeDialogTextureEnabled +@@ -90,9 +90,9 @@ EXPORTS + mcMditab_DefWindowProc + mcMditab_Initialize + mcMditab_Terminate +- mcMenubar_HandleRebarChevronPushed +- mcMenubar_Initialize +- mcMenubar_Terminate ++ ;mcMenubar_HandleRebarChevronPushed ++ ;mcMenubar_Initialize ++ ;mcMenubar_Terminate + mcOpenThemeData + mcOpenThemeDataEx + mcSetThemeAppProperties +diff --git a/src/mditab.c b/src/mditab.c +index a0452e4..4e747bc 100644 +--- a/src/mditab.c ++++ b/src/mditab.c +@@ -92,11 +92,13 @@ static DWORD mditab_wdl_flags = WD_INIT_IMAGEAPI | WD_INIT_STRINGAPI; + #define BTNID_RSCROLL 1 + #define BTNID_LIST 2 + #define BTNID_CLOSE 3 ++#define BTNID_OPEN 4 + + #define BTNMASK_LSCROLL (1 << BTNID_LSCROLL) + #define BTNMASK_RSCROLL (1 << BTNID_RSCROLL) + #define BTNMASK_LIST (1 << BTNID_LIST) + #define BTNMASK_CLOSE (1 << BTNID_CLOSE) ++#define BTNMASK_OPEN (1 << BTNID_OPEN) + + #define BTNMASK_SCROLL (BTNMASK_LSCROLL | BTNMASK_RSCROLL) + +@@ -119,6 +121,7 @@ typedef struct mditab_item_tag mditab_item_t; + struct mditab_item_tag { + TCHAR* text; + LPARAM lp; ++ DWORD hide_close : 1; + SHORT img; + USHORT ideal_width; /* Cached result of mditab_item_ideal_width() */ + int x0; /* Relative to mditab_t::area_margin0. */ +@@ -138,7 +141,8 @@ struct mditab_tag { + anim_t* animation; + dsa_t items; + DWORD style : 16; +- DWORD btn_mask : 4; ++ DWORD custom_style : 1; ++ DWORD btn_mask : 5; + DWORD focus : 1; + DWORD no_redraw : 1; + DWORD rtl : 1; +@@ -148,11 +152,13 @@ struct mditab_tag { + DWORD dirty_layout : 1; + DWORD dirty_scroll : 1; + DWORD btn_pressed : 1; /* Button ABS(item_hot) is pressed. */ ++ DWORD btn_pressed_cold : 1; /* Button is pressed but cursor is not over it. */ + DWORD scrolling_to_item : 1; /* If set, scroll_x_desired is item index. */ + DWORD dwm_extend_frame : 1; + DWORD mouse_captured : 1; + DWORD itemdrag_considering : 1; + DWORD itemdrag_started : 1; ++ DWORD hot_itemclose : 1; + int scroll_x; + int scroll_x_desired; + int scroll_x_max; +@@ -178,6 +184,7 @@ typedef struct mditab_item_layout_tag mditab_item_layout_t; + struct mditab_item_layout_tag { + WD_RECT icon_rect; + WD_RECT text_rect; ++ WD_RECT close_rect; + }; + + +@@ -292,6 +299,7 @@ mditab_get_dispinfo(mditab_t* mditab, int index, mditab_item_t* item, + info.item.pszText = NULL; + info.item.iImage = MC_I_IMAGENONE; + info.item.lParam = item->lp; ++ info.item.bDisableClose = item->hide_close ? TRUE : FALSE; + MC_SEND(mditab->notify_win, WM_NOTIFY, 0, &info); + + /* If needed, convert the text from parent to the expected format. */ +@@ -314,6 +322,11 @@ mditab_free_dispinfo(mditab_t* mditab, mditab_item_t* item, mditab_dispinfo_t* d + free(di->text); + } + ++static inline int ++mditab_button_size(const RECT* client) ++{ ++ return mc_height(client) - 4; ++} + + static inline USHORT + mditab_item_current_width(mditab_t* mditab, WORD index) +@@ -346,10 +359,16 @@ mditab_item_ideal_width(mditab_t* mditab, WORD index) + } + + if(di.text != NULL) { +- w += ceilf(wdStringWidth(NULL, mditab->font, di.text)); ++ w += ceilf(wdStringWidth(NULL, mditab->font, di.text)) + ceilf(wdStringWidth(NULL, mditab->font, TEXT("..."))); + w += MDITAB_ITEM_PADDING; + } + ++ if((mditab->style & MC_MTS_CBMASK) == MC_MTS_CBONEACHTAB || (mditab->style & MC_MTS_CBMASK) == MC_MTS_CBONACTIVETAB) { ++ RECT client; ++ GetClientRect(mditab->win, &client); ++ w += mditab_button_size(&client); ++ } ++ + item->ideal_width = w; + mditab_free_dispinfo(mditab, item, &di); + } +@@ -369,12 +388,6 @@ mditab_reset_ideal_widths(mditab_t* mditab) + } + } + +-static inline int +-mditab_button_size(const RECT* client) +-{ +- return mc_height(client) - 4; +-} +- + static void + mditab_button_rect(mditab_t* mditab, int btn_id, RECT* rect) + { +@@ -395,6 +408,14 @@ mditab_button_rect(mditab_t* mditab, int btn_id, RECT* rect) + } + + x0 = client.right - btn_size; ++ if(btn_id == BTNID_OPEN) { ++ mc_rect_set(rect, x0, y0, x0 + btn_size, y0 + btn_size); ++ return; ++ } ++ ++ if(mditab->btn_mask & (1 << BTNID_OPEN)) ++ x0 -= btn_size; ++ + if(btn_id == BTNID_CLOSE) { + mc_rect_set(rect, x0, y0, x0 + btn_size, y0 + btn_size); + return; +@@ -417,7 +438,7 @@ mditab_button_rect(mditab_t* mditab, int btn_id, RECT* rect) + + static void + mditab_setup_item_layout(mditab_t* mditab, mditab_dispinfo_t* di, int x0, int y0, +- int x1, int y1, mditab_item_layout_t* layout) ++ int x1, int y1, mditab_item_layout_t* layout, BOOL is_active) + { + int contents_x = x0 + MDITAB_ITEM_PADDING; + +@@ -433,9 +454,23 @@ mditab_setup_item_layout(mditab_t* mditab, mditab_dispinfo_t* di, int x0, int y0 + contents_x += icon_w + MDITAB_ITEM_ICON_MARGIN; + } + ++ if((mditab->style & MC_MTS_CBMASK) == MC_MTS_CBONEACHTAB || (mditab->style & MC_MTS_CBMASK) == MC_MTS_CBONACTIVETAB) { ++ RECT client; ++ int btn_size; ++ ++ GetClientRect(mditab->win, &client); ++ btn_size = mditab_button_size(&client); ++ layout->close_rect.x0 = x1 - btn_size; ++ layout->close_rect.x1 = x1; ++ layout->close_rect.y0 = y0 + (y1 - y0 - btn_size + 1) / 2; ++ layout->close_rect.y1 = layout->close_rect.y0 + btn_size; ++ if ((mditab->style & MC_MTS_CBMASK) == MC_MTS_CBONEACHTAB || is_active) ++ x1 = layout->close_rect.x0; ++ } ++ + if(di->text != NULL) { + SIZE size; +- ++ + mc_font_size(mditab->gdi_font, &size, TRUE); + layout->text_rect.x0 = contents_x; + layout->text_rect.y0 = (y0 + y1 - size.cy) / 2.0f; +@@ -508,12 +543,17 @@ mditab_hit_test_item(mditab_t* mditab, MC_MTHITTESTINFO* hti, + mditab_item_layout_t layout; + + di.text = NULL; /* <-- little hack: we only need layout.icon_rect. */ +- mditab_setup_item_layout(mditab, &di, x0, y0, x1, y1, &layout); ++ mditab_setup_item_layout(mditab, &di, x0, y0, x1, y1, &layout, TRUE); + + if(mditab->img_list != NULL && + layout.icon_rect.x0 <= x && x < layout.icon_rect.x1 && + layout.icon_rect.y0 <= y && y < layout.icon_rect.y1) + hti->flags = MC_MTHT_ONITEMICON; ++ else if(((mditab->style & MC_MTS_CBMASK) == MC_MTS_CBONEACHTAB || ++ ((mditab->style & MC_MTS_CBMASK) == MC_MTS_CBONACTIVETAB && mditab->item_selected == index)) && ++ layout.close_rect.x0 <= x && x < layout.close_rect.x1 && ++ layout.close_rect.y0 <= y && y < layout.close_rect.y1) ++ hti->flags = MC_MTHT_ONITEMCLOSEBUTTON; + else + hti->flags = MC_MTHT_ONITEMLABEL; + } +@@ -526,7 +566,7 @@ mditab_hit_test(mditab_t* mditab, MC_MTHITTESTINFO* hti, BOOL want_hti_item_flag + { + static const UINT btn_map[] = { + MC_MTHT_ONLEFTSCROLLBUTTON, MC_MTHT_ONRIGHTSCROLLBUTTON, +- MC_MTHT_ONLISTBUTTON, MC_MTHT_ONCLOSEBUTTON ++ MC_MTHT_ONLISTBUTTON, MC_MTHT_ONCLOSEBUTTON, MC_MTHT_ONOPENBUTTON + }; + + RECT client; +@@ -625,7 +665,7 @@ mditab_invalidate_item(mditab_t* mditab, WORD index) + + if(mditab->no_redraw) + return; +- ++ + item = mditab_item(mditab, index); + + GetClientRect(mditab->win, &rect); +@@ -650,19 +690,20 @@ mditab_invalidate_button(mditab_t* mditab, int btn_id) + } + + static void +-mditab_set_hot(mditab_t* mditab, SHORT hot, BOOL is_pressed) ++mditab_set_hot(mditab_t* mditab, SHORT hot, BOOL is_pressed, BOOL is_itemclose) + { +- if(hot == mditab->item_hot && is_pressed == mditab->btn_pressed) ++ if(hot == mditab->item_hot && is_pressed == mditab->btn_pressed && is_itemclose == mditab->hot_itemclose) + return; + + if(mditab->item_hot != ITEM_HOT_NONE) { + if(mditab->item_hot >= 0) +- mditab_invalidate_item(mditab, mditab->item_hot); ++ mditab_invalidate_item(mditab, mditab->item_hot); + else + mditab_invalidate_button(mditab, mditab_hot_button(mditab)); + } + + mditab->item_hot = hot; ++ mditab->hot_itemclose = is_itemclose; + mditab->btn_pressed = is_pressed; + + if(mditab->item_hot != ITEM_HOT_NONE) { +@@ -678,12 +719,12 @@ mditab_set_hot(mditab_t* mditab, SHORT hot, BOOL is_pressed) + } + } + +-static inline void mditab_set_hot_item(mditab_t* mditab, WORD hot_item) +- { mditab_set_hot(mditab, hot_item, FALSE); } ++static inline void mditab_set_hot_item(mditab_t* mditab, WORD hot_item, BOOL is_itemclose) ++ { mditab_set_hot(mditab, hot_item, FALSE, is_itemclose); } + static inline void mditab_set_hot_button(mditab_t* mditab, int btn_id, BOOL is_pressed) +- { mditab_set_hot(mditab, -(SHORT)btn_id - 1, is_pressed); } ++ { mditab_set_hot(mditab, -(SHORT)btn_id - 1, is_pressed, FALSE); } + static inline void mditab_reset_hot(mditab_t* mditab) +- { mditab_set_hot(mditab, ITEM_HOT_NONE, FALSE); } ++ { mditab_set_hot(mditab, ITEM_HOT_NONE, FALSE, FALSE); } + + static void + mditab_set_item_order(mditab_t* mditab, WORD old_index, WORD new_index) +@@ -836,20 +877,21 @@ mditab_mouse_move(mditab_t* mditab, int x, int y) + { + int index; + MC_MTHITTESTINFO hti; +- +- if(mditab->btn_pressed) +- return; ++ BOOL coldnow = mditab->btn_pressed_cold; + + /* Consider start of item dragging. */ +- if(mditab->itemdrag_considering) { ++ if(mditab->itemdrag_considering && !mditab->btn_pressed) { + MC_ASSERT(!mditab->itemdrag_started); + + switch(mousedrag_consider_start(mditab->win, x, y)) { + case MOUSEDRAG_STARTED: + mditab->itemdrag_considering = FALSE; + mditab->itemdrag_started = TRUE; +- SetCapture(mditab->win); +- mditab->mouse_captured = TRUE; ++ if (!mditab->mouse_captured) ++ { ++ SetCapture(mditab->win); ++ mditab->mouse_captured = TRUE; ++ } + break; + + case MOUSEDRAG_CONSIDERING: +@@ -858,12 +900,18 @@ mditab_mouse_move(mditab_t* mditab, int x, int y) + + case MOUSEDRAG_CANCELED: + mditab->itemdrag_considering = FALSE; ++ if (mditab->mouse_captured) ++ { ++ mditab->mouse_captured = FALSE; ++ ReleaseCapture(); ++ mc_send_notify(mditab->notify_win, mditab->win, NM_RELEASEDCAPTURE); ++ } + break; + } + } + + /* Handle drag-and-drop. */ +- if(mditab->itemdrag_started) { ++ if(mditab->itemdrag_started && !mditab->btn_pressed) { + MC_ASSERT(!mditab->itemdrag_considering); + mditab_do_drag(mditab, x, y); + return; +@@ -871,28 +919,63 @@ mditab_mouse_move(mditab_t* mditab, int x, int y) + + hti.pt.x = x; + hti.pt.y = y; +- index = mditab_hit_test(mditab, &hti, FALSE); ++ index = mditab_hit_test(mditab, &hti, TRUE); + + if(index >= 0) { +- mditab_set_hot_item(mditab, index); ++ mditab_item_t *item = mditab_item(mditab, index); ++ if(mditab->btn_pressed) { ++ if (index != mditab->item_hot || (hti.flags & MC_MTHT_ONITEMCLOSEBUTTON) == 0 || item->hide_close) ++ coldnow = TRUE; ++ else ++ coldnow = FALSE; ++ } ++ else { ++ mditab_set_hot_item(mditab, index, (hti.flags & MC_MTHT_ONITEMCLOSEBUTTON) != 0 && !item->hide_close); ++ } + } else { + int btn_id = -1; ++ mditab_item_t *item = NULL; ++ if (mditab_count(mditab) > 0 && ++ mditab->item_selected >= 0 && mditab->item_selected < mditab_count(mditab)) ++ item = mditab_item(mditab, mditab->item_selected); + + switch(hti.flags & MC_MTHT_ONBUTTON) { + case MC_MTHT_ONLEFTSCROLLBUTTON: btn_id = BTNID_LSCROLL; break; + case MC_MTHT_ONRIGHTSCROLLBUTTON: btn_id = BTNID_RSCROLL; break; + case MC_MTHT_ONLISTBUTTON: btn_id = BTNID_LIST; break; +- case MC_MTHT_ONCLOSEBUTTON: btn_id = BTNID_CLOSE; break; ++ case MC_MTHT_ONCLOSEBUTTON: btn_id = BTNID_CLOSE; ++ if (item && item->hide_close) ++ btn_id = -1; ++ break; ++ case MC_MTHT_ONOPENBUTTON: btn_id = BTNID_OPEN; break; + } +- +- if(btn_id >= 0) +- mditab_set_hot_button(mditab, btn_id, FALSE); +- else +- mditab_reset_hot(mditab); +- } ++ if(mditab->btn_pressed) { ++ if (btn_id == -1 || mditab_hot_button(mditab) != btn_id) ++ coldnow = TRUE; ++ else ++ coldnow = FALSE; ++ } ++ else { ++ if(btn_id >= 0) ++ mditab_set_hot_button(mditab, btn_id, FALSE); ++ else ++ mditab_reset_hot(mditab); ++ } ++ } ++ ++ if(coldnow != mditab->btn_pressed_cold) { /* Redraw if the cold state changed for a pressed button */ ++ mditab->btn_pressed_cold = coldnow; ++ if (mditab->item_hot >= 0) ++ mditab_invalidate_item(mditab, mditab->item_hot); ++ else { ++ int btn_id = mditab_hot_button(mditab); ++ if (btn_id >= 0) ++ mditab_invalidate_button(mditab, btn_id); ++ } ++ } + + /* Ask for WM_MOUSELEAVE. */ +- if(mditab->item_hot != ITEM_HOT_NONE && !mditab->tracking_leave) { ++ if(mditab->item_hot != ITEM_HOT_NONE && !mditab->tracking_leave && !mditab->btn_pressed) { + mc_track_mouse(mditab->win, TME_LEAVE); + mditab->tracking_leave = TRUE; + } +@@ -1007,6 +1090,9 @@ mditab_update_layout(mditab_t* mditab, BOOL refresh) + if((mditab->style & MC_MTS_CBMASK) == MC_MTS_CBONTOOLBAR) + btn_mask |= BTNMASK_CLOSE; + ++ if(mditab->custom_style & MC_MTCS_OPENBTN) ++ btn_mask |= BTNMASK_OPEN; ++ + if(((mditab->style & MC_MTS_TLBMASK) == MC_MTS_TLBALWAYS) || + ((mditab->style & MC_MTS_TLBMASK) == MC_MTS_TLBONSCROLL && need_scroll)) + btn_mask |= BTNMASK_LIST; +@@ -1027,6 +1113,8 @@ again_with_scroll: + area_margin1 += btn_size; + if(btn_mask & BTNMASK_CLOSE) + area_margin1 += btn_size; ++ if(btn_mask & BTNMASK_OPEN) ++ area_margin1 += btn_size; + + area_width = mc_width(&client) - area_margin0 - area_margin1; + +@@ -1080,9 +1168,8 @@ again_without_animation: + mditab_item_t* dragged = mditab_item(mditab, mousedrag_index); + USHORT w_dragged = dragged->x1 - dragged->x0; + UINT x = 0; +- BOOL found_gap = FALSE; +- +- /* Move all items out of the way. */ ++ UINT offset = 0; ++ + for(i = 0; i < n; i++) { + mditab_item_t* item = mditab_item(mditab, i); + USHORT w; +@@ -1093,14 +1180,38 @@ again_without_animation: + w = item->x1 - item->x0; + item->x0 = x; + item->x1 = x + w; +- +- if(!found_gap && x + (w+1)/2 > dragged->x0) { +- item->x0 += w_dragged; +- item->x1 += w_dragged; +- found_gap = TRUE; +- } +- + x = item->x1; ++ } ++ if(mditab->style & MC_MTS_RALIGNITEMS) { /* Right align if the visible area is not filled */ ++ if (x + w_dragged < area_width) ++ offset = area_width - x; ++ } ++ /* Move all items out of the way. */ ++ for(i = 0; i < n; i++) { ++ mditab_item_t* item = mditab_item(mditab, i); ++ USHORT w; ++ if(item == dragged) ++ continue; ++ ++ w = item->x1 - item->x0; ++ item->x0 += offset; //Apply any potential ++ item->x1 += offset; ++ x = item->x0; ++ ++ if (offset == 0 && x + (w+1)/2 > dragged->x0) /* Right align is inactive */ ++ { ++ //The position of the first item in the loop where this condition applies ++ // is where the dragged item would be inserted. ++ item->x0 += w_dragged; //Move this item to the right. ++ item->x1 += w_dragged; ++ } ++ else if (offset != 0 && x + (w+1)/2 <= dragged->x1) /* Right align is active */ ++ { ++ //The position of the last item in the loop where this condition applies ++ // is where the dragged item would be inserted. ++ item->x0 -= w_dragged; //Move this item to the left. ++ item->x1 -= w_dragged; ++ } + } + } else if(min_width == 0) { /* Cases #1, #2, #3, #4 */ + mditab_update_item_widths(mditab, 0, n, def_width); +@@ -1153,6 +1264,19 @@ again_without_animation: + mditab_update_item_widths(mditab, w_extra, n, w_base); + } + } ++ if((mditab->style & MC_MTS_RALIGNITEMS) ++ && mditab_item(mditab, 0)->x0 == 0 ++ && !mditab->itemdrag_started) { /* Right align if the visible area is not filled */ ++ mditab_item_t* last_item = mditab_item(mditab, n - 1); ++ if (last_item->x1 < area_width) { ++ UINT offset = area_width - last_item->x1; ++ for (i = 0; i < n; i++) { ++ mditab_item_t* item = mditab_item(mditab, i); ++ item->x0 += offset; ++ item->x1 += offset; ++ } ++ } ++ } + } + + /* Animate, i.e. compute geometry for a new animation frame. */ +@@ -1177,10 +1301,21 @@ again_without_animation: + + /* Scrolling */ + scroll_x = mditab->scroll_x; +- if(n > 0 && mditab_item(mditab, n-1)->x1 > area_width) +- mditab->scroll_x_max = mditab_item(mditab, n-1)->x1 - area_width; +- else +- mditab->scroll_x_max = 0; ++ { ++ UINT rightmost_x1 = (n > 0) ? mditab_item(mditab, n-1)->x1 : 0; ++ if(n > 1 && mditab->itemdrag_started) { /* Special behaviour for drag: keep the width of the dragged item in mind */ ++ mditab_item_t* dragged = mditab_item(mditab, mousedrag_index); ++ //The dragged item may be the last item even if it is not rightmost anymore. ++ mditab_item_t* last_item = mditab_item(mditab, (mousedrag_index == n-1) ? (n-2) : (n-1)); ++ rightmost_x1 = last_item->x1; ++ if (dragged->x0 > last_item->x0) /* If the space for the dragged item is rightmost, .. */ ++ rightmost_x1 += dragged->x1 - dragged->x0; /* .. add its width to the scrollable area */ ++ } ++ if(rightmost_x1 > area_width) ++ mditab->scroll_x_max = rightmost_x1 - area_width; ++ else ++ mditab->scroll_x_max = 0; ++ } + + if(mditab->scrolling_to_item) { + mditab_item_t* item = mditab_item(mditab, mditab->scroll_x_desired); +@@ -1267,7 +1402,8 @@ mditab_do_paint_button(mditab_t* mditab, mditab_xdraw_ctx_t* ctx, int btn_id, + { 0.6f, 0.3f, 0.4f, 0.5f, 0.4f, 0.5f, 0.6f, 0.7f }, + { 0.4f, 0.3f, 0.6f, 0.5f, 0.6f, 0.5f, 0.4f, 0.7f }, + { 0.3f, 0.4f, 0.5f, 0.6f, 0.5f, 0.6f, 0.7f, 0.4f }, +- { 0.3f, 0.3f, 0.7f, 0.7f, 0.3f, 0.7f, 0.7f, 0.3f } ++ { 0.3f, 0.3f, 0.7f, 0.7f, 0.3f, 0.7f, 0.7f, 0.3f }, ++ { 0.25f, 0.5f, 0.75f, 0.5f, 0.5f, 0.25f, 0.5f, 0.75f } + }; + + float x = rect->x0; +@@ -1283,8 +1419,14 @@ mditab_do_paint_button(mditab_t* mditab, mditab_xdraw_ctx_t* ctx, int btn_id, + : WD_COLOR_FROM_GDI_EX(127, GetSysColor(COLOR_BTNFACE))); + wdSetSolidBrushColor(ctx->solid_brush, c); + +- wdFillCircle(canvas, ctx->solid_brush, +- x + w/2.0f, y + h/2.0f, w / 2.0f - 1.0f); ++ if(mditab->style & MC_MTS_ROUNDEDITEMS) { ++ wdFillCircle(canvas, ctx->solid_brush, ++ x + w/2.0f, y + h/2.0f, h / 2.0f - 1.0f); ++ } ++ else { ++ wdFillRect(canvas, ctx->solid_brush, ++ x + w * 0.1f, y + h * 0.1f, x + w * 0.9f, y + h * 0.9f); ++ } + } + + stroke_width = (mc_win_version >= MC_WIN_10 ? 1.0f : 2.0f); +@@ -1302,6 +1444,22 @@ mditab_do_paint_button(mditab_t* mditab, mditab_xdraw_ctx_t* ctx, int btn_id, + x + w * dies[btn_id].bx0, y + h * dies[btn_id].by0, + x + w * dies[btn_id].bx1, y + h * dies[btn_id].by1, + stroke_width); ++ ++ if(state == BTNSTATE_HOT || state == BTNSTATE_PRESSED) { ++ c = WD_COLOR_FROM_GDI_EX( ++ (state == BTNSTATE_HOT ? 128 : 192), ++ WD_COLOR_TO_GDI(c)); ++ wdSetSolidBrushColor(ctx->solid_brush, c); ++ ++ if(mditab->style & MC_MTS_ROUNDEDITEMS) { ++ wdDrawCircle(canvas, ctx->solid_brush, ++ x + w/2.0f, y + h/2.0f, h / 2.0f - 1.0f, 1.0f); ++ } ++ else { ++ wdDrawRect(canvas, ctx->solid_brush, ++ x + w * 0.1f, y + h * 0.1f, x + w * 0.9f, y + h * 0.9f, 1.0f); ++ } ++ } + } + + static void +@@ -1315,7 +1473,7 @@ mditab_paint_button(mditab_t* mditab, mditab_xdraw_ctx_t* ctx, int btn_id, BOOL + if(!enabled) + state = BTNSTATE_DISABLED; + else if(btn_id == mditab_hot_button(mditab)) +- state = (mditab->btn_pressed ? BTNSTATE_PRESSED : BTNSTATE_HOT); ++ state = ((mditab->btn_pressed && !mditab->btn_pressed_cold) ? BTNSTATE_PRESSED : BTNSTATE_HOT); + else + state = BTNSTATE_NORMAL; + +@@ -1325,7 +1483,6 @@ mditab_paint_button(mditab_t* mditab, mditab_xdraw_ctx_t* ctx, int btn_id, BOOL + r.y0 = (float) rect.top; + r.x1 = (float) rect.right - 1; + r.y1 = (float) rect.bottom - 1; +- + mditab_do_paint_button(mditab, ctx, btn_id, &r, state); + } + +@@ -1375,10 +1532,13 @@ mditab_paint_item(mditab_t* mditab, mditab_xdraw_ctx_t* ctx, const RECT* client, + WD_RECT blit_rect; + mditab_item_layout_t layout; + BOOL degenerate_shape = FALSE; ++ BOOL enabled; ++ ++ enabled = IsWindowEnabled(mditab->win); + + mditab_get_dispinfo(mditab, dsa_index(&mditab->items, item), item, + &di, MC_MTIF_TEXT | MC_MTIF_IMAGE); +- mditab_setup_item_layout(mditab, &di, x0, y0, x1, y1, &layout); ++ mditab_setup_item_layout(mditab, &di, x0, y0, x1, y1, &layout, is_selected); + + /* Construct a path defining shape of the item. */ + path = wdCreatePath(canvas); +@@ -1415,6 +1575,7 @@ mditab_paint_item(mditab_t* mditab, mditab_xdraw_ctx_t* ctx, const RECT* client, + wdAddLine(&sink, x1+r+5.0f, y1); + wdEndFigure(&sink, TRUE); + } else { ++ r = 0.0f; + wdBeginFigure(&sink, x0, y1); + wdAddLine(&sink, x0, y0); + wdAddLine(&sink, x1, y0); +@@ -1467,6 +1628,21 @@ mditab_paint_item(mditab_t* mditab, mditab_xdraw_ctx_t* ctx, const RECT* client, + ctx->solid_brush, WD_STR_NOWRAP | WD_STR_ENDELLIPSIS | WD_STR_MIDDLEALIGN); + } + ++ if(((mditab->style & MC_MTS_CBMASK) == MC_MTS_CBONACTIVETAB && is_selected) ++ || (mditab->style & MC_MTS_CBMASK) == MC_MTS_CBONEACHTAB) { ++ int state; ++ ++ /* Determine button state */ ++ if(!enabled || item->hide_close) ++ state = BTNSTATE_DISABLED; ++ else if(is_hot && mditab->hot_itemclose) ++ state = ((mditab->btn_pressed && !mditab->btn_pressed_cold) ? BTNSTATE_PRESSED : BTNSTATE_HOT); ++ else ++ state = BTNSTATE_NORMAL; ++ ++ mditab_do_paint_button(mditab, ctx, BTNID_CLOSE, &layout.close_rect, state); ++ } ++ + /* Paint focus rect (if needed). */ + if(is_selected && mditab->focus && !mditab->hide_focus && di.text != NULL && + ((mditab->style & MC_MTS_FOCUSMASK) != MC_MTS_FOCUSNEVER)) +@@ -1561,7 +1737,11 @@ mditab_paint(void* ctrl, xdraw_ctx_t* raw_ctx) + if(mditab->btn_mask & BTNMASK_LSCROLL) + mditab_paint_button(mditab, ctx, BTNID_LSCROLL, (enabled && mditab->scroll_x > 0)); + if(mditab->btn_mask & BTNMASK_CLOSE) +- mditab_paint_button(mditab, ctx, BTNID_CLOSE, (enabled && n > 0)); ++ mditab_paint_button(mditab, ctx, BTNID_CLOSE, (enabled && n > 0 && ++ mditab->item_selected < n && ++ !mditab_item(mditab, mditab->item_selected)->hide_close)); ++ if(mditab->btn_mask & BTNMASK_OPEN) ++ mditab_paint_button(mditab, ctx, BTNID_OPEN, enabled); + if(mditab->btn_mask & BTNMASK_LIST) + mditab_paint_button(mditab, ctx, BTNID_LIST, (enabled && n > 0)); + if(mditab->btn_mask & BTNMASK_RSCROLL) +@@ -1611,19 +1791,14 @@ mditab_paint(void* ctrl, xdraw_ctx_t* raw_ctx) + + if(x1 <= area_x0 - r) + continue; +- if(x0 > area_x1 + r) ++ if(x0 > area_x1 + r) { ++ if(mditab->itemdrag_started && i < mousedrag_index) ++ continue; + break; ++ } + + /* We need to paint the dragged and selected item as last one, due + * to the overlaps. Remember its rect. */ +- if(i == mditab->item_selected) { +- paint_selected_item = TRUE; +- sel_rect.x0 = x0; +- sel_rect.x1 = x1; +- sel_rect.y0 = (float) MDITAB_ITEM_TOP_MARGIN; +- sel_rect.y1 = client.bottom; +- continue; +- } + if(mditab->itemdrag_started && i == mousedrag_index) { + paint_drag_item = TRUE; + drag_rect.x0 = x0; +@@ -1632,6 +1807,14 @@ mditab_paint(void* ctrl, xdraw_ctx_t* raw_ctx) + drag_rect.y1 = client.bottom; + continue; + } ++ if(i == mditab->item_selected) { ++ paint_selected_item = TRUE; ++ sel_rect.x0 = x0; ++ sel_rect.x1 = x1; ++ sel_rect.y0 = (float) MDITAB_ITEM_TOP_MARGIN; ++ sel_rect.y1 = client.bottom; ++ continue; ++ } + + /* Paint an item. */ + item_rect.x0 = x0; +@@ -1640,7 +1823,7 @@ mditab_paint(void* ctrl, xdraw_ctx_t* raw_ctx) + item_rect.y1 = client.bottom; + mditab_paint_item(mditab, ctx, &client, item, &item_rect, + area_x0, area_x1, background_image, FALSE, +- (i == mditab->item_hot)); ++ (i == mditab->item_hot)); + } + + /* Paint the selected item. */ +@@ -1760,6 +1943,7 @@ mditab_insert_item(mditab_t* mditab, int index, MC_MTITEM* id, BOOL unicode) + item->x0 = (index > 0 ? mditab_item(mditab, index-1)->x1 : 0); + item->x1 = item->x0; + item->ideal_width = 0; ++ item->hide_close = ((id->dwMask & MC_MTIF_CLOSEFLAG) && id->bDisableClose) ? 1 : 0; + + /* Update stored item indexes */ + if(index <= mditab->item_selected) +@@ -1823,6 +2007,8 @@ mditab_set_item(mditab_t* mditab, int index, MC_MTITEM* id, BOOL unicode) + item->img = (SHORT) id->iImage; + if(id->dwMask & MC_MTIF_PARAM) + item->lp = id->lParam; ++ if(id->dwMask & MC_MTIF_CLOSEFLAG) ++ item->hide_close = id->bDisableClose ? 1 : 0; + + mditab_invalidate_item(mditab, index); + if(mditab->item_def_width == 0 && (id->dwMask & MC_MTIF_TEXT)) +@@ -1864,6 +2050,8 @@ mditab_get_item(mditab_t* mditab, int index, MC_MTITEM* id, BOOL unicode) + id->iImage = di.img; + if(id->dwMask & MC_MTIF_PARAM) + id->lParam = item->lp; ++ if(id->dwMask & MC_MTIF_CLOSEFLAG) ++ id->bDisableClose = item->hide_close ? TRUE : FALSE; + + if(di_mask != 0) + mditab_free_dispinfo(mditab, item, &di); +@@ -1932,7 +2120,8 @@ mditab_delete_item(mditab_t* mditab, int index) + + if(index == mditab->item_mclose) + mditab->item_mclose = -1; +- ++ ++ mditab_reset_hot(mditab); + /* Do the delete. */ + mditab_invalidate_item(mditab, index); + mditab_notify_delete_item(mditab, index); +@@ -1956,7 +2145,6 @@ mditab_delete_item(mditab_t* mditab, int index) + mditab_cancel_drag(mditab); + } + } +- mditab_reset_hot(mditab); + + /* Refresh */ + mditab_update_layout(mditab, TRUE); +@@ -2194,6 +2382,18 @@ mditab_close_item(mditab_t* mditab, int index) + return FALSE; + } + ++static void ++mditab_notify_open_item(mditab_t* mditab) ++{ ++ NMHDR notify; ++ ++ notify.hwndFrom = mditab->win; ++ notify.idFrom = GetDlgCtrlID(mditab->win); ++ notify.code = MC_MTN_OPENITEM; ++ ++ MC_SEND(mditab->notify_win, WM_NOTIFY, notify.idFrom, ¬ify); ++} ++ + static BOOL + mditab_set_item_width(mditab_t* mditab, MC_MTITEMWIDTH* tw) + { +@@ -2210,7 +2410,7 @@ mditab_set_item_width(mditab_t* mditab, MC_MTITEMWIDTH* tw) + min_w = DEFAULT_ITEM_MIN_WIDTH; + } + +- if(def_w < min_w) ++ if(def_w > 0 && def_w < min_w) + def_w = min_w; + + if(def_w == mditab->item_def_width && min_w == mditab->item_min_width) +@@ -2382,43 +2582,70 @@ mditab_left_button_down(mditab_t* mditab, UINT keys, short x, short y) + + hti.pt.x = x; + hti.pt.y = y; +- index = mditab_hit_test(mditab, &hti, FALSE); ++ index = mditab_hit_test(mditab, &hti, TRUE); + + MDITAB_TRACE("mditab_left_button_down(): hittest index %d, flags 0x%x", + index, hti.flags); + + if(index >= 0) { +- /* Handle item selection. */ +- if(index == mditab->item_selected) { +- if((mditab->style & MC_MTS_FOCUSMASK) != MC_MTS_FOCUSNEVER) +- SetFocus(mditab->win); +- } else { +- mditab_set_cur_sel(mditab, index); +- } +- +- /* It can also be start of a drag operation. */ +- if(mditab->style & MC_MTS_DRAGDROP) { +- RECT item_rect; +- BOOL can_consider; +- +- MC_ASSERT(!mditab->itemdrag_considering); +- MC_ASSERT(!mditab->itemdrag_started); +- +- mditab_get_item_rect(mditab, index, &item_rect, TRUE); +- can_consider = mousedrag_set_candidate(mditab->win, x, y, +- x - item_rect.left, y - item_rect.top, index, 0); +- if(can_consider) +- mditab->itemdrag_considering = TRUE; +- } ++ if (hti.flags & MC_MTHT_ONITEMCLOSEBUTTON) ++ { ++ mditab_item_t *item = mditab_item(mditab, index); ++ if (!item->hide_close) ++ { ++ SetCapture(mditab->win); ++ mditab->mouse_captured = TRUE; ++ mditab->btn_pressed = TRUE; ++ mditab->btn_pressed_cold = FALSE; ++ mditab_invalidate_item(mditab, index); ++ } ++ } ++ else ++ { ++ /* Handle item selection. */ ++ if(index == mditab->item_selected) { ++ if((mditab->style & MC_MTS_FOCUSMASK) != MC_MTS_FOCUSNEVER) ++ SetFocus(mditab->win); ++ } else { ++ mditab_set_cur_sel(mditab, index); ++ } ++ ++ /* It can also be start of a drag operation. */ ++ if(mditab->style & MC_MTS_DRAGDROP) { ++ RECT item_rect; ++ BOOL can_consider; ++ ++ MC_ASSERT(!mditab->itemdrag_considering); ++ MC_ASSERT(!mditab->itemdrag_started); ++ ++ mditab_get_item_rect(mditab, index, &item_rect, TRUE); ++ can_consider = mousedrag_set_candidate(mditab->win, x, y, ++ x - item_rect.left, y - item_rect.top, index, 0); ++ if(can_consider) ++ { ++ SetCapture(mditab->win); ++ mditab->mouse_captured = TRUE; ++ mditab->itemdrag_considering = TRUE; ++ } ++ } ++ } + } else { + /* Handle auxiliary buttons */ + int btn_id = -1; ++ mditab_item_t *item = NULL; ++ if (mditab_count(mditab) > 0 && ++ mditab->item_selected >= 0 && mditab->item_selected < mditab_count(mditab)) ++ item = mditab_item(mditab, mditab->item_selected); + + switch(hti.flags) { + case MC_MTHT_ONLEFTSCROLLBUTTON: btn_id = BTNID_LSCROLL; break; + case MC_MTHT_ONRIGHTSCROLLBUTTON: btn_id = BTNID_RSCROLL; break; + case MC_MTHT_ONLISTBUTTON: btn_id = BTNID_LIST; break; +- case MC_MTHT_ONCLOSEBUTTON: btn_id = BTNID_CLOSE; break; ++ case MC_MTHT_ONCLOSEBUTTON: btn_id = BTNID_CLOSE; ++ if (item && item->hide_close) ++ btn_id = -1; ++ break; ++ case MC_MTHT_ONOPENBUTTON: btn_id = BTNID_OPEN; break; + } + + if(btn_id >= 0) { +@@ -2429,6 +2656,7 @@ mditab_left_button_down(mditab_t* mditab, UINT keys, short x, short y) + + mditab_button_rect(mditab, BTNID_LIST, &btn_rect); + mditab->btn_pressed = TRUE; ++ mditab->btn_pressed_cold = FALSE; + RedrawWindow(mditab->win, &btn_rect, NULL, RDW_INTERNALPAINT); + mditab_list_items(mditab); + mditab->btn_pressed = FALSE; +@@ -2437,6 +2665,7 @@ mditab_left_button_down(mditab_t* mditab, UINT keys, short x, short y) + SetCapture(mditab->win); + mditab->mouse_captured = TRUE; + mditab->btn_pressed = TRUE; ++ mditab->btn_pressed_cold = FALSE; + mditab_invalidate_button(mditab, btn_id); + + switch(btn_id) { +@@ -2467,13 +2696,23 @@ mditab_left_button_up(mditab_t* mditab, UINT keys, short x, short y) + + if(mditab->btn_pressed) { + mditab->btn_pressed = FALSE; +- if(mditab->item_hot < 0 && mditab->item_hot != ITEM_HOT_NONE) { +- int btn_id = mditab_hot_button(mditab); +- if(btn_id == BTNID_CLOSE && mditab->item_selected >= 0) +- mditab_close_item(mditab, mditab->item_selected); +- mditab_invalidate_button(mditab, btn_id); +- goto out; +- } ++ if(!mditab->btn_pressed_cold) { ++ if(mditab->item_hot < 0 && mditab->item_hot != ITEM_HOT_NONE) { ++ int btn_id = mditab_hot_button(mditab); ++ if(btn_id == BTNID_CLOSE && mditab->item_selected >= 0) ++ mditab_close_item(mditab, mditab->item_selected); ++ else if(btn_id == BTNID_OPEN) ++ mditab_notify_open_item(mditab); ++ mditab_invalidate_button(mditab, btn_id); ++ goto out; ++ } ++ else if(mditab->item_hot >= 0 && mditab->item_hot != ITEM_HOT_NONE && mditab->hot_itemclose) { ++ mditab_close_item(mditab, mditab->item_hot); ++ mditab_reset_hot(mditab); ++ mditab_mouse_move(mditab, x, y); //Refresh hot item ++ goto out; ++ } ++ } + } + + mc_send_notify(mditab->notify_win, mditab->win, NM_CLICK); +@@ -2527,6 +2766,24 @@ mditab_middle_button_up(mditab_t* mditab, UINT keys, short x, short y) + mditab->item_mclose = -1; + } + ++static int ++mditab_wheel(mditab_t* mditab, short delta, short x, short y) ++{ ++ MC_MTHITTESTINFO hti; ++ ++ if(!(mditab->style & MC_MTS_MOUSESCROLL)) ++ return 1; ++ ++ hti.pt.x = x; ++ hti.pt.y = y; ++ if (mditab_hit_test(mditab, &hti, FALSE) >= 0) ++ { ++ mditab_scroll_rel(mditab, -delta); ++ return 0; ++ } ++ return 1; ++} ++ + static void + mditab_change_focus(mditab_t* mditab) + { +@@ -2607,6 +2864,14 @@ mditab_exstyle_changed(mditab_t* mditab, STYLESTRUCT* ss) + } + } + ++static void ++mditab_customstyle_changed(mditab_t* mditab, STYLESTRUCT* ss) ++{ ++ mditab->custom_style = ss->styleNew; ++ if ((ss->styleOld & MC_MTCS_OPENBTN) != (ss->styleNew & MC_MTCS_OPENBTN)) ++ mditab_update_layout(mditab, FALSE); ++} ++ + static void + mditab_set_tooltip_pos(mditab_t* mditab) + { +@@ -2854,6 +3119,15 @@ mditab_proc(HWND win, UINT msg, WPARAM wp, LPARAM lp) + case MC_MTM_GETTOOLTIPS: + return (LRESULT) mditab->tooltip_win; + ++ case MC_MTM_SETCUSTOMSTYLE: ++ { ++ STYLESTRUCT ss; ++ ss.styleNew = (DWORD) wp; ++ ss.styleOld = mditab->custom_style; ++ mditab_customstyle_changed(mditab, &ss); ++ } ++ return 0; ++ + case WM_NCHITTEST: + return mditab_nchittest(mditab, GET_X_LPARAM(lp), GET_Y_LPARAM(lp)); + +@@ -2870,9 +3144,17 @@ mditab_proc(HWND win, UINT msg, WPARAM wp, LPARAM lp) + return 0; + + case WM_MBUTTONUP: +- mditab_middle_button_up(mditab, wp, GET_X_LPARAM(lp), GET_Y_LPARAM(lp)); ++ mditab_middle_button_up(mditab, wp, GET_X_LPARAM(lp), GET_Y_LPARAM(lp)); + return 0; + ++ case WM_MOUSEWHEEL: ++ { ++ POINT coords = {GET_X_LPARAM(lp), GET_Y_LPARAM(lp)}; ++ if (!ScreenToClient(win, &coords)) ++ return 1; ++ return mditab_wheel(mditab, GET_WHEEL_DELTA_WPARAM(wp), coords.x, coords.y); ++ } ++ + case WM_RBUTTONUP: + mc_send_notify(mditab->notify_win, win, NM_RCLICK); + return 0; +diff --git a/src/module.c b/src/module.c +index e903e6f..ec60bc5 100644 +--- a/src/module.c ++++ b/src/module.c +@@ -144,44 +144,44 @@ module_fini_modules(module_t** modules, int n) + /* Module definition */ + + DEFINE_MODULE(mc); +-DEFINE_MODULE(button); +-DEFINE_MODULE(chart); ++//DEFINE_MODULE(button); ++//DEFINE_MODULE(chart); + DEFINE_MODULE(dwm); +-DEFINE_MODULE(expand); +-DEFINE_MODULE(grid); +-DEFINE_MODULE(html); +-DEFINE_MODULE(imgview); ++//DEFINE_MODULE(expand); ++//DEFINE_MODULE(grid); ++//DEFINE_MODULE(html); ++//DEFINE_MODULE(imgview); + DEFINE_MODULE(mditab); +-DEFINE_MODULE(menubar); ++//DEFINE_MODULE(menubar); + DEFINE_MODULE(theme); + DEFINE_MODULE(treelist); + + + /* Public interfaces of exposed modules */ + +-static module_t* mod_button_deps[] = { &mod_mc, &mod_theme, &mod_button }; +-DEFINE_PUBLIC_IFACE(button, Button, mod_button_deps) ++//static module_t* mod_button_deps[] = { &mod_mc, &mod_theme, &mod_button }; ++//DEFINE_PUBLIC_IFACE(button, Button, mod_button_deps) + +-static module_t* mod_chart_deps[] = { &mod_mc, &mod_theme, &mod_chart }; +-DEFINE_PUBLIC_IFACE(chart, Chart, mod_chart_deps) ++//static module_t* mod_chart_deps[] = { &mod_mc, &mod_theme, &mod_chart }; ++//DEFINE_PUBLIC_IFACE(chart, Chart, mod_chart_deps) + +-static module_t* mod_expand_deps[] = { &mod_mc, &mod_theme, &mod_expand }; +-DEFINE_PUBLIC_IFACE(expand, Expand, mod_expand_deps) ++//static module_t* mod_expand_deps[] = { &mod_mc, &mod_theme, &mod_expand }; ++//DEFINE_PUBLIC_IFACE(expand, Expand, mod_expand_deps) + +-static module_t* mod_grid_deps[] = { &mod_mc, &mod_theme, &mod_grid }; +-DEFINE_PUBLIC_IFACE(grid, Grid, mod_grid_deps) ++//static module_t* mod_grid_deps[] = { &mod_mc, &mod_theme, &mod_grid }; ++//DEFINE_PUBLIC_IFACE(grid, Grid, mod_grid_deps) + +-static module_t* mod_html_deps[] = { &mod_mc, &mod_theme, &mod_html }; +-DEFINE_PUBLIC_IFACE(html, Html, mod_html_deps) ++//static module_t* mod_html_deps[] = { &mod_mc, &mod_theme, &mod_html }; ++//DEFINE_PUBLIC_IFACE(html, Html, mod_html_deps) + +-static module_t* mod_imgview_deps[] = { &mod_mc, &mod_imgview }; +-DEFINE_PUBLIC_IFACE(imgview, ImgView, mod_imgview_deps) ++//static module_t* mod_imgview_deps[] = { &mod_mc, &mod_imgview }; ++//DEFINE_PUBLIC_IFACE(imgview, ImgView, mod_imgview_deps) + + static module_t* mod_mditab_deps[] = { &mod_mc, &mod_dwm, &mod_mditab }; + DEFINE_PUBLIC_IFACE(mditab, Mditab, mod_mditab_deps) + +-static module_t* mod_menubar_deps[] = { &mod_mc, &mod_theme, &mod_menubar }; +-DEFINE_PUBLIC_IFACE(menubar, Menubar, mod_menubar_deps) ++//static module_t* mod_menubar_deps[] = { &mod_mc, &mod_theme, &mod_menubar }; ++//DEFINE_PUBLIC_IFACE(menubar, Menubar, mod_menubar_deps) + + static module_t* mod_theme_deps[] = { &mod_mc, &mod_theme }; + DEFINE_PUBLIC_IFACE(theme, Theme, mod_theme_deps) +diff --git a/src/treelist.c b/src/treelist.c +index 06d8980..00f8574 100644 +--- a/src/treelist.c ++++ b/src/treelist.c +@@ -93,6 +93,8 @@ struct treelist_item_tag { + WORD expanding_notify_in_progress : 1; + /* If set, then ->subitems[] is alloc'ed and valid, otherwise ->callback_map */ + WORD has_alloced_subitems : 1; ++ //Amount of selections in the sub tree (including this item). ++ unsigned int subtree_selected_count; + }; + + /* Iterator over ALL items of the control */ +@@ -198,6 +200,8 @@ struct treelist_tag { + treelist_item_t* hotbutton_item; + int scrolled_level; /* level of the scrolled_item */ + DWORD style : 16; ++ DWORD multiselect_children : 1; ++ DWORD always_focused : 1; + DWORD no_redraw : 1; + DWORD unicode_notifications : 1; + DWORD rtl : 1; +@@ -213,7 +217,7 @@ struct treelist_tag { + WORD item_height; + WORD item_indent; + SHORT hot_col; +- WORD scroll_y; /* in rows */ ++ int scroll_y; /* in rows */ + int scroll_x; /* in pixels */ + int scroll_x_max; + unsigned int selected_count; +@@ -473,15 +477,32 @@ treelist_first_selected(treelist_t* tl) + if(tl->selected_count == 0) + return NULL; + +- if(tl->selected_count == 1) ++ if((!(tl->style & MC_TLS_MULTISELECT) || !tl->multiselect_children) ++ && tl->selected_count == 1) + return tl->selected_last; + + ret = NULL; +- walk = tl->selected_last; ++ walk = tl->multiselect_children ? tl->root_head : tl->selected_last; + while(walk != NULL) { + if(walk->state & MC_TLIS_SELECTED) + ret = walk; +- walk = walk->sibling_prev; ++ if(tl->multiselect_children) { ++ if(ret != NULL) ++ break; ++ if(walk->subtree_selected_count > 0 && walk->child_head) { ++ walk = walk->child_head; ++ } ++ else if(!walk->sibling_next) { ++ walk = walk->parent; ++ if (walk) ++ walk = walk->sibling_next; ++ } ++ else { ++ walk = walk->sibling_next; ++ } ++ } ++ else ++ walk = walk->sibling_prev; + } + + return ret; +@@ -490,12 +511,35 @@ treelist_first_selected(treelist_t* tl) + static treelist_item_t* + treelist_next_selected(treelist_t* tl, treelist_item_t* item) + { +- if(tl->selected_count <= 1) ++ if (!(tl->style & MC_TLS_MULTISELECT)) + return NULL; /* treelist_first_selected() already returned all selected items. */ +- +- do { +- item = item->sibling_next; +- } while(item != NULL && !(item->state & MC_TLIS_SELECTED)); ++ if(tl->multiselect_children) ++ { ++ do { ++ unsigned int nSelectedChildren = item->subtree_selected_count; ++ if (item->state & MC_TLIS_SELECTED) ++ nSelectedChildren--; ++ if(item->child_head && nSelectedChildren > 0) ++ item = item->child_head; //Visit the children only if there are selected ones. ++ else { ++ //Get the next sibling (or an ancestor's sibling). ++ treelist_item_t* curit = item; ++ item = NULL; ++ do { ++ if(curit->sibling_next) { ++ item = curit->sibling_next; ++ break; ++ } ++ } while(curit = curit->parent); ++ } ++ } while(item != NULL && !(item->state & MC_TLIS_SELECTED)); ++ } ++ else ++ { ++ do { ++ item = item->sibling_next; ++ } while(item != NULL && !(item->state & MC_TLIS_SELECTED)); ++ } + + return item; + } +@@ -966,9 +1010,9 @@ treelist_custom_draw_item_state(treelist_t* tl, treelist_item_t* item) + UINT state = 0; + + if(item->state & MC_TLIS_SELECTED) { +- if(tl->focus) ++ if(item == tl->selected_last && tl->focus) + state |= CDIS_FOCUS | CDIS_SELECTED; +- else if(tl->style & MC_TLS_SHOWSELALWAYS) ++ else if(tl->always_focused || (tl->style & MC_TLS_SHOWSELALWAYS)) + state |= CDIS_SELECTED; + } + if(item == tl->hot_item) +@@ -1100,7 +1144,7 @@ treelist_paint(void* control, HDC dc, RECT* dirty, BOOL erase) + } else if(item->state & MC_TLIS_SELECTED) { + if(item == tl->hot_item) + state = TREIS_HOTSELECTED; +- else if(tl->focus) ++ else if(tl->focus || tl->always_focused) + state = TREIS_SELECTED; + else + state = TREIS_SELECTEDNOTFOCUS; +@@ -1132,7 +1176,7 @@ treelist_paint(void* control, HDC dc, RECT* dirty, BOOL erase) + ((tl->style & MC_TLS_SHOWSELALWAYS) || tl->focus) && + ((tl->style & MC_TLS_FULLROWSELECT) || col_ix == 0); + if(paint_selected && !theme_treeitem_defined) { +- if(tl->focus) { ++ if(tl->focus || tl->always_focused) { + subitem_text_color = GetSysColor(COLOR_HIGHLIGHTTEXT); + subitem_bk_color = GetSysColor(COLOR_HIGHLIGHT); + } else { +@@ -1213,7 +1257,7 @@ treelist_paint(void* control, HDC dc, RECT* dirty, BOOL erase) + + /* Calculate label rectangle */ + mc_rect_copy(&label_rect, &subitem_rect); +- treelist_label_rect(tl, dc, item->text, DT_LEFT, &label_rect, ++ treelist_label_rect(tl, dc, dispinfo.text, DT_LEFT, &label_rect, + &padding_h, &padding_v); + + /* Paint background of the main item. We expand it to all +@@ -1232,7 +1276,7 @@ treelist_paint(void* control, HDC dc, RECT* dirty, BOOL erase) + r = &label_rect; + if(subitem_bk_color != MC_CLR_NONE) + ExtTextOut(dc, 0, 0, ETO_OPAQUE, r, NULL, 0, NULL); +- if(paint_selected && tl->focus) ++ if(paint_selected && tl->focus && tl->selected_last == item) + DrawFocusRect(dc, r); + } + +@@ -1423,7 +1467,7 @@ treelist_hit_test(treelist_t* tl, MC_TLHITTESTINFO* info) + item_rect.left += img_w + ITEM_PADDING_H; + } + +- treelist_label_rect(tl, dc, item->text, DT_LEFT, &item_rect, ++ treelist_label_rect(tl, dc, dispinfo.text, DT_LEFT, &item_rect, + &ignored, &ignored); + + treelist_free_dispinfo(tl, item, &dispinfo); +@@ -1672,11 +1716,22 @@ treelist_set_sel(treelist_t* tl, treelist_item_t* item) + return; + } + } ++ ++ //Redraw the last selected item since the focus will likely move. ++ if(tl->focus && tl->selected_last != NULL) ++ { ++ treelist_invalidate_item(tl, tl->selected_last, (tl->style & MC_TLS_FULLROWSELECT) ? -1 : 0, 0); ++ } + + /* Remove old selection */ + if(tl->selected_count > 0) { ++ treelist_item_t* parentit; + if(tl->selected_count == 1) { + tl->selected_last->state &= ~MC_TLIS_SELECTED; ++ parentit = tl->selected_last; ++ do { ++ parentit->subtree_selected_count = 0; ++ } while ((parentit = parentit->parent) && parentit->subtree_selected_count > 0); + + if(do_single_expand) { + /* Collapse the old selection and all its ancestors. */ +@@ -1691,11 +1746,13 @@ treelist_set_sel(treelist_t* tl, treelist_item_t* item) + treelist_invalidate_item(tl, tl->selected_last, col_ix, 0); + } else { + treelist_item_t* it; +- + for(it = treelist_first_selected(tl); it != NULL; it = treelist_next_selected(tl, it)) { + if(it == item) + continue; +- ++ parentit = it; ++ do { ++ parentit->subtree_selected_count--; ++ } while (parentit = parentit->parent); + it->state &= ~MC_TLIS_SELECTED; + if(!tl->no_redraw) + treelist_invalidate_item(tl, it, col_ix, 0); +@@ -1707,7 +1764,14 @@ treelist_set_sel(treelist_t* tl, treelist_item_t* item) + tl->selected_last = item; + tl->selected_from = item; + if(item != NULL) { ++ treelist_item_t* parentit; ++ + item->state |= MC_TLIS_SELECTED; ++ ++ parentit = item; ++ do { ++ parentit->subtree_selected_count = 1; ++ } while (parentit = parentit->parent); + tl->selected_count = 1; + + if(do_single_expand) { +@@ -1723,6 +1787,7 @@ treelist_set_sel(treelist_t* tl, treelist_item_t* item) + if(!tl->no_redraw) + treelist_invalidate_item(tl, item, col_ix, 0); + } else { ++ //subtree_selected_count already set to 0. + tl->selected_count = 0; + } + +@@ -1749,12 +1814,13 @@ treelist_toggle_sel(treelist_t* tl, treelist_item_t* item) + /* If we toggle in the tree elsewhere then the current selection is, we may + * need to unselect old selection. Function treelist_set_sel() already + * knows how to do that. */ +- if(do_select && tl->selected_count > 0 && tl->selected_last->parent != item->parent) { ++ if(do_select && tl->selected_count > 0 && !tl->multiselect_children && tl->selected_last->parent != item->parent) { + treelist_set_sel(tl, item); + return; + } + + if(do_select) { ++ treelist_item_t* parentit; + /* Send MC_TLN_SELCHANGING */ + if(treelist_sel_notify(tl, MC_TLN_SELCHANGING, NULL, item) != 0) { + TREELIST_TRACE("treelist_toggle_sel: Denied by app."); +@@ -1762,10 +1828,21 @@ treelist_toggle_sel(treelist_t* tl, treelist_item_t* item) + } + + item->state |= MC_TLIS_SELECTED; ++ parentit = item; ++ do { ++ parentit->subtree_selected_count++; ++ } while (parentit = parentit->parent); + tl->selected_count++; + tl->selected_last = item; + } else { ++ treelist_item_t* parentit; ++ + item->state &= ~MC_TLIS_SELECTED; ++ ++ parentit = item; ++ do { ++ parentit->subtree_selected_count--; ++ } while (parentit = parentit->parent); + if(tl->selected_last == item) { + /* We must ensure ->selected_last is set properly. */ + if(tl->selected_count == 1) +@@ -1790,11 +1867,12 @@ treelist_set_sel_range(treelist_t* tl, treelist_item_t* item) + treelist_item_t* item0 = tl->selected_from; + treelist_item_t* item1; + int col_ix; ++ unsigned int accum_selection_count; + + MC_ASSERT(tl->style & MC_TLS_MULTISELECT); + MC_ASSERT(item != NULL); + +- if(item0 == NULL || item0->parent != item->parent) { ++ if(item0 == NULL || (!tl->multiselect_children && item0->parent != item->parent)) { + treelist_set_sel(tl, item); + return; + } +@@ -1803,19 +1881,54 @@ treelist_set_sel_range(treelist_t* tl, treelist_item_t* item) + + /* Make sure there is no item selected before the item0 or item + * (whatever comes first). */ +- it = (item->parent ? item->parent->child_head : tl->root_head); ++ it = ((item->parent && !tl->multiselect_children) ? item->parent->child_head : tl->root_head); ++ accum_selection_count = 0; //Counts the amount of deselected items in the current parent's subtree. + while(it != item0 && it != item) { + if(it->state & MC_TLIS_SELECTED) { + it->state &= ~MC_TLIS_SELECTED; ++ MC_ASSERT(it->subtree_selected_count > 0); ++ it->subtree_selected_count--; + tl->selected_count--; ++ accum_selection_count++; + if(!tl->no_redraw) + treelist_invalidate_item(tl, it, col_ix, 0); + } +- it = it->sibling_next; +- } ++ if(tl->multiselect_children && it->child_head) { ++ if(accum_selection_count > 0) { ++ //Subtract the amount of removed selections among siblings from the parent counters now, ++ // since the accumulated selection count needs to be reset to iterate through children. ++ treelist_item_t* parentit = it; ++ while (parentit = parentit->parent) { ++ MC_ASSERT(parentit->subtree_selected_count >= accum_selection_count); ++ parentit->subtree_selected_count -= accum_selection_count; ++ } ++ accum_selection_count = 0; //New subtree is fresh ++ } ++ it = it->child_head; ++ } ++ else { ++ MC_ASSERT(it->subtree_selected_count == 0); ++ while(!it->sibling_next) { ++ it = it->parent; ++ MC_ASSERT(it->subtree_selected_count == accum_selection_count); ++ //The current subtree did not contain the first item to select, ++ // i.e. all of its items have been deselected. ++ it->subtree_selected_count = 0; ++ } ++ it = it->sibling_next; ++ } ++ } ++ if(accum_selection_count > 0) { ++ treelist_item_t* parentit = it; ++ while (parentit = parentit->parent) { ++ MC_ASSERT(parentit->subtree_selected_count >= accum_selection_count); ++ parentit->subtree_selected_count -= accum_selection_count; ++ } ++ } + + /* Make sure all items between item0 and item1 are selected. */ + item1 = (it == item ? item0 : item); ++ accum_selection_count = 0; //Counts the amount of selected items in the current parent's subtree. + while(TRUE) { + if(!(it->state & MC_TLIS_SELECTED)) { + /* Send MC_TLN_SELCHANGING */ +@@ -1823,31 +1936,106 @@ treelist_set_sel_range(treelist_t* tl, treelist_item_t* item) + TREELIST_TRACE("treelist_set_sel_range: Denied by app."); + } else { + it->state |= MC_TLIS_SELECTED; ++ it->subtree_selected_count++; + tl->selected_count++; ++ accum_selection_count++; + if(!tl->no_redraw) + treelist_invalidate_item(tl, it, col_ix, 0); + } + } + + if(it == item1) { +- it = it->sibling_next; ++ if(it != NULL && accum_selection_count > 0) { ++ treelist_item_t* parentit = it; ++ while (parentit = parentit->parent) { ++ parentit->subtree_selected_count += accum_selection_count; ++ } ++ } ++ if(tl->multiselect_children && it->child_head) ++ it = it->child_head; ++ else if(it->sibling_next) ++ it = it->sibling_next; ++ else if(tl->multiselect_children) { ++ while (it = it->parent) { ++ if (it->sibling_next) { ++ it = it->sibling_next; ++ break; ++ } ++ } ++ } + break; + } + +- it = it->sibling_next; ++ if(tl->multiselect_children && (it->state & MC_TLIS_EXPANDED) && it->child_head) { ++ if(accum_selection_count > 0) { ++ //Add the amount of removed selections among siblings to the parent counters now, ++ // since the accumulated selection count needs to be reset to iterate through children. ++ treelist_item_t* parentit = it; ++ while (parentit = parentit->parent) { ++ parentit->subtree_selected_count += accum_selection_count; ++ } ++ accum_selection_count = 0; //New subtree is fresh ++ } ++ it = it->child_head; ++ } ++ else { ++ //Find the next sibling to select, or look for an ancestor's sibling. ++ //(The end item is not a parent of the current item , ++ // as the current item would otherwise be after the end). ++ while(!it->sibling_next) { ++ it = it->parent; ++ //Also add the selection counter to the parents. ++ it->subtree_selected_count += accum_selection_count; ++ } ++ it = it->sibling_next; ++ } + } + + /* Make sure no more items are selected */ ++ accum_selection_count = 0; //Counts the amount of deselected items in the current parent's subtree. + while(it != NULL) { + if(it->state & MC_TLIS_SELECTED) { + it->state &= ~MC_TLIS_SELECTED; ++ MC_ASSERT(it->subtree_selected_count > 0); ++ it->subtree_selected_count--; + tl->selected_count--; ++ accum_selection_count++; + if(!tl->no_redraw) + treelist_invalidate_item(tl, it, col_ix, 0); + } +- it = it->sibling_next; +- } +- ++ if(tl->multiselect_children && it->child_head) { ++ if(accum_selection_count > 0) { ++ //Subtract the amount of removed selections among siblings from the parent counters now, ++ // since the accumulated selection count needs to be reset to iterate through children. ++ treelist_item_t* parentit = it; ++ while (parentit = parentit->parent) { ++ MC_ASSERT(parentit->subtree_selected_count >= accum_selection_count); ++ parentit->subtree_selected_count -= accum_selection_count; ++ } ++ accum_selection_count = 0; //New subtree is fresh ++ } ++ it = it->child_head; ++ } ++ else { ++ while(!it->sibling_next) { ++ it = it->parent; ++ if (!it) ++ break; ++ MC_ASSERT(it->subtree_selected_count >= accum_selection_count); ++ it->subtree_selected_count -= accum_selection_count; ++ } ++ if(it) ++ it = it->sibling_next; ++ } ++ } ++ //No parents are left to remove accum_selection_count from. ++ ++ if(tl->focus) { ++ //Redraw the last selected and the new selected item for the focus border. ++ treelist_invalidate_item(tl, item, (tl->style & MC_TLS_FULLROWSELECT) ? -1 : 0, 0); ++ if(tl->selected_last != NULL && tl->selected_last != item) ++ treelist_invalidate_item(tl, tl->selected_last, (tl->style & MC_TLS_FULLROWSELECT) ? -1 : 0, 0); ++ } + tl->selected_last = item; + + treelist_sel_notify(tl, MC_TLN_SELCHANGED, NULL, NULL); +@@ -2278,11 +2466,21 @@ treelist_key_down(treelist_t* tl, int key) + + switch(key) { + case VK_UP: +- if(sel->sibling_prev) ++ if(tl->multiselect_children) { ++ treelist_item_t* new_sel = item_prev_displayed(sel); ++ if (new_sel != NULL) ++ sel = new_sel; ++ } ++ else if(sel->sibling_prev) + sel = sel->sibling_prev; + break; + case VK_DOWN: +- if(sel->sibling_next) ++ if(tl->multiselect_children) { ++ treelist_item_t* new_sel = item_next_displayed(sel, &ignored); ++ if (new_sel != NULL) ++ sel = new_sel; ++ } ++ else if(sel->sibling_next) + sel = sel->sibling_next; + break; + } +@@ -2801,7 +2999,7 @@ treelist_insert_item(treelist_t* tl, MC_TLINSERTSTRUCT* insert, BOOL unicode) + } + if(item_data->fMask & MC_TLIF_TEXT) { + if(item_data->pszText == MC_LPSTR_TEXTCALLBACK) { +- item->text = item_data->pszText; ++ text = item_data->pszText; + } else { + text = mc_str(item_data->pszText, (unicode ? MC_STRW : MC_STRA), MC_STRT); + if(MC_ERR(text == NULL && item_data->pszText != NULL)) { +@@ -2855,6 +3053,7 @@ treelist_insert_item(treelist_t* tl, MC_TLINSERTSTRUCT* insert, BOOL unicode) + } + item->expanding_notify_in_progress = 0; + item->has_alloced_subitems = 0; ++ item->subtree_selected_count = 0; + + if(parent != NULL) { + parent_displayed = item_is_displayed(parent); +@@ -2906,6 +3105,131 @@ err_alloc_item: + return NULL; + } + ++static BOOL ++treelist_move_item(treelist_t* tl, treelist_item_t* item, treelist_item_t* after) ++{ ++ treelist_item_t* parent; ++ treelist_item_t** pParent_child_head; ++ treelist_item_t** pParent_child_tail; ++ treelist_item_t* prev; ++ int y_pre; ++ BOOL parent_displayed; ++ BOOL displayed; ++ if(MC_ERR(item == NULL || item == MC_TLI_FIRST || item == MC_TLI_LAST)) ++ { ++ MC_TRACE("treelist_move_item: hItem is NULL or MC_TLI_FIRST or MC_TLI_LAST"); ++ SetLastError(ERROR_INVALID_PARAMETER); ++ return FALSE; ++ } ++ y_pre = (!tl->no_redraw) ? treelist_get_item_y(tl, item, FALSE) : 0; ++ parent = item->parent; ++ ++ if(after == MC_TLI_FIRST) { ++ prev = NULL; ++ } else if(after == MC_TLI_LAST || after == NULL) { ++ prev = (parent ? parent->child_tail : tl->root_tail); ++ } else { ++ prev = after; ++ if(MC_ERR(prev->parent != parent)) { ++ MC_TRACE("treelist_move_item: Parents of hItem and hInsertAfter do not match."); ++ SetLastError(ERROR_INVALID_PARAMETER); ++ return FALSE; ++ } ++ } ++ if(item == prev || item->sibling_prev == prev || (prev == NULL && item == (parent ? parent->child_head : tl->root_head))) { ++ /* Item is not moved */ ++ return TRUE; ++ } ++ /* Unlink item from its neighbours */ ++ if(item->sibling_prev != NULL) { ++ /* Does not overwrite prev->sibling_next, since item->sibling_prev != prev */ ++ item->sibling_prev->sibling_next = item->sibling_next; ++ } ++ if(item->sibling_next != NULL) { ++ /* May overwrite prev->sibling_prev */ ++ item->sibling_next->sibling_prev = item->sibling_prev; ++ } ++ pParent_child_head = (parent ? &parent->child_head : &tl->root_head); ++ pParent_child_tail = (parent ? &parent->child_tail : &tl->root_tail); ++ /* If the item was the tail or head, update the reference */ ++ if((parent ? parent->child_tail : tl->root_tail) == item) { ++ *pParent_child_tail = item->sibling_prev; ++ } ++ if((parent ? parent->child_head : tl->root_head) == item) { ++ *pParent_child_head = item->sibling_next; ++ } ++ if(prev == NULL) { ++ /* Moved to the front */ ++ item->sibling_prev = NULL; ++ item->sibling_next = (parent ? parent->child_head : tl->root_head); ++ item->sibling_next->sibling_prev = item; ++ *pParent_child_head = item; ++ } ++ else { ++ /* Link the item in between prev and prev->sibling_next */ ++ item->sibling_prev = prev; ++ item->sibling_next = prev->sibling_next; ++ if(prev->sibling_next != NULL) { ++ prev->sibling_next->sibling_prev = item; ++ } ++ prev->sibling_next = item; ++ if(prev == (parent ? parent->child_tail : tl->root_tail)) { ++ /* Update the tail reference */ ++ *pParent_child_tail = item; ++ } ++ } ++ ++ if(parent != NULL) { ++ parent_displayed = item_is_displayed(parent); ++ displayed = (parent_displayed && (parent->state & MC_TLIS_EXPANDED)); ++ } else { ++ parent_displayed = FALSE; ++ displayed = TRUE; ++ } ++ /* Refresh */ ++ if(!tl->no_redraw) { ++ if(displayed) { ++ RECT rect; ++ int item_height = 0; ++ int y_post = treelist_get_item_y(tl, item, FALSE); ++ { ++ /* Retrieve the height of the item plus its expanded descendants */ ++ treelist_item_t* it = item; int ignored; ++ while(TRUE) { ++ if(it == item->sibling_next || it == NULL) ++ break; ++ ++ item_height += tl->item_height; ++ it = item_next_displayed(it, &ignored); ++ } ++ } ++ ++ GetClientRect(tl->win, &rect); ++ ++ if(y_pre < y_post) { ++ rect.top = y_pre; ++ rect.bottom = y_post + item_height; ++ } ++ else { ++ rect.top = y_post; ++ rect.bottom = y_pre + item_height; ++ } ++ if(rect.top < 0) { ++ treelist_item_t* scrolled_item; ++ int ignored; ++ ++ scrolled_item = treelist_scrolled_item(tl, &ignored); ++ rect.top = treelist_get_item_y(tl, scrolled_item, TRUE); ++ } ++ if(rect.bottom >= rect.top) { ++ InvalidateRect(tl->win, &rect, TRUE); ++ } ++ } ++ } ++ treelist_refresh_hot(tl); ++ return TRUE; ++} ++ + static BOOL + treelist_set_item(treelist_t* tl, treelist_item_t* item, MC_TLITEM* item_data, + BOOL unicode) +@@ -3057,63 +3381,73 @@ treelist_delete_notify(treelist_t* tl, treelist_item_t* item, treelist_item_t* s + static int + treelist_delete_item_helper(treelist_t* tl, treelist_item_t* item, BOOL displayed) + { +- treelist_item_t* next_to_delete; ++ treelist_item_t *stop_item = item->parent; //Either NULL or an actual parent. ++ unsigned int hidden_depth = displayed ? 0 : 1; //Depth in the tree from the last visible item. + int deleted_visible = 0; +- +- while(item != NULL) { +- /* Handle deletion of children */ ++ while(item != stop_item) { ++ /* Delete children first */ + if(item->child_head != NULL) { +- if(displayed && !(item->state & MC_TLIS_EXPANDED)) { +- /* Unlike this item, all the children are hidden. So recurse +- * without getting the return value. */ +- treelist_delete_item_helper(tl, item->child_head, FALSE); +- next_to_delete = item->sibling_next; +- } else { +- /* Artificially "upgrade" the children to our level (small +- * trick to avoid recursion). */ +- item->child_tail->sibling_next = item->sibling_next; +- next_to_delete = item->child_head; +- } +- } else { +- next_to_delete = item->sibling_next; +- } +- +- /* The deletion of the item */ +- if(item->has_alloced_subitems) { +- int i; +- for(i = 0; i < tl->col_count - 1; i++) { +- if(item->subitems[i] != NULL && +- item->subitems[i] != MC_LPSTR_TEXTCALLBACK) +- free(item->subitems[i]); +- } +- free(item->subitems); +- } +- if(item->text != NULL && item->text != MC_LPSTR_TEXTCALLBACK) +- free(item->text); +- +- /* Update any selection information now */ +- if(item->state & MC_TLIS_SELECTED) +- treelist_toggle_sel(tl, item); +- if(item == tl->selected_from) +- tl->selected_from = NULL; +- +- free(item); +- +- deleted_visible++; +- +- item = next_to_delete; +- } +- ++ treelist_item_t* next_item; ++ if(hidden_depth == 0 && !(item->state & MC_TLIS_EXPANDED)) ++ hidden_depth = 1; ++ else if(hidden_depth > 0) ++ hidden_depth++; ++ next_item = item->child_head; ++ //Make sure the parent will not iterate over its by then deleted child list. ++ item->child_head = NULL; ++ item = next_item; ++ } ++ else { ++ treelist_item_t* parent_item = item->parent; ++ treelist_item_t* sibling_item = item->sibling_next; ++ /* The deletion of the item */ ++ if(item->has_alloced_subitems) { ++ int i; ++ for(i = 0; i < tl->col_count - 1; i++) { ++ if(item->subitems[i] != NULL && ++ item->subitems[i] != MC_LPSTR_TEXTCALLBACK) ++ free(item->subitems[i]); ++ } ++ free(item->subitems); ++ } ++ if(item->text != NULL && item->text != MC_LPSTR_TEXTCALLBACK) ++ free(item->text); ++ ++ /* Update any selection information now */ ++ if(item->state & MC_TLIS_SELECTED) ++ treelist_toggle_sel(tl, item); ++ if(item == tl->selected_from) ++ tl->selected_from = NULL; ++ ++ free(item); ++ if(hidden_depth == 0) ++ deleted_visible++; ++ ++ if(sibling_item != NULL) ++ item = sibling_item; ++ else { ++ /* Go upwards since this sub tree is freed */ ++ if (hidden_depth > 0) ++ hidden_depth--; //Moving closer to the last visible item. ++ item = parent_item; ++ } ++ } ++ } ++ + return deleted_visible; + } + + static BOOL +-treelist_delete_item(treelist_t* tl, treelist_item_t* item) ++treelist_delete_item(treelist_t* tl, treelist_item_t* item, BOOL notify) + { + DWORD old_displayed_items = tl->displayed_items; ++ unsigned int old_selected_count = tl->selected_count; + treelist_item_t* parent; + treelist_item_t* sibling_prev; + treelist_item_t* sibling_next; ++ WORD state; ++ unsigned int subtree_selected_count; ++ BOOL resetSelection; + BOOL is_displayed; + int y; + int displayed_del_count; +@@ -3126,7 +3460,8 @@ treelist_delete_item(treelist_t* tl, treelist_item_t* item) + treelist_set_sel(tl, NULL); + tl->scrolled_item = NULL; + +- treelist_delete_notify(tl, tl->root_head, NULL); ++ if (notify) ++ treelist_delete_notify(tl, tl->root_head, NULL); + treelist_delete_item_helper(tl, tl->root_head, FALSE); + tl->root_head = NULL; + tl->root_tail = NULL; +@@ -3145,32 +3480,50 @@ treelist_delete_item(treelist_t* tl, treelist_item_t* item) + parent = item->parent; + sibling_prev = item->sibling_prev; + sibling_next = item->sibling_next; ++ state = item->state; ++ subtree_selected_count = item->subtree_selected_count; + is_displayed = item_is_displayed(item); + y = (is_displayed ? treelist_get_item_y(tl, item, TRUE) : -1); + ++ resetSelection = FALSE; ++ + /* If the deleted subtree contains selection, we must choose another + * selection. */ + if(!(tl->style & MC_TLS_MULTISELECT)) { +- if(item_is_ancestor(item, tl->selected_last)) { +- if(item->sibling_next) +- treelist_set_sel(tl, item->sibling_next); +- else if(item->sibling_prev) +- treelist_set_sel(tl, item->sibling_prev); ++ //if(item_is_ancestor(item, tl->selected_last)) { ++ if(subtree_selected_count > 0) { ++ resetSelection = TRUE; ++ if(sibling_next) ++ treelist_set_sel(tl, sibling_next); ++ else if(sibling_prev) ++ treelist_set_sel(tl, sibling_prev); + else + treelist_set_sel(tl, parent); + } + } else { +- if(item->state & MC_TLIS_SELECTED) { +- if(tl->selected_count == 1) ++ if(state & MC_TLIS_SELECTED) { ++ MC_ASSERT(tl->selected_count >= subtree_selected_count); ++ if(tl->selected_count == subtree_selected_count) ++ { ++ resetSelection = TRUE; + treelist_set_sel(tl, parent); ++ } + } else if(tl->selected_last != NULL) { +- if(item_is_ancestor(item, tl->selected_last->parent)) ++ if(subtree_selected_count >= tl->selected_count) ++ { ++ resetSelection = TRUE; + treelist_set_sel(tl, parent); ++ } ++ //if(item_is_ancestor(item, tl->selected_last->parent)) ++ // treelist_set_sel(tl, parent); + } + } + +- /* This should be very last notification about the item and its subtree. */ +- treelist_delete_notify(tl, item, item); ++ if (notify) ++ { ++ /* This should be very last notification about the item and its subtree. */ ++ treelist_delete_notify(tl, item, item); ++ } + + /* Disconnect the item from the tree. */ + if(item->sibling_prev) { +@@ -3262,6 +3615,19 @@ treelist_delete_children(treelist_t* tl, treelist_item_t* item) + /* This should be very last notification about the item and its subtree. */ + treelist_delete_notify(tl, item->child_head, item); + ++ if (item->subtree_selected_count > 0) ++ { ++ unsigned int removed_sel_count = item->subtree_selected_count; ++ treelist_item_t* parentit; ++ if (item->state & MC_TLIS_SELECTED) ++ removed_sel_count--; // is selected but will not be removed. ++ //Subtract the amount of selected children from the parent counters. ++ parentit = item; ++ do { ++ parentit->subtree_selected_count -= removed_sel_count; ++ } while (parentit = parentit->parent); ++ } ++ + /* Disconnect the children from the tree. */ + child_head = item->child_head; + item->child_head = NULL; +@@ -3841,7 +4207,7 @@ treelist_create(treelist_t* tl) + static void + treelist_destroy(treelist_t* tl) + { +- treelist_delete_item(tl, NULL); ++ treelist_delete_item(tl, NULL, TRUE); + + if(tl->tooltip_win != NULL) { + if(!(tl->style & MC_TLS_NOTOOLTIPS)) +@@ -3938,6 +4304,26 @@ treelist_proc(HWND win, UINT msg, WPARAM wp, LPARAM lp) + return (LRESULT) treelist_insert_item(tl, (MC_TLINSERTSTRUCT*) lp, + (msg == MC_TLM_INSERTITEMW)); + ++ case MC_TLM_INSERTITEMSW: ++ case MC_TLM_INSERTITEMSA: ++ { ++ treelist_item_t *ret = NULL; ++ int i; ++ for (i = 0; i < (int) wp; i++) ++ { ++ ret = treelist_insert_item(tl, &((MC_TLINSERTSTRUCT*) lp)[i], ++ (msg == MC_TLM_INSERTITEMSW)); ++ if (!ret) ++ { ++ for (; i < (int) wp; i++) ++ ((MC_TLINSERTSTRUCT*) lp)[i].hParent = NULL; ++ break; ++ } ++ ((MC_TLINSERTSTRUCT*) lp)[i].hParent = (MC_HTREELISTITEM) ret; ++ } ++ return (LRESULT) ret; ++ } ++ + case MC_TLM_SETITEMW: + case MC_TLM_SETITEMA: + return treelist_set_item(tl, (treelist_item_t*) wp, (MC_TLITEM*) lp, +@@ -3949,7 +4335,11 @@ treelist_proc(HWND win, UINT msg, WPARAM wp, LPARAM lp) + (msg == MC_TLM_GETITEMW)); + + case MC_TLM_DELETEITEM: +- return treelist_delete_item(tl, (treelist_item_t*) lp); ++ return treelist_delete_item(tl, (treelist_item_t*) lp, ++ (wp & MC_TLDI_NONOTIFY) ? FALSE : TRUE); ++ ++ case MC_TLM_MOVEITEM: ++ return treelist_move_item(tl, (treelist_item_t*) wp, (treelist_item_t*) lp); + + case MC_TLM_SETITEMHEIGHT: + return treelist_set_item_height(tl, (int) wp, TRUE); +@@ -4022,6 +4412,11 @@ treelist_proc(HWND win, UINT msg, WPARAM wp, LPARAM lp) + case MC_TLM_GETTOOLTIPS: + return (LRESULT) tl->tooltip_win; + ++ case MC_TLM_SETCUSTOMSTYLE: ++ tl->multiselect_children = (wp & 1) ? 1 : 0; ++ tl->always_focused = (wp & 2) ? 1 : 0; ++ return 0; ++ + case WM_NOTIFY: + if(((NMHDR*)lp)->hwndFrom == tl->header_win) + return treelist_header_notify(tl, (NMHEADER*) lp); diff --git a/fetchcontent/pthreads4w-patch-02fecc211d626f28e05ecbb0c10f739bd36d6442.patch b/fetchcontent/pthreads4w-patch-02fecc211d626f28e05ecbb0c10f739bd36d6442.patch new file mode 100644 index 0000000..b2622c6 --- /dev/null +++ b/fetchcontent/pthreads4w-patch-02fecc211d626f28e05ecbb0c10f739bd36d6442.patch @@ -0,0 +1,32 @@ +diff --git a/_ptw32.h b/_ptw32.h +index bb47ab7..6da4eca 100644 +--- a/_ptw32.h ++++ b/_ptw32.h +@@ -162,16 +162,17 @@ + # include "need_errno.h" + #endif + +-#if defined(__MINGW64_VERSION_MAJOR) || defined(__BORLANDC__) +-# define int64_t LONGLONG +-# define uint64_t ULONGLONG +-#elif !defined(__MINGW32__) +-# define int64_t _int64 +-# define uint64_t unsigned _int64 +-# if defined(PTW32_CONFIG_MSVC6) +- typedef long intptr_t; +-# endif +-#endif ++//#if defined(__MINGW64_VERSION_MAJOR) || defined(__BORLANDC__) ++//# define int64_t LONGLONG ++//# define uint64_t ULONGLONG ++//#elif !defined(__MINGW32__) ++//# define int64_t _int64 ++//# define uint64_t unsigned _int64 ++//# if defined(PTW32_CONFIG_MSVC6) ++// typedef long intptr_t; ++//# endif ++//#endif ++#include + + /* + * In case ETIMEDOUT hasn't been defined above somehow. diff --git a/fetchcontent/texgenpack-patch-cf548ef583ca9592a55ea217b0ec43a2e25b9cbe.patch b/fetchcontent/texgenpack-patch-cf548ef583ca9592a55ea217b0ec43a2e25b9cbe.patch new file mode 100644 index 0000000..4bceeab --- /dev/null +++ b/fetchcontent/texgenpack-patch-cf548ef583ca9592a55ea217b0ec43a2e25b9cbe.patch @@ -0,0 +1,891 @@ +diff --git a/astc.c b/astc.c +index 1ba68db..22b385e 100644 +--- a/astc.c ++++ b/astc.c +@@ -108,42 +108,44 @@ void decompress_astc_file(const char *filename, Image *image) { + } + + void compress_image_to_astc_texture(Image *image, int texture_type, Texture *texture) { +- // Create temporary .png filename +- char *tmp_filename = tmpnam(NULL); +- char *png_filename = (char *)alloca(strlen(tmp_filename) + 5); +- strcpy(png_filename, tmp_filename); +- strcat(png_filename, ".png"); +- // Save the image to the file. +- save_png_file(image, png_filename); +- // Create temporary .astc filename +- tmp_filename = tmpnam(NULL); +- char *astc_filename = (char *)alloca(strlen(tmp_filename) + 6); +- strcpy(astc_filename, tmp_filename); +- strcat(astc_filename, ".astc"); +- // Execute encoding command. +- char *s = (char *)malloc(strlen(png_filename) + strlen(astc_filename) + 40); +- char *astcenc_speed_option; +- if (option_compression_level < SPEED_FAST) +- astcenc_speed_option = "-fast"; +- else if (option_compression_level < SPEED_MEDIUM) +- astcenc_speed_option = "-medium"; +- else if (option_compression_level < SPEED_SLOW) +- astcenc_speed_option = "-thorough"; +- else +- astcenc_speed_option = "-exhaustive"; +- TextureInfo *info = match_texture_type(texture_type); +- sprintf(s, "astcenc -c %s %s %dx%d %s -silentmode", png_filename, astc_filename, info->block_width, +- info->block_height, astcenc_speed_option); +- printf("Executing command %s\n", s); +- int r = system(s); +- remove(png_filename); +- if (r == - 1) { +- printf("Error executing command during ASTC encoding.\n"); +- exit(1); +- } +- free(s); +- // Load the created .astc texture. +- load_astc_file(astc_filename, texture); +- remove(astc_filename); ++ printf("Error -- astc compression support stripped.\n"); ++ exit(1); ++ //// Create temporary .png filename ++ //char *tmp_filename = tmpnam(NULL); ++ //char *png_filename = (char *)alloca(strlen(tmp_filename) + 5); ++ //strcpy(png_filename, tmp_filename); ++ //strcat(png_filename, ".png"); ++ //// Save the image to the file. ++ //save_png_file(image, png_filename); ++ //// Create temporary .astc filename ++ //tmp_filename = tmpnam(NULL); ++ //char *astc_filename = (char *)alloca(strlen(tmp_filename) + 6); ++ //strcpy(astc_filename, tmp_filename); ++ //strcat(astc_filename, ".astc"); ++ //// Execute encoding command. ++ //char *s = (char *)malloc(strlen(png_filename) + strlen(astc_filename) + 40); ++ //char *astcenc_speed_option; ++ //if (option_compression_level < SPEED_FAST) ++ // astcenc_speed_option = "-fast"; ++ //else if (option_compression_level < SPEED_MEDIUM) ++ // astcenc_speed_option = "-medium"; ++ //else if (option_compression_level < SPEED_SLOW) ++ // astcenc_speed_option = "-thorough"; ++ //else ++ // astcenc_speed_option = "-exhaustive"; ++ //TextureInfo *info = match_texture_type(texture_type); ++ //sprintf(s, "astcenc -c %s %s %dx%d %s -silentmode", png_filename, astc_filename, info->block_width, ++ // info->block_height, astcenc_speed_option); ++ //printf("Executing command %s\n", s); ++ //int r = system(s); ++ //remove(png_filename); ++ //if (r == - 1) { ++ // printf("Error executing command during ASTC encoding.\n"); ++ // exit(1); ++ //} ++ //free(s); ++ //// Load the created .astc texture. ++ //load_astc_file(astc_filename, texture); ++ //remove(astc_filename); + } + +diff --git a/decode.h b/decode.h +index dbd98ca..9944221 100644 +--- a/decode.h ++++ b/decode.h +@@ -16,6 +16,11 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + */ + ++#ifdef TEXGENPACK_EXPORTS ++#define TEXGENPACK_API __declspec(dllexport) ++#else ++#define TEXGENPACK_API __declspec(dllimport) ++#endif + + // Flags values for the decoding functions. + +@@ -83,29 +88,29 @@ int get_astc_block_size_height(int astc_block_type); + + // Functions defined in bptc.c + +-int draw_block4x4_bptc(const unsigned char *bitstring, unsigned int *image_buffer, int flags); +-int block4x4_bptc_get_mode(const unsigned char *bitstring); +-int draw_block4x4_bptc_float(const unsigned char *bitstring, unsigned int *image_buffer, int flags); +-int draw_block4x4_bptc_signed_float(const unsigned char *bitstring, unsigned int *image_buffer, int flags); +-int block4x4_bptc_float_get_mode(const unsigned char *bitstring); +-void block4x4_bptc_set_mode(unsigned char *bitstring, int flags); +-void block4x4_bptc_float_set_mode(unsigned char *bitstring, int flags); ++TEXGENPACK_API int draw_block4x4_bptc(const unsigned char *bitstring, unsigned int *image_buffer, int flags); ++TEXGENPACK_API int block4x4_bptc_get_mode(const unsigned char *bitstring); ++TEXGENPACK_API int draw_block4x4_bptc_float(const unsigned char *bitstring, unsigned int *image_buffer, int flags); ++TEXGENPACK_API int draw_block4x4_bptc_signed_float(const unsigned char *bitstring, unsigned int *image_buffer, int flags); ++TEXGENPACK_API int block4x4_bptc_float_get_mode(const unsigned char *bitstring); ++TEXGENPACK_API void block4x4_bptc_set_mode(unsigned char *bitstring, int flags); ++TEXGENPACK_API void block4x4_bptc_float_set_mode(unsigned char *bitstring, int flags); + // Try to preinitialize colors for particular modes. +-void bptc_set_block_colors(unsigned char *bitstring, int flags, unsigned int *colors); ++TEXGENPACK_API void bptc_set_block_colors(unsigned char *bitstring, int flags, unsigned int *colors); + + // Functions defined in rgtc.c + +-int draw_block4x4_rgtc1(const unsigned char *bitstring, unsigned int *image_buffer, int flags); +-int draw_block4x4_signed_rgtc1(const unsigned char *bitstring, unsigned int *image_buffer, int flags); +-int draw_block4x4_rgtc2(const unsigned char *bitstring, unsigned int *image_buffer, int flags); +-int draw_block4x4_signed_rgtc2(const unsigned char *bitstring, unsigned int *image_buffer, int flags); ++TEXGENPACK_API int draw_block4x4_rgtc1(const unsigned char *bitstring, unsigned int *image_buffer, int flags); ++TEXGENPACK_API int draw_block4x4_signed_rgtc1(const unsigned char *bitstring, unsigned int *image_buffer, int flags); ++TEXGENPACK_API int draw_block4x4_rgtc2(const unsigned char *bitstring, unsigned int *image_buffer, int flags); ++TEXGENPACK_API int draw_block4x4_signed_rgtc2(const unsigned char *bitstring, unsigned int *image_buffer, int flags); + + // Function defined in texture.c. + +-int draw_block4x4_uncompressed(const unsigned char *bitstring, unsigned int *image_buffer, int flags); +-int draw_block4x4_argb8(const unsigned char *bitstring, unsigned int *image_buffer, int flags); +-int draw_block4x4_uncompressed_rgb_half_float(const unsigned char *bitstring, unsigned int *image_buffer, int flags); +-int draw_block4x4_uncompressed_rgba_half_float(const unsigned char *bitstring, unsigned int *image_buffer, int flags); +-int draw_block4x4_uncompressed_r_half_float(const unsigned char *bitstring, unsigned int *image_buffer, int flags); +-int draw_block4x4_uncompressed_rg_half_float(const unsigned char *bitstring, unsigned int *image_buffer, int flags); ++TEXGENPACK_API int draw_block4x4_uncompressed(const unsigned char *bitstring, unsigned int *image_buffer, int flags); ++TEXGENPACK_API int draw_block4x4_argb8(const unsigned char *bitstring, unsigned int *image_buffer, int flags); ++TEXGENPACK_API int draw_block4x4_uncompressed_rgb_half_float(const unsigned char *bitstring, unsigned int *image_buffer, int flags); ++TEXGENPACK_API int draw_block4x4_uncompressed_rgba_half_float(const unsigned char *bitstring, unsigned int *image_buffer, int flags); ++TEXGENPACK_API int draw_block4x4_uncompressed_r_half_float(const unsigned char *bitstring, unsigned int *image_buffer, int flags); ++TEXGENPACK_API int draw_block4x4_uncompressed_rg_half_float(const unsigned char *bitstring, unsigned int *image_buffer, int flags); + +diff --git a/file.c b/file.c +index f6a53c0..b51d2c4 100644 +--- a/file.c ++++ b/file.c +@@ -21,7 +21,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + #include + #include + #include +-#include ++//#include + #include "texgenpack.h" + #include "decode.h" + #include "packing.h" +@@ -842,204 +842,204 @@ void load_ppm_file(const char *filename, Image *image) { + fclose(f); + } + +-// Load a .png file. +- +-void load_png_file(const char *filename, Image *image) { +- int png_width, png_height; +- png_byte color_type; +- png_byte bit_depth; +- png_structp png_ptr; +- png_infop info_ptr; +- int number_of_passes; +- png_bytep *row_pointers; +- +- png_byte header[8]; // 8 is the maximum size that can be checked +- +- FILE *fp = fopen(filename, "rb"); +- if (!fp) { +- printf("Error - file %s could not be opened for reading.\n", filename); +- exit(1); +- } +- fread(header, 1, 8, fp); +- if (png_sig_cmp(header, 0, 8)) { +- printf("Error - file %s is not recognized as a PNG file.\n", filename); +- exit(1); +- } +- +- png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); +- +- if (!png_ptr) { +- printf("png_create_read_struct failed\n"); +- exit(1); +- } +- +- info_ptr = png_create_info_struct(png_ptr); +- if (!info_ptr) { +- printf("png_create_info_struct failed\n"); +- exit(1); +- } +- +- if (setjmp(png_jmpbuf(png_ptr))) { +- printf("Error during init_io."); +- exit(1); +- } +- +- png_init_io(png_ptr, fp); +- png_set_sig_bytes(png_ptr, 8); +- +- png_read_info(png_ptr, info_ptr); +- +- png_width = png_get_image_width(png_ptr, info_ptr); +- png_height = png_get_image_height(png_ptr, info_ptr); +- color_type = png_get_color_type(png_ptr, info_ptr); +- bit_depth = png_get_bit_depth(png_ptr, info_ptr); +- +- number_of_passes = png_set_interlace_handling(png_ptr); +- png_read_update_info(png_ptr, info_ptr); +- +- /* read file */ +- if (setjmp(png_jmpbuf(png_ptr))) { +- printf("Error during read_image.\n"); +- exit(1); +- } +- +- row_pointers = (png_bytep *)malloc(sizeof(png_bytep) * png_height); +- for (int y = 0; y < png_height; y++) +- row_pointers[y] = (png_byte *)malloc(png_get_rowbytes(png_ptr, info_ptr)); +- +- png_read_image(png_ptr, row_pointers); +- +- fclose(fp); +- +- if (!option_quiet) { +- printf("Loading .png image with size (%d x %d), bit depth %d", png_width, png_height, bit_depth); +- if (color_type == PNG_COLOR_TYPE_RGBA) +- printf(", with alpha.\n"); +- else +- printf(".\n"); +- } +- if (color_type != PNG_COLOR_TYPE_GRAY && color_type != PNG_COLOR_TYPE_RGB && color_type != PNG_COLOR_TYPE_RGBA) { +- printf("Error - unrecognized color format.\n"); +- exit(1); +- } +- if (bit_depth != 8) { +- printf("Error - expected bit depth of 8 in PNG file.\n"); +- exit(1); +- } +- +- image->width = png_width; +- image->height = png_height; +- // In the current version, extending the image to a 4x4 block boundary is no longer necessary. +- image->extended_width = png_width; +- image->extended_height = png_height; +- image->pixels = (unsigned int *)malloc(image->extended_width * image->extended_height * 4); +- image->nu_components = 3; +- image->bits_per_component = 8; +- image->srgb = 0; +- image->is_half_float = 0; +- image->is_signed = 0; +- // The internal image format after reading a PNG file is always 32 bits per pixel. +- if (color_type == PNG_COLOR_TYPE_GRAY) { +- image->alpha_bits = 0; +- image->nu_components = 1; +- for (int y = 0; y < image->height; y++) +- for (int x = 0; x < image->width; x++) { +- unsigned int pixel = (unsigned int)*(row_pointers[y] + x) + +- 0xFF000000; +- *(image->pixels + y * image->extended_width + x) = pixel; +- } +- } +- else if (color_type == PNG_COLOR_TYPE_RGB) { +- image->alpha_bits = 0; +- for (int y = 0; y < image->height; y++) +- for (int x = 0; x < image->width; x++) { +- memcpy(image->pixels + y * image->extended_width + x, row_pointers[y] + x * 3, 3); +- // Set the alpha byte to 0xFF. +- *((unsigned char *)&image->pixels[y * image->extended_width + x] + 3) = 0xFF; +- } +- } +- else { +- image->alpha_bits = 8; +- image->nu_components = 4; +- for (int y = 0; y < image->height; y++) +- memcpy(image->pixels + y * image->extended_width, row_pointers[y], image->width * 4); +- } +- if (image->alpha_bits >= 8) { +- check_1bit_alpha(image); +- if (image->alpha_bits == 1) +- if (!option_quiet) +- printf("1-bit alpha detected.\n"); +- } +- pad_image_borders(image); +- for (int y = 0; y < png_height; y++) +- free(row_pointers[y]); +- free(row_pointers); +-} +- +-// Save a .png file. +- +-void save_png_file(Image *image, const char *filename) { +- FILE *fp; +- png_structp png_ptr; +- png_infop info_ptr; +- +- if (image->is_half_float) { +- convert_image_from_half_float(image, 0, 1.0, 1.0); +- } +- else +- if (image->bits_per_component != 8) { +- printf("Error -- cannot write PNG file with non 8-bit components.\n"); +- exit(1); +- } +- +- if (!option_quiet) +- printf("Writing .png file %s.\n", filename); +- fp = fopen(filename, "wb"); +- if (fp == NULL) { +- printf("Error - file %s could not be opened for writing.\n", filename); +- exit(1); +- } +- png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); +- if (png_ptr == NULL) { +- printf("Error using libpng.\n"); +- exit(1); +- } +- info_ptr = png_create_info_struct(png_ptr); +- if (info_ptr == NULL) { +- printf("Error using libpng.\n"); +- exit(1); +- } +- if (setjmp(png_jmpbuf(png_ptr))) { +- /* If we get here, we had a problem writing the file. */ +- printf("Error writing png file %s.\n", filename); +- exit(1); +- } +- png_init_io(png_ptr, fp); +- int t; +- if (image->nu_components == 1) +- t = PNG_COLOR_TYPE_GRAY; +- else if (image->alpha_bits > 0) +- t = PNG_COLOR_TYPE_RGBA; +- else +- t = PNG_COLOR_TYPE_RGB; +- png_set_IHDR(png_ptr, info_ptr, image->width, image->height, 8, t, +- PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); +- +- png_write_info(png_ptr, info_ptr); +- +- if (t == PNG_COLOR_TYPE_GRAY || image->alpha_bits == 0) +- // We have RGB (or one component) data in 32-bit pixels with the last byte +- // or bytes unused. +- png_set_filler(png_ptr, 0, PNG_FILLER_AFTER); +- +- png_byte **row_pointers = (png_byte **)alloca(image->height * sizeof(png_byte *)); +- for (int y = 0; y < image->height; y++) +- row_pointers[y] = (png_byte *)(image->pixels + y * image->extended_width); +- +- png_write_image(png_ptr, row_pointers); +- +- png_write_end(png_ptr, info_ptr); +- png_destroy_write_struct(&png_ptr, (png_infopp)NULL); +- fclose(fp); +-} ++//// Load a .png file. ++// ++//void load_png_file(const char *filename, Image *image) { ++// int png_width, png_height; ++// png_byte color_type; ++// png_byte bit_depth; ++// png_structp png_ptr; ++// png_infop info_ptr; ++// int number_of_passes; ++// png_bytep *row_pointers; ++// ++// png_byte header[8]; // 8 is the maximum size that can be checked ++// ++// FILE *fp = fopen(filename, "rb"); ++// if (!fp) { ++// printf("Error - file %s could not be opened for reading.\n", filename); ++// exit(1); ++// } ++// fread(header, 1, 8, fp); ++// if (png_sig_cmp(header, 0, 8)) { ++// printf("Error - file %s is not recognized as a PNG file.\n", filename); ++// exit(1); ++// } ++// ++// png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); ++// ++// if (!png_ptr) { ++// printf("png_create_read_struct failed\n"); ++// exit(1); ++// } ++// ++// info_ptr = png_create_info_struct(png_ptr); ++// if (!info_ptr) { ++// printf("png_create_info_struct failed\n"); ++// exit(1); ++// } ++// ++// if (setjmp(png_jmpbuf(png_ptr))) { ++// printf("Error during init_io."); ++// exit(1); ++// } ++// ++// png_init_io(png_ptr, fp); ++// png_set_sig_bytes(png_ptr, 8); ++// ++// png_read_info(png_ptr, info_ptr); ++// ++// png_width = png_get_image_width(png_ptr, info_ptr); ++// png_height = png_get_image_height(png_ptr, info_ptr); ++// color_type = png_get_color_type(png_ptr, info_ptr); ++// bit_depth = png_get_bit_depth(png_ptr, info_ptr); ++// ++// number_of_passes = png_set_interlace_handling(png_ptr); ++// png_read_update_info(png_ptr, info_ptr); ++// ++// /* read file */ ++// if (setjmp(png_jmpbuf(png_ptr))) { ++// printf("Error during read_image.\n"); ++// exit(1); ++// } ++// ++// row_pointers = (png_bytep *)malloc(sizeof(png_bytep) * png_height); ++// for (int y = 0; y < png_height; y++) ++// row_pointers[y] = (png_byte *)malloc(png_get_rowbytes(png_ptr, info_ptr)); ++// ++// png_read_image(png_ptr, row_pointers); ++// ++// fclose(fp); ++// ++// if (!option_quiet) { ++// printf("Loading .png image with size (%d x %d), bit depth %d", png_width, png_height, bit_depth); ++// if (color_type == PNG_COLOR_TYPE_RGBA) ++// printf(", with alpha.\n"); ++// else ++// printf(".\n"); ++// } ++// if (color_type != PNG_COLOR_TYPE_GRAY && color_type != PNG_COLOR_TYPE_RGB && color_type != PNG_COLOR_TYPE_RGBA) { ++// printf("Error - unrecognized color format.\n"); ++// exit(1); ++// } ++// if (bit_depth != 8) { ++// printf("Error - expected bit depth of 8 in PNG file.\n"); ++// exit(1); ++// } ++// ++// image->width = png_width; ++// image->height = png_height; ++// // In the current version, extending the image to a 4x4 block boundary is no longer necessary. ++// image->extended_width = png_width; ++// image->extended_height = png_height; ++// image->pixels = (unsigned int *)malloc(image->extended_width * image->extended_height * 4); ++// image->nu_components = 3; ++// image->bits_per_component = 8; ++// image->srgb = 0; ++// image->is_half_float = 0; ++// image->is_signed = 0; ++// // The internal image format after reading a PNG file is always 32 bits per pixel. ++// if (color_type == PNG_COLOR_TYPE_GRAY) { ++// image->alpha_bits = 0; ++// image->nu_components = 1; ++// for (int y = 0; y < image->height; y++) ++// for (int x = 0; x < image->width; x++) { ++// unsigned int pixel = (unsigned int)*(row_pointers[y] + x) + ++// 0xFF000000; ++// *(image->pixels + y * image->extended_width + x) = pixel; ++// } ++// } ++// else if (color_type == PNG_COLOR_TYPE_RGB) { ++// image->alpha_bits = 0; ++// for (int y = 0; y < image->height; y++) ++// for (int x = 0; x < image->width; x++) { ++// memcpy(image->pixels + y * image->extended_width + x, row_pointers[y] + x * 3, 3); ++// // Set the alpha byte to 0xFF. ++// *((unsigned char *)&image->pixels[y * image->extended_width + x] + 3) = 0xFF; ++// } ++// } ++// else { ++// image->alpha_bits = 8; ++// image->nu_components = 4; ++// for (int y = 0; y < image->height; y++) ++// memcpy(image->pixels + y * image->extended_width, row_pointers[y], image->width * 4); ++// } ++// if (image->alpha_bits >= 8) { ++// check_1bit_alpha(image); ++// if (image->alpha_bits == 1) ++// if (!option_quiet) ++// printf("1-bit alpha detected.\n"); ++// } ++// pad_image_borders(image); ++// for (int y = 0; y < png_height; y++) ++// free(row_pointers[y]); ++// free(row_pointers); ++//} ++// ++//// Save a .png file. ++// ++//void save_png_file(Image *image, const char *filename) { ++// FILE *fp; ++// png_structp png_ptr; ++// png_infop info_ptr; ++// ++// if (image->is_half_float) { ++// convert_image_from_half_float(image, 0, 1.0, 1.0); ++// } ++// else ++// if (image->bits_per_component != 8) { ++// printf("Error -- cannot write PNG file with non 8-bit components.\n"); ++// exit(1); ++// } ++// ++// if (!option_quiet) ++// printf("Writing .png file %s.\n", filename); ++// fp = fopen(filename, "wb"); ++// if (fp == NULL) { ++// printf("Error - file %s could not be opened for writing.\n", filename); ++// exit(1); ++// } ++// png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); ++// if (png_ptr == NULL) { ++// printf("Error using libpng.\n"); ++// exit(1); ++// } ++// info_ptr = png_create_info_struct(png_ptr); ++// if (info_ptr == NULL) { ++// printf("Error using libpng.\n"); ++// exit(1); ++// } ++// if (setjmp(png_jmpbuf(png_ptr))) { ++// /* If we get here, we had a problem writing the file. */ ++// printf("Error writing png file %s.\n", filename); ++// exit(1); ++// } ++// png_init_io(png_ptr, fp); ++// int t; ++// if (image->nu_components == 1) ++// t = PNG_COLOR_TYPE_GRAY; ++// else if (image->alpha_bits > 0) ++// t = PNG_COLOR_TYPE_RGBA; ++// else ++// t = PNG_COLOR_TYPE_RGB; ++// png_set_IHDR(png_ptr, info_ptr, image->width, image->height, 8, t, ++// PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); ++// ++// png_write_info(png_ptr, info_ptr); ++// ++// if (t == PNG_COLOR_TYPE_GRAY || image->alpha_bits == 0) ++// // We have RGB (or one component) data in 32-bit pixels with the last byte ++// // or bytes unused. ++// png_set_filler(png_ptr, 0, PNG_FILLER_AFTER); ++// ++// png_byte **row_pointers = (png_byte **)alloca(image->height * sizeof(png_byte *)); ++// for (int y = 0; y < image->height; y++) ++// row_pointers[y] = (png_byte *)(image->pixels + y * image->extended_width); ++// ++// png_write_image(png_ptr, row_pointers); ++// ++// png_write_end(png_ptr, info_ptr); ++// png_destroy_write_struct(&png_ptr, (png_infopp)NULL); ++// fclose(fp); ++//} + +diff --git a/image.c b/image.c +index 3358804..4a5accb 100644 +--- a/image.c ++++ b/image.c +@@ -65,9 +65,9 @@ void load_image(const char *filename, int filetype, Image *image) { + // case FILE_TYPE_PPM : + // load_ppm_file(filename, image); + // break; +- case FILE_TYPE_PNG : +- load_png_file(filename, image); +- break; ++// case FILE_TYPE_PNG : ++// load_png_file(filename, image); ++// break; + default : + printf("Error -- no support for loading image file format.\n"); + exit(1); +@@ -122,9 +122,9 @@ void save_image(Image *image, const char *filename, int filetype) { + // case FILE_TYPE_PPM : + // load_ppm_file(filename, image); + // break; +- case FILE_TYPE_PNG : +- save_png_file(image, filename); +- break; ++// case FILE_TYPE_PNG : ++// save_png_file(image, filename); ++// break; + default : + printf("Error -- no support for saving image file format.\n"); + exit(1); +diff --git a/texgenpack.h b/texgenpack.h +index 1ed22b9..9440a40 100644 +--- a/texgenpack.h ++++ b/texgenpack.h +@@ -15,6 +15,11 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + */ ++#ifdef TEXGENPACK_EXPORTS ++#define TEXGENPACK_API __declspec(dllexport) ++#else ++#define TEXGENPACK_API __declspec(dllimport) ++#endif + + #define NU_FILE_TYPES 6 + +@@ -211,70 +216,97 @@ enum { + // Slow preset 32 tries per block, nu_generations = 100. + #define SPEED_SLOW (COMPRESSION_LEVEL_CLASS_1 + 24) + +-extern int command; +-extern int option_verbose; +-extern int option_max_threads; +-extern int option_orientation; +-extern int option_compression_level; +-extern int option_progress; +-extern int option_modal_etc2; +-extern int option_allowed_modes_etc2; +-extern int option_generations; +-extern int option_islands; +-extern int option_generations_second_pass; +-extern int option_islands_second_pass; +-extern int option_texture_format; +-extern int option_flip_vertical; +-extern int option_quiet; +-extern int option_block_width; +-extern int option_block_height; +-extern int option_half_float; +-extern int option_deterministic; +-extern int option_hdr; +-extern int option_perceptive; ++#ifdef __cplusplus ++extern "C" ++{ ++#endif ++TEXGENPACK_API extern int command; ++TEXGENPACK_API extern int option_verbose; ++TEXGENPACK_API extern int option_max_threads; ++TEXGENPACK_API extern int option_orientation; ++TEXGENPACK_API extern int option_compression_level; ++TEXGENPACK_API extern int option_progress; ++TEXGENPACK_API extern int option_modal_etc2; ++TEXGENPACK_API extern int option_allowed_modes_etc2; ++TEXGENPACK_API extern int option_generations; ++TEXGENPACK_API extern int option_islands; ++TEXGENPACK_API extern int option_generations_second_pass; ++TEXGENPACK_API extern int option_islands_second_pass; ++TEXGENPACK_API extern int option_texture_format; ++TEXGENPACK_API extern int option_flip_vertical; ++TEXGENPACK_API extern int option_quiet; ++TEXGENPACK_API extern int option_block_width; ++TEXGENPACK_API extern int option_block_height; ++TEXGENPACK_API extern int option_half_float; ++TEXGENPACK_API extern int option_deterministic; ++TEXGENPACK_API extern int option_hdr; ++TEXGENPACK_API extern int option_perceptive; ++#ifdef __cplusplus ++} ++#endif + + // Defined in image.c + ++#ifdef __cplusplus ++extern "C" ++{ ++#endif + void load_image(const char *filename, int filetype, Image *image); + int load_mipmap_images(const char *filename, int filetype, int max_images, Image *image); +-void save_image(Image *image, const char *filename, int filetype); +-double compare_images(Image *image1, Image *image2); ++TEXGENPACK_API void save_image(Image *image, const char *filename, int filetype); ++TEXGENPACK_API double compare_images(Image *image1, Image *image2); + int load_texture(const char *filename, int filetype, int max_mipmaps, Texture *texture); + void save_texture(Texture *texture, int nu_mipmaps, const char *filename, int filetype); +-void convert_texture_to_image(Texture *texture, Image *image); +-void destroy_texture(Texture *texture); +-void destroy_image(Image *image); +-void clone_image(Image *image1, Image *image2); +-void clear_image(Image *image); +-void pad_image_borders(Image *image); +-void check_1bit_alpha(Image *image); +-void convert_image_from_srgb_to_rgb(Image *source_image, Image *dest_image); +-void convert_image_from_rgb_to_srgb(Image *source_image, Image *dest_image); +-void copy_image_to_uncompressed_texture(Image *image, int texture_type, Texture *texture); +-void flip_image_vertical(Image *image); +-void print_image_info(Image *image); +-void calculate_image_dynamic_range(Image *image, float *range_min_out, float *range_max_out); +-void convert_image_from_half_float(Image *image, float range_min, float range_max, float gamma); +-void convert_image_to_half_float(Image *image); +-void extend_half_float_image_to_rgb(Image *image); +-void remove_alpha_from_image(Image *image); +-void add_alpha_to_image(Image *image); +-void convert_image_from_16_bit_format(Image *image); +-void convert_image_to_16_bit_format(Image *image, int nu_components, int signed_format); +-void convert_image_from_8_bit_format(Image *image); +-void convert_image_to_8_bit_format(Image *image, int nu_components, int signed_format); ++TEXGENPACK_API void convert_texture_to_image(Texture *texture, Image *image); ++TEXGENPACK_API void destroy_texture(Texture *texture); ++TEXGENPACK_API void destroy_image(Image *image); ++TEXGENPACK_API void clone_image(Image *image1, Image *image2); ++TEXGENPACK_API void clear_image(Image *image); ++TEXGENPACK_API void pad_image_borders(Image *image); ++TEXGENPACK_API void check_1bit_alpha(Image *image); ++TEXGENPACK_API void convert_image_from_srgb_to_rgb(Image *source_image, Image *dest_image); ++TEXGENPACK_API void convert_image_from_rgb_to_srgb(Image *source_image, Image *dest_image); ++TEXGENPACK_API void copy_image_to_uncompressed_texture(Image *image, int texture_type, Texture *texture); ++TEXGENPACK_API void flip_image_vertical(Image *image); ++TEXGENPACK_API void print_image_info(Image *image); ++TEXGENPACK_API void calculate_image_dynamic_range(Image *image, float *range_min_out, float *range_max_out); ++TEXGENPACK_API void convert_image_from_half_float(Image *image, float range_min, float range_max, float gamma); ++TEXGENPACK_API void convert_image_to_half_float(Image *image); ++TEXGENPACK_API void extend_half_float_image_to_rgb(Image *image); ++TEXGENPACK_API void remove_alpha_from_image(Image *image); ++TEXGENPACK_API void add_alpha_to_image(Image *image); ++TEXGENPACK_API void convert_image_from_16_bit_format(Image *image); ++TEXGENPACK_API void convert_image_to_16_bit_format(Image *image, int nu_components, int signed_format); ++TEXGENPACK_API void convert_image_from_8_bit_format(Image *image); ++TEXGENPACK_API void convert_image_to_8_bit_format(Image *image, int nu_components, int signed_format); ++#ifdef __cplusplus ++} ++#endif + + // Defined in compress.c + +-void compress_image(Image *image, int texture_type, CompressCallbackFunction func, Texture *texture, ++#ifdef __cplusplus ++extern "C" ++{ ++#endif ++TEXGENPACK_API void compress_image(Image *image, int texture_type, CompressCallbackFunction func, Texture *texture, + int genetic_parameters, float mutation_prob, float crossover_prob); ++#ifdef __cplusplus ++} ++#endif + + // Defined in mipmap.c + +-void generate_mipmap_level_from_original(Image *source_image, int level, Image *dest_image); +-void generate_mipmap_level_from_previous_level(Image *source_image, Image *dest_image); +-int count_mipmap_levels(Image *image); +- ++#ifdef __cplusplus ++extern "C" ++{ ++#endif ++TEXGENPACK_API void generate_mipmap_level_from_original(Image *source_image, int level, Image *dest_image); ++TEXGENPACK_API void generate_mipmap_level_from_previous_level(Image *source_image, Image *dest_image); ++TEXGENPACK_API int count_mipmap_levels(Image *image); ++#ifdef __cplusplus ++} ++#endif + // Defined in file.c + + void load_pkm_file(const char *filename, Texture *texture); +@@ -286,58 +318,85 @@ void save_dds_file(Texture *texture, int nu_mipmaps, const char *filename); + void load_astc_file(const char *filename, Texture *texture); + void save_astc_file(Texture *texture, const char *filename); + void load_ppm_file(const char *filename, Image *image); +-void load_png_file(const char *filename, Image *image); +-void save_png_file(Image *image, const char *filename); ++//void load_png_file(const char *filename, Image *image); ++//void save_png_file(Image *image, const char *filename); + + // Defined in texture.c + +-TextureInfo *match_texture_type(int type); +-TextureInfo *match_texture_description(const char *s); +-TextureInfo *match_ktx_id(int gl_internal_format, int gl_format, int gl_type); +-TextureInfo *match_dds_id(const char *four_cc, int dx10_format, uint32_t pixel_format_flags, int bitcount, ++#ifdef __cplusplus ++extern "C" ++{ ++#endif ++TEXGENPACK_API TextureInfo *match_texture_type(int type); ++TEXGENPACK_API TextureInfo *match_texture_description(const char *s); ++TEXGENPACK_API TextureInfo *match_ktx_id(int gl_internal_format, int gl_format, int gl_type); ++TEXGENPACK_API TextureInfo *match_dds_id(const char *four_cc, int dx10_format, uint32_t pixel_format_flags, int bitcount, + uint32_t red_mask, uint32_t green_mask, uint32_t blue_mask, uint32_t alpha_mask); +-const char *texture_type_text(int texture_type); +-int get_number_of_texture_formats(); +-const char *get_texture_format_index_text(int i, int j); +-void set_texture_decoding_function(Texture *texture, Image *image); ++TEXGENPACK_API const char *texture_type_text(int texture_type); ++TEXGENPACK_API int get_number_of_texture_formats(); ++TEXGENPACK_API const char *get_texture_format_index_text(int i, int j); ++TEXGENPACK_API void set_texture_decoding_function(Texture *texture, Image *image); ++#ifdef __cplusplus ++} ++#endif + + // Defined in compare.c + +-extern float *half_float_table; +-extern float *gamma_corrected_half_float_table; +-extern float *normalized_float_table; +- +-double compare_block_any_size_rgba(unsigned int *image_buffer, BlockUserData *user_data); +-double compare_block_4x4_rgb(unsigned int *image_buffer, BlockUserData *user_data); +-double compare_block_perceptive_4x4_rgb(unsigned int *image_buffer, BlockUserData *user_data); +-double compare_block_4x4_rgba(unsigned int *image_buffer, BlockUserData *user_data); +-double compare_block_perceptive_4x4_rgba(unsigned int *image_buffer, BlockUserData *user_data); +-void calculate_normalized_float_table(); +-double compare_block_4x4_rgb8_with_half_float(unsigned int *image_buffer, BlockUserData *user_data); +-double compare_block_4x4_rgba8_with_half_float(unsigned int *image_buffer, BlockUserData *user_data); +-double compare_block_4x4_8_bit_components(unsigned int *image_buffer, BlockUserData *user_data); +-double compare_block_4x4_signed_8_bit_components(unsigned int *image_buffer, BlockUserData *user_data); +-double compare_block_4x4_8_bit_components_with_16_bit(unsigned int *image_buffer, BlockUserData *user_data); +-double compare_block_4x4_signed_8_bit_components_with_16_bit(unsigned int *image_buffer, BlockUserData *user_data); +-double compare_block_4x4_r16(unsigned int *image_buffer, BlockUserData *user_data); +-double compare_block_4x4_rg16(unsigned int *image_buffer, BlockUserData *user_data); +-double compare_block_4x4_r16_signed(unsigned int *image_buffer, BlockUserData *user_data); +-double compare_block_4x4_rg16_signed(unsigned int *image_buffer, BlockUserData *user_data); +-void calculate_half_float_table(); +-double compare_block_4x4_rgb_half_float(unsigned int *image_buffer, BlockUserData *user_data); +-double compare_block_4x4_rgba_half_float(unsigned int *image_buffer, BlockUserData *user_data); +-double compare_block_4x4_r_half_float(unsigned int *image_buffer, BlockUserData *user_data); +-double compare_block_4x4_rg_half_float(unsigned int *image_buffer, BlockUserData *user_data); +-void calculate_gamma_corrected_half_float_table(); +-double compare_block_4x4_rgb_half_float_hdr(unsigned int *image_buffer, BlockUserData *user_data); +-double compare_block_4x4_rgba_half_float_hdr(unsigned int *image_buffer, BlockUserData *user_data); ++#ifdef __cplusplus ++extern "C" ++{ ++#endif ++TEXGENPACK_API extern float *half_float_table; ++TEXGENPACK_API extern float *gamma_corrected_half_float_table; ++TEXGENPACK_API extern float *normalized_float_table; ++ ++TEXGENPACK_API double compare_block_any_size_rgba(unsigned int *image_buffer, BlockUserData *user_data); ++TEXGENPACK_API double compare_block_4x4_rgb(unsigned int *image_buffer, BlockUserData *user_data); ++TEXGENPACK_API double compare_block_perceptive_4x4_rgb(unsigned int *image_buffer, BlockUserData *user_data); ++TEXGENPACK_API double compare_block_4x4_rgba(unsigned int *image_buffer, BlockUserData *user_data); ++TEXGENPACK_API double compare_block_perceptive_4x4_rgba(unsigned int *image_buffer, BlockUserData *user_data); ++TEXGENPACK_API void calculate_normalized_float_table(); ++TEXGENPACK_API double compare_block_4x4_rgb8_with_half_float(unsigned int *image_buffer, BlockUserData *user_data); ++TEXGENPACK_API double compare_block_4x4_rgba8_with_half_float(unsigned int *image_buffer, BlockUserData *user_data); ++TEXGENPACK_API double compare_block_4x4_8_bit_components(unsigned int *image_buffer, BlockUserData *user_data); ++TEXGENPACK_API double compare_block_4x4_signed_8_bit_components(unsigned int *image_buffer, BlockUserData *user_data); ++TEXGENPACK_API double compare_block_4x4_8_bit_components_with_16_bit(unsigned int *image_buffer, BlockUserData *user_data); ++TEXGENPACK_API double compare_block_4x4_signed_8_bit_components_with_16_bit(unsigned int *image_buffer, BlockUserData *user_data); ++TEXGENPACK_API double compare_block_4x4_r16(unsigned int *image_buffer, BlockUserData *user_data); ++TEXGENPACK_API double compare_block_4x4_rg16(unsigned int *image_buffer, BlockUserData *user_data); ++TEXGENPACK_API double compare_block_4x4_r16_signed(unsigned int *image_buffer, BlockUserData *user_data); ++TEXGENPACK_API double compare_block_4x4_rg16_signed(unsigned int *image_buffer, BlockUserData *user_data); ++TEXGENPACK_API void calculate_half_float_table(); ++TEXGENPACK_API double compare_block_4x4_rgb_half_float(unsigned int *image_buffer, BlockUserData *user_data); ++TEXGENPACK_API double compare_block_4x4_rgba_half_float(unsigned int *image_buffer, BlockUserData *user_data); ++TEXGENPACK_API double compare_block_4x4_r_half_float(unsigned int *image_buffer, BlockUserData *user_data); ++TEXGENPACK_API double compare_block_4x4_rg_half_float(unsigned int *image_buffer, BlockUserData *user_data); ++TEXGENPACK_API void calculate_gamma_corrected_half_float_table(); ++TEXGENPACK_API double compare_block_4x4_rgb_half_float_hdr(unsigned int *image_buffer, BlockUserData *user_data); ++TEXGENPACK_API double compare_block_4x4_rgba_half_float_hdr(unsigned int *image_buffer, BlockUserData *user_data); ++#ifdef __cplusplus ++} ++#endif + + // Defined in half_float.c + +-int halfp2singles(void *target, void *source, int numel); +-int singles2halfp(void *target, void *source, int numel); ++#ifdef __cplusplus ++extern "C" ++{ ++#endif ++TEXGENPACK_API int halfp2singles(void *target, void *source, int numel); ++TEXGENPACK_API int singles2halfp(void *target, void *source, int numel); ++#ifdef __cplusplus ++} ++#endif + + // Defined in calibrate.c +- +-void calibrate_genetic_parameters(Image *image, int texture_type); +- ++ ++#ifdef __cplusplus ++extern "C" ++{ ++#endif ++TEXGENPACK_API void calibrate_genetic_parameters(Image *image, int texture_type); ++#ifdef __cplusplus ++} ++#endif diff --git a/inc/LZMA/7zTypes.h b/inc/LZMA/7zTypes.h new file mode 100644 index 0000000..778413e --- /dev/null +++ b/inc/LZMA/7zTypes.h @@ -0,0 +1,256 @@ +/* 7zTypes.h -- Basic types +2013-11-12 : Igor Pavlov : Public domain */ + +#ifndef __7Z_TYPES_H +#define __7Z_TYPES_H + +#ifdef _WIN32 +/* #include */ +#endif + +#include + +#ifndef EXTERN_C_BEGIN +#ifdef __cplusplus +#define EXTERN_C_BEGIN extern "C" { +#define EXTERN_C_END } +#else +#define EXTERN_C_BEGIN +#define EXTERN_C_END +#endif +#endif + +EXTERN_C_BEGIN + +#define SZ_OK 0 + +#define SZ_ERROR_DATA 1 +#define SZ_ERROR_MEM 2 +#define SZ_ERROR_CRC 3 +#define SZ_ERROR_UNSUPPORTED 4 +#define SZ_ERROR_PARAM 5 +#define SZ_ERROR_INPUT_EOF 6 +#define SZ_ERROR_OUTPUT_EOF 7 +#define SZ_ERROR_READ 8 +#define SZ_ERROR_WRITE 9 +#define SZ_ERROR_PROGRESS 10 +#define SZ_ERROR_FAIL 11 +#define SZ_ERROR_THREAD 12 + +#define SZ_ERROR_ARCHIVE 16 +#define SZ_ERROR_NO_ARCHIVE 17 + +typedef int SRes; + +#ifdef _WIN32 +/* typedef DWORD WRes; */ +typedef unsigned WRes; +#else +typedef int WRes; +#endif + +#ifndef RINOK +#define RINOK(x) { int __result__ = (x); if (__result__ != 0) return __result__; } +#endif + +typedef unsigned char Byte; +typedef short Int16; +typedef unsigned short UInt16; + +#ifdef _LZMA_UINT32_IS_ULONG +typedef long Int32; +typedef unsigned long UInt32; +#else +typedef int Int32; +typedef unsigned int UInt32; +#endif + +#ifdef _SZ_NO_INT_64 + +/* define _SZ_NO_INT_64, if your compiler doesn't support 64-bit integers. + NOTES: Some code will work incorrectly in that case! */ + +typedef long Int64; +typedef unsigned long UInt64; + +#else + +#if defined(_MSC_VER) || defined(__BORLANDC__) +typedef __int64 Int64; +typedef unsigned __int64 UInt64; +#define UINT64_CONST(n) n +#else +typedef long long int Int64; +typedef unsigned long long int UInt64; +#define UINT64_CONST(n) n ## ULL +#endif + +#endif + +#ifdef _LZMA_NO_SYSTEM_SIZE_T +typedef UInt32 SizeT; +#else +typedef size_t SizeT; +#endif + +typedef int Bool; +#define True 1 +#define False 0 + + +#ifdef _WIN32 +#define MY_STD_CALL __stdcall +#else +#define MY_STD_CALL +#endif + +#ifdef _MSC_VER + +#if _MSC_VER >= 1300 +#define MY_NO_INLINE __declspec(noinline) +#else +#define MY_NO_INLINE +#endif + +#define MY_CDECL __cdecl +#define MY_FAST_CALL __fastcall + +#else + +#define MY_NO_INLINE +#define MY_CDECL +#define MY_FAST_CALL + +#endif + + +/* The following interfaces use first parameter as pointer to structure */ + +typedef struct +{ + Byte (*Read)(void *p); /* reads one byte, returns 0 in case of EOF or error */ +} IByteIn; + +typedef struct +{ + void (*Write)(void *p, Byte b); +} IByteOut; + +typedef struct +{ + SRes (*Read)(void *p, void *buf, size_t *size); + /* if (input(*size) != 0 && output(*size) == 0) means end_of_stream. + (output(*size) < input(*size)) is allowed */ +} ISeqInStream; + +/* it can return SZ_ERROR_INPUT_EOF */ +SRes SeqInStream_Read(ISeqInStream *stream, void *buf, size_t size); +SRes SeqInStream_Read2(ISeqInStream *stream, void *buf, size_t size, SRes errorType); +SRes SeqInStream_ReadByte(ISeqInStream *stream, Byte *buf); + +typedef struct +{ + size_t (*Write)(void *p, const void *buf, size_t size); + /* Returns: result - the number of actually written bytes. + (result < size) means error */ +} ISeqOutStream; + +typedef enum +{ + SZ_SEEK_SET = 0, + SZ_SEEK_CUR = 1, + SZ_SEEK_END = 2 +} ESzSeek; + +typedef struct +{ + SRes (*Read)(void *p, void *buf, size_t *size); /* same as ISeqInStream::Read */ + SRes (*Seek)(void *p, Int64 *pos, ESzSeek origin); +} ISeekInStream; + +typedef struct +{ + SRes (*Look)(void *p, const void **buf, size_t *size); + /* if (input(*size) != 0 && output(*size) == 0) means end_of_stream. + (output(*size) > input(*size)) is not allowed + (output(*size) < input(*size)) is allowed */ + SRes (*Skip)(void *p, size_t offset); + /* offset must be <= output(*size) of Look */ + + SRes (*Read)(void *p, void *buf, size_t *size); + /* reads directly (without buffer). It's same as ISeqInStream::Read */ + SRes (*Seek)(void *p, Int64 *pos, ESzSeek origin); +} ILookInStream; + +SRes LookInStream_LookRead(ILookInStream *stream, void *buf, size_t *size); +SRes LookInStream_SeekTo(ILookInStream *stream, UInt64 offset); + +/* reads via ILookInStream::Read */ +SRes LookInStream_Read2(ILookInStream *stream, void *buf, size_t size, SRes errorType); +SRes LookInStream_Read(ILookInStream *stream, void *buf, size_t size); + +#define LookToRead_BUF_SIZE (1 << 14) + +typedef struct +{ + ILookInStream s; + ISeekInStream *realStream; + size_t pos; + size_t size; + Byte buf[LookToRead_BUF_SIZE]; +} CLookToRead; + +void LookToRead_CreateVTable(CLookToRead *p, int lookahead); +void LookToRead_Init(CLookToRead *p); + +typedef struct +{ + ISeqInStream s; + ILookInStream *realStream; +} CSecToLook; + +void SecToLook_CreateVTable(CSecToLook *p); + +typedef struct +{ + ISeqInStream s; + ILookInStream *realStream; +} CSecToRead; + +void SecToRead_CreateVTable(CSecToRead *p); + +typedef struct +{ + SRes (*Progress)(void *p, UInt64 inSize, UInt64 outSize); + /* Returns: result. (result != SZ_OK) means break. + Value (UInt64)(Int64)-1 for size means unknown value. */ +} ICompressProgress; + +typedef struct +{ + void *(*Alloc)(void *p, size_t size); + void (*Free)(void *p, void *address); /* address can be 0 */ +} ISzAlloc; + +#define IAlloc_Alloc(p, size) (p)->Alloc((p), size) +#define IAlloc_Free(p, a) (p)->Free((p), a) + +#ifdef _WIN32 + +#define CHAR_PATH_SEPARATOR '\\' +#define WCHAR_PATH_SEPARATOR L'\\' +#define STRING_PATH_SEPARATOR "\\" +#define WSTRING_PATH_SEPARATOR L"\\" + +#else + +#define CHAR_PATH_SEPARATOR '/' +#define WCHAR_PATH_SEPARATOR L'/' +#define STRING_PATH_SEPARATOR "/" +#define WSTRING_PATH_SEPARATOR L"/" + +#endif + +EXTERN_C_END + +#endif diff --git a/inc/LZMA/LzmaDec.h b/inc/LZMA/LzmaDec.h new file mode 100644 index 0000000..bf7f084 --- /dev/null +++ b/inc/LZMA/LzmaDec.h @@ -0,0 +1,231 @@ +/* LzmaDec.h -- LZMA Decoder +2009-02-07 : Igor Pavlov : Public domain */ + +#ifndef __LZMA_DEC_H +#define __LZMA_DEC_H + +#include "Types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* #define _LZMA_PROB32 */ +/* _LZMA_PROB32 can increase the speed on some CPUs, + but memory usage for CLzmaDec::probs will be doubled in that case */ + +#ifdef _LZMA_PROB32 +#define CLzmaProb UInt32 +#else +#define CLzmaProb UInt16 +#endif + + +/* ---------- LZMA Properties ---------- */ + +#define LZMA_PROPS_SIZE 5 + +typedef struct _CLzmaProps +{ + unsigned lc, lp, pb; + UInt32 dicSize; +} CLzmaProps; + +/* LzmaProps_Decode - decodes properties +Returns: + SZ_OK + SZ_ERROR_UNSUPPORTED - Unsupported properties +*/ + +SRes LzmaProps_Decode(CLzmaProps *p, const Byte *data, unsigned size); + + +/* ---------- LZMA Decoder state ---------- */ + +/* LZMA_REQUIRED_INPUT_MAX = number of required input bytes for worst case. + Num bits = log2((2^11 / 31) ^ 22) + 26 < 134 + 26 = 160; */ + +#define LZMA_REQUIRED_INPUT_MAX 20 + +typedef struct +{ + CLzmaProps prop; + CLzmaProb *probs; + Byte *dic; + const Byte *buf; + UInt32 range, code; + SizeT dicPos; + SizeT dicBufSize; + UInt32 processedPos; + UInt32 checkDicSize; + unsigned state; + UInt32 reps[4]; + unsigned remainLen; + int needFlush; + int needInitState; + UInt32 numProbs; + unsigned tempBufSize; + Byte tempBuf[LZMA_REQUIRED_INPUT_MAX]; +} CLzmaDec; + +#define LzmaDec_Construct(p) { (p)->dic = 0; (p)->probs = 0; } + +void LzmaDec_Init(CLzmaDec *p); + +/* There are two types of LZMA streams: + 0) Stream with end mark. That end mark adds about 6 bytes to compressed size. + 1) Stream without end mark. You must know exact uncompressed size to decompress such stream. */ + +typedef enum +{ + LZMA_FINISH_ANY, /* finish at any point */ + LZMA_FINISH_END /* block must be finished at the end */ +} ELzmaFinishMode; + +/* ELzmaFinishMode has meaning only if the decoding reaches output limit !!! + + You must use LZMA_FINISH_END, when you know that current output buffer + covers last bytes of block. In other cases you must use LZMA_FINISH_ANY. + + If LZMA decoder sees end marker before reaching output limit, it returns SZ_OK, + and output value of destLen will be less than output buffer size limit. + You can check status result also. + + You can use multiple checks to test data integrity after full decompression: + 1) Check Result and "status" variable. + 2) Check that output(destLen) = uncompressedSize, if you know real uncompressedSize. + 3) Check that output(srcLen) = compressedSize, if you know real compressedSize. + You must use correct finish mode in that case. */ + +typedef enum +{ + LZMA_STATUS_NOT_SPECIFIED, /* use main error code instead */ + LZMA_STATUS_FINISHED_WITH_MARK, /* stream was finished with end mark. */ + LZMA_STATUS_NOT_FINISHED, /* stream was not finished */ + LZMA_STATUS_NEEDS_MORE_INPUT, /* you must provide more input bytes */ + LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK /* there is probability that stream was finished without end mark */ +} ELzmaStatus; + +/* ELzmaStatus is used only as output value for function call */ + + +/* ---------- Interfaces ---------- */ + +/* There are 3 levels of interfaces: + 1) Dictionary Interface + 2) Buffer Interface + 3) One Call Interface + You can select any of these interfaces, but don't mix functions from different + groups for same object. */ + + +/* There are two variants to allocate state for Dictionary Interface: + 1) LzmaDec_Allocate / LzmaDec_Free + 2) LzmaDec_AllocateProbs / LzmaDec_FreeProbs + You can use variant 2, if you set dictionary buffer manually. + For Buffer Interface you must always use variant 1. + +LzmaDec_Allocate* can return: + SZ_OK + SZ_ERROR_MEM - Memory allocation error + SZ_ERROR_UNSUPPORTED - Unsupported properties +*/ + +SRes LzmaDec_AllocateProbs(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAlloc *alloc); +void LzmaDec_FreeProbs(CLzmaDec *p, ISzAlloc *alloc); + +SRes LzmaDec_Allocate(CLzmaDec *state, const Byte *prop, unsigned propsSize, ISzAlloc *alloc); +void LzmaDec_Free(CLzmaDec *state, ISzAlloc *alloc); + +/* ---------- Dictionary Interface ---------- */ + +/* You can use it, if you want to eliminate the overhead for data copying from + dictionary to some other external buffer. + You must work with CLzmaDec variables directly in this interface. + + STEPS: + LzmaDec_Constr() + LzmaDec_Allocate() + for (each new stream) + { + LzmaDec_Init() + while (it needs more decompression) + { + LzmaDec_DecodeToDic() + use data from CLzmaDec::dic and update CLzmaDec::dicPos + } + } + LzmaDec_Free() +*/ + +/* LzmaDec_DecodeToDic + + The decoding to internal dictionary buffer (CLzmaDec::dic). + You must manually update CLzmaDec::dicPos, if it reaches CLzmaDec::dicBufSize !!! + +finishMode: + It has meaning only if the decoding reaches output limit (dicLimit). + LZMA_FINISH_ANY - Decode just dicLimit bytes. + LZMA_FINISH_END - Stream must be finished after dicLimit. + +Returns: + SZ_OK + status: + LZMA_STATUS_FINISHED_WITH_MARK + LZMA_STATUS_NOT_FINISHED + LZMA_STATUS_NEEDS_MORE_INPUT + LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK + SZ_ERROR_DATA - Data error +*/ + +SRes LzmaDec_DecodeToDic(CLzmaDec *p, SizeT dicLimit, + const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status); + + +/* ---------- Buffer Interface ---------- */ + +/* It's zlib-like interface. + See LzmaDec_DecodeToDic description for information about STEPS and return results, + but you must use LzmaDec_DecodeToBuf instead of LzmaDec_DecodeToDic and you don't need + to work with CLzmaDec variables manually. + +finishMode: + It has meaning only if the decoding reaches output limit (*destLen). + LZMA_FINISH_ANY - Decode just destLen bytes. + LZMA_FINISH_END - Stream must be finished after (*destLen). +*/ + +SRes LzmaDec_DecodeToBuf(CLzmaDec *p, Byte *dest, SizeT *destLen, + const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status); + + +/* ---------- One Call Interface ---------- */ + +/* LzmaDecode + +finishMode: + It has meaning only if the decoding reaches output limit (*destLen). + LZMA_FINISH_ANY - Decode just destLen bytes. + LZMA_FINISH_END - Stream must be finished after (*destLen). + +Returns: + SZ_OK + status: + LZMA_STATUS_FINISHED_WITH_MARK + LZMA_STATUS_NOT_FINISHED + LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK + SZ_ERROR_DATA - Data error + SZ_ERROR_MEM - Memory allocation error + SZ_ERROR_UNSUPPORTED - Unsupported properties + SZ_ERROR_INPUT_EOF - It needs more bytes in input buffer (src). +*/ + +SRes LzmaDecode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, + const Byte *propData, unsigned propSize, ELzmaFinishMode finishMode, + ELzmaStatus *status, ISzAlloc *alloc); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/inc/LZMA/LzmaEnc.h b/inc/LZMA/LzmaEnc.h new file mode 100644 index 0000000..008373a --- /dev/null +++ b/inc/LZMA/LzmaEnc.h @@ -0,0 +1,80 @@ +/* LzmaEnc.h -- LZMA Encoder +2013-01-18 : Igor Pavlov : Public domain */ + +#ifndef __LZMA_ENC_H +#define __LZMA_ENC_H + + +#include "7zTypes.h" +#include "api.h" + +EXTERN_C_BEGIN + +#define LZMA_PROPS_SIZE 5 + +typedef struct _CLzmaEncProps +{ + int level; /* 0 <= level <= 9 */ + UInt32 dictSize; /* (1 << 12) <= dictSize <= (1 << 27) for 32-bit version + (1 << 12) <= dictSize <= (1 << 30) for 64-bit version + default = (1 << 24) */ + UInt64 reduceSize; /* estimated size of data that will be compressed. default = 0xFFFFFFFF. + Encoder uses this value to reduce dictionary size */ + int lc; /* 0 <= lc <= 8, default = 3 */ + int lp; /* 0 <= lp <= 4, default = 0 */ + int pb; /* 0 <= pb <= 4, default = 2 */ + int algo; /* 0 - fast, 1 - normal, default = 1 */ + int fb; /* 5 <= fb <= 273, default = 32 */ + int btMode; /* 0 - hashChain Mode, 1 - binTree mode - normal, default = 1 */ + int numHashBytes; /* 2, 3 or 4, default = 4 */ + UInt32 mc; /* 1 <= mc <= (1 << 30), default = 32 */ + unsigned writeEndMark; /* 0 - do not write EOPM, 1 - write EOPM, default = 0 */ + int numThreads; /* 1 or 2, default = 2 */ +} CLzmaEncProps; + +LIBCOMPRESSION_API void LzmaEncProps_Init(CLzmaEncProps *p); +LIBCOMPRESSION_API void LzmaEncProps_Normalize(CLzmaEncProps *p); +LIBCOMPRESSION_API UInt32 LzmaEncProps_GetDictSize(const CLzmaEncProps *props2); + + +/* ---------- CLzmaEncHandle Interface ---------- */ + +/* LzmaEnc_* functions can return the following exit codes: +Returns: + SZ_OK - OK + SZ_ERROR_MEM - Memory allocation error + SZ_ERROR_PARAM - Incorrect paramater in props + SZ_ERROR_WRITE - Write callback error. + SZ_ERROR_PROGRESS - some break from progress callback + SZ_ERROR_THREAD - errors in multithreading functions (only for Mt version) +*/ + +typedef void * CLzmaEncHandle; + +LIBCOMPRESSION_API CLzmaEncHandle LzmaEnc_Create(ISzAlloc *alloc); +LIBCOMPRESSION_API void LzmaEnc_Destroy(CLzmaEncHandle p, ISzAlloc *alloc, ISzAlloc *allocBig); +LIBCOMPRESSION_API SRes LzmaEnc_SetProps(CLzmaEncHandle p, const CLzmaEncProps *props); +LIBCOMPRESSION_API SRes LzmaEnc_WriteProperties(CLzmaEncHandle p, Byte *properties, SizeT *size); +LIBCOMPRESSION_API SRes LzmaEnc_Encode(CLzmaEncHandle p, ISeqOutStream *outStream, ISeqInStream *inStream, + ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig); +LIBCOMPRESSION_API SRes LzmaEnc_MemEncode(CLzmaEncHandle p, Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen, + int writeEndMark, ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig); + +/* ---------- One Call Interface ---------- */ + +/* LzmaEncode +Return code: + SZ_OK - OK + SZ_ERROR_MEM - Memory allocation error + SZ_ERROR_PARAM - Incorrect paramater + SZ_ERROR_OUTPUT_EOF - output buffer overflow + SZ_ERROR_THREAD - errors in multithreading functions (only for Mt version) +*/ + +LIBCOMPRESSION_API SRes LzmaEncode(Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen, + const CLzmaEncProps *props, Byte *propsEncoded, SizeT *propsSize, int writeEndMark, + ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig); + +EXTERN_C_END + +#endif diff --git a/inc/LZMA/LzmaLib.h b/inc/LZMA/LzmaLib.h new file mode 100644 index 0000000..e0f3891 --- /dev/null +++ b/inc/LZMA/LzmaLib.h @@ -0,0 +1,40 @@ +#pragma once +#include "LzmaEnc.h" +#include "LzmaDec.h" +#include +static void *SzAlloc(void *p, size_t size) { p = p; return malloc(size); } +static void SzFree(void *p, void *address) { p = p; free(address); } +static ISzAlloc g_Alloc = { SzAlloc, SzFree }; + +inline int LzmaCompress(unsigned char *dest, size_t *destLen, const unsigned char *src, size_t srcLen, + unsigned char *outProps, size_t *outPropsSize, + int level, /* 0 <= level <= 9, default = 5 */ + unsigned dictSize, /* use (1 << N) or (3 << N). 4 KB < dictSize <= 128 MB */ + int lc, /* 0 <= lc <= 8, default = 3 */ + int lp, /* 0 <= lp <= 4, default = 0 */ + int pb, /* 0 <= pb <= 4, default = 2 */ + int fb, /* 5 <= fb <= 273, default = 32 */ + int numThreads /* 1 or 2, default = 2 */ +) +{ + CLzmaEncProps props; + LzmaEncProps_Init(&props); + props.level = level; + props.dictSize = dictSize; + props.lc = lc; + props.lp = lp; + props.pb = pb; + props.fb = fb; + props.numThreads = numThreads; + + return LzmaEncode(dest, destLen, src, srcLen, &props, outProps, outPropsSize, 0, + NULL, &g_Alloc, &g_Alloc); +} + + +inline int LzmaUncompress(unsigned char *dest, size_t *destLen, const unsigned char *src, size_t *srcLen, + const unsigned char *props, size_t propsSize) +{ + ELzmaStatus status; + return LzmaDecode(dest, destLen, src, srcLen, props, (unsigned)propsSize, LZMA_FINISH_ANY, &status, &g_Alloc); +} diff --git a/inc/LZMA/Types.h b/inc/LZMA/Types.h new file mode 100644 index 0000000..7732c24 --- /dev/null +++ b/inc/LZMA/Types.h @@ -0,0 +1,254 @@ +/* Types.h -- Basic types +2010-10-09 : Igor Pavlov : Public domain */ + +#ifndef __7Z_TYPES_H +#define __7Z_TYPES_H + +#include + +#ifdef _WIN32 +#include +#endif + +#ifndef EXTERN_C_BEGIN +#ifdef __cplusplus +#define EXTERN_C_BEGIN extern "C" { +#define EXTERN_C_END } +#else +#define EXTERN_C_BEGIN +#define EXTERN_C_END +#endif +#endif + +EXTERN_C_BEGIN + +#define SZ_OK 0 + +#define SZ_ERROR_DATA 1 +#define SZ_ERROR_MEM 2 +#define SZ_ERROR_CRC 3 +#define SZ_ERROR_UNSUPPORTED 4 +#define SZ_ERROR_PARAM 5 +#define SZ_ERROR_INPUT_EOF 6 +#define SZ_ERROR_OUTPUT_EOF 7 +#define SZ_ERROR_READ 8 +#define SZ_ERROR_WRITE 9 +#define SZ_ERROR_PROGRESS 10 +#define SZ_ERROR_FAIL 11 +#define SZ_ERROR_THREAD 12 + +#define SZ_ERROR_ARCHIVE 16 +#define SZ_ERROR_NO_ARCHIVE 17 + +typedef int SRes; + +#ifdef _WIN32 +typedef DWORD WRes; +#else +typedef int WRes; +#endif + +#ifndef RINOK +#define RINOK(x) { int __result__ = (x); if (__result__ != 0) return __result__; } +#endif + +typedef unsigned char Byte; +typedef short Int16; +typedef unsigned short UInt16; + +#ifdef _LZMA_UINT32_IS_ULONG +typedef long Int32; +typedef unsigned long UInt32; +#else +typedef int Int32; +typedef unsigned int UInt32; +#endif + +#ifdef _SZ_NO_INT_64 + +/* define _SZ_NO_INT_64, if your compiler doesn't support 64-bit integers. + NOTES: Some code will work incorrectly in that case! */ + +typedef long Int64; +typedef unsigned long UInt64; + +#else + +#if defined(_MSC_VER) || defined(__BORLANDC__) +typedef __int64 Int64; +typedef unsigned __int64 UInt64; +#define UINT64_CONST(n) n +#else +typedef long long int Int64; +typedef unsigned long long int UInt64; +#define UINT64_CONST(n) n ## ULL +#endif + +#endif + +#ifdef _LZMA_NO_SYSTEM_SIZE_T +typedef UInt32 SizeT; +#else +typedef size_t SizeT; +#endif + +typedef int Bool; +#define True 1 +#define False 0 + + +#ifdef _WIN32 +#define MY_STD_CALL __stdcall +#else +#define MY_STD_CALL +#endif + +#ifdef _MSC_VER + +#if _MSC_VER >= 1300 +#define MY_NO_INLINE __declspec(noinline) +#else +#define MY_NO_INLINE +#endif + +#define MY_CDECL __cdecl +#define MY_FAST_CALL __fastcall + +#else + +#define MY_CDECL +#define MY_FAST_CALL + +#endif + + +/* The following interfaces use first parameter as pointer to structure */ + +typedef struct +{ + Byte (*Read)(void *p); /* reads one byte, returns 0 in case of EOF or error */ +} IByteIn; + +typedef struct +{ + void (*Write)(void *p, Byte b); +} IByteOut; + +typedef struct +{ + SRes (*Read)(void *p, void *buf, size_t *size); + /* if (input(*size) != 0 && output(*size) == 0) means end_of_stream. + (output(*size) < input(*size)) is allowed */ +} ISeqInStream; + +/* it can return SZ_ERROR_INPUT_EOF */ +SRes SeqInStream_Read(ISeqInStream *stream, void *buf, size_t size); +SRes SeqInStream_Read2(ISeqInStream *stream, void *buf, size_t size, SRes errorType); +SRes SeqInStream_ReadByte(ISeqInStream *stream, Byte *buf); + +typedef struct +{ + size_t (*Write)(void *p, const void *buf, size_t size); + /* Returns: result - the number of actually written bytes. + (result < size) means error */ +} ISeqOutStream; + +typedef enum +{ + SZ_SEEK_SET = 0, + SZ_SEEK_CUR = 1, + SZ_SEEK_END = 2 +} ESzSeek; + +typedef struct +{ + SRes (*Read)(void *p, void *buf, size_t *size); /* same as ISeqInStream::Read */ + SRes (*Seek)(void *p, Int64 *pos, ESzSeek origin); +} ISeekInStream; + +typedef struct +{ + SRes (*Look)(void *p, const void **buf, size_t *size); + /* if (input(*size) != 0 && output(*size) == 0) means end_of_stream. + (output(*size) > input(*size)) is not allowed + (output(*size) < input(*size)) is allowed */ + SRes (*Skip)(void *p, size_t offset); + /* offset must be <= output(*size) of Look */ + + SRes (*Read)(void *p, void *buf, size_t *size); + /* reads directly (without buffer). It's same as ISeqInStream::Read */ + SRes (*Seek)(void *p, Int64 *pos, ESzSeek origin); +} ILookInStream; + +SRes LookInStream_LookRead(ILookInStream *stream, void *buf, size_t *size); +SRes LookInStream_SeekTo(ILookInStream *stream, UInt64 offset); + +/* reads via ILookInStream::Read */ +SRes LookInStream_Read2(ILookInStream *stream, void *buf, size_t size, SRes errorType); +SRes LookInStream_Read(ILookInStream *stream, void *buf, size_t size); + +#define LookToRead_BUF_SIZE (1 << 14) + +typedef struct +{ + ILookInStream s; + ISeekInStream *realStream; + size_t pos; + size_t size; + Byte buf[LookToRead_BUF_SIZE]; +} CLookToRead; + +void LookToRead_CreateVTable(CLookToRead *p, int lookahead); +void LookToRead_Init(CLookToRead *p); + +typedef struct +{ + ISeqInStream s; + ILookInStream *realStream; +} CSecToLook; + +void SecToLook_CreateVTable(CSecToLook *p); + +typedef struct +{ + ISeqInStream s; + ILookInStream *realStream; +} CSecToRead; + +void SecToRead_CreateVTable(CSecToRead *p); + +typedef struct +{ + SRes (*Progress)(void *p, UInt64 inSize, UInt64 outSize); + /* Returns: result. (result != SZ_OK) means break. + Value (UInt64)(Int64)-1 for size means unknown value. */ +} ICompressProgress; + +typedef struct +{ + void *(*Alloc)(void *p, size_t size); + void (*Free)(void *p, void *address); /* address can be 0 */ +} ISzAlloc; + +#define IAlloc_Alloc(p, size) (p)->Alloc((p), size) +#define IAlloc_Free(p, a) (p)->Free((p), a) + +#ifdef _WIN32 + +#define CHAR_PATH_SEPARATOR '\\' +#define WCHAR_PATH_SEPARATOR L'\\' +#define STRING_PATH_SEPARATOR "\\" +#define WSTRING_PATH_SEPARATOR L"\\" + +#else + +#define CHAR_PATH_SEPARATOR '/' +#define WCHAR_PATH_SEPARATOR L'/' +#define STRING_PATH_SEPARATOR "/" +#define WSTRING_PATH_SEPARATOR L"/" + +#endif + +EXTERN_C_END + +#endif diff --git a/inc/LZMA/api.h b/inc/LZMA/api.h new file mode 100644 index 0000000..bfabd5d --- /dev/null +++ b/inc/LZMA/api.h @@ -0,0 +1,8 @@ +#pragma once + +#define LIBCOMPRESSION_API +#ifdef _LIBCOMPRESSION_EXPORT +//#define LIBCOMPRESSION_API __declspec(dllexport) +#else +//#define LIBCOMPRESSION_API __declspec(dllimport) +#endif \ No newline at end of file diff --git a/inc/half.hpp b/inc/half.hpp new file mode 100644 index 0000000..e86b837 --- /dev/null +++ b/inc/half.hpp @@ -0,0 +1,2909 @@ +// half - IEEE 754-based half-precision floating point library. +// +// Copyright (c) 2012-2013 Christian Rau +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, +// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// Version 1.11.0 + +/// \file +/// Main header file for half precision functionality. + +#ifndef HALF_HALF_HPP +#define HALF_HALF_HPP + +/// Combined gcc version number. +#define HALF_GNUC_VERSION (__GNUC__*100+__GNUC_MINOR__) + +//check C++11 language features +#if defined(__clang__) //clang + #if __has_feature(cxx_static_assert) && !defined(HALF_ENABLE_CPP11_STATIC_ASSERT) + #define HALF_ENABLE_CPP11_STATIC_ASSERT 1 + #endif + #if __has_feature(cxx_constexpr) && !defined(HALF_ENABLE_CPP11_CONSTEXPR) + #define HALF_ENABLE_CPP11_CONSTEXPR 1 + #endif + #if __has_feature(cxx_noexcept) && !defined(HALF_ENABLE_CPP11_NOEXCEPT) + #define HALF_ENABLE_CPP11_NOEXCEPT 1 + #endif + #if __has_feature(cxx_user_literals) && !defined(HALF_ENABLE_CPP11_USER_LITERALS) + #define HALF_ENABLE_CPP11_USER_LITERALS 1 + #endif + #if (defined(__GXX_EXPERIMENTAL_CXX0X__) || __cplusplus >= 201103L) && !defined(HALF_ENABLE_CPP11_LONG_LONG) + #define HALF_ENABLE_CPP11_LONG_LONG 1 + #endif +/*#elif defined(__INTEL_COMPILER) //Intel C++ + #if __INTEL_COMPILER >= 1100 && !defined(HALF_ENABLE_CPP11_STATIC_ASSERT) ???????? + #define HALF_ENABLE_CPP11_STATIC_ASSERT 1 + #endif + #if __INTEL_COMPILER >= 1300 && !defined(HALF_ENABLE_CPP11_CONSTEXPR) ???????? + #define HALF_ENABLE_CPP11_CONSTEXPR 1 + #endif + #if __INTEL_COMPILER >= 1300 && !defined(HALF_ENABLE_CPP11_NOEXCEPT) ???????? + #define HALF_ENABLE_CPP11_NOEXCEPT 1 + #endif + #if __INTEL_COMPILER >= 1100 && !defined(HALF_ENABLE_CPP11_LONG_LONG) ???????? + #define HALF_ENABLE_CPP11_LONG_LONG 1 + #endif*/ +#elif defined(__GNUC__) //gcc + #if defined(__GXX_EXPERIMENTAL_CXX0X__) || __cplusplus >= 201103L + #if HALF_GNUC_VERSION >= 403 && !defined(HALF_ENABLE_CPP11_STATIC_ASSERT) + #define HALF_ENABLE_CPP11_STATIC_ASSERT 1 + #endif + #if HALF_GNUC_VERSION >= 406 && !defined(HALF_ENABLE_CPP11_CONSTEXPR) + #define HALF_ENABLE_CPP11_CONSTEXPR 1 + #endif + #if HALF_GNUC_VERSION >= 406 && !defined(HALF_ENABLE_CPP11_NOEXCEPT) + #define HALF_ENABLE_CPP11_NOEXCEPT 1 + #endif + #if HALF_GNUC_VERSION >= 407 && !defined(HALF_ENABLE_CPP11_USER_LITERALS) + #define HALF_ENABLE_CPP11_USER_LITERALS 1 + #endif + #if !defined(HALF_ENABLE_CPP11_LONG_LONG) + #define HALF_ENABLE_CPP11_LONG_LONG 1 + #endif + #endif +#elif defined(_MSC_VER) //Visual C++ + #if _MSC_VER >= 1600 && !defined(HALF_ENABLE_CPP11_STATIC_ASSERT) + #define HALF_ENABLE_CPP11_STATIC_ASSERT 1 + #endif + #if _MSC_VER >= 1310 && !defined(HALF_ENABLE_CPP11_LONG_LONG) + #define HALF_ENABLE_CPP11_LONG_LONG 1 + #endif + #define HALF_POP_WARNINGS 1 + #pragma warning(push) + #pragma warning(disable : 4099 4127 4146) //struct vs class, constant in if, negative unsigned +#endif + +//check C++11 library features +#include +#if defined(_LIBCPP_VERSION) //libc++ + #if defined(__GXX_EXPERIMENTAL_CXX0X__) || __cplusplus >= 201103 + #ifndef HALF_ENABLE_CPP11_TYPE_TRAITS + #define HALF_ENABLE_CPP11_TYPE_TRAITS 1 + #endif + #ifndef HALF_ENABLE_CPP11_CSTDINT + #define HALF_ENABLE_CPP11_CSTDINT 1 + #endif + #ifndef HALF_ENABLE_CPP11_CMATH + #define HALF_ENABLE_CPP11_CMATH 1 + #endif + #ifndef HALF_ENABLE_CPP11_HASH + #define HALF_ENABLE_CPP11_HASH 1 + #endif + #endif +#elif defined(__GLIBCXX__) //libstdc++ + #if defined(__GXX_EXPERIMENTAL_CXX0X__) || __cplusplus >= 201103 + #ifdef __clang__ + #if __GLIBCXX__ >= 20080606 && !defined(HALF_ENABLE_CPP11_TYPE_TRAITS) + #define HALF_ENABLE_CPP11_TYPE_TRAITS 1 + #endif + #if __GLIBCXX__ >= 20080606 && !defined(HALF_ENABLE_CPP11_CSTDINT) + #define HALF_ENABLE_CPP11_CSTDINT 1 + #endif + #if __GLIBCXX__ >= 20080606 && !defined(HALF_ENABLE_CPP11_CMATH) + #define HALF_ENABLE_CPP11_CMATH 1 + #endif + #if __GLIBCXX__ >= 20080606 && !defined(HALF_ENABLE_CPP11_HASH) + #define HALF_ENABLE_CPP11_HASH 1 + #endif + #else + #if HALF_GNUC_VERSION >= 403 && !defined(HALF_ENABLE_CPP11_CSTDINT) + #define HALF_ENABLE_CPP11_CSTDINT 1 + #endif + #if HALF_GNUC_VERSION >= 403 && !defined(HALF_ENABLE_CPP11_CMATH) + #define HALF_ENABLE_CPP11_CMATH 1 + #endif + #if HALF_GNUC_VERSION >= 403 && !defined(HALF_ENABLE_CPP11_HASH) + #define HALF_ENABLE_CPP11_HASH 1 + #endif + #endif + #endif +#elif defined(_CPPLIB_VER) //Dinkumware/Visual C++ + #if _CPPLIB_VER >= 520 + #ifndef HALF_ENABLE_CPP11_TYPE_TRAITS + #define HALF_ENABLE_CPP11_TYPE_TRAITS 1 + #endif + #ifndef HALF_ENABLE_CPP11_CSTDINT + #define HALF_ENABLE_CPP11_CSTDINT 1 + #endif + #ifndef HALF_ENABLE_CPP11_HASH + #define HALF_ENABLE_CPP11_HASH 1 + #endif + #endif + #if _CPPLIB_VER >= 610 + #ifndef HALF_ENABLE_CPP11_CMATH + #define HALF_ENABLE_CPP11_CMATH 1 + #endif + #endif +#endif +#undef HALF_GNUC_VERSION + +//support constexpr +#if HALF_ENABLE_CPP11_CONSTEXPR + #define HALF_CONSTEXPR constexpr + #define HALF_CONSTEXPR_CONST constexpr +#else + #define HALF_CONSTEXPR + #define HALF_CONSTEXPR_CONST const +#endif + +//support noexcept +#if HALF_ENABLE_CPP11_NOEXCEPT + #define HALF_NOEXCEPT noexcept + #define HALF_NOTHROW noexcept +#else + #define HALF_NOEXCEPT + #define HALF_NOTHROW throw() +#endif + +#include +#include +#include +#include +#include +#include +#if HALF_ENABLE_CPP11_TYPE_TRAITS + #include +#endif +#if HALF_ENABLE_CPP11_CSTDINT + #include +#endif +#if HALF_ENABLE_CPP11_HASH + #include +#endif + + +/// Default rounding mode. +/// This specifies the rounding mode used for all conversions between [half](\ref half_float::half)s and `float`s as well as +/// for the half_cast() if not specifying a rounding mode explicitly. It can be redefined (before including half.hpp) to one +/// of the standard rounding modes using their respective constants or the equivalent values of `std::float_round_style`: +/// +/// `std::float_round_style` | value | rounding +/// ---------------------------------|-------|------------------------- +/// `std::round_indeterminate` | -1 | fastest (default) +/// `std::round_toward_zero` | 0 | toward zero +/// `std::round_to_nearest` | 1 | to nearest +/// `std::round_toward_infinity` | 2 | toward positive infinity +/// `std::round_toward_neg_infinity` | 3 | toward negative infinity +/// +/// By default this is set to `-1` (`std::round_indeterminate`), which uses truncation (round toward zero, but with overflows +/// set to infinity) and is the fastest rounding mode possible. It can even be set to `std::numeric_limits::round_style` +/// to synchronize the rounding mode with that of the underlying single-precision implementation. +#ifndef HALF_ROUND_STYLE + #define HALF_ROUND_STYLE -1 // = std::round_indeterminate +#endif + +/// Tie-breaking behaviour for round to nearest. +/// This specifies if ties in round to nearest should be resolved by rounding to the nearest even value. By default this is +/// defined to `0` resulting in the faster but slightly more biased behaviour of rounding away from zero in half-way cases (and +/// thus equal to the round() function), but can be redefined to `1` (before including half.hpp) if more IEEE-conformant +/// behaviour is needed. +#ifndef HALF_ROUND_TIES_TO_EVEN + #define HALF_ROUND_TIES_TO_EVEN 0 // ties away from zero +#endif + +/// Value signaling overflow. +/// In correspondence with `HUGE_VAL[F|L]` from `` this symbol expands to a positive value signaling the overflow of an +/// operation, in particular it just evaluates to positive infinity. +#define HUGE_VALH std::numeric_limits::infinity() + +/// Fast half-precision fma function. +/// This symbol is only defined if the fma() function generally executes as fast as, or faster than, a separate +/// half-precision multiplication followed by an addition. Due to the internal single-precision implementation of all +/// arithmetic operations, this is in fact always the case. +#define FP_FAST_FMAH 1 + +#ifndef FP_ILOGB0 + #define FP_ILOGB0 INT_MIN +#endif +#ifndef FP_ILOGBNAN + #define FP_ILOGBNAN INT_MAX +#endif +#ifndef FP_SUBNORMAL + #define FP_SUBNORMAL 0 +#endif +#ifndef FP_ZERO + #define FP_ZERO 1 +#endif +#ifndef FP_NAN + #define FP_NAN 2 +#endif +#ifndef FP_INFINITE + #define FP_INFINITE 3 +#endif +#ifndef FP_NORMAL + #define FP_NORMAL 4 +#endif + + +/// Main namespace for half precision functionality. +/// This namespace contains all the functionality provided by the library. +namespace half_float +{ + class half; + + /// \internal + /// \brief Implementation details. + namespace detail + { + #if HALF_ENABLE_CPP11_TYPE_TRAITS + /// Conditional type. + template struct conditional : std::conditional {}; + + /// Helper for tag dispatching. + template struct bool_type : std::integral_constant {}; + using std::true_type; + using std::false_type; + + /// Type traits for floating point types. + template struct is_float : std::is_floating_point {}; + #else + /// Conditional type. + template struct conditional { typedef T type; }; + template struct conditional { typedef F type; }; + + /// Helper for tag dispatching. + template struct bool_type {}; + typedef bool_type true_type; + typedef bool_type false_type; + + /// Type traits for floating point types. + template struct is_float : false_type {}; + template struct is_float : is_float {}; + template struct is_float : is_float {}; + template struct is_float : is_float {}; + template<> struct is_float : true_type {}; + template<> struct is_float : true_type {}; + template<> struct is_float : true_type {}; + #endif + + #if HALF_ENABLE_CPP11_CSTDINT + /// Unsigned integer of (at least) 16 bits width. + typedef std::uint_least16_t uint16; + + /// Unsigned integer of (at least) 32 bits width. + typedef std::uint_least32_t uint32; + + /// Fastest signed integer capable of holding all values of type uint16. + typedef std::int_fast32_t int17; + #else + /// Unsigned integer of (at least) 16 bits width. + typedef unsigned short uint16; + + /// Unsigned integer of (at least) 32 bits width. + typedef conditional::digits>=32,unsigned int,unsigned long>::type uint32; + + /// Fastest signed integer capable of holding all values of type uint16. + typedef conditional::digits>=16,int,long>::type int17; + #endif + + /// Tag type for binary construction. + struct binary_t {}; + + /// Tag for binary construction. + HALF_CONSTEXPR_CONST binary_t binary = binary_t(); + + /// Temporary half-precision expression. + /// This class represents a half-precision expression which just stores a single-precision value internally. + struct expr + { + /// Conversion constructor. + /// \param f single-precision value to convert + explicit HALF_CONSTEXPR expr(float f) : value_(f) {} + + /// Conversion to single-precision. + /// \return single precision value representing expression value + HALF_CONSTEXPR operator float() const { return value_; } + + private: + /// Internal expression value stored in single-precision. + float value_; + }; + + /// SFINAE helper for generic half-precision functions. + /// This class template has to be specialized for each valid combination of argument types to provide a corresponding + /// `type` member equivalent to \a T. + /// \tparam T type to return + template struct enable {}; + template struct enable { typedef T type; }; + template struct enable { typedef T type; }; + template struct enable { typedef T type; }; + template struct enable { typedef T type; }; + template struct enable { typedef T type; }; + template struct enable { typedef T type; }; + template struct enable { typedef T type; }; + template struct enable { typedef T type; }; + template struct enable { typedef T type; }; + template struct enable { typedef T type; }; + template struct enable { typedef T type; }; + template struct enable { typedef T type; }; + template struct enable { typedef T type; }; + template struct enable { typedef T type; }; + + /// Return type for specialized generic 2-argument half-precision functions. + /// This class template has to be specialized for each valid combination of argument types to provide a corresponding + /// `type` member denoting the appropriate return type. + /// \tparam T first argument type + /// \tparam U first argument type + template struct result : enable {}; + template<> struct result { typedef half type; }; + + /// \name Classification helpers + /// \{ + + /// Check for infinity. + /// \tparam T argument type (builtin floating point type) + /// \param arg value to query + /// \retval true if infinity + /// \retval false else + template bool builtin_isinf(T arg) + { + #if HALF_ENABLE_CPP11_CMATH + return std::isinf(arg); + #elif defined(_MSC_VER) + return !_finite(static_cast(arg)) && !_isnan(static_cast(arg)); + #else + return arg == std::numeric_limits::infinity() || arg == -std::numeric_limits::infinity(); + #endif + } + + /// Check for NaN. + /// \tparam T argument type (builtin floating point type) + /// \param arg value to query + /// \retval true if not a number + /// \retval false else + template bool builtin_isnan(T arg) + { + #if HALF_ENABLE_CPP11_CMATH + return std::isnan(arg); + #elif defined(_MSC_VER) + return _isnan(static_cast(arg)) != 0; + #else + return arg != arg; + #endif + } + + /// Check sign. + /// \tparam T argument type (builtin floating point type) + /// \param arg value to query + /// \retval true if signbit set + /// \retval false else + template bool builtin_signbit(T arg) + { + #if HALF_ENABLE_CPP11_CMATH + return std::signbit(arg); + #else + return arg < T() || (arg == T() && T(1)/arg < T()); + #endif + } + + /// \} + /// \name Conversion + /// \{ + + /// Convert IEEE single-precision to half-precision. + /// Credit for this goes to [Jeroen van der Zijp](ftp://ftp.fox-toolkit.org/pub/fasthalffloatconversion.pdf). + /// \tparam R rounding mode to use, `std::round_indeterminate` for fastest rounding + /// \param value single-precision value + /// \return binary representation of half-precision value + template uint16 float2half_impl(float value, true_type) + { + #if HALF_ENABLE_CPP11_STATIC_ASSERT + static_assert(std::numeric_limits::is_iec559, "float to half conversion needs IEEE 754 conformant 'float' type"); + static_assert(sizeof(uint32)==sizeof(float), "float to half conversion needs unsigned integer type of exactly the size of a 'float'"); + #endif + static const uint16 base_table[512] = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, 0x0100, + 0x0200, 0x0400, 0x0800, 0x0C00, 0x1000, 0x1400, 0x1800, 0x1C00, 0x2000, 0x2400, 0x2800, 0x2C00, 0x3000, 0x3400, 0x3800, 0x3C00, + 0x4000, 0x4400, 0x4800, 0x4C00, 0x5000, 0x5400, 0x5800, 0x5C00, 0x6000, 0x6400, 0x6800, 0x6C00, 0x7000, 0x7400, 0x7800, 0x7C00, + 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, + 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, + 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, + 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, + 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, + 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, + 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, 0x7C00, + 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, + 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, + 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, + 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, + 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, + 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, + 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8001, 0x8002, 0x8004, 0x8008, 0x8010, 0x8020, 0x8040, 0x8080, 0x8100, + 0x8200, 0x8400, 0x8800, 0x8C00, 0x9000, 0x9400, 0x9800, 0x9C00, 0xA000, 0xA400, 0xA800, 0xAC00, 0xB000, 0xB400, 0xB800, 0xBC00, + 0xC000, 0xC400, 0xC800, 0xCC00, 0xD000, 0xD400, 0xD800, 0xDC00, 0xE000, 0xE400, 0xE800, 0xEC00, 0xF000, 0xF400, 0xF800, 0xFC00, + 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, + 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, + 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, + 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, + 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, + 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, + 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00, 0xFC00 }; + static const unsigned char shift_table[512] = { + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 13, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 13 }; + uint32 bits;// = *reinterpret_cast(&value); //violating strict aliasing! + std::memcpy(&bits, &value, sizeof(float)); + uint16 hbits = base_table[bits>>23] + static_cast((bits&0x7FFFFF)>>shift_table[bits>>23]); + if(R == std::round_to_nearest) + hbits += (((bits&0x7FFFFF)>>(shift_table[bits>>23]-1))|(((bits>>23)&0xFF)==102)) & ((hbits&0x7C00)!=0x7C00) + #if HALF_ROUND_TIES_TO_EVEN + & (((((static_cast(1)<<(shift_table[bits>>23]-1))-1)&bits)!=0)|hbits) + #endif + ; + else if(R == std::round_toward_zero) + hbits -= ((hbits&0x7FFF)==0x7C00) & ~shift_table[bits>>23]; + else if(R == std::round_toward_infinity) + hbits += ((((bits&0x7FFFFF&((static_cast(1)<<(shift_table[bits>>23]))-1))!=0)|(((bits>>23)<=102)& + ((bits>>23)!=0)))&(hbits<0x7C00)) - ((hbits==0xFC00)&((bits>>23)!=511)); + else if(R == std::round_toward_neg_infinity) + hbits += ((((bits&0x7FFFFF&((static_cast(1)<<(shift_table[bits>>23]))-1))!=0)|(((bits>>23)<=358)& + ((bits>>23)!=256)))&(hbits<0xFC00)&(hbits>>15)) - ((hbits==0x7C00)&((bits>>23)!=255)); + return hbits; + } + + /// Convert non-IEEE single-precision to half-precision. + /// \param value single-precision value + /// \return binary representation of half-precision value + template uint16 float2half_impl(float value, false_type) + { + uint16 hbits = builtin_signbit(value) << 15; + if(value == 0.0f) + return hbits; + if(builtin_isnan(value)) + return hbits | 0x7FFF; + if(builtin_isinf(value)) + return hbits | 0x7C00; + int exp; + std::frexp(value, &exp); + if(exp > 16) + { + if(R == std::round_toward_zero) + return hbits | 0x7BFF; + else if(R == std::round_toward_infinity) + return hbits | 0x7C00 - (hbits>>15); + else if(R == std::round_toward_neg_infinity) + return hbits | 0x7BFF + (hbits>>15); + return hbits | 0x7C00; + } + if(exp < -13) + value = std::ldexp(value, 24); + else + { + value = std::ldexp(value, 11-exp); + hbits |= ((exp+14)<<10); + } + int ival = static_cast(value); + hbits |= static_cast(std::abs(ival)&0x3FF); + if(R == std::round_to_nearest) + { + float diff = std::abs(value-static_cast(ival)); + #if HALF_ROUND_TIES_TO_EVEN + hbits += (diff>0.5f) | ((diff==0.5f)&hbits); + #else + hbits += diff >= 0.5f; + #endif + } + else if(R == std::round_toward_infinity) + hbits += value > static_cast(ival); + else if(R == std::round_toward_neg_infinity) + hbits += value < static_cast(ival); + return hbits; + } + + /// Convert single-precision to half-precision. + /// \param value single-precision value + /// \return binary representation of half-precision value + template uint16 float2half(float value) + { + return float2half_impl(value, bool_type::is_iec559&&sizeof(uint32)==sizeof(float)>()); + } + + /// Convert integer to half-precision floating point. + /// \tparam R rounding mode to use, `std::round_indeterminate` for fastest rounding + /// \tparam S `true` if value negative, `false` else + /// \tparam T type to convert (builtin integer type) + /// \param value non-negative integral value + /// \return binary representation of half-precision value + template uint16 int2half_impl(T value) + { + if(S) + value = -value; + uint16 bits = S << 15; + if(value > 65504) + { + if(R == std::round_toward_infinity) + bits |= 0x7C00 - S; + else if(R == std::round_toward_neg_infinity) + bits |= 0x7BFF + S; + else + bits |= 0x7BFF + (R!=std::round_toward_zero); + } + else if(value) + { + unsigned int m = value, exp = 25; + for(; m<0x400; m<<=1,--exp) ; + for(; m>0x7FF; m>>=1,++exp) ; + bits |= (exp<<10) | (m&0x3FF); + if(exp > 25) + { + if(R == std::round_to_nearest) + bits += (value>>(exp-26)) & 1 + #if HALF_ROUND_TIES_TO_EVEN + & (((((1<<(exp-26))-1)&value)!=0)|bits) + #endif + ; + else if(R == std::round_toward_infinity) + bits += ((value&((1<<(exp-25))-1))!=0) & !S; + else if(R == std::round_toward_neg_infinity) + bits += ((value&((1<<(exp-25))-1))!=0) & S; + } + } + return bits; + } + + /// Convert integer to half-precision floating point. + /// \tparam R rounding mode to use, `std::round_indeterminate` for fastest rounding + /// \tparam T type to convert (builtin integer type) + /// \param value integral value + /// \return binary representation of half-precision value + template uint16 int2half(T value) + { + return (value<0) ? int2half_impl(value) : int2half_impl(value); + } + + /// Convert half-precision to IEEE single-precision. + /// Credit for this goes to [Jeroen van der Zijp](ftp://ftp.fox-toolkit.org/pub/fasthalffloatconversion.pdf). + /// \param value binary representation of half-precision value + /// \return single-precision value + inline float half2float_impl(uint16 value, true_type) + { + #if HALF_ENABLE_CPP11_STATIC_ASSERT + static_assert(std::numeric_limits::is_iec559, "half to float conversion needs IEEE 754 conformant 'float' type"); + static_assert(sizeof(uint32)==sizeof(float), "half to float conversion needs unsigned integer type of exactly the size of a 'float'"); + #endif + static const uint32 mantissa_table[2048] = { + 0x00000000, 0x33800000, 0x34000000, 0x34400000, 0x34800000, 0x34A00000, 0x34C00000, 0x34E00000, 0x35000000, 0x35100000, 0x35200000, 0x35300000, 0x35400000, 0x35500000, 0x35600000, 0x35700000, + 0x35800000, 0x35880000, 0x35900000, 0x35980000, 0x35A00000, 0x35A80000, 0x35B00000, 0x35B80000, 0x35C00000, 0x35C80000, 0x35D00000, 0x35D80000, 0x35E00000, 0x35E80000, 0x35F00000, 0x35F80000, + 0x36000000, 0x36040000, 0x36080000, 0x360C0000, 0x36100000, 0x36140000, 0x36180000, 0x361C0000, 0x36200000, 0x36240000, 0x36280000, 0x362C0000, 0x36300000, 0x36340000, 0x36380000, 0x363C0000, + 0x36400000, 0x36440000, 0x36480000, 0x364C0000, 0x36500000, 0x36540000, 0x36580000, 0x365C0000, 0x36600000, 0x36640000, 0x36680000, 0x366C0000, 0x36700000, 0x36740000, 0x36780000, 0x367C0000, + 0x36800000, 0x36820000, 0x36840000, 0x36860000, 0x36880000, 0x368A0000, 0x368C0000, 0x368E0000, 0x36900000, 0x36920000, 0x36940000, 0x36960000, 0x36980000, 0x369A0000, 0x369C0000, 0x369E0000, + 0x36A00000, 0x36A20000, 0x36A40000, 0x36A60000, 0x36A80000, 0x36AA0000, 0x36AC0000, 0x36AE0000, 0x36B00000, 0x36B20000, 0x36B40000, 0x36B60000, 0x36B80000, 0x36BA0000, 0x36BC0000, 0x36BE0000, + 0x36C00000, 0x36C20000, 0x36C40000, 0x36C60000, 0x36C80000, 0x36CA0000, 0x36CC0000, 0x36CE0000, 0x36D00000, 0x36D20000, 0x36D40000, 0x36D60000, 0x36D80000, 0x36DA0000, 0x36DC0000, 0x36DE0000, + 0x36E00000, 0x36E20000, 0x36E40000, 0x36E60000, 0x36E80000, 0x36EA0000, 0x36EC0000, 0x36EE0000, 0x36F00000, 0x36F20000, 0x36F40000, 0x36F60000, 0x36F80000, 0x36FA0000, 0x36FC0000, 0x36FE0000, + 0x37000000, 0x37010000, 0x37020000, 0x37030000, 0x37040000, 0x37050000, 0x37060000, 0x37070000, 0x37080000, 0x37090000, 0x370A0000, 0x370B0000, 0x370C0000, 0x370D0000, 0x370E0000, 0x370F0000, + 0x37100000, 0x37110000, 0x37120000, 0x37130000, 0x37140000, 0x37150000, 0x37160000, 0x37170000, 0x37180000, 0x37190000, 0x371A0000, 0x371B0000, 0x371C0000, 0x371D0000, 0x371E0000, 0x371F0000, + 0x37200000, 0x37210000, 0x37220000, 0x37230000, 0x37240000, 0x37250000, 0x37260000, 0x37270000, 0x37280000, 0x37290000, 0x372A0000, 0x372B0000, 0x372C0000, 0x372D0000, 0x372E0000, 0x372F0000, + 0x37300000, 0x37310000, 0x37320000, 0x37330000, 0x37340000, 0x37350000, 0x37360000, 0x37370000, 0x37380000, 0x37390000, 0x373A0000, 0x373B0000, 0x373C0000, 0x373D0000, 0x373E0000, 0x373F0000, + 0x37400000, 0x37410000, 0x37420000, 0x37430000, 0x37440000, 0x37450000, 0x37460000, 0x37470000, 0x37480000, 0x37490000, 0x374A0000, 0x374B0000, 0x374C0000, 0x374D0000, 0x374E0000, 0x374F0000, + 0x37500000, 0x37510000, 0x37520000, 0x37530000, 0x37540000, 0x37550000, 0x37560000, 0x37570000, 0x37580000, 0x37590000, 0x375A0000, 0x375B0000, 0x375C0000, 0x375D0000, 0x375E0000, 0x375F0000, + 0x37600000, 0x37610000, 0x37620000, 0x37630000, 0x37640000, 0x37650000, 0x37660000, 0x37670000, 0x37680000, 0x37690000, 0x376A0000, 0x376B0000, 0x376C0000, 0x376D0000, 0x376E0000, 0x376F0000, + 0x37700000, 0x37710000, 0x37720000, 0x37730000, 0x37740000, 0x37750000, 0x37760000, 0x37770000, 0x37780000, 0x37790000, 0x377A0000, 0x377B0000, 0x377C0000, 0x377D0000, 0x377E0000, 0x377F0000, + 0x37800000, 0x37808000, 0x37810000, 0x37818000, 0x37820000, 0x37828000, 0x37830000, 0x37838000, 0x37840000, 0x37848000, 0x37850000, 0x37858000, 0x37860000, 0x37868000, 0x37870000, 0x37878000, + 0x37880000, 0x37888000, 0x37890000, 0x37898000, 0x378A0000, 0x378A8000, 0x378B0000, 0x378B8000, 0x378C0000, 0x378C8000, 0x378D0000, 0x378D8000, 0x378E0000, 0x378E8000, 0x378F0000, 0x378F8000, + 0x37900000, 0x37908000, 0x37910000, 0x37918000, 0x37920000, 0x37928000, 0x37930000, 0x37938000, 0x37940000, 0x37948000, 0x37950000, 0x37958000, 0x37960000, 0x37968000, 0x37970000, 0x37978000, + 0x37980000, 0x37988000, 0x37990000, 0x37998000, 0x379A0000, 0x379A8000, 0x379B0000, 0x379B8000, 0x379C0000, 0x379C8000, 0x379D0000, 0x379D8000, 0x379E0000, 0x379E8000, 0x379F0000, 0x379F8000, + 0x37A00000, 0x37A08000, 0x37A10000, 0x37A18000, 0x37A20000, 0x37A28000, 0x37A30000, 0x37A38000, 0x37A40000, 0x37A48000, 0x37A50000, 0x37A58000, 0x37A60000, 0x37A68000, 0x37A70000, 0x37A78000, + 0x37A80000, 0x37A88000, 0x37A90000, 0x37A98000, 0x37AA0000, 0x37AA8000, 0x37AB0000, 0x37AB8000, 0x37AC0000, 0x37AC8000, 0x37AD0000, 0x37AD8000, 0x37AE0000, 0x37AE8000, 0x37AF0000, 0x37AF8000, + 0x37B00000, 0x37B08000, 0x37B10000, 0x37B18000, 0x37B20000, 0x37B28000, 0x37B30000, 0x37B38000, 0x37B40000, 0x37B48000, 0x37B50000, 0x37B58000, 0x37B60000, 0x37B68000, 0x37B70000, 0x37B78000, + 0x37B80000, 0x37B88000, 0x37B90000, 0x37B98000, 0x37BA0000, 0x37BA8000, 0x37BB0000, 0x37BB8000, 0x37BC0000, 0x37BC8000, 0x37BD0000, 0x37BD8000, 0x37BE0000, 0x37BE8000, 0x37BF0000, 0x37BF8000, + 0x37C00000, 0x37C08000, 0x37C10000, 0x37C18000, 0x37C20000, 0x37C28000, 0x37C30000, 0x37C38000, 0x37C40000, 0x37C48000, 0x37C50000, 0x37C58000, 0x37C60000, 0x37C68000, 0x37C70000, 0x37C78000, + 0x37C80000, 0x37C88000, 0x37C90000, 0x37C98000, 0x37CA0000, 0x37CA8000, 0x37CB0000, 0x37CB8000, 0x37CC0000, 0x37CC8000, 0x37CD0000, 0x37CD8000, 0x37CE0000, 0x37CE8000, 0x37CF0000, 0x37CF8000, + 0x37D00000, 0x37D08000, 0x37D10000, 0x37D18000, 0x37D20000, 0x37D28000, 0x37D30000, 0x37D38000, 0x37D40000, 0x37D48000, 0x37D50000, 0x37D58000, 0x37D60000, 0x37D68000, 0x37D70000, 0x37D78000, + 0x37D80000, 0x37D88000, 0x37D90000, 0x37D98000, 0x37DA0000, 0x37DA8000, 0x37DB0000, 0x37DB8000, 0x37DC0000, 0x37DC8000, 0x37DD0000, 0x37DD8000, 0x37DE0000, 0x37DE8000, 0x37DF0000, 0x37DF8000, + 0x37E00000, 0x37E08000, 0x37E10000, 0x37E18000, 0x37E20000, 0x37E28000, 0x37E30000, 0x37E38000, 0x37E40000, 0x37E48000, 0x37E50000, 0x37E58000, 0x37E60000, 0x37E68000, 0x37E70000, 0x37E78000, + 0x37E80000, 0x37E88000, 0x37E90000, 0x37E98000, 0x37EA0000, 0x37EA8000, 0x37EB0000, 0x37EB8000, 0x37EC0000, 0x37EC8000, 0x37ED0000, 0x37ED8000, 0x37EE0000, 0x37EE8000, 0x37EF0000, 0x37EF8000, + 0x37F00000, 0x37F08000, 0x37F10000, 0x37F18000, 0x37F20000, 0x37F28000, 0x37F30000, 0x37F38000, 0x37F40000, 0x37F48000, 0x37F50000, 0x37F58000, 0x37F60000, 0x37F68000, 0x37F70000, 0x37F78000, + 0x37F80000, 0x37F88000, 0x37F90000, 0x37F98000, 0x37FA0000, 0x37FA8000, 0x37FB0000, 0x37FB8000, 0x37FC0000, 0x37FC8000, 0x37FD0000, 0x37FD8000, 0x37FE0000, 0x37FE8000, 0x37FF0000, 0x37FF8000, + 0x38000000, 0x38004000, 0x38008000, 0x3800C000, 0x38010000, 0x38014000, 0x38018000, 0x3801C000, 0x38020000, 0x38024000, 0x38028000, 0x3802C000, 0x38030000, 0x38034000, 0x38038000, 0x3803C000, + 0x38040000, 0x38044000, 0x38048000, 0x3804C000, 0x38050000, 0x38054000, 0x38058000, 0x3805C000, 0x38060000, 0x38064000, 0x38068000, 0x3806C000, 0x38070000, 0x38074000, 0x38078000, 0x3807C000, + 0x38080000, 0x38084000, 0x38088000, 0x3808C000, 0x38090000, 0x38094000, 0x38098000, 0x3809C000, 0x380A0000, 0x380A4000, 0x380A8000, 0x380AC000, 0x380B0000, 0x380B4000, 0x380B8000, 0x380BC000, + 0x380C0000, 0x380C4000, 0x380C8000, 0x380CC000, 0x380D0000, 0x380D4000, 0x380D8000, 0x380DC000, 0x380E0000, 0x380E4000, 0x380E8000, 0x380EC000, 0x380F0000, 0x380F4000, 0x380F8000, 0x380FC000, + 0x38100000, 0x38104000, 0x38108000, 0x3810C000, 0x38110000, 0x38114000, 0x38118000, 0x3811C000, 0x38120000, 0x38124000, 0x38128000, 0x3812C000, 0x38130000, 0x38134000, 0x38138000, 0x3813C000, + 0x38140000, 0x38144000, 0x38148000, 0x3814C000, 0x38150000, 0x38154000, 0x38158000, 0x3815C000, 0x38160000, 0x38164000, 0x38168000, 0x3816C000, 0x38170000, 0x38174000, 0x38178000, 0x3817C000, + 0x38180000, 0x38184000, 0x38188000, 0x3818C000, 0x38190000, 0x38194000, 0x38198000, 0x3819C000, 0x381A0000, 0x381A4000, 0x381A8000, 0x381AC000, 0x381B0000, 0x381B4000, 0x381B8000, 0x381BC000, + 0x381C0000, 0x381C4000, 0x381C8000, 0x381CC000, 0x381D0000, 0x381D4000, 0x381D8000, 0x381DC000, 0x381E0000, 0x381E4000, 0x381E8000, 0x381EC000, 0x381F0000, 0x381F4000, 0x381F8000, 0x381FC000, + 0x38200000, 0x38204000, 0x38208000, 0x3820C000, 0x38210000, 0x38214000, 0x38218000, 0x3821C000, 0x38220000, 0x38224000, 0x38228000, 0x3822C000, 0x38230000, 0x38234000, 0x38238000, 0x3823C000, + 0x38240000, 0x38244000, 0x38248000, 0x3824C000, 0x38250000, 0x38254000, 0x38258000, 0x3825C000, 0x38260000, 0x38264000, 0x38268000, 0x3826C000, 0x38270000, 0x38274000, 0x38278000, 0x3827C000, + 0x38280000, 0x38284000, 0x38288000, 0x3828C000, 0x38290000, 0x38294000, 0x38298000, 0x3829C000, 0x382A0000, 0x382A4000, 0x382A8000, 0x382AC000, 0x382B0000, 0x382B4000, 0x382B8000, 0x382BC000, + 0x382C0000, 0x382C4000, 0x382C8000, 0x382CC000, 0x382D0000, 0x382D4000, 0x382D8000, 0x382DC000, 0x382E0000, 0x382E4000, 0x382E8000, 0x382EC000, 0x382F0000, 0x382F4000, 0x382F8000, 0x382FC000, + 0x38300000, 0x38304000, 0x38308000, 0x3830C000, 0x38310000, 0x38314000, 0x38318000, 0x3831C000, 0x38320000, 0x38324000, 0x38328000, 0x3832C000, 0x38330000, 0x38334000, 0x38338000, 0x3833C000, + 0x38340000, 0x38344000, 0x38348000, 0x3834C000, 0x38350000, 0x38354000, 0x38358000, 0x3835C000, 0x38360000, 0x38364000, 0x38368000, 0x3836C000, 0x38370000, 0x38374000, 0x38378000, 0x3837C000, + 0x38380000, 0x38384000, 0x38388000, 0x3838C000, 0x38390000, 0x38394000, 0x38398000, 0x3839C000, 0x383A0000, 0x383A4000, 0x383A8000, 0x383AC000, 0x383B0000, 0x383B4000, 0x383B8000, 0x383BC000, + 0x383C0000, 0x383C4000, 0x383C8000, 0x383CC000, 0x383D0000, 0x383D4000, 0x383D8000, 0x383DC000, 0x383E0000, 0x383E4000, 0x383E8000, 0x383EC000, 0x383F0000, 0x383F4000, 0x383F8000, 0x383FC000, + 0x38400000, 0x38404000, 0x38408000, 0x3840C000, 0x38410000, 0x38414000, 0x38418000, 0x3841C000, 0x38420000, 0x38424000, 0x38428000, 0x3842C000, 0x38430000, 0x38434000, 0x38438000, 0x3843C000, + 0x38440000, 0x38444000, 0x38448000, 0x3844C000, 0x38450000, 0x38454000, 0x38458000, 0x3845C000, 0x38460000, 0x38464000, 0x38468000, 0x3846C000, 0x38470000, 0x38474000, 0x38478000, 0x3847C000, + 0x38480000, 0x38484000, 0x38488000, 0x3848C000, 0x38490000, 0x38494000, 0x38498000, 0x3849C000, 0x384A0000, 0x384A4000, 0x384A8000, 0x384AC000, 0x384B0000, 0x384B4000, 0x384B8000, 0x384BC000, + 0x384C0000, 0x384C4000, 0x384C8000, 0x384CC000, 0x384D0000, 0x384D4000, 0x384D8000, 0x384DC000, 0x384E0000, 0x384E4000, 0x384E8000, 0x384EC000, 0x384F0000, 0x384F4000, 0x384F8000, 0x384FC000, + 0x38500000, 0x38504000, 0x38508000, 0x3850C000, 0x38510000, 0x38514000, 0x38518000, 0x3851C000, 0x38520000, 0x38524000, 0x38528000, 0x3852C000, 0x38530000, 0x38534000, 0x38538000, 0x3853C000, + 0x38540000, 0x38544000, 0x38548000, 0x3854C000, 0x38550000, 0x38554000, 0x38558000, 0x3855C000, 0x38560000, 0x38564000, 0x38568000, 0x3856C000, 0x38570000, 0x38574000, 0x38578000, 0x3857C000, + 0x38580000, 0x38584000, 0x38588000, 0x3858C000, 0x38590000, 0x38594000, 0x38598000, 0x3859C000, 0x385A0000, 0x385A4000, 0x385A8000, 0x385AC000, 0x385B0000, 0x385B4000, 0x385B8000, 0x385BC000, + 0x385C0000, 0x385C4000, 0x385C8000, 0x385CC000, 0x385D0000, 0x385D4000, 0x385D8000, 0x385DC000, 0x385E0000, 0x385E4000, 0x385E8000, 0x385EC000, 0x385F0000, 0x385F4000, 0x385F8000, 0x385FC000, + 0x38600000, 0x38604000, 0x38608000, 0x3860C000, 0x38610000, 0x38614000, 0x38618000, 0x3861C000, 0x38620000, 0x38624000, 0x38628000, 0x3862C000, 0x38630000, 0x38634000, 0x38638000, 0x3863C000, + 0x38640000, 0x38644000, 0x38648000, 0x3864C000, 0x38650000, 0x38654000, 0x38658000, 0x3865C000, 0x38660000, 0x38664000, 0x38668000, 0x3866C000, 0x38670000, 0x38674000, 0x38678000, 0x3867C000, + 0x38680000, 0x38684000, 0x38688000, 0x3868C000, 0x38690000, 0x38694000, 0x38698000, 0x3869C000, 0x386A0000, 0x386A4000, 0x386A8000, 0x386AC000, 0x386B0000, 0x386B4000, 0x386B8000, 0x386BC000, + 0x386C0000, 0x386C4000, 0x386C8000, 0x386CC000, 0x386D0000, 0x386D4000, 0x386D8000, 0x386DC000, 0x386E0000, 0x386E4000, 0x386E8000, 0x386EC000, 0x386F0000, 0x386F4000, 0x386F8000, 0x386FC000, + 0x38700000, 0x38704000, 0x38708000, 0x3870C000, 0x38710000, 0x38714000, 0x38718000, 0x3871C000, 0x38720000, 0x38724000, 0x38728000, 0x3872C000, 0x38730000, 0x38734000, 0x38738000, 0x3873C000, + 0x38740000, 0x38744000, 0x38748000, 0x3874C000, 0x38750000, 0x38754000, 0x38758000, 0x3875C000, 0x38760000, 0x38764000, 0x38768000, 0x3876C000, 0x38770000, 0x38774000, 0x38778000, 0x3877C000, + 0x38780000, 0x38784000, 0x38788000, 0x3878C000, 0x38790000, 0x38794000, 0x38798000, 0x3879C000, 0x387A0000, 0x387A4000, 0x387A8000, 0x387AC000, 0x387B0000, 0x387B4000, 0x387B8000, 0x387BC000, + 0x387C0000, 0x387C4000, 0x387C8000, 0x387CC000, 0x387D0000, 0x387D4000, 0x387D8000, 0x387DC000, 0x387E0000, 0x387E4000, 0x387E8000, 0x387EC000, 0x387F0000, 0x387F4000, 0x387F8000, 0x387FC000, + 0x38000000, 0x38002000, 0x38004000, 0x38006000, 0x38008000, 0x3800A000, 0x3800C000, 0x3800E000, 0x38010000, 0x38012000, 0x38014000, 0x38016000, 0x38018000, 0x3801A000, 0x3801C000, 0x3801E000, + 0x38020000, 0x38022000, 0x38024000, 0x38026000, 0x38028000, 0x3802A000, 0x3802C000, 0x3802E000, 0x38030000, 0x38032000, 0x38034000, 0x38036000, 0x38038000, 0x3803A000, 0x3803C000, 0x3803E000, + 0x38040000, 0x38042000, 0x38044000, 0x38046000, 0x38048000, 0x3804A000, 0x3804C000, 0x3804E000, 0x38050000, 0x38052000, 0x38054000, 0x38056000, 0x38058000, 0x3805A000, 0x3805C000, 0x3805E000, + 0x38060000, 0x38062000, 0x38064000, 0x38066000, 0x38068000, 0x3806A000, 0x3806C000, 0x3806E000, 0x38070000, 0x38072000, 0x38074000, 0x38076000, 0x38078000, 0x3807A000, 0x3807C000, 0x3807E000, + 0x38080000, 0x38082000, 0x38084000, 0x38086000, 0x38088000, 0x3808A000, 0x3808C000, 0x3808E000, 0x38090000, 0x38092000, 0x38094000, 0x38096000, 0x38098000, 0x3809A000, 0x3809C000, 0x3809E000, + 0x380A0000, 0x380A2000, 0x380A4000, 0x380A6000, 0x380A8000, 0x380AA000, 0x380AC000, 0x380AE000, 0x380B0000, 0x380B2000, 0x380B4000, 0x380B6000, 0x380B8000, 0x380BA000, 0x380BC000, 0x380BE000, + 0x380C0000, 0x380C2000, 0x380C4000, 0x380C6000, 0x380C8000, 0x380CA000, 0x380CC000, 0x380CE000, 0x380D0000, 0x380D2000, 0x380D4000, 0x380D6000, 0x380D8000, 0x380DA000, 0x380DC000, 0x380DE000, + 0x380E0000, 0x380E2000, 0x380E4000, 0x380E6000, 0x380E8000, 0x380EA000, 0x380EC000, 0x380EE000, 0x380F0000, 0x380F2000, 0x380F4000, 0x380F6000, 0x380F8000, 0x380FA000, 0x380FC000, 0x380FE000, + 0x38100000, 0x38102000, 0x38104000, 0x38106000, 0x38108000, 0x3810A000, 0x3810C000, 0x3810E000, 0x38110000, 0x38112000, 0x38114000, 0x38116000, 0x38118000, 0x3811A000, 0x3811C000, 0x3811E000, + 0x38120000, 0x38122000, 0x38124000, 0x38126000, 0x38128000, 0x3812A000, 0x3812C000, 0x3812E000, 0x38130000, 0x38132000, 0x38134000, 0x38136000, 0x38138000, 0x3813A000, 0x3813C000, 0x3813E000, + 0x38140000, 0x38142000, 0x38144000, 0x38146000, 0x38148000, 0x3814A000, 0x3814C000, 0x3814E000, 0x38150000, 0x38152000, 0x38154000, 0x38156000, 0x38158000, 0x3815A000, 0x3815C000, 0x3815E000, + 0x38160000, 0x38162000, 0x38164000, 0x38166000, 0x38168000, 0x3816A000, 0x3816C000, 0x3816E000, 0x38170000, 0x38172000, 0x38174000, 0x38176000, 0x38178000, 0x3817A000, 0x3817C000, 0x3817E000, + 0x38180000, 0x38182000, 0x38184000, 0x38186000, 0x38188000, 0x3818A000, 0x3818C000, 0x3818E000, 0x38190000, 0x38192000, 0x38194000, 0x38196000, 0x38198000, 0x3819A000, 0x3819C000, 0x3819E000, + 0x381A0000, 0x381A2000, 0x381A4000, 0x381A6000, 0x381A8000, 0x381AA000, 0x381AC000, 0x381AE000, 0x381B0000, 0x381B2000, 0x381B4000, 0x381B6000, 0x381B8000, 0x381BA000, 0x381BC000, 0x381BE000, + 0x381C0000, 0x381C2000, 0x381C4000, 0x381C6000, 0x381C8000, 0x381CA000, 0x381CC000, 0x381CE000, 0x381D0000, 0x381D2000, 0x381D4000, 0x381D6000, 0x381D8000, 0x381DA000, 0x381DC000, 0x381DE000, + 0x381E0000, 0x381E2000, 0x381E4000, 0x381E6000, 0x381E8000, 0x381EA000, 0x381EC000, 0x381EE000, 0x381F0000, 0x381F2000, 0x381F4000, 0x381F6000, 0x381F8000, 0x381FA000, 0x381FC000, 0x381FE000, + 0x38200000, 0x38202000, 0x38204000, 0x38206000, 0x38208000, 0x3820A000, 0x3820C000, 0x3820E000, 0x38210000, 0x38212000, 0x38214000, 0x38216000, 0x38218000, 0x3821A000, 0x3821C000, 0x3821E000, + 0x38220000, 0x38222000, 0x38224000, 0x38226000, 0x38228000, 0x3822A000, 0x3822C000, 0x3822E000, 0x38230000, 0x38232000, 0x38234000, 0x38236000, 0x38238000, 0x3823A000, 0x3823C000, 0x3823E000, + 0x38240000, 0x38242000, 0x38244000, 0x38246000, 0x38248000, 0x3824A000, 0x3824C000, 0x3824E000, 0x38250000, 0x38252000, 0x38254000, 0x38256000, 0x38258000, 0x3825A000, 0x3825C000, 0x3825E000, + 0x38260000, 0x38262000, 0x38264000, 0x38266000, 0x38268000, 0x3826A000, 0x3826C000, 0x3826E000, 0x38270000, 0x38272000, 0x38274000, 0x38276000, 0x38278000, 0x3827A000, 0x3827C000, 0x3827E000, + 0x38280000, 0x38282000, 0x38284000, 0x38286000, 0x38288000, 0x3828A000, 0x3828C000, 0x3828E000, 0x38290000, 0x38292000, 0x38294000, 0x38296000, 0x38298000, 0x3829A000, 0x3829C000, 0x3829E000, + 0x382A0000, 0x382A2000, 0x382A4000, 0x382A6000, 0x382A8000, 0x382AA000, 0x382AC000, 0x382AE000, 0x382B0000, 0x382B2000, 0x382B4000, 0x382B6000, 0x382B8000, 0x382BA000, 0x382BC000, 0x382BE000, + 0x382C0000, 0x382C2000, 0x382C4000, 0x382C6000, 0x382C8000, 0x382CA000, 0x382CC000, 0x382CE000, 0x382D0000, 0x382D2000, 0x382D4000, 0x382D6000, 0x382D8000, 0x382DA000, 0x382DC000, 0x382DE000, + 0x382E0000, 0x382E2000, 0x382E4000, 0x382E6000, 0x382E8000, 0x382EA000, 0x382EC000, 0x382EE000, 0x382F0000, 0x382F2000, 0x382F4000, 0x382F6000, 0x382F8000, 0x382FA000, 0x382FC000, 0x382FE000, + 0x38300000, 0x38302000, 0x38304000, 0x38306000, 0x38308000, 0x3830A000, 0x3830C000, 0x3830E000, 0x38310000, 0x38312000, 0x38314000, 0x38316000, 0x38318000, 0x3831A000, 0x3831C000, 0x3831E000, + 0x38320000, 0x38322000, 0x38324000, 0x38326000, 0x38328000, 0x3832A000, 0x3832C000, 0x3832E000, 0x38330000, 0x38332000, 0x38334000, 0x38336000, 0x38338000, 0x3833A000, 0x3833C000, 0x3833E000, + 0x38340000, 0x38342000, 0x38344000, 0x38346000, 0x38348000, 0x3834A000, 0x3834C000, 0x3834E000, 0x38350000, 0x38352000, 0x38354000, 0x38356000, 0x38358000, 0x3835A000, 0x3835C000, 0x3835E000, + 0x38360000, 0x38362000, 0x38364000, 0x38366000, 0x38368000, 0x3836A000, 0x3836C000, 0x3836E000, 0x38370000, 0x38372000, 0x38374000, 0x38376000, 0x38378000, 0x3837A000, 0x3837C000, 0x3837E000, + 0x38380000, 0x38382000, 0x38384000, 0x38386000, 0x38388000, 0x3838A000, 0x3838C000, 0x3838E000, 0x38390000, 0x38392000, 0x38394000, 0x38396000, 0x38398000, 0x3839A000, 0x3839C000, 0x3839E000, + 0x383A0000, 0x383A2000, 0x383A4000, 0x383A6000, 0x383A8000, 0x383AA000, 0x383AC000, 0x383AE000, 0x383B0000, 0x383B2000, 0x383B4000, 0x383B6000, 0x383B8000, 0x383BA000, 0x383BC000, 0x383BE000, + 0x383C0000, 0x383C2000, 0x383C4000, 0x383C6000, 0x383C8000, 0x383CA000, 0x383CC000, 0x383CE000, 0x383D0000, 0x383D2000, 0x383D4000, 0x383D6000, 0x383D8000, 0x383DA000, 0x383DC000, 0x383DE000, + 0x383E0000, 0x383E2000, 0x383E4000, 0x383E6000, 0x383E8000, 0x383EA000, 0x383EC000, 0x383EE000, 0x383F0000, 0x383F2000, 0x383F4000, 0x383F6000, 0x383F8000, 0x383FA000, 0x383FC000, 0x383FE000, + 0x38400000, 0x38402000, 0x38404000, 0x38406000, 0x38408000, 0x3840A000, 0x3840C000, 0x3840E000, 0x38410000, 0x38412000, 0x38414000, 0x38416000, 0x38418000, 0x3841A000, 0x3841C000, 0x3841E000, + 0x38420000, 0x38422000, 0x38424000, 0x38426000, 0x38428000, 0x3842A000, 0x3842C000, 0x3842E000, 0x38430000, 0x38432000, 0x38434000, 0x38436000, 0x38438000, 0x3843A000, 0x3843C000, 0x3843E000, + 0x38440000, 0x38442000, 0x38444000, 0x38446000, 0x38448000, 0x3844A000, 0x3844C000, 0x3844E000, 0x38450000, 0x38452000, 0x38454000, 0x38456000, 0x38458000, 0x3845A000, 0x3845C000, 0x3845E000, + 0x38460000, 0x38462000, 0x38464000, 0x38466000, 0x38468000, 0x3846A000, 0x3846C000, 0x3846E000, 0x38470000, 0x38472000, 0x38474000, 0x38476000, 0x38478000, 0x3847A000, 0x3847C000, 0x3847E000, + 0x38480000, 0x38482000, 0x38484000, 0x38486000, 0x38488000, 0x3848A000, 0x3848C000, 0x3848E000, 0x38490000, 0x38492000, 0x38494000, 0x38496000, 0x38498000, 0x3849A000, 0x3849C000, 0x3849E000, + 0x384A0000, 0x384A2000, 0x384A4000, 0x384A6000, 0x384A8000, 0x384AA000, 0x384AC000, 0x384AE000, 0x384B0000, 0x384B2000, 0x384B4000, 0x384B6000, 0x384B8000, 0x384BA000, 0x384BC000, 0x384BE000, + 0x384C0000, 0x384C2000, 0x384C4000, 0x384C6000, 0x384C8000, 0x384CA000, 0x384CC000, 0x384CE000, 0x384D0000, 0x384D2000, 0x384D4000, 0x384D6000, 0x384D8000, 0x384DA000, 0x384DC000, 0x384DE000, + 0x384E0000, 0x384E2000, 0x384E4000, 0x384E6000, 0x384E8000, 0x384EA000, 0x384EC000, 0x384EE000, 0x384F0000, 0x384F2000, 0x384F4000, 0x384F6000, 0x384F8000, 0x384FA000, 0x384FC000, 0x384FE000, + 0x38500000, 0x38502000, 0x38504000, 0x38506000, 0x38508000, 0x3850A000, 0x3850C000, 0x3850E000, 0x38510000, 0x38512000, 0x38514000, 0x38516000, 0x38518000, 0x3851A000, 0x3851C000, 0x3851E000, + 0x38520000, 0x38522000, 0x38524000, 0x38526000, 0x38528000, 0x3852A000, 0x3852C000, 0x3852E000, 0x38530000, 0x38532000, 0x38534000, 0x38536000, 0x38538000, 0x3853A000, 0x3853C000, 0x3853E000, + 0x38540000, 0x38542000, 0x38544000, 0x38546000, 0x38548000, 0x3854A000, 0x3854C000, 0x3854E000, 0x38550000, 0x38552000, 0x38554000, 0x38556000, 0x38558000, 0x3855A000, 0x3855C000, 0x3855E000, + 0x38560000, 0x38562000, 0x38564000, 0x38566000, 0x38568000, 0x3856A000, 0x3856C000, 0x3856E000, 0x38570000, 0x38572000, 0x38574000, 0x38576000, 0x38578000, 0x3857A000, 0x3857C000, 0x3857E000, + 0x38580000, 0x38582000, 0x38584000, 0x38586000, 0x38588000, 0x3858A000, 0x3858C000, 0x3858E000, 0x38590000, 0x38592000, 0x38594000, 0x38596000, 0x38598000, 0x3859A000, 0x3859C000, 0x3859E000, + 0x385A0000, 0x385A2000, 0x385A4000, 0x385A6000, 0x385A8000, 0x385AA000, 0x385AC000, 0x385AE000, 0x385B0000, 0x385B2000, 0x385B4000, 0x385B6000, 0x385B8000, 0x385BA000, 0x385BC000, 0x385BE000, + 0x385C0000, 0x385C2000, 0x385C4000, 0x385C6000, 0x385C8000, 0x385CA000, 0x385CC000, 0x385CE000, 0x385D0000, 0x385D2000, 0x385D4000, 0x385D6000, 0x385D8000, 0x385DA000, 0x385DC000, 0x385DE000, + 0x385E0000, 0x385E2000, 0x385E4000, 0x385E6000, 0x385E8000, 0x385EA000, 0x385EC000, 0x385EE000, 0x385F0000, 0x385F2000, 0x385F4000, 0x385F6000, 0x385F8000, 0x385FA000, 0x385FC000, 0x385FE000, + 0x38600000, 0x38602000, 0x38604000, 0x38606000, 0x38608000, 0x3860A000, 0x3860C000, 0x3860E000, 0x38610000, 0x38612000, 0x38614000, 0x38616000, 0x38618000, 0x3861A000, 0x3861C000, 0x3861E000, + 0x38620000, 0x38622000, 0x38624000, 0x38626000, 0x38628000, 0x3862A000, 0x3862C000, 0x3862E000, 0x38630000, 0x38632000, 0x38634000, 0x38636000, 0x38638000, 0x3863A000, 0x3863C000, 0x3863E000, + 0x38640000, 0x38642000, 0x38644000, 0x38646000, 0x38648000, 0x3864A000, 0x3864C000, 0x3864E000, 0x38650000, 0x38652000, 0x38654000, 0x38656000, 0x38658000, 0x3865A000, 0x3865C000, 0x3865E000, + 0x38660000, 0x38662000, 0x38664000, 0x38666000, 0x38668000, 0x3866A000, 0x3866C000, 0x3866E000, 0x38670000, 0x38672000, 0x38674000, 0x38676000, 0x38678000, 0x3867A000, 0x3867C000, 0x3867E000, + 0x38680000, 0x38682000, 0x38684000, 0x38686000, 0x38688000, 0x3868A000, 0x3868C000, 0x3868E000, 0x38690000, 0x38692000, 0x38694000, 0x38696000, 0x38698000, 0x3869A000, 0x3869C000, 0x3869E000, + 0x386A0000, 0x386A2000, 0x386A4000, 0x386A6000, 0x386A8000, 0x386AA000, 0x386AC000, 0x386AE000, 0x386B0000, 0x386B2000, 0x386B4000, 0x386B6000, 0x386B8000, 0x386BA000, 0x386BC000, 0x386BE000, + 0x386C0000, 0x386C2000, 0x386C4000, 0x386C6000, 0x386C8000, 0x386CA000, 0x386CC000, 0x386CE000, 0x386D0000, 0x386D2000, 0x386D4000, 0x386D6000, 0x386D8000, 0x386DA000, 0x386DC000, 0x386DE000, + 0x386E0000, 0x386E2000, 0x386E4000, 0x386E6000, 0x386E8000, 0x386EA000, 0x386EC000, 0x386EE000, 0x386F0000, 0x386F2000, 0x386F4000, 0x386F6000, 0x386F8000, 0x386FA000, 0x386FC000, 0x386FE000, + 0x38700000, 0x38702000, 0x38704000, 0x38706000, 0x38708000, 0x3870A000, 0x3870C000, 0x3870E000, 0x38710000, 0x38712000, 0x38714000, 0x38716000, 0x38718000, 0x3871A000, 0x3871C000, 0x3871E000, + 0x38720000, 0x38722000, 0x38724000, 0x38726000, 0x38728000, 0x3872A000, 0x3872C000, 0x3872E000, 0x38730000, 0x38732000, 0x38734000, 0x38736000, 0x38738000, 0x3873A000, 0x3873C000, 0x3873E000, + 0x38740000, 0x38742000, 0x38744000, 0x38746000, 0x38748000, 0x3874A000, 0x3874C000, 0x3874E000, 0x38750000, 0x38752000, 0x38754000, 0x38756000, 0x38758000, 0x3875A000, 0x3875C000, 0x3875E000, + 0x38760000, 0x38762000, 0x38764000, 0x38766000, 0x38768000, 0x3876A000, 0x3876C000, 0x3876E000, 0x38770000, 0x38772000, 0x38774000, 0x38776000, 0x38778000, 0x3877A000, 0x3877C000, 0x3877E000, + 0x38780000, 0x38782000, 0x38784000, 0x38786000, 0x38788000, 0x3878A000, 0x3878C000, 0x3878E000, 0x38790000, 0x38792000, 0x38794000, 0x38796000, 0x38798000, 0x3879A000, 0x3879C000, 0x3879E000, + 0x387A0000, 0x387A2000, 0x387A4000, 0x387A6000, 0x387A8000, 0x387AA000, 0x387AC000, 0x387AE000, 0x387B0000, 0x387B2000, 0x387B4000, 0x387B6000, 0x387B8000, 0x387BA000, 0x387BC000, 0x387BE000, + 0x387C0000, 0x387C2000, 0x387C4000, 0x387C6000, 0x387C8000, 0x387CA000, 0x387CC000, 0x387CE000, 0x387D0000, 0x387D2000, 0x387D4000, 0x387D6000, 0x387D8000, 0x387DA000, 0x387DC000, 0x387DE000, + 0x387E0000, 0x387E2000, 0x387E4000, 0x387E6000, 0x387E8000, 0x387EA000, 0x387EC000, 0x387EE000, 0x387F0000, 0x387F2000, 0x387F4000, 0x387F6000, 0x387F8000, 0x387FA000, 0x387FC000, 0x387FE000 }; + static const uint32 exponent_table[64] = { + 0x00000000, 0x00800000, 0x01000000, 0x01800000, 0x02000000, 0x02800000, 0x03000000, 0x03800000, 0x04000000, 0x04800000, 0x05000000, 0x05800000, 0x06000000, 0x06800000, 0x07000000, 0x07800000, + 0x08000000, 0x08800000, 0x09000000, 0x09800000, 0x0A000000, 0x0A800000, 0x0B000000, 0x0B800000, 0x0C000000, 0x0C800000, 0x0D000000, 0x0D800000, 0x0E000000, 0x0E800000, 0x0F000000, 0x47800000, + 0x80000000, 0x80800000, 0x81000000, 0x81800000, 0x82000000, 0x82800000, 0x83000000, 0x83800000, 0x84000000, 0x84800000, 0x85000000, 0x85800000, 0x86000000, 0x86800000, 0x87000000, 0x87800000, + 0x88000000, 0x88800000, 0x89000000, 0x89800000, 0x8A000000, 0x8A800000, 0x8B000000, 0x8B800000, 0x8C000000, 0x8C800000, 0x8D000000, 0x8D800000, 0x8E000000, 0x8E800000, 0x8F000000, 0xC7800000 }; + static const unsigned short offset_table[64] = { + 0, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, + 0, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024 }; + uint32 bits = mantissa_table[offset_table[value>>10]+(value&0x3FF)] + exponent_table[value>>10]; +// uint32 bits = mantissa_table[(((value&0x7C00)!=0)<<10)+(value&0x3FF)] + exponent_table[value>>10]; +// return *reinterpret_cast(&bits); //violating strict aliasing! + float out; + std::memcpy(&out, &bits, sizeof(float)); + return out; + } + + /// Convert half-precision to non-IEEE single-precision. + /// \param value binary representation of half-precision value + /// \return single-precision value + inline float half2float_impl(uint16 value, false_type) + { + float out; + int abs = value & 0x7FFF; + if(abs > 0x7C00) + out = std::numeric_limits::has_quiet_NaN ? std::numeric_limits::quiet_NaN() : 0.0f; + else if(abs == 0x7C00) + out = std::numeric_limits::has_infinity ? std::numeric_limits::infinity() : FLT_MAX;//std::numeric_limits::max(); + else if(abs > 0x3FF) + out = std::ldexp(static_cast((value&0x3FF)|0x400), (abs>>10)-25); + else + out = std::ldexp(static_cast(abs), -24); + return (value&0x8000) ? -out : out; + } + + /// Convert half-precision to single-precision. + /// \param value binary representation of half-precision value + /// \return single-precision value + inline float half2float(uint16 value) + { + return half2float_impl(value, bool_type::is_iec559&&sizeof(uint32)==sizeof(float)>()); + } + + /// Convert half-precision floating point to integer. + /// \tparam R rounding mode to use, `std::round_indeterminate` for fastest rounding + /// \tparam E `true` for round to even, `false` for round away from zero + /// \tparam T type to convert to (buitlin integer type with at least 16 bits precision, excluding any implicit sign bits) + /// \param value binary representation of half-precision value + /// \return integral value + template T half2int_impl(uint16 value) + { + unsigned int e = value & 0x7FFF; + if(e >= 0x7C00) + return (value&0x8000) ? std::numeric_limits::min() : std::numeric_limits::max(); + if(e < 0x3800) + { + if(R == std::round_toward_infinity) + return T(~(value>>15)&(e!=0)); + else if(R == std::round_toward_neg_infinity) + return -T(value>0x8000); + return T(); + } + int17 m = (value&0x3FF) | 0x400; + e >>= 10; + if(e < 25) + { + if(R == std::round_indeterminate || R == std::round_toward_zero) + m >>= 25 - e; + else + { + if(R == std::round_to_nearest) + m += (1<<(24-e)) - (~(m>>(25-e))&E); + else if(R == std::round_toward_infinity) + m += ((value>>15)-1) & ((1<<(25-e))-1U); + else if(R == std::round_toward_neg_infinity) + m += -(value>>15) & ((1<<(25-e))-1U); + m >>= 25 - e; + } + } + else + m <<= e - 25; +// if(std::numeric_limits::digits < 16) +// return std::min(std::max(m, static_cast(std::numeric_limits::min())), static_cast(std::numeric_limits::max())); + return static_cast((value&0x8000) ? -m : m); + } + + /// Convert half-precision floating point to integer. + /// \tparam R rounding mode to use, `std::round_indeterminate` for fastest rounding + /// \tparam T type to convert to (buitlin integer type with at least 16 bits precision, excluding any implicit sign bits) + /// \param value binary representation of half-precision value + /// \return integral value + template T half2int(uint16 value) { return half2int_impl(value); } + + /// Convert half-precision floating point to integer using round-to-nearest-away-from-zero. + /// \tparam T type to convert to (buitlin integer type with at least 16 bits precision, excluding any implicit sign bits) + /// \param value binary representation of half-precision value + /// \return integral value + template T half2int_up(uint16 value) { return half2int_impl(value); } + + /// Round half-precision number to nearest integer value. + /// \tparam R rounding mode to use, `std::round_indeterminate` for fastest rounding + /// \tparam E `true` for round to even, `false` for round away from zero + /// \param value binary representation of half-precision value + /// \return half-precision bits for nearest integral value + template uint16 round_half_impl(uint16 value) + { + unsigned int e = value & 0x7FFF; + uint16 result = value; + if(e < 0x3C00) + { + result &= 0x8000; + if(R == std::round_to_nearest) + result |= 0x3C00U & -(e>=(0x3800+E)); + else if(R == std::round_toward_infinity) + result |= 0x3C00U & -(~(value>>15)&(e!=0)); + else if(R == std::round_toward_neg_infinity) + result |= 0x3C00U & -(value>0x8000); + } + else if(e < 0x6400) + { + e = 25 - (e>>10); + unsigned int mask = (1<>e)&E); + else if(R == std::round_toward_infinity) + result += mask & ((value>>15)-1); + else if(R == std::round_toward_neg_infinity) + result += mask & -(value>>15); + result &= ~mask; + } + return result; + } + + /// Round half-precision number to nearest integer value. + /// \tparam R rounding mode to use, `std::round_indeterminate` for fastest rounding + /// \param value binary representation of half-precision value + /// \return half-precision bits for nearest integral value + template uint16 round_half(uint16 value) { return round_half_impl(value); } + + /// Round half-precision number to nearest integer value using round-to-nearest-away-from-zero. + /// \param value binary representation of half-precision value + /// \return half-precision bits for nearest integral value + inline uint16 round_half_up(uint16 value) { return round_half_impl(value); } + /// \} + + struct functions; + template struct unary_specialized; + template struct binary_specialized; + template struct half_caster; + } + + /// Half-precision floating point type. + /// This class implements an IEEE-conformant half-precision floating point type with the usual arithmetic operators and + /// conversions. It is implicitly convertible to single-precision floating point, which makes artihmetic expressions and + /// functions with mixed-type operands to be of the most precise operand type. Additionally all arithmetic operations + /// (and many mathematical functions) are carried out in single-precision internally. All conversions from single- to + /// half-precision are done using truncation (round towards zero), but temporary results inside chained arithmetic + /// expressions are kept in single-precision as long as possible (while of course still maintaining a strong half-precision type). + /// + /// According to the C++98/03 definition, the half type is not a POD type. But according to C++11's less strict and + /// extended definitions it is both a standard layout type and a trivially copyable type (even if not a POD type), which + /// means it can be standard-conformantly copied using raw binary copies. But in this context some more words about the + /// actual size of the type. Although the half is representing an IEEE 16-bit type, it does not neccessarily have to be of + /// exactly 16-bits size. But on any reasonable implementation the actual binary representation of this type will most + /// probably not ivolve any additional "magic" or padding beyond the simple binary representation of the underlying 16-bit + /// IEEE number, even if not strictly guaranteed by the standard. But even then it only has an actual size of 16 bits if + /// your C++ implementation supports an unsigned integer type of exactly 16 bits width. But this should be the case on + /// nearly any reasonable platform. + /// + /// So if your C++ implementation is not totally exotic or imposes special alignment requirements, it is a reasonable + /// assumption that the data of a half is just comprised of the 2 bytes of the underlying IEEE representation. + class half + { + friend struct detail::functions; + friend struct detail::unary_specialized; + friend struct detail::binary_specialized; + template friend struct detail::half_caster; + friend class std::numeric_limits; + #if HALF_ENABLE_CPP11_HASH + friend struct std::hash; + #endif + + public: + /// Default constructor. + /// This initializes the half to 0. Although this does not match the builtin types' default-initialization semantics + /// and may be less efficient than no initialization, it is needed to provide proper value-initialization semantics. + HALF_CONSTEXPR half() : data_() {} + + /// Copy constructor. + /// \tparam T type of concrete half expression + /// \param rhs half expression to copy from + half(detail::expr rhs) : data_(detail::float2half(rhs)) {} + + /// Conversion constructor. + /// \param rhs float to convert + explicit half(float rhs) : data_(detail::float2half(rhs)) {} + + /// Conversion to single-precision. + /// \return single precision value representing expression value + operator float() const { return detail::half2float(data_); } + + /// Assignment operator. + /// \tparam T type of concrete half expression + /// \param rhs half expression to copy from + /// \return reference to this half + half& operator=(detail::expr rhs) { return *this = static_cast(rhs); } + + /// Arithmetic assignment. + /// \tparam T type of concrete half expression + /// \param rhs half expression to add + /// \return reference to this half + template typename detail::enable::type operator+=(T rhs) { return *this += static_cast(rhs); } + + /// Arithmetic assignment. + /// \tparam T type of concrete half expression + /// \param rhs half expression to subtract + /// \return reference to this half + template typename detail::enable::type operator-=(T rhs) { return *this -= static_cast(rhs); } + + /// Arithmetic assignment. + /// \tparam T type of concrete half expression + /// \param rhs half expression to multiply with + /// \return reference to this half + template typename detail::enable::type operator*=(T rhs) { return *this *= static_cast(rhs); } + + /// Arithmetic assignment. + /// \tparam T type of concrete half expression + /// \param rhs half expression to divide by + /// \return reference to this half + template typename detail::enable::type operator/=(T rhs) { return *this /= static_cast(rhs); } + + /// Assignment operator. + /// \param rhs single-precision value to copy from + /// \return reference to this half + half& operator=(float rhs) { data_ = detail::float2half(rhs); return *this; } + + /// Arithmetic assignment. + /// \param rhs single-precision value to add + /// \return reference to this half + half& operator+=(float rhs) { data_ = detail::float2half(detail::half2float(data_)+rhs); return *this; } + + /// Arithmetic assignment. + /// \param rhs single-precision value to subtract + /// \return reference to this half + half& operator-=(float rhs) { data_ = detail::float2half(detail::half2float(data_)-rhs); return *this; } + + /// Arithmetic assignment. + /// \param rhs single-precision value to multiply with + /// \return reference to this half + half& operator*=(float rhs) { data_ = detail::float2half(detail::half2float(data_)*rhs); return *this; } + + /// Arithmetic assignment. + /// \param rhs single-precision value to divide by + /// \return reference to this half + half& operator/=(float rhs) { data_ = detail::float2half(detail::half2float(data_)/rhs); return *this; } + + /// Prefix increment. + /// \return incremented half value + half& operator++() { return *this += 1.0f; } + + /// Prefix decrement. + /// \return decremented half value + half& operator--() { return *this -= 1.0f; } + + /// Postfix increment. + /// \return non-incremented half value + half operator++(int) { half out(*this); ++*this; return out; } + + /// Postfix decrement. + /// \return non-decremented half value + half operator--(int) { half out(*this); --*this; return out; } + + private: + /// Rounding mode to use (always `std::round_indeterminate`) + static const std::float_round_style round_style = (std::float_round_style)(HALF_ROUND_STYLE); + + /// Constructor. + /// \param bits binary representation to set half to + HALF_CONSTEXPR half(detail::binary_t, detail::uint16 bits) : data_(bits) {} + + /// Internal binary representation + detail::uint16 data_; + }; + +#if HALF_ENABLE_CPP11_USER_LITERALS + /// Library-defined half-precision literals. + /// Import this namespace to enable half-precision floating point literals: + /// ~~~~{.cpp} + /// using namespace half_float::literal; + /// half_float::half = 4.2_h; + /// ~~~~ + namespace literal + { + /// Half literal. + /// While this returns an actual half-precision value, half literals can unfortunately not be constant expressions due + /// to rather involved single-to-half conversion. + /// \param value literal value + /// \return half with given value (if representable) + inline half operator "" _h(long double value) { return half(static_cast(value)); } + } +#endif + + namespace detail + { + /// Wrapper implementing unspecialized half-precision functions. + struct functions + { + /// Addition implementation. + /// \param x first operand + /// \param y second operand + /// \return Half-precision sum stored in single-precision + static expr plus(float x, float y) { return expr(x+y); } + + /// Subtraction implementation. + /// \param x first operand + /// \param y second operand + /// \return Half-precision difference stored in single-precision + static expr minus(float x, float y) { return expr(x-y); } + + /// Multiplication implementation. + /// \param x first operand + /// \param y second operand + /// \return Half-precision product stored in single-precision + static expr multiplies(float x, float y) { return expr(x*y); } + + /// Division implementation. + /// \param x first operand + /// \param y second operand + /// \return Half-precision quotient stored in single-precision + static expr divides(float x, float y) { return expr(x/y); } + + /// Output implementation. + /// \param out stream to write to + /// \param arg value to write + /// \return reference to stream + template static std::basic_ostream& write(std::basic_ostream &out, float arg) { return out << arg; } + + /// Input implementation. + /// \param in stream to read from + /// \param arg half to read into + /// \return reference to stream + template static std::basic_istream& read(std::basic_istream &in, half &arg) + { + float f; + if(in >> f) + arg = f; + return in; + } + + /// Modulo implementation. + /// \param x first operand + /// \param y second operand + /// \return Half-precision division remainder stored in single-precision + static expr fmod(float x, float y) { return expr(std::fmod(x, y)); } + + /// Remainder implementation. + /// \param x first operand + /// \param y second operand + /// \return Half-precision division remainder stored in single-precision + static expr remainder(float x, float y) + { + #if HALF_ENABLE_CPP11_CMATH + return expr(std::remainder(x, y)); + #else + if(builtin_isnan(x) || builtin_isnan(y)) + return expr(std::numeric_limits::quiet_NaN()); + float ax = std::fabs(x), ay = std::fabs(y); + if(ax >= 65536.0f || ay < std::ldexp(1.0f, -24)) + return expr(std::numeric_limits::quiet_NaN()); + if(ay >= 65536.0f) + return expr(x); + if(ax == ay) + return expr(builtin_signbit(x) ? -0.0f : 0.0f); + ax = std::fmod(ax, ay+ay); + float y2 = 0.5f * ay; + if(ax > y2) + { + ax -= ay; + if(ax >= y2) + ax -= ay; + } + return expr(builtin_signbit(x) ? -ax : ax); + #endif + } + + /// Remainder implementation. + /// \param x first operand + /// \param y second operand + /// \param quo address to store quotient bits at + /// \return Half-precision division remainder stored in single-precision + static expr remquo(float x, float y, int *quo) + { + #if HALF_ENABLE_CPP11_CMATH + return expr(std::remquo(x, y, quo)); + #else + if(builtin_isnan(x) || builtin_isnan(y)) + return expr(std::numeric_limits::quiet_NaN()); + bool sign = builtin_signbit(x), qsign = static_cast(sign^builtin_signbit(y)); + float ax = std::fabs(x), ay = std::fabs(y); + if(ax >= 65536.0f || ay < std::ldexp(1.0f, -24)) + return expr(std::numeric_limits::quiet_NaN()); + if(ay >= 65536.0f) + return expr(x); + if(ax == ay) + return *quo = qsign ? -1 : 1, expr(sign ? -0.0f : 0.0f); + ax = std::fmod(ax, 8.0f*ay); + int cquo = 0; + if(ax >= 4.0f * ay) + { + ax -= 4.0f * ay; + cquo += 4; + } + if(ax >= 2.0f * ay) + { + ax -= 2.0f * ay; + cquo += 2; + } + float y2 = 0.5f * ay; + if(ax > y2) + { + ax -= ay; + ++cquo; + if(ax >= y2) + { + ax -= ay; + ++cquo; + } + } + return *quo = qsign ? -cquo : cquo, expr(sign ? -ax : ax); + #endif + } + + /// Positive difference implementation. + /// \param x first operand + /// \param y second operand + /// \return Positive difference stored in single-precision + static expr fdim(float x, float y) + { + #if HALF_ENABLE_CPP11_CMATH + return expr(std::fdim(x, y)); + #else + return expr((x<=y) ? 0.0f : (x-y)); + #endif + } + + /// Fused multiply-add implementation. + /// \param x first operand + /// \param y second operand + /// \param z third operand + /// \return \a x * \a y + \a z stored in single-precision + static expr fma(float x, float y, float z) + { + #if HALF_ENABLE_CPP11_CMATH && defined(FP_FAST_FMAF) + return expr(std::fma(x, y, z)); + #else + return expr(x*y+z); + #endif + } + + /// Get NaN. + /// \return Half-precision quiet NaN + static half nanh(const char*) { return half(binary, 0x7FFF); } + + /// Exponential implementation. + /// \param arg function argument + /// \return function value stored in single-preicision + static expr exp(float arg) { return expr(std::exp(arg)); } + + /// Exponential implementation. + /// \param arg function argument + /// \return function value stored in single-preicision + static expr expm1(float arg) + { + #if HALF_ENABLE_CPP11_CMATH + return expr(std::expm1(arg)); + #else + return expr(static_cast(std::exp(static_cast(arg))-1.0)); + #endif + } + + /// Binary exponential implementation. + /// \param arg function argument + /// \return function value stored in single-preicision + static expr exp2(float arg) + { + #if HALF_ENABLE_CPP11_CMATH + return expr(std::exp2(arg)); + #else + return expr(static_cast(std::exp(arg*0.69314718055994530941723212145818))); + #endif + } + + /// Logarithm implementation. + /// \param arg function argument + /// \return function value stored in single-preicision + static expr log(float arg) { return expr(std::log(arg)); } + + /// Common logarithm implementation. + /// \param arg function argument + /// \return function value stored in single-preicision + static expr log10(float arg) { return expr(std::log10(arg)); } + + /// Logarithm implementation. + /// \param arg function argument + /// \return function value stored in single-preicision + static expr log1p(float arg) + { + #if HALF_ENABLE_CPP11_CMATH + return expr(std::log1p(arg)); + #else + return expr(static_cast(std::log(1.0+arg))); + #endif + } + + /// Binary logarithm implementation. + /// \param arg function argument + /// \return function value stored in single-preicision + static expr log2(float arg) + { + #if HALF_ENABLE_CPP11_CMATH + return expr(std::log2(arg)); + #else + return expr(static_cast(std::log(static_cast(arg))*1.4426950408889634073599246810019)); + #endif + } + + /// Square root implementation. + /// \param arg function argument + /// \return function value stored in single-preicision + static expr sqrt(float arg) { return expr(std::sqrt(arg)); } + + /// Cubic root implementation. + /// \param arg function argument + /// \return function value stored in single-preicision + static expr cbrt(float arg) + { + #if HALF_ENABLE_CPP11_CMATH + return expr(std::cbrt(arg)); + #else + if(builtin_isnan(arg) || builtin_isinf(arg)) + return expr(arg); + return expr(builtin_signbit(arg) ? -static_cast(std::pow(std::fabs(static_cast(arg)), 1.0/3.0)) : + static_cast(std::pow(static_cast(arg), 1.0/3.0))); + #endif + } + + /// Hypotenuse implementation. + /// \param x first argument + /// \param y second argument + /// \return function value stored in single-preicision + static expr hypot(float x, float y) + { + #if HALF_ENABLE_CPP11_CMATH + return expr(std::hypot(x, y)); + #else + return expr((builtin_isinf(x) || builtin_isinf(y)) ? std::numeric_limits::infinity() : + static_cast(std::sqrt(static_cast(x)*x+static_cast(y)*y))); + #endif + } + + /// Power implementation. + /// \param base value to exponentiate + /// \param exp power to expontiate to + /// \return function value stored in single-preicision + static expr pow(float base, float exp) { return expr(std::pow(base, exp)); } + + /// Sine implementation. + /// \param arg function argument + /// \return function value stored in single-preicision + static expr sin(float arg) { return expr(std::sin(arg)); } + + /// Cosine implementation. + /// \param arg function argument + /// \return function value stored in single-preicision + static expr cos(float arg) { return expr(std::cos(arg)); } + + /// Tan implementation. + /// \param arg function argument + /// \return function value stored in single-preicision + static expr tan(float arg) { return expr(std::tan(arg)); } + + /// Arc sine implementation. + /// \param arg function argument + /// \return function value stored in single-preicision + static expr asin(float arg) { return expr(std::asin(arg)); } + + /// Arc cosine implementation. + /// \param arg function argument + /// \return function value stored in single-preicision + static expr acos(float arg) { return expr(std::acos(arg)); } + + /// Arc tangent implementation. + /// \param arg function argument + /// \return function value stored in single-preicision + static expr atan(float arg) { return expr(std::atan(arg)); } + + /// Arc tangent implementation. + /// \param x first argument + /// \param y second argument + /// \return function value stored in single-preicision + static expr atan2(float x, float y) { return expr(std::atan2(x, y)); } + + /// Hyperbolic sine implementation. + /// \param arg function argument + /// \return function value stored in single-preicision + static expr sinh(float arg) { return expr(std::sinh(arg)); } + + /// Hyperbolic cosine implementation. + /// \param arg function argument + /// \return function value stored in single-preicision + static expr cosh(float arg) { return expr(std::cosh(arg)); } + + /// Hyperbolic tangent implementation. + /// \param arg function argument + /// \return function value stored in single-preicision + static expr tanh(float arg) { return expr(std::tanh(arg)); } + + /// Hyperbolic area sine implementation. + /// \param arg function argument + /// \return function value stored in single-preicision + static expr asinh(float arg) + { + #if HALF_ENABLE_CPP11_CMATH + return expr(std::asinh(arg)); + #else + return expr((arg==-std::numeric_limits::infinity()) ? arg : static_cast(std::log(arg+std::sqrt(arg*arg+1.0)))); + #endif + } + + /// Hyperbolic area cosine implementation. + /// \param arg function argument + /// \return function value stored in single-preicision + static expr acosh(float arg) + { + #if HALF_ENABLE_CPP11_CMATH + return expr(std::acosh(arg)); + #else + return expr((arg<-1.0f) ? std::numeric_limits::quiet_NaN() : static_cast(std::log(arg+std::sqrt(arg*arg-1.0)))); + #endif + } + + /// Hyperbolic area tangent implementation. + /// \param arg function argument + /// \return function value stored in single-preicision + static expr atanh(float arg) + { + #if HALF_ENABLE_CPP11_CMATH + return expr(std::atanh(arg)); + #else + return expr(static_cast(0.5*std::log((1.0+arg)/(1.0-arg)))); + #endif + } + + /// Error function implementation. + /// \param arg function argument + /// \return function value stored in single-preicision + static expr erf(float arg) + { + #if HALF_ENABLE_CPP11_CMATH + return expr(std::erf(arg)); + #else + return expr(static_cast(erf(static_cast(arg)))); + #endif + } + + /// Complementary implementation. + /// \param arg function argument + /// \return function value stored in single-preicision + static expr erfc(float arg) + { + #if HALF_ENABLE_CPP11_CMATH + return expr(std::erfc(arg)); + #else + return expr(static_cast(1.0-erf(static_cast(arg)))); + #endif + } + + /// Gamma logarithm implementation. + /// \param arg function argument + /// \return function value stored in single-preicision + static expr lgamma(float arg) + { + #if HALF_ENABLE_CPP11_CMATH + return expr(std::lgamma(arg)); + #else + if(builtin_isinf(arg)) + return expr(std::numeric_limits::infinity()); + double z = static_cast(arg); + if(z < 0) + { + double i, f = std::modf(-z, &i); + if(f == 0.0) + return expr(std::numeric_limits::infinity()); + return expr(static_cast(1.1447298858494001741434273513531-std::log(std::abs(std::sin(3.1415926535897932384626433832795*f)))-lgamma(1.0-z))); + } +// if(z < 8.0) + return expr(static_cast(lgamma(static_cast(arg)))); + return expr(static_cast(0.5*(1.8378770664093454835606594728112-std::log(z))+z*(std::log(z+1.0/(12.0*z-1.0/(10.0*z)-1.0))-1.0))); + #endif + } + + /// Gamma implementation. + /// \param arg function argument + /// \return function value stored in single-preicision + static expr tgamma(float arg) + { + #if HALF_ENABLE_CPP11_CMATH + return expr(std::tgamma(arg)); + #else + double z = static_cast(arg); + if(z == 0.0) + return builtin_signbit(z) ? expr(-std::numeric_limits::infinity()) : expr(std::numeric_limits::infinity()); + if(z < 0.0) + { + double i, f = std::modf(-z, &i); + if(f == 0.0) + return expr(std::numeric_limits::quiet_NaN()); + double sign = (std::fmod(i, 2.0)==0.0) ? -1.0 : 1.0; + return expr(static_cast(sign*3.1415926535897932384626433832795/(std::sin(3.1415926535897932384626433832795*f)*std::exp(lgamma(1.0-z))))); + } + if(builtin_isinf(arg)) + return expr(arg); +// if(arg < 8.0f) + return expr(static_cast(std::exp(lgamma(z)))); + return expr(static_cast(std::sqrt(6.283185307179586476925286766559/z)*std::pow(0.36787944117144232159552377016146*(z+1.0/(12.0*z-1.0/(10.0*z))), z))); + #endif + } + + /// Floor implementation. + /// \param arg value to round + /// \return rounded value + static half floor(half arg) { return half(binary, round_half(arg.data_)); } + + /// Ceiling implementation. + /// \param arg value to round + /// \return rounded value + static half ceil(half arg) { return half(binary, round_half(arg.data_)); } + + /// Truncation implementation. + /// \param arg value to round + /// \return rounded value + static half trunc(half arg) { return half(binary, round_half(arg.data_)); } + + /// Nearest integer implementation. + /// \param arg value to round + /// \return rounded value + static half round(half arg) { return half(binary, round_half_up(arg.data_)); } + + /// Nearest integer implementation. + /// \param arg value to round + /// \return rounded value + static long lround(half arg) { return detail::half2int_up(arg.data_); } + + /// Nearest integer implementation. + /// \param arg value to round + /// \return rounded value + static half rint(half arg) { return half(binary, round_half(arg.data_)); } + + /// Nearest integer implementation. + /// \param arg value to round + /// \return rounded value + static long lrint(half arg) { return detail::half2int(arg.data_); } + + #if HALF_ENABLE_CPP11_LONG_LONG + /// Nearest integer implementation. + /// \param arg value to round + /// \return rounded value + static long long llround(half arg) { return detail::half2int_up(arg.data_); } + + /// Nearest integer implementation. + /// \param arg value to round + /// \return rounded value + static long long llrint(half arg) { return detail::half2int(arg.data_); } + #endif + + /// Decompression implementation. + /// \param arg number to decompress + /// \param exp address to store exponent at + /// \return normalized significant + static half frexp(half arg, int *exp) + { + unsigned int m = arg.data_ & 0x7FFF; + if(m >= 0x7C00 || !m) + return *exp = 0, arg; + int e = m >> 10; + if(!e) + for(m<<=1; m<0x400; m<<=1,--e) ; + return *exp = e-14, half(binary, static_cast((arg.data_&0x8000)|0x3800|(m&0x3FF))); + } + + /// Decompression implementation. + /// \param arg number to decompress + /// \param iptr address to store integer part at + /// \return fractional part + static half modf(half arg, half *iptr) + { + unsigned int e = arg.data_ & 0x7C00; + if(e > 0x6000) + return *iptr = arg, (e==0x7C00&&(arg.data_&0x3FF)) ? arg : half(binary, arg.data_&0x8000); + if(e < 0x3C00) + return iptr->data_ = arg.data_ & 0x8000, arg; + e >>= 10; + unsigned int mask = (1<<(25-e)) - 1, m = arg.data_ & mask; + iptr->data_ = arg.data_ & ~mask; + if(!m) + return half(binary, arg.data_&0x8000); + for(; m<0x400; m<<=1,--e) ; + return half(binary, static_cast((arg.data_&0x8000)|(e<<10)|(m&0x3FF))); + } + + /// Scaling implementation. + /// \param arg number to scale + /// \param exp power of two to scale by + /// \return scaled number + static half scalbln(half arg, long exp) + { + long e = arg.data_ & 0x7C00; + if(e == 0x7C00) + return arg; + unsigned int m = arg.data_ & 0x3FF; + if(e >>= 10) + m |= 0x400; + else + { + if(!m) + return arg; + for(m<<=1; m<0x400; m<<=1,--e) ; + } + e += exp; + uint16 value = arg.data_ & 0x8000; + if(e > 30) + { + if(half::round_style == std::round_toward_zero) + value |= 0x7BFF; + else if(half::round_style == std::round_toward_infinity) + value |= 0x7C00 - (value>>15); + else if(half::round_style == std::round_toward_neg_infinity) + value |= 0x7BFF + (value>>15); + else + value |= 0x7C00; + } + else if(e > 0) + value |= (e<<10) | (m&0x3FF); + else if(e > -11) + { + if(half::round_style == std::round_to_nearest) + { + m += 1 << -e; + #if HALF_ROUND_TIES_TO_EVEN + m -= (m>>(1-e)) & 1; + #endif + } + else if(half::round_style == std::round_toward_infinity) + m += ((value>>15)-1) & ((1<<(1-e))-1U); + else if(half::round_style == std::round_toward_neg_infinity) + m += -(value>>15) & ((1<<(1-e))-1U); + value |= m >> (1-e); + } + else if(half::round_style == std::round_toward_infinity) + value |= ((value>>15)-1) & 1; + else if(half::round_style == std::round_toward_neg_infinity) + value |= value >> 15; + return half(binary, value); + } + + /// Exponent implementation. + /// \param arg number to query + /// \return floating point exponent + static int ilogb(half arg) + { + int exp = arg.data_ & 0x7FFF; + if(!exp) + return FP_ILOGB0; + if(exp < 0x7C00) + { + if(!(exp>>=10)) + for(unsigned int m=(arg.data_&0x3FF); m<0x200; m<<=1,--exp) ; + return exp - 15; + } + if(exp > 0x7C00) + return FP_ILOGBNAN; + return INT_MAX; + } + + /// Exponent implementation. + /// \param arg number to query + /// \return floating point exponent + static half logb(half arg) + { + int exp = arg.data_ & 0x7FFF; + if(!exp) + return half(binary, 0xFC00); + if(exp < 0x7C00) + { + if(!(exp>>=10)) + for(unsigned int m=(arg.data_&0x3FF); m<0x200; m<<=1,--exp) ; + return half(static_cast(exp-15)); + } + if(exp > 0x7C00) + return arg; + return half(binary, 0x7C00); + } + + /// Enumeration implementation. + /// \param from number to increase/decrease + /// \param to direction to enumerate into + /// \return next representable number + static half nextafter(half from, half to) + { + uint16 fabs = from.data_ & 0x7FFF, tabs = to.data_ & 0x7FFF; + if(fabs > 0x7C00) + return from; + if(tabs > 0x7C00 || from.data_ == to.data_ || !(fabs|tabs)) + return to; + if(!fabs) + return half(binary, (to.data_&0x8000)+1); + bool lt = (signbit(from) ? (static_cast(0x8000)-from.data_) : static_cast(from.data_)) < + (signbit(to) ? (static_cast(0x8000)-to.data_) : static_cast(to.data_)); + return half(binary, from.data_+(((from.data_>>15)^static_cast(lt))<<1)-1); + } + + /// Enumeration implementation. + /// \param from number to increase/decrease + /// \param to direction to enumerate into + /// \return next representable number + static half nexttoward(half from, long double to) + { + if(isnan(from)) + return from; + long double lfrom = static_cast(from); + if(builtin_isnan(to) || lfrom == to) + return half(static_cast(to)); + if(!(from.data_&0x7FFF)) + return half(binary, (static_cast(builtin_signbit(to))<<15)+1); + return half(binary, from.data_+(((from.data_>>15)^static_cast(lfrom 0x7C00) + return FP_NAN; + if(abs == 0x7C00) + return FP_INFINITE; + if(abs > 0x3FF) + return FP_NORMAL; + return abs ? FP_SUBNORMAL : FP_ZERO; + } + + /// Classification implementation. + /// \param arg value to classify + /// \retval true if finite number + /// \retval false else + static bool isfinite(half arg) { return (arg.data_&0x7C00) != 0x7C00; } + + /// Classification implementation. + /// \param arg value to classify + /// \retval true if infinite number + /// \retval false else + static bool isinf(half arg) { return (arg.data_&0x7FFF) == 0x7C00; } + + /// Classification implementation. + /// \param arg value to classify + /// \retval true if not a number + /// \retval false else + static bool isnan(half arg) { return (arg.data_&0x7FFF) > 0x7C00; } + + /// Classification implementation. + /// \param arg value to classify + /// \retval true if normal number + /// \retval false else + static bool isnormal(half arg) { return ((arg.data_&0x7C00)!=0) & ((arg.data_&0x7C00)!=0x7C00); } + + /// Sign bit implementation. + /// \param arg value to check + /// \retval true if signed + /// \retval false if unsigned + static bool signbit(half arg) { return (arg.data_&0x8000) != 0; } + + /// Comparison implementation. + /// \param x first operand + /// \param y second operand + /// \retval true if operands equal + /// \retval false else + static bool isequal(half x, half y) { return (x.data_==y.data_ || !((x.data_|y.data_)&0x7FFF)) && !isnan(x); } + + /// Comparison implementation. + /// \param x first operand + /// \param y second operand + /// \retval true if operands not equal + /// \retval false else + static bool isnotequal(half x, half y) { return (x.data_!=y.data_ && ((x.data_|y.data_)&0x7FFF)) || isnan(x); } + + /// Comparison implementation. + /// \param x first operand + /// \param y second operand + /// \retval true if \a x > \a y + /// \retval false else + static bool isgreater(half x, half y) { return !isnan(x) && !isnan(y) && ((signbit(x) ? (static_cast(0x8000)-x.data_) : + static_cast(x.data_)) > (signbit(y) ? (static_cast(0x8000)-y.data_) : static_cast(y.data_))); } + + /// Comparison implementation. + /// \param x first operand + /// \param y second operand + /// \retval true if \a x >= \a y + /// \retval false else + static bool isgreaterequal(half x, half y) { return !isnan(x) && !isnan(y) && ((signbit(x) ? (static_cast(0x8000)-x.data_) : + static_cast(x.data_)) >= (signbit(y) ? (static_cast(0x8000)-y.data_) : static_cast(y.data_))); } + + /// Comparison implementation. + /// \param x first operand + /// \param y second operand + /// \retval true if \a x < \a y + /// \retval false else + static bool isless(half x, half y) { return !isnan(x) && !isnan(y) && ((signbit(x) ? (static_cast(0x8000)-x.data_) : + static_cast(x.data_)) < (signbit(y) ? (static_cast(0x8000)-y.data_) : static_cast(y.data_))); } + + /// Comparison implementation. + /// \param x first operand + /// \param y second operand + /// \retval true if \a x <= \a y + /// \retval false else + static bool islessequal(half x, half y) { return !isnan(x) && !isnan(y) && ((signbit(x) ? (static_cast(0x8000)-x.data_) : + static_cast(x.data_)) <= (signbit(y) ? (static_cast(0x8000)-y.data_) : static_cast(y.data_))); } + + /// Comparison implementation. + /// \param x first operand + /// \param y second operand + /// \retval true neither \a x > \a y nor \a x < \a y + /// \retval false else + static bool islessgreater(half x, half y) + { + if(isnan(x) || isnan(y)) + return false; + int17 a = signbit(x) ? (static_cast(0x8000)-x.data_) : static_cast(x.data_); + int17 b = signbit(y) ? (static_cast(0x8000)-y.data_) : static_cast(y.data_); + return a < b || a > b; + } + + /// Comparison implementation. + /// \param x first operand + /// \param y second operand + /// \retval true if operand unordered + /// \retval false else + static bool isunordered(half x, half y) { return isnan(x) || isnan(y); } + + private: + static double erf(double arg) + { + if(builtin_isinf(arg)) + return (arg<0.0) ? -1.0 : 1.0; + double x2 = static_cast(arg) * static_cast(arg), ax2 = 0.147 * x2; + double value = std::sqrt(1.0-std::exp(-x2*(1.2732395447351626861510701069801+ax2)/(1.0+ax2))); + return builtin_signbit(arg) ? -value : value; + } + + static double lgamma(double arg) + { + double v = 1.0; + for(; arg<8.0; ++arg) v *= arg; + double w = 1.0 / (arg * arg); + return (((((((-0.02955065359477124183006535947712*w+0.00641025641025641025641025641026)*w+ + -0.00191752691752691752691752691753)*w+8.4175084175084175084175084175084e-4)*w+ + -5.952380952380952380952380952381e-4)*w+7.9365079365079365079365079365079e-4)*w+ + -0.00277777777777777777777777777778)*w+0.08333333333333333333333333333333)/arg + + 0.91893853320467274178032973640562 - std::log(v) - arg + (arg-0.5) * std::log(arg); + } + }; + + /// Wrapper for unary half-precision functions needing specialization for individual argument types. + /// \tparam T argument type + template struct unary_specialized + { + /// Negation implementation. + /// \param arg value to negate + /// \return negated value + static HALF_CONSTEXPR half negate(half arg) { return half(binary, arg.data_^0x8000); } + + /// Absolute value implementation. + /// \param arg function argument + /// \return absolute value + static half fabs(half arg) { return half(binary, arg.data_&0x7FFF); } + }; + template<> struct unary_specialized + { + static HALF_CONSTEXPR expr negate(float arg) { return expr(-arg); } + static expr fabs(float arg) { return expr(std::fabs(arg)); } + }; + + /// Wrapper for binary half-precision functions needing specialization for individual argument types. + /// \tparam T first argument type + /// \tparam U first argument type + template struct binary_specialized + { + /// Minimum implementation. + /// \param x first operand + /// \param y second operand + /// \return minimum value + static expr fmin(float x, float y) + { + #if HALF_ENABLE_CPP11_CMATH + return expr(std::fmin(x, y)); + #else + if(builtin_isnan(x)) + return expr(y); + if(builtin_isnan(y)) + return expr(x); + return expr(std::min(x, y)); + #endif + } + + /// Maximum implementation. + /// \param x first operand + /// \param y second operand + /// \return maximum value + static expr fmax(float x, float y) + { + #if HALF_ENABLE_CPP11_CMATH + return expr(std::fmax(x, y)); + #else + if(builtin_isnan(x)) + return expr(y); + if(builtin_isnan(y)) + return expr(x); + return expr(std::max(x, y)); + #endif + } + }; + template<> struct binary_specialized + { + static half fmin(half x, half y) + { + if(functions::isnan(x)) + return y; + if(functions::isnan(y)) + return x; + return ((functions::signbit(x) ? (static_cast(0x8000)-x.data_) : static_cast(x.data_)) > + (functions::signbit(y) ? (static_cast(0x8000)-y.data_) : static_cast(y.data_))) ? y : x; + } + static half fmax(half x, half y) + { + if(functions::isnan(x)) + return y; + if(functions::isnan(y)) + return x; + return ((functions::signbit(x) ? (static_cast(0x8000)-x.data_) : static_cast(x.data_)) < + (functions::signbit(y) ? (static_cast(0x8000)-y.data_) : static_cast(y.data_))) ? y : x; + } + }; + + /// Helper class for half casts. + /// This class template has to be specialized for all valid cast argument to define an appropriate static `cast` member + /// function and a corresponding `type` member denoting its return type. + /// \tparam T destination type + /// \tparam U source type + /// \tparam R rounding mode to use + template struct half_caster {}; + template struct half_caster + { + #if HALF_ENABLE_CPP11_STATIC_ASSERT && HALF_ENABLE_CPP11_TYPE_TRAITS + static_assert(std::is_arithmetic::value, "half_cast from non-arithmetic type unsupported"); + #endif + + typedef half type; + static half cast(U arg) { return cast_impl(arg, is_float()); }; + + private: + static half cast_impl(U arg, true_type) { return half(binary, float2half(static_cast(arg))); } + static half cast_impl(U arg, false_type) { return half(binary, int2half(arg)); } + }; + template struct half_caster + { + #if HALF_ENABLE_CPP11_STATIC_ASSERT && HALF_ENABLE_CPP11_TYPE_TRAITS + static_assert(std::is_arithmetic::value, "half_cast to non-arithmetic type unsupported"); + #endif + + typedef T type; + template static T cast(U arg) { return cast_impl(arg, is_float()); } + + private: + static T cast_impl(float arg, true_type) { return static_cast(arg); } + static T cast_impl(half arg, false_type) { return half2int(arg.data_); } + }; + template struct half_caster : public half_caster {}; + template struct half_caster + { + typedef half type; + static half cast(half arg) { return arg; } + }; + template struct half_caster : public half_caster {}; + + /// \name Comparison operators + /// \{ + + /// Comparison for equality. + /// \param x first operand + /// \param y second operand + /// \retval true if operands equal + /// \retval false else + template typename enable::type operator==(T x, U y) { return functions::isequal(x, y); } + + /// Comparison for inequality. + /// \param x first operand + /// \param y second operand + /// \retval true if operands not equal + /// \retval false else + template typename enable::type operator!=(T x, U y) { return functions::isnotequal(x, y); } + + /// Comparison for less than. + /// \param x first operand + /// \param y second operand + /// \retval true if \a x less than \a y + /// \retval false else + template typename enable::type operator<(T x, U y) { return functions::isless(x, y); } + + /// Comparison for greater than. + /// \param x first operand + /// \param y second operand + /// \retval true if \a x greater than \a y + /// \retval false else + template typename enable::type operator>(T x, U y) { return functions::isgreater(x, y); } + + /// Comparison for less equal. + /// \param x first operand + /// \param y second operand + /// \retval true if \a x less equal \a y + /// \retval false else + template typename enable::type operator<=(T x, U y) { return functions::islessequal(x, y); } + + /// Comparison for greater equal. + /// \param x first operand + /// \param y second operand + /// \retval true if \a x greater equal \a y + /// \retval false else + template typename enable::type operator>=(T x, U y) { return functions::isgreaterequal(x, y); } + + /// \} + /// \name Arithmetic operators + /// \{ + + /// Add halfs. + /// \param x left operand + /// \param y right operand + /// \return sum of half expressions + template typename enable::type operator+(T x, U y) { return functions::plus(x, y); } + + /// Subtract halfs. + /// \param x left operand + /// \param y right operand + /// \return difference of half expressions + template typename enable::type operator-(T x, U y) { return functions::minus(x, y); } + + /// Multiply halfs. + /// \param x left operand + /// \param y right operand + /// \return product of half expressions + template typename enable::type operator*(T x, U y) { return functions::multiplies(x, y); } + + /// Divide halfs. + /// \param x left operand + /// \param y right operand + /// \return quotient of half expressions + template typename enable::type operator/(T x, U y) { return functions::divides(x, y); } + + /// Identity. + /// \param arg operand + /// \return uncahnged operand + template HALF_CONSTEXPR typename enable::type operator+(T arg) { return arg; } + + /// Negation. + /// \param arg operand + /// \return negated operand + template HALF_CONSTEXPR typename enable::type operator-(T arg) { return unary_specialized::negate(arg); } + + /// \} + /// \name Input and output + /// \{ + + /// Output operator. + /// \param out output stream to write into + /// \param arg half expression to write + /// \return reference to output stream + template typename enable&,T>::type + operator<<(std::basic_ostream &out, T arg) { return functions::write(out, arg); } + + /// Input operator. + /// \param in input stream to read from + /// \param arg half to read into + /// \return reference to input stream + template std::basic_istream& + operator>>(std::basic_istream &in, half &arg) { return functions::read(in, arg); } + + /// \} + /// \name Basic mathematical operations + /// \{ + + /// Absolute value. + /// \param arg operand + /// \return absolute value of \a arg +// template typename enable::type abs(T arg) { return unary_specialized::fabs(arg); } + inline half abs(half arg) { return unary_specialized::fabs(arg); } + inline expr abs(expr arg) { return unary_specialized::fabs(arg); } + + /// Absolute value. + /// \param arg operand + /// \return absolute value of \a arg +// template typename enable::type fabs(T arg) { return unary_specialized::fabs(arg); } + inline half fabs(half arg) { return unary_specialized::fabs(arg); } + inline expr fabs(expr arg) { return unary_specialized::fabs(arg); } + + /// Remainder of division. + /// \param x first operand + /// \param y second operand + /// \return remainder of floating point division. +// template typename enable::type fmod(T x, U y) { return functions::fmod(x, y); } + inline expr fmod(half x, half y) { return functions::fmod(x, y); } + inline expr fmod(half x, expr y) { return functions::fmod(x, y); } + inline expr fmod(expr x, half y) { return functions::fmod(x, y); } + inline expr fmod(expr x, expr y) { return functions::fmod(x, y); } + + /// Remainder of division. + /// \param x first operand + /// \param y second operand + /// \return remainder of floating point division. +// template typename enable::type remainder(T x, U y) { return functions::remainder(x, y); } + inline expr remainder(half x, half y) { return functions::remainder(x, y); } + inline expr remainder(half x, expr y) { return functions::remainder(x, y); } + inline expr remainder(expr x, half y) { return functions::remainder(x, y); } + inline expr remainder(expr x, expr y) { return functions::remainder(x, y); } + + /// Remainder of division. + /// \param x first operand + /// \param y second operand + /// \param quo address to store some bits of quotient at + /// \return remainder of floating point division. +// template typename enable::type remquo(T x, U y, int *quo) { return functions::remquo(x, y, quo); } + inline expr remquo(half x, half y, int *quo) { return functions::remquo(x, y, quo); } + inline expr remquo(half x, expr y, int *quo) { return functions::remquo(x, y, quo); } + inline expr remquo(expr x, half y, int *quo) { return functions::remquo(x, y, quo); } + inline expr remquo(expr x, expr y, int *quo) { return functions::remquo(x, y, quo); } + + /// Fused multiply add. + /// \param x first operand + /// \param y second operand + /// \param z third operand + /// \return ( \a x * \a y ) + \a z rounded as one operation. +// template typename enable::type fma(T x, U y, V z) { return functions::fma(x, y, z); } + inline expr fma(half x, half y, half z) { return functions::fma(x, y, z); } + inline expr fma(half x, half y, expr z) { return functions::fma(x, y, z); } + inline expr fma(half x, expr y, half z) { return functions::fma(x, y, z); } + inline expr fma(half x, expr y, expr z) { return functions::fma(x, y, z); } + inline expr fma(expr x, half y, half z) { return functions::fma(x, y, z); } + inline expr fma(expr x, half y, expr z) { return functions::fma(x, y, z); } + inline expr fma(expr x, expr y, half z) { return functions::fma(x, y, z); } + inline expr fma(expr x, expr y, expr z) { return functions::fma(x, y, z); } + + /// Maximum of half expressions. + /// \param x first operand + /// \param y second operand + /// \return maximum of operands +// template typename result::type fmax(T x, U y) { return binary_specialized::fmax(x, y); } + inline half fmax(half x, half y) { return binary_specialized::fmax(x, y); } + inline expr fmax(half x, expr y) { return binary_specialized::fmax(x, y); } + inline expr fmax(expr x, half y) { return binary_specialized::fmax(x, y); } + inline expr fmax(expr x, expr y) { return binary_specialized::fmax(x, y); } + + /// Minimum of half expressions. + /// \param x first operand + /// \param y second operand + /// \return minimum of operands +// template typename result::type fmin(T x, U y) { return binary_specialized::fmin(x, y); } + inline half fmin(half x, half y) { return binary_specialized::fmin(x, y); } + inline expr fmin(half x, expr y) { return binary_specialized::fmin(x, y); } + inline expr fmin(expr x, half y) { return binary_specialized::fmin(x, y); } + inline expr fmin(expr x, expr y) { return binary_specialized::fmin(x, y); } + + /// Positive difference. + /// \param x first operand + /// \param y second operand + /// \return \a x - \a y or 0 if difference negative +// template typename enable::type fdim(T x, U y) { return functions::fdim(x, y); } + inline expr fdim(half x, half y) { return functions::fdim(x, y); } + inline expr fdim(half x, expr y) { return functions::fdim(x, y); } + inline expr fdim(expr x, half y) { return functions::fdim(x, y); } + inline expr fdim(expr x, expr y) { return functions::fdim(x, y); } + + /// Get NaN value. + /// \param arg descriptive string (ignored) + /// \return quiet NaN + inline half nanh(const char *arg) { return functions::nanh(arg); } + + /// \} + /// \name Exponential functions + /// \{ + + /// Exponential function. + /// \param arg function argument + /// \return e raised to \a arg +// template typename enable::type exp(T arg) { return functions::exp(arg); } + inline expr exp(half arg) { return functions::exp(arg); } + inline expr exp(expr arg) { return functions::exp(arg); } + + /// Exponential minus one. + /// \param arg function argument + /// \return e raised to \a arg subtracted by 1 +// template typename enable::type expm1(T arg) { return functions::expm1(arg); } + inline expr expm1(half arg) { return functions::expm1(arg); } + inline expr expm1(expr arg) { return functions::expm1(arg); } + + /// Binary exponential. + /// \param arg function argument + /// \return 2 raised to \a arg +// template typename enable::type exp2(T arg) { return functions::exp2(arg); } + inline expr exp2(half arg) { return functions::exp2(arg); } + inline expr exp2(expr arg) { return functions::exp2(arg); } + + /// Natural logorithm. + /// \param arg function argument + /// \return logarithm of \a arg to base e +// template typename enable::type log(T arg) { return functions::log(arg); } + inline expr log(half arg) { return functions::log(arg); } + inline expr log(expr arg) { return functions::log(arg); } + + /// Common logorithm. + /// \param arg function argument + /// \return logarithm of \a arg to base 10 +// template typename enable::type log10(T arg) { return functions::log10(arg); } + inline expr log10(half arg) { return functions::log10(arg); } + inline expr log10(expr arg) { return functions::log10(arg); } + + /// Natural logorithm. + /// \param arg function argument + /// \return logarithm of \a arg plus 1 to base e +// template typename enable::type log1p(T arg) { return functions::log1p(arg); } + inline expr log1p(half arg) { return functions::log1p(arg); } + inline expr log1p(expr arg) { return functions::log1p(arg); } + + /// Binary logorithm. + /// \param arg function argument + /// \return logarithm of \a arg to base 2 +// template typename enable::type log2(T arg) { return functions::log2(arg); } + inline expr log2(half arg) { return functions::log2(arg); } + inline expr log2(expr arg) { return functions::log2(arg); } + + /// \} + /// \name Power functions + /// \{ + + /// Square root. + /// \param arg function argument + /// \return square root of \a arg +// template typename enable::type sqrt(T arg) { return functions::sqrt(arg); } + inline expr sqrt(half arg) { return functions::sqrt(arg); } + inline expr sqrt(expr arg) { return functions::sqrt(arg); } + + /// Cubic root. + /// \param arg function argument + /// \return cubic root of \a arg +// template typename enable::type cbrt(T arg) { return functions::cbrt(arg); } + inline expr cbrt(half arg) { return functions::cbrt(arg); } + inline expr cbrt(expr arg) { return functions::cbrt(arg); } + + /// Hypotenuse function. + /// \param x first argument + /// \param y second argument + /// \return square root of sum of squares without internal over- or underflows +// template typename enable::type hypot(T x, U y) { return functions::hypot(x, y); } + inline expr hypot(half x, half y) { return functions::hypot(x, y); } + inline expr hypot(half x, expr y) { return functions::hypot(x, y); } + inline expr hypot(expr x, half y) { return functions::hypot(x, y); } + inline expr hypot(expr x, expr y) { return functions::hypot(x, y); } + + /// Power function. + /// \param base first argument + /// \param exp second argument + /// \return \a base raised to \a exp +// template typename enable::type pow(T base, U exp) { return functions::pow(base, exp); } + inline expr pow(half base, half exp) { return functions::pow(base, exp); } + inline expr pow(half base, expr exp) { return functions::pow(base, exp); } + inline expr pow(expr base, half exp) { return functions::pow(base, exp); } + inline expr pow(expr base, expr exp) { return functions::pow(base, exp); } + + /// \} + /// \name Trigonometric functions + /// \{ + + /// Sine function. + /// \param arg function argument + /// \return sine value of \a arg +// template typename enable::type sin(T arg) { return functions::sin(arg); } + inline expr sin(half arg) { return functions::sin(arg); } + inline expr sin(expr arg) { return functions::sin(arg); } + + /// Cosine function. + /// \param arg function argument + /// \return cosine value of \a arg +// template typename enable::type cos(T arg) { return functions::cos(arg); } + inline expr cos(half arg) { return functions::cos(arg); } + inline expr cos(expr arg) { return functions::cos(arg); } + + /// Tangent function. + /// \param arg function argument + /// \return tangent value of \a arg +// template typename enable::type tan(T arg) { return functions::tan(arg); } + inline expr tan(half arg) { return functions::tan(arg); } + inline expr tan(expr arg) { return functions::tan(arg); } + + /// Arc sine. + /// \param arg function argument + /// \return arc sine value of \a arg +// template typename enable::type asin(T arg) { return functions::asin(arg); } + inline expr asin(half arg) { return functions::asin(arg); } + inline expr asin(expr arg) { return functions::asin(arg); } + + /// Arc cosine function. + /// \param arg function argument + /// \return arc cosine value of \a arg +// template typename enable::type acos(T arg) { return functions::acos(arg); } + inline expr acos(half arg) { return functions::acos(arg); } + inline expr acos(expr arg) { return functions::acos(arg); } + + /// Arc tangent function. + /// \param arg function argument + /// \return arc tangent value of \a arg +// template typename enable::type atan(T arg) { return functions::atan(arg); } + inline expr atan(half arg) { return functions::atan(arg); } + inline expr atan(expr arg) { return functions::atan(arg); } + + /// Arc tangent function. + /// \param x first argument + /// \param y second argument + /// \return arc tangent value +// template typename enable::type atan2(T x, U y) { return functions::atan2(x, y); } + inline expr atan2(half x, half y) { return functions::atan2(x, y); } + inline expr atan2(half x, expr y) { return functions::atan2(x, y); } + inline expr atan2(expr x, half y) { return functions::atan2(x, y); } + inline expr atan2(expr x, expr y) { return functions::atan2(x, y); } + + /// \} + /// \name Hyperbolic functions + /// \{ + + /// Hyperbolic sine. + /// \param arg function argument + /// \return hyperbolic sine value of \a arg +// template typename enable::type sinh(T arg) { return functions::sinh(arg); } + inline expr sinh(half arg) { return functions::sinh(arg); } + inline expr sinh(expr arg) { return functions::sinh(arg); } + + /// Hyperbolic cosine. + /// \param arg function argument + /// \return hyperbolic cosine value of \a arg +// template typename enable::type cosh(T arg) { return functions::cosh(arg); } + inline expr cosh(half arg) { return functions::cosh(arg); } + inline expr cosh(expr arg) { return functions::cosh(arg); } + + /// Hyperbolic tangent. + /// \param arg function argument + /// \return hyperbolic tangent value of \a arg +// template typename enable::type tanh(T arg) { return functions::tanh(arg); } + inline expr tanh(half arg) { return functions::tanh(arg); } + inline expr tanh(expr arg) { return functions::tanh(arg); } + + /// Hyperbolic area sine. + /// \param arg function argument + /// \return area sine value of \a arg +// template typename enable::type asinh(T arg) { return functions::asinh(arg); } + inline expr asinh(half arg) { return functions::asinh(arg); } + inline expr asinh(expr arg) { return functions::asinh(arg); } + + /// Hyperbolic area cosine. + /// \param arg function argument + /// \return area cosine value of \a arg +// template typename enable::type acosh(T arg) { return functions::acosh(arg); } + inline expr acosh(half arg) { return functions::acosh(arg); } + inline expr acosh(expr arg) { return functions::acosh(arg); } + + /// Hyperbolic area tangent. + /// \param arg function argument + /// \return area tangent value of \a arg +// template typename enable::type atanh(T arg) { return functions::atanh(arg); } + inline expr atanh(half arg) { return functions::atanh(arg); } + inline expr atanh(expr arg) { return functions::atanh(arg); } + + /// \} + /// \name Error and gamma functions + /// \{ + + /// Error function. + /// \param arg function argument + /// \return error function value of \a arg +// template typename enable::type erf(T arg) { return functions::erf(arg); } + inline expr erf(half arg) { return functions::erf(arg); } + inline expr erf(expr arg) { return functions::erf(arg); } + + /// Complementary error function. + /// \param arg function argument + /// \return 1 minus error function value of \a arg +// template typename enable::type erfc(T arg) { return functions::erfc(arg); } + inline expr erfc(half arg) { return functions::erfc(arg); } + inline expr erfc(expr arg) { return functions::erfc(arg); } + + /// Natural logarithm of gamma function. + /// \param arg function argument + /// \return natural logarith of gamma function for \a arg +// template typename enable::type lgamma(T arg) { return functions::lgamma(arg); } + inline expr lgamma(half arg) { return functions::lgamma(arg); } + inline expr lgamma(expr arg) { return functions::lgamma(arg); } + + /// Gamma function. + /// \param arg function argument + /// \return gamma function value of \a arg +// template typename enable::type tgamma(T arg) { return functions::tgamma(arg); } + inline expr tgamma(half arg) { return functions::tgamma(arg); } + inline expr tgamma(expr arg) { return functions::tgamma(arg); } + + /// \} + /// \name Rounding + /// \{ + + /// Nearest integer not less than half value. + /// \param arg half to round + /// \return nearest integer not less than \a arg +// template typename enable::type ceil(T arg) { return functions::ceil(arg); } + inline half ceil(half arg) { return functions::ceil(arg); } + inline half ceil(expr arg) { return functions::ceil(arg); } + + /// Nearest integer not greater than half value. + /// \param arg half to round + /// \return nearest integer not greater than \a arg +// template typename enable::type floor(T arg) { return functions::floor(arg); } + inline half floor(half arg) { return functions::floor(arg); } + inline half floor(expr arg) { return functions::floor(arg); } + + /// Nearest integer not greater in magnitude than half value. + /// \param arg half to round + /// \return nearest integer not greater in magnitude than \a arg +// template typename enable::type trunc(T arg) { return functions::trunc(arg); } + inline half trunc(half arg) { return functions::trunc(arg); } + inline half trunc(expr arg) { return functions::trunc(arg); } + + /// Nearest integer. + /// \param arg half to round + /// \return nearest integer, rounded away from zero in half-way cases +// template typename enable::type round(T arg) { return functions::round(arg); } + inline half round(half arg) { return functions::round(arg); } + inline half round(expr arg) { return functions::round(arg); } + + /// Nearest integer. + /// \param arg half to round + /// \return nearest integer, rounded away from zero in half-way cases +// template typename enable::type lround(T arg) { return functions::lround(arg); } + inline long lround(half arg) { return functions::lround(arg); } + inline long lround(expr arg) { return functions::lround(arg); } + + /// Nearest integer using half's internal rounding mode. + /// \param arg half expression to round + /// \return nearest integer using default rounding mode +// template typename enable::type nearbyint(T arg) { return functions::nearbyint(arg); } + inline half nearbyint(half arg) { return functions::rint(arg); } + inline half nearbyint(expr arg) { return functions::rint(arg); } + + /// Nearest integer using half's internal rounding mode. + /// \param arg half expression to round + /// \return nearest integer using default rounding mode +// template typename enable::type rint(T arg) { return functions::rint(arg); } + inline half rint(half arg) { return functions::rint(arg); } + inline half rint(expr arg) { return functions::rint(arg); } + + /// Nearest integer using half's internal rounding mode. + /// \param arg half expression to round + /// \return nearest integer using default rounding mode +// template typename enable::type lrint(T arg) { return functions::lrint(arg); } + inline long lrint(half arg) { return functions::lrint(arg); } + inline long lrint(expr arg) { return functions::lrint(arg); } + #if HALF_ENABLE_CPP11_LONG_LONG + /// Nearest integer. + /// \param arg half to round + /// \return nearest integer, rounded away from zero in half-way cases +// template typename enable::type llround(T arg) { return functions::llround(arg); } + inline long long llround(half arg) { return functions::llround(arg); } + inline long long llround(expr arg) { return functions::llround(arg); } + + /// Nearest integer using half's internal rounding mode. + /// \param arg half expression to round + /// \return nearest integer using default rounding mode +// template typename enable::type llrint(T arg) { return functions::llrint(arg); } + inline long long llrint(half arg) { return functions::llrint(arg); } + inline long long llrint(expr arg) { return functions::llrint(arg); } + #endif + + /// \} + /// \name Floating point manipulation + /// \{ + + /// Decompress floating point number. + /// \param arg number to decompress + /// \param exp address to store exponent at + /// \return significant in range [0.5, 1) +// template typename enable::type frexp(T arg, int *exp) { return functions::frexp(arg, exp); } + inline half frexp(half arg, int *exp) { return functions::frexp(arg, exp); } + inline half frexp(expr arg, int *exp) { return functions::frexp(arg, exp); } + + /// Multiply by power of two. + /// \param arg number to modify + /// \param exp power of two to multiply with + /// \return \a arg multplied by 2 raised to \a exp +// template typename enable::type ldexp(T arg, int exp) { return functions::scalbln(arg, exp); } + inline half ldexp(half arg, int exp) { return functions::scalbln(arg, exp); } + inline half ldexp(expr arg, int exp) { return functions::scalbln(arg, exp); } + + /// Extract integer and fractional parts. + /// \param arg number to decompress + /// \param iptr address to store integer part at + /// \return fractional part +// template typename enable::type modf(T arg, half *iptr) { return functions::modf(arg, iptr); } + inline half modf(half arg, half *iptr) { return functions::modf(arg, iptr); } + inline half modf(expr arg, half *iptr) { return functions::modf(arg, iptr); } + + /// Multiply by power of two. + /// \param arg number to modify + /// \param exp power of two to multiply with + /// \return \a arg multplied by 2 raised to \a exp +// template typename enable::type scalbn(T arg, int exp) { return functions::scalbln(arg, exp); } + inline half scalbn(half arg, int exp) { return functions::scalbln(arg, exp); } + inline half scalbn(expr arg, int exp) { return functions::scalbln(arg, exp); } + + /// Multiply by power of two. + /// \param arg number to modify + /// \param exp power of two to multiply with + /// \return \a arg multplied by 2 raised to \a exp +// template typename enable::type scalbln(T arg, long exp) { return functions::scalbln(arg, exp); } + inline half scalbln(half arg, long exp) { return functions::scalbln(arg, exp); } + inline half scalbln(expr arg, long exp) { return functions::scalbln(arg, exp); } + + /// Extract exponent. + /// \param arg number to query + /// \return floating point exponent + /// \retval FP_ILOGB0 for zero + /// \retval FP_ILOGBNAN for NaN + /// \retval MAX_INT for infinity +// template typename enable::type ilogb(T arg) { return functions::ilogb(arg); } + inline int ilogb(half arg) { return functions::ilogb(arg); } + inline int ilogb(expr arg) { return functions::ilogb(arg); } + + /// Extract exponent. + /// \param arg number to query + /// \return floating point exponent +// template typename enable::type logb(T arg) { return functions::logb(arg); } + inline half logb(half arg) { return functions::logb(arg); } + inline half logb(expr arg) { return functions::logb(arg); } + + /// Next representable value. + /// \param from value to compute next representable value for + /// \param to direction towards which to compute next value + /// \return next representable value after \a from in direction towards \a to +// template typename enable::type nextafter(T from, U to) { return functions::nextafter(from, to); } + inline half nextafter(half from, half to) { return functions::nextafter(from, to); } + inline half nextafter(half from, expr to) { return functions::nextafter(from, to); } + inline half nextafter(expr from, half to) { return functions::nextafter(from, to); } + inline half nextafter(expr from, expr to) { return functions::nextafter(from, to); } + + /// Next representable value. + /// \param from value to compute next representable value for + /// \param to direction towards which to compute next value + /// \return next representable value after \a from in direction towards \a to +// template typename enable::type nexttoward(T from, long double to) { return functions::nexttoward(from, to); } + inline half nexttoward(half from, long double to) { return functions::nexttoward(from, to); } + inline half nexttoward(expr from, long double to) { return functions::nexttoward(from, to); } + + /// Take sign. + /// \param x value to change sign for + /// \param y value to take sign from + /// \return value equal to \a x in magnitude and to \a y in sign +// template typename enable::type copysign(T x, U y) { return functions::copysign(x, y); } + inline half copysign(half x, half y) { return functions::copysign(x, y); } + inline half copysign(half x, expr y) { return functions::copysign(x, y); } + inline half copysign(expr x, half y) { return functions::copysign(x, y); } + inline half copysign(expr x, expr y) { return functions::copysign(x, y); } + + /// \} + /// \name Floating point classification + /// \{ + + + /// Classify floating point value. + /// \param arg number to classify + /// \retval FP_ZERO for positive and negative zero + /// \retval FP_SUBNORMAL for subnormal numbers + /// \retval FP_INFINITY for positive and negative infinity + /// \retval FP_NAN for NaNs + /// \retval FP_NORMAL for all other (normal) values +// template typename enable::type fpclassify(T arg) { return functions::fpclassify(arg); } + inline int fpclassify(half arg) { return functions::fpclassify(arg); } + inline int fpclassify(expr arg) { return functions::fpclassify(arg); } + + /// Check if finite number. + /// \param arg number to check + /// \retval true if neither infinity nor NaN + /// \retval false else +// template typename enable::type isfinite(T arg) { return functions::isfinite(arg); } + inline bool isfinite(half arg) { return functions::isfinite(arg); } + inline bool isfinite(expr arg) { return functions::isfinite(arg); } + + /// Check for infinity. + /// \param arg number to check + /// \retval true for positive or negative infinity + /// \retval false else +// template typename enable::type isinf(T arg) { return functions::isinf(arg); } + inline bool isinf(half arg) { return functions::isinf(arg); } + inline bool isinf(expr arg) { return functions::isinf(arg); } + + /// Check for NaN. + /// \param arg number to check + /// \retval true for NaNs + /// \retval false else +// template typename enable::type isnan(T arg) { return functions::isnan(arg); } + inline bool isnan(half arg) { return functions::isnan(arg); } + inline bool isnan(expr arg) { return functions::isnan(arg); } + + /// Check if normal number. + /// \param arg number to check + /// \retval true if normal number + /// \retval false if either subnormal, zero, infinity or NaN +// template typename enable::type isnormal(T arg) { return functions::isnormal(arg); } + inline bool isnormal(half arg) { return functions::isnormal(arg); } + inline bool isnormal(expr arg) { return functions::isnormal(arg); } + + /// Check sign. + /// \param arg number to check + /// \retval true for negative number + /// \retval false for positive number +// template typename enable::type signbit(T arg) { return functions::signbit(arg); } + inline bool signbit(half arg) { return functions::signbit(arg); } + inline bool signbit(expr arg) { return functions::signbit(arg); } + + /// \} + /// \name Comparison + /// \{ + + /// Comparison for greater than. + /// \param x first operand + /// \param y second operand + /// \retval true if \a x greater than \a y + /// \retval false else +// template typename enable::type isgreater(T x, U y) { return functions::isgreater(x, y); } + inline bool isgreater(half x, half y) { return functions::isgreater(x, y); } + inline bool isgreater(half x, expr y) { return functions::isgreater(x, y); } + inline bool isgreater(expr x, half y) { return functions::isgreater(x, y); } + inline bool isgreater(expr x, expr y) { return functions::isgreater(x, y); } + + /// Comparison for greater equal. + /// \param x first operand + /// \param y second operand + /// \retval true if \a x greater equal \a y + /// \retval false else +// template typename enable::type isgreaterequal(T x, U y) { return functions::isgreaterequal(x, y); } + inline bool isgreaterequal(half x, half y) { return functions::isgreaterequal(x, y); } + inline bool isgreaterequal(half x, expr y) { return functions::isgreaterequal(x, y); } + inline bool isgreaterequal(expr x, half y) { return functions::isgreaterequal(x, y); } + inline bool isgreaterequal(expr x, expr y) { return functions::isgreaterequal(x, y); } + + /// Comparison for less than. + /// \param x first operand + /// \param y second operand + /// \retval true if \a x less than \a y + /// \retval false else +// template typename enable::type isless(T x, U y) { return functions::isless(x, y); } + inline bool isless(half x, half y) { return functions::isless(x, y); } + inline bool isless(half x, expr y) { return functions::isless(x, y); } + inline bool isless(expr x, half y) { return functions::isless(x, y); } + inline bool isless(expr x, expr y) { return functions::isless(x, y); } + + /// Comparison for less equal. + /// \param x first operand + /// \param y second operand + /// \retval true if \a x less equal \a y + /// \retval false else +// template typename enable::type islessequal(T x, U y) { return functions::islessequal(x, y); } + inline bool islessequal(half x, half y) { return functions::islessequal(x, y); } + inline bool islessequal(half x, expr y) { return functions::islessequal(x, y); } + inline bool islessequal(expr x, half y) { return functions::islessequal(x, y); } + inline bool islessequal(expr x, expr y) { return functions::islessequal(x, y); } + + /// Comarison for less or greater. + /// \param x first operand + /// \param y second operand + /// \retval true if either less or greater + /// \retval false else +// template typename enable::type islessgreater(T x, U y) { return functions::islessgreater(x, y); } + inline bool islessgreater(half x, half y) { return functions::islessgreater(x, y); } + inline bool islessgreater(half x, expr y) { return functions::islessgreater(x, y); } + inline bool islessgreater(expr x, half y) { return functions::islessgreater(x, y); } + inline bool islessgreater(expr x, expr y) { return functions::islessgreater(x, y); } + + /// Check if unordered. + /// \param x first operand + /// \param y second operand + /// \retval true if unordered (one or two NaN operands) + /// \retval false else +// template typename enable::type isunordered(T x, U y) { return functions::isunordered(x, y); } + inline bool isunordered(half x, half y) { return functions::isunordered(x, y); } + inline bool isunordered(half x, expr y) { return functions::isunordered(x, y); } + inline bool isunordered(expr x, half y) { return functions::isunordered(x, y); } + inline bool isunordered(expr x, expr y) { return functions::isunordered(x, y); } + + /// \name Casting + /// \{ + + /// Cast to or from half-precision floating point number. + /// This casts between [half](\ref half_float::half) and any built-in arithmetic type. Floating point types are + /// converted via an explicit cast to/from `float` (using the rounding mode of the built-in single precision + /// implementation) and thus any possible warnings due to an otherwise implicit conversion to/from `float` will be + /// suppressed. Integer types are converted directly using the given rounding mode, without any roundtrip over `float` + /// that a `static_cast` would otherwise do. It uses the default rounding mode. + /// + /// Using this cast with neither of the two types being a [half](\ref half_float::half) or with any of the two types + /// not being a built-in arithmetic type (apart from [half](\ref half_float::half), of course) results in a compiler + /// error and casting between [half](\ref half_float::half)s is just a no-op. + /// \tparam T destination type (half or built-in arithmetic type) + /// \tparam U source type (half or built-in arithmetic type) + /// \param arg value to cast + /// \return \a arg converted to destination type + template typename half_caster::type half_cast(U arg) { return half_caster::cast(arg); } + + /// Cast to or from half-precision floating point number. + /// This casts between [half](\ref half_float::half) and any built-in arithmetic type. Floating point types are + /// converted via an explicit cast to/from `float` (using the rounding mode of the built-in single precision + /// implementation) and thus any possible warnings due to an otherwise implicit conversion to/from `float` will be + /// suppressed. Integer types are converted directly using the given rounding mode, without any roundtrip over `float` + /// that a `static_cast` would otherwise do. + /// + /// Using this cast with neither of the two types being a [half](\ref half_float::half) or with any of the two types + /// not being a built-in arithmetic type (apart from [half](\ref half_float::half), of course) results in a compiler + /// error and casting between [half](\ref half_float::half)s is just a no-op. + /// \tparam T destination type (half or built-in arithmetic type) + /// \tparam R rounding mode to use. + /// \tparam U source type (half or built-in arithmetic type) + /// \param arg value to cast + /// \return \a arg converted to destination type + template typename half_caster::type half_cast(U arg) + { return half_caster::cast(arg); } + /// \} + } + + using detail::operator==; + using detail::operator!=; + using detail::operator<; + using detail::operator>; + using detail::operator<=; + using detail::operator>=; + using detail::operator+; + using detail::operator-; + using detail::operator*; + using detail::operator/; + using detail::operator<<; + using detail::operator>>; + + using detail::abs; + using detail::fabs; + using detail::fmod; + using detail::remainder; + using detail::remquo; + using detail::fma; + using detail::fmax; + using detail::fmin; + using detail::fdim; + using detail::nanh; + using detail::exp; + using detail::expm1; + using detail::exp2; + using detail::log; + using detail::log10; + using detail::log1p; + using detail::log2; + using detail::sqrt; + using detail::cbrt; + using detail::hypot; + using detail::pow; + using detail::sin; + using detail::cos; + using detail::tan; + using detail::asin; + using detail::acos; + using detail::atan; + using detail::atan2; + using detail::sinh; + using detail::cosh; + using detail::tanh; + using detail::asinh; + using detail::acosh; + using detail::atanh; + using detail::erf; + using detail::erfc; + using detail::lgamma; + using detail::tgamma; + using detail::ceil; + using detail::floor; + using detail::trunc; + using detail::round; + using detail::lround; + using detail::nearbyint; + using detail::rint; + using detail::lrint; +#if HALF_ENABLE_CPP11_LONG_LONG + using detail::llround; + using detail::llrint; +#endif + using detail::frexp; + using detail::ldexp; + using detail::modf; + using detail::scalbn; + using detail::scalbln; + using detail::ilogb; + using detail::logb; + using detail::nextafter; + using detail::nexttoward; + using detail::copysign; + using detail::fpclassify; + using detail::isfinite; + using detail::isinf; + using detail::isnan; + using detail::isnormal; + using detail::signbit; + using detail::isgreater; + using detail::isgreaterequal; + using detail::isless; + using detail::islessequal; + using detail::islessgreater; + using detail::isunordered; + + using detail::half_cast; +} + + +/// Extensions to the C++ standard library. +namespace std +{ + /// Numeric limits for half-precision floats. + /// Because of the underlying single-precision implementation of many operations, it inherits some properties from + /// `std::numeric_limits`. + template<> class numeric_limits : public numeric_limits + { + public: + /// Supports signed values. + static HALF_CONSTEXPR_CONST bool is_signed = true; + + /// Is not exact. + static HALF_CONSTEXPR_CONST bool is_exact = false; + + /// Doesn't provide modulo arithmetic. + static HALF_CONSTEXPR_CONST bool is_modulo = false; + + /// IEEE conformant. + static HALF_CONSTEXPR_CONST bool is_iec559 = true; + + /// Supports infinity. + static HALF_CONSTEXPR_CONST bool has_infinity = true; + + /// Supports quiet NaNs. + static HALF_CONSTEXPR_CONST bool has_quiet_NaN = true; + + /// Supports subnormal values. + static HALF_CONSTEXPR_CONST float_denorm_style has_denorm = denorm_present; + + /// Rounding mode. + /// Due to the mix of internal single-precision computations (using the rounding mode of the underlying + /// single-precision implementation) with explicit truncation of the single-to-half conversions, the actual rounding + /// mode is indeterminate. + static HALF_CONSTEXPR_CONST float_round_style round_style = (std::numeric_limits::round_style== + half_float::half::round_style) ? half_float::half::round_style : round_indeterminate; + + /// Significant digits. + static HALF_CONSTEXPR_CONST int digits = 11; + + /// Significant decimal digits. + static HALF_CONSTEXPR_CONST int digits10 = 3; + + /// Required decimal digits to represent all possible values. + static HALF_CONSTEXPR_CONST int max_digits10 = 5; + + /// Number base. + static HALF_CONSTEXPR_CONST int radix = 2; + + /// One more than smallest exponent. + static HALF_CONSTEXPR_CONST int min_exponent = -13; + + /// Smallest normalized representable power of 10. + static HALF_CONSTEXPR_CONST int min_exponent10 = -4; + + /// One more than largest exponent + static HALF_CONSTEXPR_CONST int max_exponent = 16; + + /// Largest finitely representable power of 10. + static HALF_CONSTEXPR_CONST int max_exponent10 = 4; + + /// Smallest positive normal value. + static HALF_CONSTEXPR half_float::half min() HALF_NOTHROW { return half_float::half(half_float::detail::binary, 0x0400); } + + /// Smallest finite value. + static HALF_CONSTEXPR half_float::half lowest() HALF_NOTHROW { return half_float::half(half_float::detail::binary, 0xFBFF); } + + /// Largest finite value. + static HALF_CONSTEXPR half_float::half max() HALF_NOTHROW { return half_float::half(half_float::detail::binary, 0x7BFF); } + + /// Difference between one and next representable value. + static HALF_CONSTEXPR half_float::half epsilon() HALF_NOTHROW { return half_float::half(half_float::detail::binary, 0x1400); } + + /// Maximum rounding error. + static HALF_CONSTEXPR half_float::half round_error() HALF_NOTHROW + { return half_float::half(half_float::detail::binary, (round_style==std::round_to_nearest) ? 0x3800 : 0x3C00); } + + /// Positive infinity. + static HALF_CONSTEXPR half_float::half infinity() HALF_NOTHROW { return half_float::half(half_float::detail::binary, 0x7C00); } + + /// Quiet NaN. + static HALF_CONSTEXPR half_float::half quiet_NaN() HALF_NOTHROW { return half_float::half(half_float::detail::binary, 0x7FFF); } + + /// Signalling NaN. + static HALF_CONSTEXPR half_float::half signaling_NaN() HALF_NOTHROW { return half_float::half(half_float::detail::binary, 0x7DFF); } + + /// Smallest positive subnormal value. + static HALF_CONSTEXPR half_float::half denorm_min() HALF_NOTHROW { return half_float::half(half_float::detail::binary, 0x0001); } + }; + +#if HALF_ENABLE_CPP11_HASH + /// Hash function for half-precision floats. + /// This is only defined if C++11 `std::hash` is supported and enabled. + template<> struct hash //: unary_function + { + /// Type of function argument. + typedef half_float::half argument_type; + + /// Function return type. + typedef size_t result_type; + + /// Compute hash function. + /// \param arg half to hash + /// \return hash value + result_type operator()(argument_type arg) const + { return hash()(static_cast(arg.data_)&-(arg.data_!=0x8000)); } + }; +#endif +} + + +#undef HALF_CONSTEXPR +#undef HALF_CONSTEXPR_CONST +#undef HALF_NOEXCEPT +#undef HALF_NOTHROW +#ifdef HALF_POP_WARNINGS + #pragma warning(pop) + #undef HALF_POP_WARNINGS +#endif + +#endif diff --git a/libCompression/7zTypes.h b/libCompression/7zTypes.h new file mode 100644 index 0000000..778413e --- /dev/null +++ b/libCompression/7zTypes.h @@ -0,0 +1,256 @@ +/* 7zTypes.h -- Basic types +2013-11-12 : Igor Pavlov : Public domain */ + +#ifndef __7Z_TYPES_H +#define __7Z_TYPES_H + +#ifdef _WIN32 +/* #include */ +#endif + +#include + +#ifndef EXTERN_C_BEGIN +#ifdef __cplusplus +#define EXTERN_C_BEGIN extern "C" { +#define EXTERN_C_END } +#else +#define EXTERN_C_BEGIN +#define EXTERN_C_END +#endif +#endif + +EXTERN_C_BEGIN + +#define SZ_OK 0 + +#define SZ_ERROR_DATA 1 +#define SZ_ERROR_MEM 2 +#define SZ_ERROR_CRC 3 +#define SZ_ERROR_UNSUPPORTED 4 +#define SZ_ERROR_PARAM 5 +#define SZ_ERROR_INPUT_EOF 6 +#define SZ_ERROR_OUTPUT_EOF 7 +#define SZ_ERROR_READ 8 +#define SZ_ERROR_WRITE 9 +#define SZ_ERROR_PROGRESS 10 +#define SZ_ERROR_FAIL 11 +#define SZ_ERROR_THREAD 12 + +#define SZ_ERROR_ARCHIVE 16 +#define SZ_ERROR_NO_ARCHIVE 17 + +typedef int SRes; + +#ifdef _WIN32 +/* typedef DWORD WRes; */ +typedef unsigned WRes; +#else +typedef int WRes; +#endif + +#ifndef RINOK +#define RINOK(x) { int __result__ = (x); if (__result__ != 0) return __result__; } +#endif + +typedef unsigned char Byte; +typedef short Int16; +typedef unsigned short UInt16; + +#ifdef _LZMA_UINT32_IS_ULONG +typedef long Int32; +typedef unsigned long UInt32; +#else +typedef int Int32; +typedef unsigned int UInt32; +#endif + +#ifdef _SZ_NO_INT_64 + +/* define _SZ_NO_INT_64, if your compiler doesn't support 64-bit integers. + NOTES: Some code will work incorrectly in that case! */ + +typedef long Int64; +typedef unsigned long UInt64; + +#else + +#if defined(_MSC_VER) || defined(__BORLANDC__) +typedef __int64 Int64; +typedef unsigned __int64 UInt64; +#define UINT64_CONST(n) n +#else +typedef long long int Int64; +typedef unsigned long long int UInt64; +#define UINT64_CONST(n) n ## ULL +#endif + +#endif + +#ifdef _LZMA_NO_SYSTEM_SIZE_T +typedef UInt32 SizeT; +#else +typedef size_t SizeT; +#endif + +typedef int Bool; +#define True 1 +#define False 0 + + +#ifdef _WIN32 +#define MY_STD_CALL __stdcall +#else +#define MY_STD_CALL +#endif + +#ifdef _MSC_VER + +#if _MSC_VER >= 1300 +#define MY_NO_INLINE __declspec(noinline) +#else +#define MY_NO_INLINE +#endif + +#define MY_CDECL __cdecl +#define MY_FAST_CALL __fastcall + +#else + +#define MY_NO_INLINE +#define MY_CDECL +#define MY_FAST_CALL + +#endif + + +/* The following interfaces use first parameter as pointer to structure */ + +typedef struct +{ + Byte (*Read)(void *p); /* reads one byte, returns 0 in case of EOF or error */ +} IByteIn; + +typedef struct +{ + void (*Write)(void *p, Byte b); +} IByteOut; + +typedef struct +{ + SRes (*Read)(void *p, void *buf, size_t *size); + /* if (input(*size) != 0 && output(*size) == 0) means end_of_stream. + (output(*size) < input(*size)) is allowed */ +} ISeqInStream; + +/* it can return SZ_ERROR_INPUT_EOF */ +SRes SeqInStream_Read(ISeqInStream *stream, void *buf, size_t size); +SRes SeqInStream_Read2(ISeqInStream *stream, void *buf, size_t size, SRes errorType); +SRes SeqInStream_ReadByte(ISeqInStream *stream, Byte *buf); + +typedef struct +{ + size_t (*Write)(void *p, const void *buf, size_t size); + /* Returns: result - the number of actually written bytes. + (result < size) means error */ +} ISeqOutStream; + +typedef enum +{ + SZ_SEEK_SET = 0, + SZ_SEEK_CUR = 1, + SZ_SEEK_END = 2 +} ESzSeek; + +typedef struct +{ + SRes (*Read)(void *p, void *buf, size_t *size); /* same as ISeqInStream::Read */ + SRes (*Seek)(void *p, Int64 *pos, ESzSeek origin); +} ISeekInStream; + +typedef struct +{ + SRes (*Look)(void *p, const void **buf, size_t *size); + /* if (input(*size) != 0 && output(*size) == 0) means end_of_stream. + (output(*size) > input(*size)) is not allowed + (output(*size) < input(*size)) is allowed */ + SRes (*Skip)(void *p, size_t offset); + /* offset must be <= output(*size) of Look */ + + SRes (*Read)(void *p, void *buf, size_t *size); + /* reads directly (without buffer). It's same as ISeqInStream::Read */ + SRes (*Seek)(void *p, Int64 *pos, ESzSeek origin); +} ILookInStream; + +SRes LookInStream_LookRead(ILookInStream *stream, void *buf, size_t *size); +SRes LookInStream_SeekTo(ILookInStream *stream, UInt64 offset); + +/* reads via ILookInStream::Read */ +SRes LookInStream_Read2(ILookInStream *stream, void *buf, size_t size, SRes errorType); +SRes LookInStream_Read(ILookInStream *stream, void *buf, size_t size); + +#define LookToRead_BUF_SIZE (1 << 14) + +typedef struct +{ + ILookInStream s; + ISeekInStream *realStream; + size_t pos; + size_t size; + Byte buf[LookToRead_BUF_SIZE]; +} CLookToRead; + +void LookToRead_CreateVTable(CLookToRead *p, int lookahead); +void LookToRead_Init(CLookToRead *p); + +typedef struct +{ + ISeqInStream s; + ILookInStream *realStream; +} CSecToLook; + +void SecToLook_CreateVTable(CSecToLook *p); + +typedef struct +{ + ISeqInStream s; + ILookInStream *realStream; +} CSecToRead; + +void SecToRead_CreateVTable(CSecToRead *p); + +typedef struct +{ + SRes (*Progress)(void *p, UInt64 inSize, UInt64 outSize); + /* Returns: result. (result != SZ_OK) means break. + Value (UInt64)(Int64)-1 for size means unknown value. */ +} ICompressProgress; + +typedef struct +{ + void *(*Alloc)(void *p, size_t size); + void (*Free)(void *p, void *address); /* address can be 0 */ +} ISzAlloc; + +#define IAlloc_Alloc(p, size) (p)->Alloc((p), size) +#define IAlloc_Free(p, a) (p)->Free((p), a) + +#ifdef _WIN32 + +#define CHAR_PATH_SEPARATOR '\\' +#define WCHAR_PATH_SEPARATOR L'\\' +#define STRING_PATH_SEPARATOR "\\" +#define WSTRING_PATH_SEPARATOR L"\\" + +#else + +#define CHAR_PATH_SEPARATOR '/' +#define WCHAR_PATH_SEPARATOR L'/' +#define STRING_PATH_SEPARATOR "/" +#define WSTRING_PATH_SEPARATOR L"/" + +#endif + +EXTERN_C_END + +#endif diff --git a/libCompression/CMakeLists.txt b/libCompression/CMakeLists.txt new file mode 100644 index 0000000..df3a069 --- /dev/null +++ b/libCompression/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library (libCompression STATIC lz4.c lz4frame.c lz4hc.c xxhash.c lz4dec.cpp lz4enc.cpp LzFind.c LzFindMt.c LzmaDec.c LzmaEnc.c Threads.c) +target_include_directories (libCompression PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/libCompression/Compiler.h b/libCompression/Compiler.h new file mode 100644 index 0000000..5bba7ee --- /dev/null +++ b/libCompression/Compiler.h @@ -0,0 +1,32 @@ +/* Compiler.h +2015-08-02 : Igor Pavlov : Public domain */ + +#ifndef __7Z_COMPILER_H +#define __7Z_COMPILER_H + +#ifdef _MSC_VER + + #ifdef UNDER_CE + #define RPC_NO_WINDOWS_H + /* #pragma warning(disable : 4115) // '_RPC_ASYNC_STATE' : named type definition in parentheses */ + #pragma warning(disable : 4201) // nonstandard extension used : nameless struct/union + #pragma warning(disable : 4214) // nonstandard extension used : bit field types other than int + #endif + + #if _MSC_VER >= 1300 + #pragma warning(disable : 4996) // This function or variable may be unsafe + #else + #pragma warning(disable : 4511) // copy constructor could not be generated + #pragma warning(disable : 4512) // assignment operator could not be generated + #pragma warning(disable : 4514) // unreferenced inline function has been removed + #pragma warning(disable : 4702) // unreachable code + #pragma warning(disable : 4710) // not inlined + #pragma warning(disable : 4786) // identifier was truncated to '255' characters in the debug information + #endif + +#endif + +#define UNUSED_VAR(x) (void)x; +/* #define UNUSED_VAR(x) x=x; */ + +#endif diff --git a/libCompression/LzFind.c b/libCompression/LzFind.c new file mode 100644 index 0000000..2d05fa3 --- /dev/null +++ b/libCompression/LzFind.c @@ -0,0 +1,1044 @@ +/* LzFind.c -- Match finder for LZ algorithms +2015-10-15 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include + +#include "LzFind.h" +#include "LzHash.h" + +#define kEmptyHashValue 0 +#define kMaxValForNormalize ((UInt32)0xFFFFFFFF) +#define kNormalizeStepMin (1 << 10) /* it must be power of 2 */ +#define kNormalizeMask (~(UInt32)(kNormalizeStepMin - 1)) +#define kMaxHistorySize ((UInt32)7 << 29) + +#define kStartMaxLen 3 + +static void LzInWindow_Free(CMatchFinder *p, ISzAlloc *alloc) +{ + if (!p->directInput) + { + alloc->Free(alloc, p->bufferBase); + p->bufferBase = NULL; + } +} + +/* keepSizeBefore + keepSizeAfter + keepSizeReserv must be < 4G) */ + +static int LzInWindow_Create(CMatchFinder *p, UInt32 keepSizeReserv, ISzAlloc *alloc) +{ + UInt32 blockSize = p->keepSizeBefore + p->keepSizeAfter + keepSizeReserv; + if (p->directInput) + { + p->blockSize = blockSize; + return 1; + } + if (!p->bufferBase || p->blockSize != blockSize) + { + LzInWindow_Free(p, alloc); + p->blockSize = blockSize; + p->bufferBase = (Byte *)alloc->Alloc(alloc, (size_t)blockSize); + } + return (p->bufferBase != NULL); +} + +Byte *MatchFinder_GetPointerToCurrentPos(CMatchFinder *p) { return p->buffer; } + +UInt32 MatchFinder_GetNumAvailableBytes(CMatchFinder *p) { return p->streamPos - p->pos; } + +void MatchFinder_ReduceOffsets(CMatchFinder *p, UInt32 subValue) +{ + p->posLimit -= subValue; + p->pos -= subValue; + p->streamPos -= subValue; +} + +static void MatchFinder_ReadBlock(CMatchFinder *p) +{ + if (p->streamEndWasReached || p->result != SZ_OK) + return; + + /* We use (p->streamPos - p->pos) value. (p->streamPos < p->pos) is allowed. */ + + if (p->directInput) + { + UInt32 curSize = 0xFFFFFFFF - (p->streamPos - p->pos); + if (curSize > p->directInputRem) + curSize = (UInt32)p->directInputRem; + p->directInputRem -= curSize; + p->streamPos += curSize; + if (p->directInputRem == 0) + p->streamEndWasReached = 1; + return; + } + + for (;;) + { + Byte *dest = p->buffer + (p->streamPos - p->pos); + size_t size = (p->bufferBase + p->blockSize - dest); + if (size == 0) + return; + + p->result = p->stream->Read(p->stream, dest, &size); + if (p->result != SZ_OK) + return; + if (size == 0) + { + p->streamEndWasReached = 1; + return; + } + p->streamPos += (UInt32)size; + if (p->streamPos - p->pos > p->keepSizeAfter) + return; + } +} + +void MatchFinder_MoveBlock(CMatchFinder *p) +{ + memmove(p->bufferBase, + p->buffer - p->keepSizeBefore, + (size_t)(p->streamPos - p->pos) + p->keepSizeBefore); + p->buffer = p->bufferBase + p->keepSizeBefore; +} + +int MatchFinder_NeedMove(CMatchFinder *p) +{ + if (p->directInput) + return 0; + /* if (p->streamEndWasReached) return 0; */ + return ((size_t)(p->bufferBase + p->blockSize - p->buffer) <= p->keepSizeAfter); +} + +void MatchFinder_ReadIfRequired(CMatchFinder *p) +{ + if (p->streamEndWasReached) + return; + if (p->keepSizeAfter >= p->streamPos - p->pos) + MatchFinder_ReadBlock(p); +} + +static void MatchFinder_CheckAndMoveAndRead(CMatchFinder *p) +{ + if (MatchFinder_NeedMove(p)) + MatchFinder_MoveBlock(p); + MatchFinder_ReadBlock(p); +} + +static void MatchFinder_SetDefaultSettings(CMatchFinder *p) +{ + p->cutValue = 32; + p->btMode = 1; + p->numHashBytes = 4; + p->bigHash = 0; +} + +#define kCrcPoly 0xEDB88320 + +void MatchFinder_Construct(CMatchFinder *p) +{ + UInt32 i; + p->bufferBase = NULL; + p->directInput = 0; + p->hash = NULL; + MatchFinder_SetDefaultSettings(p); + + for (i = 0; i < 256; i++) + { + UInt32 r = i; + unsigned j; + for (j = 0; j < 8; j++) + r = (r >> 1) ^ (kCrcPoly & ~((r & 1) - 1)); + p->crc[i] = r; + } +} + +static void MatchFinder_FreeThisClassMemory(CMatchFinder *p, ISzAlloc *alloc) +{ + alloc->Free(alloc, p->hash); + p->hash = NULL; +} + +void MatchFinder_Free(CMatchFinder *p, ISzAlloc *alloc) +{ + MatchFinder_FreeThisClassMemory(p, alloc); + LzInWindow_Free(p, alloc); +} + +static CLzRef* AllocRefs(size_t num, ISzAlloc *alloc) +{ + size_t sizeInBytes = (size_t)num * sizeof(CLzRef); + if (sizeInBytes / sizeof(CLzRef) != num) + return NULL; + return (CLzRef *)alloc->Alloc(alloc, sizeInBytes); +} + +int MatchFinder_Create(CMatchFinder *p, UInt32 historySize, + UInt32 keepAddBufferBefore, UInt32 matchMaxLen, UInt32 keepAddBufferAfter, + ISzAlloc *alloc) +{ + UInt32 sizeReserv; + + if (historySize > kMaxHistorySize) + { + MatchFinder_Free(p, alloc); + return 0; + } + + sizeReserv = historySize >> 1; + if (historySize >= ((UInt32)3 << 30)) sizeReserv = historySize >> 3; + else if (historySize >= ((UInt32)2 << 30)) sizeReserv = historySize >> 2; + + sizeReserv += (keepAddBufferBefore + matchMaxLen + keepAddBufferAfter) / 2 + (1 << 19); + + p->keepSizeBefore = historySize + keepAddBufferBefore + 1; + p->keepSizeAfter = matchMaxLen + keepAddBufferAfter; + + /* we need one additional byte, since we use MoveBlock after pos++ and before dictionary using */ + + if (LzInWindow_Create(p, sizeReserv, alloc)) + { + UInt32 newCyclicBufferSize = historySize + 1; + UInt32 hs; + p->matchMaxLen = matchMaxLen; + { + p->fixedHashSize = 0; + if (p->numHashBytes == 2) + hs = (1 << 16) - 1; + else + { + hs = historySize - 1; + hs |= (hs >> 1); + hs |= (hs >> 2); + hs |= (hs >> 4); + hs |= (hs >> 8); + hs >>= 1; + hs |= 0xFFFF; /* don't change it! It's required for Deflate */ + if (hs > (1 << 24)) + { + if (p->numHashBytes == 3) + hs = (1 << 24) - 1; + else + hs >>= 1; + /* if (bigHash) mode, GetHeads4b() in LzFindMt.c needs (hs >= ((1 << 24) - 1))) */ + } + } + p->hashMask = hs; + hs++; + if (p->numHashBytes > 2) p->fixedHashSize += kHash2Size; + if (p->numHashBytes > 3) p->fixedHashSize += kHash3Size; + if (p->numHashBytes > 4) p->fixedHashSize += kHash4Size; + hs += p->fixedHashSize; + } + + { + size_t newSize; + size_t numSons; + p->historySize = historySize; + p->hashSizeSum = hs; + p->cyclicBufferSize = newCyclicBufferSize; + + numSons = newCyclicBufferSize; + if (p->btMode) + numSons <<= 1; + newSize = hs + numSons; + + if (p->hash && p->numRefs == newSize) + return 1; + + MatchFinder_FreeThisClassMemory(p, alloc); + p->numRefs = newSize; + p->hash = AllocRefs(newSize, alloc); + + if (p->hash) + { + p->son = p->hash + p->hashSizeSum; + return 1; + } + } + } + + MatchFinder_Free(p, alloc); + return 0; +} + +static void MatchFinder_SetLimits(CMatchFinder *p) +{ + UInt32 limit = kMaxValForNormalize - p->pos; + UInt32 limit2 = p->cyclicBufferSize - p->cyclicBufferPos; + + if (limit2 < limit) + limit = limit2; + limit2 = p->streamPos - p->pos; + + if (limit2 <= p->keepSizeAfter) + { + if (limit2 > 0) + limit2 = 1; + } + else + limit2 -= p->keepSizeAfter; + + if (limit2 < limit) + limit = limit2; + + { + UInt32 lenLimit = p->streamPos - p->pos; + if (lenLimit > p->matchMaxLen) + lenLimit = p->matchMaxLen; + p->lenLimit = lenLimit; + } + p->posLimit = p->pos + limit; +} + +void MatchFinder_Init_2(CMatchFinder *p, int readData) +{ + UInt32 i; + UInt32 *hash = p->hash; + UInt32 num = p->hashSizeSum; + for (i = 0; i < num; i++) + hash[i] = kEmptyHashValue; + + p->cyclicBufferPos = 0; + p->buffer = p->bufferBase; + p->pos = p->streamPos = p->cyclicBufferSize; + p->result = SZ_OK; + p->streamEndWasReached = 0; + + if (readData) + MatchFinder_ReadBlock(p); + + MatchFinder_SetLimits(p); +} + +void MatchFinder_Init(CMatchFinder *p) +{ + MatchFinder_Init_2(p, True); +} + +static UInt32 MatchFinder_GetSubValue(CMatchFinder *p) +{ + return (p->pos - p->historySize - 1) & kNormalizeMask; +} + +void MatchFinder_Normalize3(UInt32 subValue, CLzRef *items, size_t numItems) +{ + size_t i; + for (i = 0; i < numItems; i++) + { + UInt32 value = items[i]; + if (value <= subValue) + value = kEmptyHashValue; + else + value -= subValue; + items[i] = value; + } +} + +static void MatchFinder_Normalize(CMatchFinder *p) +{ + UInt32 subValue = MatchFinder_GetSubValue(p); + MatchFinder_Normalize3(subValue, p->hash, p->numRefs); + MatchFinder_ReduceOffsets(p, subValue); +} + +static void MatchFinder_CheckLimits(CMatchFinder *p) +{ + if (p->pos == kMaxValForNormalize) + MatchFinder_Normalize(p); + if (!p->streamEndWasReached && p->keepSizeAfter == p->streamPos - p->pos) + MatchFinder_CheckAndMoveAndRead(p); + if (p->cyclicBufferPos == p->cyclicBufferSize) + p->cyclicBufferPos = 0; + MatchFinder_SetLimits(p); +} + +static UInt32 * Hc_GetMatchesSpec(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byte *cur, CLzRef *son, + UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 cutValue, + UInt32 *distances, UInt32 maxLen) +{ + son[_cyclicBufferPos] = curMatch; + for (;;) + { + UInt32 delta = pos - curMatch; + if (cutValue-- == 0 || delta >= _cyclicBufferSize) + return distances; + { + const Byte *pb = cur - delta; + curMatch = son[_cyclicBufferPos - delta + ((delta > _cyclicBufferPos) ? _cyclicBufferSize : 0)]; + if (pb[maxLen] == cur[maxLen] && *pb == *cur) + { + UInt32 len = 0; + while (++len != lenLimit) + if (pb[len] != cur[len]) + break; + if (maxLen < len) + { + *distances++ = maxLen = len; + *distances++ = delta - 1; + if (len == lenLimit) + return distances; + } + } + } + } +} + +UInt32 * GetMatchesSpec1(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byte *cur, CLzRef *son, + UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 cutValue, + UInt32 *distances, UInt32 maxLen) +{ + CLzRef *ptr0 = son + (_cyclicBufferPos << 1) + 1; + CLzRef *ptr1 = son + (_cyclicBufferPos << 1); + UInt32 len0 = 0, len1 = 0; + for (;;) + { + UInt32 delta = pos - curMatch; + if (cutValue-- == 0 || delta >= _cyclicBufferSize) + { + *ptr0 = *ptr1 = kEmptyHashValue; + return distances; + } + { + CLzRef *pair = son + ((_cyclicBufferPos - delta + ((delta > _cyclicBufferPos) ? _cyclicBufferSize : 0)) << 1); + const Byte *pb = cur - delta; + UInt32 len = (len0 < len1 ? len0 : len1); + if (pb[len] == cur[len]) + { + if (++len != lenLimit && pb[len] == cur[len]) + while (++len != lenLimit) + if (pb[len] != cur[len]) + break; + if (maxLen < len) + { + *distances++ = maxLen = len; + *distances++ = delta - 1; + if (len == lenLimit) + { + *ptr1 = pair[0]; + *ptr0 = pair[1]; + return distances; + } + } + } + if (pb[len] < cur[len]) + { + *ptr1 = curMatch; + ptr1 = pair + 1; + curMatch = *ptr1; + len1 = len; + } + else + { + *ptr0 = curMatch; + ptr0 = pair; + curMatch = *ptr0; + len0 = len; + } + } + } +} + +static void SkipMatchesSpec(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byte *cur, CLzRef *son, + UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 cutValue) +{ + CLzRef *ptr0 = son + (_cyclicBufferPos << 1) + 1; + CLzRef *ptr1 = son + (_cyclicBufferPos << 1); + UInt32 len0 = 0, len1 = 0; + for (;;) + { + UInt32 delta = pos - curMatch; + if (cutValue-- == 0 || delta >= _cyclicBufferSize) + { + *ptr0 = *ptr1 = kEmptyHashValue; + return; + } + { + CLzRef *pair = son + ((_cyclicBufferPos - delta + ((delta > _cyclicBufferPos) ? _cyclicBufferSize : 0)) << 1); + const Byte *pb = cur - delta; + UInt32 len = (len0 < len1 ? len0 : len1); + if (pb[len] == cur[len]) + { + while (++len != lenLimit) + if (pb[len] != cur[len]) + break; + { + if (len == lenLimit) + { + *ptr1 = pair[0]; + *ptr0 = pair[1]; + return; + } + } + } + if (pb[len] < cur[len]) + { + *ptr1 = curMatch; + ptr1 = pair + 1; + curMatch = *ptr1; + len1 = len; + } + else + { + *ptr0 = curMatch; + ptr0 = pair; + curMatch = *ptr0; + len0 = len; + } + } + } +} + +#define MOVE_POS \ + ++p->cyclicBufferPos; \ + p->buffer++; \ + if (++p->pos == p->posLimit) MatchFinder_CheckLimits(p); + +#define MOVE_POS_RET MOVE_POS return offset; + +static void MatchFinder_MovePos(CMatchFinder *p) { MOVE_POS; } + +#define GET_MATCHES_HEADER2(minLen, ret_op) \ + UInt32 lenLimit; UInt32 hv; const Byte *cur; UInt32 curMatch; \ + lenLimit = p->lenLimit; { if (lenLimit < minLen) { MatchFinder_MovePos(p); ret_op; }} \ + cur = p->buffer; + +#define GET_MATCHES_HEADER(minLen) GET_MATCHES_HEADER2(minLen, return 0) +#define SKIP_HEADER(minLen) GET_MATCHES_HEADER2(minLen, continue) + +#define MF_PARAMS(p) p->pos, p->buffer, p->son, p->cyclicBufferPos, p->cyclicBufferSize, p->cutValue + +#define GET_MATCHES_FOOTER(offset, maxLen) \ + offset = (UInt32)(GetMatchesSpec1(lenLimit, curMatch, MF_PARAMS(p), \ + distances + offset, maxLen) - distances); MOVE_POS_RET; + +#define SKIP_FOOTER \ + SkipMatchesSpec(lenLimit, curMatch, MF_PARAMS(p)); MOVE_POS; + +#define UPDATE_maxLen { \ + ptrdiff_t diff = (ptrdiff_t)0 - d2; \ + const Byte *c = cur + maxLen; \ + const Byte *lim = cur + lenLimit; \ + for (; c != lim; c++) if (*(c + diff) != *c) break; \ + maxLen = (UInt32)(c - cur); } + +static UInt32 Bt2_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) +{ + UInt32 offset; + GET_MATCHES_HEADER(2) + HASH2_CALC; + curMatch = p->hash[hv]; + p->hash[hv] = p->pos; + offset = 0; + GET_MATCHES_FOOTER(offset, 1) +} + +UInt32 Bt3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) +{ + UInt32 offset; + GET_MATCHES_HEADER(3) + HASH_ZIP_CALC; + curMatch = p->hash[hv]; + p->hash[hv] = p->pos; + offset = 0; + GET_MATCHES_FOOTER(offset, 2) +} + +static UInt32 Bt3_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) +{ + UInt32 h2, d2, maxLen, offset, pos; + UInt32 *hash; + GET_MATCHES_HEADER(3) + + HASH3_CALC; + + hash = p->hash; + pos = p->pos; + + d2 = pos - hash[h2]; + + curMatch = hash[kFix3HashSize + hv]; + + hash[h2] = pos; + hash[kFix3HashSize + hv] = pos; + + maxLen = 2; + offset = 0; + + if (d2 < p->cyclicBufferSize && *(cur - d2) == *cur) + { + UPDATE_maxLen + distances[0] = maxLen; + distances[1] = d2 - 1; + offset = 2; + if (maxLen == lenLimit) + { + SkipMatchesSpec(lenLimit, curMatch, MF_PARAMS(p)); + MOVE_POS_RET; + } + } + + GET_MATCHES_FOOTER(offset, maxLen) +} + +static UInt32 Bt4_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) +{ + UInt32 h2, h3, d2, d3, maxLen, offset, pos; + UInt32 *hash; + GET_MATCHES_HEADER(4) + + HASH4_CALC; + + hash = p->hash; + pos = p->pos; + + d2 = pos - hash[ h2]; + d3 = pos - hash[kFix3HashSize + h3]; + + curMatch = hash[kFix4HashSize + hv]; + + hash[ h2] = pos; + hash[kFix3HashSize + h3] = pos; + hash[kFix4HashSize + hv] = pos; + + maxLen = 0; + offset = 0; + + if (d2 < p->cyclicBufferSize && *(cur - d2) == *cur) + { + distances[0] = maxLen = 2; + distances[1] = d2 - 1; + offset = 2; + } + + if (d2 != d3 && d3 < p->cyclicBufferSize && *(cur - d3) == *cur) + { + maxLen = 3; + distances[offset + 1] = d3 - 1; + offset += 2; + d2 = d3; + } + + if (offset != 0) + { + UPDATE_maxLen + distances[offset - 2] = maxLen; + if (maxLen == lenLimit) + { + SkipMatchesSpec(lenLimit, curMatch, MF_PARAMS(p)); + MOVE_POS_RET; + } + } + + if (maxLen < 3) + maxLen = 3; + + GET_MATCHES_FOOTER(offset, maxLen) +} + +/* +static UInt32 Bt5_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) +{ + UInt32 h2, h3, h4, d2, d3, d4, maxLen, offset, pos; + UInt32 *hash; + GET_MATCHES_HEADER(5) + + HASH5_CALC; + + hash = p->hash; + pos = p->pos; + + d2 = pos - hash[ h2]; + d3 = pos - hash[kFix3HashSize + h3]; + d4 = pos - hash[kFix4HashSize + h4]; + + curMatch = hash[kFix5HashSize + hv]; + + hash[ h2] = pos; + hash[kFix3HashSize + h3] = pos; + hash[kFix4HashSize + h4] = pos; + hash[kFix5HashSize + hv] = pos; + + maxLen = 0; + offset = 0; + + if (d2 < p->cyclicBufferSize && *(cur - d2) == *cur) + { + distances[0] = maxLen = 2; + distances[1] = d2 - 1; + offset = 2; + if (*(cur - d2 + 2) == cur[2]) + distances[0] = maxLen = 3; + else if (d3 < p->cyclicBufferSize && *(cur - d3) == *cur) + { + distances[2] = maxLen = 3; + distances[3] = d3 - 1; + offset = 4; + d2 = d3; + } + } + else if (d3 < p->cyclicBufferSize && *(cur - d3) == *cur) + { + distances[0] = maxLen = 3; + distances[1] = d3 - 1; + offset = 2; + d2 = d3; + } + + if (d2 != d4 && d4 < p->cyclicBufferSize + && *(cur - d4) == *cur + && *(cur - d4 + 3) == *(cur + 3)) + { + maxLen = 4; + distances[offset + 1] = d4 - 1; + offset += 2; + d2 = d4; + } + + if (offset != 0) + { + UPDATE_maxLen + distances[offset - 2] = maxLen; + if (maxLen == lenLimit) + { + SkipMatchesSpec(lenLimit, curMatch, MF_PARAMS(p)); + MOVE_POS_RET; + } + } + + if (maxLen < 4) + maxLen = 4; + + GET_MATCHES_FOOTER(offset, maxLen) +} +*/ + +static UInt32 Hc4_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) +{ + UInt32 h2, h3, d2, d3, maxLen, offset, pos; + UInt32 *hash; + GET_MATCHES_HEADER(4) + + HASH4_CALC; + + hash = p->hash; + pos = p->pos; + + d2 = pos - hash[ h2]; + d3 = pos - hash[kFix3HashSize + h3]; + + curMatch = hash[kFix4HashSize + hv]; + + hash[ h2] = pos; + hash[kFix3HashSize + h3] = pos; + hash[kFix4HashSize + hv] = pos; + + maxLen = 0; + offset = 0; + + if (d2 < p->cyclicBufferSize && *(cur - d2) == *cur) + { + distances[0] = maxLen = 2; + distances[1] = d2 - 1; + offset = 2; + } + + if (d2 != d3 && d3 < p->cyclicBufferSize && *(cur - d3) == *cur) + { + maxLen = 3; + distances[offset + 1] = d3 - 1; + offset += 2; + d2 = d3; + } + + if (offset != 0) + { + UPDATE_maxLen + distances[offset - 2] = maxLen; + if (maxLen == lenLimit) + { + p->son[p->cyclicBufferPos] = curMatch; + MOVE_POS_RET; + } + } + + if (maxLen < 3) + maxLen = 3; + + offset = (UInt32)(Hc_GetMatchesSpec(lenLimit, curMatch, MF_PARAMS(p), + distances + offset, maxLen) - (distances)); + MOVE_POS_RET +} + +/* +static UInt32 Hc5_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) +{ + UInt32 h2, h3, h4, d2, d3, d4, maxLen, offset, pos + UInt32 *hash; + GET_MATCHES_HEADER(5) + + HASH5_CALC; + + hash = p->hash; + pos = p->pos; + + d2 = pos - hash[ h2]; + d3 = pos - hash[kFix3HashSize + h3]; + d4 = pos - hash[kFix4HashSize + h4]; + + curMatch = hash[kFix5HashSize + hv]; + + hash[ h2] = pos; + hash[kFix3HashSize + h3] = pos; + hash[kFix4HashSize + h4] = pos; + hash[kFix5HashSize + hv] = pos; + + maxLen = 0; + offset = 0; + + if (d2 < p->cyclicBufferSize && *(cur - d2) == *cur) + { + distances[0] = maxLen = 2; + distances[1] = d2 - 1; + offset = 2; + if (*(cur - d2 + 2) == cur[2]) + distances[0] = maxLen = 3; + else if (d3 < p->cyclicBufferSize && *(cur - d3) == *cur) + { + distances[2] = maxLen = 3; + distances[3] = d3 - 1; + offset = 4; + d2 = d3; + } + } + else if (d3 < p->cyclicBufferSize && *(cur - d3) == *cur) + { + distances[0] = maxLen = 3; + distances[1] = d3 - 1; + offset = 2; + d2 = d3; + } + + if (d2 != d4 && d4 < p->cyclicBufferSize + && *(cur - d4) == *cur + && *(cur - d4 + 3) == *(cur + 3)) + { + maxLen = 4; + distances[offset + 1] = d4 - 1; + offset += 2; + d2 = d4; + } + + if (offset != 0) + { + UPDATE_maxLen + distances[offset - 2] = maxLen; + if (maxLen == lenLimit) + { + p->son[p->cyclicBufferPos] = curMatch; + MOVE_POS_RET; + } + } + + if (maxLen < 4) + maxLen = 4; + + offset = (UInt32)(Hc_GetMatchesSpec(lenLimit, curMatch, MF_PARAMS(p), + distances + offset, maxLen) - (distances)); + MOVE_POS_RET +} +*/ + +UInt32 Hc3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) +{ + UInt32 offset; + GET_MATCHES_HEADER(3) + HASH_ZIP_CALC; + curMatch = p->hash[hv]; + p->hash[hv] = p->pos; + offset = (UInt32)(Hc_GetMatchesSpec(lenLimit, curMatch, MF_PARAMS(p), + distances, 2) - (distances)); + MOVE_POS_RET +} + +static void Bt2_MatchFinder_Skip(CMatchFinder *p, UInt32 num) +{ + do + { + SKIP_HEADER(2) + HASH2_CALC; + curMatch = p->hash[hv]; + p->hash[hv] = p->pos; + SKIP_FOOTER + } + while (--num != 0); +} + +void Bt3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num) +{ + do + { + SKIP_HEADER(3) + HASH_ZIP_CALC; + curMatch = p->hash[hv]; + p->hash[hv] = p->pos; + SKIP_FOOTER + } + while (--num != 0); +} + +static void Bt3_MatchFinder_Skip(CMatchFinder *p, UInt32 num) +{ + do + { + UInt32 h2; + UInt32 *hash; + SKIP_HEADER(3) + HASH3_CALC; + hash = p->hash; + curMatch = hash[kFix3HashSize + hv]; + hash[h2] = + hash[kFix3HashSize + hv] = p->pos; + SKIP_FOOTER + } + while (--num != 0); +} + +static void Bt4_MatchFinder_Skip(CMatchFinder *p, UInt32 num) +{ + do + { + UInt32 h2, h3; + UInt32 *hash; + SKIP_HEADER(4) + HASH4_CALC; + hash = p->hash; + curMatch = hash[kFix4HashSize + hv]; + hash[ h2] = + hash[kFix3HashSize + h3] = + hash[kFix4HashSize + hv] = p->pos; + SKIP_FOOTER + } + while (--num != 0); +} + +/* +static void Bt5_MatchFinder_Skip(CMatchFinder *p, UInt32 num) +{ + do + { + UInt32 h2, h3, h4; + UInt32 *hash; + SKIP_HEADER(5) + HASH5_CALC; + hash = p->hash; + curMatch = hash[kFix5HashSize + hv]; + hash[ h2] = + hash[kFix3HashSize + h3] = + hash[kFix4HashSize + h4] = + hash[kFix5HashSize + hv] = p->pos; + SKIP_FOOTER + } + while (--num != 0); +} +*/ + +static void Hc4_MatchFinder_Skip(CMatchFinder *p, UInt32 num) +{ + do + { + UInt32 h2, h3; + UInt32 *hash; + SKIP_HEADER(4) + HASH4_CALC; + hash = p->hash; + curMatch = hash[kFix4HashSize + hv]; + hash[ h2] = + hash[kFix3HashSize + h3] = + hash[kFix4HashSize + hv] = p->pos; + p->son[p->cyclicBufferPos] = curMatch; + MOVE_POS + } + while (--num != 0); +} + +/* +static void Hc5_MatchFinder_Skip(CMatchFinder *p, UInt32 num) +{ + do + { + UInt32 h2, h3, h4; + UInt32 *hash; + SKIP_HEADER(5) + HASH5_CALC; + hash = p->hash; + curMatch = p->hash[kFix5HashSize + hv]; + hash[ h2] = + hash[kFix3HashSize + h3] = + hash[kFix4HashSize + h4] = + hash[kFix5HashSize + hv] = p->pos; + p->son[p->cyclicBufferPos] = curMatch; + MOVE_POS + } + while (--num != 0); +} +*/ + +void Hc3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num) +{ + do + { + SKIP_HEADER(3) + HASH_ZIP_CALC; + curMatch = p->hash[hv]; + p->hash[hv] = p->pos; + p->son[p->cyclicBufferPos] = curMatch; + MOVE_POS + } + while (--num != 0); +} + +void MatchFinder_CreateVTable(CMatchFinder *p, IMatchFinder *vTable) +{ + vTable->Init = (Mf_Init_Func)MatchFinder_Init; + vTable->GetNumAvailableBytes = (Mf_GetNumAvailableBytes_Func)MatchFinder_GetNumAvailableBytes; + vTable->GetPointerToCurrentPos = (Mf_GetPointerToCurrentPos_Func)MatchFinder_GetPointerToCurrentPos; + if (!p->btMode) + { + /* if (p->numHashBytes <= 4) */ + { + vTable->GetMatches = (Mf_GetMatches_Func)Hc4_MatchFinder_GetMatches; + vTable->Skip = (Mf_Skip_Func)Hc4_MatchFinder_Skip; + } + /* + else + { + vTable->GetMatches = (Mf_GetMatches_Func)Hc5_MatchFinder_GetMatches; + vTable->Skip = (Mf_Skip_Func)Hc5_MatchFinder_Skip; + } + */ + } + else if (p->numHashBytes == 2) + { + vTable->GetMatches = (Mf_GetMatches_Func)Bt2_MatchFinder_GetMatches; + vTable->Skip = (Mf_Skip_Func)Bt2_MatchFinder_Skip; + } + else if (p->numHashBytes == 3) + { + vTable->GetMatches = (Mf_GetMatches_Func)Bt3_MatchFinder_GetMatches; + vTable->Skip = (Mf_Skip_Func)Bt3_MatchFinder_Skip; + } + else /* if (p->numHashBytes == 4) */ + { + vTable->GetMatches = (Mf_GetMatches_Func)Bt4_MatchFinder_GetMatches; + vTable->Skip = (Mf_Skip_Func)Bt4_MatchFinder_Skip; + } + /* + else + { + vTable->GetMatches = (Mf_GetMatches_Func)Bt5_MatchFinder_GetMatches; + vTable->Skip = (Mf_Skip_Func)Bt5_MatchFinder_Skip; + } + */ +} diff --git a/libCompression/LzFind.h b/libCompression/LzFind.h new file mode 100644 index 0000000..d119944 --- /dev/null +++ b/libCompression/LzFind.h @@ -0,0 +1,117 @@ +/* LzFind.h -- Match finder for LZ algorithms +2015-10-15 : Igor Pavlov : Public domain */ + +#ifndef __LZ_FIND_H +#define __LZ_FIND_H + +#include "7zTypes.h" + +EXTERN_C_BEGIN + +typedef UInt32 CLzRef; + +typedef struct _CMatchFinder +{ + Byte *buffer; + UInt32 pos; + UInt32 posLimit; + UInt32 streamPos; + UInt32 lenLimit; + + UInt32 cyclicBufferPos; + UInt32 cyclicBufferSize; /* it must be = (historySize + 1) */ + + Byte streamEndWasReached; + Byte btMode; + Byte bigHash; + Byte directInput; + + UInt32 matchMaxLen; + CLzRef *hash; + CLzRef *son; + UInt32 hashMask; + UInt32 cutValue; + + Byte *bufferBase; + ISeqInStream *stream; + + UInt32 blockSize; + UInt32 keepSizeBefore; + UInt32 keepSizeAfter; + + UInt32 numHashBytes; + size_t directInputRem; + UInt32 historySize; + UInt32 fixedHashSize; + UInt32 hashSizeSum; + SRes result; + UInt32 crc[256]; + size_t numRefs; +} CMatchFinder; + +#define Inline_MatchFinder_GetPointerToCurrentPos(p) ((p)->buffer) + +#define Inline_MatchFinder_GetNumAvailableBytes(p) ((p)->streamPos - (p)->pos) + +#define Inline_MatchFinder_IsFinishedOK(p) \ + ((p)->streamEndWasReached \ + && (p)->streamPos == (p)->pos \ + && (!(p)->directInput || (p)->directInputRem == 0)) + +int MatchFinder_NeedMove(CMatchFinder *p); +Byte *MatchFinder_GetPointerToCurrentPos(CMatchFinder *p); +void MatchFinder_MoveBlock(CMatchFinder *p); +void MatchFinder_ReadIfRequired(CMatchFinder *p); + +void MatchFinder_Construct(CMatchFinder *p); + +/* Conditions: + historySize <= 3 GB + keepAddBufferBefore + matchMaxLen + keepAddBufferAfter < 511MB +*/ +int MatchFinder_Create(CMatchFinder *p, UInt32 historySize, + UInt32 keepAddBufferBefore, UInt32 matchMaxLen, UInt32 keepAddBufferAfter, + ISzAlloc *alloc); +void MatchFinder_Free(CMatchFinder *p, ISzAlloc *alloc); +void MatchFinder_Normalize3(UInt32 subValue, CLzRef *items, size_t numItems); +void MatchFinder_ReduceOffsets(CMatchFinder *p, UInt32 subValue); + +UInt32 * GetMatchesSpec1(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byte *buffer, CLzRef *son, + UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 _cutValue, + UInt32 *distances, UInt32 maxLen); + +/* +Conditions: + Mf_GetNumAvailableBytes_Func must be called before each Mf_GetMatchLen_Func. + Mf_GetPointerToCurrentPos_Func's result must be used only before any other function +*/ + +typedef void (*Mf_Init_Func)(void *object); +typedef UInt32 (*Mf_GetNumAvailableBytes_Func)(void *object); +typedef const Byte * (*Mf_GetPointerToCurrentPos_Func)(void *object); +typedef UInt32 (*Mf_GetMatches_Func)(void *object, UInt32 *distances); +typedef void (*Mf_Skip_Func)(void *object, UInt32); + +typedef struct _IMatchFinder +{ + Mf_Init_Func Init; + Mf_GetNumAvailableBytes_Func GetNumAvailableBytes; + Mf_GetPointerToCurrentPos_Func GetPointerToCurrentPos; + Mf_GetMatches_Func GetMatches; + Mf_Skip_Func Skip; +} IMatchFinder; + +void MatchFinder_CreateVTable(CMatchFinder *p, IMatchFinder *vTable); + +void MatchFinder_Init_2(CMatchFinder *p, int readData); +void MatchFinder_Init(CMatchFinder *p); + +UInt32 Bt3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances); +UInt32 Hc3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances); + +void Bt3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num); +void Hc3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num); + +EXTERN_C_END + +#endif diff --git a/libCompression/LzFindMt.c b/libCompression/LzFindMt.c new file mode 100644 index 0000000..a4ffa5e --- /dev/null +++ b/libCompression/LzFindMt.c @@ -0,0 +1,803 @@ +/* LzFindMt.c -- multithreaded Match finder for LZ algorithms +2015-10-15 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include "LzHash.h" + +#include "LzFindMt.h" + +static void MtSync_Construct(CMtSync *p) +{ + p->wasCreated = False; + p->csWasInitialized = False; + p->csWasEntered = False; + Thread_Construct(&p->thread); + Event_Construct(&p->canStart); + Event_Construct(&p->wasStarted); + Event_Construct(&p->wasStopped); + Semaphore_Construct(&p->freeSemaphore); + Semaphore_Construct(&p->filledSemaphore); +} + +static void MtSync_GetNextBlock(CMtSync *p) +{ + if (p->needStart) + { + p->numProcessedBlocks = 1; + p->needStart = False; + p->stopWriting = False; + p->exit = False; + Event_Reset(&p->wasStarted); + Event_Reset(&p->wasStopped); + + Event_Set(&p->canStart); + Event_Wait(&p->wasStarted); + } + else + { + CriticalSection_Leave(&p->cs); + p->csWasEntered = False; + p->numProcessedBlocks++; + Semaphore_Release1(&p->freeSemaphore); + } + Semaphore_Wait(&p->filledSemaphore); + CriticalSection_Enter(&p->cs); + p->csWasEntered = True; +} + +/* MtSync_StopWriting must be called if Writing was started */ + +static void MtSync_StopWriting(CMtSync *p) +{ + UInt32 myNumBlocks = p->numProcessedBlocks; + if (!Thread_WasCreated(&p->thread) || p->needStart) + return; + p->stopWriting = True; + if (p->csWasEntered) + { + CriticalSection_Leave(&p->cs); + p->csWasEntered = False; + } + Semaphore_Release1(&p->freeSemaphore); + + Event_Wait(&p->wasStopped); + + while (myNumBlocks++ != p->numProcessedBlocks) + { + Semaphore_Wait(&p->filledSemaphore); + Semaphore_Release1(&p->freeSemaphore); + } + p->needStart = True; +} + +static void MtSync_Destruct(CMtSync *p) +{ + if (Thread_WasCreated(&p->thread)) + { + MtSync_StopWriting(p); + p->exit = True; + if (p->needStart) + Event_Set(&p->canStart); + Thread_Wait(&p->thread); + Thread_Close(&p->thread); + } + if (p->csWasInitialized) + { + CriticalSection_Delete(&p->cs); + p->csWasInitialized = False; + } + + Event_Close(&p->canStart); + Event_Close(&p->wasStarted); + Event_Close(&p->wasStopped); + Semaphore_Close(&p->freeSemaphore); + Semaphore_Close(&p->filledSemaphore); + + p->wasCreated = False; +} + +#define RINOK_THREAD(x) { if ((x) != 0) return SZ_ERROR_THREAD; } + +static SRes MtSync_Create2(CMtSync *p, THREAD_FUNC_TYPE startAddress, void *obj, UInt32 numBlocks) +{ + if (p->wasCreated) + return SZ_OK; + + RINOK_THREAD(CriticalSection_Init(&p->cs)); + p->csWasInitialized = True; + + RINOK_THREAD(AutoResetEvent_CreateNotSignaled(&p->canStart)); + RINOK_THREAD(AutoResetEvent_CreateNotSignaled(&p->wasStarted)); + RINOK_THREAD(AutoResetEvent_CreateNotSignaled(&p->wasStopped)); + + RINOK_THREAD(Semaphore_Create(&p->freeSemaphore, numBlocks, numBlocks)); + RINOK_THREAD(Semaphore_Create(&p->filledSemaphore, 0, numBlocks)); + + p->needStart = True; + + RINOK_THREAD(Thread_Create(&p->thread, startAddress, obj)); + p->wasCreated = True; + return SZ_OK; +} + +static SRes MtSync_Create(CMtSync *p, THREAD_FUNC_TYPE startAddress, void *obj, UInt32 numBlocks) +{ + SRes res = MtSync_Create2(p, startAddress, obj, numBlocks); + if (res != SZ_OK) + MtSync_Destruct(p); + return res; +} + +void MtSync_Init(CMtSync *p) { p->needStart = True; } + +#define kMtMaxValForNormalize 0xFFFFFFFF + +#define DEF_GetHeads2(name, v, action) \ + static void GetHeads ## name(const Byte *p, UInt32 pos, \ + UInt32 *hash, UInt32 hashMask, UInt32 *heads, UInt32 numHeads, const UInt32 *crc) \ + { action; for (; numHeads != 0; numHeads--) { \ + const UInt32 value = (v); p++; *heads++ = pos - hash[value]; hash[value] = pos++; } } + +#define DEF_GetHeads(name, v) DEF_GetHeads2(name, v, ;) + +DEF_GetHeads2(2, (p[0] | ((UInt32)p[1] << 8)), UNUSED_VAR(hashMask); UNUSED_VAR(crc); ) +DEF_GetHeads(3, (crc[p[0]] ^ p[1] ^ ((UInt32)p[2] << 8)) & hashMask) +DEF_GetHeads(4, (crc[p[0]] ^ p[1] ^ ((UInt32)p[2] << 8) ^ (crc[p[3]] << 5)) & hashMask) +DEF_GetHeads(4b, (crc[p[0]] ^ p[1] ^ ((UInt32)p[2] << 8) ^ ((UInt32)p[3] << 16)) & hashMask) +/* DEF_GetHeads(5, (crc[p[0]] ^ p[1] ^ ((UInt32)p[2] << 8) ^ (crc[p[3]] << 5) ^ (crc[p[4]] << 3)) & hashMask) */ + +static void HashThreadFunc(CMatchFinderMt *mt) +{ + CMtSync *p = &mt->hashSync; + for (;;) + { + UInt32 numProcessedBlocks = 0; + Event_Wait(&p->canStart); + Event_Set(&p->wasStarted); + for (;;) + { + if (p->exit) + return; + if (p->stopWriting) + { + p->numProcessedBlocks = numProcessedBlocks; + Event_Set(&p->wasStopped); + break; + } + + { + CMatchFinder *mf = mt->MatchFinder; + if (MatchFinder_NeedMove(mf)) + { + CriticalSection_Enter(&mt->btSync.cs); + CriticalSection_Enter(&mt->hashSync.cs); + { + const Byte *beforePtr = Inline_MatchFinder_GetPointerToCurrentPos(mf); + ptrdiff_t offset; + MatchFinder_MoveBlock(mf); + offset = beforePtr - Inline_MatchFinder_GetPointerToCurrentPos(mf); + mt->pointerToCurPos -= offset; + mt->buffer -= offset; + } + CriticalSection_Leave(&mt->btSync.cs); + CriticalSection_Leave(&mt->hashSync.cs); + continue; + } + + Semaphore_Wait(&p->freeSemaphore); + + MatchFinder_ReadIfRequired(mf); + if (mf->pos > (kMtMaxValForNormalize - kMtHashBlockSize)) + { + UInt32 subValue = (mf->pos - mf->historySize - 1); + MatchFinder_ReduceOffsets(mf, subValue); + MatchFinder_Normalize3(subValue, mf->hash + mf->fixedHashSize, (size_t)mf->hashMask + 1); + } + { + UInt32 *heads = mt->hashBuf + ((numProcessedBlocks++) & kMtHashNumBlocksMask) * kMtHashBlockSize; + UInt32 num = mf->streamPos - mf->pos; + heads[0] = 2; + heads[1] = num; + if (num >= mf->numHashBytes) + { + num = num - mf->numHashBytes + 1; + if (num > kMtHashBlockSize - 2) + num = kMtHashBlockSize - 2; + mt->GetHeadsFunc(mf->buffer, mf->pos, mf->hash + mf->fixedHashSize, mf->hashMask, heads + 2, num, mf->crc); + heads[0] += num; + } + mf->pos += num; + mf->buffer += num; + } + } + + Semaphore_Release1(&p->filledSemaphore); + } + } +} + +static void MatchFinderMt_GetNextBlock_Hash(CMatchFinderMt *p) +{ + MtSync_GetNextBlock(&p->hashSync); + p->hashBufPosLimit = p->hashBufPos = ((p->hashSync.numProcessedBlocks - 1) & kMtHashNumBlocksMask) * kMtHashBlockSize; + p->hashBufPosLimit += p->hashBuf[p->hashBufPos++]; + p->hashNumAvail = p->hashBuf[p->hashBufPos++]; +} + +#define kEmptyHashValue 0 + +/* #define MFMT_GM_INLINE */ + +#ifdef MFMT_GM_INLINE + +#define NO_INLINE MY_FAST_CALL + +static Int32 NO_INLINE GetMatchesSpecN(UInt32 lenLimit, UInt32 pos, const Byte *cur, CLzRef *son, + UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 _cutValue, + UInt32 *_distances, UInt32 _maxLen, const UInt32 *hash, Int32 limit, UInt32 size, UInt32 *posRes) +{ + do + { + UInt32 *distances = _distances + 1; + UInt32 curMatch = pos - *hash++; + + CLzRef *ptr0 = son + (_cyclicBufferPos << 1) + 1; + CLzRef *ptr1 = son + (_cyclicBufferPos << 1); + UInt32 len0 = 0, len1 = 0; + UInt32 cutValue = _cutValue; + UInt32 maxLen = _maxLen; + for (;;) + { + UInt32 delta = pos - curMatch; + if (cutValue-- == 0 || delta >= _cyclicBufferSize) + { + *ptr0 = *ptr1 = kEmptyHashValue; + break; + } + { + CLzRef *pair = son + ((_cyclicBufferPos - delta + ((delta > _cyclicBufferPos) ? _cyclicBufferSize : 0)) << 1); + const Byte *pb = cur - delta; + UInt32 len = (len0 < len1 ? len0 : len1); + if (pb[len] == cur[len]) + { + if (++len != lenLimit && pb[len] == cur[len]) + while (++len != lenLimit) + if (pb[len] != cur[len]) + break; + if (maxLen < len) + { + *distances++ = maxLen = len; + *distances++ = delta - 1; + if (len == lenLimit) + { + *ptr1 = pair[0]; + *ptr0 = pair[1]; + break; + } + } + } + if (pb[len] < cur[len]) + { + *ptr1 = curMatch; + ptr1 = pair + 1; + curMatch = *ptr1; + len1 = len; + } + else + { + *ptr0 = curMatch; + ptr0 = pair; + curMatch = *ptr0; + len0 = len; + } + } + } + pos++; + _cyclicBufferPos++; + cur++; + { + UInt32 num = (UInt32)(distances - _distances); + *_distances = num - 1; + _distances += num; + limit -= num; + } + } + while (limit > 0 && --size != 0); + *posRes = pos; + return limit; +} + +#endif + +static void BtGetMatches(CMatchFinderMt *p, UInt32 *distances) +{ + UInt32 numProcessed = 0; + UInt32 curPos = 2; + UInt32 limit = kMtBtBlockSize - (p->matchMaxLen * 2); + + distances[1] = p->hashNumAvail; + + while (curPos < limit) + { + if (p->hashBufPos == p->hashBufPosLimit) + { + MatchFinderMt_GetNextBlock_Hash(p); + distances[1] = numProcessed + p->hashNumAvail; + if (p->hashNumAvail >= p->numHashBytes) + continue; + distances[0] = curPos + p->hashNumAvail; + distances += curPos; + for (; p->hashNumAvail != 0; p->hashNumAvail--) + *distances++ = 0; + return; + } + { + UInt32 size = p->hashBufPosLimit - p->hashBufPos; + UInt32 lenLimit = p->matchMaxLen; + UInt32 pos = p->pos; + UInt32 cyclicBufferPos = p->cyclicBufferPos; + if (lenLimit >= p->hashNumAvail) + lenLimit = p->hashNumAvail; + { + UInt32 size2 = p->hashNumAvail - lenLimit + 1; + if (size2 < size) + size = size2; + size2 = p->cyclicBufferSize - cyclicBufferPos; + if (size2 < size) + size = size2; + } + + #ifndef MFMT_GM_INLINE + while (curPos < limit && size-- != 0) + { + UInt32 *startDistances = distances + curPos; + UInt32 num = (UInt32)(GetMatchesSpec1(lenLimit, pos - p->hashBuf[p->hashBufPos++], + pos, p->buffer, p->son, cyclicBufferPos, p->cyclicBufferSize, p->cutValue, + startDistances + 1, p->numHashBytes - 1) - startDistances); + *startDistances = num - 1; + curPos += num; + cyclicBufferPos++; + pos++; + p->buffer++; + } + #else + { + UInt32 posRes; + curPos = limit - GetMatchesSpecN(lenLimit, pos, p->buffer, p->son, cyclicBufferPos, p->cyclicBufferSize, p->cutValue, + distances + curPos, p->numHashBytes - 1, p->hashBuf + p->hashBufPos, (Int32)(limit - curPos), size, &posRes); + p->hashBufPos += posRes - pos; + cyclicBufferPos += posRes - pos; + p->buffer += posRes - pos; + pos = posRes; + } + #endif + + numProcessed += pos - p->pos; + p->hashNumAvail -= pos - p->pos; + p->pos = pos; + if (cyclicBufferPos == p->cyclicBufferSize) + cyclicBufferPos = 0; + p->cyclicBufferPos = cyclicBufferPos; + } + } + + distances[0] = curPos; +} + +static void BtFillBlock(CMatchFinderMt *p, UInt32 globalBlockIndex) +{ + CMtSync *sync = &p->hashSync; + if (!sync->needStart) + { + CriticalSection_Enter(&sync->cs); + sync->csWasEntered = True; + } + + BtGetMatches(p, p->btBuf + (globalBlockIndex & kMtBtNumBlocksMask) * kMtBtBlockSize); + + if (p->pos > kMtMaxValForNormalize - kMtBtBlockSize) + { + UInt32 subValue = p->pos - p->cyclicBufferSize; + MatchFinder_Normalize3(subValue, p->son, (size_t)p->cyclicBufferSize * 2); + p->pos -= subValue; + } + + if (!sync->needStart) + { + CriticalSection_Leave(&sync->cs); + sync->csWasEntered = False; + } +} + +void BtThreadFunc(CMatchFinderMt *mt) +{ + CMtSync *p = &mt->btSync; + for (;;) + { + UInt32 blockIndex = 0; + Event_Wait(&p->canStart); + Event_Set(&p->wasStarted); + for (;;) + { + if (p->exit) + return; + if (p->stopWriting) + { + p->numProcessedBlocks = blockIndex; + MtSync_StopWriting(&mt->hashSync); + Event_Set(&p->wasStopped); + break; + } + Semaphore_Wait(&p->freeSemaphore); + BtFillBlock(mt, blockIndex++); + Semaphore_Release1(&p->filledSemaphore); + } + } +} + +void MatchFinderMt_Construct(CMatchFinderMt *p) +{ + p->hashBuf = NULL; + MtSync_Construct(&p->hashSync); + MtSync_Construct(&p->btSync); +} + +static void MatchFinderMt_FreeMem(CMatchFinderMt *p, ISzAlloc *alloc) +{ + alloc->Free(alloc, p->hashBuf); + p->hashBuf = NULL; +} + +void MatchFinderMt_Destruct(CMatchFinderMt *p, ISzAlloc *alloc) +{ + MtSync_Destruct(&p->hashSync); + MtSync_Destruct(&p->btSync); + MatchFinderMt_FreeMem(p, alloc); +} + +#define kHashBufferSize (kMtHashBlockSize * kMtHashNumBlocks) +#define kBtBufferSize (kMtBtBlockSize * kMtBtNumBlocks) + +static THREAD_FUNC_RET_TYPE THREAD_FUNC_CALL_TYPE HashThreadFunc2(void *p) { HashThreadFunc((CMatchFinderMt *)p); return 0; } +static THREAD_FUNC_RET_TYPE THREAD_FUNC_CALL_TYPE BtThreadFunc2(void *p) +{ + Byte allocaDummy[0x180]; + unsigned i = 0; + for (i = 0; i < 16; i++) + allocaDummy[i] = (Byte)0; + if (allocaDummy[0] == 0) + BtThreadFunc((CMatchFinderMt *)p); + return 0; +} + +SRes MatchFinderMt_Create(CMatchFinderMt *p, UInt32 historySize, UInt32 keepAddBufferBefore, + UInt32 matchMaxLen, UInt32 keepAddBufferAfter, ISzAlloc *alloc) +{ + CMatchFinder *mf = p->MatchFinder; + p->historySize = historySize; + if (kMtBtBlockSize <= matchMaxLen * 4) + return SZ_ERROR_PARAM; + if (!p->hashBuf) + { + p->hashBuf = (UInt32 *)alloc->Alloc(alloc, (kHashBufferSize + kBtBufferSize) * sizeof(UInt32)); + if (!p->hashBuf) + return SZ_ERROR_MEM; + p->btBuf = p->hashBuf + kHashBufferSize; + } + keepAddBufferBefore += (kHashBufferSize + kBtBufferSize); + keepAddBufferAfter += kMtHashBlockSize; + if (!MatchFinder_Create(mf, historySize, keepAddBufferBefore, matchMaxLen, keepAddBufferAfter, alloc)) + return SZ_ERROR_MEM; + + RINOK(MtSync_Create(&p->hashSync, HashThreadFunc2, p, kMtHashNumBlocks)); + RINOK(MtSync_Create(&p->btSync, BtThreadFunc2, p, kMtBtNumBlocks)); + return SZ_OK; +} + +/* Call it after ReleaseStream / SetStream */ +void MatchFinderMt_Init(CMatchFinderMt *p) +{ + CMatchFinder *mf = p->MatchFinder; + p->btBufPos = p->btBufPosLimit = 0; + p->hashBufPos = p->hashBufPosLimit = 0; + + /* Init without data reading. We don't want to read data in this thread */ + MatchFinder_Init_2(mf, False); + + p->pointerToCurPos = Inline_MatchFinder_GetPointerToCurrentPos(mf); + p->btNumAvailBytes = 0; + p->lzPos = p->historySize + 1; + + p->hash = mf->hash; + p->fixedHashSize = mf->fixedHashSize; + p->crc = mf->crc; + + p->son = mf->son; + p->matchMaxLen = mf->matchMaxLen; + p->numHashBytes = mf->numHashBytes; + p->pos = mf->pos; + p->buffer = mf->buffer; + p->cyclicBufferPos = mf->cyclicBufferPos; + p->cyclicBufferSize = mf->cyclicBufferSize; + p->cutValue = mf->cutValue; +} + +/* ReleaseStream is required to finish multithreading */ +void MatchFinderMt_ReleaseStream(CMatchFinderMt *p) +{ + MtSync_StopWriting(&p->btSync); + /* p->MatchFinder->ReleaseStream(); */ +} + +static void MatchFinderMt_Normalize(CMatchFinderMt *p) +{ + MatchFinder_Normalize3(p->lzPos - p->historySize - 1, p->hash, p->fixedHashSize); + p->lzPos = p->historySize + 1; +} + +static void MatchFinderMt_GetNextBlock_Bt(CMatchFinderMt *p) +{ + UInt32 blockIndex; + MtSync_GetNextBlock(&p->btSync); + blockIndex = ((p->btSync.numProcessedBlocks - 1) & kMtBtNumBlocksMask); + p->btBufPosLimit = p->btBufPos = blockIndex * kMtBtBlockSize; + p->btBufPosLimit += p->btBuf[p->btBufPos++]; + p->btNumAvailBytes = p->btBuf[p->btBufPos++]; + if (p->lzPos >= kMtMaxValForNormalize - kMtBtBlockSize) + MatchFinderMt_Normalize(p); +} + +static const Byte * MatchFinderMt_GetPointerToCurrentPos(CMatchFinderMt *p) +{ + return p->pointerToCurPos; +} + +#define GET_NEXT_BLOCK_IF_REQUIRED if (p->btBufPos == p->btBufPosLimit) MatchFinderMt_GetNextBlock_Bt(p); + +static UInt32 MatchFinderMt_GetNumAvailableBytes(CMatchFinderMt *p) +{ + GET_NEXT_BLOCK_IF_REQUIRED; + return p->btNumAvailBytes; +} + +static UInt32 * MixMatches2(CMatchFinderMt *p, UInt32 matchMinPos, UInt32 *distances) +{ + UInt32 h2, curMatch2; + UInt32 *hash = p->hash; + const Byte *cur = p->pointerToCurPos; + UInt32 lzPos = p->lzPos; + MT_HASH2_CALC + + curMatch2 = hash[h2]; + hash[h2] = lzPos; + + if (curMatch2 >= matchMinPos) + if (cur[(ptrdiff_t)curMatch2 - lzPos] == cur[0]) + { + *distances++ = 2; + *distances++ = lzPos - curMatch2 - 1; + } + + return distances; +} + +static UInt32 * MixMatches3(CMatchFinderMt *p, UInt32 matchMinPos, UInt32 *distances) +{ + UInt32 h2, h3, curMatch2, curMatch3; + UInt32 *hash = p->hash; + const Byte *cur = p->pointerToCurPos; + UInt32 lzPos = p->lzPos; + MT_HASH3_CALC + + curMatch2 = hash[ h2]; + curMatch3 = hash[kFix3HashSize + h3]; + + hash[ h2] = lzPos; + hash[kFix3HashSize + h3] = lzPos; + + if (curMatch2 >= matchMinPos && cur[(ptrdiff_t)curMatch2 - lzPos] == cur[0]) + { + distances[1] = lzPos - curMatch2 - 1; + if (cur[(ptrdiff_t)curMatch2 - lzPos + 2] == cur[2]) + { + distances[0] = 3; + return distances + 2; + } + distances[0] = 2; + distances += 2; + } + + if (curMatch3 >= matchMinPos && cur[(ptrdiff_t)curMatch3 - lzPos] == cur[0]) + { + *distances++ = 3; + *distances++ = lzPos - curMatch3 - 1; + } + + return distances; +} + +/* +static UInt32 *MixMatches4(CMatchFinderMt *p, UInt32 matchMinPos, UInt32 *distances) +{ + UInt32 h2, h3, h4, curMatch2, curMatch3, curMatch4; + UInt32 *hash = p->hash; + const Byte *cur = p->pointerToCurPos; + UInt32 lzPos = p->lzPos; + MT_HASH4_CALC + + curMatch2 = hash[ h2]; + curMatch3 = hash[kFix3HashSize + h3]; + curMatch4 = hash[kFix4HashSize + h4]; + + hash[ h2] = lzPos; + hash[kFix3HashSize + h3] = lzPos; + hash[kFix4HashSize + h4] = lzPos; + + if (curMatch2 >= matchMinPos && cur[(ptrdiff_t)curMatch2 - lzPos] == cur[0]) + { + distances[1] = lzPos - curMatch2 - 1; + if (cur[(ptrdiff_t)curMatch2 - lzPos + 2] == cur[2]) + { + distances[0] = (cur[(ptrdiff_t)curMatch2 - lzPos + 3] == cur[3]) ? 4 : 3; + return distances + 2; + } + distances[0] = 2; + distances += 2; + } + + if (curMatch3 >= matchMinPos && cur[(ptrdiff_t)curMatch3 - lzPos] == cur[0]) + { + distances[1] = lzPos - curMatch3 - 1; + if (cur[(ptrdiff_t)curMatch3 - lzPos + 3] == cur[3]) + { + distances[0] = 4; + return distances + 2; + } + distances[0] = 3; + distances += 2; + } + + if (curMatch4 >= matchMinPos) + if ( + cur[(ptrdiff_t)curMatch4 - lzPos] == cur[0] && + cur[(ptrdiff_t)curMatch4 - lzPos + 3] == cur[3] + ) + { + *distances++ = 4; + *distances++ = lzPos - curMatch4 - 1; + } + + return distances; +} +*/ + +#define INCREASE_LZ_POS p->lzPos++; p->pointerToCurPos++; + +static UInt32 MatchFinderMt2_GetMatches(CMatchFinderMt *p, UInt32 *distances) +{ + const UInt32 *btBuf = p->btBuf + p->btBufPos; + UInt32 len = *btBuf++; + p->btBufPos += 1 + len; + p->btNumAvailBytes--; + { + UInt32 i; + for (i = 0; i < len; i += 2) + { + *distances++ = *btBuf++; + *distances++ = *btBuf++; + } + } + INCREASE_LZ_POS + return len; +} + +static UInt32 MatchFinderMt_GetMatches(CMatchFinderMt *p, UInt32 *distances) +{ + const UInt32 *btBuf = p->btBuf + p->btBufPos; + UInt32 len = *btBuf++; + p->btBufPos += 1 + len; + + if (len == 0) + { + /* change for bt5 ! */ + if (p->btNumAvailBytes-- >= 4) + len = (UInt32)(p->MixMatchesFunc(p, p->lzPos - p->historySize, distances) - (distances)); + } + else + { + /* Condition: there are matches in btBuf with length < p->numHashBytes */ + UInt32 *distances2; + p->btNumAvailBytes--; + distances2 = p->MixMatchesFunc(p, p->lzPos - btBuf[1], distances); + do + { + *distances2++ = *btBuf++; + *distances2++ = *btBuf++; + } + while ((len -= 2) != 0); + len = (UInt32)(distances2 - (distances)); + } + INCREASE_LZ_POS + return len; +} + +#define SKIP_HEADER2_MT do { GET_NEXT_BLOCK_IF_REQUIRED +#define SKIP_HEADER_MT(n) SKIP_HEADER2_MT if (p->btNumAvailBytes-- >= (n)) { const Byte *cur = p->pointerToCurPos; UInt32 *hash = p->hash; +#define SKIP_FOOTER_MT } INCREASE_LZ_POS p->btBufPos += p->btBuf[p->btBufPos] + 1; } while (--num != 0); + +static void MatchFinderMt0_Skip(CMatchFinderMt *p, UInt32 num) +{ + SKIP_HEADER2_MT { p->btNumAvailBytes--; + SKIP_FOOTER_MT +} + +static void MatchFinderMt2_Skip(CMatchFinderMt *p, UInt32 num) +{ + SKIP_HEADER_MT(2) + UInt32 h2; + MT_HASH2_CALC + hash[h2] = p->lzPos; + SKIP_FOOTER_MT +} + +static void MatchFinderMt3_Skip(CMatchFinderMt *p, UInt32 num) +{ + SKIP_HEADER_MT(3) + UInt32 h2, h3; + MT_HASH3_CALC + hash[kFix3HashSize + h3] = + hash[ h2] = + p->lzPos; + SKIP_FOOTER_MT +} + +/* +static void MatchFinderMt4_Skip(CMatchFinderMt *p, UInt32 num) +{ + SKIP_HEADER_MT(4) + UInt32 h2, h3, h4; + MT_HASH4_CALC + hash[kFix4HashSize + h4] = + hash[kFix3HashSize + h3] = + hash[ h2] = + p->lzPos; + SKIP_FOOTER_MT +} +*/ + +void MatchFinderMt_CreateVTable(CMatchFinderMt *p, IMatchFinder *vTable) +{ + vTable->Init = (Mf_Init_Func)MatchFinderMt_Init; + vTable->GetNumAvailableBytes = (Mf_GetNumAvailableBytes_Func)MatchFinderMt_GetNumAvailableBytes; + vTable->GetPointerToCurrentPos = (Mf_GetPointerToCurrentPos_Func)MatchFinderMt_GetPointerToCurrentPos; + vTable->GetMatches = (Mf_GetMatches_Func)MatchFinderMt_GetMatches; + + switch (p->MatchFinder->numHashBytes) + { + case 2: + p->GetHeadsFunc = GetHeads2; + p->MixMatchesFunc = (Mf_Mix_Matches)0; + vTable->Skip = (Mf_Skip_Func)MatchFinderMt0_Skip; + vTable->GetMatches = (Mf_GetMatches_Func)MatchFinderMt2_GetMatches; + break; + case 3: + p->GetHeadsFunc = GetHeads3; + p->MixMatchesFunc = (Mf_Mix_Matches)MixMatches2; + vTable->Skip = (Mf_Skip_Func)MatchFinderMt2_Skip; + break; + default: + /* case 4: */ + p->GetHeadsFunc = p->MatchFinder->bigHash ? GetHeads4b : GetHeads4; + p->MixMatchesFunc = (Mf_Mix_Matches)MixMatches3; + vTable->Skip = (Mf_Skip_Func)MatchFinderMt3_Skip; + break; + /* + default: + p->GetHeadsFunc = GetHeads5; + p->MixMatchesFunc = (Mf_Mix_Matches)MixMatches4; + vTable->Skip = (Mf_Skip_Func)MatchFinderMt4_Skip; + break; + */ + } +} diff --git a/libCompression/LzFindMt.h b/libCompression/LzFindMt.h new file mode 100644 index 0000000..89b91fe --- /dev/null +++ b/libCompression/LzFindMt.h @@ -0,0 +1,101 @@ +/* LzFindMt.h -- multithreaded Match finder for LZ algorithms +2015-05-03 : Igor Pavlov : Public domain */ + +#ifndef __LZ_FIND_MT_H +#define __LZ_FIND_MT_H + +#include "LzFind.h" +#include "Threads.h" + +EXTERN_C_BEGIN + +#define kMtHashBlockSize (1 << 13) +#define kMtHashNumBlocks (1 << 3) +#define kMtHashNumBlocksMask (kMtHashNumBlocks - 1) + +#define kMtBtBlockSize (1 << 14) +#define kMtBtNumBlocks (1 << 6) +#define kMtBtNumBlocksMask (kMtBtNumBlocks - 1) + +typedef struct _CMtSync +{ + Bool wasCreated; + Bool needStart; + Bool exit; + Bool stopWriting; + + CThread thread; + CAutoResetEvent canStart; + CAutoResetEvent wasStarted; + CAutoResetEvent wasStopped; + CSemaphore freeSemaphore; + CSemaphore filledSemaphore; + Bool csWasInitialized; + Bool csWasEntered; + CCriticalSection cs; + UInt32 numProcessedBlocks; +} CMtSync; + +typedef UInt32 * (*Mf_Mix_Matches)(void *p, UInt32 matchMinPos, UInt32 *distances); + +/* kMtCacheLineDummy must be >= size_of_CPU_cache_line */ +#define kMtCacheLineDummy 128 + +typedef void (*Mf_GetHeads)(const Byte *buffer, UInt32 pos, + UInt32 *hash, UInt32 hashMask, UInt32 *heads, UInt32 numHeads, const UInt32 *crc); + +typedef struct _CMatchFinderMt +{ + /* LZ */ + const Byte *pointerToCurPos; + UInt32 *btBuf; + UInt32 btBufPos; + UInt32 btBufPosLimit; + UInt32 lzPos; + UInt32 btNumAvailBytes; + + UInt32 *hash; + UInt32 fixedHashSize; + UInt32 historySize; + const UInt32 *crc; + + Mf_Mix_Matches MixMatchesFunc; + + /* LZ + BT */ + CMtSync btSync; + Byte btDummy[kMtCacheLineDummy]; + + /* BT */ + UInt32 *hashBuf; + UInt32 hashBufPos; + UInt32 hashBufPosLimit; + UInt32 hashNumAvail; + + CLzRef *son; + UInt32 matchMaxLen; + UInt32 numHashBytes; + UInt32 pos; + const Byte *buffer; + UInt32 cyclicBufferPos; + UInt32 cyclicBufferSize; /* it must be historySize + 1 */ + UInt32 cutValue; + + /* BT + Hash */ + CMtSync hashSync; + /* Byte hashDummy[kMtCacheLineDummy]; */ + + /* Hash */ + Mf_GetHeads GetHeadsFunc; + CMatchFinder *MatchFinder; +} CMatchFinderMt; + +void MatchFinderMt_Construct(CMatchFinderMt *p); +void MatchFinderMt_Destruct(CMatchFinderMt *p, ISzAlloc *alloc); +SRes MatchFinderMt_Create(CMatchFinderMt *p, UInt32 historySize, UInt32 keepAddBufferBefore, + UInt32 matchMaxLen, UInt32 keepAddBufferAfter, ISzAlloc *alloc); +void MatchFinderMt_CreateVTable(CMatchFinderMt *p, IMatchFinder *vTable); +void MatchFinderMt_ReleaseStream(CMatchFinderMt *p); + +EXTERN_C_END + +#endif diff --git a/libCompression/LzHash.h b/libCompression/LzHash.h new file mode 100644 index 0000000..e7c9423 --- /dev/null +++ b/libCompression/LzHash.h @@ -0,0 +1,57 @@ +/* LzHash.h -- HASH functions for LZ algorithms +2015-04-12 : Igor Pavlov : Public domain */ + +#ifndef __LZ_HASH_H +#define __LZ_HASH_H + +#define kHash2Size (1 << 10) +#define kHash3Size (1 << 16) +#define kHash4Size (1 << 20) + +#define kFix3HashSize (kHash2Size) +#define kFix4HashSize (kHash2Size + kHash3Size) +#define kFix5HashSize (kHash2Size + kHash3Size + kHash4Size) + +#define HASH2_CALC hv = cur[0] | ((UInt32)cur[1] << 8); + +#define HASH3_CALC { \ + UInt32 temp = p->crc[cur[0]] ^ cur[1]; \ + h2 = temp & (kHash2Size - 1); \ + hv = (temp ^ ((UInt32)cur[2] << 8)) & p->hashMask; } + +#define HASH4_CALC { \ + UInt32 temp = p->crc[cur[0]] ^ cur[1]; \ + h2 = temp & (kHash2Size - 1); \ + temp ^= ((UInt32)cur[2] << 8); \ + h3 = temp & (kHash3Size - 1); \ + hv = (temp ^ (p->crc[cur[3]] << 5)) & p->hashMask; } + +#define HASH5_CALC { \ + UInt32 temp = p->crc[cur[0]] ^ cur[1]; \ + h2 = temp & (kHash2Size - 1); \ + temp ^= ((UInt32)cur[2] << 8); \ + h3 = temp & (kHash3Size - 1); \ + temp ^= (p->crc[cur[3]] << 5); \ + h4 = temp & (kHash4Size - 1); \ + hv = (temp ^ (p->crc[cur[4]] << 3)) & p->hashMask; } + +/* #define HASH_ZIP_CALC hv = ((cur[0] | ((UInt32)cur[1] << 8)) ^ p->crc[cur[2]]) & 0xFFFF; */ +#define HASH_ZIP_CALC hv = ((cur[2] | ((UInt32)cur[0] << 8)) ^ p->crc[cur[1]]) & 0xFFFF; + + +#define MT_HASH2_CALC \ + h2 = (p->crc[cur[0]] ^ cur[1]) & (kHash2Size - 1); + +#define MT_HASH3_CALC { \ + UInt32 temp = p->crc[cur[0]] ^ cur[1]; \ + h2 = temp & (kHash2Size - 1); \ + h3 = (temp ^ ((UInt32)cur[2] << 8)) & (kHash3Size - 1); } + +#define MT_HASH4_CALC { \ + UInt32 temp = p->crc[cur[0]] ^ cur[1]; \ + h2 = temp & (kHash2Size - 1); \ + temp ^= ((UInt32)cur[2] << 8); \ + h3 = temp & (kHash3Size - 1); \ + h4 = (temp ^ (p->crc[cur[3]] << 5)) & (kHash4Size - 1); } + +#endif diff --git a/libCompression/LzmaDec.c b/libCompression/LzmaDec.c new file mode 100644 index 0000000..12dce11 --- /dev/null +++ b/libCompression/LzmaDec.c @@ -0,0 +1,1100 @@ +/* LzmaDec.c -- LZMA Decoder +2016-05-16 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include "LzmaDec.h" + +#include + +#define kNumTopBits 24 +#define kTopValue ((UInt32)1 << kNumTopBits) + +#define kNumBitModelTotalBits 11 +#define kBitModelTotal (1 << kNumBitModelTotalBits) +#define kNumMoveBits 5 + +#define RC_INIT_SIZE 5 + +#define NORMALIZE if (range < kTopValue) { range <<= 8; code = (code << 8) | (*buf++); } + +#define IF_BIT_0(p) ttt = *(p); NORMALIZE; bound = (range >> kNumBitModelTotalBits) * ttt; if (code < bound) +#define UPDATE_0(p) range = bound; *(p) = (CLzmaProb)(ttt + ((kBitModelTotal - ttt) >> kNumMoveBits)); +#define UPDATE_1(p) range -= bound; code -= bound; *(p) = (CLzmaProb)(ttt - (ttt >> kNumMoveBits)); +#define GET_BIT2(p, i, A0, A1) IF_BIT_0(p) \ + { UPDATE_0(p); i = (i + i); A0; } else \ + { UPDATE_1(p); i = (i + i) + 1; A1; } +#define GET_BIT(p, i) GET_BIT2(p, i, ; , ;) + +#define TREE_GET_BIT(probs, i) { GET_BIT((probs + i), i); } +#define TREE_DECODE(probs, limit, i) \ + { i = 1; do { TREE_GET_BIT(probs, i); } while (i < limit); i -= limit; } + +/* #define _LZMA_SIZE_OPT */ + +#ifdef _LZMA_SIZE_OPT +#define TREE_6_DECODE(probs, i) TREE_DECODE(probs, (1 << 6), i) +#else +#define TREE_6_DECODE(probs, i) \ + { i = 1; \ + TREE_GET_BIT(probs, i); \ + TREE_GET_BIT(probs, i); \ + TREE_GET_BIT(probs, i); \ + TREE_GET_BIT(probs, i); \ + TREE_GET_BIT(probs, i); \ + TREE_GET_BIT(probs, i); \ + i -= 0x40; } +#endif + +#define NORMAL_LITER_DEC GET_BIT(prob + symbol, symbol) +#define MATCHED_LITER_DEC \ + matchByte <<= 1; \ + bit = (matchByte & offs); \ + probLit = prob + offs + bit + symbol; \ + GET_BIT2(probLit, symbol, offs &= ~bit, offs &= bit) + +#define NORMALIZE_CHECK if (range < kTopValue) { if (buf >= bufLimit) return DUMMY_ERROR; range <<= 8; code = (code << 8) | (*buf++); } + +#define IF_BIT_0_CHECK(p) ttt = *(p); NORMALIZE_CHECK; bound = (range >> kNumBitModelTotalBits) * ttt; if (code < bound) +#define UPDATE_0_CHECK range = bound; +#define UPDATE_1_CHECK range -= bound; code -= bound; +#define GET_BIT2_CHECK(p, i, A0, A1) IF_BIT_0_CHECK(p) \ + { UPDATE_0_CHECK; i = (i + i); A0; } else \ + { UPDATE_1_CHECK; i = (i + i) + 1; A1; } +#define GET_BIT_CHECK(p, i) GET_BIT2_CHECK(p, i, ; , ;) +#define TREE_DECODE_CHECK(probs, limit, i) \ + { i = 1; do { GET_BIT_CHECK(probs + i, i) } while (i < limit); i -= limit; } + + +#define kNumPosBitsMax 4 +#define kNumPosStatesMax (1 << kNumPosBitsMax) + +#define kLenNumLowBits 3 +#define kLenNumLowSymbols (1 << kLenNumLowBits) +#define kLenNumMidBits 3 +#define kLenNumMidSymbols (1 << kLenNumMidBits) +#define kLenNumHighBits 8 +#define kLenNumHighSymbols (1 << kLenNumHighBits) + +#define LenChoice 0 +#define LenChoice2 (LenChoice + 1) +#define LenLow (LenChoice2 + 1) +#define LenMid (LenLow + (kNumPosStatesMax << kLenNumLowBits)) +#define LenHigh (LenMid + (kNumPosStatesMax << kLenNumMidBits)) +#define kNumLenProbs (LenHigh + kLenNumHighSymbols) + + +#define kNumStates 12 +#define kNumLitStates 7 + +#define kStartPosModelIndex 4 +#define kEndPosModelIndex 14 +#define kNumFullDistances (1 << (kEndPosModelIndex >> 1)) + +#define kNumPosSlotBits 6 +#define kNumLenToPosStates 4 + +#define kNumAlignBits 4 +#define kAlignTableSize (1 << kNumAlignBits) + +#define kMatchMinLen 2 +#define kMatchSpecLenStart (kMatchMinLen + kLenNumLowSymbols + kLenNumMidSymbols + kLenNumHighSymbols) + +#define IsMatch 0 +#define IsRep (IsMatch + (kNumStates << kNumPosBitsMax)) +#define IsRepG0 (IsRep + kNumStates) +#define IsRepG1 (IsRepG0 + kNumStates) +#define IsRepG2 (IsRepG1 + kNumStates) +#define IsRep0Long (IsRepG2 + kNumStates) +#define PosSlot (IsRep0Long + (kNumStates << kNumPosBitsMax)) +#define SpecPos (PosSlot + (kNumLenToPosStates << kNumPosSlotBits)) +#define Align (SpecPos + kNumFullDistances - kEndPosModelIndex) +#define LenCoder (Align + kAlignTableSize) +#define RepLenCoder (LenCoder + kNumLenProbs) +#define Literal (RepLenCoder + kNumLenProbs) + +#define LZMA_BASE_SIZE 1846 +#define LZMA_LIT_SIZE 0x300 + +#if Literal != LZMA_BASE_SIZE +StopCompilingDueBUG +#endif + +#define LzmaProps_GetNumProbs(p) (Literal + ((UInt32)LZMA_LIT_SIZE << ((p)->lc + (p)->lp))) + +#define LZMA_DIC_MIN (1 << 12) + +/* First LZMA-symbol is always decoded. +And it decodes new LZMA-symbols while (buf < bufLimit), but "buf" is without last normalization +Out: + Result: + SZ_OK - OK + SZ_ERROR_DATA - Error + p->remainLen: + < kMatchSpecLenStart : normal remain + = kMatchSpecLenStart : finished + = kMatchSpecLenStart + 1 : Flush marker (unused now) + = kMatchSpecLenStart + 2 : State Init Marker (unused now) +*/ + +static int MY_FAST_CALL LzmaDec_DecodeReal(CLzmaDec *p, SizeT limit, const Byte *bufLimit) +{ + CLzmaProb *probs = p->probs; + + unsigned state = p->state; + UInt32 rep0 = p->reps[0], rep1 = p->reps[1], rep2 = p->reps[2], rep3 = p->reps[3]; + unsigned pbMask = ((unsigned)1 << (p->prop.pb)) - 1; + unsigned lpMask = ((unsigned)1 << (p->prop.lp)) - 1; + unsigned lc = p->prop.lc; + + Byte *dic = p->dic; + SizeT dicBufSize = p->dicBufSize; + SizeT dicPos = p->dicPos; + + UInt32 processedPos = p->processedPos; + UInt32 checkDicSize = p->checkDicSize; + unsigned len = 0; + + const Byte *buf = p->buf; + UInt32 range = p->range; + UInt32 code = p->code; + + do + { + CLzmaProb *prob; + UInt32 bound; + unsigned ttt; + unsigned posState = processedPos & pbMask; + + prob = probs + IsMatch + (state << kNumPosBitsMax) + posState; + IF_BIT_0(prob) + { + unsigned symbol; + UPDATE_0(prob); + prob = probs + Literal; + if (processedPos != 0 || checkDicSize != 0) + prob += ((UInt32)LZMA_LIT_SIZE * (((processedPos & lpMask) << lc) + + (dic[(dicPos == 0 ? dicBufSize : dicPos) - 1] >> (8 - lc)))); + processedPos++; + + if (state < kNumLitStates) + { + state -= (state < 4) ? state : 3; + symbol = 1; + #ifdef _LZMA_SIZE_OPT + do { NORMAL_LITER_DEC } while (symbol < 0x100); + #else + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + #endif + } + else + { + unsigned matchByte = dic[dicPos - rep0 + (dicPos < rep0 ? dicBufSize : 0)]; + unsigned offs = 0x100; + state -= (state < 10) ? 3 : 6; + symbol = 1; + #ifdef _LZMA_SIZE_OPT + do + { + unsigned bit; + CLzmaProb *probLit; + MATCHED_LITER_DEC + } + while (symbol < 0x100); + #else + { + unsigned bit; + CLzmaProb *probLit; + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + } + #endif + } + + dic[dicPos++] = (Byte)symbol; + continue; + } + + { + UPDATE_1(prob); + prob = probs + IsRep + state; + IF_BIT_0(prob) + { + UPDATE_0(prob); + state += kNumStates; + prob = probs + LenCoder; + } + else + { + UPDATE_1(prob); + if (checkDicSize == 0 && processedPos == 0) + return SZ_ERROR_DATA; + prob = probs + IsRepG0 + state; + IF_BIT_0(prob) + { + UPDATE_0(prob); + prob = probs + IsRep0Long + (state << kNumPosBitsMax) + posState; + IF_BIT_0(prob) + { + UPDATE_0(prob); + dic[dicPos] = dic[dicPos - rep0 + (dicPos < rep0 ? dicBufSize : 0)]; + dicPos++; + processedPos++; + state = state < kNumLitStates ? 9 : 11; + continue; + } + UPDATE_1(prob); + } + else + { + UInt32 distance; + UPDATE_1(prob); + prob = probs + IsRepG1 + state; + IF_BIT_0(prob) + { + UPDATE_0(prob); + distance = rep1; + } + else + { + UPDATE_1(prob); + prob = probs + IsRepG2 + state; + IF_BIT_0(prob) + { + UPDATE_0(prob); + distance = rep2; + } + else + { + UPDATE_1(prob); + distance = rep3; + rep3 = rep2; + } + rep2 = rep1; + } + rep1 = rep0; + rep0 = distance; + } + state = state < kNumLitStates ? 8 : 11; + prob = probs + RepLenCoder; + } + + #ifdef _LZMA_SIZE_OPT + { + unsigned lim, offset; + CLzmaProb *probLen = prob + LenChoice; + IF_BIT_0(probLen) + { + UPDATE_0(probLen); + probLen = prob + LenLow + (posState << kLenNumLowBits); + offset = 0; + lim = (1 << kLenNumLowBits); + } + else + { + UPDATE_1(probLen); + probLen = prob + LenChoice2; + IF_BIT_0(probLen) + { + UPDATE_0(probLen); + probLen = prob + LenMid + (posState << kLenNumMidBits); + offset = kLenNumLowSymbols; + lim = (1 << kLenNumMidBits); + } + else + { + UPDATE_1(probLen); + probLen = prob + LenHigh; + offset = kLenNumLowSymbols + kLenNumMidSymbols; + lim = (1 << kLenNumHighBits); + } + } + TREE_DECODE(probLen, lim, len); + len += offset; + } + #else + { + CLzmaProb *probLen = prob + LenChoice; + IF_BIT_0(probLen) + { + UPDATE_0(probLen); + probLen = prob + LenLow + (posState << kLenNumLowBits); + len = 1; + TREE_GET_BIT(probLen, len); + TREE_GET_BIT(probLen, len); + TREE_GET_BIT(probLen, len); + len -= 8; + } + else + { + UPDATE_1(probLen); + probLen = prob + LenChoice2; + IF_BIT_0(probLen) + { + UPDATE_0(probLen); + probLen = prob + LenMid + (posState << kLenNumMidBits); + len = 1; + TREE_GET_BIT(probLen, len); + TREE_GET_BIT(probLen, len); + TREE_GET_BIT(probLen, len); + } + else + { + UPDATE_1(probLen); + probLen = prob + LenHigh; + TREE_DECODE(probLen, (1 << kLenNumHighBits), len); + len += kLenNumLowSymbols + kLenNumMidSymbols; + } + } + } + #endif + + if (state >= kNumStates) + { + UInt32 distance; + prob = probs + PosSlot + + ((len < kNumLenToPosStates ? len : kNumLenToPosStates - 1) << kNumPosSlotBits); + TREE_6_DECODE(prob, distance); + if (distance >= kStartPosModelIndex) + { + unsigned posSlot = (unsigned)distance; + unsigned numDirectBits = (unsigned)(((distance >> 1) - 1)); + distance = (2 | (distance & 1)); + if (posSlot < kEndPosModelIndex) + { + distance <<= numDirectBits; + prob = probs + SpecPos + distance - posSlot - 1; + { + UInt32 mask = 1; + unsigned i = 1; + do + { + GET_BIT2(prob + i, i, ; , distance |= mask); + mask <<= 1; + } + while (--numDirectBits != 0); + } + } + else + { + numDirectBits -= kNumAlignBits; + do + { + NORMALIZE + range >>= 1; + + { + UInt32 t; + code -= range; + t = (0 - ((UInt32)code >> 31)); /* (UInt32)((Int32)code >> 31) */ + distance = (distance << 1) + (t + 1); + code += range & t; + } + /* + distance <<= 1; + if (code >= range) + { + code -= range; + distance |= 1; + } + */ + } + while (--numDirectBits != 0); + prob = probs + Align; + distance <<= kNumAlignBits; + { + unsigned i = 1; + GET_BIT2(prob + i, i, ; , distance |= 1); + GET_BIT2(prob + i, i, ; , distance |= 2); + GET_BIT2(prob + i, i, ; , distance |= 4); + GET_BIT2(prob + i, i, ; , distance |= 8); + } + if (distance == (UInt32)0xFFFFFFFF) + { + len += kMatchSpecLenStart; + state -= kNumStates; + break; + } + } + } + + rep3 = rep2; + rep2 = rep1; + rep1 = rep0; + rep0 = distance + 1; + if (checkDicSize == 0) + { + if (distance >= processedPos) + { + p->dicPos = dicPos; + return SZ_ERROR_DATA; + } + } + else if (distance >= checkDicSize) + { + p->dicPos = dicPos; + return SZ_ERROR_DATA; + } + state = (state < kNumStates + kNumLitStates) ? kNumLitStates : kNumLitStates + 3; + } + + len += kMatchMinLen; + + { + SizeT rem; + unsigned curLen; + SizeT pos; + + if ((rem = limit - dicPos) == 0) + { + p->dicPos = dicPos; + return SZ_ERROR_DATA; + } + + curLen = ((rem < len) ? (unsigned)rem : len); + pos = dicPos - rep0 + (dicPos < rep0 ? dicBufSize : 0); + + processedPos += curLen; + + len -= curLen; + if (curLen <= dicBufSize - pos) + { + Byte *dest = dic + dicPos; + ptrdiff_t src = (ptrdiff_t)pos - (ptrdiff_t)dicPos; + const Byte *lim = dest + curLen; + dicPos += curLen; + do + *(dest) = (Byte)*(dest + src); + while (++dest != lim); + } + else + { + do + { + dic[dicPos++] = dic[pos]; + if (++pos == dicBufSize) + pos = 0; + } + while (--curLen != 0); + } + } + } + } + while (dicPos < limit && buf < bufLimit); + + NORMALIZE; + + p->buf = buf; + p->range = range; + p->code = code; + p->remainLen = len; + p->dicPos = dicPos; + p->processedPos = processedPos; + p->reps[0] = rep0; + p->reps[1] = rep1; + p->reps[2] = rep2; + p->reps[3] = rep3; + p->state = state; + + return SZ_OK; +} + +static void MY_FAST_CALL LzmaDec_WriteRem(CLzmaDec *p, SizeT limit) +{ + if (p->remainLen != 0 && p->remainLen < kMatchSpecLenStart) + { + Byte *dic = p->dic; + SizeT dicPos = p->dicPos; + SizeT dicBufSize = p->dicBufSize; + unsigned len = p->remainLen; + SizeT rep0 = p->reps[0]; /* we use SizeT to avoid the BUG of VC14 for AMD64 */ + SizeT rem = limit - dicPos; + if (rem < len) + len = (unsigned)(rem); + + if (p->checkDicSize == 0 && p->prop.dicSize - p->processedPos <= len) + p->checkDicSize = p->prop.dicSize; + + p->processedPos += len; + p->remainLen -= len; + while (len != 0) + { + len--; + dic[dicPos] = dic[dicPos - rep0 + (dicPos < rep0 ? dicBufSize : 0)]; + dicPos++; + } + p->dicPos = dicPos; + } +} + +static int MY_FAST_CALL LzmaDec_DecodeReal2(CLzmaDec *p, SizeT limit, const Byte *bufLimit) +{ + do + { + SizeT limit2 = limit; + if (p->checkDicSize == 0) + { + UInt32 rem = p->prop.dicSize - p->processedPos; + if (limit - p->dicPos > rem) + limit2 = p->dicPos + rem; + } + + RINOK(LzmaDec_DecodeReal(p, limit2, bufLimit)); + + if (p->checkDicSize == 0 && p->processedPos >= p->prop.dicSize) + p->checkDicSize = p->prop.dicSize; + + LzmaDec_WriteRem(p, limit); + } + while (p->dicPos < limit && p->buf < bufLimit && p->remainLen < kMatchSpecLenStart); + + if (p->remainLen > kMatchSpecLenStart) + p->remainLen = kMatchSpecLenStart; + + return 0; +} + +typedef enum +{ + DUMMY_ERROR, /* unexpected end of input stream */ + DUMMY_LIT, + DUMMY_MATCH, + DUMMY_REP +} ELzmaDummy; + +static ELzmaDummy LzmaDec_TryDummy(const CLzmaDec *p, const Byte *buf, SizeT inSize) +{ + UInt32 range = p->range; + UInt32 code = p->code; + const Byte *bufLimit = buf + inSize; + const CLzmaProb *probs = p->probs; + unsigned state = p->state; + ELzmaDummy res; + + { + const CLzmaProb *prob; + UInt32 bound; + unsigned ttt; + unsigned posState = (p->processedPos) & ((1 << p->prop.pb) - 1); + + prob = probs + IsMatch + (state << kNumPosBitsMax) + posState; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK + + /* if (bufLimit - buf >= 7) return DUMMY_LIT; */ + + prob = probs + Literal; + if (p->checkDicSize != 0 || p->processedPos != 0) + prob += ((UInt32)LZMA_LIT_SIZE * + ((((p->processedPos) & ((1 << (p->prop.lp)) - 1)) << p->prop.lc) + + (p->dic[(p->dicPos == 0 ? p->dicBufSize : p->dicPos) - 1] >> (8 - p->prop.lc)))); + + if (state < kNumLitStates) + { + unsigned symbol = 1; + do { GET_BIT_CHECK(prob + symbol, symbol) } while (symbol < 0x100); + } + else + { + unsigned matchByte = p->dic[p->dicPos - p->reps[0] + + (p->dicPos < p->reps[0] ? p->dicBufSize : 0)]; + unsigned offs = 0x100; + unsigned symbol = 1; + do + { + unsigned bit; + const CLzmaProb *probLit; + matchByte <<= 1; + bit = (matchByte & offs); + probLit = prob + offs + bit + symbol; + GET_BIT2_CHECK(probLit, symbol, offs &= ~bit, offs &= bit) + } + while (symbol < 0x100); + } + res = DUMMY_LIT; + } + else + { + unsigned len; + UPDATE_1_CHECK; + + prob = probs + IsRep + state; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK; + state = 0; + prob = probs + LenCoder; + res = DUMMY_MATCH; + } + else + { + UPDATE_1_CHECK; + res = DUMMY_REP; + prob = probs + IsRepG0 + state; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK; + prob = probs + IsRep0Long + (state << kNumPosBitsMax) + posState; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK; + NORMALIZE_CHECK; + return DUMMY_REP; + } + else + { + UPDATE_1_CHECK; + } + } + else + { + UPDATE_1_CHECK; + prob = probs + IsRepG1 + state; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK; + } + else + { + UPDATE_1_CHECK; + prob = probs + IsRepG2 + state; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK; + } + else + { + UPDATE_1_CHECK; + } + } + } + state = kNumStates; + prob = probs + RepLenCoder; + } + { + unsigned limit, offset; + const CLzmaProb *probLen = prob + LenChoice; + IF_BIT_0_CHECK(probLen) + { + UPDATE_0_CHECK; + probLen = prob + LenLow + (posState << kLenNumLowBits); + offset = 0; + limit = 1 << kLenNumLowBits; + } + else + { + UPDATE_1_CHECK; + probLen = prob + LenChoice2; + IF_BIT_0_CHECK(probLen) + { + UPDATE_0_CHECK; + probLen = prob + LenMid + (posState << kLenNumMidBits); + offset = kLenNumLowSymbols; + limit = 1 << kLenNumMidBits; + } + else + { + UPDATE_1_CHECK; + probLen = prob + LenHigh; + offset = kLenNumLowSymbols + kLenNumMidSymbols; + limit = 1 << kLenNumHighBits; + } + } + TREE_DECODE_CHECK(probLen, limit, len); + len += offset; + } + + if (state < 4) + { + unsigned posSlot; + prob = probs + PosSlot + + ((len < kNumLenToPosStates ? len : kNumLenToPosStates - 1) << + kNumPosSlotBits); + TREE_DECODE_CHECK(prob, 1 << kNumPosSlotBits, posSlot); + if (posSlot >= kStartPosModelIndex) + { + unsigned numDirectBits = ((posSlot >> 1) - 1); + + /* if (bufLimit - buf >= 8) return DUMMY_MATCH; */ + + if (posSlot < kEndPosModelIndex) + { + prob = probs + SpecPos + ((2 | (posSlot & 1)) << numDirectBits) - posSlot - 1; + } + else + { + numDirectBits -= kNumAlignBits; + do + { + NORMALIZE_CHECK + range >>= 1; + code -= range & (((code - range) >> 31) - 1); + /* if (code >= range) code -= range; */ + } + while (--numDirectBits != 0); + prob = probs + Align; + numDirectBits = kNumAlignBits; + } + { + unsigned i = 1; + do + { + GET_BIT_CHECK(prob + i, i); + } + while (--numDirectBits != 0); + } + } + } + } + } + NORMALIZE_CHECK; + return res; +} + + +void LzmaDec_InitDicAndState(CLzmaDec *p, Bool initDic, Bool initState) +{ + p->needFlush = 1; + p->remainLen = 0; + p->tempBufSize = 0; + + if (initDic) + { + p->processedPos = 0; + p->checkDicSize = 0; + p->needInitState = 1; + } + if (initState) + p->needInitState = 1; +} + +void LzmaDec_Init(CLzmaDec *p) +{ + p->dicPos = 0; + LzmaDec_InitDicAndState(p, True, True); +} + +static void LzmaDec_InitStateReal(CLzmaDec *p) +{ + SizeT numProbs = LzmaProps_GetNumProbs(&p->prop); + SizeT i; + CLzmaProb *probs = p->probs; + for (i = 0; i < numProbs; i++) + probs[i] = kBitModelTotal >> 1; + p->reps[0] = p->reps[1] = p->reps[2] = p->reps[3] = 1; + p->state = 0; + p->needInitState = 0; +} + +SRes LzmaDec_DecodeToDic(CLzmaDec *p, SizeT dicLimit, const Byte *src, SizeT *srcLen, + ELzmaFinishMode finishMode, ELzmaStatus *status) +{ + SizeT inSize = *srcLen; + (*srcLen) = 0; + LzmaDec_WriteRem(p, dicLimit); + + *status = LZMA_STATUS_NOT_SPECIFIED; + + while (p->remainLen != kMatchSpecLenStart) + { + int checkEndMarkNow; + + if (p->needFlush) + { + for (; inSize > 0 && p->tempBufSize < RC_INIT_SIZE; (*srcLen)++, inSize--) + p->tempBuf[p->tempBufSize++] = *src++; + if (p->tempBufSize < RC_INIT_SIZE) + { + *status = LZMA_STATUS_NEEDS_MORE_INPUT; + return SZ_OK; + } + if (p->tempBuf[0] != 0) + return SZ_ERROR_DATA; + p->code = + ((UInt32)p->tempBuf[1] << 24) + | ((UInt32)p->tempBuf[2] << 16) + | ((UInt32)p->tempBuf[3] << 8) + | ((UInt32)p->tempBuf[4]); + p->range = 0xFFFFFFFF; + p->needFlush = 0; + p->tempBufSize = 0; + } + + checkEndMarkNow = 0; + if (p->dicPos >= dicLimit) + { + if (p->remainLen == 0 && p->code == 0) + { + *status = LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK; + return SZ_OK; + } + if (finishMode == LZMA_FINISH_ANY) + { + *status = LZMA_STATUS_NOT_FINISHED; + return SZ_OK; + } + if (p->remainLen != 0) + { + *status = LZMA_STATUS_NOT_FINISHED; + return SZ_ERROR_DATA; + } + checkEndMarkNow = 1; + } + + if (p->needInitState) + LzmaDec_InitStateReal(p); + + if (p->tempBufSize == 0) + { + SizeT processed; + const Byte *bufLimit; + if (inSize < LZMA_REQUIRED_INPUT_MAX || checkEndMarkNow) + { + int dummyRes = LzmaDec_TryDummy(p, src, inSize); + if (dummyRes == DUMMY_ERROR) + { + memcpy(p->tempBuf, src, inSize); + p->tempBufSize = (unsigned)inSize; + (*srcLen) += inSize; + *status = LZMA_STATUS_NEEDS_MORE_INPUT; + return SZ_OK; + } + if (checkEndMarkNow && dummyRes != DUMMY_MATCH) + { + *status = LZMA_STATUS_NOT_FINISHED; + return SZ_ERROR_DATA; + } + bufLimit = src; + } + else + bufLimit = src + inSize - LZMA_REQUIRED_INPUT_MAX; + p->buf = src; + if (LzmaDec_DecodeReal2(p, dicLimit, bufLimit) != 0) + return SZ_ERROR_DATA; + processed = (SizeT)(p->buf - src); + (*srcLen) += processed; + src += processed; + inSize -= processed; + } + else + { + unsigned rem = p->tempBufSize, lookAhead = 0; + while (rem < LZMA_REQUIRED_INPUT_MAX && lookAhead < inSize) + p->tempBuf[rem++] = src[lookAhead++]; + p->tempBufSize = rem; + if (rem < LZMA_REQUIRED_INPUT_MAX || checkEndMarkNow) + { + int dummyRes = LzmaDec_TryDummy(p, p->tempBuf, rem); + if (dummyRes == DUMMY_ERROR) + { + (*srcLen) += lookAhead; + *status = LZMA_STATUS_NEEDS_MORE_INPUT; + return SZ_OK; + } + if (checkEndMarkNow && dummyRes != DUMMY_MATCH) + { + *status = LZMA_STATUS_NOT_FINISHED; + return SZ_ERROR_DATA; + } + } + p->buf = p->tempBuf; + if (LzmaDec_DecodeReal2(p, dicLimit, p->buf) != 0) + return SZ_ERROR_DATA; + + { + unsigned kkk = (unsigned)(p->buf - p->tempBuf); + if (rem < kkk) + return SZ_ERROR_FAIL; /* some internal error */ + rem -= kkk; + if (lookAhead < rem) + return SZ_ERROR_FAIL; /* some internal error */ + lookAhead -= rem; + } + (*srcLen) += lookAhead; + src += lookAhead; + inSize -= lookAhead; + p->tempBufSize = 0; + } + } + if (p->code == 0) + *status = LZMA_STATUS_FINISHED_WITH_MARK; + return (p->code == 0) ? SZ_OK : SZ_ERROR_DATA; +} + +SRes LzmaDec_DecodeToBuf(CLzmaDec *p, Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status) +{ + SizeT outSize = *destLen; + SizeT inSize = *srcLen; + *srcLen = *destLen = 0; + for (;;) + { + SizeT inSizeCur = inSize, outSizeCur, dicPos; + ELzmaFinishMode curFinishMode; + SRes res; + if (p->dicPos == p->dicBufSize) + p->dicPos = 0; + dicPos = p->dicPos; + if (outSize > p->dicBufSize - dicPos) + { + outSizeCur = p->dicBufSize; + curFinishMode = LZMA_FINISH_ANY; + } + else + { + outSizeCur = dicPos + outSize; + curFinishMode = finishMode; + } + + res = LzmaDec_DecodeToDic(p, outSizeCur, src, &inSizeCur, curFinishMode, status); + src += inSizeCur; + inSize -= inSizeCur; + *srcLen += inSizeCur; + outSizeCur = p->dicPos - dicPos; + memcpy(dest, p->dic + dicPos, outSizeCur); + dest += outSizeCur; + outSize -= outSizeCur; + *destLen += outSizeCur; + if (res != 0) + return res; + if (outSizeCur == 0 || outSize == 0) + return SZ_OK; + } +} + +void LzmaDec_FreeProbs(CLzmaDec *p, ISzAlloc *alloc) +{ + alloc->Free(alloc, p->probs); + p->probs = NULL; +} + +static void LzmaDec_FreeDict(CLzmaDec *p, ISzAlloc *alloc) +{ + alloc->Free(alloc, p->dic); + p->dic = NULL; +} + +void LzmaDec_Free(CLzmaDec *p, ISzAlloc *alloc) +{ + LzmaDec_FreeProbs(p, alloc); + LzmaDec_FreeDict(p, alloc); +} + +SRes LzmaProps_Decode(CLzmaProps *p, const Byte *data, unsigned size) +{ + UInt32 dicSize; + Byte d; + + if (size < LZMA_PROPS_SIZE) + return SZ_ERROR_UNSUPPORTED; + else + dicSize = data[1] | ((UInt32)data[2] << 8) | ((UInt32)data[3] << 16) | ((UInt32)data[4] << 24); + + if (dicSize < LZMA_DIC_MIN) + dicSize = LZMA_DIC_MIN; + p->dicSize = dicSize; + + d = data[0]; + if (d >= (9 * 5 * 5)) + return SZ_ERROR_UNSUPPORTED; + + p->lc = d % 9; + d /= 9; + p->pb = d / 5; + p->lp = d % 5; + + return SZ_OK; +} + +static SRes LzmaDec_AllocateProbs2(CLzmaDec *p, const CLzmaProps *propNew, ISzAlloc *alloc) +{ + UInt32 numProbs = LzmaProps_GetNumProbs(propNew); + if (!p->probs || numProbs != p->numProbs) + { + LzmaDec_FreeProbs(p, alloc); + p->probs = (CLzmaProb *)alloc->Alloc(alloc, numProbs * sizeof(CLzmaProb)); + p->numProbs = numProbs; + if (!p->probs) + return SZ_ERROR_MEM; + } + return SZ_OK; +} + +SRes LzmaDec_AllocateProbs(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAlloc *alloc) +{ + CLzmaProps propNew; + RINOK(LzmaProps_Decode(&propNew, props, propsSize)); + RINOK(LzmaDec_AllocateProbs2(p, &propNew, alloc)); + p->prop = propNew; + return SZ_OK; +} + +SRes LzmaDec_Allocate(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAlloc *alloc) +{ + CLzmaProps propNew; + SizeT dicBufSize; + RINOK(LzmaProps_Decode(&propNew, props, propsSize)); + RINOK(LzmaDec_AllocateProbs2(p, &propNew, alloc)); + + { + UInt32 dictSize = propNew.dicSize; + SizeT mask = ((UInt32)1 << 12) - 1; + if (dictSize >= ((UInt32)1 << 30)) mask = ((UInt32)1 << 22) - 1; + else if (dictSize >= ((UInt32)1 << 22)) mask = ((UInt32)1 << 20) - 1;; + dicBufSize = ((SizeT)dictSize + mask) & ~mask; + if (dicBufSize < dictSize) + dicBufSize = dictSize; + } + + if (!p->dic || dicBufSize != p->dicBufSize) + { + LzmaDec_FreeDict(p, alloc); + p->dic = (Byte *)alloc->Alloc(alloc, dicBufSize); + if (!p->dic) + { + LzmaDec_FreeProbs(p, alloc); + return SZ_ERROR_MEM; + } + } + p->dicBufSize = dicBufSize; + p->prop = propNew; + return SZ_OK; +} + +SRes LzmaDecode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, + const Byte *propData, unsigned propSize, ELzmaFinishMode finishMode, + ELzmaStatus *status, ISzAlloc *alloc) +{ + CLzmaDec p; + SRes res; + SizeT outSize = *destLen, inSize = *srcLen; + *destLen = *srcLen = 0; + *status = LZMA_STATUS_NOT_SPECIFIED; + if (inSize < RC_INIT_SIZE) + return SZ_ERROR_INPUT_EOF; + LzmaDec_Construct(&p); + RINOK(LzmaDec_AllocateProbs(&p, propData, propSize, alloc)); + p.dic = dest; + p.dicBufSize = outSize; + LzmaDec_Init(&p); + *srcLen = inSize; + res = LzmaDec_DecodeToDic(&p, outSize, src, srcLen, finishMode, status); + *destLen = p.dicPos; + if (res == SZ_OK && *status == LZMA_STATUS_NEEDS_MORE_INPUT) + res = SZ_ERROR_INPUT_EOF; + LzmaDec_FreeProbs(&p, alloc); + return res; +} diff --git a/libCompression/LzmaDec.h b/libCompression/LzmaDec.h new file mode 100644 index 0000000..cc44dae --- /dev/null +++ b/libCompression/LzmaDec.h @@ -0,0 +1,227 @@ +/* LzmaDec.h -- LZMA Decoder +2013-01-18 : Igor Pavlov : Public domain */ + +#ifndef __LZMA_DEC_H +#define __LZMA_DEC_H + +#include "7zTypes.h" + +EXTERN_C_BEGIN + +/* #define _LZMA_PROB32 */ +/* _LZMA_PROB32 can increase the speed on some CPUs, + but memory usage for CLzmaDec::probs will be doubled in that case */ + +#ifdef _LZMA_PROB32 +#define CLzmaProb UInt32 +#else +#define CLzmaProb UInt16 +#endif + + +/* ---------- LZMA Properties ---------- */ + +#define LZMA_PROPS_SIZE 5 + +typedef struct _CLzmaProps +{ + unsigned lc, lp, pb; + UInt32 dicSize; +} CLzmaProps; + +/* LzmaProps_Decode - decodes properties +Returns: + SZ_OK + SZ_ERROR_UNSUPPORTED - Unsupported properties +*/ + +SRes LzmaProps_Decode(CLzmaProps *p, const Byte *data, unsigned size); + + +/* ---------- LZMA Decoder state ---------- */ + +/* LZMA_REQUIRED_INPUT_MAX = number of required input bytes for worst case. + Num bits = log2((2^11 / 31) ^ 22) + 26 < 134 + 26 = 160; */ + +#define LZMA_REQUIRED_INPUT_MAX 20 + +typedef struct +{ + CLzmaProps prop; + CLzmaProb *probs; + Byte *dic; + const Byte *buf; + UInt32 range, code; + SizeT dicPos; + SizeT dicBufSize; + UInt32 processedPos; + UInt32 checkDicSize; + unsigned state; + UInt32 reps[4]; + unsigned remainLen; + int needFlush; + int needInitState; + UInt32 numProbs; + unsigned tempBufSize; + Byte tempBuf[LZMA_REQUIRED_INPUT_MAX]; +} CLzmaDec; + +#define LzmaDec_Construct(p) { (p)->dic = 0; (p)->probs = 0; } + +void LzmaDec_Init(CLzmaDec *p); + +/* There are two types of LZMA streams: + 0) Stream with end mark. That end mark adds about 6 bytes to compressed size. + 1) Stream without end mark. You must know exact uncompressed size to decompress such stream. */ + +typedef enum +{ + LZMA_FINISH_ANY, /* finish at any point */ + LZMA_FINISH_END /* block must be finished at the end */ +} ELzmaFinishMode; + +/* ELzmaFinishMode has meaning only if the decoding reaches output limit !!! + + You must use LZMA_FINISH_END, when you know that current output buffer + covers last bytes of block. In other cases you must use LZMA_FINISH_ANY. + + If LZMA decoder sees end marker before reaching output limit, it returns SZ_OK, + and output value of destLen will be less than output buffer size limit. + You can check status result also. + + You can use multiple checks to test data integrity after full decompression: + 1) Check Result and "status" variable. + 2) Check that output(destLen) = uncompressedSize, if you know real uncompressedSize. + 3) Check that output(srcLen) = compressedSize, if you know real compressedSize. + You must use correct finish mode in that case. */ + +typedef enum +{ + LZMA_STATUS_NOT_SPECIFIED, /* use main error code instead */ + LZMA_STATUS_FINISHED_WITH_MARK, /* stream was finished with end mark. */ + LZMA_STATUS_NOT_FINISHED, /* stream was not finished */ + LZMA_STATUS_NEEDS_MORE_INPUT, /* you must provide more input bytes */ + LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK /* there is probability that stream was finished without end mark */ +} ELzmaStatus; + +/* ELzmaStatus is used only as output value for function call */ + + +/* ---------- Interfaces ---------- */ + +/* There are 3 levels of interfaces: + 1) Dictionary Interface + 2) Buffer Interface + 3) One Call Interface + You can select any of these interfaces, but don't mix functions from different + groups for same object. */ + + +/* There are two variants to allocate state for Dictionary Interface: + 1) LzmaDec_Allocate / LzmaDec_Free + 2) LzmaDec_AllocateProbs / LzmaDec_FreeProbs + You can use variant 2, if you set dictionary buffer manually. + For Buffer Interface you must always use variant 1. + +LzmaDec_Allocate* can return: + SZ_OK + SZ_ERROR_MEM - Memory allocation error + SZ_ERROR_UNSUPPORTED - Unsupported properties +*/ + +SRes LzmaDec_AllocateProbs(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAlloc *alloc); +void LzmaDec_FreeProbs(CLzmaDec *p, ISzAlloc *alloc); + +SRes LzmaDec_Allocate(CLzmaDec *state, const Byte *prop, unsigned propsSize, ISzAlloc *alloc); +void LzmaDec_Free(CLzmaDec *state, ISzAlloc *alloc); + +/* ---------- Dictionary Interface ---------- */ + +/* You can use it, if you want to eliminate the overhead for data copying from + dictionary to some other external buffer. + You must work with CLzmaDec variables directly in this interface. + + STEPS: + LzmaDec_Constr() + LzmaDec_Allocate() + for (each new stream) + { + LzmaDec_Init() + while (it needs more decompression) + { + LzmaDec_DecodeToDic() + use data from CLzmaDec::dic and update CLzmaDec::dicPos + } + } + LzmaDec_Free() +*/ + +/* LzmaDec_DecodeToDic + + The decoding to internal dictionary buffer (CLzmaDec::dic). + You must manually update CLzmaDec::dicPos, if it reaches CLzmaDec::dicBufSize !!! + +finishMode: + It has meaning only if the decoding reaches output limit (dicLimit). + LZMA_FINISH_ANY - Decode just dicLimit bytes. + LZMA_FINISH_END - Stream must be finished after dicLimit. + +Returns: + SZ_OK + status: + LZMA_STATUS_FINISHED_WITH_MARK + LZMA_STATUS_NOT_FINISHED + LZMA_STATUS_NEEDS_MORE_INPUT + LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK + SZ_ERROR_DATA - Data error +*/ + +SRes LzmaDec_DecodeToDic(CLzmaDec *p, SizeT dicLimit, + const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status); + + +/* ---------- Buffer Interface ---------- */ + +/* It's zlib-like interface. + See LzmaDec_DecodeToDic description for information about STEPS and return results, + but you must use LzmaDec_DecodeToBuf instead of LzmaDec_DecodeToDic and you don't need + to work with CLzmaDec variables manually. + +finishMode: + It has meaning only if the decoding reaches output limit (*destLen). + LZMA_FINISH_ANY - Decode just destLen bytes. + LZMA_FINISH_END - Stream must be finished after (*destLen). +*/ + +SRes LzmaDec_DecodeToBuf(CLzmaDec *p, Byte *dest, SizeT *destLen, + const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status); + + +/* ---------- One Call Interface ---------- */ + +/* LzmaDecode + +finishMode: + It has meaning only if the decoding reaches output limit (*destLen). + LZMA_FINISH_ANY - Decode just destLen bytes. + LZMA_FINISH_END - Stream must be finished after (*destLen). + +Returns: + SZ_OK + status: + LZMA_STATUS_FINISHED_WITH_MARK + LZMA_STATUS_NOT_FINISHED + LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK + SZ_ERROR_DATA - Data error + SZ_ERROR_MEM - Memory allocation error + SZ_ERROR_UNSUPPORTED - Unsupported properties + SZ_ERROR_INPUT_EOF - It needs more bytes in input buffer (src). +*/ + +SRes LzmaDecode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, + const Byte *propData, unsigned propSize, ELzmaFinishMode finishMode, + ELzmaStatus *status, ISzAlloc *alloc); + +EXTERN_C_END + +#endif diff --git a/libCompression/LzmaEnc.c b/libCompression/LzmaEnc.c new file mode 100644 index 0000000..70df456 --- /dev/null +++ b/libCompression/LzmaEnc.c @@ -0,0 +1,2351 @@ +/* LzmaEnc.c -- LZMA Encoder +2016-05-16 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include + +/* #define SHOW_STAT */ +/* #define SHOW_STAT2 */ + +#if defined(SHOW_STAT) || defined(SHOW_STAT2) +#include +#endif + +#include "LzmaEnc.h" + +#include "LzFind.h" +#ifndef _7ZIP_ST +#include "LzFindMt.h" +#endif + +#ifdef SHOW_STAT +static unsigned g_STAT_OFFSET = 0; +#endif + +#define kMaxHistorySize ((UInt32)3 << 29) +/* #define kMaxHistorySize ((UInt32)7 << 29) */ + +#define kBlockSizeMax ((1 << LZMA_NUM_BLOCK_SIZE_BITS) - 1) + +#define kBlockSize (9 << 10) +#define kUnpackBlockSize (1 << 18) +#define kMatchArraySize (1 << 21) +#define kMatchRecordMaxSize ((LZMA_MATCH_LEN_MAX * 2 + 3) * LZMA_MATCH_LEN_MAX) + +#define kNumMaxDirectBits (31) + +#define kNumTopBits 24 +#define kTopValue ((UInt32)1 << kNumTopBits) + +#define kNumBitModelTotalBits 11 +#define kBitModelTotal (1 << kNumBitModelTotalBits) +#define kNumMoveBits 5 +#define kProbInitValue (kBitModelTotal >> 1) + +#define kNumMoveReducingBits 4 +#define kNumBitPriceShiftBits 4 +#define kBitPrice (1 << kNumBitPriceShiftBits) + +void LzmaEncProps_Init(CLzmaEncProps *p) +{ + p->level = 5; + p->dictSize = p->mc = 0; + p->reduceSize = (UInt64)(Int64)-1; + p->lc = p->lp = p->pb = p->algo = p->fb = p->btMode = p->numHashBytes = p->numThreads = -1; + p->writeEndMark = 0; +} + +void LzmaEncProps_Normalize(CLzmaEncProps *p) +{ + int level = p->level; + if (level < 0) level = 5; + p->level = level; + + if (p->dictSize == 0) p->dictSize = (level <= 5 ? (1 << (level * 2 + 14)) : (level == 6 ? (1 << 25) : (1 << 26))); + if (p->dictSize > p->reduceSize) + { + unsigned i; + for (i = 11; i <= 30; i++) + { + if ((UInt32)p->reduceSize <= ((UInt32)2 << i)) { p->dictSize = ((UInt32)2 << i); break; } + if ((UInt32)p->reduceSize <= ((UInt32)3 << i)) { p->dictSize = ((UInt32)3 << i); break; } + } + } + + if (p->lc < 0) p->lc = 3; + if (p->lp < 0) p->lp = 0; + if (p->pb < 0) p->pb = 2; + + if (p->algo < 0) p->algo = (level < 5 ? 0 : 1); + if (p->fb < 0) p->fb = (level < 7 ? 32 : 64); + if (p->btMode < 0) p->btMode = (p->algo == 0 ? 0 : 1); + if (p->numHashBytes < 0) p->numHashBytes = 4; + if (p->mc == 0) p->mc = (16 + (p->fb >> 1)) >> (p->btMode ? 0 : 1); + + if (p->numThreads < 0) + p->numThreads = + #ifndef _7ZIP_ST + ((p->btMode && p->algo) ? 2 : 1); + #else + 1; + #endif +} + +UInt32 LzmaEncProps_GetDictSize(const CLzmaEncProps *props2) +{ + CLzmaEncProps props = *props2; + LzmaEncProps_Normalize(&props); + return props.dictSize; +} + +#if (_MSC_VER >= 1400) +/* BSR code is fast for some new CPUs */ +/* #define LZMA_LOG_BSR */ +#endif + +#ifdef LZMA_LOG_BSR + +#define kDicLogSizeMaxCompress 32 + +#define BSR2_RET(pos, res) { unsigned long zz; _BitScanReverse(&zz, (pos)); res = (zz + zz) + ((pos >> (zz - 1)) & 1); } + +static UInt32 GetPosSlot1(UInt32 pos) +{ + UInt32 res; + BSR2_RET(pos, res); + return res; +} +#define GetPosSlot2(pos, res) { BSR2_RET(pos, res); } +#define GetPosSlot(pos, res) { if (pos < 2) res = pos; else BSR2_RET(pos, res); } + +#else + +#define kNumLogBits (9 + sizeof(size_t) / 2) +/* #define kNumLogBits (11 + sizeof(size_t) / 8 * 3) */ + +#define kDicLogSizeMaxCompress ((kNumLogBits - 1) * 2 + 7) + +static void LzmaEnc_FastPosInit(Byte *g_FastPos) +{ + unsigned slot; + g_FastPos[0] = 0; + g_FastPos[1] = 1; + g_FastPos += 2; + + for (slot = 2; slot < kNumLogBits * 2; slot++) + { + size_t k = ((size_t)1 << ((slot >> 1) - 1)); + size_t j; + for (j = 0; j < k; j++) + g_FastPos[j] = (Byte)slot; + g_FastPos += k; + } +} + +/* we can use ((limit - pos) >> 31) only if (pos < ((UInt32)1 << 31)) */ +/* +#define BSR2_RET(pos, res) { UInt32 zz = 6 + ((kNumLogBits - 1) & \ + (0 - (((((UInt32)1 << (kNumLogBits + 6)) - 1) - pos) >> 31))); \ + res = p->g_FastPos[pos >> zz] + (zz * 2); } +*/ + +/* +#define BSR2_RET(pos, res) { UInt32 zz = 6 + ((kNumLogBits - 1) & \ + (0 - (((((UInt32)1 << (kNumLogBits)) - 1) - (pos >> 6)) >> 31))); \ + res = p->g_FastPos[pos >> zz] + (zz * 2); } +*/ + +#define BSR2_RET(pos, res) { UInt32 zz = (pos < (1 << (kNumLogBits + 6))) ? 6 : 6 + kNumLogBits - 1; \ + res = p->g_FastPos[pos >> zz] + (zz * 2); } + +/* +#define BSR2_RET(pos, res) { res = (pos < (1 << (kNumLogBits + 6))) ? \ + p->g_FastPos[pos >> 6] + 12 : \ + p->g_FastPos[pos >> (6 + kNumLogBits - 1)] + (6 + (kNumLogBits - 1)) * 2; } +*/ + +#define GetPosSlot1(pos) p->g_FastPos[pos] +#define GetPosSlot2(pos, res) { BSR2_RET(pos, res); } +#define GetPosSlot(pos, res) { if (pos < kNumFullDistances) res = p->g_FastPos[pos]; else BSR2_RET(pos, res); } + +#endif + + +#define LZMA_NUM_REPS 4 + +typedef unsigned CState; + +typedef struct +{ + UInt32 price; + + CState state; + int prev1IsChar; + int prev2; + + UInt32 posPrev2; + UInt32 backPrev2; + + UInt32 posPrev; + UInt32 backPrev; + UInt32 backs[LZMA_NUM_REPS]; +} COptimal; + +#define kNumOpts (1 << 12) + +#define kNumLenToPosStates 4 +#define kNumPosSlotBits 6 +#define kDicLogSizeMin 0 +#define kDicLogSizeMax 32 +#define kDistTableSizeMax (kDicLogSizeMax * 2) + + +#define kNumAlignBits 4 +#define kAlignTableSize (1 << kNumAlignBits) +#define kAlignMask (kAlignTableSize - 1) + +#define kStartPosModelIndex 4 +#define kEndPosModelIndex 14 +#define kNumPosModels (kEndPosModelIndex - kStartPosModelIndex) + +#define kNumFullDistances (1 << (kEndPosModelIndex >> 1)) + +#ifdef _LZMA_PROB32 +#define CLzmaProb UInt32 +#else +#define CLzmaProb UInt16 +#endif + +#define LZMA_PB_MAX 4 +#define LZMA_LC_MAX 8 +#define LZMA_LP_MAX 4 + +#define LZMA_NUM_PB_STATES_MAX (1 << LZMA_PB_MAX) + + +#define kLenNumLowBits 3 +#define kLenNumLowSymbols (1 << kLenNumLowBits) +#define kLenNumMidBits 3 +#define kLenNumMidSymbols (1 << kLenNumMidBits) +#define kLenNumHighBits 8 +#define kLenNumHighSymbols (1 << kLenNumHighBits) + +#define kLenNumSymbolsTotal (kLenNumLowSymbols + kLenNumMidSymbols + kLenNumHighSymbols) + +#define LZMA_MATCH_LEN_MIN 2 +#define LZMA_MATCH_LEN_MAX (LZMA_MATCH_LEN_MIN + kLenNumSymbolsTotal - 1) + +#define kNumStates 12 + + +typedef struct +{ + CLzmaProb choice; + CLzmaProb choice2; + CLzmaProb low[LZMA_NUM_PB_STATES_MAX << kLenNumLowBits]; + CLzmaProb mid[LZMA_NUM_PB_STATES_MAX << kLenNumMidBits]; + CLzmaProb high[kLenNumHighSymbols]; +} CLenEnc; + + +typedef struct +{ + CLenEnc p; + UInt32 tableSize; + UInt32 prices[LZMA_NUM_PB_STATES_MAX][kLenNumSymbolsTotal]; + UInt32 counters[LZMA_NUM_PB_STATES_MAX]; +} CLenPriceEnc; + + +typedef struct +{ + UInt32 range; + Byte cache; + UInt64 low; + UInt64 cacheSize; + Byte *buf; + Byte *bufLim; + Byte *bufBase; + ISeqOutStream *outStream; + UInt64 processed; + SRes res; +} CRangeEnc; + + +typedef struct +{ + CLzmaProb *litProbs; + + UInt32 state; + UInt32 reps[LZMA_NUM_REPS]; + + CLzmaProb isMatch[kNumStates][LZMA_NUM_PB_STATES_MAX]; + CLzmaProb isRep[kNumStates]; + CLzmaProb isRepG0[kNumStates]; + CLzmaProb isRepG1[kNumStates]; + CLzmaProb isRepG2[kNumStates]; + CLzmaProb isRep0Long[kNumStates][LZMA_NUM_PB_STATES_MAX]; + + CLzmaProb posSlotEncoder[kNumLenToPosStates][1 << kNumPosSlotBits]; + CLzmaProb posEncoders[kNumFullDistances - kEndPosModelIndex]; + CLzmaProb posAlignEncoder[1 << kNumAlignBits]; + + CLenPriceEnc lenEnc; + CLenPriceEnc repLenEnc; +} CSaveState; + + +typedef struct +{ + void *matchFinderObj; + IMatchFinder matchFinder; + + UInt32 optimumEndIndex; + UInt32 optimumCurrentIndex; + + UInt32 longestMatchLength; + UInt32 numPairs; + UInt32 numAvail; + + UInt32 numFastBytes; + UInt32 additionalOffset; + UInt32 reps[LZMA_NUM_REPS]; + UInt32 state; + + unsigned lc, lp, pb; + unsigned lpMask, pbMask; + unsigned lclp; + + CLzmaProb *litProbs; + + Bool fastMode; + Bool writeEndMark; + Bool finished; + Bool multiThread; + Bool needInit; + + UInt64 nowPos64; + + UInt32 matchPriceCount; + UInt32 alignPriceCount; + + UInt32 distTableSize; + + UInt32 dictSize; + SRes result; + + CRangeEnc rc; + + #ifndef _7ZIP_ST + Bool mtMode; + CMatchFinderMt matchFinderMt; + #endif + + CMatchFinder matchFinderBase; + + #ifndef _7ZIP_ST + Byte pad[128]; + #endif + + COptimal opt[kNumOpts]; + + #ifndef LZMA_LOG_BSR + Byte g_FastPos[1 << kNumLogBits]; + #endif + + UInt32 ProbPrices[kBitModelTotal >> kNumMoveReducingBits]; + UInt32 matches[LZMA_MATCH_LEN_MAX * 2 + 2 + 1]; + + UInt32 posSlotPrices[kNumLenToPosStates][kDistTableSizeMax]; + UInt32 distancesPrices[kNumLenToPosStates][kNumFullDistances]; + UInt32 alignPrices[kAlignTableSize]; + + CLzmaProb isMatch[kNumStates][LZMA_NUM_PB_STATES_MAX]; + CLzmaProb isRep[kNumStates]; + CLzmaProb isRepG0[kNumStates]; + CLzmaProb isRepG1[kNumStates]; + CLzmaProb isRepG2[kNumStates]; + CLzmaProb isRep0Long[kNumStates][LZMA_NUM_PB_STATES_MAX]; + + CLzmaProb posSlotEncoder[kNumLenToPosStates][1 << kNumPosSlotBits]; + CLzmaProb posEncoders[kNumFullDistances - kEndPosModelIndex]; + CLzmaProb posAlignEncoder[1 << kNumAlignBits]; + + CLenPriceEnc lenEnc; + CLenPriceEnc repLenEnc; + + CSaveState saveState; + + #ifndef _7ZIP_ST + Byte pad2[128]; + #endif +} CLzmaEnc; + + +void LzmaEnc_SaveState(CLzmaEncHandle pp) +{ + CLzmaEnc *p = (CLzmaEnc *)pp; + CSaveState *dest = &p->saveState; + int i; + dest->lenEnc = p->lenEnc; + dest->repLenEnc = p->repLenEnc; + dest->state = p->state; + + for (i = 0; i < kNumStates; i++) + { + memcpy(dest->isMatch[i], p->isMatch[i], sizeof(p->isMatch[i])); + memcpy(dest->isRep0Long[i], p->isRep0Long[i], sizeof(p->isRep0Long[i])); + } + for (i = 0; i < kNumLenToPosStates; i++) + memcpy(dest->posSlotEncoder[i], p->posSlotEncoder[i], sizeof(p->posSlotEncoder[i])); + memcpy(dest->isRep, p->isRep, sizeof(p->isRep)); + memcpy(dest->isRepG0, p->isRepG0, sizeof(p->isRepG0)); + memcpy(dest->isRepG1, p->isRepG1, sizeof(p->isRepG1)); + memcpy(dest->isRepG2, p->isRepG2, sizeof(p->isRepG2)); + memcpy(dest->posEncoders, p->posEncoders, sizeof(p->posEncoders)); + memcpy(dest->posAlignEncoder, p->posAlignEncoder, sizeof(p->posAlignEncoder)); + memcpy(dest->reps, p->reps, sizeof(p->reps)); + memcpy(dest->litProbs, p->litProbs, ((UInt32)0x300 << p->lclp) * sizeof(CLzmaProb)); +} + +void LzmaEnc_RestoreState(CLzmaEncHandle pp) +{ + CLzmaEnc *dest = (CLzmaEnc *)pp; + const CSaveState *p = &dest->saveState; + int i; + dest->lenEnc = p->lenEnc; + dest->repLenEnc = p->repLenEnc; + dest->state = p->state; + + for (i = 0; i < kNumStates; i++) + { + memcpy(dest->isMatch[i], p->isMatch[i], sizeof(p->isMatch[i])); + memcpy(dest->isRep0Long[i], p->isRep0Long[i], sizeof(p->isRep0Long[i])); + } + for (i = 0; i < kNumLenToPosStates; i++) + memcpy(dest->posSlotEncoder[i], p->posSlotEncoder[i], sizeof(p->posSlotEncoder[i])); + memcpy(dest->isRep, p->isRep, sizeof(p->isRep)); + memcpy(dest->isRepG0, p->isRepG0, sizeof(p->isRepG0)); + memcpy(dest->isRepG1, p->isRepG1, sizeof(p->isRepG1)); + memcpy(dest->isRepG2, p->isRepG2, sizeof(p->isRepG2)); + memcpy(dest->posEncoders, p->posEncoders, sizeof(p->posEncoders)); + memcpy(dest->posAlignEncoder, p->posAlignEncoder, sizeof(p->posAlignEncoder)); + memcpy(dest->reps, p->reps, sizeof(p->reps)); + memcpy(dest->litProbs, p->litProbs, ((UInt32)0x300 << dest->lclp) * sizeof(CLzmaProb)); +} + +SRes LzmaEnc_SetProps(CLzmaEncHandle pp, const CLzmaEncProps *props2) +{ + CLzmaEnc *p = (CLzmaEnc *)pp; + CLzmaEncProps props = *props2; + LzmaEncProps_Normalize(&props); + + if (props.lc > LZMA_LC_MAX + || props.lp > LZMA_LP_MAX + || props.pb > LZMA_PB_MAX + || props.dictSize > ((UInt64)1 << kDicLogSizeMaxCompress) + || props.dictSize > kMaxHistorySize) + return SZ_ERROR_PARAM; + + p->dictSize = props.dictSize; + { + unsigned fb = props.fb; + if (fb < 5) + fb = 5; + if (fb > LZMA_MATCH_LEN_MAX) + fb = LZMA_MATCH_LEN_MAX; + p->numFastBytes = fb; + } + p->lc = props.lc; + p->lp = props.lp; + p->pb = props.pb; + p->fastMode = (props.algo == 0); + p->matchFinderBase.btMode = (Byte)(props.btMode ? 1 : 0); + { + UInt32 numHashBytes = 4; + if (props.btMode) + { + if (props.numHashBytes < 2) + numHashBytes = 2; + else if (props.numHashBytes < 4) + numHashBytes = props.numHashBytes; + } + p->matchFinderBase.numHashBytes = numHashBytes; + } + + p->matchFinderBase.cutValue = props.mc; + + p->writeEndMark = props.writeEndMark; + + #ifndef _7ZIP_ST + /* + if (newMultiThread != _multiThread) + { + ReleaseMatchFinder(); + _multiThread = newMultiThread; + } + */ + p->multiThread = (props.numThreads > 1); + #endif + + return SZ_OK; +} + +static const int kLiteralNextStates[kNumStates] = {0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 4, 5}; +static const int kMatchNextStates[kNumStates] = {7, 7, 7, 7, 7, 7, 7, 10, 10, 10, 10, 10}; +static const int kRepNextStates[kNumStates] = {8, 8, 8, 8, 8, 8, 8, 11, 11, 11, 11, 11}; +static const int kShortRepNextStates[kNumStates]= {9, 9, 9, 9, 9, 9, 9, 11, 11, 11, 11, 11}; + +#define IsCharState(s) ((s) < 7) + +#define GetLenToPosState(len) (((len) < kNumLenToPosStates + 1) ? (len) - 2 : kNumLenToPosStates - 1) + +#define kInfinityPrice (1 << 30) + +static void RangeEnc_Construct(CRangeEnc *p) +{ + p->outStream = NULL; + p->bufBase = NULL; +} + +#define RangeEnc_GetProcessed(p) ((p)->processed + ((p)->buf - (p)->bufBase) + (p)->cacheSize) + +#define RC_BUF_SIZE (1 << 16) +static int RangeEnc_Alloc(CRangeEnc *p, ISzAlloc *alloc) +{ + if (!p->bufBase) + { + p->bufBase = (Byte *)alloc->Alloc(alloc, RC_BUF_SIZE); + if (!p->bufBase) + return 0; + p->bufLim = p->bufBase + RC_BUF_SIZE; + } + return 1; +} + +static void RangeEnc_Free(CRangeEnc *p, ISzAlloc *alloc) +{ + alloc->Free(alloc, p->bufBase); + p->bufBase = 0; +} + +static void RangeEnc_Init(CRangeEnc *p) +{ + /* Stream.Init(); */ + p->low = 0; + p->range = 0xFFFFFFFF; + p->cacheSize = 1; + p->cache = 0; + + p->buf = p->bufBase; + + p->processed = 0; + p->res = SZ_OK; +} + +static void RangeEnc_FlushStream(CRangeEnc *p) +{ + size_t num; + if (p->res != SZ_OK) + return; + num = p->buf - p->bufBase; + if (num != p->outStream->Write(p->outStream, p->bufBase, num)) + p->res = SZ_ERROR_WRITE; + p->processed += num; + p->buf = p->bufBase; +} + +static void MY_FAST_CALL RangeEnc_ShiftLow(CRangeEnc *p) +{ + if ((UInt32)p->low < (UInt32)0xFF000000 || (unsigned)(p->low >> 32) != 0) + { + Byte temp = p->cache; + do + { + Byte *buf = p->buf; + *buf++ = (Byte)(temp + (Byte)(p->low >> 32)); + p->buf = buf; + if (buf == p->bufLim) + RangeEnc_FlushStream(p); + temp = 0xFF; + } + while (--p->cacheSize != 0); + p->cache = (Byte)((UInt32)p->low >> 24); + } + p->cacheSize++; + p->low = (UInt32)p->low << 8; +} + +static void RangeEnc_FlushData(CRangeEnc *p) +{ + int i; + for (i = 0; i < 5; i++) + RangeEnc_ShiftLow(p); +} + +static void RangeEnc_EncodeDirectBits(CRangeEnc *p, UInt32 value, unsigned numBits) +{ + do + { + p->range >>= 1; + p->low += p->range & (0 - ((value >> --numBits) & 1)); + if (p->range < kTopValue) + { + p->range <<= 8; + RangeEnc_ShiftLow(p); + } + } + while (numBits != 0); +} + +static void RangeEnc_EncodeBit(CRangeEnc *p, CLzmaProb *prob, UInt32 symbol) +{ + UInt32 ttt = *prob; + UInt32 newBound = (p->range >> kNumBitModelTotalBits) * ttt; + if (symbol == 0) + { + p->range = newBound; + ttt += (kBitModelTotal - ttt) >> kNumMoveBits; + } + else + { + p->low += newBound; + p->range -= newBound; + ttt -= ttt >> kNumMoveBits; + } + *prob = (CLzmaProb)ttt; + if (p->range < kTopValue) + { + p->range <<= 8; + RangeEnc_ShiftLow(p); + } +} + +static void LitEnc_Encode(CRangeEnc *p, CLzmaProb *probs, UInt32 symbol) +{ + symbol |= 0x100; + do + { + RangeEnc_EncodeBit(p, probs + (symbol >> 8), (symbol >> 7) & 1); + symbol <<= 1; + } + while (symbol < 0x10000); +} + +static void LitEnc_EncodeMatched(CRangeEnc *p, CLzmaProb *probs, UInt32 symbol, UInt32 matchByte) +{ + UInt32 offs = 0x100; + symbol |= 0x100; + do + { + matchByte <<= 1; + RangeEnc_EncodeBit(p, probs + (offs + (matchByte & offs) + (symbol >> 8)), (symbol >> 7) & 1); + symbol <<= 1; + offs &= ~(matchByte ^ symbol); + } + while (symbol < 0x10000); +} + +static void LzmaEnc_InitPriceTables(UInt32 *ProbPrices) +{ + UInt32 i; + for (i = (1 << kNumMoveReducingBits) / 2; i < kBitModelTotal; i += (1 << kNumMoveReducingBits)) + { + const int kCyclesBits = kNumBitPriceShiftBits; + UInt32 w = i; + UInt32 bitCount = 0; + int j; + for (j = 0; j < kCyclesBits; j++) + { + w = w * w; + bitCount <<= 1; + while (w >= ((UInt32)1 << 16)) + { + w >>= 1; + bitCount++; + } + } + ProbPrices[i >> kNumMoveReducingBits] = ((kNumBitModelTotalBits << kCyclesBits) - 15 - bitCount); + } +} + + +#define GET_PRICE(prob, symbol) \ + p->ProbPrices[((prob) ^ (((-(int)(symbol))) & (kBitModelTotal - 1))) >> kNumMoveReducingBits]; + +#define GET_PRICEa(prob, symbol) \ + ProbPrices[((prob) ^ ((-((int)(symbol))) & (kBitModelTotal - 1))) >> kNumMoveReducingBits]; + +#define GET_PRICE_0(prob) p->ProbPrices[(prob) >> kNumMoveReducingBits] +#define GET_PRICE_1(prob) p->ProbPrices[((prob) ^ (kBitModelTotal - 1)) >> kNumMoveReducingBits] + +#define GET_PRICE_0a(prob) ProbPrices[(prob) >> kNumMoveReducingBits] +#define GET_PRICE_1a(prob) ProbPrices[((prob) ^ (kBitModelTotal - 1)) >> kNumMoveReducingBits] + +static UInt32 LitEnc_GetPrice(const CLzmaProb *probs, UInt32 symbol, const UInt32 *ProbPrices) +{ + UInt32 price = 0; + symbol |= 0x100; + do + { + price += GET_PRICEa(probs[symbol >> 8], (symbol >> 7) & 1); + symbol <<= 1; + } + while (symbol < 0x10000); + return price; +} + +static UInt32 LitEnc_GetPriceMatched(const CLzmaProb *probs, UInt32 symbol, UInt32 matchByte, const UInt32 *ProbPrices) +{ + UInt32 price = 0; + UInt32 offs = 0x100; + symbol |= 0x100; + do + { + matchByte <<= 1; + price += GET_PRICEa(probs[offs + (matchByte & offs) + (symbol >> 8)], (symbol >> 7) & 1); + symbol <<= 1; + offs &= ~(matchByte ^ symbol); + } + while (symbol < 0x10000); + return price; +} + + +static void RcTree_Encode(CRangeEnc *rc, CLzmaProb *probs, int numBitLevels, UInt32 symbol) +{ + UInt32 m = 1; + int i; + for (i = numBitLevels; i != 0;) + { + UInt32 bit; + i--; + bit = (symbol >> i) & 1; + RangeEnc_EncodeBit(rc, probs + m, bit); + m = (m << 1) | bit; + } +} + +static void RcTree_ReverseEncode(CRangeEnc *rc, CLzmaProb *probs, int numBitLevels, UInt32 symbol) +{ + UInt32 m = 1; + int i; + for (i = 0; i < numBitLevels; i++) + { + UInt32 bit = symbol & 1; + RangeEnc_EncodeBit(rc, probs + m, bit); + m = (m << 1) | bit; + symbol >>= 1; + } +} + +static UInt32 RcTree_GetPrice(const CLzmaProb *probs, int numBitLevels, UInt32 symbol, const UInt32 *ProbPrices) +{ + UInt32 price = 0; + symbol |= (1 << numBitLevels); + while (symbol != 1) + { + price += GET_PRICEa(probs[symbol >> 1], symbol & 1); + symbol >>= 1; + } + return price; +} + +static UInt32 RcTree_ReverseGetPrice(const CLzmaProb *probs, int numBitLevels, UInt32 symbol, const UInt32 *ProbPrices) +{ + UInt32 price = 0; + UInt32 m = 1; + int i; + for (i = numBitLevels; i != 0; i--) + { + UInt32 bit = symbol & 1; + symbol >>= 1; + price += GET_PRICEa(probs[m], bit); + m = (m << 1) | bit; + } + return price; +} + + +static void LenEnc_Init(CLenEnc *p) +{ + unsigned i; + p->choice = p->choice2 = kProbInitValue; + for (i = 0; i < (LZMA_NUM_PB_STATES_MAX << kLenNumLowBits); i++) + p->low[i] = kProbInitValue; + for (i = 0; i < (LZMA_NUM_PB_STATES_MAX << kLenNumMidBits); i++) + p->mid[i] = kProbInitValue; + for (i = 0; i < kLenNumHighSymbols; i++) + p->high[i] = kProbInitValue; +} + +static void LenEnc_Encode(CLenEnc *p, CRangeEnc *rc, UInt32 symbol, UInt32 posState) +{ + if (symbol < kLenNumLowSymbols) + { + RangeEnc_EncodeBit(rc, &p->choice, 0); + RcTree_Encode(rc, p->low + (posState << kLenNumLowBits), kLenNumLowBits, symbol); + } + else + { + RangeEnc_EncodeBit(rc, &p->choice, 1); + if (symbol < kLenNumLowSymbols + kLenNumMidSymbols) + { + RangeEnc_EncodeBit(rc, &p->choice2, 0); + RcTree_Encode(rc, p->mid + (posState << kLenNumMidBits), kLenNumMidBits, symbol - kLenNumLowSymbols); + } + else + { + RangeEnc_EncodeBit(rc, &p->choice2, 1); + RcTree_Encode(rc, p->high, kLenNumHighBits, symbol - kLenNumLowSymbols - kLenNumMidSymbols); + } + } +} + +static void LenEnc_SetPrices(CLenEnc *p, UInt32 posState, UInt32 numSymbols, UInt32 *prices, const UInt32 *ProbPrices) +{ + UInt32 a0 = GET_PRICE_0a(p->choice); + UInt32 a1 = GET_PRICE_1a(p->choice); + UInt32 b0 = a1 + GET_PRICE_0a(p->choice2); + UInt32 b1 = a1 + GET_PRICE_1a(p->choice2); + UInt32 i = 0; + for (i = 0; i < kLenNumLowSymbols; i++) + { + if (i >= numSymbols) + return; + prices[i] = a0 + RcTree_GetPrice(p->low + (posState << kLenNumLowBits), kLenNumLowBits, i, ProbPrices); + } + for (; i < kLenNumLowSymbols + kLenNumMidSymbols; i++) + { + if (i >= numSymbols) + return; + prices[i] = b0 + RcTree_GetPrice(p->mid + (posState << kLenNumMidBits), kLenNumMidBits, i - kLenNumLowSymbols, ProbPrices); + } + for (; i < numSymbols; i++) + prices[i] = b1 + RcTree_GetPrice(p->high, kLenNumHighBits, i - kLenNumLowSymbols - kLenNumMidSymbols, ProbPrices); +} + +static void MY_FAST_CALL LenPriceEnc_UpdateTable(CLenPriceEnc *p, UInt32 posState, const UInt32 *ProbPrices) +{ + LenEnc_SetPrices(&p->p, posState, p->tableSize, p->prices[posState], ProbPrices); + p->counters[posState] = p->tableSize; +} + +static void LenPriceEnc_UpdateTables(CLenPriceEnc *p, UInt32 numPosStates, const UInt32 *ProbPrices) +{ + UInt32 posState; + for (posState = 0; posState < numPosStates; posState++) + LenPriceEnc_UpdateTable(p, posState, ProbPrices); +} + +static void LenEnc_Encode2(CLenPriceEnc *p, CRangeEnc *rc, UInt32 symbol, UInt32 posState, Bool updatePrice, const UInt32 *ProbPrices) +{ + LenEnc_Encode(&p->p, rc, symbol, posState); + if (updatePrice) + if (--p->counters[posState] == 0) + LenPriceEnc_UpdateTable(p, posState, ProbPrices); +} + + + + +static void MovePos(CLzmaEnc *p, UInt32 num) +{ + #ifdef SHOW_STAT + g_STAT_OFFSET += num; + printf("\n MovePos %u", num); + #endif + + if (num != 0) + { + p->additionalOffset += num; + p->matchFinder.Skip(p->matchFinderObj, num); + } +} + +static UInt32 ReadMatchDistances(CLzmaEnc *p, UInt32 *numDistancePairsRes) +{ + UInt32 lenRes = 0, numPairs; + p->numAvail = p->matchFinder.GetNumAvailableBytes(p->matchFinderObj); + numPairs = p->matchFinder.GetMatches(p->matchFinderObj, p->matches); + + #ifdef SHOW_STAT + printf("\n i = %u numPairs = %u ", g_STAT_OFFSET, numPairs / 2); + g_STAT_OFFSET++; + { + UInt32 i; + for (i = 0; i < numPairs; i += 2) + printf("%2u %6u | ", p->matches[i], p->matches[i + 1]); + } + #endif + + if (numPairs > 0) + { + lenRes = p->matches[numPairs - 2]; + if (lenRes == p->numFastBytes) + { + UInt32 numAvail = p->numAvail; + if (numAvail > LZMA_MATCH_LEN_MAX) + numAvail = LZMA_MATCH_LEN_MAX; + { + const Byte *pbyCur = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1; + const Byte *pby = pbyCur + lenRes; + ptrdiff_t dif = (ptrdiff_t)-1 - p->matches[numPairs - 1]; + const Byte *pbyLim = pbyCur + numAvail; + for (; pby != pbyLim && *pby == pby[dif]; pby++); + lenRes = (UInt32)(pby - pbyCur); + } + } + } + p->additionalOffset++; + *numDistancePairsRes = numPairs; + return lenRes; +} + + +#define MakeAsChar(p) (p)->backPrev = (UInt32)(-1); (p)->prev1IsChar = False; +#define MakeAsShortRep(p) (p)->backPrev = 0; (p)->prev1IsChar = False; +#define IsShortRep(p) ((p)->backPrev == 0) + +static UInt32 GetRepLen1Price(CLzmaEnc *p, UInt32 state, UInt32 posState) +{ + return + GET_PRICE_0(p->isRepG0[state]) + + GET_PRICE_0(p->isRep0Long[state][posState]); +} + +static UInt32 GetPureRepPrice(CLzmaEnc *p, UInt32 repIndex, UInt32 state, UInt32 posState) +{ + UInt32 price; + if (repIndex == 0) + { + price = GET_PRICE_0(p->isRepG0[state]); + price += GET_PRICE_1(p->isRep0Long[state][posState]); + } + else + { + price = GET_PRICE_1(p->isRepG0[state]); + if (repIndex == 1) + price += GET_PRICE_0(p->isRepG1[state]); + else + { + price += GET_PRICE_1(p->isRepG1[state]); + price += GET_PRICE(p->isRepG2[state], repIndex - 2); + } + } + return price; +} + +static UInt32 GetRepPrice(CLzmaEnc *p, UInt32 repIndex, UInt32 len, UInt32 state, UInt32 posState) +{ + return p->repLenEnc.prices[posState][len - LZMA_MATCH_LEN_MIN] + + GetPureRepPrice(p, repIndex, state, posState); +} + +static UInt32 Backward(CLzmaEnc *p, UInt32 *backRes, UInt32 cur) +{ + UInt32 posMem = p->opt[cur].posPrev; + UInt32 backMem = p->opt[cur].backPrev; + p->optimumEndIndex = cur; + do + { + if (p->opt[cur].prev1IsChar) + { + MakeAsChar(&p->opt[posMem]) + p->opt[posMem].posPrev = posMem - 1; + if (p->opt[cur].prev2) + { + p->opt[posMem - 1].prev1IsChar = False; + p->opt[posMem - 1].posPrev = p->opt[cur].posPrev2; + p->opt[posMem - 1].backPrev = p->opt[cur].backPrev2; + } + } + { + UInt32 posPrev = posMem; + UInt32 backCur = backMem; + + backMem = p->opt[posPrev].backPrev; + posMem = p->opt[posPrev].posPrev; + + p->opt[posPrev].backPrev = backCur; + p->opt[posPrev].posPrev = cur; + cur = posPrev; + } + } + while (cur != 0); + *backRes = p->opt[0].backPrev; + p->optimumCurrentIndex = p->opt[0].posPrev; + return p->optimumCurrentIndex; +} + +#define LIT_PROBS(pos, prevByte) (p->litProbs + ((((pos) & p->lpMask) << p->lc) + ((prevByte) >> (8 - p->lc))) * (UInt32)0x300) + +static UInt32 GetOptimum(CLzmaEnc *p, UInt32 position, UInt32 *backRes) +{ + UInt32 lenEnd, cur; + UInt32 reps[LZMA_NUM_REPS], repLens[LZMA_NUM_REPS]; + UInt32 *matches; + + { + + UInt32 numAvail, mainLen, numPairs, repMaxIndex, i, posState, len; + UInt32 matchPrice, repMatchPrice, normalMatchPrice; + const Byte *data; + Byte curByte, matchByte; + + if (p->optimumEndIndex != p->optimumCurrentIndex) + { + const COptimal *opt = &p->opt[p->optimumCurrentIndex]; + UInt32 lenRes = opt->posPrev - p->optimumCurrentIndex; + *backRes = opt->backPrev; + p->optimumCurrentIndex = opt->posPrev; + return lenRes; + } + p->optimumCurrentIndex = p->optimumEndIndex = 0; + + if (p->additionalOffset == 0) + mainLen = ReadMatchDistances(p, &numPairs); + else + { + mainLen = p->longestMatchLength; + numPairs = p->numPairs; + } + + numAvail = p->numAvail; + if (numAvail < 2) + { + *backRes = (UInt32)(-1); + return 1; + } + if (numAvail > LZMA_MATCH_LEN_MAX) + numAvail = LZMA_MATCH_LEN_MAX; + + data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1; + repMaxIndex = 0; + for (i = 0; i < LZMA_NUM_REPS; i++) + { + UInt32 lenTest; + const Byte *data2; + reps[i] = p->reps[i]; + data2 = data - reps[i] - 1; + if (data[0] != data2[0] || data[1] != data2[1]) + { + repLens[i] = 0; + continue; + } + for (lenTest = 2; lenTest < numAvail && data[lenTest] == data2[lenTest]; lenTest++); + repLens[i] = lenTest; + if (lenTest > repLens[repMaxIndex]) + repMaxIndex = i; + } + if (repLens[repMaxIndex] >= p->numFastBytes) + { + UInt32 lenRes; + *backRes = repMaxIndex; + lenRes = repLens[repMaxIndex]; + MovePos(p, lenRes - 1); + return lenRes; + } + + matches = p->matches; + if (mainLen >= p->numFastBytes) + { + *backRes = matches[numPairs - 1] + LZMA_NUM_REPS; + MovePos(p, mainLen - 1); + return mainLen; + } + curByte = *data; + matchByte = *(data - (reps[0] + 1)); + + if (mainLen < 2 && curByte != matchByte && repLens[repMaxIndex] < 2) + { + *backRes = (UInt32)-1; + return 1; + } + + p->opt[0].state = (CState)p->state; + + posState = (position & p->pbMask); + + { + const CLzmaProb *probs = LIT_PROBS(position, *(data - 1)); + p->opt[1].price = GET_PRICE_0(p->isMatch[p->state][posState]) + + (!IsCharState(p->state) ? + LitEnc_GetPriceMatched(probs, curByte, matchByte, p->ProbPrices) : + LitEnc_GetPrice(probs, curByte, p->ProbPrices)); + } + + MakeAsChar(&p->opt[1]); + + matchPrice = GET_PRICE_1(p->isMatch[p->state][posState]); + repMatchPrice = matchPrice + GET_PRICE_1(p->isRep[p->state]); + + if (matchByte == curByte) + { + UInt32 shortRepPrice = repMatchPrice + GetRepLen1Price(p, p->state, posState); + if (shortRepPrice < p->opt[1].price) + { + p->opt[1].price = shortRepPrice; + MakeAsShortRep(&p->opt[1]); + } + } + lenEnd = ((mainLen >= repLens[repMaxIndex]) ? mainLen : repLens[repMaxIndex]); + + if (lenEnd < 2) + { + *backRes = p->opt[1].backPrev; + return 1; + } + + p->opt[1].posPrev = 0; + for (i = 0; i < LZMA_NUM_REPS; i++) + p->opt[0].backs[i] = reps[i]; + + len = lenEnd; + do + p->opt[len--].price = kInfinityPrice; + while (len >= 2); + + for (i = 0; i < LZMA_NUM_REPS; i++) + { + UInt32 repLen = repLens[i]; + UInt32 price; + if (repLen < 2) + continue; + price = repMatchPrice + GetPureRepPrice(p, i, p->state, posState); + do + { + UInt32 curAndLenPrice = price + p->repLenEnc.prices[posState][repLen - 2]; + COptimal *opt = &p->opt[repLen]; + if (curAndLenPrice < opt->price) + { + opt->price = curAndLenPrice; + opt->posPrev = 0; + opt->backPrev = i; + opt->prev1IsChar = False; + } + } + while (--repLen >= 2); + } + + normalMatchPrice = matchPrice + GET_PRICE_0(p->isRep[p->state]); + + len = ((repLens[0] >= 2) ? repLens[0] + 1 : 2); + if (len <= mainLen) + { + UInt32 offs = 0; + while (len > matches[offs]) + offs += 2; + for (; ; len++) + { + COptimal *opt; + UInt32 distance = matches[offs + 1]; + + UInt32 curAndLenPrice = normalMatchPrice + p->lenEnc.prices[posState][len - LZMA_MATCH_LEN_MIN]; + UInt32 lenToPosState = GetLenToPosState(len); + if (distance < kNumFullDistances) + curAndLenPrice += p->distancesPrices[lenToPosState][distance]; + else + { + UInt32 slot; + GetPosSlot2(distance, slot); + curAndLenPrice += p->alignPrices[distance & kAlignMask] + p->posSlotPrices[lenToPosState][slot]; + } + opt = &p->opt[len]; + if (curAndLenPrice < opt->price) + { + opt->price = curAndLenPrice; + opt->posPrev = 0; + opt->backPrev = distance + LZMA_NUM_REPS; + opt->prev1IsChar = False; + } + if (len == matches[offs]) + { + offs += 2; + if (offs == numPairs) + break; + } + } + } + + cur = 0; + + #ifdef SHOW_STAT2 + /* if (position >= 0) */ + { + unsigned i; + printf("\n pos = %4X", position); + for (i = cur; i <= lenEnd; i++) + printf("\nprice[%4X] = %u", position - cur + i, p->opt[i].price); + } + #endif + + } + + for (;;) + { + UInt32 numAvail; + UInt32 numAvailFull, newLen, numPairs, posPrev, state, posState, startLen; + UInt32 curPrice, curAnd1Price, matchPrice, repMatchPrice; + Bool nextIsChar; + Byte curByte, matchByte; + const Byte *data; + COptimal *curOpt; + COptimal *nextOpt; + + cur++; + if (cur == lenEnd) + return Backward(p, backRes, cur); + + newLen = ReadMatchDistances(p, &numPairs); + if (newLen >= p->numFastBytes) + { + p->numPairs = numPairs; + p->longestMatchLength = newLen; + return Backward(p, backRes, cur); + } + position++; + curOpt = &p->opt[cur]; + posPrev = curOpt->posPrev; + if (curOpt->prev1IsChar) + { + posPrev--; + if (curOpt->prev2) + { + state = p->opt[curOpt->posPrev2].state; + if (curOpt->backPrev2 < LZMA_NUM_REPS) + state = kRepNextStates[state]; + else + state = kMatchNextStates[state]; + } + else + state = p->opt[posPrev].state; + state = kLiteralNextStates[state]; + } + else + state = p->opt[posPrev].state; + if (posPrev == cur - 1) + { + if (IsShortRep(curOpt)) + state = kShortRepNextStates[state]; + else + state = kLiteralNextStates[state]; + } + else + { + UInt32 pos; + const COptimal *prevOpt; + if (curOpt->prev1IsChar && curOpt->prev2) + { + posPrev = curOpt->posPrev2; + pos = curOpt->backPrev2; + state = kRepNextStates[state]; + } + else + { + pos = curOpt->backPrev; + if (pos < LZMA_NUM_REPS) + state = kRepNextStates[state]; + else + state = kMatchNextStates[state]; + } + prevOpt = &p->opt[posPrev]; + if (pos < LZMA_NUM_REPS) + { + UInt32 i; + reps[0] = prevOpt->backs[pos]; + for (i = 1; i <= pos; i++) + reps[i] = prevOpt->backs[i - 1]; + for (; i < LZMA_NUM_REPS; i++) + reps[i] = prevOpt->backs[i]; + } + else + { + UInt32 i; + reps[0] = (pos - LZMA_NUM_REPS); + for (i = 1; i < LZMA_NUM_REPS; i++) + reps[i] = prevOpt->backs[i - 1]; + } + } + curOpt->state = (CState)state; + + curOpt->backs[0] = reps[0]; + curOpt->backs[1] = reps[1]; + curOpt->backs[2] = reps[2]; + curOpt->backs[3] = reps[3]; + + curPrice = curOpt->price; + nextIsChar = False; + data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1; + curByte = *data; + matchByte = *(data - (reps[0] + 1)); + + posState = (position & p->pbMask); + + curAnd1Price = curPrice + GET_PRICE_0(p->isMatch[state][posState]); + { + const CLzmaProb *probs = LIT_PROBS(position, *(data - 1)); + curAnd1Price += + (!IsCharState(state) ? + LitEnc_GetPriceMatched(probs, curByte, matchByte, p->ProbPrices) : + LitEnc_GetPrice(probs, curByte, p->ProbPrices)); + } + + nextOpt = &p->opt[cur + 1]; + + if (curAnd1Price < nextOpt->price) + { + nextOpt->price = curAnd1Price; + nextOpt->posPrev = cur; + MakeAsChar(nextOpt); + nextIsChar = True; + } + + matchPrice = curPrice + GET_PRICE_1(p->isMatch[state][posState]); + repMatchPrice = matchPrice + GET_PRICE_1(p->isRep[state]); + + if (matchByte == curByte && !(nextOpt->posPrev < cur && nextOpt->backPrev == 0)) + { + UInt32 shortRepPrice = repMatchPrice + GetRepLen1Price(p, state, posState); + if (shortRepPrice <= nextOpt->price) + { + nextOpt->price = shortRepPrice; + nextOpt->posPrev = cur; + MakeAsShortRep(nextOpt); + nextIsChar = True; + } + } + numAvailFull = p->numAvail; + { + UInt32 temp = kNumOpts - 1 - cur; + if (temp < numAvailFull) + numAvailFull = temp; + } + + if (numAvailFull < 2) + continue; + numAvail = (numAvailFull <= p->numFastBytes ? numAvailFull : p->numFastBytes); + + if (!nextIsChar && matchByte != curByte) /* speed optimization */ + { + /* try Literal + rep0 */ + UInt32 temp; + UInt32 lenTest2; + const Byte *data2 = data - reps[0] - 1; + UInt32 limit = p->numFastBytes + 1; + if (limit > numAvailFull) + limit = numAvailFull; + + for (temp = 1; temp < limit && data[temp] == data2[temp]; temp++); + lenTest2 = temp - 1; + if (lenTest2 >= 2) + { + UInt32 state2 = kLiteralNextStates[state]; + UInt32 posStateNext = (position + 1) & p->pbMask; + UInt32 nextRepMatchPrice = curAnd1Price + + GET_PRICE_1(p->isMatch[state2][posStateNext]) + + GET_PRICE_1(p->isRep[state2]); + /* for (; lenTest2 >= 2; lenTest2--) */ + { + UInt32 curAndLenPrice; + COptimal *opt; + UInt32 offset = cur + 1 + lenTest2; + while (lenEnd < offset) + p->opt[++lenEnd].price = kInfinityPrice; + curAndLenPrice = nextRepMatchPrice + GetRepPrice(p, 0, lenTest2, state2, posStateNext); + opt = &p->opt[offset]; + if (curAndLenPrice < opt->price) + { + opt->price = curAndLenPrice; + opt->posPrev = cur + 1; + opt->backPrev = 0; + opt->prev1IsChar = True; + opt->prev2 = False; + } + } + } + } + + startLen = 2; /* speed optimization */ + { + UInt32 repIndex; + for (repIndex = 0; repIndex < LZMA_NUM_REPS; repIndex++) + { + UInt32 lenTest; + UInt32 lenTestTemp; + UInt32 price; + const Byte *data2 = data - reps[repIndex] - 1; + if (data[0] != data2[0] || data[1] != data2[1]) + continue; + for (lenTest = 2; lenTest < numAvail && data[lenTest] == data2[lenTest]; lenTest++); + while (lenEnd < cur + lenTest) + p->opt[++lenEnd].price = kInfinityPrice; + lenTestTemp = lenTest; + price = repMatchPrice + GetPureRepPrice(p, repIndex, state, posState); + do + { + UInt32 curAndLenPrice = price + p->repLenEnc.prices[posState][lenTest - 2]; + COptimal *opt = &p->opt[cur + lenTest]; + if (curAndLenPrice < opt->price) + { + opt->price = curAndLenPrice; + opt->posPrev = cur; + opt->backPrev = repIndex; + opt->prev1IsChar = False; + } + } + while (--lenTest >= 2); + lenTest = lenTestTemp; + + if (repIndex == 0) + startLen = lenTest + 1; + + /* if (_maxMode) */ + { + UInt32 lenTest2 = lenTest + 1; + UInt32 limit = lenTest2 + p->numFastBytes; + if (limit > numAvailFull) + limit = numAvailFull; + for (; lenTest2 < limit && data[lenTest2] == data2[lenTest2]; lenTest2++); + lenTest2 -= lenTest + 1; + if (lenTest2 >= 2) + { + UInt32 nextRepMatchPrice; + UInt32 state2 = kRepNextStates[state]; + UInt32 posStateNext = (position + lenTest) & p->pbMask; + UInt32 curAndLenCharPrice = + price + p->repLenEnc.prices[posState][lenTest - 2] + + GET_PRICE_0(p->isMatch[state2][posStateNext]) + + LitEnc_GetPriceMatched(LIT_PROBS(position + lenTest, data[lenTest - 1]), + data[lenTest], data2[lenTest], p->ProbPrices); + state2 = kLiteralNextStates[state2]; + posStateNext = (position + lenTest + 1) & p->pbMask; + nextRepMatchPrice = curAndLenCharPrice + + GET_PRICE_1(p->isMatch[state2][posStateNext]) + + GET_PRICE_1(p->isRep[state2]); + + /* for (; lenTest2 >= 2; lenTest2--) */ + { + UInt32 curAndLenPrice; + COptimal *opt; + UInt32 offset = cur + lenTest + 1 + lenTest2; + while (lenEnd < offset) + p->opt[++lenEnd].price = kInfinityPrice; + curAndLenPrice = nextRepMatchPrice + GetRepPrice(p, 0, lenTest2, state2, posStateNext); + opt = &p->opt[offset]; + if (curAndLenPrice < opt->price) + { + opt->price = curAndLenPrice; + opt->posPrev = cur + lenTest + 1; + opt->backPrev = 0; + opt->prev1IsChar = True; + opt->prev2 = True; + opt->posPrev2 = cur; + opt->backPrev2 = repIndex; + } + } + } + } + } + } + /* for (UInt32 lenTest = 2; lenTest <= newLen; lenTest++) */ + if (newLen > numAvail) + { + newLen = numAvail; + for (numPairs = 0; newLen > matches[numPairs]; numPairs += 2); + matches[numPairs] = newLen; + numPairs += 2; + } + if (newLen >= startLen) + { + UInt32 normalMatchPrice = matchPrice + GET_PRICE_0(p->isRep[state]); + UInt32 offs, curBack, posSlot; + UInt32 lenTest; + while (lenEnd < cur + newLen) + p->opt[++lenEnd].price = kInfinityPrice; + + offs = 0; + while (startLen > matches[offs]) + offs += 2; + curBack = matches[offs + 1]; + GetPosSlot2(curBack, posSlot); + for (lenTest = /*2*/ startLen; ; lenTest++) + { + UInt32 curAndLenPrice = normalMatchPrice + p->lenEnc.prices[posState][lenTest - LZMA_MATCH_LEN_MIN]; + { + UInt32 lenToPosState = GetLenToPosState(lenTest); + COptimal *opt; + if (curBack < kNumFullDistances) + curAndLenPrice += p->distancesPrices[lenToPosState][curBack]; + else + curAndLenPrice += p->posSlotPrices[lenToPosState][posSlot] + p->alignPrices[curBack & kAlignMask]; + + opt = &p->opt[cur + lenTest]; + if (curAndLenPrice < opt->price) + { + opt->price = curAndLenPrice; + opt->posPrev = cur; + opt->backPrev = curBack + LZMA_NUM_REPS; + opt->prev1IsChar = False; + } + } + + if (/*_maxMode && */lenTest == matches[offs]) + { + /* Try Match + Literal + Rep0 */ + const Byte *data2 = data - curBack - 1; + UInt32 lenTest2 = lenTest + 1; + UInt32 limit = lenTest2 + p->numFastBytes; + if (limit > numAvailFull) + limit = numAvailFull; + for (; lenTest2 < limit && data[lenTest2] == data2[lenTest2]; lenTest2++); + lenTest2 -= lenTest + 1; + if (lenTest2 >= 2) + { + UInt32 nextRepMatchPrice; + UInt32 state2 = kMatchNextStates[state]; + UInt32 posStateNext = (position + lenTest) & p->pbMask; + UInt32 curAndLenCharPrice = curAndLenPrice + + GET_PRICE_0(p->isMatch[state2][posStateNext]) + + LitEnc_GetPriceMatched(LIT_PROBS(position + lenTest, data[lenTest - 1]), + data[lenTest], data2[lenTest], p->ProbPrices); + state2 = kLiteralNextStates[state2]; + posStateNext = (posStateNext + 1) & p->pbMask; + nextRepMatchPrice = curAndLenCharPrice + + GET_PRICE_1(p->isMatch[state2][posStateNext]) + + GET_PRICE_1(p->isRep[state2]); + + /* for (; lenTest2 >= 2; lenTest2--) */ + { + UInt32 offset = cur + lenTest + 1 + lenTest2; + UInt32 curAndLenPrice2; + COptimal *opt; + while (lenEnd < offset) + p->opt[++lenEnd].price = kInfinityPrice; + curAndLenPrice2 = nextRepMatchPrice + GetRepPrice(p, 0, lenTest2, state2, posStateNext); + opt = &p->opt[offset]; + if (curAndLenPrice2 < opt->price) + { + opt->price = curAndLenPrice2; + opt->posPrev = cur + lenTest + 1; + opt->backPrev = 0; + opt->prev1IsChar = True; + opt->prev2 = True; + opt->posPrev2 = cur; + opt->backPrev2 = curBack + LZMA_NUM_REPS; + } + } + } + offs += 2; + if (offs == numPairs) + break; + curBack = matches[offs + 1]; + if (curBack >= kNumFullDistances) + GetPosSlot2(curBack, posSlot); + } + } + } + } +} + +#define ChangePair(smallDist, bigDist) (((bigDist) >> 7) > (smallDist)) + +static UInt32 GetOptimumFast(CLzmaEnc *p, UInt32 *backRes) +{ + UInt32 numAvail, mainLen, mainDist, numPairs, repIndex, repLen, i; + const Byte *data; + const UInt32 *matches; + + if (p->additionalOffset == 0) + mainLen = ReadMatchDistances(p, &numPairs); + else + { + mainLen = p->longestMatchLength; + numPairs = p->numPairs; + } + + numAvail = p->numAvail; + *backRes = (UInt32)-1; + if (numAvail < 2) + return 1; + if (numAvail > LZMA_MATCH_LEN_MAX) + numAvail = LZMA_MATCH_LEN_MAX; + data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1; + + repLen = repIndex = 0; + for (i = 0; i < LZMA_NUM_REPS; i++) + { + UInt32 len; + const Byte *data2 = data - p->reps[i] - 1; + if (data[0] != data2[0] || data[1] != data2[1]) + continue; + for (len = 2; len < numAvail && data[len] == data2[len]; len++); + if (len >= p->numFastBytes) + { + *backRes = i; + MovePos(p, len - 1); + return len; + } + if (len > repLen) + { + repIndex = i; + repLen = len; + } + } + + matches = p->matches; + if (mainLen >= p->numFastBytes) + { + *backRes = matches[numPairs - 1] + LZMA_NUM_REPS; + MovePos(p, mainLen - 1); + return mainLen; + } + + mainDist = 0; /* for GCC */ + if (mainLen >= 2) + { + mainDist = matches[numPairs - 1]; + while (numPairs > 2 && mainLen == matches[numPairs - 4] + 1) + { + if (!ChangePair(matches[numPairs - 3], mainDist)) + break; + numPairs -= 2; + mainLen = matches[numPairs - 2]; + mainDist = matches[numPairs - 1]; + } + if (mainLen == 2 && mainDist >= 0x80) + mainLen = 1; + } + + if (repLen >= 2 && ( + (repLen + 1 >= mainLen) || + (repLen + 2 >= mainLen && mainDist >= (1 << 9)) || + (repLen + 3 >= mainLen && mainDist >= (1 << 15)))) + { + *backRes = repIndex; + MovePos(p, repLen - 1); + return repLen; + } + + if (mainLen < 2 || numAvail <= 2) + return 1; + + p->longestMatchLength = ReadMatchDistances(p, &p->numPairs); + if (p->longestMatchLength >= 2) + { + UInt32 newDistance = matches[p->numPairs - 1]; + if ((p->longestMatchLength >= mainLen && newDistance < mainDist) || + (p->longestMatchLength == mainLen + 1 && !ChangePair(mainDist, newDistance)) || + (p->longestMatchLength > mainLen + 1) || + (p->longestMatchLength + 1 >= mainLen && mainLen >= 3 && ChangePair(newDistance, mainDist))) + return 1; + } + + data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1; + for (i = 0; i < LZMA_NUM_REPS; i++) + { + UInt32 len, limit; + const Byte *data2 = data - p->reps[i] - 1; + if (data[0] != data2[0] || data[1] != data2[1]) + continue; + limit = mainLen - 1; + for (len = 2; len < limit && data[len] == data2[len]; len++); + if (len >= limit) + return 1; + } + *backRes = mainDist + LZMA_NUM_REPS; + MovePos(p, mainLen - 2); + return mainLen; +} + +static void WriteEndMarker(CLzmaEnc *p, UInt32 posState) +{ + UInt32 len; + RangeEnc_EncodeBit(&p->rc, &p->isMatch[p->state][posState], 1); + RangeEnc_EncodeBit(&p->rc, &p->isRep[p->state], 0); + p->state = kMatchNextStates[p->state]; + len = LZMA_MATCH_LEN_MIN; + LenEnc_Encode2(&p->lenEnc, &p->rc, len - LZMA_MATCH_LEN_MIN, posState, !p->fastMode, p->ProbPrices); + RcTree_Encode(&p->rc, p->posSlotEncoder[GetLenToPosState(len)], kNumPosSlotBits, (1 << kNumPosSlotBits) - 1); + RangeEnc_EncodeDirectBits(&p->rc, (((UInt32)1 << 30) - 1) >> kNumAlignBits, 30 - kNumAlignBits); + RcTree_ReverseEncode(&p->rc, p->posAlignEncoder, kNumAlignBits, kAlignMask); +} + +static SRes CheckErrors(CLzmaEnc *p) +{ + if (p->result != SZ_OK) + return p->result; + if (p->rc.res != SZ_OK) + p->result = SZ_ERROR_WRITE; + if (p->matchFinderBase.result != SZ_OK) + p->result = SZ_ERROR_READ; + if (p->result != SZ_OK) + p->finished = True; + return p->result; +} + +static SRes Flush(CLzmaEnc *p, UInt32 nowPos) +{ + /* ReleaseMFStream(); */ + p->finished = True; + if (p->writeEndMark) + WriteEndMarker(p, nowPos & p->pbMask); + RangeEnc_FlushData(&p->rc); + RangeEnc_FlushStream(&p->rc); + return CheckErrors(p); +} + +static void FillAlignPrices(CLzmaEnc *p) +{ + UInt32 i; + for (i = 0; i < kAlignTableSize; i++) + p->alignPrices[i] = RcTree_ReverseGetPrice(p->posAlignEncoder, kNumAlignBits, i, p->ProbPrices); + p->alignPriceCount = 0; +} + +static void FillDistancesPrices(CLzmaEnc *p) +{ + UInt32 tempPrices[kNumFullDistances]; + UInt32 i, lenToPosState; + for (i = kStartPosModelIndex; i < kNumFullDistances; i++) + { + UInt32 posSlot = GetPosSlot1(i); + UInt32 footerBits = ((posSlot >> 1) - 1); + UInt32 base = ((2 | (posSlot & 1)) << footerBits); + tempPrices[i] = RcTree_ReverseGetPrice(p->posEncoders + base - posSlot - 1, footerBits, i - base, p->ProbPrices); + } + + for (lenToPosState = 0; lenToPosState < kNumLenToPosStates; lenToPosState++) + { + UInt32 posSlot; + const CLzmaProb *encoder = p->posSlotEncoder[lenToPosState]; + UInt32 *posSlotPrices = p->posSlotPrices[lenToPosState]; + for (posSlot = 0; posSlot < p->distTableSize; posSlot++) + posSlotPrices[posSlot] = RcTree_GetPrice(encoder, kNumPosSlotBits, posSlot, p->ProbPrices); + for (posSlot = kEndPosModelIndex; posSlot < p->distTableSize; posSlot++) + posSlotPrices[posSlot] += ((((posSlot >> 1) - 1) - kNumAlignBits) << kNumBitPriceShiftBits); + + { + UInt32 *distancesPrices = p->distancesPrices[lenToPosState]; + for (i = 0; i < kStartPosModelIndex; i++) + distancesPrices[i] = posSlotPrices[i]; + for (; i < kNumFullDistances; i++) + distancesPrices[i] = posSlotPrices[GetPosSlot1(i)] + tempPrices[i]; + } + } + p->matchPriceCount = 0; +} + +void LzmaEnc_Construct(CLzmaEnc *p) +{ + RangeEnc_Construct(&p->rc); + MatchFinder_Construct(&p->matchFinderBase); + + #ifndef _7ZIP_ST + MatchFinderMt_Construct(&p->matchFinderMt); + p->matchFinderMt.MatchFinder = &p->matchFinderBase; + #endif + + { + CLzmaEncProps props; + LzmaEncProps_Init(&props); + LzmaEnc_SetProps(p, &props); + } + + #ifndef LZMA_LOG_BSR + LzmaEnc_FastPosInit(p->g_FastPos); + #endif + + LzmaEnc_InitPriceTables(p->ProbPrices); + p->litProbs = NULL; + p->saveState.litProbs = NULL; +} + +CLzmaEncHandle LzmaEnc_Create(ISzAlloc *alloc) +{ + void *p; + p = alloc->Alloc(alloc, sizeof(CLzmaEnc)); + if (p) + LzmaEnc_Construct((CLzmaEnc *)p); + return p; +} + +void LzmaEnc_FreeLits(CLzmaEnc *p, ISzAlloc *alloc) +{ + alloc->Free(alloc, p->litProbs); + alloc->Free(alloc, p->saveState.litProbs); + p->litProbs = NULL; + p->saveState.litProbs = NULL; +} + +void LzmaEnc_Destruct(CLzmaEnc *p, ISzAlloc *alloc, ISzAlloc *allocBig) +{ + #ifndef _7ZIP_ST + MatchFinderMt_Destruct(&p->matchFinderMt, allocBig); + #endif + + MatchFinder_Free(&p->matchFinderBase, allocBig); + LzmaEnc_FreeLits(p, alloc); + RangeEnc_Free(&p->rc, alloc); +} + +void LzmaEnc_Destroy(CLzmaEncHandle p, ISzAlloc *alloc, ISzAlloc *allocBig) +{ + LzmaEnc_Destruct((CLzmaEnc *)p, alloc, allocBig); + alloc->Free(alloc, p); +} + +static SRes LzmaEnc_CodeOneBlock(CLzmaEnc *p, Bool useLimits, UInt32 maxPackSize, UInt32 maxUnpackSize) +{ + UInt32 nowPos32, startPos32; + if (p->needInit) + { + p->matchFinder.Init(p->matchFinderObj); + p->needInit = 0; + } + + if (p->finished) + return p->result; + RINOK(CheckErrors(p)); + + nowPos32 = (UInt32)p->nowPos64; + startPos32 = nowPos32; + + if (p->nowPos64 == 0) + { + UInt32 numPairs; + Byte curByte; + if (p->matchFinder.GetNumAvailableBytes(p->matchFinderObj) == 0) + return Flush(p, nowPos32); + ReadMatchDistances(p, &numPairs); + RangeEnc_EncodeBit(&p->rc, &p->isMatch[p->state][0], 0); + p->state = kLiteralNextStates[p->state]; + curByte = *(p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - p->additionalOffset); + LitEnc_Encode(&p->rc, p->litProbs, curByte); + p->additionalOffset--; + nowPos32++; + } + + if (p->matchFinder.GetNumAvailableBytes(p->matchFinderObj) != 0) + for (;;) + { + UInt32 pos, len, posState; + + if (p->fastMode) + len = GetOptimumFast(p, &pos); + else + len = GetOptimum(p, nowPos32, &pos); + + #ifdef SHOW_STAT2 + printf("\n pos = %4X, len = %u pos = %u", nowPos32, len, pos); + #endif + + posState = nowPos32 & p->pbMask; + if (len == 1 && pos == (UInt32)-1) + { + Byte curByte; + CLzmaProb *probs; + const Byte *data; + + RangeEnc_EncodeBit(&p->rc, &p->isMatch[p->state][posState], 0); + data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - p->additionalOffset; + curByte = *data; + probs = LIT_PROBS(nowPos32, *(data - 1)); + if (IsCharState(p->state)) + LitEnc_Encode(&p->rc, probs, curByte); + else + LitEnc_EncodeMatched(&p->rc, probs, curByte, *(data - p->reps[0] - 1)); + p->state = kLiteralNextStates[p->state]; + } + else + { + RangeEnc_EncodeBit(&p->rc, &p->isMatch[p->state][posState], 1); + if (pos < LZMA_NUM_REPS) + { + RangeEnc_EncodeBit(&p->rc, &p->isRep[p->state], 1); + if (pos == 0) + { + RangeEnc_EncodeBit(&p->rc, &p->isRepG0[p->state], 0); + RangeEnc_EncodeBit(&p->rc, &p->isRep0Long[p->state][posState], ((len == 1) ? 0 : 1)); + } + else + { + UInt32 distance = p->reps[pos]; + RangeEnc_EncodeBit(&p->rc, &p->isRepG0[p->state], 1); + if (pos == 1) + RangeEnc_EncodeBit(&p->rc, &p->isRepG1[p->state], 0); + else + { + RangeEnc_EncodeBit(&p->rc, &p->isRepG1[p->state], 1); + RangeEnc_EncodeBit(&p->rc, &p->isRepG2[p->state], pos - 2); + if (pos == 3) + p->reps[3] = p->reps[2]; + p->reps[2] = p->reps[1]; + } + p->reps[1] = p->reps[0]; + p->reps[0] = distance; + } + if (len == 1) + p->state = kShortRepNextStates[p->state]; + else + { + LenEnc_Encode2(&p->repLenEnc, &p->rc, len - LZMA_MATCH_LEN_MIN, posState, !p->fastMode, p->ProbPrices); + p->state = kRepNextStates[p->state]; + } + } + else + { + UInt32 posSlot; + RangeEnc_EncodeBit(&p->rc, &p->isRep[p->state], 0); + p->state = kMatchNextStates[p->state]; + LenEnc_Encode2(&p->lenEnc, &p->rc, len - LZMA_MATCH_LEN_MIN, posState, !p->fastMode, p->ProbPrices); + pos -= LZMA_NUM_REPS; + GetPosSlot(pos, posSlot); + RcTree_Encode(&p->rc, p->posSlotEncoder[GetLenToPosState(len)], kNumPosSlotBits, posSlot); + + if (posSlot >= kStartPosModelIndex) + { + UInt32 footerBits = ((posSlot >> 1) - 1); + UInt32 base = ((2 | (posSlot & 1)) << footerBits); + UInt32 posReduced = pos - base; + + if (posSlot < kEndPosModelIndex) + RcTree_ReverseEncode(&p->rc, p->posEncoders + base - posSlot - 1, footerBits, posReduced); + else + { + RangeEnc_EncodeDirectBits(&p->rc, posReduced >> kNumAlignBits, footerBits - kNumAlignBits); + RcTree_ReverseEncode(&p->rc, p->posAlignEncoder, kNumAlignBits, posReduced & kAlignMask); + p->alignPriceCount++; + } + } + p->reps[3] = p->reps[2]; + p->reps[2] = p->reps[1]; + p->reps[1] = p->reps[0]; + p->reps[0] = pos; + p->matchPriceCount++; + } + } + p->additionalOffset -= len; + nowPos32 += len; + if (p->additionalOffset == 0) + { + UInt32 processed; + if (!p->fastMode) + { + if (p->matchPriceCount >= (1 << 7)) + FillDistancesPrices(p); + if (p->alignPriceCount >= kAlignTableSize) + FillAlignPrices(p); + } + if (p->matchFinder.GetNumAvailableBytes(p->matchFinderObj) == 0) + break; + processed = nowPos32 - startPos32; + if (useLimits) + { + if (processed + kNumOpts + 300 >= maxUnpackSize || + RangeEnc_GetProcessed(&p->rc) + kNumOpts * 2 >= maxPackSize) + break; + } + else if (processed >= (1 << 17)) + { + p->nowPos64 += nowPos32 - startPos32; + return CheckErrors(p); + } + } + } + p->nowPos64 += nowPos32 - startPos32; + return Flush(p, nowPos32); +} + +#define kBigHashDicLimit ((UInt32)1 << 24) + +static SRes LzmaEnc_Alloc(CLzmaEnc *p, UInt32 keepWindowSize, ISzAlloc *alloc, ISzAlloc *allocBig) +{ + UInt32 beforeSize = kNumOpts; + if (!RangeEnc_Alloc(&p->rc, alloc)) + return SZ_ERROR_MEM; + + #ifndef _7ZIP_ST + p->mtMode = (p->multiThread && !p->fastMode && (p->matchFinderBase.btMode != 0)); + #endif + + { + unsigned lclp = p->lc + p->lp; + if (!p->litProbs || !p->saveState.litProbs || p->lclp != lclp) + { + LzmaEnc_FreeLits(p, alloc); + p->litProbs = (CLzmaProb *)alloc->Alloc(alloc, ((UInt32)0x300 << lclp) * sizeof(CLzmaProb)); + p->saveState.litProbs = (CLzmaProb *)alloc->Alloc(alloc, ((UInt32)0x300 << lclp) * sizeof(CLzmaProb)); + if (!p->litProbs || !p->saveState.litProbs) + { + LzmaEnc_FreeLits(p, alloc); + return SZ_ERROR_MEM; + } + p->lclp = lclp; + } + } + + p->matchFinderBase.bigHash = (Byte)(p->dictSize > kBigHashDicLimit ? 1 : 0); + + if (beforeSize + p->dictSize < keepWindowSize) + beforeSize = keepWindowSize - p->dictSize; + + #ifndef _7ZIP_ST + if (p->mtMode) + { + RINOK(MatchFinderMt_Create(&p->matchFinderMt, p->dictSize, beforeSize, p->numFastBytes, LZMA_MATCH_LEN_MAX, allocBig)); + p->matchFinderObj = &p->matchFinderMt; + MatchFinderMt_CreateVTable(&p->matchFinderMt, &p->matchFinder); + } + else + #endif + { + if (!MatchFinder_Create(&p->matchFinderBase, p->dictSize, beforeSize, p->numFastBytes, LZMA_MATCH_LEN_MAX, allocBig)) + return SZ_ERROR_MEM; + p->matchFinderObj = &p->matchFinderBase; + MatchFinder_CreateVTable(&p->matchFinderBase, &p->matchFinder); + } + + return SZ_OK; +} + +void LzmaEnc_Init(CLzmaEnc *p) +{ + UInt32 i; + p->state = 0; + for (i = 0 ; i < LZMA_NUM_REPS; i++) + p->reps[i] = 0; + + RangeEnc_Init(&p->rc); + + + for (i = 0; i < kNumStates; i++) + { + UInt32 j; + for (j = 0; j < LZMA_NUM_PB_STATES_MAX; j++) + { + p->isMatch[i][j] = kProbInitValue; + p->isRep0Long[i][j] = kProbInitValue; + } + p->isRep[i] = kProbInitValue; + p->isRepG0[i] = kProbInitValue; + p->isRepG1[i] = kProbInitValue; + p->isRepG2[i] = kProbInitValue; + } + + { + UInt32 num = (UInt32)0x300 << (p->lp + p->lc); + CLzmaProb *probs = p->litProbs; + for (i = 0; i < num; i++) + probs[i] = kProbInitValue; + } + + { + for (i = 0; i < kNumLenToPosStates; i++) + { + CLzmaProb *probs = p->posSlotEncoder[i]; + UInt32 j; + for (j = 0; j < (1 << kNumPosSlotBits); j++) + probs[j] = kProbInitValue; + } + } + { + for (i = 0; i < kNumFullDistances - kEndPosModelIndex; i++) + p->posEncoders[i] = kProbInitValue; + } + + LenEnc_Init(&p->lenEnc.p); + LenEnc_Init(&p->repLenEnc.p); + + for (i = 0; i < (1 << kNumAlignBits); i++) + p->posAlignEncoder[i] = kProbInitValue; + + p->optimumEndIndex = 0; + p->optimumCurrentIndex = 0; + p->additionalOffset = 0; + + p->pbMask = (1 << p->pb) - 1; + p->lpMask = (1 << p->lp) - 1; +} + +void LzmaEnc_InitPrices(CLzmaEnc *p) +{ + if (!p->fastMode) + { + FillDistancesPrices(p); + FillAlignPrices(p); + } + + p->lenEnc.tableSize = + p->repLenEnc.tableSize = + p->numFastBytes + 1 - LZMA_MATCH_LEN_MIN; + LenPriceEnc_UpdateTables(&p->lenEnc, 1 << p->pb, p->ProbPrices); + LenPriceEnc_UpdateTables(&p->repLenEnc, 1 << p->pb, p->ProbPrices); +} + +static SRes LzmaEnc_AllocAndInit(CLzmaEnc *p, UInt32 keepWindowSize, ISzAlloc *alloc, ISzAlloc *allocBig) +{ + UInt32 i; + for (i = 0; i < (UInt32)kDicLogSizeMaxCompress; i++) + if (p->dictSize <= ((UInt32)1 << i)) + break; + p->distTableSize = i * 2; + + p->finished = False; + p->result = SZ_OK; + RINOK(LzmaEnc_Alloc(p, keepWindowSize, alloc, allocBig)); + LzmaEnc_Init(p); + LzmaEnc_InitPrices(p); + p->nowPos64 = 0; + return SZ_OK; +} + +static SRes LzmaEnc_Prepare(CLzmaEncHandle pp, ISeqOutStream *outStream, ISeqInStream *inStream, + ISzAlloc *alloc, ISzAlloc *allocBig) +{ + CLzmaEnc *p = (CLzmaEnc *)pp; + p->matchFinderBase.stream = inStream; + p->needInit = 1; + p->rc.outStream = outStream; + return LzmaEnc_AllocAndInit(p, 0, alloc, allocBig); +} + +SRes LzmaEnc_PrepareForLzma2(CLzmaEncHandle pp, + ISeqInStream *inStream, UInt32 keepWindowSize, + ISzAlloc *alloc, ISzAlloc *allocBig) +{ + CLzmaEnc *p = (CLzmaEnc *)pp; + p->matchFinderBase.stream = inStream; + p->needInit = 1; + return LzmaEnc_AllocAndInit(p, keepWindowSize, alloc, allocBig); +} + +static void LzmaEnc_SetInputBuf(CLzmaEnc *p, const Byte *src, SizeT srcLen) +{ + p->matchFinderBase.directInput = 1; + p->matchFinderBase.bufferBase = (Byte *)src; + p->matchFinderBase.directInputRem = srcLen; +} + +SRes LzmaEnc_MemPrepare(CLzmaEncHandle pp, const Byte *src, SizeT srcLen, + UInt32 keepWindowSize, ISzAlloc *alloc, ISzAlloc *allocBig) +{ + CLzmaEnc *p = (CLzmaEnc *)pp; + LzmaEnc_SetInputBuf(p, src, srcLen); + p->needInit = 1; + + return LzmaEnc_AllocAndInit(p, keepWindowSize, alloc, allocBig); +} + +void LzmaEnc_Finish(CLzmaEncHandle pp) +{ + #ifndef _7ZIP_ST + CLzmaEnc *p = (CLzmaEnc *)pp; + if (p->mtMode) + MatchFinderMt_ReleaseStream(&p->matchFinderMt); + #else + UNUSED_VAR(pp); + #endif +} + + +typedef struct +{ + ISeqOutStream funcTable; + Byte *data; + SizeT rem; + Bool overflow; +} CSeqOutStreamBuf; + +static size_t MyWrite(void *pp, const void *data, size_t size) +{ + CSeqOutStreamBuf *p = (CSeqOutStreamBuf *)pp; + if (p->rem < size) + { + size = p->rem; + p->overflow = True; + } + memcpy(p->data, data, size); + p->rem -= size; + p->data += size; + return size; +} + + +UInt32 LzmaEnc_GetNumAvailableBytes(CLzmaEncHandle pp) +{ + const CLzmaEnc *p = (CLzmaEnc *)pp; + return p->matchFinder.GetNumAvailableBytes(p->matchFinderObj); +} + + +const Byte *LzmaEnc_GetCurBuf(CLzmaEncHandle pp) +{ + const CLzmaEnc *p = (CLzmaEnc *)pp; + return p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - p->additionalOffset; +} + + +SRes LzmaEnc_CodeOneMemBlock(CLzmaEncHandle pp, Bool reInit, + Byte *dest, size_t *destLen, UInt32 desiredPackSize, UInt32 *unpackSize) +{ + CLzmaEnc *p = (CLzmaEnc *)pp; + UInt64 nowPos64; + SRes res; + CSeqOutStreamBuf outStream; + + outStream.funcTable.Write = MyWrite; + outStream.data = dest; + outStream.rem = *destLen; + outStream.overflow = False; + + p->writeEndMark = False; + p->finished = False; + p->result = SZ_OK; + + if (reInit) + LzmaEnc_Init(p); + LzmaEnc_InitPrices(p); + nowPos64 = p->nowPos64; + RangeEnc_Init(&p->rc); + p->rc.outStream = &outStream.funcTable; + + res = LzmaEnc_CodeOneBlock(p, True, desiredPackSize, *unpackSize); + + *unpackSize = (UInt32)(p->nowPos64 - nowPos64); + *destLen -= outStream.rem; + if (outStream.overflow) + return SZ_ERROR_OUTPUT_EOF; + + return res; +} + + +static SRes LzmaEnc_Encode2(CLzmaEnc *p, ICompressProgress *progress) +{ + SRes res = SZ_OK; + + #ifndef _7ZIP_ST + Byte allocaDummy[0x300]; + allocaDummy[0] = 0; + allocaDummy[1] = allocaDummy[0]; + #endif + + for (;;) + { + res = LzmaEnc_CodeOneBlock(p, False, 0, 0); + if (res != SZ_OK || p->finished) + break; + if (progress) + { + res = progress->Progress(progress, p->nowPos64, RangeEnc_GetProcessed(&p->rc)); + if (res != SZ_OK) + { + res = SZ_ERROR_PROGRESS; + break; + } + } + } + + LzmaEnc_Finish(p); + + /* + if (res == S_OK && !Inline_MatchFinder_IsFinishedOK(&p->matchFinderBase)) + res = SZ_ERROR_FAIL; + } + */ + + return res; +} + + +SRes LzmaEnc_Encode(CLzmaEncHandle pp, ISeqOutStream *outStream, ISeqInStream *inStream, ICompressProgress *progress, + ISzAlloc *alloc, ISzAlloc *allocBig) +{ + RINOK(LzmaEnc_Prepare(pp, outStream, inStream, alloc, allocBig)); + return LzmaEnc_Encode2((CLzmaEnc *)pp, progress); +} + + +SRes LzmaEnc_WriteProperties(CLzmaEncHandle pp, Byte *props, SizeT *size) +{ + CLzmaEnc *p = (CLzmaEnc *)pp; + unsigned i; + UInt32 dictSize = p->dictSize; + if (*size < LZMA_PROPS_SIZE) + return SZ_ERROR_PARAM; + *size = LZMA_PROPS_SIZE; + props[0] = (Byte)((p->pb * 5 + p->lp) * 9 + p->lc); + + if (dictSize >= ((UInt32)1 << 22)) + { + UInt32 kDictMask = ((UInt32)1 << 20) - 1; + if (dictSize < (UInt32)0xFFFFFFFF - kDictMask) + dictSize = (dictSize + kDictMask) & ~kDictMask; + } + else for (i = 11; i <= 30; i++) + { + if (dictSize <= ((UInt32)2 << i)) { dictSize = (2 << i); break; } + if (dictSize <= ((UInt32)3 << i)) { dictSize = (3 << i); break; } + } + + for (i = 0; i < 4; i++) + props[1 + i] = (Byte)(dictSize >> (8 * i)); + return SZ_OK; +} + + +SRes LzmaEnc_MemEncode(CLzmaEncHandle pp, Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen, + int writeEndMark, ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig) +{ + SRes res; + CLzmaEnc *p = (CLzmaEnc *)pp; + + CSeqOutStreamBuf outStream; + + outStream.funcTable.Write = MyWrite; + outStream.data = dest; + outStream.rem = *destLen; + outStream.overflow = False; + + p->writeEndMark = writeEndMark; + p->rc.outStream = &outStream.funcTable; + + res = LzmaEnc_MemPrepare(pp, src, srcLen, 0, alloc, allocBig); + + if (res == SZ_OK) + { + res = LzmaEnc_Encode2(p, progress); + if (res == SZ_OK && p->nowPos64 != srcLen) + res = SZ_ERROR_FAIL; + } + + *destLen -= outStream.rem; + if (outStream.overflow) + return SZ_ERROR_OUTPUT_EOF; + return res; +} + + +SRes LzmaEncode(Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen, + const CLzmaEncProps *props, Byte *propsEncoded, SizeT *propsSize, int writeEndMark, + ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig) +{ + CLzmaEnc *p = (CLzmaEnc *)LzmaEnc_Create(alloc); + SRes res; + if (!p) + return SZ_ERROR_MEM; + + res = LzmaEnc_SetProps(p, props); + if (res == SZ_OK) + { + res = LzmaEnc_WriteProperties(p, propsEncoded, propsSize); + if (res == SZ_OK) + res = LzmaEnc_MemEncode(p, dest, destLen, src, srcLen, + writeEndMark, progress, alloc, allocBig); + } + + LzmaEnc_Destroy(p, alloc, allocBig); + return res; +} diff --git a/libCompression/LzmaEnc.h b/libCompression/LzmaEnc.h new file mode 100644 index 0000000..cffe220 --- /dev/null +++ b/libCompression/LzmaEnc.h @@ -0,0 +1,78 @@ +/* LzmaEnc.h -- LZMA Encoder +2013-01-18 : Igor Pavlov : Public domain */ + +#ifndef __LZMA_ENC_H +#define __LZMA_ENC_H + +#include "7zTypes.h" + +EXTERN_C_BEGIN + +#define LZMA_PROPS_SIZE 5 + +typedef struct _CLzmaEncProps +{ + int level; /* 0 <= level <= 9 */ + UInt32 dictSize; /* (1 << 12) <= dictSize <= (1 << 27) for 32-bit version + (1 << 12) <= dictSize <= (1 << 30) for 64-bit version + default = (1 << 24) */ + UInt64 reduceSize; /* estimated size of data that will be compressed. default = 0xFFFFFFFF. + Encoder uses this value to reduce dictionary size */ + int lc; /* 0 <= lc <= 8, default = 3 */ + int lp; /* 0 <= lp <= 4, default = 0 */ + int pb; /* 0 <= pb <= 4, default = 2 */ + int algo; /* 0 - fast, 1 - normal, default = 1 */ + int fb; /* 5 <= fb <= 273, default = 32 */ + int btMode; /* 0 - hashChain Mode, 1 - binTree mode - normal, default = 1 */ + int numHashBytes; /* 2, 3 or 4, default = 4 */ + UInt32 mc; /* 1 <= mc <= (1 << 30), default = 32 */ + unsigned writeEndMark; /* 0 - do not write EOPM, 1 - write EOPM, default = 0 */ + int numThreads; /* 1 or 2, default = 2 */ +} CLzmaEncProps; + +void LzmaEncProps_Init(CLzmaEncProps *p); +void LzmaEncProps_Normalize(CLzmaEncProps *p); +UInt32 LzmaEncProps_GetDictSize(const CLzmaEncProps *props2); + + +/* ---------- CLzmaEncHandle Interface ---------- */ + +/* LzmaEnc_* functions can return the following exit codes: +Returns: + SZ_OK - OK + SZ_ERROR_MEM - Memory allocation error + SZ_ERROR_PARAM - Incorrect paramater in props + SZ_ERROR_WRITE - Write callback error. + SZ_ERROR_PROGRESS - some break from progress callback + SZ_ERROR_THREAD - errors in multithreading functions (only for Mt version) +*/ + +typedef void * CLzmaEncHandle; + +CLzmaEncHandle LzmaEnc_Create(ISzAlloc *alloc); +void LzmaEnc_Destroy(CLzmaEncHandle p, ISzAlloc *alloc, ISzAlloc *allocBig); +SRes LzmaEnc_SetProps(CLzmaEncHandle p, const CLzmaEncProps *props); +SRes LzmaEnc_WriteProperties(CLzmaEncHandle p, Byte *properties, SizeT *size); +SRes LzmaEnc_Encode(CLzmaEncHandle p, ISeqOutStream *outStream, ISeqInStream *inStream, + ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig); +SRes LzmaEnc_MemEncode(CLzmaEncHandle p, Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen, + int writeEndMark, ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig); + +/* ---------- One Call Interface ---------- */ + +/* LzmaEncode +Return code: + SZ_OK - OK + SZ_ERROR_MEM - Memory allocation error + SZ_ERROR_PARAM - Incorrect paramater + SZ_ERROR_OUTPUT_EOF - output buffer overflow + SZ_ERROR_THREAD - errors in multithreading functions (only for Mt version) +*/ + +SRes LzmaEncode(Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen, + const CLzmaEncProps *props, Byte *propsEncoded, SizeT *propsSize, int writeEndMark, + ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig); + +EXTERN_C_END + +#endif diff --git a/libCompression/Precomp.h b/libCompression/Precomp.h new file mode 100644 index 0000000..e8ff8b4 --- /dev/null +++ b/libCompression/Precomp.h @@ -0,0 +1,10 @@ +/* Precomp.h -- StdAfx +2013-11-12 : Igor Pavlov : Public domain */ + +#ifndef __7Z_PRECOMP_H +#define __7Z_PRECOMP_H + +#include "Compiler.h" +/* #include "7zTypes.h" */ + +#endif diff --git a/libCompression/Threads.c b/libCompression/Threads.c new file mode 100644 index 0000000..d3d0912 --- /dev/null +++ b/libCompression/Threads.c @@ -0,0 +1,93 @@ +/* Threads.c -- multithreading library +2014-09-21 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#ifndef UNDER_CE +#include +#endif + +#include "Threads.h" + +static WRes GetError() +{ + DWORD res = GetLastError(); + return (res) ? (WRes)(res) : 1; +} + +WRes HandleToWRes(HANDLE h) { return (h != 0) ? 0 : GetError(); } +WRes BOOLToWRes(BOOL v) { return v ? 0 : GetError(); } + +WRes HandlePtr_Close(HANDLE *p) +{ + if (*p != NULL) + if (!CloseHandle(*p)) + return GetError(); + *p = NULL; + return 0; +} + +WRes Handle_WaitObject(HANDLE h) { return (WRes)WaitForSingleObject(h, INFINITE); } + +WRes Thread_Create(CThread *p, THREAD_FUNC_TYPE func, LPVOID param) +{ + /* Windows Me/98/95: threadId parameter may not be NULL in _beginthreadex/CreateThread functions */ + + #ifdef UNDER_CE + + DWORD threadId; + *p = CreateThread(0, 0, func, param, 0, &threadId); + + #else + + unsigned threadId; + *p = (HANDLE)_beginthreadex(NULL, 0, func, param, 0, &threadId); + + #endif + + /* maybe we must use errno here, but probably GetLastError() is also OK. */ + return HandleToWRes(*p); +} + +WRes Event_Create(CEvent *p, BOOL manualReset, int signaled) +{ + *p = CreateEvent(NULL, manualReset, (signaled ? TRUE : FALSE), NULL); + return HandleToWRes(*p); +} + +WRes Event_Set(CEvent *p) { return BOOLToWRes(SetEvent(*p)); } +WRes Event_Reset(CEvent *p) { return BOOLToWRes(ResetEvent(*p)); } + +WRes ManualResetEvent_Create(CManualResetEvent *p, int signaled) { return Event_Create(p, TRUE, signaled); } +WRes AutoResetEvent_Create(CAutoResetEvent *p, int signaled) { return Event_Create(p, FALSE, signaled); } +WRes ManualResetEvent_CreateNotSignaled(CManualResetEvent *p) { return ManualResetEvent_Create(p, 0); } +WRes AutoResetEvent_CreateNotSignaled(CAutoResetEvent *p) { return AutoResetEvent_Create(p, 0); } + + +WRes Semaphore_Create(CSemaphore *p, UInt32 initCount, UInt32 maxCount) +{ + *p = CreateSemaphore(NULL, (LONG)initCount, (LONG)maxCount, NULL); + return HandleToWRes(*p); +} + +static WRes Semaphore_Release(CSemaphore *p, LONG releaseCount, LONG *previousCount) + { return BOOLToWRes(ReleaseSemaphore(*p, releaseCount, previousCount)); } +WRes Semaphore_ReleaseN(CSemaphore *p, UInt32 num) + { return Semaphore_Release(p, (LONG)num, NULL); } +WRes Semaphore_Release1(CSemaphore *p) { return Semaphore_ReleaseN(p, 1); } + +WRes CriticalSection_Init(CCriticalSection *p) +{ + /* InitializeCriticalSection can raise only STATUS_NO_MEMORY exception */ + #ifdef _MSC_VER + __try + #endif + { + InitializeCriticalSection(p); + /* InitializeCriticalSectionAndSpinCount(p, 0); */ + } + #ifdef _MSC_VER + __except (EXCEPTION_EXECUTE_HANDLER) { return 1; } + #endif + return 0; +} diff --git a/libCompression/Threads.h b/libCompression/Threads.h new file mode 100644 index 0000000..9b3e1c5 --- /dev/null +++ b/libCompression/Threads.h @@ -0,0 +1,67 @@ +/* Threads.h -- multithreading library +2013-11-12 : Igor Pavlov : Public domain */ + +#ifndef __7Z_THREADS_H +#define __7Z_THREADS_H + +#ifdef _WIN32 +#include +#endif + +#include "7zTypes.h" + +EXTERN_C_BEGIN + +WRes HandlePtr_Close(HANDLE *h); +WRes Handle_WaitObject(HANDLE h); + +typedef HANDLE CThread; +#define Thread_Construct(p) *(p) = NULL +#define Thread_WasCreated(p) (*(p) != NULL) +#define Thread_Close(p) HandlePtr_Close(p) +#define Thread_Wait(p) Handle_WaitObject(*(p)) + +typedef +#ifdef UNDER_CE + DWORD +#else + unsigned +#endif + THREAD_FUNC_RET_TYPE; + +#define THREAD_FUNC_CALL_TYPE MY_STD_CALL +#define THREAD_FUNC_DECL THREAD_FUNC_RET_TYPE THREAD_FUNC_CALL_TYPE +typedef THREAD_FUNC_RET_TYPE (THREAD_FUNC_CALL_TYPE * THREAD_FUNC_TYPE)(void *); +WRes Thread_Create(CThread *p, THREAD_FUNC_TYPE func, LPVOID param); + +typedef HANDLE CEvent; +typedef CEvent CAutoResetEvent; +typedef CEvent CManualResetEvent; +#define Event_Construct(p) *(p) = NULL +#define Event_IsCreated(p) (*(p) != NULL) +#define Event_Close(p) HandlePtr_Close(p) +#define Event_Wait(p) Handle_WaitObject(*(p)) +WRes Event_Set(CEvent *p); +WRes Event_Reset(CEvent *p); +WRes ManualResetEvent_Create(CManualResetEvent *p, int signaled); +WRes ManualResetEvent_CreateNotSignaled(CManualResetEvent *p); +WRes AutoResetEvent_Create(CAutoResetEvent *p, int signaled); +WRes AutoResetEvent_CreateNotSignaled(CAutoResetEvent *p); + +typedef HANDLE CSemaphore; +#define Semaphore_Construct(p) (*p) = NULL +#define Semaphore_Close(p) HandlePtr_Close(p) +#define Semaphore_Wait(p) Handle_WaitObject(*(p)) +WRes Semaphore_Create(CSemaphore *p, UInt32 initCount, UInt32 maxCount); +WRes Semaphore_ReleaseN(CSemaphore *p, UInt32 num); +WRes Semaphore_Release1(CSemaphore *p); + +typedef CRITICAL_SECTION CCriticalSection; +WRes CriticalSection_Init(CCriticalSection *p); +#define CriticalSection_Delete(p) DeleteCriticalSection(p) +#define CriticalSection_Enter(p) EnterCriticalSection(p) +#define CriticalSection_Leave(p) LeaveCriticalSection(p) + +EXTERN_C_END + +#endif diff --git a/libCompression/api.h b/libCompression/api.h new file mode 100644 index 0000000..bfabd5d --- /dev/null +++ b/libCompression/api.h @@ -0,0 +1,8 @@ +#pragma once + +#define LIBCOMPRESSION_API +#ifdef _LIBCOMPRESSION_EXPORT +//#define LIBCOMPRESSION_API __declspec(dllexport) +#else +//#define LIBCOMPRESSION_API __declspec(dllimport) +#endif \ No newline at end of file diff --git a/libCompression/lz4.c b/libCompression/lz4.c new file mode 100644 index 0000000..5499547 --- /dev/null +++ b/libCompression/lz4.c @@ -0,0 +1,2519 @@ +/* + LZ4 - Fast LZ compression algorithm + Copyright (C) 2011-2020, Yann Collet. + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 homepage : http://www.lz4.org + - LZ4 source repository : https://github.com/lz4/lz4 +*/ + +/*-************************************ +* Tuning parameters +**************************************/ +/* + * LZ4_HEAPMODE : + * Select how default compression functions will allocate memory for their hash table, + * in memory stack (0:default, fastest), or in memory heap (1:requires malloc()). + */ +#ifndef LZ4_HEAPMODE +# define LZ4_HEAPMODE 0 +#endif + +/* + * LZ4_ACCELERATION_DEFAULT : + * Select "acceleration" for LZ4_compress_fast() when parameter value <= 0 + */ +#define LZ4_ACCELERATION_DEFAULT 1 +/* + * LZ4_ACCELERATION_MAX : + * Any "acceleration" value higher than this threshold + * get treated as LZ4_ACCELERATION_MAX instead (fix #876) + */ +#define LZ4_ACCELERATION_MAX 65537 + + +/*-************************************ +* CPU Feature Detection +**************************************/ +/* LZ4_FORCE_MEMORY_ACCESS + * By default, access to unaligned memory is controlled by `memcpy()`, which is safe and portable. + * Unfortunately, on some target/compiler combinations, the generated assembly is sub-optimal. + * The below switch allow to select different access method for improved performance. + * Method 0 (default) : use `memcpy()`. Safe and portable. + * Method 1 : `__packed` statement. It depends on compiler extension (ie, not portable). + * This method is safe if your compiler supports it, and *generally* as fast or faster than `memcpy`. + * Method 2 : direct access. This method is portable but violate C standard. + * It can generate buggy code on targets which assembly generation depends on alignment. + * But in some circumstances, it's the only known way to get the most performance (ie GCC + ARMv6) + * See https://fastcompression.blogspot.fr/2015/08/accessing-unaligned-memory.html for details. + * Prefer these methods in priority order (0 > 1 > 2) + */ +#ifndef LZ4_FORCE_MEMORY_ACCESS /* can be defined externally */ +# if defined(__GNUC__) && \ + ( defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) \ + || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) ) +# define LZ4_FORCE_MEMORY_ACCESS 2 +# elif (defined(__INTEL_COMPILER) && !defined(_WIN32)) || defined(__GNUC__) +# define LZ4_FORCE_MEMORY_ACCESS 1 +# endif +#endif + +/* + * LZ4_FORCE_SW_BITCOUNT + * Define this parameter if your target system or compiler does not support hardware bit count + */ +#if defined(_MSC_VER) && defined(_WIN32_WCE) /* Visual Studio for WinCE doesn't support Hardware bit count */ +# undef LZ4_FORCE_SW_BITCOUNT /* avoid double def */ +# define LZ4_FORCE_SW_BITCOUNT +#endif + + + +/*-************************************ +* Dependency +**************************************/ +/* + * LZ4_SRC_INCLUDED: + * Amalgamation flag, whether lz4.c is included + */ +#ifndef LZ4_SRC_INCLUDED +# define LZ4_SRC_INCLUDED 1 +#endif + +#ifndef LZ4_STATIC_LINKING_ONLY +#define LZ4_STATIC_LINKING_ONLY +#endif + +#ifndef LZ4_DISABLE_DEPRECATE_WARNINGS +#define LZ4_DISABLE_DEPRECATE_WARNINGS /* due to LZ4_decompress_safe_withPrefix64k */ +#endif + +#define LZ4_STATIC_LINKING_ONLY /* LZ4_DISTANCE_MAX */ +#include "lz4.h" +/* see also "memory routines" below */ + + +/*-************************************ +* Compiler Options +**************************************/ +#if defined(_MSC_VER) && (_MSC_VER >= 1400) /* Visual Studio 2005+ */ +# include /* only present in VS2005+ */ +# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ +#endif /* _MSC_VER */ + +#ifndef LZ4_FORCE_INLINE +# ifdef _MSC_VER /* Visual Studio */ +# define LZ4_FORCE_INLINE static __forceinline +# else +# if defined (__cplusplus) || defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */ +# ifdef __GNUC__ +# define LZ4_FORCE_INLINE static inline __attribute__((always_inline)) +# else +# define LZ4_FORCE_INLINE static inline +# endif +# else +# define LZ4_FORCE_INLINE static +# endif /* __STDC_VERSION__ */ +# endif /* _MSC_VER */ +#endif /* LZ4_FORCE_INLINE */ + +/* LZ4_FORCE_O2 and LZ4_FORCE_INLINE + * gcc on ppc64le generates an unrolled SIMDized loop for LZ4_wildCopy8, + * together with a simple 8-byte copy loop as a fall-back path. + * However, this optimization hurts the decompression speed by >30%, + * because the execution does not go to the optimized loop + * for typical compressible data, and all of the preamble checks + * before going to the fall-back path become useless overhead. + * This optimization happens only with the -O3 flag, and -O2 generates + * a simple 8-byte copy loop. + * With gcc on ppc64le, all of the LZ4_decompress_* and LZ4_wildCopy8 + * functions are annotated with __attribute__((optimize("O2"))), + * and also LZ4_wildCopy8 is forcibly inlined, so that the O2 attribute + * of LZ4_wildCopy8 does not affect the compression speed. + */ +#if defined(__PPC64__) && defined(__LITTLE_ENDIAN__) && defined(__GNUC__) && !defined(__clang__) +# define LZ4_FORCE_O2 __attribute__((optimize("O2"))) +# undef LZ4_FORCE_INLINE +# define LZ4_FORCE_INLINE static __inline __attribute__((optimize("O2"),always_inline)) +#else +# define LZ4_FORCE_O2 +#endif + +#if (defined(__GNUC__) && (__GNUC__ >= 3)) || (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 800)) || defined(__clang__) +# define expect(expr,value) (__builtin_expect ((expr),(value)) ) +#else +# define expect(expr,value) (expr) +#endif + +#ifndef likely +#define likely(expr) expect((expr) != 0, 1) +#endif +#ifndef unlikely +#define unlikely(expr) expect((expr) != 0, 0) +#endif + +/* Should the alignment test prove unreliable, for some reason, + * it can be disabled by setting LZ4_ALIGN_TEST to 0 */ +#ifndef LZ4_ALIGN_TEST /* can be externally provided */ +# define LZ4_ALIGN_TEST 1 +#endif + + +/*-************************************ +* Memory routines +**************************************/ +#ifdef LZ4_USER_MEMORY_FUNCTIONS +/* memory management functions can be customized by user project. + * Below functions must exist somewhere in the Project + * and be available at link time */ +void* LZ4_malloc(size_t s); +void* LZ4_calloc(size_t n, size_t s); +void LZ4_free(void* p); +# define ALLOC(s) LZ4_malloc(s) +# define ALLOC_AND_ZERO(s) LZ4_calloc(1,s) +# define FREEMEM(p) LZ4_free(p) +#else +# include /* malloc, calloc, free */ +# define ALLOC(s) malloc(s) +# define ALLOC_AND_ZERO(s) calloc(1,s) +# define FREEMEM(p) free(p) +#endif + +#include /* memset, memcpy */ +#define MEM_INIT(p,v,s) memset((p),(v),(s)) + + +/*-************************************ +* Common Constants +**************************************/ +#define MINMATCH 4 + +#define WILDCOPYLENGTH 8 +#define LASTLITERALS 5 /* see ../doc/lz4_Block_format.md#parsing-restrictions */ +#define MFLIMIT 12 /* see ../doc/lz4_Block_format.md#parsing-restrictions */ +#define MATCH_SAFEGUARD_DISTANCE ((2*WILDCOPYLENGTH) - MINMATCH) /* ensure it's possible to write 2 x wildcopyLength without overflowing output buffer */ +#define FASTLOOP_SAFE_DISTANCE 64 +static const int LZ4_minLength = (MFLIMIT+1); + +#define KB *(1 <<10) +#define MB *(1 <<20) +#define GB *(1U<<30) + +#define LZ4_DISTANCE_ABSOLUTE_MAX 65535 +#if (LZ4_DISTANCE_MAX > LZ4_DISTANCE_ABSOLUTE_MAX) /* max supported by LZ4 format */ +# error "LZ4_DISTANCE_MAX is too big : must be <= 65535" +#endif + +#define ML_BITS 4 +#define ML_MASK ((1U<=1) +# include +#else +# ifndef assert +# define assert(condition) ((void)0) +# endif +#endif + +#define LZ4_STATIC_ASSERT(c) { enum { LZ4_static_assert = 1/(int)(!!(c)) }; } /* use after variable declarations */ + +#if defined(LZ4_DEBUG) && (LZ4_DEBUG>=2) +# include + static int g_debuglog_enable = 1; +# define DEBUGLOG(l, ...) { \ + if ((g_debuglog_enable) && (l<=LZ4_DEBUG)) { \ + fprintf(stderr, __FILE__ ": "); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, " \n"); \ + } } +#else +# define DEBUGLOG(l, ...) {} /* disabled */ +#endif + +static int LZ4_isAligned(const void* ptr, size_t alignment) +{ + return ((size_t)ptr & (alignment -1)) == 0; +} + + +/*-************************************ +* Types +**************************************/ +#include +#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) +# include + typedef uint8_t BYTE; + typedef uint16_t U16; + typedef uint32_t U32; + typedef int32_t S32; + typedef uint64_t U64; + typedef uintptr_t uptrval; +#else +# if UINT_MAX != 4294967295UL +# error "LZ4 code (when not C++ or C99) assumes that sizeof(int) == 4" +# endif + typedef unsigned char BYTE; + typedef unsigned short U16; + typedef unsigned int U32; + typedef signed int S32; + typedef unsigned long long U64; + typedef size_t uptrval; /* generally true, except OpenVMS-64 */ +#endif + +#if defined(__x86_64__) + typedef U64 reg_t; /* 64-bits in x32 mode */ +#else + typedef size_t reg_t; /* 32-bits in x32 mode */ +#endif + +typedef enum { + notLimited = 0, + limitedOutput = 1, + fillOutput = 2 +} limitedOutput_directive; + + +/*-************************************ +* Reading and writing into memory +**************************************/ + +/** + * LZ4 relies on memcpy with a constant size being inlined. In freestanding + * environments, the compiler can't assume the implementation of memcpy() is + * standard compliant, so it can't apply its specialized memcpy() inlining + * logic. When possible, use __builtin_memcpy() to tell the compiler to analyze + * memcpy() as if it were standard compliant, so it can inline it in freestanding + * environments. This is needed when decompressing the Linux Kernel, for example. + */ +#if defined(__GNUC__) && (__GNUC__ >= 4) +#define LZ4_memcpy(dst, src, size) __builtin_memcpy(dst, src, size) +#else +#define LZ4_memcpy(dst, src, size) memcpy(dst, src, size) +#endif + +static unsigned LZ4_isLittleEndian(void) +{ + const union { U32 u; BYTE c[4]; } one = { 1 }; /* don't use static : performance detrimental */ + return one.c[0]; +} + + +#if defined(LZ4_FORCE_MEMORY_ACCESS) && (LZ4_FORCE_MEMORY_ACCESS==2) +/* lie to the compiler about data alignment; use with caution */ + +static U16 LZ4_read16(const void* memPtr) { return *(const U16*) memPtr; } +static U32 LZ4_read32(const void* memPtr) { return *(const U32*) memPtr; } +static reg_t LZ4_read_ARCH(const void* memPtr) { return *(const reg_t*) memPtr; } + +static void LZ4_write16(void* memPtr, U16 value) { *(U16*)memPtr = value; } +static void LZ4_write32(void* memPtr, U32 value) { *(U32*)memPtr = value; } + +#elif defined(LZ4_FORCE_MEMORY_ACCESS) && (LZ4_FORCE_MEMORY_ACCESS==1) + +/* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */ +/* currently only defined for gcc and icc */ +typedef union { U16 u16; U32 u32; reg_t uArch; } __attribute__((packed)) unalign; + +static U16 LZ4_read16(const void* ptr) { return ((const unalign*)ptr)->u16; } +static U32 LZ4_read32(const void* ptr) { return ((const unalign*)ptr)->u32; } +static reg_t LZ4_read_ARCH(const void* ptr) { return ((const unalign*)ptr)->uArch; } + +static void LZ4_write16(void* memPtr, U16 value) { ((unalign*)memPtr)->u16 = value; } +static void LZ4_write32(void* memPtr, U32 value) { ((unalign*)memPtr)->u32 = value; } + +#else /* safe and portable access using memcpy() */ + +static U16 LZ4_read16(const void* memPtr) +{ + U16 val; LZ4_memcpy(&val, memPtr, sizeof(val)); return val; +} + +static U32 LZ4_read32(const void* memPtr) +{ + U32 val; LZ4_memcpy(&val, memPtr, sizeof(val)); return val; +} + +static reg_t LZ4_read_ARCH(const void* memPtr) +{ + reg_t val; LZ4_memcpy(&val, memPtr, sizeof(val)); return val; +} + +static void LZ4_write16(void* memPtr, U16 value) +{ + LZ4_memcpy(memPtr, &value, sizeof(value)); +} + +static void LZ4_write32(void* memPtr, U32 value) +{ + LZ4_memcpy(memPtr, &value, sizeof(value)); +} + +#endif /* LZ4_FORCE_MEMORY_ACCESS */ + + +static U16 LZ4_readLE16(const void* memPtr) +{ + if (LZ4_isLittleEndian()) { + return LZ4_read16(memPtr); + } else { + const BYTE* p = (const BYTE*)memPtr; + return (U16)((U16)p[0] + (p[1]<<8)); + } +} + +static void LZ4_writeLE16(void* memPtr, U16 value) +{ + if (LZ4_isLittleEndian()) { + LZ4_write16(memPtr, value); + } else { + BYTE* p = (BYTE*)memPtr; + p[0] = (BYTE) value; + p[1] = (BYTE)(value>>8); + } +} + +/* customized variant of memcpy, which can overwrite up to 8 bytes beyond dstEnd */ +LZ4_FORCE_INLINE +void LZ4_wildCopy8(void* dstPtr, const void* srcPtr, void* dstEnd) +{ + BYTE* d = (BYTE*)dstPtr; + const BYTE* s = (const BYTE*)srcPtr; + BYTE* const e = (BYTE*)dstEnd; + + do { LZ4_memcpy(d,s,8); d+=8; s+=8; } while (d= 16. */ +LZ4_FORCE_INLINE void +LZ4_wildCopy32(void* dstPtr, const void* srcPtr, void* dstEnd) +{ + BYTE* d = (BYTE*)dstPtr; + const BYTE* s = (const BYTE*)srcPtr; + BYTE* const e = (BYTE*)dstEnd; + + do { LZ4_memcpy(d,s,16); LZ4_memcpy(d+16,s+16,16); d+=32; s+=32; } while (d= dstPtr + MINMATCH + * - there is at least 8 bytes available to write after dstEnd */ +LZ4_FORCE_INLINE void +LZ4_memcpy_using_offset(BYTE* dstPtr, const BYTE* srcPtr, BYTE* dstEnd, const size_t offset) +{ + BYTE v[8]; + + assert(dstEnd >= dstPtr + MINMATCH); + + switch(offset) { + case 1: + MEM_INIT(v, *srcPtr, 8); + break; + case 2: + LZ4_memcpy(v, srcPtr, 2); + LZ4_memcpy(&v[2], srcPtr, 2); + LZ4_memcpy(&v[4], v, 4); + break; + case 4: + LZ4_memcpy(v, srcPtr, 4); + LZ4_memcpy(&v[4], srcPtr, 4); + break; + default: + LZ4_memcpy_using_offset_base(dstPtr, srcPtr, dstEnd, offset); + return; + } + + LZ4_memcpy(dstPtr, v, 8); + dstPtr += 8; + while (dstPtr < dstEnd) { + LZ4_memcpy(dstPtr, v, 8); + dstPtr += 8; + } +} +#endif + + +/*-************************************ +* Common functions +**************************************/ +static unsigned LZ4_NbCommonBytes (reg_t val) +{ + assert(val != 0); + if (LZ4_isLittleEndian()) { + if (sizeof(val) == 8) { +# if defined(_MSC_VER) && (_MSC_VER >= 1800) && defined(_M_AMD64) && !defined(LZ4_FORCE_SW_BITCOUNT) +# if defined(__clang__) && (__clang_major__ < 10) + /* Avoid undefined clang-cl intrinics issue. + * See https://github.com/lz4/lz4/pull/1017 for details. */ + return (unsigned)__builtin_ia32_tzcnt_u64(val) >> 3; +# else + /* x64 CPUS without BMI support interpret `TZCNT` as `REP BSF` */ + return (unsigned)_tzcnt_u64(val) >> 3; +# endif +# elif defined(_MSC_VER) && defined(_WIN64) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r = 0; + _BitScanForward64(&r, (U64)val); + return (unsigned)r >> 3; +# elif (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \ + ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \ + !defined(LZ4_FORCE_SW_BITCOUNT) + return (unsigned)__builtin_ctzll((U64)val) >> 3; +# else + const U64 m = 0x0101010101010101ULL; + val ^= val - 1; + return (unsigned)(((U64)((val & (m - 1)) * m)) >> 56); +# endif + } else /* 32 bits */ { +# if defined(_MSC_VER) && (_MSC_VER >= 1400) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r; + _BitScanForward(&r, (U32)val); + return (unsigned)r >> 3; +# elif (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \ + ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \ + !defined(__TINYC__) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (unsigned)__builtin_ctz((U32)val) >> 3; +# else + const U32 m = 0x01010101; + return (unsigned)((((val - 1) ^ val) & (m - 1)) * m) >> 24; +# endif + } + } else /* Big Endian CPU */ { + if (sizeof(val)==8) { +# if (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \ + ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \ + !defined(__TINYC__) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (unsigned)__builtin_clzll((U64)val) >> 3; +# else +#if 1 + /* this method is probably faster, + * but adds a 128 bytes lookup table */ + static const unsigned char ctz7_tab[128] = { + 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + }; + U64 const mask = 0x0101010101010101ULL; + U64 const t = (((val >> 8) - mask) | val) & mask; + return ctz7_tab[(t * 0x0080402010080402ULL) >> 57]; +#else + /* this method doesn't consume memory space like the previous one, + * but it contains several branches, + * that may end up slowing execution */ + static const U32 by32 = sizeof(val)*4; /* 32 on 64 bits (goal), 16 on 32 bits. + Just to avoid some static analyzer complaining about shift by 32 on 32-bits target. + Note that this code path is never triggered in 32-bits mode. */ + unsigned r; + if (!(val>>by32)) { r=4; } else { r=0; val>>=by32; } + if (!(val>>16)) { r+=2; val>>=8; } else { val>>=24; } + r += (!val); + return r; +#endif +# endif + } else /* 32 bits */ { +# if (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \ + ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \ + !defined(LZ4_FORCE_SW_BITCOUNT) + return (unsigned)__builtin_clz((U32)val) >> 3; +# else + val >>= 8; + val = ((((val + 0x00FFFF00) | 0x00FFFFFF) + val) | + (val + 0x00FF0000)) >> 24; + return (unsigned)val ^ 3; +# endif + } + } +} + + +#define STEPSIZE sizeof(reg_t) +LZ4_FORCE_INLINE +unsigned LZ4_count(const BYTE* pIn, const BYTE* pMatch, const BYTE* pInLimit) +{ + const BYTE* const pStart = pIn; + + if (likely(pIn < pInLimit-(STEPSIZE-1))) { + reg_t const diff = LZ4_read_ARCH(pMatch) ^ LZ4_read_ARCH(pIn); + if (!diff) { + pIn+=STEPSIZE; pMatch+=STEPSIZE; + } else { + return LZ4_NbCommonBytes(diff); + } } + + while (likely(pIn < pInLimit-(STEPSIZE-1))) { + reg_t const diff = LZ4_read_ARCH(pMatch) ^ LZ4_read_ARCH(pIn); + if (!diff) { pIn+=STEPSIZE; pMatch+=STEPSIZE; continue; } + pIn += LZ4_NbCommonBytes(diff); + return (unsigned)(pIn - pStart); + } + + if ((STEPSIZE==8) && (pIn<(pInLimit-3)) && (LZ4_read32(pMatch) == LZ4_read32(pIn))) { pIn+=4; pMatch+=4; } + if ((pIn<(pInLimit-1)) && (LZ4_read16(pMatch) == LZ4_read16(pIn))) { pIn+=2; pMatch+=2; } + if ((pIn compression run slower on incompressible data */ + + +/*-************************************ +* Local Structures and types +**************************************/ +typedef enum { clearedTable = 0, byPtr, byU32, byU16 } tableType_t; + +/** + * This enum distinguishes several different modes of accessing previous + * content in the stream. + * + * - noDict : There is no preceding content. + * - withPrefix64k : Table entries up to ctx->dictSize before the current blob + * blob being compressed are valid and refer to the preceding + * content (of length ctx->dictSize), which is available + * contiguously preceding in memory the content currently + * being compressed. + * - usingExtDict : Like withPrefix64k, but the preceding content is somewhere + * else in memory, starting at ctx->dictionary with length + * ctx->dictSize. + * - usingDictCtx : Everything concerning the preceding content is + * in a separate context, pointed to by ctx->dictCtx. + * ctx->dictionary, ctx->dictSize, and table entries + * in the current context that refer to positions + * preceding the beginning of the current compression are + * ignored. Instead, ctx->dictCtx->dictionary and ctx->dictCtx + * ->dictSize describe the location and size of the preceding + * content, and matches are found by looking in the ctx + * ->dictCtx->hashTable. + */ +typedef enum { noDict = 0, withPrefix64k, usingExtDict, usingDictCtx } dict_directive; +typedef enum { noDictIssue = 0, dictSmall } dictIssue_directive; + + +/*-************************************ +* Local Utils +**************************************/ +int LZ4_versionNumber (void) { return LZ4_VERSION_NUMBER; } +const char* LZ4_versionString(void) { return LZ4_VERSION_STRING; } +int LZ4_compressBound(int isize) { return LZ4_COMPRESSBOUND(isize); } +int LZ4_sizeofState(void) { return LZ4_STREAMSIZE; } + + +/*-**************************************** +* Internal Definitions, used only in Tests +*******************************************/ +#if defined (__cplusplus) +extern "C" { +#endif + +int LZ4_compress_forceExtDict (LZ4_stream_t* LZ4_dict, const char* source, char* dest, int srcSize); + +int LZ4_decompress_safe_forceExtDict(const char* source, char* dest, + int compressedSize, int maxOutputSize, + const void* dictStart, size_t dictSize); + +#if defined (__cplusplus) +} +#endif + +/*-****************************** +* Compression functions +********************************/ +LZ4_FORCE_INLINE U32 LZ4_hash4(U32 sequence, tableType_t const tableType) +{ + if (tableType == byU16) + return ((sequence * 2654435761U) >> ((MINMATCH*8)-(LZ4_HASHLOG+1))); + else + return ((sequence * 2654435761U) >> ((MINMATCH*8)-LZ4_HASHLOG)); +} + +LZ4_FORCE_INLINE U32 LZ4_hash5(U64 sequence, tableType_t const tableType) +{ + const U32 hashLog = (tableType == byU16) ? LZ4_HASHLOG+1 : LZ4_HASHLOG; + if (LZ4_isLittleEndian()) { + const U64 prime5bytes = 889523592379ULL; + return (U32)(((sequence << 24) * prime5bytes) >> (64 - hashLog)); + } else { + const U64 prime8bytes = 11400714785074694791ULL; + return (U32)(((sequence >> 24) * prime8bytes) >> (64 - hashLog)); + } +} + +LZ4_FORCE_INLINE U32 LZ4_hashPosition(const void* const p, tableType_t const tableType) +{ + if ((sizeof(reg_t)==8) && (tableType != byU16)) return LZ4_hash5(LZ4_read_ARCH(p), tableType); + return LZ4_hash4(LZ4_read32(p), tableType); +} + +LZ4_FORCE_INLINE void LZ4_clearHash(U32 h, void* tableBase, tableType_t const tableType) +{ + switch (tableType) + { + default: /* fallthrough */ + case clearedTable: { /* illegal! */ assert(0); return; } + case byPtr: { const BYTE** hashTable = (const BYTE**)tableBase; hashTable[h] = NULL; return; } + case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = 0; return; } + case byU16: { U16* hashTable = (U16*) tableBase; hashTable[h] = 0; return; } + } +} + +LZ4_FORCE_INLINE void LZ4_putIndexOnHash(U32 idx, U32 h, void* tableBase, tableType_t const tableType) +{ + switch (tableType) + { + default: /* fallthrough */ + case clearedTable: /* fallthrough */ + case byPtr: { /* illegal! */ assert(0); return; } + case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = idx; return; } + case byU16: { U16* hashTable = (U16*) tableBase; assert(idx < 65536); hashTable[h] = (U16)idx; return; } + } +} + +LZ4_FORCE_INLINE void LZ4_putPositionOnHash(const BYTE* p, U32 h, + void* tableBase, tableType_t const tableType, + const BYTE* srcBase) +{ + switch (tableType) + { + case clearedTable: { /* illegal! */ assert(0); return; } + case byPtr: { const BYTE** hashTable = (const BYTE**)tableBase; hashTable[h] = p; return; } + case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = (U32)(p-srcBase); return; } + case byU16: { U16* hashTable = (U16*) tableBase; hashTable[h] = (U16)(p-srcBase); return; } + } +} + +LZ4_FORCE_INLINE void LZ4_putPosition(const BYTE* p, void* tableBase, tableType_t tableType, const BYTE* srcBase) +{ + U32 const h = LZ4_hashPosition(p, tableType); + LZ4_putPositionOnHash(p, h, tableBase, tableType, srcBase); +} + +/* LZ4_getIndexOnHash() : + * Index of match position registered in hash table. + * hash position must be calculated by using base+index, or dictBase+index. + * Assumption 1 : only valid if tableType == byU32 or byU16. + * Assumption 2 : h is presumed valid (within limits of hash table) + */ +LZ4_FORCE_INLINE U32 LZ4_getIndexOnHash(U32 h, const void* tableBase, tableType_t tableType) +{ + LZ4_STATIC_ASSERT(LZ4_MEMORY_USAGE > 2); + if (tableType == byU32) { + const U32* const hashTable = (const U32*) tableBase; + assert(h < (1U << (LZ4_MEMORY_USAGE-2))); + return hashTable[h]; + } + if (tableType == byU16) { + const U16* const hashTable = (const U16*) tableBase; + assert(h < (1U << (LZ4_MEMORY_USAGE-1))); + return hashTable[h]; + } + assert(0); return 0; /* forbidden case */ +} + +static const BYTE* LZ4_getPositionOnHash(U32 h, const void* tableBase, tableType_t tableType, const BYTE* srcBase) +{ + if (tableType == byPtr) { const BYTE* const* hashTable = (const BYTE* const*) tableBase; return hashTable[h]; } + if (tableType == byU32) { const U32* const hashTable = (const U32*) tableBase; return hashTable[h] + srcBase; } + { const U16* const hashTable = (const U16*) tableBase; return hashTable[h] + srcBase; } /* default, to ensure a return */ +} + +LZ4_FORCE_INLINE const BYTE* +LZ4_getPosition(const BYTE* p, + const void* tableBase, tableType_t tableType, + const BYTE* srcBase) +{ + U32 const h = LZ4_hashPosition(p, tableType); + return LZ4_getPositionOnHash(h, tableBase, tableType, srcBase); +} + +LZ4_FORCE_INLINE void +LZ4_prepareTable(LZ4_stream_t_internal* const cctx, + const int inputSize, + const tableType_t tableType) { + /* If the table hasn't been used, it's guaranteed to be zeroed out, and is + * therefore safe to use no matter what mode we're in. Otherwise, we figure + * out if it's safe to leave as is or whether it needs to be reset. + */ + if ((tableType_t)cctx->tableType != clearedTable) { + assert(inputSize >= 0); + if ((tableType_t)cctx->tableType != tableType + || ((tableType == byU16) && cctx->currentOffset + (unsigned)inputSize >= 0xFFFFU) + || ((tableType == byU32) && cctx->currentOffset > 1 GB) + || tableType == byPtr + || inputSize >= 4 KB) + { + DEBUGLOG(4, "LZ4_prepareTable: Resetting table in %p", cctx); + MEM_INIT(cctx->hashTable, 0, LZ4_HASHTABLESIZE); + cctx->currentOffset = 0; + cctx->tableType = (U32)clearedTable; + } else { + DEBUGLOG(4, "LZ4_prepareTable: Re-use hash table (no reset)"); + } + } + + /* Adding a gap, so all previous entries are > LZ4_DISTANCE_MAX back, + * is faster than compressing without a gap. + * However, compressing with currentOffset == 0 is faster still, + * so we preserve that case. + */ + if (cctx->currentOffset != 0 && tableType == byU32) { + DEBUGLOG(5, "LZ4_prepareTable: adding 64KB to currentOffset"); + cctx->currentOffset += 64 KB; + } + + /* Finally, clear history */ + cctx->dictCtx = NULL; + cctx->dictionary = NULL; + cctx->dictSize = 0; +} + +/** LZ4_compress_generic() : + * inlined, to ensure branches are decided at compilation time. + * Presumed already validated at this stage: + * - source != NULL + * - inputSize > 0 + */ +LZ4_FORCE_INLINE int LZ4_compress_generic_validated( + LZ4_stream_t_internal* const cctx, + const char* const source, + char* const dest, + const int inputSize, + int *inputConsumed, /* only written when outputDirective == fillOutput */ + const int maxOutputSize, + const limitedOutput_directive outputDirective, + const tableType_t tableType, + const dict_directive dictDirective, + const dictIssue_directive dictIssue, + const int acceleration) +{ + int result; + const BYTE* ip = (const BYTE*) source; + + U32 const startIndex = cctx->currentOffset; + const BYTE* base = (const BYTE*) source - startIndex; + const BYTE* lowLimit; + + const LZ4_stream_t_internal* dictCtx = (const LZ4_stream_t_internal*) cctx->dictCtx; + const BYTE* const dictionary = + dictDirective == usingDictCtx ? dictCtx->dictionary : cctx->dictionary; + const U32 dictSize = + dictDirective == usingDictCtx ? dictCtx->dictSize : cctx->dictSize; + const U32 dictDelta = (dictDirective == usingDictCtx) ? startIndex - dictCtx->currentOffset : 0; /* make indexes in dictCtx comparable with index in current context */ + + int const maybe_extMem = (dictDirective == usingExtDict) || (dictDirective == usingDictCtx); + U32 const prefixIdxLimit = startIndex - dictSize; /* used when dictDirective == dictSmall */ + const BYTE* const dictEnd = dictionary ? dictionary + dictSize : dictionary; + const BYTE* anchor = (const BYTE*) source; + const BYTE* const iend = ip + inputSize; + const BYTE* const mflimitPlusOne = iend - MFLIMIT + 1; + const BYTE* const matchlimit = iend - LASTLITERALS; + + /* the dictCtx currentOffset is indexed on the start of the dictionary, + * while a dictionary in the current context precedes the currentOffset */ + const BYTE* dictBase = (dictionary == NULL) ? NULL : + (dictDirective == usingDictCtx) ? + dictionary + dictSize - dictCtx->currentOffset : + dictionary + dictSize - startIndex; + + BYTE* op = (BYTE*) dest; + BYTE* const olimit = op + maxOutputSize; + + U32 offset = 0; + U32 forwardH; + + DEBUGLOG(5, "LZ4_compress_generic_validated: srcSize=%i, tableType=%u", inputSize, tableType); + assert(ip != NULL); + /* If init conditions are not met, we don't have to mark stream + * as having dirty context, since no action was taken yet */ + if (outputDirective == fillOutput && maxOutputSize < 1) { return 0; } /* Impossible to store anything */ + if ((tableType == byU16) && (inputSize>=LZ4_64Klimit)) { return 0; } /* Size too large (not within 64K limit) */ + if (tableType==byPtr) assert(dictDirective==noDict); /* only supported use case with byPtr */ + assert(acceleration >= 1); + + lowLimit = (const BYTE*)source - (dictDirective == withPrefix64k ? dictSize : 0); + + /* Update context state */ + if (dictDirective == usingDictCtx) { + /* Subsequent linked blocks can't use the dictionary. */ + /* Instead, they use the block we just compressed. */ + cctx->dictCtx = NULL; + cctx->dictSize = (U32)inputSize; + } else { + cctx->dictSize += (U32)inputSize; + } + cctx->currentOffset += (U32)inputSize; + cctx->tableType = (U32)tableType; + + if (inputSizehashTable, tableType, base); + ip++; forwardH = LZ4_hashPosition(ip, tableType); + + /* Main Loop */ + for ( ; ; ) { + const BYTE* match; + BYTE* token; + const BYTE* filledIp; + + /* Find a match */ + if (tableType == byPtr) { + const BYTE* forwardIp = ip; + int step = 1; + int searchMatchNb = acceleration << LZ4_skipTrigger; + do { + U32 const h = forwardH; + ip = forwardIp; + forwardIp += step; + step = (searchMatchNb++ >> LZ4_skipTrigger); + + if (unlikely(forwardIp > mflimitPlusOne)) goto _last_literals; + assert(ip < mflimitPlusOne); + + match = LZ4_getPositionOnHash(h, cctx->hashTable, tableType, base); + forwardH = LZ4_hashPosition(forwardIp, tableType); + LZ4_putPositionOnHash(ip, h, cctx->hashTable, tableType, base); + + } while ( (match+LZ4_DISTANCE_MAX < ip) + || (LZ4_read32(match) != LZ4_read32(ip)) ); + + } else { /* byU32, byU16 */ + + const BYTE* forwardIp = ip; + int step = 1; + int searchMatchNb = acceleration << LZ4_skipTrigger; + do { + U32 const h = forwardH; + U32 const current = (U32)(forwardIp - base); + U32 matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType); + assert(matchIndex <= current); + assert(forwardIp - base < (ptrdiff_t)(2 GB - 1)); + ip = forwardIp; + forwardIp += step; + step = (searchMatchNb++ >> LZ4_skipTrigger); + + if (unlikely(forwardIp > mflimitPlusOne)) goto _last_literals; + assert(ip < mflimitPlusOne); + + if (dictDirective == usingDictCtx) { + if (matchIndex < startIndex) { + /* there was no match, try the dictionary */ + assert(tableType == byU32); + matchIndex = LZ4_getIndexOnHash(h, dictCtx->hashTable, byU32); + match = dictBase + matchIndex; + matchIndex += dictDelta; /* make dictCtx index comparable with current context */ + lowLimit = dictionary; + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; + } + } else if (dictDirective == usingExtDict) { + if (matchIndex < startIndex) { + DEBUGLOG(7, "extDict candidate: matchIndex=%5u < startIndex=%5u", matchIndex, startIndex); + assert(startIndex - matchIndex >= MINMATCH); + assert(dictBase); + match = dictBase + matchIndex; + lowLimit = dictionary; + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; + } + } else { /* single continuous memory segment */ + match = base + matchIndex; + } + forwardH = LZ4_hashPosition(forwardIp, tableType); + LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType); + + DEBUGLOG(7, "candidate at pos=%u (offset=%u \n", matchIndex, current - matchIndex); + if ((dictIssue == dictSmall) && (matchIndex < prefixIdxLimit)) { continue; } /* match outside of valid area */ + assert(matchIndex < current); + if ( ((tableType != byU16) || (LZ4_DISTANCE_MAX < LZ4_DISTANCE_ABSOLUTE_MAX)) + && (matchIndex+LZ4_DISTANCE_MAX < current)) { + continue; + } /* too far */ + assert((current - matchIndex) <= LZ4_DISTANCE_MAX); /* match now expected within distance */ + + if (LZ4_read32(match) == LZ4_read32(ip)) { + if (maybe_extMem) offset = current - matchIndex; + break; /* match found */ + } + + } while(1); + } + + /* Catch up */ + filledIp = ip; + while (((ip>anchor) & (match > lowLimit)) && (unlikely(ip[-1]==match[-1]))) { ip--; match--; } + + /* Encode Literals */ + { unsigned const litLength = (unsigned)(ip - anchor); + token = op++; + if ((outputDirective == limitedOutput) && /* Check output buffer overflow */ + (unlikely(op + litLength + (2 + 1 + LASTLITERALS) + (litLength/255) > olimit)) ) { + return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */ + } + if ((outputDirective == fillOutput) && + (unlikely(op + (litLength+240)/255 /* litlen */ + litLength /* literals */ + 2 /* offset */ + 1 /* token */ + MFLIMIT - MINMATCH /* min last literals so last match is <= end - MFLIMIT */ > olimit))) { + op--; + goto _last_literals; + } + if (litLength >= RUN_MASK) { + int len = (int)(litLength - RUN_MASK); + *token = (RUN_MASK<= 255 ; len-=255) *op++ = 255; + *op++ = (BYTE)len; + } + else *token = (BYTE)(litLength< olimit)) { + /* the match was too close to the end, rewind and go to last literals */ + op = token; + goto _last_literals; + } + + /* Encode Offset */ + if (maybe_extMem) { /* static test */ + DEBUGLOG(6, " with offset=%u (ext if > %i)", offset, (int)(ip - (const BYTE*)source)); + assert(offset <= LZ4_DISTANCE_MAX && offset > 0); + LZ4_writeLE16(op, (U16)offset); op+=2; + } else { + DEBUGLOG(6, " with offset=%u (same segment)", (U32)(ip - match)); + assert(ip-match <= LZ4_DISTANCE_MAX); + LZ4_writeLE16(op, (U16)(ip - match)); op+=2; + } + + /* Encode MatchLength */ + { unsigned matchCode; + + if ( (dictDirective==usingExtDict || dictDirective==usingDictCtx) + && (lowLimit==dictionary) /* match within extDict */ ) { + const BYTE* limit = ip + (dictEnd-match); + assert(dictEnd > match); + if (limit > matchlimit) limit = matchlimit; + matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, limit); + ip += (size_t)matchCode + MINMATCH; + if (ip==limit) { + unsigned const more = LZ4_count(limit, (const BYTE*)source, matchlimit); + matchCode += more; + ip += more; + } + DEBUGLOG(6, " with matchLength=%u starting in extDict", matchCode+MINMATCH); + } else { + matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, matchlimit); + ip += (size_t)matchCode + MINMATCH; + DEBUGLOG(6, " with matchLength=%u", matchCode+MINMATCH); + } + + if ((outputDirective) && /* Check output buffer overflow */ + (unlikely(op + (1 + LASTLITERALS) + (matchCode+240)/255 > olimit)) ) { + if (outputDirective == fillOutput) { + /* Match description too long : reduce it */ + U32 newMatchCode = 15 /* in token */ - 1 /* to avoid needing a zero byte */ + ((U32)(olimit - op) - 1 - LASTLITERALS) * 255; + ip -= matchCode - newMatchCode; + assert(newMatchCode < matchCode); + matchCode = newMatchCode; + if (unlikely(ip <= filledIp)) { + /* We have already filled up to filledIp so if ip ends up less than filledIp + * we have positions in the hash table beyond the current position. This is + * a problem if we reuse the hash table. So we have to remove these positions + * from the hash table. + */ + const BYTE* ptr; + DEBUGLOG(5, "Clearing %u positions", (U32)(filledIp - ip)); + for (ptr = ip; ptr <= filledIp; ++ptr) { + U32 const h = LZ4_hashPosition(ptr, tableType); + LZ4_clearHash(h, cctx->hashTable, tableType); + } + } + } else { + assert(outputDirective == limitedOutput); + return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */ + } + } + if (matchCode >= ML_MASK) { + *token += ML_MASK; + matchCode -= ML_MASK; + LZ4_write32(op, 0xFFFFFFFF); + while (matchCode >= 4*255) { + op+=4; + LZ4_write32(op, 0xFFFFFFFF); + matchCode -= 4*255; + } + op += matchCode / 255; + *op++ = (BYTE)(matchCode % 255); + } else + *token += (BYTE)(matchCode); + } + /* Ensure we have enough space for the last literals. */ + assert(!(outputDirective == fillOutput && op + 1 + LASTLITERALS > olimit)); + + anchor = ip; + + /* Test end of chunk */ + if (ip >= mflimitPlusOne) break; + + /* Fill table */ + LZ4_putPosition(ip-2, cctx->hashTable, tableType, base); + + /* Test next position */ + if (tableType == byPtr) { + + match = LZ4_getPosition(ip, cctx->hashTable, tableType, base); + LZ4_putPosition(ip, cctx->hashTable, tableType, base); + if ( (match+LZ4_DISTANCE_MAX >= ip) + && (LZ4_read32(match) == LZ4_read32(ip)) ) + { token=op++; *token=0; goto _next_match; } + + } else { /* byU32, byU16 */ + + U32 const h = LZ4_hashPosition(ip, tableType); + U32 const current = (U32)(ip-base); + U32 matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType); + assert(matchIndex < current); + if (dictDirective == usingDictCtx) { + if (matchIndex < startIndex) { + /* there was no match, try the dictionary */ + matchIndex = LZ4_getIndexOnHash(h, dictCtx->hashTable, byU32); + match = dictBase + matchIndex; + lowLimit = dictionary; /* required for match length counter */ + matchIndex += dictDelta; + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; /* required for match length counter */ + } + } else if (dictDirective==usingExtDict) { + if (matchIndex < startIndex) { + assert(dictBase); + match = dictBase + matchIndex; + lowLimit = dictionary; /* required for match length counter */ + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; /* required for match length counter */ + } + } else { /* single memory segment */ + match = base + matchIndex; + } + LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType); + assert(matchIndex < current); + if ( ((dictIssue==dictSmall) ? (matchIndex >= prefixIdxLimit) : 1) + && (((tableType==byU16) && (LZ4_DISTANCE_MAX == LZ4_DISTANCE_ABSOLUTE_MAX)) ? 1 : (matchIndex+LZ4_DISTANCE_MAX >= current)) + && (LZ4_read32(match) == LZ4_read32(ip)) ) { + token=op++; + *token=0; + if (maybe_extMem) offset = current - matchIndex; + DEBUGLOG(6, "seq.start:%i, literals=%u, match.start:%i", + (int)(anchor-(const BYTE*)source), 0, (int)(ip-(const BYTE*)source)); + goto _next_match; + } + } + + /* Prepare next loop */ + forwardH = LZ4_hashPosition(++ip, tableType); + + } + +_last_literals: + /* Encode Last Literals */ + { size_t lastRun = (size_t)(iend - anchor); + if ( (outputDirective) && /* Check output buffer overflow */ + (op + lastRun + 1 + ((lastRun+255-RUN_MASK)/255) > olimit)) { + if (outputDirective == fillOutput) { + /* adapt lastRun to fill 'dst' */ + assert(olimit >= op); + lastRun = (size_t)(olimit-op) - 1/*token*/; + lastRun -= (lastRun + 256 - RUN_MASK) / 256; /*additional length tokens*/ + } else { + assert(outputDirective == limitedOutput); + return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */ + } + } + DEBUGLOG(6, "Final literal run : %i literals", (int)lastRun); + if (lastRun >= RUN_MASK) { + size_t accumulator = lastRun - RUN_MASK; + *op++ = RUN_MASK << ML_BITS; + for(; accumulator >= 255 ; accumulator-=255) *op++ = 255; + *op++ = (BYTE) accumulator; + } else { + *op++ = (BYTE)(lastRun< 0); + DEBUGLOG(5, "LZ4_compress_generic: compressed %i bytes into %i bytes", inputSize, result); + return result; +} + +/** LZ4_compress_generic() : + * inlined, to ensure branches are decided at compilation time; + * takes care of src == (NULL, 0) + * and forward the rest to LZ4_compress_generic_validated */ +LZ4_FORCE_INLINE int LZ4_compress_generic( + LZ4_stream_t_internal* const cctx, + const char* const src, + char* const dst, + const int srcSize, + int *inputConsumed, /* only written when outputDirective == fillOutput */ + const int dstCapacity, + const limitedOutput_directive outputDirective, + const tableType_t tableType, + const dict_directive dictDirective, + const dictIssue_directive dictIssue, + const int acceleration) +{ + DEBUGLOG(5, "LZ4_compress_generic: srcSize=%i, dstCapacity=%i", + srcSize, dstCapacity); + + if ((U32)srcSize > (U32)LZ4_MAX_INPUT_SIZE) { return 0; } /* Unsupported srcSize, too large (or negative) */ + if (srcSize == 0) { /* src == NULL supported if srcSize == 0 */ + if (outputDirective != notLimited && dstCapacity <= 0) return 0; /* no output, can't write anything */ + DEBUGLOG(5, "Generating an empty block"); + assert(outputDirective == notLimited || dstCapacity >= 1); + assert(dst != NULL); + dst[0] = 0; + if (outputDirective == fillOutput) { + assert (inputConsumed != NULL); + *inputConsumed = 0; + } + return 1; + } + assert(src != NULL); + + return LZ4_compress_generic_validated(cctx, src, dst, srcSize, + inputConsumed, /* only written into if outputDirective == fillOutput */ + dstCapacity, outputDirective, + tableType, dictDirective, dictIssue, acceleration); +} + + +int LZ4_compress_fast_extState(void* state, const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration) +{ + LZ4_stream_t_internal* const ctx = & LZ4_initStream(state, sizeof(LZ4_stream_t)) -> internal_donotuse; + assert(ctx != NULL); + if (acceleration < 1) acceleration = LZ4_ACCELERATION_DEFAULT; + if (acceleration > LZ4_ACCELERATION_MAX) acceleration = LZ4_ACCELERATION_MAX; + if (maxOutputSize >= LZ4_compressBound(inputSize)) { + if (inputSize < LZ4_64Klimit) { + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, 0, notLimited, byU16, noDict, noDictIssue, acceleration); + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)source > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration); + } + } else { + if (inputSize < LZ4_64Klimit) { + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, byU16, noDict, noDictIssue, acceleration); + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)source > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, noDict, noDictIssue, acceleration); + } + } +} + +/** + * LZ4_compress_fast_extState_fastReset() : + * A variant of LZ4_compress_fast_extState(). + * + * Using this variant avoids an expensive initialization step. It is only safe + * to call if the state buffer is known to be correctly initialized already + * (see comment in lz4.h on LZ4_resetStream_fast() for a definition of + * "correctly initialized"). + */ +int LZ4_compress_fast_extState_fastReset(void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration) +{ + LZ4_stream_t_internal* ctx = &((LZ4_stream_t*)state)->internal_donotuse; + if (acceleration < 1) acceleration = LZ4_ACCELERATION_DEFAULT; + if (acceleration > LZ4_ACCELERATION_MAX) acceleration = LZ4_ACCELERATION_MAX; + + if (dstCapacity >= LZ4_compressBound(srcSize)) { + if (srcSize < LZ4_64Klimit) { + const tableType_t tableType = byU16; + LZ4_prepareTable(ctx, srcSize, tableType); + if (ctx->currentOffset) { + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, dictSmall, acceleration); + } else { + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration); + } + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + LZ4_prepareTable(ctx, srcSize, tableType); + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration); + } + } else { + if (srcSize < LZ4_64Klimit) { + const tableType_t tableType = byU16; + LZ4_prepareTable(ctx, srcSize, tableType); + if (ctx->currentOffset) { + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, dictSmall, acceleration); + } else { + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, noDictIssue, acceleration); + } + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + LZ4_prepareTable(ctx, srcSize, tableType); + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, noDictIssue, acceleration); + } + } +} + + +int LZ4_compress_fast(const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration) +{ + int result; +#if (LZ4_HEAPMODE) + LZ4_stream_t* ctxPtr = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t)); /* malloc-calloc always properly aligned */ + if (ctxPtr == NULL) return 0; +#else + LZ4_stream_t ctx; + LZ4_stream_t* const ctxPtr = &ctx; +#endif + result = LZ4_compress_fast_extState(ctxPtr, source, dest, inputSize, maxOutputSize, acceleration); + +#if (LZ4_HEAPMODE) + FREEMEM(ctxPtr); +#endif + return result; +} + + +int LZ4_compress_default(const char* src, char* dst, int srcSize, int maxOutputSize) +{ + return LZ4_compress_fast(src, dst, srcSize, maxOutputSize, 1); +} + + +/* Note!: This function leaves the stream in an unclean/broken state! + * It is not safe to subsequently use the same state with a _fastReset() or + * _continue() call without resetting it. */ +static int LZ4_compress_destSize_extState (LZ4_stream_t* state, const char* src, char* dst, int* srcSizePtr, int targetDstSize) +{ + void* const s = LZ4_initStream(state, sizeof (*state)); + assert(s != NULL); (void)s; + + if (targetDstSize >= LZ4_compressBound(*srcSizePtr)) { /* compression success is guaranteed */ + return LZ4_compress_fast_extState(state, src, dst, *srcSizePtr, targetDstSize, 1); + } else { + if (*srcSizePtr < LZ4_64Klimit) { + return LZ4_compress_generic(&state->internal_donotuse, src, dst, *srcSizePtr, srcSizePtr, targetDstSize, fillOutput, byU16, noDict, noDictIssue, 1); + } else { + tableType_t const addrMode = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + return LZ4_compress_generic(&state->internal_donotuse, src, dst, *srcSizePtr, srcSizePtr, targetDstSize, fillOutput, addrMode, noDict, noDictIssue, 1); + } } +} + + +int LZ4_compress_destSize(const char* src, char* dst, int* srcSizePtr, int targetDstSize) +{ +#if (LZ4_HEAPMODE) + LZ4_stream_t* ctx = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t)); /* malloc-calloc always properly aligned */ + if (ctx == NULL) return 0; +#else + LZ4_stream_t ctxBody; + LZ4_stream_t* ctx = &ctxBody; +#endif + + int result = LZ4_compress_destSize_extState(ctx, src, dst, srcSizePtr, targetDstSize); + +#if (LZ4_HEAPMODE) + FREEMEM(ctx); +#endif + return result; +} + + + +/*-****************************** +* Streaming functions +********************************/ + +LZ4_stream_t* LZ4_createStream(void) +{ + LZ4_stream_t* const lz4s = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t)); + LZ4_STATIC_ASSERT(LZ4_STREAMSIZE >= sizeof(LZ4_stream_t_internal)); /* A compilation error here means LZ4_STREAMSIZE is not large enough */ + DEBUGLOG(4, "LZ4_createStream %p", lz4s); + if (lz4s == NULL) return NULL; + LZ4_initStream(lz4s, sizeof(*lz4s)); + return lz4s; +} + +static size_t LZ4_stream_t_alignment(void) +{ +#if LZ4_ALIGN_TEST + typedef struct { char c; LZ4_stream_t t; } t_a; + return sizeof(t_a) - sizeof(LZ4_stream_t); +#else + return 1; /* effectively disabled */ +#endif +} + +LZ4_stream_t* LZ4_initStream (void* buffer, size_t size) +{ + DEBUGLOG(5, "LZ4_initStream"); + if (buffer == NULL) { return NULL; } + if (size < sizeof(LZ4_stream_t)) { return NULL; } + if (!LZ4_isAligned(buffer, LZ4_stream_t_alignment())) return NULL; + MEM_INIT(buffer, 0, sizeof(LZ4_stream_t_internal)); + return (LZ4_stream_t*)buffer; +} + +/* resetStream is now deprecated, + * prefer initStream() which is more general */ +void LZ4_resetStream (LZ4_stream_t* LZ4_stream) +{ + DEBUGLOG(5, "LZ4_resetStream (ctx:%p)", LZ4_stream); + MEM_INIT(LZ4_stream, 0, sizeof(LZ4_stream_t_internal)); +} + +void LZ4_resetStream_fast(LZ4_stream_t* ctx) { + LZ4_prepareTable(&(ctx->internal_donotuse), 0, byU32); +} + +int LZ4_freeStream (LZ4_stream_t* LZ4_stream) +{ + if (!LZ4_stream) return 0; /* support free on NULL */ + DEBUGLOG(5, "LZ4_freeStream %p", LZ4_stream); + FREEMEM(LZ4_stream); + return (0); +} + + +#define HASH_UNIT sizeof(reg_t) +int LZ4_loadDict (LZ4_stream_t* LZ4_dict, const char* dictionary, int dictSize) +{ + LZ4_stream_t_internal* dict = &LZ4_dict->internal_donotuse; + const tableType_t tableType = byU32; + const BYTE* p = (const BYTE*)dictionary; + const BYTE* const dictEnd = p + dictSize; + const BYTE* base; + + DEBUGLOG(4, "LZ4_loadDict (%i bytes from %p into %p)", dictSize, dictionary, LZ4_dict); + + /* It's necessary to reset the context, + * and not just continue it with prepareTable() + * to avoid any risk of generating overflowing matchIndex + * when compressing using this dictionary */ + LZ4_resetStream(LZ4_dict); + + /* We always increment the offset by 64 KB, since, if the dict is longer, + * we truncate it to the last 64k, and if it's shorter, we still want to + * advance by a whole window length so we can provide the guarantee that + * there are only valid offsets in the window, which allows an optimization + * in LZ4_compress_fast_continue() where it uses noDictIssue even when the + * dictionary isn't a full 64k. */ + dict->currentOffset += 64 KB; + + if (dictSize < (int)HASH_UNIT) { + return 0; + } + + if ((dictEnd - p) > 64 KB) p = dictEnd - 64 KB; + base = dictEnd - dict->currentOffset; + dict->dictionary = p; + dict->dictSize = (U32)(dictEnd - p); + dict->tableType = (U32)tableType; + + while (p <= dictEnd-HASH_UNIT) { + LZ4_putPosition(p, dict->hashTable, tableType, base); + p+=3; + } + + return (int)dict->dictSize; +} + +void LZ4_attach_dictionary(LZ4_stream_t* workingStream, const LZ4_stream_t* dictionaryStream) +{ + const LZ4_stream_t_internal* dictCtx = (dictionaryStream == NULL) ? NULL : + &(dictionaryStream->internal_donotuse); + + DEBUGLOG(4, "LZ4_attach_dictionary (%p, %p, size %u)", + workingStream, dictionaryStream, + dictCtx != NULL ? dictCtx->dictSize : 0); + + if (dictCtx != NULL) { + /* If the current offset is zero, we will never look in the + * external dictionary context, since there is no value a table + * entry can take that indicate a miss. In that case, we need + * to bump the offset to something non-zero. + */ + if (workingStream->internal_donotuse.currentOffset == 0) { + workingStream->internal_donotuse.currentOffset = 64 KB; + } + + /* Don't actually attach an empty dictionary. + */ + if (dictCtx->dictSize == 0) { + dictCtx = NULL; + } + } + workingStream->internal_donotuse.dictCtx = dictCtx; +} + + +static void LZ4_renormDictT(LZ4_stream_t_internal* LZ4_dict, int nextSize) +{ + assert(nextSize >= 0); + if (LZ4_dict->currentOffset + (unsigned)nextSize > 0x80000000) { /* potential ptrdiff_t overflow (32-bits mode) */ + /* rescale hash table */ + U32 const delta = LZ4_dict->currentOffset - 64 KB; + const BYTE* dictEnd = LZ4_dict->dictionary + LZ4_dict->dictSize; + int i; + DEBUGLOG(4, "LZ4_renormDictT"); + for (i=0; ihashTable[i] < delta) LZ4_dict->hashTable[i]=0; + else LZ4_dict->hashTable[i] -= delta; + } + LZ4_dict->currentOffset = 64 KB; + if (LZ4_dict->dictSize > 64 KB) LZ4_dict->dictSize = 64 KB; + LZ4_dict->dictionary = dictEnd - LZ4_dict->dictSize; + } +} + + +int LZ4_compress_fast_continue (LZ4_stream_t* LZ4_stream, + const char* source, char* dest, + int inputSize, int maxOutputSize, + int acceleration) +{ + const tableType_t tableType = byU32; + LZ4_stream_t_internal* const streamPtr = &LZ4_stream->internal_donotuse; + const char* dictEnd = streamPtr->dictSize ? (const char*)streamPtr->dictionary + streamPtr->dictSize : NULL; + + DEBUGLOG(5, "LZ4_compress_fast_continue (inputSize=%i, dictSize=%u)", inputSize, streamPtr->dictSize); + + LZ4_renormDictT(streamPtr, inputSize); /* fix index overflow */ + if (acceleration < 1) acceleration = LZ4_ACCELERATION_DEFAULT; + if (acceleration > LZ4_ACCELERATION_MAX) acceleration = LZ4_ACCELERATION_MAX; + + /* invalidate tiny dictionaries */ + if ( (streamPtr->dictSize < 4) /* tiny dictionary : not enough for a hash */ + && (dictEnd != source) /* prefix mode */ + && (inputSize > 0) /* tolerance : don't lose history, in case next invocation would use prefix mode */ + && (streamPtr->dictCtx == NULL) /* usingDictCtx */ + ) { + DEBUGLOG(5, "LZ4_compress_fast_continue: dictSize(%u) at addr:%p is too small", streamPtr->dictSize, streamPtr->dictionary); + /* remove dictionary existence from history, to employ faster prefix mode */ + streamPtr->dictSize = 0; + streamPtr->dictionary = (const BYTE*)source; + dictEnd = source; + } + + /* Check overlapping input/dictionary space */ + { const char* const sourceEnd = source + inputSize; + if ((sourceEnd > (const char*)streamPtr->dictionary) && (sourceEnd < dictEnd)) { + streamPtr->dictSize = (U32)(dictEnd - sourceEnd); + if (streamPtr->dictSize > 64 KB) streamPtr->dictSize = 64 KB; + if (streamPtr->dictSize < 4) streamPtr->dictSize = 0; + streamPtr->dictionary = (const BYTE*)dictEnd - streamPtr->dictSize; + } + } + + /* prefix mode : source data follows dictionary */ + if (dictEnd == source) { + if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) + return LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, withPrefix64k, dictSmall, acceleration); + else + return LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, withPrefix64k, noDictIssue, acceleration); + } + + /* external dictionary mode */ + { int result; + if (streamPtr->dictCtx) { + /* We depend here on the fact that dictCtx'es (produced by + * LZ4_loadDict) guarantee that their tables contain no references + * to offsets between dictCtx->currentOffset - 64 KB and + * dictCtx->currentOffset - dictCtx->dictSize. This makes it safe + * to use noDictIssue even when the dict isn't a full 64 KB. + */ + if (inputSize > 4 KB) { + /* For compressing large blobs, it is faster to pay the setup + * cost to copy the dictionary's tables into the active context, + * so that the compression loop is only looking into one table. + */ + LZ4_memcpy(streamPtr, streamPtr->dictCtx, sizeof(*streamPtr)); + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, noDictIssue, acceleration); + } else { + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingDictCtx, noDictIssue, acceleration); + } + } else { /* small data <= 4 KB */ + if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) { + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, dictSmall, acceleration); + } else { + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, noDictIssue, acceleration); + } + } + streamPtr->dictionary = (const BYTE*)source; + streamPtr->dictSize = (U32)inputSize; + return result; + } +} + + +/* Hidden debug function, to force-test external dictionary mode */ +int LZ4_compress_forceExtDict (LZ4_stream_t* LZ4_dict, const char* source, char* dest, int srcSize) +{ + LZ4_stream_t_internal* streamPtr = &LZ4_dict->internal_donotuse; + int result; + + LZ4_renormDictT(streamPtr, srcSize); + + if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) { + result = LZ4_compress_generic(streamPtr, source, dest, srcSize, NULL, 0, notLimited, byU32, usingExtDict, dictSmall, 1); + } else { + result = LZ4_compress_generic(streamPtr, source, dest, srcSize, NULL, 0, notLimited, byU32, usingExtDict, noDictIssue, 1); + } + + streamPtr->dictionary = (const BYTE*)source; + streamPtr->dictSize = (U32)srcSize; + + return result; +} + + +/*! LZ4_saveDict() : + * If previously compressed data block is not guaranteed to remain available at its memory location, + * save it into a safer place (char* safeBuffer). + * Note : no need to call LZ4_loadDict() afterwards, dictionary is immediately usable, + * one can therefore call LZ4_compress_fast_continue() right after. + * @return : saved dictionary size in bytes (necessarily <= dictSize), or 0 if error. + */ +int LZ4_saveDict (LZ4_stream_t* LZ4_dict, char* safeBuffer, int dictSize) +{ + LZ4_stream_t_internal* const dict = &LZ4_dict->internal_donotuse; + + DEBUGLOG(5, "LZ4_saveDict : dictSize=%i, safeBuffer=%p", dictSize, safeBuffer); + + if ((U32)dictSize > 64 KB) { dictSize = 64 KB; } /* useless to define a dictionary > 64 KB */ + if ((U32)dictSize > dict->dictSize) { dictSize = (int)dict->dictSize; } + + if (safeBuffer == NULL) assert(dictSize == 0); + if (dictSize > 0) { + const BYTE* const previousDictEnd = dict->dictionary + dict->dictSize; + assert(dict->dictionary); + memmove(safeBuffer, previousDictEnd - dictSize, dictSize); + } + + dict->dictionary = (const BYTE*)safeBuffer; + dict->dictSize = (U32)dictSize; + + return dictSize; +} + + + +/*-******************************* + * Decompression functions + ********************************/ + +typedef enum { endOnOutputSize = 0, endOnInputSize = 1 } endCondition_directive; +typedef enum { decode_full_block = 0, partial_decode = 1 } earlyEnd_directive; + +#undef MIN +#define MIN(a,b) ( (a) < (b) ? (a) : (b) ) + +/* Read the variable-length literal or match length. + * + * ip - pointer to use as input. + * lencheck - end ip. Return an error if ip advances >= lencheck. + * loop_check - check ip >= lencheck in body of loop. Returns loop_error if so. + * initial_check - check ip >= lencheck before start of loop. Returns initial_error if so. + * error (output) - error code. Should be set to 0 before call. + */ +typedef enum { loop_error = -2, initial_error = -1, ok = 0 } variable_length_error; +LZ4_FORCE_INLINE unsigned +read_variable_length(const BYTE**ip, const BYTE* lencheck, + int loop_check, int initial_check, + variable_length_error* error) +{ + U32 length = 0; + U32 s; + if (initial_check && unlikely((*ip) >= lencheck)) { /* overflow detection */ + *error = initial_error; + return length; + } + do { + s = **ip; + (*ip)++; + length += s; + if (loop_check && unlikely((*ip) >= lencheck)) { /* overflow detection */ + *error = loop_error; + return length; + } + } while (s==255); + + return length; +} + +/*! LZ4_decompress_generic() : + * This generic decompression function covers all use cases. + * It shall be instantiated several times, using different sets of directives. + * Note that it is important for performance that this function really get inlined, + * in order to remove useless branches during compilation optimization. + */ +LZ4_FORCE_INLINE int +LZ4_decompress_generic( + const char* const src, + char* const dst, + int srcSize, + int outputSize, /* If endOnInput==endOnInputSize, this value is `dstCapacity` */ + + endCondition_directive endOnInput, /* endOnOutputSize, endOnInputSize */ + earlyEnd_directive partialDecoding, /* full, partial */ + dict_directive dict, /* noDict, withPrefix64k, usingExtDict */ + const BYTE* const lowPrefix, /* always <= dst, == dst when no prefix */ + const BYTE* const dictStart, /* only if dict==usingExtDict */ + const size_t dictSize /* note : = 0 if noDict */ + ) +{ + if ((src == NULL) || (outputSize < 0)) { return -1; } + + { const BYTE* ip = (const BYTE*) src; + const BYTE* const iend = ip + srcSize; + + BYTE* op = (BYTE*) dst; + BYTE* const oend = op + outputSize; + BYTE* cpy; + + const BYTE* const dictEnd = (dictStart == NULL) ? NULL : dictStart + dictSize; + + const int safeDecode = (endOnInput==endOnInputSize); + const int checkOffset = ((safeDecode) && (dictSize < (int)(64 KB))); + + + /* Set up the "end" pointers for the shortcut. */ + const BYTE* const shortiend = iend - (endOnInput ? 14 : 8) /*maxLL*/ - 2 /*offset*/; + const BYTE* const shortoend = oend - (endOnInput ? 14 : 8) /*maxLL*/ - 18 /*maxML*/; + + const BYTE* match; + size_t offset; + unsigned token; + size_t length; + + + DEBUGLOG(5, "LZ4_decompress_generic (srcSize:%i, dstSize:%i)", srcSize, outputSize); + + /* Special cases */ + assert(lowPrefix <= op); + if ((endOnInput) && (unlikely(outputSize==0))) { + /* Empty output buffer */ + if (partialDecoding) return 0; + return ((srcSize==1) && (*ip==0)) ? 0 : -1; + } + if ((!endOnInput) && (unlikely(outputSize==0))) { return (*ip==0 ? 1 : -1); } + if ((endOnInput) && unlikely(srcSize==0)) { return -1; } + + /* Currently the fast loop shows a regression on qualcomm arm chips. */ +#if LZ4_FAST_DEC_LOOP + if ((oend - op) < FASTLOOP_SAFE_DISTANCE) { + DEBUGLOG(6, "skip fast decode loop"); + goto safe_decode; + } + + /* Fast loop : decode sequences as long as output < iend-FASTLOOP_SAFE_DISTANCE */ + while (1) { + /* Main fastloop assertion: We can always wildcopy FASTLOOP_SAFE_DISTANCE */ + assert(oend - op >= FASTLOOP_SAFE_DISTANCE); + if (endOnInput) { assert(ip < iend); } + token = *ip++; + length = token >> ML_BITS; /* literal length */ + + assert(!endOnInput || ip <= iend); /* ip < iend before the increment */ + + /* decode literal length */ + if (length == RUN_MASK) { + variable_length_error error = ok; + length += read_variable_length(&ip, iend-RUN_MASK, (int)endOnInput, (int)endOnInput, &error); + if (error == initial_error) { goto _output_error; } + if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)(op))) { goto _output_error; } /* overflow detection */ + if ((safeDecode) && unlikely((uptrval)(ip)+length<(uptrval)(ip))) { goto _output_error; } /* overflow detection */ + + /* copy literals */ + cpy = op+length; + LZ4_STATIC_ASSERT(MFLIMIT >= WILDCOPYLENGTH); + if (endOnInput) { /* LZ4_decompress_safe() */ + if ((cpy>oend-32) || (ip+length>iend-32)) { goto safe_literal_copy; } + LZ4_wildCopy32(op, ip, cpy); + } else { /* LZ4_decompress_fast() */ + if (cpy>oend-8) { goto safe_literal_copy; } + LZ4_wildCopy8(op, ip, cpy); /* LZ4_decompress_fast() cannot copy more than 8 bytes at a time : + * it doesn't know input length, and only relies on end-of-block properties */ + } + ip += length; op = cpy; + } else { + cpy = op+length; + if (endOnInput) { /* LZ4_decompress_safe() */ + DEBUGLOG(7, "copy %u bytes in a 16-bytes stripe", (unsigned)length); + /* We don't need to check oend, since we check it once for each loop below */ + if (ip > iend-(16 + 1/*max lit + offset + nextToken*/)) { goto safe_literal_copy; } + /* Literals can only be 14, but hope compilers optimize if we copy by a register size */ + LZ4_memcpy(op, ip, 16); + } else { /* LZ4_decompress_fast() */ + /* LZ4_decompress_fast() cannot copy more than 8 bytes at a time : + * it doesn't know input length, and relies on end-of-block properties */ + LZ4_memcpy(op, ip, 8); + if (length > 8) { LZ4_memcpy(op+8, ip+8, 8); } + } + ip += length; op = cpy; + } + + /* get offset */ + offset = LZ4_readLE16(ip); ip+=2; + match = op - offset; + assert(match <= op); + + /* get matchlength */ + length = token & ML_MASK; + + if (length == ML_MASK) { + variable_length_error error = ok; + if ((checkOffset) && (unlikely(match + dictSize < lowPrefix))) { goto _output_error; } /* Error : offset outside buffers */ + length += read_variable_length(&ip, iend - LASTLITERALS + 1, (int)endOnInput, 0, &error); + if (error != ok) { goto _output_error; } + if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)op)) { goto _output_error; } /* overflow detection */ + length += MINMATCH; + if (op + length >= oend - FASTLOOP_SAFE_DISTANCE) { + goto safe_match_copy; + } + } else { + length += MINMATCH; + if (op + length >= oend - FASTLOOP_SAFE_DISTANCE) { + goto safe_match_copy; + } + + /* Fastpath check: Avoids a branch in LZ4_wildCopy32 if true */ + if ((dict == withPrefix64k) || (match >= lowPrefix)) { + if (offset >= 8) { + assert(match >= lowPrefix); + assert(match <= op); + assert(op + 18 <= oend); + + LZ4_memcpy(op, match, 8); + LZ4_memcpy(op+8, match+8, 8); + LZ4_memcpy(op+16, match+16, 2); + op += length; + continue; + } } } + + if (checkOffset && (unlikely(match + dictSize < lowPrefix))) { goto _output_error; } /* Error : offset outside buffers */ + /* match starting within external dictionary */ + if ((dict==usingExtDict) && (match < lowPrefix)) { + if (unlikely(op+length > oend-LASTLITERALS)) { + if (partialDecoding) { + DEBUGLOG(7, "partialDecoding: dictionary match, close to dstEnd"); + length = MIN(length, (size_t)(oend-op)); + } else { + goto _output_error; /* end-of-block condition violated */ + } } + + if (length <= (size_t)(lowPrefix-match)) { + /* match fits entirely within external dictionary : just copy */ + memmove(op, dictEnd - (lowPrefix-match), length); + op += length; + } else { + /* match stretches into both external dictionary and current block */ + size_t const copySize = (size_t)(lowPrefix - match); + size_t const restSize = length - copySize; + LZ4_memcpy(op, dictEnd - copySize, copySize); + op += copySize; + if (restSize > (size_t)(op - lowPrefix)) { /* overlap copy */ + BYTE* const endOfMatch = op + restSize; + const BYTE* copyFrom = lowPrefix; + while (op < endOfMatch) { *op++ = *copyFrom++; } + } else { + LZ4_memcpy(op, lowPrefix, restSize); + op += restSize; + } } + continue; + } + + /* copy match within block */ + cpy = op + length; + + assert((op <= oend) && (oend-op >= 32)); + if (unlikely(offset<16)) { + LZ4_memcpy_using_offset(op, match, cpy, offset); + } else { + LZ4_wildCopy32(op, match, cpy); + } + + op = cpy; /* wildcopy correction */ + } + safe_decode: +#endif + + /* Main Loop : decode remaining sequences where output < FASTLOOP_SAFE_DISTANCE */ + while (1) { + token = *ip++; + length = token >> ML_BITS; /* literal length */ + + assert(!endOnInput || ip <= iend); /* ip < iend before the increment */ + + /* A two-stage shortcut for the most common case: + * 1) If the literal length is 0..14, and there is enough space, + * enter the shortcut and copy 16 bytes on behalf of the literals + * (in the fast mode, only 8 bytes can be safely copied this way). + * 2) Further if the match length is 4..18, copy 18 bytes in a similar + * manner; but we ensure that there's enough space in the output for + * those 18 bytes earlier, upon entering the shortcut (in other words, + * there is a combined check for both stages). + */ + if ( (endOnInput ? length != RUN_MASK : length <= 8) + /* strictly "less than" on input, to re-enter the loop with at least one byte */ + && likely((endOnInput ? ip < shortiend : 1) & (op <= shortoend)) ) { + /* Copy the literals */ + LZ4_memcpy(op, ip, endOnInput ? 16 : 8); + op += length; ip += length; + + /* The second stage: prepare for match copying, decode full info. + * If it doesn't work out, the info won't be wasted. */ + length = token & ML_MASK; /* match length */ + offset = LZ4_readLE16(ip); ip += 2; + match = op - offset; + assert(match <= op); /* check overflow */ + + /* Do not deal with overlapping matches. */ + if ( (length != ML_MASK) + && (offset >= 8) + && (dict==withPrefix64k || match >= lowPrefix) ) { + /* Copy the match. */ + LZ4_memcpy(op + 0, match + 0, 8); + LZ4_memcpy(op + 8, match + 8, 8); + LZ4_memcpy(op +16, match +16, 2); + op += length + MINMATCH; + /* Both stages worked, load the next token. */ + continue; + } + + /* The second stage didn't work out, but the info is ready. + * Propel it right to the point of match copying. */ + goto _copy_match; + } + + /* decode literal length */ + if (length == RUN_MASK) { + variable_length_error error = ok; + length += read_variable_length(&ip, iend-RUN_MASK, (int)endOnInput, (int)endOnInput, &error); + if (error == initial_error) { goto _output_error; } + if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)(op))) { goto _output_error; } /* overflow detection */ + if ((safeDecode) && unlikely((uptrval)(ip)+length<(uptrval)(ip))) { goto _output_error; } /* overflow detection */ + } + + /* copy literals */ + cpy = op+length; +#if LZ4_FAST_DEC_LOOP + safe_literal_copy: +#endif + LZ4_STATIC_ASSERT(MFLIMIT >= WILDCOPYLENGTH); + if ( ((endOnInput) && ((cpy>oend-MFLIMIT) || (ip+length>iend-(2+1+LASTLITERALS))) ) + || ((!endOnInput) && (cpy>oend-WILDCOPYLENGTH)) ) + { + /* We've either hit the input parsing restriction or the output parsing restriction. + * In the normal scenario, decoding a full block, it must be the last sequence, + * otherwise it's an error (invalid input or dimensions). + * In partialDecoding scenario, it's necessary to ensure there is no buffer overflow. + */ + if (partialDecoding) { + /* Since we are partial decoding we may be in this block because of the output parsing + * restriction, which is not valid since the output buffer is allowed to be undersized. + */ + assert(endOnInput); + DEBUGLOG(7, "partialDecoding: copying literals, close to input or output end") + DEBUGLOG(7, "partialDecoding: literal length = %u", (unsigned)length); + DEBUGLOG(7, "partialDecoding: remaining space in dstBuffer : %i", (int)(oend - op)); + DEBUGLOG(7, "partialDecoding: remaining space in srcBuffer : %i", (int)(iend - ip)); + /* Finishing in the middle of a literals segment, + * due to lack of input. + */ + if (ip+length > iend) { + length = (size_t)(iend-ip); + cpy = op + length; + } + /* Finishing in the middle of a literals segment, + * due to lack of output space. + */ + if (cpy > oend) { + cpy = oend; + assert(op<=oend); + length = (size_t)(oend-op); + } + } else { + /* We must be on the last sequence because of the parsing limitations so check + * that we exactly regenerate the original size (must be exact when !endOnInput). + */ + if ((!endOnInput) && (cpy != oend)) { goto _output_error; } + /* We must be on the last sequence (or invalid) because of the parsing limitations + * so check that we exactly consume the input and don't overrun the output buffer. + */ + if ((endOnInput) && ((ip+length != iend) || (cpy > oend))) { + DEBUGLOG(6, "should have been last run of literals") + DEBUGLOG(6, "ip(%p) + length(%i) = %p != iend (%p)", ip, (int)length, ip+length, iend); + DEBUGLOG(6, "or cpy(%p) > oend(%p)", cpy, oend); + goto _output_error; + } + } + memmove(op, ip, length); /* supports overlapping memory regions; only matters for in-place decompression scenarios */ + ip += length; + op += length; + /* Necessarily EOF when !partialDecoding. + * When partialDecoding, it is EOF if we've either + * filled the output buffer or + * can't proceed with reading an offset for following match. + */ + if (!partialDecoding || (cpy == oend) || (ip >= (iend-2))) { + break; + } + } else { + LZ4_wildCopy8(op, ip, cpy); /* may overwrite up to WILDCOPYLENGTH beyond cpy */ + ip += length; op = cpy; + } + + /* get offset */ + offset = LZ4_readLE16(ip); ip+=2; + match = op - offset; + + /* get matchlength */ + length = token & ML_MASK; + + _copy_match: + if (length == ML_MASK) { + variable_length_error error = ok; + length += read_variable_length(&ip, iend - LASTLITERALS + 1, (int)endOnInput, 0, &error); + if (error != ok) goto _output_error; + if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)op)) goto _output_error; /* overflow detection */ + } + length += MINMATCH; + +#if LZ4_FAST_DEC_LOOP + safe_match_copy: +#endif + if ((checkOffset) && (unlikely(match + dictSize < lowPrefix))) goto _output_error; /* Error : offset outside buffers */ + /* match starting within external dictionary */ + if ((dict==usingExtDict) && (match < lowPrefix)) { + if (unlikely(op+length > oend-LASTLITERALS)) { + if (partialDecoding) length = MIN(length, (size_t)(oend-op)); + else goto _output_error; /* doesn't respect parsing restriction */ + } + + if (length <= (size_t)(lowPrefix-match)) { + /* match fits entirely within external dictionary : just copy */ + memmove(op, dictEnd - (lowPrefix-match), length); + op += length; + } else { + /* match stretches into both external dictionary and current block */ + size_t const copySize = (size_t)(lowPrefix - match); + size_t const restSize = length - copySize; + LZ4_memcpy(op, dictEnd - copySize, copySize); + op += copySize; + if (restSize > (size_t)(op - lowPrefix)) { /* overlap copy */ + BYTE* const endOfMatch = op + restSize; + const BYTE* copyFrom = lowPrefix; + while (op < endOfMatch) *op++ = *copyFrom++; + } else { + LZ4_memcpy(op, lowPrefix, restSize); + op += restSize; + } } + continue; + } + assert(match >= lowPrefix); + + /* copy match within block */ + cpy = op + length; + + /* partialDecoding : may end anywhere within the block */ + assert(op<=oend); + if (partialDecoding && (cpy > oend-MATCH_SAFEGUARD_DISTANCE)) { + size_t const mlen = MIN(length, (size_t)(oend-op)); + const BYTE* const matchEnd = match + mlen; + BYTE* const copyEnd = op + mlen; + if (matchEnd > op) { /* overlap copy */ + while (op < copyEnd) { *op++ = *match++; } + } else { + LZ4_memcpy(op, match, mlen); + } + op = copyEnd; + if (op == oend) { break; } + continue; + } + + if (unlikely(offset<8)) { + LZ4_write32(op, 0); /* silence msan warning when offset==0 */ + op[0] = match[0]; + op[1] = match[1]; + op[2] = match[2]; + op[3] = match[3]; + match += inc32table[offset]; + LZ4_memcpy(op+4, match, 4); + match -= dec64table[offset]; + } else { + LZ4_memcpy(op, match, 8); + match += 8; + } + op += 8; + + if (unlikely(cpy > oend-MATCH_SAFEGUARD_DISTANCE)) { + BYTE* const oCopyLimit = oend - (WILDCOPYLENGTH-1); + if (cpy > oend-LASTLITERALS) { goto _output_error; } /* Error : last LASTLITERALS bytes must be literals (uncompressed) */ + if (op < oCopyLimit) { + LZ4_wildCopy8(op, match, oCopyLimit); + match += oCopyLimit - op; + op = oCopyLimit; + } + while (op < cpy) { *op++ = *match++; } + } else { + LZ4_memcpy(op, match, 8); + if (length > 16) { LZ4_wildCopy8(op+8, match+8, cpy); } + } + op = cpy; /* wildcopy correction */ + } + + /* end of decoding */ + if (endOnInput) { + DEBUGLOG(5, "decoded %i bytes", (int) (((char*)op)-dst)); + return (int) (((char*)op)-dst); /* Nb of output bytes decoded */ + } else { + return (int) (((const char*)ip)-src); /* Nb of input bytes read */ + } + + /* Overflow error detected */ + _output_error: + return (int) (-(((const char*)ip)-src))-1; + } +} + + +/*===== Instantiate the API decoding functions. =====*/ + +LZ4_FORCE_O2 +int LZ4_decompress_safe(const char* source, char* dest, int compressedSize, int maxDecompressedSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxDecompressedSize, + endOnInputSize, decode_full_block, noDict, + (BYTE*)dest, NULL, 0); +} + +LZ4_FORCE_O2 +int LZ4_decompress_safe_partial(const char* src, char* dst, int compressedSize, int targetOutputSize, int dstCapacity) +{ + dstCapacity = MIN(targetOutputSize, dstCapacity); + return LZ4_decompress_generic(src, dst, compressedSize, dstCapacity, + endOnInputSize, partial_decode, + noDict, (BYTE*)dst, NULL, 0); +} + +LZ4_FORCE_O2 +int LZ4_decompress_fast(const char* source, char* dest, int originalSize) +{ + return LZ4_decompress_generic(source, dest, 0, originalSize, + endOnOutputSize, decode_full_block, withPrefix64k, + (BYTE*)dest - 64 KB, NULL, 0); +} + +/*===== Instantiate a few more decoding cases, used more than once. =====*/ + +LZ4_FORCE_O2 /* Exported, an obsolete API function. */ +int LZ4_decompress_safe_withPrefix64k(const char* source, char* dest, int compressedSize, int maxOutputSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + endOnInputSize, decode_full_block, withPrefix64k, + (BYTE*)dest - 64 KB, NULL, 0); +} + +/* Another obsolete API function, paired with the previous one. */ +int LZ4_decompress_fast_withPrefix64k(const char* source, char* dest, int originalSize) +{ + /* LZ4_decompress_fast doesn't validate match offsets, + * and thus serves well with any prefixed dictionary. */ + return LZ4_decompress_fast(source, dest, originalSize); +} + +LZ4_FORCE_O2 +static int LZ4_decompress_safe_withSmallPrefix(const char* source, char* dest, int compressedSize, int maxOutputSize, + size_t prefixSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + endOnInputSize, decode_full_block, noDict, + (BYTE*)dest-prefixSize, NULL, 0); +} + +LZ4_FORCE_O2 +int LZ4_decompress_safe_forceExtDict(const char* source, char* dest, + int compressedSize, int maxOutputSize, + const void* dictStart, size_t dictSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + endOnInputSize, decode_full_block, usingExtDict, + (BYTE*)dest, (const BYTE*)dictStart, dictSize); +} + +LZ4_FORCE_O2 +static int LZ4_decompress_fast_extDict(const char* source, char* dest, int originalSize, + const void* dictStart, size_t dictSize) +{ + return LZ4_decompress_generic(source, dest, 0, originalSize, + endOnOutputSize, decode_full_block, usingExtDict, + (BYTE*)dest, (const BYTE*)dictStart, dictSize); +} + +/* The "double dictionary" mode, for use with e.g. ring buffers: the first part + * of the dictionary is passed as prefix, and the second via dictStart + dictSize. + * These routines are used only once, in LZ4_decompress_*_continue(). + */ +LZ4_FORCE_INLINE +int LZ4_decompress_safe_doubleDict(const char* source, char* dest, int compressedSize, int maxOutputSize, + size_t prefixSize, const void* dictStart, size_t dictSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + endOnInputSize, decode_full_block, usingExtDict, + (BYTE*)dest-prefixSize, (const BYTE*)dictStart, dictSize); +} + +LZ4_FORCE_INLINE +int LZ4_decompress_fast_doubleDict(const char* source, char* dest, int originalSize, + size_t prefixSize, const void* dictStart, size_t dictSize) +{ + return LZ4_decompress_generic(source, dest, 0, originalSize, + endOnOutputSize, decode_full_block, usingExtDict, + (BYTE*)dest-prefixSize, (const BYTE*)dictStart, dictSize); +} + +/*===== streaming decompression functions =====*/ + +LZ4_streamDecode_t* LZ4_createStreamDecode(void) +{ + LZ4_streamDecode_t* lz4s = (LZ4_streamDecode_t*) ALLOC_AND_ZERO(sizeof(LZ4_streamDecode_t)); + LZ4_STATIC_ASSERT(LZ4_STREAMDECODESIZE >= sizeof(LZ4_streamDecode_t_internal)); /* A compilation error here means LZ4_STREAMDECODESIZE is not large enough */ + return lz4s; +} + +int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream) +{ + if (LZ4_stream == NULL) { return 0; } /* support free on NULL */ + FREEMEM(LZ4_stream); + return 0; +} + +/*! LZ4_setStreamDecode() : + * Use this function to instruct where to find the dictionary. + * This function is not necessary if previous data is still available where it was decoded. + * Loading a size of 0 is allowed (same effect as no dictionary). + * @return : 1 if OK, 0 if error + */ +int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize) +{ + LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse; + lz4sd->prefixSize = (size_t)dictSize; + if (dictSize) { + assert(dictionary != NULL); + lz4sd->prefixEnd = (const BYTE*) dictionary + dictSize; + } else { + lz4sd->prefixEnd = (const BYTE*) dictionary; + } + lz4sd->externalDict = NULL; + lz4sd->extDictSize = 0; + return 1; +} + +/*! LZ4_decoderRingBufferSize() : + * when setting a ring buffer for streaming decompression (optional scenario), + * provides the minimum size of this ring buffer + * to be compatible with any source respecting maxBlockSize condition. + * Note : in a ring buffer scenario, + * blocks are presumed decompressed next to each other. + * When not enough space remains for next block (remainingSize < maxBlockSize), + * decoding resumes from beginning of ring buffer. + * @return : minimum ring buffer size, + * or 0 if there is an error (invalid maxBlockSize). + */ +int LZ4_decoderRingBufferSize(int maxBlockSize) +{ + if (maxBlockSize < 0) return 0; + if (maxBlockSize > LZ4_MAX_INPUT_SIZE) return 0; + if (maxBlockSize < 16) maxBlockSize = 16; + return LZ4_DECODER_RING_BUFFER_SIZE(maxBlockSize); +} + +/* +*_continue() : + These decoding functions allow decompression of multiple blocks in "streaming" mode. + Previously decoded blocks must still be available at the memory position where they were decoded. + If it's not possible, save the relevant part of decoded data into a safe buffer, + and indicate where it stands using LZ4_setStreamDecode() +*/ +LZ4_FORCE_O2 +int LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* source, char* dest, int compressedSize, int maxOutputSize) +{ + LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse; + int result; + + if (lz4sd->prefixSize == 0) { + /* The first call, no dictionary yet. */ + assert(lz4sd->extDictSize == 0); + result = LZ4_decompress_safe(source, dest, compressedSize, maxOutputSize); + if (result <= 0) return result; + lz4sd->prefixSize = (size_t)result; + lz4sd->prefixEnd = (BYTE*)dest + result; + } else if (lz4sd->prefixEnd == (BYTE*)dest) { + /* They're rolling the current segment. */ + if (lz4sd->prefixSize >= 64 KB - 1) + result = LZ4_decompress_safe_withPrefix64k(source, dest, compressedSize, maxOutputSize); + else if (lz4sd->extDictSize == 0) + result = LZ4_decompress_safe_withSmallPrefix(source, dest, compressedSize, maxOutputSize, + lz4sd->prefixSize); + else + result = LZ4_decompress_safe_doubleDict(source, dest, compressedSize, maxOutputSize, + lz4sd->prefixSize, lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize += (size_t)result; + lz4sd->prefixEnd += result; + } else { + /* The buffer wraps around, or they're switching to another buffer. */ + lz4sd->extDictSize = lz4sd->prefixSize; + lz4sd->externalDict = lz4sd->prefixEnd - lz4sd->extDictSize; + result = LZ4_decompress_safe_forceExtDict(source, dest, compressedSize, maxOutputSize, + lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize = (size_t)result; + lz4sd->prefixEnd = (BYTE*)dest + result; + } + + return result; +} + +LZ4_FORCE_O2 +int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* source, char* dest, int originalSize) +{ + LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse; + int result; + assert(originalSize >= 0); + + if (lz4sd->prefixSize == 0) { + assert(lz4sd->extDictSize == 0); + result = LZ4_decompress_fast(source, dest, originalSize); + if (result <= 0) return result; + lz4sd->prefixSize = (size_t)originalSize; + lz4sd->prefixEnd = (BYTE*)dest + originalSize; + } else if (lz4sd->prefixEnd == (BYTE*)dest) { + if (lz4sd->prefixSize >= 64 KB - 1 || lz4sd->extDictSize == 0) + result = LZ4_decompress_fast(source, dest, originalSize); + else + result = LZ4_decompress_fast_doubleDict(source, dest, originalSize, + lz4sd->prefixSize, lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize += (size_t)originalSize; + lz4sd->prefixEnd += originalSize; + } else { + lz4sd->extDictSize = lz4sd->prefixSize; + lz4sd->externalDict = lz4sd->prefixEnd - lz4sd->extDictSize; + result = LZ4_decompress_fast_extDict(source, dest, originalSize, + lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize = (size_t)originalSize; + lz4sd->prefixEnd = (BYTE*)dest + originalSize; + } + + return result; +} + + +/* +Advanced decoding functions : +*_usingDict() : + These decoding functions work the same as "_continue" ones, + the dictionary must be explicitly provided within parameters +*/ + +int LZ4_decompress_safe_usingDict(const char* source, char* dest, int compressedSize, int maxOutputSize, const char* dictStart, int dictSize) +{ + if (dictSize==0) + return LZ4_decompress_safe(source, dest, compressedSize, maxOutputSize); + if (dictStart+dictSize == dest) { + if (dictSize >= 64 KB - 1) { + return LZ4_decompress_safe_withPrefix64k(source, dest, compressedSize, maxOutputSize); + } + assert(dictSize >= 0); + return LZ4_decompress_safe_withSmallPrefix(source, dest, compressedSize, maxOutputSize, (size_t)dictSize); + } + assert(dictSize >= 0); + return LZ4_decompress_safe_forceExtDict(source, dest, compressedSize, maxOutputSize, dictStart, (size_t)dictSize); +} + +int LZ4_decompress_fast_usingDict(const char* source, char* dest, int originalSize, const char* dictStart, int dictSize) +{ + if (dictSize==0 || dictStart+dictSize == dest) + return LZ4_decompress_fast(source, dest, originalSize); + assert(dictSize >= 0); + return LZ4_decompress_fast_extDict(source, dest, originalSize, dictStart, (size_t)dictSize); +} + + +/*=************************************************* +* Obsolete Functions +***************************************************/ +/* obsolete compression functions */ +int LZ4_compress_limitedOutput(const char* source, char* dest, int inputSize, int maxOutputSize) +{ + return LZ4_compress_default(source, dest, inputSize, maxOutputSize); +} +int LZ4_compress(const char* src, char* dest, int srcSize) +{ + return LZ4_compress_default(src, dest, srcSize, LZ4_compressBound(srcSize)); +} +int LZ4_compress_limitedOutput_withState (void* state, const char* src, char* dst, int srcSize, int dstSize) +{ + return LZ4_compress_fast_extState(state, src, dst, srcSize, dstSize, 1); +} +int LZ4_compress_withState (void* state, const char* src, char* dst, int srcSize) +{ + return LZ4_compress_fast_extState(state, src, dst, srcSize, LZ4_compressBound(srcSize), 1); +} +int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_stream, const char* src, char* dst, int srcSize, int dstCapacity) +{ + return LZ4_compress_fast_continue(LZ4_stream, src, dst, srcSize, dstCapacity, 1); +} +int LZ4_compress_continue (LZ4_stream_t* LZ4_stream, const char* source, char* dest, int inputSize) +{ + return LZ4_compress_fast_continue(LZ4_stream, source, dest, inputSize, LZ4_compressBound(inputSize), 1); +} + +/* +These decompression functions are deprecated and should no longer be used. +They are only provided here for compatibility with older user programs. +- LZ4_uncompress is totally equivalent to LZ4_decompress_fast +- LZ4_uncompress_unknownOutputSize is totally equivalent to LZ4_decompress_safe +*/ +int LZ4_uncompress (const char* source, char* dest, int outputSize) +{ + return LZ4_decompress_fast(source, dest, outputSize); +} +int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize) +{ + return LZ4_decompress_safe(source, dest, isize, maxOutputSize); +} + +/* Obsolete Streaming functions */ + +int LZ4_sizeofStreamState(void) { return LZ4_STREAMSIZE; } + +int LZ4_resetStreamState(void* state, char* inputBuffer) +{ + (void)inputBuffer; + LZ4_resetStream((LZ4_stream_t*)state); + return 0; +} + +void* LZ4_create (char* inputBuffer) +{ + (void)inputBuffer; + return LZ4_createStream(); +} + +char* LZ4_slideInputBuffer (void* state) +{ + /* avoid const char * -> char * conversion warning */ + return (char *)(uptrval)((LZ4_stream_t*)state)->internal_donotuse.dictionary; +} + +#endif /* LZ4_COMMONDEFS_ONLY */ diff --git a/libCompression/lz4.h b/libCompression/lz4.h new file mode 100644 index 0000000..a520adc --- /dev/null +++ b/libCompression/lz4.h @@ -0,0 +1,774 @@ +/* + * LZ4 - Fast LZ compression algorithm + * Header File + * Copyright (C) 2011-2020, Yann Collet. + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 homepage : http://www.lz4.org + - LZ4 source repository : https://github.com/lz4/lz4 +*/ +#if defined (__cplusplus) +extern "C" { +#endif + +#ifndef LZ4_H_2983827168210 +#define LZ4_H_2983827168210 + +/* --- Dependency --- */ +#include /* size_t */ + + +/** + Introduction + + LZ4 is lossless compression algorithm, providing compression speed >500 MB/s per core, + scalable with multi-cores CPU. It features an extremely fast decoder, with speed in + multiple GB/s per core, typically reaching RAM speed limits on multi-core systems. + + The LZ4 compression library provides in-memory compression and decompression functions. + It gives full buffer control to user. + Compression can be done in: + - a single step (described as Simple Functions) + - a single step, reusing a context (described in Advanced Functions) + - unbounded multiple steps (described as Streaming compression) + + lz4.h generates and decodes LZ4-compressed blocks (doc/lz4_Block_format.md). + Decompressing such a compressed block requires additional metadata. + Exact metadata depends on exact decompression function. + For the typical case of LZ4_decompress_safe(), + metadata includes block's compressed size, and maximum bound of decompressed size. + Each application is free to encode and pass such metadata in whichever way it wants. + + lz4.h only handle blocks, it can not generate Frames. + + Blocks are different from Frames (doc/lz4_Frame_format.md). + Frames bundle both blocks and metadata in a specified manner. + Embedding metadata is required for compressed data to be self-contained and portable. + Frame format is delivered through a companion API, declared in lz4frame.h. + The `lz4` CLI can only manage frames. +*/ + +/*^*************************************************************** +* Export parameters +*****************************************************************/ +/* +* LZ4_DLL_EXPORT : +* Enable exporting of functions when building a Windows DLL +* LZ4LIB_VISIBILITY : +* Control library symbols visibility. +*/ +#ifndef LZ4LIB_VISIBILITY +# if defined(__GNUC__) && (__GNUC__ >= 4) +# define LZ4LIB_VISIBILITY __attribute__ ((visibility ("default"))) +# else +# define LZ4LIB_VISIBILITY +# endif +#endif +#if defined(LZ4_DLL_EXPORT) && (LZ4_DLL_EXPORT==1) +# define LZ4LIB_API __declspec(dllexport) LZ4LIB_VISIBILITY +#elif defined(LZ4_DLL_IMPORT) && (LZ4_DLL_IMPORT==1) +# define LZ4LIB_API __declspec(dllimport) LZ4LIB_VISIBILITY /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/ +#else +# define LZ4LIB_API LZ4LIB_VISIBILITY +#endif + +/*------ Version ------*/ +#define LZ4_VERSION_MAJOR 1 /* for breaking interface changes */ +#define LZ4_VERSION_MINOR 9 /* for new (non-breaking) interface capabilities */ +#define LZ4_VERSION_RELEASE 3 /* for tweaks, bug-fixes, or development */ + +#define LZ4_VERSION_NUMBER (LZ4_VERSION_MAJOR *100*100 + LZ4_VERSION_MINOR *100 + LZ4_VERSION_RELEASE) + +#define LZ4_LIB_VERSION LZ4_VERSION_MAJOR.LZ4_VERSION_MINOR.LZ4_VERSION_RELEASE +#define LZ4_QUOTE(str) #str +#define LZ4_EXPAND_AND_QUOTE(str) LZ4_QUOTE(str) +#define LZ4_VERSION_STRING LZ4_EXPAND_AND_QUOTE(LZ4_LIB_VERSION) + +LZ4LIB_API int LZ4_versionNumber (void); /**< library version number; useful to check dll version */ +LZ4LIB_API const char* LZ4_versionString (void); /**< library version string; useful to check dll version */ + + +/*-************************************ +* Tuning parameter +**************************************/ +/*! + * LZ4_MEMORY_USAGE : + * Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB; etc.) + * Increasing memory usage improves compression ratio. + * Reduced memory usage may improve speed, thanks to better cache locality. + * Default value is 14, for 16KB, which nicely fits into Intel x86 L1 cache + */ +#ifndef LZ4_MEMORY_USAGE +# define LZ4_MEMORY_USAGE 14 +#endif + + +/*-************************************ +* Simple Functions +**************************************/ +/*! LZ4_compress_default() : + * Compresses 'srcSize' bytes from buffer 'src' + * into already allocated 'dst' buffer of size 'dstCapacity'. + * Compression is guaranteed to succeed if 'dstCapacity' >= LZ4_compressBound(srcSize). + * It also runs faster, so it's a recommended setting. + * If the function cannot compress 'src' into a more limited 'dst' budget, + * compression stops *immediately*, and the function result is zero. + * In which case, 'dst' content is undefined (invalid). + * srcSize : max supported value is LZ4_MAX_INPUT_SIZE. + * dstCapacity : size of buffer 'dst' (which must be already allocated) + * @return : the number of bytes written into buffer 'dst' (necessarily <= dstCapacity) + * or 0 if compression fails + * Note : This function is protected against buffer overflow scenarios (never writes outside 'dst' buffer, nor read outside 'source' buffer). + */ +LZ4LIB_API int LZ4_compress_default(const char* src, char* dst, int srcSize, int dstCapacity); + +/*! LZ4_decompress_safe() : + * compressedSize : is the exact complete size of the compressed block. + * dstCapacity : is the size of destination buffer (which must be already allocated), presumed an upper bound of decompressed size. + * @return : the number of bytes decompressed into destination buffer (necessarily <= dstCapacity) + * If destination buffer is not large enough, decoding will stop and output an error code (negative value). + * If the source stream is detected malformed, the function will stop decoding and return a negative result. + * Note 1 : This function is protected against malicious data packets : + * it will never writes outside 'dst' buffer, nor read outside 'source' buffer, + * even if the compressed block is maliciously modified to order the decoder to do these actions. + * In such case, the decoder stops immediately, and considers the compressed block malformed. + * Note 2 : compressedSize and dstCapacity must be provided to the function, the compressed block does not contain them. + * The implementation is free to send / store / derive this information in whichever way is most beneficial. + * If there is a need for a different format which bundles together both compressed data and its metadata, consider looking at lz4frame.h instead. + */ +LZ4LIB_API int LZ4_decompress_safe (const char* src, char* dst, int compressedSize, int dstCapacity); + + +/*-************************************ +* Advanced Functions +**************************************/ +#define LZ4_MAX_INPUT_SIZE 0x7E000000 /* 2 113 929 216 bytes */ +#define LZ4_COMPRESSBOUND(isize) ((unsigned)(isize) > (unsigned)LZ4_MAX_INPUT_SIZE ? 0 : (isize) + ((isize)/255) + 16) + +/*! LZ4_compressBound() : + Provides the maximum size that LZ4 compression may output in a "worst case" scenario (input data not compressible) + This function is primarily useful for memory allocation purposes (destination buffer size). + Macro LZ4_COMPRESSBOUND() is also provided for compilation-time evaluation (stack memory allocation for example). + Note that LZ4_compress_default() compresses faster when dstCapacity is >= LZ4_compressBound(srcSize) + inputSize : max supported value is LZ4_MAX_INPUT_SIZE + return : maximum output size in a "worst case" scenario + or 0, if input size is incorrect (too large or negative) +*/ +LZ4LIB_API int LZ4_compressBound(int inputSize); + +/*! LZ4_compress_fast() : + Same as LZ4_compress_default(), but allows selection of "acceleration" factor. + The larger the acceleration value, the faster the algorithm, but also the lesser the compression. + It's a trade-off. It can be fine tuned, with each successive value providing roughly +~3% to speed. + An acceleration value of "1" is the same as regular LZ4_compress_default() + Values <= 0 will be replaced by LZ4_ACCELERATION_DEFAULT (currently == 1, see lz4.c). + Values > LZ4_ACCELERATION_MAX will be replaced by LZ4_ACCELERATION_MAX (currently == 65537, see lz4.c). +*/ +LZ4LIB_API int LZ4_compress_fast (const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + + +/*! LZ4_compress_fast_extState() : + * Same as LZ4_compress_fast(), using an externally allocated memory space for its state. + * Use LZ4_sizeofState() to know how much memory must be allocated, + * and allocate it on 8-bytes boundaries (using `malloc()` typically). + * Then, provide this buffer as `void* state` to compression function. + */ +LZ4LIB_API int LZ4_sizeofState(void); +LZ4LIB_API int LZ4_compress_fast_extState (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + + +/*! LZ4_compress_destSize() : + * Reverse the logic : compresses as much data as possible from 'src' buffer + * into already allocated buffer 'dst', of size >= 'targetDestSize'. + * This function either compresses the entire 'src' content into 'dst' if it's large enough, + * or fill 'dst' buffer completely with as much data as possible from 'src'. + * note: acceleration parameter is fixed to "default". + * + * *srcSizePtr : will be modified to indicate how many bytes where read from 'src' to fill 'dst'. + * New value is necessarily <= input value. + * @return : Nb bytes written into 'dst' (necessarily <= targetDestSize) + * or 0 if compression fails. + * + * Note : from v1.8.2 to v1.9.1, this function had a bug (fixed un v1.9.2+): + * the produced compressed content could, in specific circumstances, + * require to be decompressed into a destination buffer larger + * by at least 1 byte than the content to decompress. + * If an application uses `LZ4_compress_destSize()`, + * it's highly recommended to update liblz4 to v1.9.2 or better. + * If this can't be done or ensured, + * the receiving decompression function should provide + * a dstCapacity which is > decompressedSize, by at least 1 byte. + * See https://github.com/lz4/lz4/issues/859 for details + */ +LZ4LIB_API int LZ4_compress_destSize (const char* src, char* dst, int* srcSizePtr, int targetDstSize); + + +/*! LZ4_decompress_safe_partial() : + * Decompress an LZ4 compressed block, of size 'srcSize' at position 'src', + * into destination buffer 'dst' of size 'dstCapacity'. + * Up to 'targetOutputSize' bytes will be decoded. + * The function stops decoding on reaching this objective. + * This can be useful to boost performance + * whenever only the beginning of a block is required. + * + * @return : the number of bytes decoded in `dst` (necessarily <= targetOutputSize) + * If source stream is detected malformed, function returns a negative result. + * + * Note 1 : @return can be < targetOutputSize, if compressed block contains less data. + * + * Note 2 : targetOutputSize must be <= dstCapacity + * + * Note 3 : this function effectively stops decoding on reaching targetOutputSize, + * so dstCapacity is kind of redundant. + * This is because in older versions of this function, + * decoding operation would still write complete sequences. + * Therefore, there was no guarantee that it would stop writing at exactly targetOutputSize, + * it could write more bytes, though only up to dstCapacity. + * Some "margin" used to be required for this operation to work properly. + * Thankfully, this is no longer necessary. + * The function nonetheless keeps the same signature, in an effort to preserve API compatibility. + * + * Note 4 : If srcSize is the exact size of the block, + * then targetOutputSize can be any value, + * including larger than the block's decompressed size. + * The function will, at most, generate block's decompressed size. + * + * Note 5 : If srcSize is _larger_ than block's compressed size, + * then targetOutputSize **MUST** be <= block's decompressed size. + * Otherwise, *silent corruption will occur*. + */ +LZ4LIB_API int LZ4_decompress_safe_partial (const char* src, char* dst, int srcSize, int targetOutputSize, int dstCapacity); + + +/*-********************************************* +* Streaming Compression Functions +***********************************************/ +typedef union LZ4_stream_u LZ4_stream_t; /* incomplete type (defined later) */ + +LZ4LIB_API LZ4_stream_t* LZ4_createStream(void); +LZ4LIB_API int LZ4_freeStream (LZ4_stream_t* streamPtr); + +/*! LZ4_resetStream_fast() : v1.9.0+ + * Use this to prepare an LZ4_stream_t for a new chain of dependent blocks + * (e.g., LZ4_compress_fast_continue()). + * + * An LZ4_stream_t must be initialized once before usage. + * This is automatically done when created by LZ4_createStream(). + * However, should the LZ4_stream_t be simply declared on stack (for example), + * it's necessary to initialize it first, using LZ4_initStream(). + * + * After init, start any new stream with LZ4_resetStream_fast(). + * A same LZ4_stream_t can be re-used multiple times consecutively + * and compress multiple streams, + * provided that it starts each new stream with LZ4_resetStream_fast(). + * + * LZ4_resetStream_fast() is much faster than LZ4_initStream(), + * but is not compatible with memory regions containing garbage data. + * + * Note: it's only useful to call LZ4_resetStream_fast() + * in the context of streaming compression. + * The *extState* functions perform their own resets. + * Invoking LZ4_resetStream_fast() before is redundant, and even counterproductive. + */ +LZ4LIB_API void LZ4_resetStream_fast (LZ4_stream_t* streamPtr); + +/*! LZ4_loadDict() : + * Use this function to reference a static dictionary into LZ4_stream_t. + * The dictionary must remain available during compression. + * LZ4_loadDict() triggers a reset, so any previous data will be forgotten. + * The same dictionary will have to be loaded on decompression side for successful decoding. + * Dictionary are useful for better compression of small data (KB range). + * While LZ4 accept any input as dictionary, + * results are generally better when using Zstandard's Dictionary Builder. + * Loading a size of 0 is allowed, and is the same as reset. + * @return : loaded dictionary size, in bytes (necessarily <= 64 KB) + */ +LZ4LIB_API int LZ4_loadDict (LZ4_stream_t* streamPtr, const char* dictionary, int dictSize); + +/*! LZ4_compress_fast_continue() : + * Compress 'src' content using data from previously compressed blocks, for better compression ratio. + * 'dst' buffer must be already allocated. + * If dstCapacity >= LZ4_compressBound(srcSize), compression is guaranteed to succeed, and runs faster. + * + * @return : size of compressed block + * or 0 if there is an error (typically, cannot fit into 'dst'). + * + * Note 1 : Each invocation to LZ4_compress_fast_continue() generates a new block. + * Each block has precise boundaries. + * Each block must be decompressed separately, calling LZ4_decompress_*() with relevant metadata. + * It's not possible to append blocks together and expect a single invocation of LZ4_decompress_*() to decompress them together. + * + * Note 2 : The previous 64KB of source data is __assumed__ to remain present, unmodified, at same address in memory ! + * + * Note 3 : When input is structured as a double-buffer, each buffer can have any size, including < 64 KB. + * Make sure that buffers are separated, by at least one byte. + * This construction ensures that each block only depends on previous block. + * + * Note 4 : If input buffer is a ring-buffer, it can have any size, including < 64 KB. + * + * Note 5 : After an error, the stream status is undefined (invalid), it can only be reset or freed. + */ +LZ4LIB_API int LZ4_compress_fast_continue (LZ4_stream_t* streamPtr, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + +/*! LZ4_saveDict() : + * If last 64KB data cannot be guaranteed to remain available at its current memory location, + * save it into a safer place (char* safeBuffer). + * This is schematically equivalent to a memcpy() followed by LZ4_loadDict(), + * but is much faster, because LZ4_saveDict() doesn't need to rebuild tables. + * @return : saved dictionary size in bytes (necessarily <= maxDictSize), or 0 if error. + */ +LZ4LIB_API int LZ4_saveDict (LZ4_stream_t* streamPtr, char* safeBuffer, int maxDictSize); + + +/*-********************************************** +* Streaming Decompression Functions +* Bufferless synchronous API +************************************************/ +typedef union LZ4_streamDecode_u LZ4_streamDecode_t; /* tracking context */ + +/*! LZ4_createStreamDecode() and LZ4_freeStreamDecode() : + * creation / destruction of streaming decompression tracking context. + * A tracking context can be re-used multiple times. + */ +LZ4LIB_API LZ4_streamDecode_t* LZ4_createStreamDecode(void); +LZ4LIB_API int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream); + +/*! LZ4_setStreamDecode() : + * An LZ4_streamDecode_t context can be allocated once and re-used multiple times. + * Use this function to start decompression of a new stream of blocks. + * A dictionary can optionally be set. Use NULL or size 0 for a reset order. + * Dictionary is presumed stable : it must remain accessible and unmodified during next decompression. + * @return : 1 if OK, 0 if error + */ +LZ4LIB_API int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize); + +/*! LZ4_decoderRingBufferSize() : v1.8.2+ + * Note : in a ring buffer scenario (optional), + * blocks are presumed decompressed next to each other + * up to the moment there is not enough remaining space for next block (remainingSize < maxBlockSize), + * at which stage it resumes from beginning of ring buffer. + * When setting such a ring buffer for streaming decompression, + * provides the minimum size of this ring buffer + * to be compatible with any source respecting maxBlockSize condition. + * @return : minimum ring buffer size, + * or 0 if there is an error (invalid maxBlockSize). + */ +LZ4LIB_API int LZ4_decoderRingBufferSize(int maxBlockSize); +#define LZ4_DECODER_RING_BUFFER_SIZE(maxBlockSize) (65536 + 14 + (maxBlockSize)) /* for static allocation; maxBlockSize presumed valid */ + +/*! LZ4_decompress_*_continue() : + * These decoding functions allow decompression of consecutive blocks in "streaming" mode. + * A block is an unsplittable entity, it must be presented entirely to a decompression function. + * Decompression functions only accepts one block at a time. + * The last 64KB of previously decoded data *must* remain available and unmodified at the memory position where they were decoded. + * If less than 64KB of data has been decoded, all the data must be present. + * + * Special : if decompression side sets a ring buffer, it must respect one of the following conditions : + * - Decompression buffer size is _at least_ LZ4_decoderRingBufferSize(maxBlockSize). + * maxBlockSize is the maximum size of any single block. It can have any value > 16 bytes. + * In which case, encoding and decoding buffers do not need to be synchronized. + * Actually, data can be produced by any source compliant with LZ4 format specification, and respecting maxBlockSize. + * - Synchronized mode : + * Decompression buffer size is _exactly_ the same as compression buffer size, + * and follows exactly same update rule (block boundaries at same positions), + * and decoding function is provided with exact decompressed size of each block (exception for last block of the stream), + * _then_ decoding & encoding ring buffer can have any size, including small ones ( < 64 KB). + * - Decompression buffer is larger than encoding buffer, by a minimum of maxBlockSize more bytes. + * In which case, encoding and decoding buffers do not need to be synchronized, + * and encoding ring buffer can have any size, including small ones ( < 64 KB). + * + * Whenever these conditions are not possible, + * save the last 64KB of decoded data into a safe buffer where it can't be modified during decompression, + * then indicate where this data is saved using LZ4_setStreamDecode(), before decompressing next block. +*/ +LZ4LIB_API int LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int srcSize, int dstCapacity); + + +/*! LZ4_decompress_*_usingDict() : + * These decoding functions work the same as + * a combination of LZ4_setStreamDecode() followed by LZ4_decompress_*_continue() + * They are stand-alone, and don't need an LZ4_streamDecode_t structure. + * Dictionary is presumed stable : it must remain accessible and unmodified during decompression. + * Performance tip : Decompression speed can be substantially increased + * when dst == dictStart + dictSize. + */ +LZ4LIB_API int LZ4_decompress_safe_usingDict (const char* src, char* dst, int srcSize, int dstCapcity, const char* dictStart, int dictSize); + +#endif /* LZ4_H_2983827168210 */ + + +/*^************************************* + * !!!!!! STATIC LINKING ONLY !!!!!! + ***************************************/ + +/*-**************************************************************************** + * Experimental section + * + * Symbols declared in this section must be considered unstable. Their + * signatures or semantics may change, or they may be removed altogether in the + * future. They are therefore only safe to depend on when the caller is + * statically linked against the library. + * + * To protect against unsafe usage, not only are the declarations guarded, + * the definitions are hidden by default + * when building LZ4 as a shared/dynamic library. + * + * In order to access these declarations, + * define LZ4_STATIC_LINKING_ONLY in your application + * before including LZ4's headers. + * + * In order to make their implementations accessible dynamically, you must + * define LZ4_PUBLISH_STATIC_FUNCTIONS when building the LZ4 library. + ******************************************************************************/ + +#ifdef LZ4_STATIC_LINKING_ONLY + +#ifndef LZ4_STATIC_3504398509 +#define LZ4_STATIC_3504398509 + +#ifdef LZ4_PUBLISH_STATIC_FUNCTIONS +#define LZ4LIB_STATIC_API LZ4LIB_API +#else +#define LZ4LIB_STATIC_API +#endif + + +/*! LZ4_compress_fast_extState_fastReset() : + * A variant of LZ4_compress_fast_extState(). + * + * Using this variant avoids an expensive initialization step. + * It is only safe to call if the state buffer is known to be correctly initialized already + * (see above comment on LZ4_resetStream_fast() for a definition of "correctly initialized"). + * From a high level, the difference is that + * this function initializes the provided state with a call to something like LZ4_resetStream_fast() + * while LZ4_compress_fast_extState() starts with a call to LZ4_resetStream(). + */ +LZ4LIB_STATIC_API int LZ4_compress_fast_extState_fastReset (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + +/*! LZ4_attach_dictionary() : + * This is an experimental API that allows + * efficient use of a static dictionary many times. + * + * Rather than re-loading the dictionary buffer into a working context before + * each compression, or copying a pre-loaded dictionary's LZ4_stream_t into a + * working LZ4_stream_t, this function introduces a no-copy setup mechanism, + * in which the working stream references the dictionary stream in-place. + * + * Several assumptions are made about the state of the dictionary stream. + * Currently, only streams which have been prepared by LZ4_loadDict() should + * be expected to work. + * + * Alternatively, the provided dictionaryStream may be NULL, + * in which case any existing dictionary stream is unset. + * + * If a dictionary is provided, it replaces any pre-existing stream history. + * The dictionary contents are the only history that can be referenced and + * logically immediately precede the data compressed in the first subsequent + * compression call. + * + * The dictionary will only remain attached to the working stream through the + * first compression call, at the end of which it is cleared. The dictionary + * stream (and source buffer) must remain in-place / accessible / unchanged + * through the completion of the first compression call on the stream. + */ +LZ4LIB_STATIC_API void LZ4_attach_dictionary(LZ4_stream_t* workingStream, const LZ4_stream_t* dictionaryStream); + + +/*! In-place compression and decompression + * + * It's possible to have input and output sharing the same buffer, + * for highly constrained memory environments. + * In both cases, it requires input to lay at the end of the buffer, + * and decompression to start at beginning of the buffer. + * Buffer size must feature some margin, hence be larger than final size. + * + * |<------------------------buffer--------------------------------->| + * |<-----------compressed data--------->| + * |<-----------decompressed size------------------>| + * |<----margin---->| + * + * This technique is more useful for decompression, + * since decompressed size is typically larger, + * and margin is short. + * + * In-place decompression will work inside any buffer + * which size is >= LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(decompressedSize). + * This presumes that decompressedSize > compressedSize. + * Otherwise, it means compression actually expanded data, + * and it would be more efficient to store such data with a flag indicating it's not compressed. + * This can happen when data is not compressible (already compressed, or encrypted). + * + * For in-place compression, margin is larger, as it must be able to cope with both + * history preservation, requiring input data to remain unmodified up to LZ4_DISTANCE_MAX, + * and data expansion, which can happen when input is not compressible. + * As a consequence, buffer size requirements are much higher, + * and memory savings offered by in-place compression are more limited. + * + * There are ways to limit this cost for compression : + * - Reduce history size, by modifying LZ4_DISTANCE_MAX. + * Note that it is a compile-time constant, so all compressions will apply this limit. + * Lower values will reduce compression ratio, except when input_size < LZ4_DISTANCE_MAX, + * so it's a reasonable trick when inputs are known to be small. + * - Require the compressor to deliver a "maximum compressed size". + * This is the `dstCapacity` parameter in `LZ4_compress*()`. + * When this size is < LZ4_COMPRESSBOUND(inputSize), then compression can fail, + * in which case, the return code will be 0 (zero). + * The caller must be ready for these cases to happen, + * and typically design a backup scheme to send data uncompressed. + * The combination of both techniques can significantly reduce + * the amount of margin required for in-place compression. + * + * In-place compression can work in any buffer + * which size is >= (maxCompressedSize) + * with maxCompressedSize == LZ4_COMPRESSBOUND(srcSize) for guaranteed compression success. + * LZ4_COMPRESS_INPLACE_BUFFER_SIZE() depends on both maxCompressedSize and LZ4_DISTANCE_MAX, + * so it's possible to reduce memory requirements by playing with them. + */ + +#define LZ4_DECOMPRESS_INPLACE_MARGIN(compressedSize) (((compressedSize) >> 8) + 32) +#define LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(decompressedSize) ((decompressedSize) + LZ4_DECOMPRESS_INPLACE_MARGIN(decompressedSize)) /**< note: presumes that compressedSize < decompressedSize. note2: margin is overestimated a bit, since it could use compressedSize instead */ + +#ifndef LZ4_DISTANCE_MAX /* history window size; can be user-defined at compile time */ +# define LZ4_DISTANCE_MAX 65535 /* set to maximum value by default */ +#endif + +#define LZ4_COMPRESS_INPLACE_MARGIN (LZ4_DISTANCE_MAX + 32) /* LZ4_DISTANCE_MAX can be safely replaced by srcSize when it's smaller */ +#define LZ4_COMPRESS_INPLACE_BUFFER_SIZE(maxCompressedSize) ((maxCompressedSize) + LZ4_COMPRESS_INPLACE_MARGIN) /**< maxCompressedSize is generally LZ4_COMPRESSBOUND(inputSize), but can be set to any lower value, with the risk that compression can fail (return code 0(zero)) */ + +#endif /* LZ4_STATIC_3504398509 */ +#endif /* LZ4_STATIC_LINKING_ONLY */ + + + +#ifndef LZ4_H_98237428734687 +#define LZ4_H_98237428734687 + +/*-************************************************************ + * Private Definitions + ************************************************************** + * Do not use these definitions directly. + * They are only exposed to allow static allocation of `LZ4_stream_t` and `LZ4_streamDecode_t`. + * Accessing members will expose user code to API and/or ABI break in future versions of the library. + **************************************************************/ +#define LZ4_HASHLOG (LZ4_MEMORY_USAGE-2) +#define LZ4_HASHTABLESIZE (1 << LZ4_MEMORY_USAGE) +#define LZ4_HASH_SIZE_U32 (1 << LZ4_HASHLOG) /* required as macro for static allocation */ + +#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) +# include + typedef int8_t LZ4_i8; + typedef uint8_t LZ4_byte; + typedef uint16_t LZ4_u16; + typedef uint32_t LZ4_u32; +#else + typedef signed char LZ4_i8; + typedef unsigned char LZ4_byte; + typedef unsigned short LZ4_u16; + typedef unsigned int LZ4_u32; +#endif + +typedef struct LZ4_stream_t_internal LZ4_stream_t_internal; +struct LZ4_stream_t_internal { + LZ4_u32 hashTable[LZ4_HASH_SIZE_U32]; + LZ4_u32 currentOffset; + LZ4_u32 tableType; + const LZ4_byte* dictionary; + const LZ4_stream_t_internal* dictCtx; + LZ4_u32 dictSize; +}; + +typedef struct { + const LZ4_byte* externalDict; + size_t extDictSize; + const LZ4_byte* prefixEnd; + size_t prefixSize; +} LZ4_streamDecode_t_internal; + + +/*! LZ4_stream_t : + * Do not use below internal definitions directly ! + * Declare or allocate an LZ4_stream_t instead. + * LZ4_stream_t can also be created using LZ4_createStream(), which is recommended. + * The structure definition can be convenient for static allocation + * (on stack, or as part of larger structure). + * Init this structure with LZ4_initStream() before first use. + * note : only use this definition in association with static linking ! + * this definition is not API/ABI safe, and may change in future versions. + */ +#define LZ4_STREAMSIZE ((1UL << LZ4_MEMORY_USAGE) + 32) /* static size, for inter-version compatibility */ +#define LZ4_STREAMSIZE_VOIDP (LZ4_STREAMSIZE / sizeof(void*)) +union LZ4_stream_u { + void* table[LZ4_STREAMSIZE_VOIDP]; + LZ4_stream_t_internal internal_donotuse; +}; /* previously typedef'd to LZ4_stream_t */ + + +/*! LZ4_initStream() : v1.9.0+ + * An LZ4_stream_t structure must be initialized at least once. + * This is automatically done when invoking LZ4_createStream(), + * but it's not when the structure is simply declared on stack (for example). + * + * Use LZ4_initStream() to properly initialize a newly declared LZ4_stream_t. + * It can also initialize any arbitrary buffer of sufficient size, + * and will @return a pointer of proper type upon initialization. + * + * Note : initialization fails if size and alignment conditions are not respected. + * In which case, the function will @return NULL. + * Note2: An LZ4_stream_t structure guarantees correct alignment and size. + * Note3: Before v1.9.0, use LZ4_resetStream() instead + */ +LZ4LIB_API LZ4_stream_t* LZ4_initStream (void* buffer, size_t size); + + +/*! LZ4_streamDecode_t : + * information structure to track an LZ4 stream during decompression. + * init this structure using LZ4_setStreamDecode() before first use. + * note : only use in association with static linking ! + * this definition is not API/ABI safe, + * and may change in a future version ! + */ +#define LZ4_STREAMDECODESIZE_U64 (4 + ((sizeof(void*)==16) ? 2 : 0) /*AS-400*/ ) +#define LZ4_STREAMDECODESIZE (LZ4_STREAMDECODESIZE_U64 * sizeof(unsigned long long)) +union LZ4_streamDecode_u { + unsigned long long table[LZ4_STREAMDECODESIZE_U64]; + LZ4_streamDecode_t_internal internal_donotuse; +} ; /* previously typedef'd to LZ4_streamDecode_t */ + + + +/*-************************************ +* Obsolete Functions +**************************************/ + +/*! Deprecation warnings + * + * Deprecated functions make the compiler generate a warning when invoked. + * This is meant to invite users to update their source code. + * Should deprecation warnings be a problem, it is generally possible to disable them, + * typically with -Wno-deprecated-declarations for gcc + * or _CRT_SECURE_NO_WARNINGS in Visual. + * + * Another method is to define LZ4_DISABLE_DEPRECATE_WARNINGS + * before including the header file. + */ +#ifdef LZ4_DISABLE_DEPRECATE_WARNINGS +# define LZ4_DEPRECATED(message) /* disable deprecation warnings */ +#else +# if defined (__cplusplus) && (__cplusplus >= 201402) /* C++14 or greater */ +# define LZ4_DEPRECATED(message) [[deprecated(message)]] +# elif defined(_MSC_VER) +# define LZ4_DEPRECATED(message) __declspec(deprecated(message)) +# elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ * 10 + __GNUC_MINOR__ >= 45)) +# define LZ4_DEPRECATED(message) __attribute__((deprecated(message))) +# elif defined(__GNUC__) && (__GNUC__ * 10 + __GNUC_MINOR__ >= 31) +# define LZ4_DEPRECATED(message) __attribute__((deprecated)) +# else +# pragma message("WARNING: LZ4_DEPRECATED needs custom implementation for this compiler") +# define LZ4_DEPRECATED(message) /* disabled */ +# endif +#endif /* LZ4_DISABLE_DEPRECATE_WARNINGS */ + +/*! Obsolete compression functions (since v1.7.3) */ +LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress (const char* src, char* dest, int srcSize); +LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress_limitedOutput (const char* src, char* dest, int srcSize, int maxOutputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_withState (void* state, const char* source, char* dest, int inputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_limitedOutput_withState (void* state, const char* source, char* dest, int inputSize, int maxOutputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize, int maxOutputSize); + +/*! Obsolete decompression functions (since v1.8.0) */ +LZ4_DEPRECATED("use LZ4_decompress_fast() instead") LZ4LIB_API int LZ4_uncompress (const char* source, char* dest, int outputSize); +LZ4_DEPRECATED("use LZ4_decompress_safe() instead") LZ4LIB_API int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize); + +/* Obsolete streaming functions (since v1.7.0) + * degraded functionality; do not use! + * + * In order to perform streaming compression, these functions depended on data + * that is no longer tracked in the state. They have been preserved as well as + * possible: using them will still produce a correct output. However, they don't + * actually retain any history between compression calls. The compression ratio + * achieved will therefore be no better than compressing each chunk + * independently. + */ +LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API void* LZ4_create (char* inputBuffer); +LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API int LZ4_sizeofStreamState(void); +LZ4_DEPRECATED("Use LZ4_resetStream() instead") LZ4LIB_API int LZ4_resetStreamState(void* state, char* inputBuffer); +LZ4_DEPRECATED("Use LZ4_saveDict() instead") LZ4LIB_API char* LZ4_slideInputBuffer (void* state); + +/*! Obsolete streaming decoding functions (since v1.7.0) */ +LZ4_DEPRECATED("use LZ4_decompress_safe_usingDict() instead") LZ4LIB_API int LZ4_decompress_safe_withPrefix64k (const char* src, char* dst, int compressedSize, int maxDstSize); +LZ4_DEPRECATED("use LZ4_decompress_fast_usingDict() instead") LZ4LIB_API int LZ4_decompress_fast_withPrefix64k (const char* src, char* dst, int originalSize); + +/*! Obsolete LZ4_decompress_fast variants (since v1.9.0) : + * These functions used to be faster than LZ4_decompress_safe(), + * but this is no longer the case. They are now slower. + * This is because LZ4_decompress_fast() doesn't know the input size, + * and therefore must progress more cautiously into the input buffer to not read beyond the end of block. + * On top of that `LZ4_decompress_fast()` is not protected vs malformed or malicious inputs, making it a security liability. + * As a consequence, LZ4_decompress_fast() is strongly discouraged, and deprecated. + * + * The last remaining LZ4_decompress_fast() specificity is that + * it can decompress a block without knowing its compressed size. + * Such functionality can be achieved in a more secure manner + * by employing LZ4_decompress_safe_partial(). + * + * Parameters: + * originalSize : is the uncompressed size to regenerate. + * `dst` must be already allocated, its size must be >= 'originalSize' bytes. + * @return : number of bytes read from source buffer (== compressed size). + * The function expects to finish at block's end exactly. + * If the source stream is detected malformed, the function stops decoding and returns a negative result. + * note : LZ4_decompress_fast*() requires originalSize. Thanks to this information, it never writes past the output buffer. + * However, since it doesn't know its 'src' size, it may read an unknown amount of input, past input buffer bounds. + * Also, since match offsets are not validated, match reads from 'src' may underflow too. + * These issues never happen if input (compressed) data is correct. + * But they may happen if input data is invalid (error or intentional tampering). + * As a consequence, use these functions in trusted environments with trusted data **only**. + */ +LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe() instead") +LZ4LIB_API int LZ4_decompress_fast (const char* src, char* dst, int originalSize); +LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_continue() instead") +LZ4LIB_API int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int originalSize); +LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_usingDict() instead") +LZ4LIB_API int LZ4_decompress_fast_usingDict (const char* src, char* dst, int originalSize, const char* dictStart, int dictSize); + +/*! LZ4_resetStream() : + * An LZ4_stream_t structure must be initialized at least once. + * This is done with LZ4_initStream(), or LZ4_resetStream(). + * Consider switching to LZ4_initStream(), + * invoking LZ4_resetStream() will trigger deprecation warnings in the future. + */ +LZ4LIB_API void LZ4_resetStream (LZ4_stream_t* streamPtr); + + +#endif /* LZ4_H_98237428734687 */ + + +#if defined (__cplusplus) +} +#endif diff --git a/libCompression/lz4_LICENSE b/libCompression/lz4_LICENSE new file mode 100644 index 0000000..4884916 --- /dev/null +++ b/libCompression/lz4_LICENSE @@ -0,0 +1,24 @@ +LZ4 Library +Copyright (c) 2011-2020, Yann Collet +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/libCompression/lz4dec.cpp b/libCompression/lz4dec.cpp new file mode 100644 index 0000000..4b66c66 --- /dev/null +++ b/libCompression/lz4dec.cpp @@ -0,0 +1,374 @@ +#pragma once +#include "lz4dec.h" +#include +#include + +#ifdef _MSC_VER /* Visual Studio */ +# include +# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ +# pragma warning(disable : 4293) /* disable: C4293: too large shift (32-bits) */ +#endif /* _MSC_VER */ + +#ifndef FORCE_INLINE +# ifdef _MSC_VER /* Visual Studio */ +# define FORCE_INLINE static __forceinline +# else +# if defined (__cplusplus) || defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */ +# ifdef __GNUC__ +# define FORCE_INLINE static inline __attribute__((always_inline)) +# else +# define FORCE_INLINE static inline +# endif +# else +# define FORCE_INLINE static +# endif /* __STDC_VERSION__ */ +# endif /* _MSC_VER */ +#endif /* FORCE_INLINE */ + +//Branch optimization stuff (expected scenario == true for likely, false for unlikely). +#if (defined(__GNUC__) && (__GNUC__ >= 3)) || (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 800)) || defined(__clang__) +# define expect(expr,value) (__builtin_expect ((expr),(value)) ) +#else +# define expect(expr,value) (expr) +#endif + +#define likely(expr) expect((expr) != 0, 1) +#define unlikely(expr) expect((expr) != 0, 0) + + +/*-************************************ +* Basic Types +**************************************/ +#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) +# include + typedef uint8_t BYTE; + typedef uint16_t U16; + typedef uint32_t U32; + typedef int32_t S32; + typedef uint64_t U64; + typedef uintptr_t uptrval; +#else + typedef unsigned char BYTE; + typedef unsigned short U16; + typedef unsigned int U32; + typedef signed int S32; + typedef unsigned long long U64; + typedef size_t uptrval; /* generally true, except OpenVMS-64 */ +#endif + +/*-************************************ +* Common Constants +**************************************/ +#define MINMATCH 4 + +#define WILDCOPYLENGTH 8 +#define LASTLITERALS 5 +#define MFLIMIT (WILDCOPYLENGTH+MINMATCH) +static const int LZ4_minLength = (MFLIMIT+1); + +#define KB *(1 <<10) +#define MB *(1 <<20) +#define GB *(1U<<30) + +#define MAXD_LOG 16 +#define MAX_DISTANCE ((1 << MAXD_LOG) - 1) + +#define ML_BITS 4 +#define ML_MASK ((1U<pos += (ip-istart); + return inStream->callback(istart, imax-istart, inStream); +} +//Flushes all written data and keeps the last written 64KiB (dictionary) at ostart. +//Returns the new dictionary size (max. 64KiB), or returns omax - ostart on error. +static int LZ4e_outputBuffer(BYTE* ostart, BYTE* op, BYTE* omax, BYTE* odend, LZ4e_outstream_t* outStream) +{ + if (op < odend) + return omax - ostart; + if (outStream->callback(odend, op-odend, outStream) < op-odend) + return omax - ostart; + + int newDictionarySize = 0; + int curDataSize = op - ostart; + if (curDataSize > 64 KB) + { + memmove(ostart, op - (64 KB), 64 KB); + newDictionarySize = 64 KB; + } + else + { + newDictionarySize = curDataSize; + } + return newDictionarySize; +} + +static int LZ4_decompress_generic( + char* const sourceBuf, + char* const destBuf, + int inputBufSize, + int outputBufSize, /* If endOnInput==endOnInputSize, this value is the max size of Output Buffer. */ + + int endOnInput, /* endOnOutputSize, endOnInputSize */ + + LZ4e_instream_t* inStream, + LZ4e_outstream_t* outStream + ) +{ + /* Local Variables */ + BYTE* const istart = (BYTE*) sourceBuf; + BYTE* ip = istart; + BYTE* iend = istart + 0; + BYTE* const imax = istart + inputBufSize - 8; + + BYTE* const ostart = (BYTE*) destBuf; + BYTE* op = ostart; + BYTE* odend = ostart + 0; //dictionary end + BYTE* const omax = ostart + outputBufSize - 8; + + BYTE* cpy; + const BYTE* const lowLimit = ostart; + + const unsigned dec32table[] = {0, 1, 2, 1, 4, 4, 4, 4}; + const int dec64table[] = {0, 0, 0, -1, 0, 1, 2, 3}; + + const int safeDecode = (endOnInput==endOnInputSize); + const int checkOffset = (safeDecode); + + if (outputBufSize < (28 + 64 KB)) return -1; + if (inputBufSize < 16) return -1; + + /* Main Loop : decode sequences */ + while (1) { + size_t length; + const BYTE* match; + size_t offset; + + if (unlikely(iend-ip==0)) + { + iend = istart + LZ4e_inputBuffer(istart, ip, imax, inStream); + ip = istart; + if (unlikely(iend-ip==0)) { + goto _output_error; /* overflow detection */ + } + } + /* get literal length */ + unsigned const token = *ip++; + if ((length=(token>>ML_BITS)) == RUN_MASK) { + unsigned s; + do { + if (unlikely(ip+RUN_MASK+1>=iend)) { + iend = istart + LZ4e_inputBuffer(istart, ip, imax, inStream); + ip = istart; + if (unlikely(iend==istart)) { + goto _output_error; + } + } + s = *ip++; + length += s; + } while ( likely(endOnInput ? ipiend)?(iend-ip):(length); + size_t c; + if (!ci) { + iend = istart + LZ4e_inputBuffer(istart, ip, imax, inStream); + ip = istart; + if (unlikely(iend==istart)) { + goto _output_error; + } + continue; + } + c = (op+ci>omax)?(omax-op):(ci); + if (!c) { + odend = ostart + LZ4e_outputBuffer(ostart, op, omax, odend, outStream); + op = odend; + if (unlikely(odend==omax)) { + goto _output_error; + } + continue; + } + LZ4_wildCopy(op, ip, op+c); + ip += c; op += c; + length -= c; + } + + //Compressed data may end here. + if (unlikely(ip+2>iend)) { + iend = istart + LZ4e_inputBuffer(istart, ip, imax, inStream); + ip = istart; + if ((iend-ip)<2) //(unlikely((iend-ip)<3)) + { + //Only throw an error if there still are bytes to copy. + if ((token & ML_MASK) != 0) { + goto _output_error; + } + break; + } + } + if (unlikely(op+20>omax)) { + odend = ostart + LZ4e_outputBuffer(ostart, op, omax, odend, outStream); + op = odend; + if (unlikely(odend==omax)) { + goto _output_error; + } + } + + /* get offset */ + offset = LZ4_readLE16(ip); ip+=2; + match = op - offset; + if ((checkOffset) && (unlikely(match < lowLimit))) { + goto _output_error; /* Error : offset outside buffers */ + } + LZ4_write32(op, (U32)offset); /* costs ~1%; silence an msan warning when offset==0 */ + + /* get matchlength */ + length = token & ML_MASK; + if (length == ML_MASK) { + unsigned s; + do { + if (unlikely(ip+LASTLITERALS>iend)) { + iend = istart + LZ4e_inputBuffer(istart, ip, imax, inStream); + ip = istart; + if (unlikely(ip+LASTLITERALS>iend)) { + goto _output_error; + } + //if ((endOnInput) && (ip > iend-LASTLITERALS)) goto _output_error; + } + s = *ip++; + length += s; + } while (s==255); + if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)op)) { + goto _output_error; /* overflow detection */ + } + } + length += MINMATCH; + //As the dictionary boundaries shift with each byte written, the values copied from match can be beyond the current output pointer. + //This is no issue since it can never exceed the output buffer location that also moves on in the same 'speed' as the match location while copying. + + /* copy match within block */ + cpy = op + length; + if (unlikely(offset<8)) { + const int dec64 = dec64table[offset]; + op[0] = match[0]; + op[1] = match[1]; + op[2] = match[2]; + op[3] = match[3]; + match += dec32table[offset]; + memcpy(op+4, match, 4); + match -= dec64; + } else { LZ4_copy8(op, match); match+=8; } + op += 8; + + { + LZ4_copy8(op, match); + op += 8; match += 8; + if (length > 16) + { + length -= 16; + while (length) + { + size_t c = (op+length>omax)?(omax-op):(length); + if (!c) { + size_t oldOffs = cpy - op; + odend = ostart + LZ4e_outputBuffer(ostart, op, omax, odend, outStream); + if (unlikely(odend==omax)) { + goto _output_error; + } + match -= op-odend; + op = odend; + cpy = op + oldOffs; + continue; + } + LZ4_wildCopy(op, match, op+c); + op += c; match += c; + length -= c; + } + } + } + op=cpy; /* correction */ + + + } + + /* end of decoding */ + //if (endOnInput) + // return (int) (((char*)op)-dest); /* Nb of output bytes decoded */ + //else + // return (int) (((const char*)ip)-source); /* Nb of input bytes read */ + inStream->pos += (ip - istart); + return LZ4e_outputBuffer(ostart, op, omax, odend, outStream) != (omax-ostart); + /* Overflow error detected */ +_output_error: + return (int) (-(((const char*)ip)-sourceBuf))-1; +} + + +int LZ4e_decompress_safe(char* sourceBuf, char* destBuf, int sourceBufSize, int destBufSize, LZ4e_instream_t* inStream, LZ4e_outstream_t* outStream) +{ + return LZ4_decompress_generic(sourceBuf, destBuf, sourceBufSize, destBufSize, endOnInputSize, inStream, outStream); +} \ No newline at end of file diff --git a/libCompression/lz4dec.h b/libCompression/lz4dec.h new file mode 100644 index 0000000..8e73818 --- /dev/null +++ b/libCompression/lz4dec.h @@ -0,0 +1,7 @@ +//Custom LZ4 decompression with data streams based on the original algorithm. + +#pragma once +#include "lz4e.h" + +//Returns 1 on success. Returns 0 if it failed to write the final data or a negative value if it failed to read/write data. +int LZ4e_decompress_safe(char* sourceBuf, char* destBuf, int sourceBufSize, int destBufSize, LZ4e_instream_t* inStream, LZ4e_outstream_t* outStream); \ No newline at end of file diff --git a/libCompression/lz4e.h b/libCompression/lz4e.h new file mode 100644 index 0000000..94bc4b5 --- /dev/null +++ b/libCompression/lz4e.h @@ -0,0 +1,19 @@ +#pragma once +#include + +//Returns the number of bytes read. Must not return negative values. +typedef int(_cdecl *LZ4e_read_callback_t)(void *buffer, int size, struct LZ4e_instream_t *stream); + +struct LZ4e_instream_t { + uint64_t pos; //0 must stand for the first input byte + LZ4e_read_callback_t callback; + void *user; +}; + +//Returns the number of bytes written. Must not return negative values. +typedef int(_cdecl *LZ4e_write_callback_t)(const void *buffer, int size, struct LZ4e_outstream_t *stream); + +struct LZ4e_outstream_t { + LZ4e_write_callback_t callback; + void *user; +}; \ No newline at end of file diff --git a/libCompression/lz4enc.cpp b/libCompression/lz4enc.cpp new file mode 100644 index 0000000..d4c1dbf --- /dev/null +++ b/libCompression/lz4enc.cpp @@ -0,0 +1,1015 @@ +#include "lz4enc.h" + +#ifdef _DEBUG +#define LZ4_DEBUG 3//16 +#else +#define LZ4_DEBUG 2 +#endif + +#define LZ4_HEAPMODE 1 + +#pragma region Copied from lz4.h +#ifndef LZ4_MEMORY_USAGE +# define LZ4_MEMORY_USAGE 14 +#endif + +typedef union LZ4_stream_u LZ4_stream_t; /* incomplete type (defined later) */ +typedef struct LZ4_stream_t_internal LZ4_stream_t_internal; + +#define LZ4_HASHLOG (LZ4_MEMORY_USAGE-2) +#define LZ4_HASH_SIZE_U32 (1 << LZ4_HASHLOG) /* required as macro for static allocation */ + +struct LZ4_stream_t_internal { + uint32_t hashTable[LZ4_HASH_SIZE_U32]; + uint32_t currentOffset; + uint16_t initCheck; + uint16_t tableType; + const uint8_t* dictionary; + const LZ4_stream_t_internal* dictCtx; + uint32_t dictSize; +}; + +/*! + * LZ4_stream_t : + * information structure to track an LZ4 stream. + * init this structure before first use. + * note : only use in association with static linking ! + * this definition is not API/ABI safe, + * it may change in a future version ! + */ +#define LZ4_STREAMSIZE_U64 ((1 << (LZ4_MEMORY_USAGE-3)) + 4) +#define LZ4_STREAMSIZE (LZ4_STREAMSIZE_U64 * sizeof(unsigned long long)) +union LZ4_stream_u { + unsigned long long table[LZ4_STREAMSIZE_U64]; + LZ4_stream_t_internal internal_donotuse; +}; + +/*-************************************ +* Advanced Functions +**************************************/ +#define LZ4_MAX_INPUT_SIZE 0x7E000000 /* 2 113 929 216 bytes */ +#define LZ4_COMPRESSBOUND(isize) ((unsigned)(isize) > (unsigned)LZ4_MAX_INPUT_SIZE ? 0 : (isize) + ((isize)/255) + 16) + +#pragma endregion Copied from lz4.h + +#pragma region Misc + +#define ACCELERATION_DEFAULT 1 + +/*-************************************ +* Compiler Options +**************************************/ +#ifdef _MSC_VER /* Visual Studio */ +# include +# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ +# pragma warning(disable : 4293) /* disable: C4293: too large shift (32-bits) */ +#endif /* _MSC_VER */ + +#ifndef LZ4_FORCE_INLINE +# ifdef _MSC_VER /* Visual Studio */ +# define LZ4_FORCE_INLINE static __forceinline +# else +# if defined (__cplusplus) || defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */ +# ifdef __GNUC__ +# define LZ4_FORCE_INLINE static inline __attribute__((always_inline)) +# else +# define LZ4_FORCE_INLINE static inline +# endif +# else +# define LZ4_FORCE_INLINE static +# endif /* __STDC_VERSION__ */ +# endif /* _MSC_VER */ +#endif /* LZ4_FORCE_INLINE */ + +#if defined(__PPC64__) && defined(__LITTLE_ENDIAN__) && defined(__GNUC__) +# define LZ4_FORCE_O2_GCC_PPC64LE __attribute__((optimize("O2"))) +# define LZ4_FORCE_O2_INLINE_GCC_PPC64LE __attribute__((optimize("O2"))) LZ4_FORCE_INLINE +#else +# define LZ4_FORCE_O2_GCC_PPC64LE +# define LZ4_FORCE_O2_INLINE_GCC_PPC64LE static +#endif + +/*-************************************ +* Basic Types +**************************************/ +#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) +# include + typedef uint8_t BYTE; + typedef uint16_t U16; + typedef uint32_t U32; + typedef int32_t S32; + typedef uint64_t U64; + typedef uintptr_t uptrval; +#else + typedef unsigned char BYTE; + typedef unsigned short U16; + typedef unsigned int U32; + typedef signed int S32; + typedef unsigned long long U64; + typedef size_t uptrval; /* generally true, except OpenVMS-64 */ +#endif + +/*-************************************ +* Error detection +**************************************/ +#if defined(LZ4_DEBUG) && (LZ4_DEBUG>=3) //>=1 +# include +#else +# ifndef assert +# define assert(condition) ((void)0) +# endif +#endif + +#define LZ4_STATIC_ASSERT(c) { enum { LZ4_static_assert = 1/(int)(!!(c)) }; } /* use after variable declarations */ + +#if defined(LZ4_DEBUG) && (LZ4_DEBUG>=2) +# include +static int g_debuglog_enable = 1; +# define DEBUGLOG(l, ...) { \ + if ((g_debuglog_enable) && (l<=LZ4_DEBUG)) { \ + fprintf(stderr, __FILE__ ": "); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, " \n"); \ + } } +#else +# define DEBUGLOG(l, ...) {} /* disabled */ +#endif + +#if (defined(__GNUC__) && (__GNUC__ >= 3)) || (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 800)) || defined(__clang__) +# define expect(expr,value) (__builtin_expect ((expr),(value)) ) +#else +# define expect(expr,value) (expr) +#endif + +#ifndef likely +#define likely(expr) expect((expr) != 0, 1) +#endif +#ifndef unlikely +#define unlikely(expr) expect((expr) != 0, 0) +#endif + +/*-************************************ +* Memory routines +**************************************/ +#include /* malloc, calloc, free */ +#define ALLOC(s) malloc(s) +#define ALLOC_AND_ZERO(s) calloc(1,s) +#define FREEMEM(p) free(p) +#include /* memset, memcpy */ +#define MEM_INIT(p,v,s) memset((p),(v),(s)) + +/*-************************************ +* Common Constants +**************************************/ +#define MINMATCH 4 +#define WILDCOPYLENGTH 8 +#define LASTLITERALS 5 +#define MFLIMIT (WILDCOPYLENGTH+MINMATCH) +static const int LZ4_minLength = (MFLIMIT+1); +#define KB *(1 <<10) +#define MB *(1 <<20) +#define GB *(1U<<30) +#define MAXD_LOG 16 +#define MAX_DISTANCE ((1 << MAXD_LOG) - 1) +#define ML_BITS 4 +#define ML_MASK ((1U< compression run slower on incompressible data */ + +typedef enum { noDict = 0, withPrefix64k, usingExtDict, usingDictCtx } dict_directive; +typedef enum { noDictIssue = 0, dictSmall } dictIssue_directive; + +typedef enum { notLimited = 0, limitedOutput = 1, fillOutput = 2 } limitedOutput_directive; +typedef enum { clearedTable = 0, byPtr, byU32, byU16 } tableType_t; + +#if defined(__x86_64__) + typedef U64 reg_t; /* 64-bits in x32 mode */ +#else + typedef size_t reg_t; /* 32-bits in x32 mode */ +#endif + +static unsigned LZ4_isLittleEndian(void) +{ + const union { U32 u; BYTE c[4]; } one = { 1 }; /* don't use static : performance detrimental */ + return one.c[0]; +} +#pragma endregion Misc + +void LZ4_resetStream (LZ4_stream_t* LZ4_stream) +{ + DEBUGLOG(5, "LZ4_resetStream (ctx:%p)", LZ4_stream); + MEM_INIT(LZ4_stream, 0, sizeof(LZ4_stream_t)); +} + +static unsigned LZ4_NbCommonBytes (reg_t val) +{ + if (LZ4_isLittleEndian()) { + if (sizeof(val)==8) { +# if defined(_MSC_VER) && defined(_WIN64) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r = 0; + _BitScanForward64( &r, (U64)val ); + return (int)(r>>3); +# elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (__builtin_ctzll((U64)val) >> 3); +# else + static const int DeBruijnBytePos[64] = { 0, 0, 0, 0, 0, 1, 1, 2, + 0, 3, 1, 3, 1, 4, 2, 7, + 0, 2, 3, 6, 1, 5, 3, 5, + 1, 3, 4, 4, 2, 5, 6, 7, + 7, 0, 1, 2, 3, 3, 4, 6, + 2, 6, 5, 5, 3, 4, 5, 6, + 7, 1, 2, 4, 6, 4, 4, 5, + 7, 2, 6, 5, 7, 6, 7, 7 }; + return DeBruijnBytePos[((U64)((val & -(long long)val) * 0x0218A392CDABBD3FULL)) >> 58]; +# endif + } else /* 32 bits */ { +# if defined(_MSC_VER) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r; + _BitScanForward( &r, (U32)val ); + return (int)(r>>3); +# elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (__builtin_ctz((U32)val) >> 3); +# else + static const int DeBruijnBytePos[32] = { 0, 0, 3, 0, 3, 1, 3, 0, + 3, 2, 2, 1, 3, 2, 0, 1, + 3, 3, 1, 2, 2, 2, 2, 0, + 3, 1, 2, 0, 1, 0, 1, 1 }; + return DeBruijnBytePos[((U32)((val & -(S32)val) * 0x077CB531U)) >> 27]; +# endif + } + } else /* Big Endian CPU */ { + if (sizeof(val)==8) { /* 64-bits */ +# if defined(_MSC_VER) && defined(_WIN64) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r = 0; + _BitScanReverse64( &r, val ); + return (unsigned)(r>>3); +# elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (__builtin_clzll((U64)val) >> 3); +# else + static const U32 by32 = sizeof(val)*4; /* 32 on 64 bits (goal), 16 on 32 bits. + Just to avoid some static analyzer complaining about shift by 32 on 32-bits target. + Note that this code path is never triggered in 32-bits mode. */ + unsigned r; + if (!(val>>by32)) { r=4; } else { r=0; val>>=by32; } + if (!(val>>16)) { r+=2; val>>=8; } else { val>>=24; } + r += (!val); + return r; +# endif + } else /* 32 bits */ { +# if defined(_MSC_VER) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r = 0; + _BitScanReverse( &r, (unsigned long)val ); + return (unsigned)(r>>3); +# elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (__builtin_clz((U32)val) >> 3); +# else + unsigned r; + if (!(val>>16)) { r=2; val>>=8; } else { r=0; val>>=24; } + r += (!val); + return r; +# endif + } + } +} + +static U16 LZ4_read16(const void* memPtr) { return *(const U16*) memPtr; } +static U32 LZ4_read32(const void* memPtr) { return *(const U32*) memPtr; } +static reg_t LZ4_read_ARCH(const void* memPtr) { return *(const reg_t*) memPtr; } +static U32 LZ4_hash4(U32 sequence, tableType_t const tableType) +{ + if (tableType == byU16) + return ((sequence * 2654435761U) >> ((MINMATCH*8)-(LZ4_HASHLOG+1))); + else + return ((sequence * 2654435761U) >> ((MINMATCH*8)-LZ4_HASHLOG)); +} + +static U32 LZ4_hash5(U64 sequence, tableType_t const tableType) +{ + static const U64 prime5bytes = 889523592379ULL; + static const U64 prime8bytes = 11400714785074694791ULL; + const U32 hashLog = (tableType == byU16) ? LZ4_HASHLOG+1 : LZ4_HASHLOG; + if (LZ4_isLittleEndian()) + return (U32)(((sequence << 24) * prime5bytes) >> (64 - hashLog)); + else + return (U32)(((sequence >> 24) * prime8bytes) >> (64 - hashLog)); +} +LZ4_FORCE_INLINE U32 LZ4_hashPosition(const void* const p, tableType_t const tableType) +{ + if ((sizeof(reg_t)==8) && (tableType != byU16)) return LZ4_hash5(LZ4_read_ARCH(p), tableType); + return LZ4_hash4(LZ4_read32(p), tableType); +} +static void LZ4_putPositionOnHash(U32 offset, U32 h, + void* tableBase, tableType_t const tableType) +{ + switch (tableType) + { + default: /* fallthrough */ + case clearedTable: /* fallthrough */ + case byPtr: { /* illegal! */ assert(0); return; } + case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = (U32)(offset); return; } + case byU16: { U16* hashTable = (U16*) tableBase; assert(offset < 65536); hashTable[h] = (U16)(offset); return; } + } +} + +static void LZ4_putIndexOnHash(U32 idx, U32 h, void* tableBase, tableType_t const tableType) +{ + switch (tableType) + { + default: /* fallthrough */ + case clearedTable: /* fallthrough */ + case byPtr: { /* illegal! */ assert(0); return; } + case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = idx; return; } + case byU16: { U16* hashTable = (U16*) tableBase; assert(idx < 65536); hashTable[h] = (U16)idx; return; } + } +} + +LZ4_FORCE_INLINE void LZ4_putPosition(const void* const p, U32 offset, void* tableBase, tableType_t tableType) +{ + U32 const h = LZ4_hashPosition(p, tableType); + LZ4_putPositionOnHash(offset, h, tableBase, tableType); +} + +static U32 LZ4_getIndexOnHash(U32 h, const void* tableBase, tableType_t tableType) +{ + LZ4_STATIC_ASSERT(LZ4_MEMORY_USAGE > 2); + if (tableType == byU32) { + const U32* const hashTable = (const U32*) tableBase; + assert(h < (1U << (LZ4_MEMORY_USAGE-2))); + return hashTable[h]; + } + if (tableType == byU16) { + const U16* const hashTable = (const U16*) tableBase; + assert(h < (1U << (LZ4_MEMORY_USAGE-1))); + return hashTable[h]; + } + assert(0); return 0; /* forbidden case */ +} + +//Returns the bytes read. +static int LZ4e_inputBuffer(BYTE* istart, BYTE*& ip, BYTE* imax, LZ4e_instream_t* inStream) +{ + //Keep enough bytes for the dictionary. + uint64_t absIPOffset = ip-istart + inStream->pos; + int baseOffset = (imax - istart) / 2; + if (baseOffset > LZ4_64Klimit) baseOffset = LZ4_64Klimit; + if (baseOffset > absIPOffset) baseOffset = absIPOffset; + + inStream->pos = absIPOffset - baseOffset; + int len = inStream->callback(istart, imax-istart, inStream); + + assert(len >= baseOffset); + if (unlikely(len < baseOffset)) + ip = istart + len; //safety + else + ip = istart + baseOffset; + + return len; +} +//Returns the bytes written. +static int LZ4e_outputBuffer(BYTE* ostart, BYTE*& op, BYTE* omax, LZ4e_outstream_t* outStream) +{ + int len = outStream->callback(ostart, op-ostart, outStream); + assert(len == op-ostart); + op = ostart; + return len; +} +static U32 LZ4e_Read32Stream(unsigned int pos, LZ4e_instream_t* inStream, int *error) +{ + uint64_t oldPos = inStream->pos; + inStream->pos = pos; + U32 result = 0; + int size = inStream->callback(&result, 4, inStream); + inStream->pos = oldPos; + *error = size != 4; + return result; +} +static uint8_t LZ4e_Read8Stream(unsigned int pos, LZ4e_instream_t* inStream, int *error) +{ + uint64_t oldPos = inStream->pos; + inStream->pos = pos; + U32 result = 0; + int size = inStream->callback(&result, 1, inStream); + inStream->pos = oldPos; + *error = size != 1; + return result; +} +static int LZ4e_ReadStreamTemp(unsigned int pos, void *buf, unsigned int len, LZ4e_instream_t* inStream) +{ + uint64_t oldPos = inStream->pos; + inStream->pos = pos; + int size = inStream->callback(buf, len, inStream); + inStream->pos = oldPos; + return size; +} + +/* customized variant of memcpy, which can overwrite up to 8 bytes beyond dstEnd */ +LZ4_FORCE_O2_INLINE_GCC_PPC64LE +void LZ4_wildCopy(void* dstPtr, const void* srcPtr, void* dstEnd) +{ + BYTE* d = (BYTE*)dstPtr; + const BYTE* s = (const BYTE*)srcPtr; + BYTE* const e = (BYTE*)dstEnd; + + do { memcpy(d,s,8); d+=8; s+=8; } while (d>8); + } +} +static void LZ4_write32(void* memPtr, U32 value) +{ + memcpy(memPtr, &value, sizeof(value)); +} +#define STEPSIZE sizeof(reg_t) +//bufBase : pointer to buffer with length 2*bufLen +LZ4_FORCE_INLINE +unsigned LZ4e_count(U32 inOffs, U32 matchOffs, + BYTE* const bufBase, const U32 bufLen, + const BYTE* const inBufBase, const U32 inBufLen, const U32 inBufOffs, + LZ4e_instream_t *pInStream) +{ + BYTE* const inBuf = bufBase; + BYTE* const matchBuf = bufBase + bufLen; + unsigned totalCount = 0; + int inDataLen = 0; + int matchDataLen = 0; + const BYTE* pInLimit = NULL; + const BYTE* pIn = NULL; + const BYTE* pMatch = NULL; + if (inOffs >= inBufOffs && inOffs + LASTLITERALS < inBufOffs + inBufLen) + { + pIn = inBufBase + (inOffs - inBufOffs); + inDataLen = inBufLen - LASTLITERALS - (inOffs - inBufOffs); + pInLimit = inBufBase + inBufLen - LASTLITERALS; + } + else + { + inDataLen = LZ4e_ReadStreamTemp(inOffs, inBuf, bufLen, pInStream); + pIn = inBuf; + } + if (matchOffs >= inBufOffs && matchOffs < inBufOffs + inBufLen) + { + pMatch = inBufBase + (matchOffs - inBufOffs); + matchDataLen = inBufLen - (matchOffs - inBufOffs); + } + else + { + matchDataLen = LZ4e_ReadStreamTemp(matchOffs, matchBuf, bufLen, pInStream); + pMatch = matchBuf; + } + while (true) + { + const BYTE* const pInBase = pIn; + + if (unlikely(inDataLen <= LASTLITERALS || matchDataLen <= 0)) + break; + if (unlikely(inDataLen - LASTLITERALS > matchDataLen)) + inDataLen = matchDataLen + LASTLITERALS; + + if (likely(pIn < pInLimit-(STEPSIZE-1))) { + reg_t const diff = LZ4_read_ARCH(pMatch) ^ LZ4_read_ARCH(pIn); + if (!diff) { + pIn+=STEPSIZE; pMatch+=STEPSIZE; + } else { + return totalCount + LZ4_NbCommonBytes(diff); + } } + + while (likely(pIn < pInLimit-(STEPSIZE-1))) { + reg_t const diff = LZ4_read_ARCH(pMatch) ^ LZ4_read_ARCH(pIn); + if (!diff) { pIn+=STEPSIZE; pMatch+=STEPSIZE; continue; } + return totalCount + (pIn-pInBase) + LZ4_NbCommonBytes(diff); + } + + if ((STEPSIZE==8) && (pIn<(pInLimit-3)) && (LZ4_read32(pMatch) == LZ4_read32(pIn))) {pIn+=4; pMatch+=4;} + if ((pIn<(pInLimit-1)) && (LZ4_read16(pMatch) == LZ4_read16(pIn))) {pIn+=2; pMatch+=2;} + if ((pIndictCtx; + const BYTE* const dictionary = cctx->dictionary; + const U32 dictSize = cctx->dictSize; + const U32 dictDelta = 0; /* make indexes in dictCtx comparable with index in current context */ + + const BYTE* const dictEnd = dictionary + dictSize; + unsigned int anchor = 0; + BYTE* const iendb = ipb + streamBufSize; + + /* the dictCtx currentOffset is indexed on the start of the dictionary, + * while a dictionary in the current context precedes the currentOffset */ + const BYTE* dictBase = dictionary + dictSize; + + BYTE* const opbaseb = (BYTE*)streamBuf + streamBufSize * 2; + BYTE* opb = (BYTE*)streamBuf + streamBufSize * 2; + BYTE* const olimitb = (BYTE*)streamBuf + streamBufSize * 3; + + BYTE* const tempbase = (BYTE*)streamBuf + streamBufSize; + BYTE* const templimit = (BYTE*)streamBuf + streamBufSize * 2; + U32 outputOffset = 0; + + U32 offset = 0; + U32 forwardH; + + DEBUGLOG(5, "LZ4_compress_generic: streamBufSize=%u, tableType=%u", streamBufSize, tableType); + if (streamBufSize < LZ4_64Klimit) return 0; + /* Init conditions */ + //if (outputLimited == fillOutput && maxOutputSize < 1) return 0; /* Impossible to store anything */ + //if ((U32)inputSize > (U32)LZ4_MAX_INPUT_SIZE) return 0; /* Unsupported inputSize, too large (or negative) */ + //if ((tableType == byU16) && (inputSize>=LZ4_64Klimit)) return 0; /* Size too large (not within 64K limit) */ + if (tableType==byPtr) return 0;//assert(dictDirective==noDict); /* only supported use case with byPtr */ + assert(acceleration >= 1); + + lowLimitb = (const BYTE*)streamBuf; + + int curSize = LZ4e_inputBuffer(baseb, ipb, iendb, ssource); + unsigned int curOffset = 0; + if (curSizehashTable, tableType); + ipb++; curOffset++; forwardH = LZ4_hashPosition(ipb, tableType); + + /* Main Loop */ + for ( ; ; ) { + BYTE* token; + U32 matchIndex; + + /* Find a match */ + { /* byU32, byU16 */ + + BYTE* forwardIp = ipb; + unsigned step = 1; + unsigned searchMatchNb = acceleration << LZ4_skipTrigger; + do { + U32 const h = forwardH; + U32 const current = curOffset + (forwardIp - ipb);//(U32)(forwardIp - base); + matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType); + assert(matchIndex <= current); + assert(current < (ptrdiff_t)(2 GB - 1)); + //assert(forwardIp - base < (ptrdiff_t)(2 GB - 1)); + curOffset += (forwardIp - ipb); + assert(forwardIp >= ipb); + ipb = forwardIp; + forwardIp += step; + step = (searchMatchNb++ >> LZ4_skipTrigger); + + if (unlikely(forwardIp-baseb >= (curSize - MFLIMIT + 1))) + { + if (likely(curSize == streamBufSize)) + { + curSize = LZ4e_inputBuffer(baseb, ipb, iendb, ssource); + forwardIp = ipb + step; + if (unlikely(forwardIp-baseb + MFLIMIT >= (curSize + 1))) + goto _last_literals; //still not enough read + } + else + goto _last_literals; //nothing left to read + } + //if (unlikely(forwardIp > mflimitPlusOne)) goto _last_literals; + //assert(ip < mflimitPlusOne); + assert(ipb-baseb < (curSize - MFLIMIT + 1)); + + forwardH = LZ4_hashPosition(forwardIp, tableType); + LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType); + + assert(matchIndex < current); + if ((tableType != byU16) && (matchIndex+MAX_DISTANCE < current)) continue; /* too far */ + if (tableType == byU16) assert((current - matchIndex) <= MAX_DISTANCE); /* too_far presumed impossible with byU16 */ + + int error; + if (likely((curOffset - matchIndex) <= (ipb-baseb))) + { + if (LZ4_read32(ipb - (curOffset - matchIndex)) == LZ4_read32(ipb)) { + offset = current - matchIndex; + break; //match index is inside the read buffer + } + } + else if (LZ4e_Read32Stream(matchIndex, ssource, &error) == LZ4_read32(ipb) && likely(!error)) { + offset = current - matchIndex; + //if (maybe_extMem) offset = current - matchIndex; + break; /* match found */ + } + else if (unlikely(error)) + { + DEBUGLOG(1, "break 15"); + return 0; + } + } while(1); + } + + /* Catch up */ + { + unsigned int startOffset = curOffset - (ipb - baseb); + unsigned int tempStartOffset = 0, tempLen = 0; + while ((curOffset>anchor) & (matchIndex > 0)) + { + BYTE match, ip; + if (matchIndex <= startOffset) + { + if (unlikely(tempLen == 0 || matchIndex <= tempStartOffset)) + { + tempStartOffset = (matchIndex < streamBufSize) ? 0 : (matchIndex - streamBufSize); + tempLen = LZ4e_ReadStreamTemp(tempStartOffset, tempbase, streamBufSize, ssource); + if (unlikely((tempStartOffset + tempLen) < matchIndex)) + { + DEBUGLOG(1, "break 14"); + return 0; + } + } + match = tempbase[matchIndex - tempStartOffset - 1]; + } + else + match = baseb[matchIndex - startOffset - 1]; + if (unlikely(ipb == baseb)) + { + int error; + ip = LZ4e_Read8Stream(startOffset - 1, ssource, &error); + if (unlikely(error)) + { + DEBUGLOG(1, "break 13"); + return 0; + } + if (unlikely(match == ip)) + { + unsigned int offset = (curOffset < 16) ? curOffset : 16; + unsigned int movesize = streamBufSize - offset; + if (movesize > curSize) + movesize = curSize; + memmove(&baseb[offset], baseb, movesize); + + ssource->pos = startOffset - offset; + if (unlikely(ssource->callback(baseb, offset, ssource) != offset)) + { + DEBUGLOG(1, "break 12"); + return 0; + } + + ipb += offset; + curSize = movesize + offset; + startOffset -= offset; + } + } + else + ip = ipb[-1]; + if (unlikely(match == ip)) + { + ipb--; curOffset--; + matchIndex--; + } + else + break; + } + } + + /*if (likely((curOffset - matchIndex) <= (ipb-baseb))) + { + DEBUGLOG(6, "match for %x (0x%x) found at %x (0x%x), anchor %x", curOffset, LZ4_read32(ipb), matchIndex, LZ4_read32(ipb - (curOffset - matchIndex)), anchor); + } + else + { + int error; + DEBUGLOG(6, "match for %x (0x%x) found at %x (0x%x), anchor %x", curOffset, LZ4_read32(ipb), matchIndex, LZ4e_Read32Stream(matchIndex, ssource, &error), anchor); + }*/ + //while (((curOffset>anchor) & (matchIndex > 0)) && (unlikely(ip[-1]==match[-1]))) { ip--; match--; } + unsigned litLength = (unsigned)(curOffset - anchor);//(ip - anchor); + /* Encode Literals */ + { + if (unlikely((2 + 1 + LASTLITERALS) + (litLength/255) + 2 > streamBufSize - (opb - opbaseb))) + { + if (unlikely((2 + 1 + LASTLITERALS) + (litLength/255) > streamBufSize)) + { + DEBUGLOG(1, "break 11"); + return 0; + } + unsigned const expectedLen = opb-opbaseb; + if (expectedLen != LZ4e_outputBuffer(opbaseb, opb, olimitb, sdest)) + { + DEBUGLOG(1, "break 10"); + return 0; + } + totalOutputSize += expectedLen; + } + token = opb++; + if (litLength >= RUN_MASK) { + int len = (int)litLength-RUN_MASK; + *token = (RUN_MASK<= 255 ; len-=255) *opb++ = 255; + *opb++ = (BYTE)len; + } + else *token = (BYTE)(litLength<= ML_MASK) + *token += ML_MASK; + else + *token += (BYTE)(matchCode); + } + /* Write Literals (if any) */ + { + U32 readOffs = 0; + while (litLength > 0) + { + BYTE* pAnchor; U32 readLength; + if (unlikely(anchor+readOffs < (curOffset - (ipb-baseb)) || anchor+readOffs+litLength+8 > curOffset+(baseb+curSize-ipb))) + { + if (litLength < streamBufSize) readLength = litLength; + else readLength = streamBufSize; + if ((readLength = LZ4e_ReadStreamTemp(anchor+readOffs, tempbase, readLength, ssource)) <= 0) + { + DEBUGLOG(1, "break 9"); + return 0; + } + if (readLength > litLength) readLength = litLength; + pAnchor = tempbase; + } + else + { + pAnchor = &baseb[anchor+readOffs - (curOffset - (ipb-baseb))]; + readLength = (baseb + curSize) - pAnchor; + if (readLength > litLength) readLength = litLength; + } + if (readLength+8 > streamBufSize - (opb-opbaseb)) + { + unsigned const expectedLen = opb-opbaseb; + if (expectedLen != LZ4e_outputBuffer(opbaseb, opb, olimitb, sdest)) + { + DEBUGLOG(1, "break 8"); + return 0; + } + totalOutputSize += expectedLen; + } + U32 outputCount = readLength; + if (readLength > streamBufSize - (opb-opbaseb) - 8) + outputCount = streamBufSize - (opb-opbaseb) - 8; + /* Copy Literals */ + LZ4_wildCopy(opb, pAnchor, opb+outputCount); + opb+=outputCount; + litLength-=outputCount; + readOffs+=outputCount; + } + } + LZ4_writeLE16(opb, (U16)offset); opb+=2; + { + //matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, matchlimit); + ipb += MINMATCH + matchCode; + curOffset += MINMATCH + matchCode; + DEBUGLOG(6, " with matchLength=%x and match.source=%x", matchCode+MINMATCH, matchIndex); + if (unlikely((1 + LASTLITERALS) + (matchCode>>8) > streamBufSize-(opb-opbaseb))) + { + if (unlikely((1 + LASTLITERALS) + (matchCode>>8) > streamBufSize)) + { + DEBUGLOG(1, "break 7"); + return 0; + } + unsigned const expectedLen = opb-opbaseb; + if (expectedLen != LZ4e_outputBuffer(opbaseb, opb, olimitb, sdest)) + { + DEBUGLOG(1, "break 6"); + return 0; + } + totalOutputSize += expectedLen; + } + if (matchCode >= ML_MASK) { + //*token += ML_MASK; + matchCode -= ML_MASK; + LZ4_write32(opb, 0xFFFFFFFF); + while (matchCode >= 4*255) { + opb+=4; + LZ4_write32(opb, 0xFFFFFFFF); + matchCode -= 4*255; + } + opb += matchCode / 255; + *opb++ = (BYTE)(matchCode % 255); + } //else + // *token += (BYTE)(matchCode); + } + + //anchor = ip; + anchor = curOffset; + + /* Test end of chunk */ + if (ipb >= baseb + curSize - MFLIMIT + 1) + { + if (curSize < streamBufSize) break; + curSize = LZ4e_inputBuffer(baseb, ipb, iendb, ssource); + if (ipb >= baseb + curSize - MFLIMIT + 1) break; + } + //if (ip >= mflimitPlusOne) break; + + /* Fill table */ + //Since LZ4e_inputBuffer keeps previous data (up to 64K), there should always be 2 bytes behind ipb at this place. + LZ4_putPosition(ipb-2, curOffset-2, cctx->hashTable, tableType); + //LZ4_putPosition(ip-2, cctx->hashTable, tableType, base); + + /* Test next position */ + { /* byU32, byU16 */ + U32 const h = LZ4_hashPosition(ipb, tableType); + U32 const current = curOffset;//(U32)(ip-base); + matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType); + assert(matchIndex < current); + + LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType); + assert(matchIndex < current); + if ( ((tableType==byU16) ? 1 : (matchIndex+MAX_DISTANCE >= current)) ) + { + int error = 0; + if (likely((curOffset - matchIndex) <= (ipb-baseb)) + ? (LZ4_read32(ipb - (curOffset - matchIndex)) == LZ4_read32(ipb)) + : (LZ4e_Read32Stream(matchIndex, ssource, &error) == LZ4_read32(ipb))) { + if (unlikely(error)) + { + DEBUGLOG(1, "break 5"); + return 0; + } + token=opb++; + *token=0; + //if (maybe_extMem) offset = current - matchIndex; + offset = current - matchIndex; + DEBUGLOG(6, "seq.start:%x, literals=%u, match.start:%x", + (int)(anchor), 0, (int)(curOffset)); + goto _next_match; + } + } + } + + /* Prepare next loop */ + forwardH = LZ4_hashPosition(++ipb, tableType); + ++curOffset; + } + +_last_literals: + /* Encode Last Literals */ + { + size_t lastRun = curOffset + (baseb+curSize-ipb) - anchor; + int curSizeTemp = curSize; unsigned int curTempOffset = curOffset + (baseb+curSize-ipb); + while (curSizeTemp == streamBufSize) + { + curSizeTemp = LZ4e_ReadStreamTemp(curTempOffset, tempbase, streamBufSize, ssource); + //if (curSizeTemp >= 0) + { + lastRun += (size_t)curSizeTemp; + curTempOffset += (unsigned int)curSizeTemp; + } + } + unsigned const expectedLen = opb-opbaseb; + if (expectedLen != LZ4e_outputBuffer(opbaseb, opb, olimitb, sdest)) + { + DEBUGLOG(1, "break 4"); + return 0; + } + totalOutputSize += expectedLen; + if (1 + ((lastRun+255-RUN_MASK)/255) > streamBufSize) + { + DEBUGLOG(1, "break 3"); + return 0; + } + if (lastRun >= RUN_MASK) { + size_t accumulator = lastRun - RUN_MASK; + *opb++ = RUN_MASK << ML_BITS; + for(; accumulator >= 255 ; accumulator-=255) *opb++ = 255; + *opb++ = (BYTE) accumulator; + } else { + *opb++ = (BYTE)(lastRun< 0) { + unsigned int batchSize = streamBufSize; + if (batchSize > lastRun) + batchSize = (unsigned int)lastRun; + curSizeTemp = LZ4e_ReadStreamTemp(curTempOffset, tempbase, batchSize, ssource); + if (curSizeTemp != batchSize) + { + DEBUGLOG(1, "break 2"); + return 0; + } + unsigned const expectedLen = opb-opbaseb; + if (expectedLen != LZ4e_outputBuffer(opbaseb, opb, olimitb, sdest)) + { + DEBUGLOG(1, "break 1"); + return 0; + } + totalOutputSize += expectedLen; + memcpy(opb, tempbase, batchSize); + curTempOffset += batchSize; + opb += batchSize; + lastRun -= batchSize; + } + ssource->pos = curTempOffset; + } + + cctx->dictSize += (U32)totalInputSize; + cctx->currentOffset += (U32)totalInputSize; + unsigned const expectedLen = opb-opbaseb; + if (expectedLen != LZ4e_outputBuffer(opbaseb, opb, olimitb, sdest)) + { + DEBUGLOG(1, "break 0"); + return 0; + } + totalOutputSize += expectedLen; + DEBUGLOG(5, "LZ4_compress_generic: compressed %llu bytes into %i bytes", ssource->pos, totalOutputSize); + return totalOutputSize;//(int)(((char*)op) - dest); +} + +unsigned int LZ4_compress_fast_extState(void* state, LZ4e_instream_t* source, LZ4e_outstream_t* dest, void *streamBuf, int acceleration, unsigned int totalSize, unsigned int streamBufSize) +{ + LZ4_stream_t_internal* ctx = &((LZ4_stream_t*)state)->internal_donotuse; + if (acceleration < 1) acceleration = ACCELERATION_DEFAULT; + LZ4_resetStream((LZ4_stream_t*)state); + if (totalSize < LZ4_64Klimit) { + return LZ4_compress_generic(ctx, source, dest, streamBuf, streamBufSize, byU16, acceleration); + } else { + return LZ4_compress_generic(ctx, source, dest, streamBuf, streamBufSize, byU32, acceleration); + } +} + +unsigned int LZ4e_compress_fast(LZ4e_instream_t* source, LZ4e_outstream_t* dest, int acceleration, unsigned int totalSize, unsigned int streamBufSize) +{ + unsigned int result; +#if (LZ4_HEAPMODE) + LZ4_stream_t* ctxPtr = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t)); /* malloc-calloc always properly aligned */ + if (ctxPtr == NULL) return 0; + if (streamBufSize < LZ4_64Klimit) + streamBufSize = LZ4_64Klimit; + void* streamBuf = ALLOC(streamBufSize * 3); + if (streamBuf == NULL) + { + FREEMEM(ctxPtr); + return 0; + } +#else + LZ4_stream_t ctx; + LZ4_stream_t* const ctxPtr = &ctx; + streamBufSize = LZ4_64Klimit; + BYTE streamBuf[LZ4_64Klimit * 3]; +#endif + result = LZ4_compress_fast_extState(ctxPtr, source, dest, streamBuf, acceleration, totalSize, streamBufSize); + +#if (LZ4_HEAPMODE) + FREEMEM(streamBuf); + FREEMEM(ctxPtr); +#endif + return result; +} \ No newline at end of file diff --git a/libCompression/lz4enc.h b/libCompression/lz4enc.h new file mode 100644 index 0000000..b15d06c --- /dev/null +++ b/libCompression/lz4enc.h @@ -0,0 +1,4 @@ +#pragma once +#include "lz4e.h" + +unsigned int LZ4e_compress_fast(LZ4e_instream_t* source, LZ4e_outstream_t* dest, int acceleration, unsigned int totalSize = (unsigned int)-1, unsigned int streamBufSize = 8519680); \ No newline at end of file diff --git a/libCompression/lz4frame.c b/libCompression/lz4frame.c new file mode 100644 index 0000000..9069717 --- /dev/null +++ b/libCompression/lz4frame.c @@ -0,0 +1,1920 @@ +/* + * LZ4 auto-framing library + * Copyright (C) 2011-2016, Yann Collet. + * + * BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * You can contact the author at : + * - LZ4 homepage : http://www.lz4.org + * - LZ4 source repository : https://github.com/lz4/lz4 + */ + +/* LZ4F is a stand-alone API to create LZ4-compressed Frames + * in full conformance with specification v1.6.1 . + * This library rely upon memory management capabilities (malloc, free) + * provided either by , + * or redirected towards another library of user's choice + * (see Memory Routines below). + */ + + +/*-************************************ +* Compiler Options +**************************************/ +#ifdef _MSC_VER /* Visual Studio */ +# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ +#endif + + +/*-************************************ +* Tuning parameters +**************************************/ +/* + * LZ4F_HEAPMODE : + * Select how default compression functions will allocate memory for their hash table, + * in memory stack (0:default, fastest), or in memory heap (1:requires malloc()). + */ +#ifndef LZ4F_HEAPMODE +# define LZ4F_HEAPMODE 0 +#endif + + +/*-************************************ +* Memory routines +**************************************/ +/* + * User may redirect invocations of + * malloc(), calloc() and free() + * towards another library or solution of their choice + * by modifying below section. + */ +#ifndef LZ4_SRC_INCLUDED /* avoid redefinition when sources are coalesced */ +# include /* malloc, calloc, free */ +# define ALLOC(s) malloc(s) +# define ALLOC_AND_ZERO(s) calloc(1,(s)) +# define FREEMEM(p) free(p) +#endif + +#include /* memset, memcpy, memmove */ +#ifndef LZ4_SRC_INCLUDED /* avoid redefinition when sources are coalesced */ +# define MEM_INIT(p,v,s) memset((p),(v),(s)) +#endif + + +/*-************************************ +* Library declarations +**************************************/ +#define LZ4F_STATIC_LINKING_ONLY +#include "lz4frame.h" +#define LZ4_STATIC_LINKING_ONLY +#include "lz4.h" +#define LZ4_HC_STATIC_LINKING_ONLY +#include "lz4hc.h" +#define XXH_STATIC_LINKING_ONLY +#include "xxhash.h" + + +/*-************************************ +* Debug +**************************************/ +#if defined(LZ4_DEBUG) && (LZ4_DEBUG>=1) +# include +#else +# ifndef assert +# define assert(condition) ((void)0) +# endif +#endif + +#define LZ4F_STATIC_ASSERT(c) { enum { LZ4F_static_assert = 1/(int)(!!(c)) }; } /* use only *after* variable declarations */ + +#if defined(LZ4_DEBUG) && (LZ4_DEBUG>=2) && !defined(DEBUGLOG) +# include +static int g_debuglog_enable = 1; +# define DEBUGLOG(l, ...) { \ + if ((g_debuglog_enable) && (l<=LZ4_DEBUG)) { \ + fprintf(stderr, __FILE__ ": "); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, " \n"); \ + } } +#else +# define DEBUGLOG(l, ...) {} /* disabled */ +#endif + + +/*-************************************ +* Basic Types +**************************************/ +#if !defined (__VMS) && (defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) +# include + typedef uint8_t BYTE; + typedef uint16_t U16; + typedef uint32_t U32; + typedef int32_t S32; + typedef uint64_t U64; +#else + typedef unsigned char BYTE; + typedef unsigned short U16; + typedef unsigned int U32; + typedef signed int S32; + typedef unsigned long long U64; +#endif + + +/* unoptimized version; solves endianess & alignment issues */ +static U32 LZ4F_readLE32 (const void* src) +{ + const BYTE* const srcPtr = (const BYTE*)src; + U32 value32 = srcPtr[0]; + value32 += ((U32)srcPtr[1])<< 8; + value32 += ((U32)srcPtr[2])<<16; + value32 += ((U32)srcPtr[3])<<24; + return value32; +} + +static void LZ4F_writeLE32 (void* dst, U32 value32) +{ + BYTE* const dstPtr = (BYTE*)dst; + dstPtr[0] = (BYTE)value32; + dstPtr[1] = (BYTE)(value32 >> 8); + dstPtr[2] = (BYTE)(value32 >> 16); + dstPtr[3] = (BYTE)(value32 >> 24); +} + +static U64 LZ4F_readLE64 (const void* src) +{ + const BYTE* const srcPtr = (const BYTE*)src; + U64 value64 = srcPtr[0]; + value64 += ((U64)srcPtr[1]<<8); + value64 += ((U64)srcPtr[2]<<16); + value64 += ((U64)srcPtr[3]<<24); + value64 += ((U64)srcPtr[4]<<32); + value64 += ((U64)srcPtr[5]<<40); + value64 += ((U64)srcPtr[6]<<48); + value64 += ((U64)srcPtr[7]<<56); + return value64; +} + +static void LZ4F_writeLE64 (void* dst, U64 value64) +{ + BYTE* const dstPtr = (BYTE*)dst; + dstPtr[0] = (BYTE)value64; + dstPtr[1] = (BYTE)(value64 >> 8); + dstPtr[2] = (BYTE)(value64 >> 16); + dstPtr[3] = (BYTE)(value64 >> 24); + dstPtr[4] = (BYTE)(value64 >> 32); + dstPtr[5] = (BYTE)(value64 >> 40); + dstPtr[6] = (BYTE)(value64 >> 48); + dstPtr[7] = (BYTE)(value64 >> 56); +} + + +/*-************************************ +* Constants +**************************************/ +#ifndef LZ4_SRC_INCLUDED /* avoid double definition */ +# define KB *(1<<10) +# define MB *(1<<20) +# define GB *(1<<30) +#endif + +#define _1BIT 0x01 +#define _2BITS 0x03 +#define _3BITS 0x07 +#define _4BITS 0x0F +#define _8BITS 0xFF + +#define LZ4F_MAGIC_SKIPPABLE_START 0x184D2A50U +#define LZ4F_MAGICNUMBER 0x184D2204U +#define LZ4F_BLOCKUNCOMPRESSED_FLAG 0x80000000U +#define LZ4F_BLOCKSIZEID_DEFAULT LZ4F_max64KB + +static const size_t minFHSize = LZ4F_HEADER_SIZE_MIN; /* 7 */ +static const size_t maxFHSize = LZ4F_HEADER_SIZE_MAX; /* 19 */ +static const size_t BHSize = LZ4F_BLOCK_HEADER_SIZE; /* block header : size, and compress flag */ +static const size_t BFSize = LZ4F_BLOCK_CHECKSUM_SIZE; /* block footer : checksum (optional) */ + + +/*-************************************ +* Structures and local types +**************************************/ +typedef struct LZ4F_cctx_s +{ + LZ4F_preferences_t prefs; + U32 version; + U32 cStage; + const LZ4F_CDict* cdict; + size_t maxBlockSize; + size_t maxBufferSize; + BYTE* tmpBuff; /* internal buffer, for streaming */ + BYTE* tmpIn; /* starting position of data compress within internal buffer (>= tmpBuff) */ + size_t tmpInSize; /* amount of data to compress after tmpIn */ + U64 totalInSize; + XXH32_state_t xxh; + void* lz4CtxPtr; + U16 lz4CtxAlloc; /* sized for: 0 = none, 1 = lz4 ctx, 2 = lz4hc ctx */ + U16 lz4CtxState; /* in use as: 0 = none, 1 = lz4 ctx, 2 = lz4hc ctx */ +} LZ4F_cctx_t; + + +/*-************************************ +* Error management +**************************************/ +#define LZ4F_GENERATE_STRING(STRING) #STRING, +static const char* LZ4F_errorStrings[] = { LZ4F_LIST_ERRORS(LZ4F_GENERATE_STRING) }; + + +unsigned LZ4F_isError(LZ4F_errorCode_t code) +{ + return (code > (LZ4F_errorCode_t)(-LZ4F_ERROR_maxCode)); +} + +const char* LZ4F_getErrorName(LZ4F_errorCode_t code) +{ + static const char* codeError = "Unspecified error code"; + if (LZ4F_isError(code)) return LZ4F_errorStrings[-(int)(code)]; + return codeError; +} + +LZ4F_errorCodes LZ4F_getErrorCode(size_t functionResult) +{ + if (!LZ4F_isError(functionResult)) return LZ4F_OK_NoError; + return (LZ4F_errorCodes)(-(ptrdiff_t)functionResult); +} + +static LZ4F_errorCode_t err0r(LZ4F_errorCodes code) +{ + /* A compilation error here means sizeof(ptrdiff_t) is not large enough */ + LZ4F_STATIC_ASSERT(sizeof(ptrdiff_t) >= sizeof(size_t)); + return (LZ4F_errorCode_t)-(ptrdiff_t)code; +} + +unsigned LZ4F_getVersion(void) { return LZ4F_VERSION; } + +int LZ4F_compressionLevel_max(void) { return LZ4HC_CLEVEL_MAX; } + +size_t LZ4F_getBlockSize(unsigned blockSizeID) +{ + static const size_t blockSizes[4] = { 64 KB, 256 KB, 1 MB, 4 MB }; + + if (blockSizeID == 0) blockSizeID = LZ4F_BLOCKSIZEID_DEFAULT; + if (blockSizeID < LZ4F_max64KB || blockSizeID > LZ4F_max4MB) + return err0r(LZ4F_ERROR_maxBlockSize_invalid); + blockSizeID -= LZ4F_max64KB; + return blockSizes[blockSizeID]; +} + +/*-************************************ +* Private functions +**************************************/ +#define MIN(a,b) ( (a) < (b) ? (a) : (b) ) + +static BYTE LZ4F_headerChecksum (const void* header, size_t length) +{ + U32 const xxh = XXH32(header, length, 0); + return (BYTE)(xxh >> 8); +} + + +/*-************************************ +* Simple-pass compression functions +**************************************/ +static LZ4F_blockSizeID_t LZ4F_optimalBSID(const LZ4F_blockSizeID_t requestedBSID, + const size_t srcSize) +{ + LZ4F_blockSizeID_t proposedBSID = LZ4F_max64KB; + size_t maxBlockSize = 64 KB; + while (requestedBSID > proposedBSID) { + if (srcSize <= maxBlockSize) + return proposedBSID; + proposedBSID = (LZ4F_blockSizeID_t)((int)proposedBSID + 1); + maxBlockSize <<= 2; + } + return requestedBSID; +} + +/*! LZ4F_compressBound_internal() : + * Provides dstCapacity given a srcSize to guarantee operation success in worst case situations. + * prefsPtr is optional : if NULL is provided, preferences will be set to cover worst case scenario. + * @return is always the same for a srcSize and prefsPtr, so it can be relied upon to size reusable buffers. + * When srcSize==0, LZ4F_compressBound() provides an upper bound for LZ4F_flush() and LZ4F_compressEnd() operations. + */ +static size_t LZ4F_compressBound_internal(size_t srcSize, + const LZ4F_preferences_t* preferencesPtr, + size_t alreadyBuffered) +{ + LZ4F_preferences_t prefsNull = LZ4F_INIT_PREFERENCES; + prefsNull.frameInfo.contentChecksumFlag = LZ4F_contentChecksumEnabled; /* worst case */ + prefsNull.frameInfo.blockChecksumFlag = LZ4F_blockChecksumEnabled; /* worst case */ + { const LZ4F_preferences_t* const prefsPtr = (preferencesPtr==NULL) ? &prefsNull : preferencesPtr; + U32 const flush = prefsPtr->autoFlush | (srcSize==0); + LZ4F_blockSizeID_t const blockID = prefsPtr->frameInfo.blockSizeID; + size_t const blockSize = LZ4F_getBlockSize(blockID); + size_t const maxBuffered = blockSize - 1; + size_t const bufferedSize = MIN(alreadyBuffered, maxBuffered); + size_t const maxSrcSize = srcSize + bufferedSize; + unsigned const nbFullBlocks = (unsigned)(maxSrcSize / blockSize); + size_t const partialBlockSize = maxSrcSize & (blockSize-1); + size_t const lastBlockSize = flush ? partialBlockSize : 0; + unsigned const nbBlocks = nbFullBlocks + (lastBlockSize>0); + + size_t const blockCRCSize = BFSize * prefsPtr->frameInfo.blockChecksumFlag; + size_t const frameEnd = BHSize + (prefsPtr->frameInfo.contentChecksumFlag*BFSize); + + return ((BHSize + blockCRCSize) * nbBlocks) + + (blockSize * nbFullBlocks) + lastBlockSize + frameEnd; + } +} + +size_t LZ4F_compressFrameBound(size_t srcSize, const LZ4F_preferences_t* preferencesPtr) +{ + LZ4F_preferences_t prefs; + size_t const headerSize = maxFHSize; /* max header size, including optional fields */ + + if (preferencesPtr!=NULL) prefs = *preferencesPtr; + else MEM_INIT(&prefs, 0, sizeof(prefs)); + prefs.autoFlush = 1; + + return headerSize + LZ4F_compressBound_internal(srcSize, &prefs, 0);; +} + + +/*! LZ4F_compressFrame_usingCDict() : + * Compress srcBuffer using a dictionary, in a single step. + * cdict can be NULL, in which case, no dictionary is used. + * dstBuffer MUST be >= LZ4F_compressFrameBound(srcSize, preferencesPtr). + * The LZ4F_preferences_t structure is optional : you may provide NULL as argument, + * however, it's the only way to provide a dictID, so it's not recommended. + * @return : number of bytes written into dstBuffer, + * or an error code if it fails (can be tested using LZ4F_isError()) + */ +size_t LZ4F_compressFrame_usingCDict(LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, + const void* srcBuffer, size_t srcSize, + const LZ4F_CDict* cdict, + const LZ4F_preferences_t* preferencesPtr) +{ + LZ4F_preferences_t prefs; + LZ4F_compressOptions_t options; + BYTE* const dstStart = (BYTE*) dstBuffer; + BYTE* dstPtr = dstStart; + BYTE* const dstEnd = dstStart + dstCapacity; + + if (preferencesPtr!=NULL) + prefs = *preferencesPtr; + else + MEM_INIT(&prefs, 0, sizeof(prefs)); + if (prefs.frameInfo.contentSize != 0) + prefs.frameInfo.contentSize = (U64)srcSize; /* auto-correct content size if selected (!=0) */ + + prefs.frameInfo.blockSizeID = LZ4F_optimalBSID(prefs.frameInfo.blockSizeID, srcSize); + prefs.autoFlush = 1; + if (srcSize <= LZ4F_getBlockSize(prefs.frameInfo.blockSizeID)) + prefs.frameInfo.blockMode = LZ4F_blockIndependent; /* only one block => no need for inter-block link */ + + MEM_INIT(&options, 0, sizeof(options)); + options.stableSrc = 1; + + if (dstCapacity < LZ4F_compressFrameBound(srcSize, &prefs)) /* condition to guarantee success */ + return err0r(LZ4F_ERROR_dstMaxSize_tooSmall); + + { size_t const headerSize = LZ4F_compressBegin_usingCDict(cctx, dstBuffer, dstCapacity, cdict, &prefs); /* write header */ + if (LZ4F_isError(headerSize)) return headerSize; + dstPtr += headerSize; /* header size */ } + + assert(dstEnd >= dstPtr); + { size_t const cSize = LZ4F_compressUpdate(cctx, dstPtr, (size_t)(dstEnd-dstPtr), srcBuffer, srcSize, &options); + if (LZ4F_isError(cSize)) return cSize; + dstPtr += cSize; } + + assert(dstEnd >= dstPtr); + { size_t const tailSize = LZ4F_compressEnd(cctx, dstPtr, (size_t)(dstEnd-dstPtr), &options); /* flush last block, and generate suffix */ + if (LZ4F_isError(tailSize)) return tailSize; + dstPtr += tailSize; } + + assert(dstEnd >= dstStart); + return (size_t)(dstPtr - dstStart); +} + + +/*! LZ4F_compressFrame() : + * Compress an entire srcBuffer into a valid LZ4 frame, in a single step. + * dstBuffer MUST be >= LZ4F_compressFrameBound(srcSize, preferencesPtr). + * The LZ4F_preferences_t structure is optional : you can provide NULL as argument. All preferences will be set to default. + * @return : number of bytes written into dstBuffer. + * or an error code if it fails (can be tested using LZ4F_isError()) + */ +size_t LZ4F_compressFrame(void* dstBuffer, size_t dstCapacity, + const void* srcBuffer, size_t srcSize, + const LZ4F_preferences_t* preferencesPtr) +{ + size_t result; +#if (LZ4F_HEAPMODE) + LZ4F_cctx_t *cctxPtr; + result = LZ4F_createCompressionContext(&cctxPtr, LZ4F_VERSION); + if (LZ4F_isError(result)) return result; +#else + LZ4F_cctx_t cctx; + LZ4_stream_t lz4ctx; + LZ4F_cctx_t *cctxPtr = &cctx; + + DEBUGLOG(4, "LZ4F_compressFrame"); + MEM_INIT(&cctx, 0, sizeof(cctx)); + cctx.version = LZ4F_VERSION; + cctx.maxBufferSize = 5 MB; /* mess with real buffer size to prevent dynamic allocation; works only because autoflush==1 & stableSrc==1 */ + if (preferencesPtr == NULL || + preferencesPtr->compressionLevel < LZ4HC_CLEVEL_MIN) + { + LZ4_initStream(&lz4ctx, sizeof(lz4ctx)); + cctxPtr->lz4CtxPtr = &lz4ctx; + cctxPtr->lz4CtxAlloc = 1; + cctxPtr->lz4CtxState = 1; + } +#endif + + result = LZ4F_compressFrame_usingCDict(cctxPtr, dstBuffer, dstCapacity, + srcBuffer, srcSize, + NULL, preferencesPtr); + +#if (LZ4F_HEAPMODE) + LZ4F_freeCompressionContext(cctxPtr); +#else + if (preferencesPtr != NULL && + preferencesPtr->compressionLevel >= LZ4HC_CLEVEL_MIN) + { + FREEMEM(cctxPtr->lz4CtxPtr); + } +#endif + return result; +} + + +/*-*************************************************** +* Dictionary compression +*****************************************************/ + +struct LZ4F_CDict_s { + void* dictContent; + LZ4_stream_t* fastCtx; + LZ4_streamHC_t* HCCtx; +}; /* typedef'd to LZ4F_CDict within lz4frame_static.h */ + +/*! LZ4F_createCDict() : + * When compressing multiple messages / blocks with the same dictionary, it's recommended to load it just once. + * LZ4F_createCDict() will create a digested dictionary, ready to start future compression operations without startup delay. + * LZ4F_CDict can be created once and shared by multiple threads concurrently, since its usage is read-only. + * `dictBuffer` can be released after LZ4F_CDict creation, since its content is copied within CDict + * @return : digested dictionary for compression, or NULL if failed */ +LZ4F_CDict* LZ4F_createCDict(const void* dictBuffer, size_t dictSize) +{ + const char* dictStart = (const char*)dictBuffer; + LZ4F_CDict* cdict = (LZ4F_CDict*) ALLOC(sizeof(*cdict)); + DEBUGLOG(4, "LZ4F_createCDict"); + if (!cdict) return NULL; + if (dictSize > 64 KB) { + dictStart += dictSize - 64 KB; + dictSize = 64 KB; + } + cdict->dictContent = ALLOC(dictSize); + cdict->fastCtx = LZ4_createStream(); + cdict->HCCtx = LZ4_createStreamHC(); + if (!cdict->dictContent || !cdict->fastCtx || !cdict->HCCtx) { + LZ4F_freeCDict(cdict); + return NULL; + } + memcpy(cdict->dictContent, dictStart, dictSize); + LZ4_loadDict (cdict->fastCtx, (const char*)cdict->dictContent, (int)dictSize); + LZ4_setCompressionLevel(cdict->HCCtx, LZ4HC_CLEVEL_DEFAULT); + LZ4_loadDictHC(cdict->HCCtx, (const char*)cdict->dictContent, (int)dictSize); + return cdict; +} + +void LZ4F_freeCDict(LZ4F_CDict* cdict) +{ + if (cdict==NULL) return; /* support free on NULL */ + FREEMEM(cdict->dictContent); + LZ4_freeStream(cdict->fastCtx); + LZ4_freeStreamHC(cdict->HCCtx); + FREEMEM(cdict); +} + + +/*-********************************* +* Advanced compression functions +***********************************/ + +/*! LZ4F_createCompressionContext() : + * The first thing to do is to create a compressionContext object, which will be used in all compression operations. + * This is achieved using LZ4F_createCompressionContext(), which takes as argument a version and an LZ4F_preferences_t structure. + * The version provided MUST be LZ4F_VERSION. It is intended to track potential incompatible differences between different binaries. + * The function will provide a pointer to an allocated LZ4F_compressionContext_t object. + * If the result LZ4F_errorCode_t is not OK_NoError, there was an error during context creation. + * Object can release its memory using LZ4F_freeCompressionContext(); + */ +LZ4F_errorCode_t LZ4F_createCompressionContext(LZ4F_cctx** LZ4F_compressionContextPtr, unsigned version) +{ + LZ4F_cctx_t* const cctxPtr = (LZ4F_cctx_t*)ALLOC_AND_ZERO(sizeof(LZ4F_cctx_t)); + if (cctxPtr==NULL) return err0r(LZ4F_ERROR_allocation_failed); + + cctxPtr->version = version; + cctxPtr->cStage = 0; /* Uninitialized. Next stage : init cctx */ + + *LZ4F_compressionContextPtr = cctxPtr; + + return LZ4F_OK_NoError; +} + + +LZ4F_errorCode_t LZ4F_freeCompressionContext(LZ4F_cctx* cctxPtr) +{ + if (cctxPtr != NULL) { /* support free on NULL */ + FREEMEM(cctxPtr->lz4CtxPtr); /* note: LZ4_streamHC_t and LZ4_stream_t are simple POD types */ + FREEMEM(cctxPtr->tmpBuff); + FREEMEM(cctxPtr); + } + + return LZ4F_OK_NoError; +} + + +/** + * This function prepares the internal LZ4(HC) stream for a new compression, + * resetting the context and attaching the dictionary, if there is one. + * + * It needs to be called at the beginning of each independent compression + * stream (i.e., at the beginning of a frame in blockLinked mode, or at the + * beginning of each block in blockIndependent mode). + */ +static void LZ4F_initStream(void* ctx, + const LZ4F_CDict* cdict, + int level, + LZ4F_blockMode_t blockMode) { + if (level < LZ4HC_CLEVEL_MIN) { + if (cdict != NULL || blockMode == LZ4F_blockLinked) { + /* In these cases, we will call LZ4_compress_fast_continue(), + * which needs an already reset context. Otherwise, we'll call a + * one-shot API. The non-continued APIs internally perform their own + * resets at the beginning of their calls, where they know what + * tableType they need the context to be in. So in that case this + * would be misguided / wasted work. */ + LZ4_resetStream_fast((LZ4_stream_t*)ctx); + } + LZ4_attach_dictionary((LZ4_stream_t *)ctx, cdict ? cdict->fastCtx : NULL); + } else { + LZ4_resetStreamHC_fast((LZ4_streamHC_t*)ctx, level); + LZ4_attach_HC_dictionary((LZ4_streamHC_t *)ctx, cdict ? cdict->HCCtx : NULL); + } +} + +static int ctxTypeID_to_size(int ctxTypeID) { + switch(ctxTypeID) { + case 1: + return LZ4_sizeofState(); + case 2: + return LZ4_sizeofStateHC(); + default: + return 0; + } +} + +/*! LZ4F_compressBegin_usingCDict() : + * init streaming compression AND writes frame header into @dstBuffer. + * @dstCapacity must be >= LZ4F_HEADER_SIZE_MAX bytes. + * @return : number of bytes written into @dstBuffer for the header + * or an error code (can be tested using LZ4F_isError()) + */ +size_t LZ4F_compressBegin_usingCDict(LZ4F_cctx* cctxPtr, + void* dstBuffer, size_t dstCapacity, + const LZ4F_CDict* cdict, + const LZ4F_preferences_t* preferencesPtr) +{ + LZ4F_preferences_t const prefNull = LZ4F_INIT_PREFERENCES; + BYTE* const dstStart = (BYTE*)dstBuffer; + BYTE* dstPtr = dstStart; + + if (dstCapacity < maxFHSize) return err0r(LZ4F_ERROR_dstMaxSize_tooSmall); + if (preferencesPtr == NULL) preferencesPtr = &prefNull; + cctxPtr->prefs = *preferencesPtr; + + /* cctx Management */ + { U16 const ctxTypeID = (cctxPtr->prefs.compressionLevel < LZ4HC_CLEVEL_MIN) ? 1 : 2; + int requiredSize = ctxTypeID_to_size(ctxTypeID); + int allocatedSize = ctxTypeID_to_size(cctxPtr->lz4CtxAlloc); + if (allocatedSize < requiredSize) { + /* not enough space allocated */ + FREEMEM(cctxPtr->lz4CtxPtr); + if (cctxPtr->prefs.compressionLevel < LZ4HC_CLEVEL_MIN) { + cctxPtr->lz4CtxPtr = LZ4_createStream(); + } else { + cctxPtr->lz4CtxPtr = LZ4_createStreamHC(); + } + if (cctxPtr->lz4CtxPtr == NULL) + return err0r(LZ4F_ERROR_allocation_failed); + cctxPtr->lz4CtxAlloc = ctxTypeID; + cctxPtr->lz4CtxState = ctxTypeID; + } else if (cctxPtr->lz4CtxState != ctxTypeID) { + /* otherwise, a sufficient buffer is already allocated, + * but we need to reset it to the correct context type */ + if (cctxPtr->prefs.compressionLevel < LZ4HC_CLEVEL_MIN) { + LZ4_initStream((LZ4_stream_t*)cctxPtr->lz4CtxPtr, sizeof(LZ4_stream_t)); + } else { + LZ4_initStreamHC((LZ4_streamHC_t*)cctxPtr->lz4CtxPtr, sizeof(LZ4_streamHC_t)); + LZ4_setCompressionLevel((LZ4_streamHC_t*)cctxPtr->lz4CtxPtr, cctxPtr->prefs.compressionLevel); + } + cctxPtr->lz4CtxState = ctxTypeID; + } + } + + /* Buffer Management */ + if (cctxPtr->prefs.frameInfo.blockSizeID == 0) + cctxPtr->prefs.frameInfo.blockSizeID = LZ4F_BLOCKSIZEID_DEFAULT; + cctxPtr->maxBlockSize = LZ4F_getBlockSize(cctxPtr->prefs.frameInfo.blockSizeID); + + { size_t const requiredBuffSize = preferencesPtr->autoFlush ? + ((cctxPtr->prefs.frameInfo.blockMode == LZ4F_blockLinked) ? 64 KB : 0) : /* only needs past data up to window size */ + cctxPtr->maxBlockSize + ((cctxPtr->prefs.frameInfo.blockMode == LZ4F_blockLinked) ? 128 KB : 0); + + if (cctxPtr->maxBufferSize < requiredBuffSize) { + cctxPtr->maxBufferSize = 0; + FREEMEM(cctxPtr->tmpBuff); + cctxPtr->tmpBuff = (BYTE*)ALLOC_AND_ZERO(requiredBuffSize); + if (cctxPtr->tmpBuff == NULL) return err0r(LZ4F_ERROR_allocation_failed); + cctxPtr->maxBufferSize = requiredBuffSize; + } } + cctxPtr->tmpIn = cctxPtr->tmpBuff; + cctxPtr->tmpInSize = 0; + (void)XXH32_reset(&(cctxPtr->xxh), 0); + + /* context init */ + cctxPtr->cdict = cdict; + if (cctxPtr->prefs.frameInfo.blockMode == LZ4F_blockLinked) { + /* frame init only for blockLinked : blockIndependent will be init at each block */ + LZ4F_initStream(cctxPtr->lz4CtxPtr, cdict, cctxPtr->prefs.compressionLevel, LZ4F_blockLinked); + } + if (preferencesPtr->compressionLevel >= LZ4HC_CLEVEL_MIN) { + LZ4_favorDecompressionSpeed((LZ4_streamHC_t*)cctxPtr->lz4CtxPtr, (int)preferencesPtr->favorDecSpeed); + } + + /* Magic Number */ + LZ4F_writeLE32(dstPtr, LZ4F_MAGICNUMBER); + dstPtr += 4; + { BYTE* const headerStart = dstPtr; + + /* FLG Byte */ + *dstPtr++ = (BYTE)(((1 & _2BITS) << 6) /* Version('01') */ + + ((cctxPtr->prefs.frameInfo.blockMode & _1BIT ) << 5) + + ((cctxPtr->prefs.frameInfo.blockChecksumFlag & _1BIT ) << 4) + + ((unsigned)(cctxPtr->prefs.frameInfo.contentSize > 0) << 3) + + ((cctxPtr->prefs.frameInfo.contentChecksumFlag & _1BIT ) << 2) + + (cctxPtr->prefs.frameInfo.dictID > 0) ); + /* BD Byte */ + *dstPtr++ = (BYTE)((cctxPtr->prefs.frameInfo.blockSizeID & _3BITS) << 4); + /* Optional Frame content size field */ + if (cctxPtr->prefs.frameInfo.contentSize) { + LZ4F_writeLE64(dstPtr, cctxPtr->prefs.frameInfo.contentSize); + dstPtr += 8; + cctxPtr->totalInSize = 0; + } + /* Optional dictionary ID field */ + if (cctxPtr->prefs.frameInfo.dictID) { + LZ4F_writeLE32(dstPtr, cctxPtr->prefs.frameInfo.dictID); + dstPtr += 4; + } + /* Header CRC Byte */ + *dstPtr = LZ4F_headerChecksum(headerStart, (size_t)(dstPtr - headerStart)); + dstPtr++; + } + + cctxPtr->cStage = 1; /* header written, now request input data block */ + return (size_t)(dstPtr - dstStart); +} + + +/*! LZ4F_compressBegin() : + * init streaming compression AND writes frame header into @dstBuffer. + * @dstCapacity must be >= LZ4F_HEADER_SIZE_MAX bytes. + * @preferencesPtr can be NULL, in which case default parameters are selected. + * @return : number of bytes written into dstBuffer for the header + * or an error code (can be tested using LZ4F_isError()) + */ +size_t LZ4F_compressBegin(LZ4F_cctx* cctxPtr, + void* dstBuffer, size_t dstCapacity, + const LZ4F_preferences_t* preferencesPtr) +{ + return LZ4F_compressBegin_usingCDict(cctxPtr, dstBuffer, dstCapacity, + NULL, preferencesPtr); +} + + +/* LZ4F_compressBound() : + * @return minimum capacity of dstBuffer for a given srcSize to handle worst case scenario. + * LZ4F_preferences_t structure is optional : if NULL, preferences will be set to cover worst case scenario. + * This function cannot fail. + */ +size_t LZ4F_compressBound(size_t srcSize, const LZ4F_preferences_t* preferencesPtr) +{ + if (preferencesPtr && preferencesPtr->autoFlush) { + return LZ4F_compressBound_internal(srcSize, preferencesPtr, 0); + } + return LZ4F_compressBound_internal(srcSize, preferencesPtr, (size_t)-1); +} + + +typedef int (*compressFunc_t)(void* ctx, const char* src, char* dst, int srcSize, int dstSize, int level, const LZ4F_CDict* cdict); + + +/*! LZ4F_makeBlock(): + * compress a single block, add header and optional checksum. + * assumption : dst buffer capacity is >= BHSize + srcSize + crcSize + */ +static size_t LZ4F_makeBlock(void* dst, + const void* src, size_t srcSize, + compressFunc_t compress, void* lz4ctx, int level, + const LZ4F_CDict* cdict, + LZ4F_blockChecksum_t crcFlag) +{ + BYTE* const cSizePtr = (BYTE*)dst; + U32 cSize = (U32)compress(lz4ctx, (const char*)src, (char*)(cSizePtr+BHSize), + (int)(srcSize), (int)(srcSize-1), + level, cdict); + if (cSize == 0) { /* compression failed */ + DEBUGLOG(5, "LZ4F_makeBlock: compression failed, creating a raw block (size %u)", (U32)srcSize); + cSize = (U32)srcSize; + LZ4F_writeLE32(cSizePtr, cSize | LZ4F_BLOCKUNCOMPRESSED_FLAG); + memcpy(cSizePtr+BHSize, src, srcSize); + } else { + LZ4F_writeLE32(cSizePtr, cSize); + } + if (crcFlag) { + U32 const crc32 = XXH32(cSizePtr+BHSize, cSize, 0); /* checksum of compressed data */ + LZ4F_writeLE32(cSizePtr+BHSize+cSize, crc32); + } + return BHSize + cSize + ((U32)crcFlag)*BFSize; +} + + +static int LZ4F_compressBlock(void* ctx, const char* src, char* dst, int srcSize, int dstCapacity, int level, const LZ4F_CDict* cdict) +{ + int const acceleration = (level < 0) ? -level + 1 : 1; + DEBUGLOG(5, "LZ4F_compressBlock (srcSize=%i)", srcSize); + LZ4F_initStream(ctx, cdict, level, LZ4F_blockIndependent); + if (cdict) { + return LZ4_compress_fast_continue((LZ4_stream_t*)ctx, src, dst, srcSize, dstCapacity, acceleration); + } else { + return LZ4_compress_fast_extState_fastReset(ctx, src, dst, srcSize, dstCapacity, acceleration); + } +} + +static int LZ4F_compressBlock_continue(void* ctx, const char* src, char* dst, int srcSize, int dstCapacity, int level, const LZ4F_CDict* cdict) +{ + int const acceleration = (level < 0) ? -level + 1 : 1; + (void)cdict; /* init once at beginning of frame */ + DEBUGLOG(5, "LZ4F_compressBlock_continue (srcSize=%i)", srcSize); + return LZ4_compress_fast_continue((LZ4_stream_t*)ctx, src, dst, srcSize, dstCapacity, acceleration); +} + +static int LZ4F_compressBlockHC(void* ctx, const char* src, char* dst, int srcSize, int dstCapacity, int level, const LZ4F_CDict* cdict) +{ + LZ4F_initStream(ctx, cdict, level, LZ4F_blockIndependent); + if (cdict) { + return LZ4_compress_HC_continue((LZ4_streamHC_t*)ctx, src, dst, srcSize, dstCapacity); + } + return LZ4_compress_HC_extStateHC_fastReset(ctx, src, dst, srcSize, dstCapacity, level); +} + +static int LZ4F_compressBlockHC_continue(void* ctx, const char* src, char* dst, int srcSize, int dstCapacity, int level, const LZ4F_CDict* cdict) +{ + (void)level; (void)cdict; /* init once at beginning of frame */ + return LZ4_compress_HC_continue((LZ4_streamHC_t*)ctx, src, dst, srcSize, dstCapacity); +} + +static compressFunc_t LZ4F_selectCompression(LZ4F_blockMode_t blockMode, int level) +{ + if (level < LZ4HC_CLEVEL_MIN) { + if (blockMode == LZ4F_blockIndependent) return LZ4F_compressBlock; + return LZ4F_compressBlock_continue; + } + if (blockMode == LZ4F_blockIndependent) return LZ4F_compressBlockHC; + return LZ4F_compressBlockHC_continue; +} + +/* Save history (up to 64KB) into @tmpBuff */ +static int LZ4F_localSaveDict(LZ4F_cctx_t* cctxPtr) +{ + if (cctxPtr->prefs.compressionLevel < LZ4HC_CLEVEL_MIN) + return LZ4_saveDict ((LZ4_stream_t*)(cctxPtr->lz4CtxPtr), (char*)(cctxPtr->tmpBuff), 64 KB); + return LZ4_saveDictHC ((LZ4_streamHC_t*)(cctxPtr->lz4CtxPtr), (char*)(cctxPtr->tmpBuff), 64 KB); +} + +typedef enum { notDone, fromTmpBuffer, fromSrcBuffer } LZ4F_lastBlockStatus; + +static const LZ4F_compressOptions_t k_cOptionsNull = { 0, { 0, 0, 0 } }; + +/*! LZ4F_compressUpdate() : + * LZ4F_compressUpdate() can be called repetitively to compress as much data as necessary. + * When successful, the function always entirely consumes @srcBuffer. + * src data is either buffered or compressed into @dstBuffer. + * @dstCapacity MUST be >= LZ4F_compressBound(srcSize, preferencesPtr). + * @compressOptionsPtr is optional : provide NULL to mean "default". + * @return : the number of bytes written into dstBuffer. It can be zero, meaning input data was just buffered. + * or an error code if it fails (which can be tested using LZ4F_isError()) + * After an error, the state is left in a UB state, and must be re-initialized. + */ +size_t LZ4F_compressUpdate(LZ4F_cctx* cctxPtr, + void* dstBuffer, size_t dstCapacity, + const void* srcBuffer, size_t srcSize, + const LZ4F_compressOptions_t* compressOptionsPtr) +{ + size_t const blockSize = cctxPtr->maxBlockSize; + const BYTE* srcPtr = (const BYTE*)srcBuffer; + const BYTE* const srcEnd = srcPtr + srcSize; + BYTE* const dstStart = (BYTE*)dstBuffer; + BYTE* dstPtr = dstStart; + LZ4F_lastBlockStatus lastBlockCompressed = notDone; + compressFunc_t const compress = LZ4F_selectCompression(cctxPtr->prefs.frameInfo.blockMode, cctxPtr->prefs.compressionLevel); + + DEBUGLOG(4, "LZ4F_compressUpdate (srcSize=%zu)", srcSize); + + if (cctxPtr->cStage != 1) return err0r(LZ4F_ERROR_GENERIC); /* state must be initialized and waiting for next block */ + if (dstCapacity < LZ4F_compressBound_internal(srcSize, &(cctxPtr->prefs), cctxPtr->tmpInSize)) + return err0r(LZ4F_ERROR_dstMaxSize_tooSmall); + if (compressOptionsPtr == NULL) compressOptionsPtr = &k_cOptionsNull; + + /* complete tmp buffer */ + if (cctxPtr->tmpInSize > 0) { /* some data already within tmp buffer */ + size_t const sizeToCopy = blockSize - cctxPtr->tmpInSize; + assert(blockSize > cctxPtr->tmpInSize); + if (sizeToCopy > srcSize) { + /* add src to tmpIn buffer */ + memcpy(cctxPtr->tmpIn + cctxPtr->tmpInSize, srcBuffer, srcSize); + srcPtr = srcEnd; + cctxPtr->tmpInSize += srcSize; + /* still needs some CRC */ + } else { + /* complete tmpIn block and then compress it */ + lastBlockCompressed = fromTmpBuffer; + memcpy(cctxPtr->tmpIn + cctxPtr->tmpInSize, srcBuffer, sizeToCopy); + srcPtr += sizeToCopy; + + dstPtr += LZ4F_makeBlock(dstPtr, + cctxPtr->tmpIn, blockSize, + compress, cctxPtr->lz4CtxPtr, cctxPtr->prefs.compressionLevel, + cctxPtr->cdict, + cctxPtr->prefs.frameInfo.blockChecksumFlag); + + if (cctxPtr->prefs.frameInfo.blockMode==LZ4F_blockLinked) cctxPtr->tmpIn += blockSize; + cctxPtr->tmpInSize = 0; + } } + + while ((size_t)(srcEnd - srcPtr) >= blockSize) { + /* compress full blocks */ + lastBlockCompressed = fromSrcBuffer; + dstPtr += LZ4F_makeBlock(dstPtr, + srcPtr, blockSize, + compress, cctxPtr->lz4CtxPtr, cctxPtr->prefs.compressionLevel, + cctxPtr->cdict, + cctxPtr->prefs.frameInfo.blockChecksumFlag); + srcPtr += blockSize; + } + + if ((cctxPtr->prefs.autoFlush) && (srcPtr < srcEnd)) { + /* autoFlush : remaining input (< blockSize) is compressed */ + lastBlockCompressed = fromSrcBuffer; + dstPtr += LZ4F_makeBlock(dstPtr, + srcPtr, (size_t)(srcEnd - srcPtr), + compress, cctxPtr->lz4CtxPtr, cctxPtr->prefs.compressionLevel, + cctxPtr->cdict, + cctxPtr->prefs.frameInfo.blockChecksumFlag); + srcPtr = srcEnd; + } + + /* preserve dictionary within @tmpBuff whenever necessary */ + if ((cctxPtr->prefs.frameInfo.blockMode==LZ4F_blockLinked) && (lastBlockCompressed==fromSrcBuffer)) { + if (compressOptionsPtr->stableSrc) { + cctxPtr->tmpIn = cctxPtr->tmpBuff; /* src is stable : dictionary remains in src across invocations */ + } else { + int const realDictSize = LZ4F_localSaveDict(cctxPtr); + if (realDictSize==0) return err0r(LZ4F_ERROR_GENERIC); + cctxPtr->tmpIn = cctxPtr->tmpBuff + realDictSize; + } + } + + /* keep tmpIn within limits */ + if (!(cctxPtr->prefs.autoFlush) /* no autoflush : there may be some data left within internal buffer */ + && (cctxPtr->tmpIn + blockSize) > (cctxPtr->tmpBuff + cctxPtr->maxBufferSize) ) /* not enough room to store next block */ + { + /* only preserve 64KB within internal buffer. Ensures there is enough room for next block. + * note: this situation necessarily implies lastBlockCompressed==fromTmpBuffer */ + int const realDictSize = LZ4F_localSaveDict(cctxPtr); + cctxPtr->tmpIn = cctxPtr->tmpBuff + realDictSize; + assert((cctxPtr->tmpIn + blockSize) <= (cctxPtr->tmpBuff + cctxPtr->maxBufferSize)); + } + + /* some input data left, necessarily < blockSize */ + if (srcPtr < srcEnd) { + /* fill tmp buffer */ + size_t const sizeToCopy = (size_t)(srcEnd - srcPtr); + memcpy(cctxPtr->tmpIn, srcPtr, sizeToCopy); + cctxPtr->tmpInSize = sizeToCopy; + } + + if (cctxPtr->prefs.frameInfo.contentChecksumFlag == LZ4F_contentChecksumEnabled) + (void)XXH32_update(&(cctxPtr->xxh), srcBuffer, srcSize); + + cctxPtr->totalInSize += srcSize; + return (size_t)(dstPtr - dstStart); +} + + +/*! LZ4F_flush() : + * When compressed data must be sent immediately, without waiting for a block to be filled, + * invoke LZ4_flush(), which will immediately compress any remaining data stored within LZ4F_cctx. + * The result of the function is the number of bytes written into dstBuffer. + * It can be zero, this means there was no data left within LZ4F_cctx. + * The function outputs an error code if it fails (can be tested using LZ4F_isError()) + * LZ4F_compressOptions_t* is optional. NULL is a valid argument. + */ +size_t LZ4F_flush(LZ4F_cctx* cctxPtr, + void* dstBuffer, size_t dstCapacity, + const LZ4F_compressOptions_t* compressOptionsPtr) +{ + BYTE* const dstStart = (BYTE*)dstBuffer; + BYTE* dstPtr = dstStart; + compressFunc_t compress; + + if (cctxPtr->tmpInSize == 0) return 0; /* nothing to flush */ + if (cctxPtr->cStage != 1) return err0r(LZ4F_ERROR_GENERIC); + if (dstCapacity < (cctxPtr->tmpInSize + BHSize + BFSize)) + return err0r(LZ4F_ERROR_dstMaxSize_tooSmall); + (void)compressOptionsPtr; /* not yet useful */ + + /* select compression function */ + compress = LZ4F_selectCompression(cctxPtr->prefs.frameInfo.blockMode, cctxPtr->prefs.compressionLevel); + + /* compress tmp buffer */ + dstPtr += LZ4F_makeBlock(dstPtr, + cctxPtr->tmpIn, cctxPtr->tmpInSize, + compress, cctxPtr->lz4CtxPtr, cctxPtr->prefs.compressionLevel, + cctxPtr->cdict, + cctxPtr->prefs.frameInfo.blockChecksumFlag); + assert(((void)"flush overflows dstBuffer!", (size_t)(dstPtr - dstStart) <= dstCapacity)); + + if (cctxPtr->prefs.frameInfo.blockMode == LZ4F_blockLinked) + cctxPtr->tmpIn += cctxPtr->tmpInSize; + cctxPtr->tmpInSize = 0; + + /* keep tmpIn within limits */ + if ((cctxPtr->tmpIn + cctxPtr->maxBlockSize) > (cctxPtr->tmpBuff + cctxPtr->maxBufferSize)) { /* necessarily LZ4F_blockLinked */ + int const realDictSize = LZ4F_localSaveDict(cctxPtr); + cctxPtr->tmpIn = cctxPtr->tmpBuff + realDictSize; + } + + return (size_t)(dstPtr - dstStart); +} + + +/*! LZ4F_compressEnd() : + * When you want to properly finish the compressed frame, just call LZ4F_compressEnd(). + * It will flush whatever data remained within compressionContext (like LZ4_flush()) + * but also properly finalize the frame, with an endMark and an (optional) checksum. + * LZ4F_compressOptions_t structure is optional : you can provide NULL as argument. + * @return: the number of bytes written into dstBuffer (necessarily >= 4 (endMark size)) + * or an error code if it fails (can be tested using LZ4F_isError()) + * The context can then be used again to compress a new frame, starting with LZ4F_compressBegin(). + */ +size_t LZ4F_compressEnd(LZ4F_cctx* cctxPtr, + void* dstBuffer, size_t dstCapacity, + const LZ4F_compressOptions_t* compressOptionsPtr) +{ + BYTE* const dstStart = (BYTE*)dstBuffer; + BYTE* dstPtr = dstStart; + + size_t const flushSize = LZ4F_flush(cctxPtr, dstBuffer, dstCapacity, compressOptionsPtr); + DEBUGLOG(5,"LZ4F_compressEnd: dstCapacity=%u", (unsigned)dstCapacity); + if (LZ4F_isError(flushSize)) return flushSize; + dstPtr += flushSize; + + assert(flushSize <= dstCapacity); + dstCapacity -= flushSize; + + if (dstCapacity < 4) return err0r(LZ4F_ERROR_dstMaxSize_tooSmall); + LZ4F_writeLE32(dstPtr, 0); + dstPtr += 4; /* endMark */ + + if (cctxPtr->prefs.frameInfo.contentChecksumFlag == LZ4F_contentChecksumEnabled) { + U32 const xxh = XXH32_digest(&(cctxPtr->xxh)); + if (dstCapacity < 8) return err0r(LZ4F_ERROR_dstMaxSize_tooSmall); + DEBUGLOG(5,"Writing 32-bit content checksum"); + LZ4F_writeLE32(dstPtr, xxh); + dstPtr+=4; /* content Checksum */ + } + + cctxPtr->cStage = 0; /* state is now re-usable (with identical preferences) */ + cctxPtr->maxBufferSize = 0; /* reuse HC context */ + + if (cctxPtr->prefs.frameInfo.contentSize) { + if (cctxPtr->prefs.frameInfo.contentSize != cctxPtr->totalInSize) + return err0r(LZ4F_ERROR_frameSize_wrong); + } + + return (size_t)(dstPtr - dstStart); +} + + +/*-*************************************************** +* Frame Decompression +*****************************************************/ + +typedef enum { + dstage_getFrameHeader=0, dstage_storeFrameHeader, + dstage_init, + dstage_getBlockHeader, dstage_storeBlockHeader, + dstage_copyDirect, dstage_getBlockChecksum, + dstage_getCBlock, dstage_storeCBlock, + dstage_flushOut, + dstage_getSuffix, dstage_storeSuffix, + dstage_getSFrameSize, dstage_storeSFrameSize, + dstage_skipSkippable +} dStage_t; + +struct LZ4F_dctx_s { + LZ4F_frameInfo_t frameInfo; + U32 version; + dStage_t dStage; + U64 frameRemainingSize; + size_t maxBlockSize; + size_t maxBufferSize; + BYTE* tmpIn; + size_t tmpInSize; + size_t tmpInTarget; + BYTE* tmpOutBuffer; + const BYTE* dict; + size_t dictSize; + BYTE* tmpOut; + size_t tmpOutSize; + size_t tmpOutStart; + XXH32_state_t xxh; + XXH32_state_t blockChecksum; + BYTE header[LZ4F_HEADER_SIZE_MAX]; +}; /* typedef'd to LZ4F_dctx in lz4frame.h */ + + +/*! LZ4F_createDecompressionContext() : + * Create a decompressionContext object, which will track all decompression operations. + * Provides a pointer to a fully allocated and initialized LZ4F_decompressionContext object. + * Object can later be released using LZ4F_freeDecompressionContext(). + * @return : if != 0, there was an error during context creation. + */ +LZ4F_errorCode_t LZ4F_createDecompressionContext(LZ4F_dctx** LZ4F_decompressionContextPtr, unsigned versionNumber) +{ + LZ4F_dctx* const dctx = (LZ4F_dctx*)ALLOC_AND_ZERO(sizeof(LZ4F_dctx)); + if (dctx == NULL) { /* failed allocation */ + *LZ4F_decompressionContextPtr = NULL; + return err0r(LZ4F_ERROR_allocation_failed); + } + + dctx->version = versionNumber; + *LZ4F_decompressionContextPtr = dctx; + return LZ4F_OK_NoError; +} + +LZ4F_errorCode_t LZ4F_freeDecompressionContext(LZ4F_dctx* dctx) +{ + LZ4F_errorCode_t result = LZ4F_OK_NoError; + if (dctx != NULL) { /* can accept NULL input, like free() */ + result = (LZ4F_errorCode_t)dctx->dStage; + FREEMEM(dctx->tmpIn); + FREEMEM(dctx->tmpOutBuffer); + FREEMEM(dctx); + } + return result; +} + + +/*==--- Streaming Decompression operations ---==*/ + +void LZ4F_resetDecompressionContext(LZ4F_dctx* dctx) +{ + dctx->dStage = dstage_getFrameHeader; + dctx->dict = NULL; + dctx->dictSize = 0; +} + + +/*! LZ4F_decodeHeader() : + * input : `src` points at the **beginning of the frame** + * output : set internal values of dctx, such as + * dctx->frameInfo and dctx->dStage. + * Also allocates internal buffers. + * @return : nb Bytes read from src (necessarily <= srcSize) + * or an error code (testable with LZ4F_isError()) + */ +static size_t LZ4F_decodeHeader(LZ4F_dctx* dctx, const void* src, size_t srcSize) +{ + unsigned blockMode, blockChecksumFlag, contentSizeFlag, contentChecksumFlag, dictIDFlag, blockSizeID; + size_t frameHeaderSize; + const BYTE* srcPtr = (const BYTE*)src; + + DEBUGLOG(5, "LZ4F_decodeHeader"); + /* need to decode header to get frameInfo */ + if (srcSize < minFHSize) return err0r(LZ4F_ERROR_frameHeader_incomplete); /* minimal frame header size */ + MEM_INIT(&(dctx->frameInfo), 0, sizeof(dctx->frameInfo)); + + /* special case : skippable frames */ + if ((LZ4F_readLE32(srcPtr) & 0xFFFFFFF0U) == LZ4F_MAGIC_SKIPPABLE_START) { + dctx->frameInfo.frameType = LZ4F_skippableFrame; + if (src == (void*)(dctx->header)) { + dctx->tmpInSize = srcSize; + dctx->tmpInTarget = 8; + dctx->dStage = dstage_storeSFrameSize; + return srcSize; + } else { + dctx->dStage = dstage_getSFrameSize; + return 4; + } + } + + /* control magic number */ +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + if (LZ4F_readLE32(srcPtr) != LZ4F_MAGICNUMBER) { + DEBUGLOG(4, "frame header error : unknown magic number"); + return err0r(LZ4F_ERROR_frameType_unknown); + } +#endif + dctx->frameInfo.frameType = LZ4F_frame; + + /* Flags */ + { U32 const FLG = srcPtr[4]; + U32 const version = (FLG>>6) & _2BITS; + blockChecksumFlag = (FLG>>4) & _1BIT; + blockMode = (FLG>>5) & _1BIT; + contentSizeFlag = (FLG>>3) & _1BIT; + contentChecksumFlag = (FLG>>2) & _1BIT; + dictIDFlag = FLG & _1BIT; + /* validate */ + if (((FLG>>1)&_1BIT) != 0) return err0r(LZ4F_ERROR_reservedFlag_set); /* Reserved bit */ + if (version != 1) return err0r(LZ4F_ERROR_headerVersion_wrong); /* Version Number, only supported value */ + } + + /* Frame Header Size */ + frameHeaderSize = minFHSize + (contentSizeFlag?8:0) + (dictIDFlag?4:0); + + if (srcSize < frameHeaderSize) { + /* not enough input to fully decode frame header */ + if (srcPtr != dctx->header) + memcpy(dctx->header, srcPtr, srcSize); + dctx->tmpInSize = srcSize; + dctx->tmpInTarget = frameHeaderSize; + dctx->dStage = dstage_storeFrameHeader; + return srcSize; + } + + { U32 const BD = srcPtr[5]; + blockSizeID = (BD>>4) & _3BITS; + /* validate */ + if (((BD>>7)&_1BIT) != 0) return err0r(LZ4F_ERROR_reservedFlag_set); /* Reserved bit */ + if (blockSizeID < 4) return err0r(LZ4F_ERROR_maxBlockSize_invalid); /* 4-7 only supported values for the time being */ + if (((BD>>0)&_4BITS) != 0) return err0r(LZ4F_ERROR_reservedFlag_set); /* Reserved bits */ + } + + /* check header */ + assert(frameHeaderSize > 5); +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + { BYTE const HC = LZ4F_headerChecksum(srcPtr+4, frameHeaderSize-5); + if (HC != srcPtr[frameHeaderSize-1]) + return err0r(LZ4F_ERROR_headerChecksum_invalid); + } +#endif + + /* save */ + dctx->frameInfo.blockMode = (LZ4F_blockMode_t)blockMode; + dctx->frameInfo.blockChecksumFlag = (LZ4F_blockChecksum_t)blockChecksumFlag; + dctx->frameInfo.contentChecksumFlag = (LZ4F_contentChecksum_t)contentChecksumFlag; + dctx->frameInfo.blockSizeID = (LZ4F_blockSizeID_t)blockSizeID; + dctx->maxBlockSize = LZ4F_getBlockSize(blockSizeID); + if (contentSizeFlag) + dctx->frameRemainingSize = + dctx->frameInfo.contentSize = LZ4F_readLE64(srcPtr+6); + if (dictIDFlag) + dctx->frameInfo.dictID = LZ4F_readLE32(srcPtr + frameHeaderSize - 5); + + dctx->dStage = dstage_init; + + return frameHeaderSize; +} + + +/*! LZ4F_headerSize() : + * @return : size of frame header + * or an error code, which can be tested using LZ4F_isError() + */ +size_t LZ4F_headerSize(const void* src, size_t srcSize) +{ + if (src == NULL) return err0r(LZ4F_ERROR_srcPtr_wrong); + + /* minimal srcSize to determine header size */ + if (srcSize < LZ4F_MIN_SIZE_TO_KNOW_HEADER_LENGTH) + return err0r(LZ4F_ERROR_frameHeader_incomplete); + + /* special case : skippable frames */ + if ((LZ4F_readLE32(src) & 0xFFFFFFF0U) == LZ4F_MAGIC_SKIPPABLE_START) + return 8; + + /* control magic number */ +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + if (LZ4F_readLE32(src) != LZ4F_MAGICNUMBER) + return err0r(LZ4F_ERROR_frameType_unknown); +#endif + + /* Frame Header Size */ + { BYTE const FLG = ((const BYTE*)src)[4]; + U32 const contentSizeFlag = (FLG>>3) & _1BIT; + U32 const dictIDFlag = FLG & _1BIT; + return minFHSize + (contentSizeFlag?8:0) + (dictIDFlag?4:0); + } +} + +/*! LZ4F_getFrameInfo() : + * This function extracts frame parameters (max blockSize, frame checksum, etc.). + * Usage is optional. Objective is to provide relevant information for allocation purposes. + * This function works in 2 situations : + * - At the beginning of a new frame, in which case it will decode this information from `srcBuffer`, and start the decoding process. + * Amount of input data provided must be large enough to successfully decode the frame header. + * A header size is variable, but is guaranteed to be <= LZ4F_HEADER_SIZE_MAX bytes. It's possible to provide more input data than this minimum. + * - After decoding has been started. In which case, no input is read, frame parameters are extracted from dctx. + * The number of bytes consumed from srcBuffer will be updated within *srcSizePtr (necessarily <= original value). + * Decompression must resume from (srcBuffer + *srcSizePtr). + * @return : an hint about how many srcSize bytes LZ4F_decompress() expects for next call, + * or an error code which can be tested using LZ4F_isError() + * note 1 : in case of error, dctx is not modified. Decoding operations can resume from where they stopped. + * note 2 : frame parameters are *copied into* an already allocated LZ4F_frameInfo_t structure. + */ +LZ4F_errorCode_t LZ4F_getFrameInfo(LZ4F_dctx* dctx, + LZ4F_frameInfo_t* frameInfoPtr, + const void* srcBuffer, size_t* srcSizePtr) +{ + LZ4F_STATIC_ASSERT(dstage_getFrameHeader < dstage_storeFrameHeader); + if (dctx->dStage > dstage_storeFrameHeader) { + /* frameInfo already decoded */ + size_t o=0, i=0; + *srcSizePtr = 0; + *frameInfoPtr = dctx->frameInfo; + /* returns : recommended nb of bytes for LZ4F_decompress() */ + return LZ4F_decompress(dctx, NULL, &o, NULL, &i, NULL); + } else { + if (dctx->dStage == dstage_storeFrameHeader) { + /* frame decoding already started, in the middle of header => automatic fail */ + *srcSizePtr = 0; + return err0r(LZ4F_ERROR_frameDecoding_alreadyStarted); + } else { + size_t const hSize = LZ4F_headerSize(srcBuffer, *srcSizePtr); + if (LZ4F_isError(hSize)) { *srcSizePtr=0; return hSize; } + if (*srcSizePtr < hSize) { + *srcSizePtr=0; + return err0r(LZ4F_ERROR_frameHeader_incomplete); + } + + { size_t decodeResult = LZ4F_decodeHeader(dctx, srcBuffer, hSize); + if (LZ4F_isError(decodeResult)) { + *srcSizePtr = 0; + } else { + *srcSizePtr = decodeResult; + decodeResult = BHSize; /* block header size */ + } + *frameInfoPtr = dctx->frameInfo; + return decodeResult; + } } } +} + + +/* LZ4F_updateDict() : + * only used for LZ4F_blockLinked mode + * Condition : dstPtr != NULL + */ +static void LZ4F_updateDict(LZ4F_dctx* dctx, + const BYTE* dstPtr, size_t dstSize, const BYTE* dstBufferStart, + unsigned withinTmp) +{ + assert(dstPtr != NULL); + if (dctx->dictSize==0) { + dctx->dict = (const BYTE*)dstPtr; /* priority to prefix mode */ + } + assert(dctx->dict != NULL); + + if (dctx->dict + dctx->dictSize == dstPtr) { /* prefix mode, everything within dstBuffer */ + dctx->dictSize += dstSize; + return; + } + + assert(dstPtr >= dstBufferStart); + if ((size_t)(dstPtr - dstBufferStart) + dstSize >= 64 KB) { /* history in dstBuffer becomes large enough to become dictionary */ + dctx->dict = (const BYTE*)dstBufferStart; + dctx->dictSize = (size_t)(dstPtr - dstBufferStart) + dstSize; + return; + } + + assert(dstSize < 64 KB); /* if dstSize >= 64 KB, dictionary would be set into dstBuffer directly */ + + /* dstBuffer does not contain whole useful history (64 KB), so it must be saved within tmpOutBuffer */ + assert(dctx->tmpOutBuffer != NULL); + + if (withinTmp && (dctx->dict == dctx->tmpOutBuffer)) { /* continue history within tmpOutBuffer */ + /* withinTmp expectation : content of [dstPtr,dstSize] is same as [dict+dictSize,dstSize], so we just extend it */ + assert(dctx->dict + dctx->dictSize == dctx->tmpOut + dctx->tmpOutStart); + dctx->dictSize += dstSize; + return; + } + + if (withinTmp) { /* copy relevant dict portion in front of tmpOut within tmpOutBuffer */ + size_t const preserveSize = (size_t)(dctx->tmpOut - dctx->tmpOutBuffer); + size_t copySize = 64 KB - dctx->tmpOutSize; + const BYTE* const oldDictEnd = dctx->dict + dctx->dictSize - dctx->tmpOutStart; + if (dctx->tmpOutSize > 64 KB) copySize = 0; + if (copySize > preserveSize) copySize = preserveSize; + + memcpy(dctx->tmpOutBuffer + preserveSize - copySize, oldDictEnd - copySize, copySize); + + dctx->dict = dctx->tmpOutBuffer; + dctx->dictSize = preserveSize + dctx->tmpOutStart + dstSize; + return; + } + + if (dctx->dict == dctx->tmpOutBuffer) { /* copy dst into tmp to complete dict */ + if (dctx->dictSize + dstSize > dctx->maxBufferSize) { /* tmp buffer not large enough */ + size_t const preserveSize = 64 KB - dstSize; + memcpy(dctx->tmpOutBuffer, dctx->dict + dctx->dictSize - preserveSize, preserveSize); + dctx->dictSize = preserveSize; + } + memcpy(dctx->tmpOutBuffer + dctx->dictSize, dstPtr, dstSize); + dctx->dictSize += dstSize; + return; + } + + /* join dict & dest into tmp */ + { size_t preserveSize = 64 KB - dstSize; + if (preserveSize > dctx->dictSize) preserveSize = dctx->dictSize; + memcpy(dctx->tmpOutBuffer, dctx->dict + dctx->dictSize - preserveSize, preserveSize); + memcpy(dctx->tmpOutBuffer + preserveSize, dstPtr, dstSize); + dctx->dict = dctx->tmpOutBuffer; + dctx->dictSize = preserveSize + dstSize; + } +} + + + +/*! LZ4F_decompress() : + * Call this function repetitively to regenerate compressed data in srcBuffer. + * The function will attempt to decode up to *srcSizePtr bytes from srcBuffer + * into dstBuffer of capacity *dstSizePtr. + * + * The number of bytes regenerated into dstBuffer will be provided within *dstSizePtr (necessarily <= original value). + * + * The number of bytes effectively read from srcBuffer will be provided within *srcSizePtr (necessarily <= original value). + * If number of bytes read is < number of bytes provided, then decompression operation is not complete. + * Remaining data will have to be presented again in a subsequent invocation. + * + * The function result is an hint of the better srcSize to use for next call to LZ4F_decompress. + * Schematically, it's the size of the current (or remaining) compressed block + header of next block. + * Respecting the hint provides a small boost to performance, since it allows less buffer shuffling. + * Note that this is just a hint, and it's always possible to any srcSize value. + * When a frame is fully decoded, @return will be 0. + * If decompression failed, @return is an error code which can be tested using LZ4F_isError(). + */ +size_t LZ4F_decompress(LZ4F_dctx* dctx, + void* dstBuffer, size_t* dstSizePtr, + const void* srcBuffer, size_t* srcSizePtr, + const LZ4F_decompressOptions_t* decompressOptionsPtr) +{ + LZ4F_decompressOptions_t optionsNull; + const BYTE* const srcStart = (const BYTE*)srcBuffer; + const BYTE* const srcEnd = srcStart + *srcSizePtr; + const BYTE* srcPtr = srcStart; + BYTE* const dstStart = (BYTE*)dstBuffer; + BYTE* const dstEnd = dstStart ? dstStart + *dstSizePtr : NULL; + BYTE* dstPtr = dstStart; + const BYTE* selectedIn = NULL; + unsigned doAnotherStage = 1; + size_t nextSrcSizeHint = 1; + + + DEBUGLOG(5, "LZ4F_decompress : %p,%u => %p,%u", + srcBuffer, (unsigned)*srcSizePtr, dstBuffer, (unsigned)*dstSizePtr); + if (dstBuffer == NULL) assert(*dstSizePtr == 0); + MEM_INIT(&optionsNull, 0, sizeof(optionsNull)); + if (decompressOptionsPtr==NULL) decompressOptionsPtr = &optionsNull; + *srcSizePtr = 0; + *dstSizePtr = 0; + assert(dctx != NULL); + + /* behaves as a state machine */ + + while (doAnotherStage) { + + switch(dctx->dStage) + { + + case dstage_getFrameHeader: + DEBUGLOG(6, "dstage_getFrameHeader"); + if ((size_t)(srcEnd-srcPtr) >= maxFHSize) { /* enough to decode - shortcut */ + size_t const hSize = LZ4F_decodeHeader(dctx, srcPtr, (size_t)(srcEnd-srcPtr)); /* will update dStage appropriately */ + if (LZ4F_isError(hSize)) return hSize; + srcPtr += hSize; + break; + } + dctx->tmpInSize = 0; + if (srcEnd-srcPtr == 0) return minFHSize; /* 0-size input */ + dctx->tmpInTarget = minFHSize; /* minimum size to decode header */ + dctx->dStage = dstage_storeFrameHeader; + /* fall-through */ + + case dstage_storeFrameHeader: + DEBUGLOG(6, "dstage_storeFrameHeader"); + { size_t const sizeToCopy = MIN(dctx->tmpInTarget - dctx->tmpInSize, (size_t)(srcEnd - srcPtr)); + memcpy(dctx->header + dctx->tmpInSize, srcPtr, sizeToCopy); + dctx->tmpInSize += sizeToCopy; + srcPtr += sizeToCopy; + } + if (dctx->tmpInSize < dctx->tmpInTarget) { + nextSrcSizeHint = (dctx->tmpInTarget - dctx->tmpInSize) + BHSize; /* rest of header + nextBlockHeader */ + doAnotherStage = 0; /* not enough src data, ask for some more */ + break; + } + { size_t const hSize = LZ4F_decodeHeader(dctx, dctx->header, dctx->tmpInTarget); /* will update dStage appropriately */ + if (LZ4F_isError(hSize)) return hSize; + } + break; + + case dstage_init: + DEBUGLOG(6, "dstage_init"); + if (dctx->frameInfo.contentChecksumFlag) (void)XXH32_reset(&(dctx->xxh), 0); + /* internal buffers allocation */ + { size_t const bufferNeeded = dctx->maxBlockSize + + ((dctx->frameInfo.blockMode==LZ4F_blockLinked) ? 128 KB : 0); + if (bufferNeeded > dctx->maxBufferSize) { /* tmp buffers too small */ + dctx->maxBufferSize = 0; /* ensure allocation will be re-attempted on next entry*/ + FREEMEM(dctx->tmpIn); + dctx->tmpIn = (BYTE*)ALLOC(dctx->maxBlockSize + BFSize /* block checksum */); + if (dctx->tmpIn == NULL) + return err0r(LZ4F_ERROR_allocation_failed); + FREEMEM(dctx->tmpOutBuffer); + dctx->tmpOutBuffer= (BYTE*)ALLOC(bufferNeeded); + if (dctx->tmpOutBuffer== NULL) + return err0r(LZ4F_ERROR_allocation_failed); + dctx->maxBufferSize = bufferNeeded; + } } + dctx->tmpInSize = 0; + dctx->tmpInTarget = 0; + dctx->tmpOut = dctx->tmpOutBuffer; + dctx->tmpOutStart = 0; + dctx->tmpOutSize = 0; + + dctx->dStage = dstage_getBlockHeader; + /* fall-through */ + + case dstage_getBlockHeader: + if ((size_t)(srcEnd - srcPtr) >= BHSize) { + selectedIn = srcPtr; + srcPtr += BHSize; + } else { + /* not enough input to read cBlockSize field */ + dctx->tmpInSize = 0; + dctx->dStage = dstage_storeBlockHeader; + } + + if (dctx->dStage == dstage_storeBlockHeader) /* can be skipped */ + case dstage_storeBlockHeader: + { size_t const remainingInput = (size_t)(srcEnd - srcPtr); + size_t const wantedData = BHSize - dctx->tmpInSize; + size_t const sizeToCopy = MIN(wantedData, remainingInput); + memcpy(dctx->tmpIn + dctx->tmpInSize, srcPtr, sizeToCopy); + srcPtr += sizeToCopy; + dctx->tmpInSize += sizeToCopy; + + if (dctx->tmpInSize < BHSize) { /* not enough input for cBlockSize */ + nextSrcSizeHint = BHSize - dctx->tmpInSize; + doAnotherStage = 0; + break; + } + selectedIn = dctx->tmpIn; + } /* if (dctx->dStage == dstage_storeBlockHeader) */ + + /* decode block header */ + { U32 const blockHeader = LZ4F_readLE32(selectedIn); + size_t const nextCBlockSize = blockHeader & 0x7FFFFFFFU; + size_t const crcSize = dctx->frameInfo.blockChecksumFlag * BFSize; + if (blockHeader==0) { /* frameEnd signal, no more block */ + DEBUGLOG(5, "end of frame"); + dctx->dStage = dstage_getSuffix; + break; + } + if (nextCBlockSize > dctx->maxBlockSize) { + return err0r(LZ4F_ERROR_maxBlockSize_invalid); + } + if (blockHeader & LZ4F_BLOCKUNCOMPRESSED_FLAG) { + /* next block is uncompressed */ + dctx->tmpInTarget = nextCBlockSize; + DEBUGLOG(5, "next block is uncompressed (size %u)", (U32)nextCBlockSize); + if (dctx->frameInfo.blockChecksumFlag) { + (void)XXH32_reset(&dctx->blockChecksum, 0); + } + dctx->dStage = dstage_copyDirect; + break; + } + /* next block is a compressed block */ + dctx->tmpInTarget = nextCBlockSize + crcSize; + dctx->dStage = dstage_getCBlock; + if (dstPtr==dstEnd || srcPtr==srcEnd) { + nextSrcSizeHint = BHSize + nextCBlockSize + crcSize; + doAnotherStage = 0; + } + break; + } + + case dstage_copyDirect: /* uncompressed block */ + DEBUGLOG(6, "dstage_copyDirect"); + { size_t sizeToCopy; + if (dstPtr == NULL) { + sizeToCopy = 0; + } else { + size_t const minBuffSize = MIN((size_t)(srcEnd-srcPtr), (size_t)(dstEnd-dstPtr)); + sizeToCopy = MIN(dctx->tmpInTarget, minBuffSize); + memcpy(dstPtr, srcPtr, sizeToCopy); + if (dctx->frameInfo.blockChecksumFlag) { + (void)XXH32_update(&dctx->blockChecksum, srcPtr, sizeToCopy); + } + if (dctx->frameInfo.contentChecksumFlag) + (void)XXH32_update(&dctx->xxh, srcPtr, sizeToCopy); + if (dctx->frameInfo.contentSize) + dctx->frameRemainingSize -= sizeToCopy; + + /* history management (linked blocks only)*/ + if (dctx->frameInfo.blockMode == LZ4F_blockLinked) { + LZ4F_updateDict(dctx, dstPtr, sizeToCopy, dstStart, 0); + } } + + srcPtr += sizeToCopy; + dstPtr += sizeToCopy; + if (sizeToCopy == dctx->tmpInTarget) { /* all done */ + if (dctx->frameInfo.blockChecksumFlag) { + dctx->tmpInSize = 0; + dctx->dStage = dstage_getBlockChecksum; + } else + dctx->dStage = dstage_getBlockHeader; /* new block */ + break; + } + dctx->tmpInTarget -= sizeToCopy; /* need to copy more */ + } + nextSrcSizeHint = dctx->tmpInTarget + + +(dctx->frameInfo.blockChecksumFlag ? BFSize : 0) + + BHSize /* next header size */; + doAnotherStage = 0; + break; + + /* check block checksum for recently transferred uncompressed block */ + case dstage_getBlockChecksum: + DEBUGLOG(6, "dstage_getBlockChecksum"); + { const void* crcSrc; + if ((srcEnd-srcPtr >= 4) && (dctx->tmpInSize==0)) { + crcSrc = srcPtr; + srcPtr += 4; + } else { + size_t const stillToCopy = 4 - dctx->tmpInSize; + size_t const sizeToCopy = MIN(stillToCopy, (size_t)(srcEnd-srcPtr)); + memcpy(dctx->header + dctx->tmpInSize, srcPtr, sizeToCopy); + dctx->tmpInSize += sizeToCopy; + srcPtr += sizeToCopy; + if (dctx->tmpInSize < 4) { /* all input consumed */ + doAnotherStage = 0; + break; + } + crcSrc = dctx->header; + } + { U32 const readCRC = LZ4F_readLE32(crcSrc); + U32 const calcCRC = XXH32_digest(&dctx->blockChecksum); +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + DEBUGLOG(6, "compare block checksum"); + if (readCRC != calcCRC) { + DEBUGLOG(4, "incorrect block checksum: %08X != %08X", + readCRC, calcCRC); + return err0r(LZ4F_ERROR_blockChecksum_invalid); + } +#else + (void)readCRC; + (void)calcCRC; +#endif + } } + dctx->dStage = dstage_getBlockHeader; /* new block */ + break; + + case dstage_getCBlock: + DEBUGLOG(6, "dstage_getCBlock"); + if ((size_t)(srcEnd-srcPtr) < dctx->tmpInTarget) { + dctx->tmpInSize = 0; + dctx->dStage = dstage_storeCBlock; + break; + } + /* input large enough to read full block directly */ + selectedIn = srcPtr; + srcPtr += dctx->tmpInTarget; + + if (0) /* always jump over next block */ + case dstage_storeCBlock: + { size_t const wantedData = dctx->tmpInTarget - dctx->tmpInSize; + size_t const inputLeft = (size_t)(srcEnd-srcPtr); + size_t const sizeToCopy = MIN(wantedData, inputLeft); + memcpy(dctx->tmpIn + dctx->tmpInSize, srcPtr, sizeToCopy); + dctx->tmpInSize += sizeToCopy; + srcPtr += sizeToCopy; + if (dctx->tmpInSize < dctx->tmpInTarget) { /* need more input */ + nextSrcSizeHint = (dctx->tmpInTarget - dctx->tmpInSize) + + (dctx->frameInfo.blockChecksumFlag ? BFSize : 0) + + BHSize /* next header size */; + doAnotherStage = 0; + break; + } + selectedIn = dctx->tmpIn; + } + + /* At this stage, input is large enough to decode a block */ + if (dctx->frameInfo.blockChecksumFlag) { + dctx->tmpInTarget -= 4; + assert(selectedIn != NULL); /* selectedIn is defined at this stage (either srcPtr, or dctx->tmpIn) */ + { U32 const readBlockCrc = LZ4F_readLE32(selectedIn + dctx->tmpInTarget); + U32 const calcBlockCrc = XXH32(selectedIn, dctx->tmpInTarget, 0); +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + if (readBlockCrc != calcBlockCrc) + return err0r(LZ4F_ERROR_blockChecksum_invalid); +#else + (void)readBlockCrc; + (void)calcBlockCrc; +#endif + } } + + if ((size_t)(dstEnd-dstPtr) >= dctx->maxBlockSize) { + const char* dict = (const char*)dctx->dict; + size_t dictSize = dctx->dictSize; + int decodedSize; + assert(dstPtr != NULL); + if (dict && dictSize > 1 GB) { + /* the dictSize param is an int, avoid truncation / sign issues */ + dict += dictSize - 64 KB; + dictSize = 64 KB; + } + /* enough capacity in `dst` to decompress directly there */ + decodedSize = LZ4_decompress_safe_usingDict( + (const char*)selectedIn, (char*)dstPtr, + (int)dctx->tmpInTarget, (int)dctx->maxBlockSize, + dict, (int)dictSize); + if (decodedSize < 0) return err0r(LZ4F_ERROR_GENERIC); /* decompression failed */ + if (dctx->frameInfo.contentChecksumFlag) + XXH32_update(&(dctx->xxh), dstPtr, (size_t)decodedSize); + if (dctx->frameInfo.contentSize) + dctx->frameRemainingSize -= (size_t)decodedSize; + + /* dictionary management */ + if (dctx->frameInfo.blockMode==LZ4F_blockLinked) { + LZ4F_updateDict(dctx, dstPtr, (size_t)decodedSize, dstStart, 0); + } + + dstPtr += decodedSize; + dctx->dStage = dstage_getBlockHeader; + break; + } + + /* not enough place into dst : decode into tmpOut */ + /* ensure enough place for tmpOut */ + if (dctx->frameInfo.blockMode == LZ4F_blockLinked) { + if (dctx->dict == dctx->tmpOutBuffer) { + if (dctx->dictSize > 128 KB) { + memcpy(dctx->tmpOutBuffer, dctx->dict + dctx->dictSize - 64 KB, 64 KB); + dctx->dictSize = 64 KB; + } + dctx->tmpOut = dctx->tmpOutBuffer + dctx->dictSize; + } else { /* dict not within tmp */ + size_t const reservedDictSpace = MIN(dctx->dictSize, 64 KB); + dctx->tmpOut = dctx->tmpOutBuffer + reservedDictSpace; + } } + + /* Decode block */ + { const char* dict = (const char*)dctx->dict; + size_t dictSize = dctx->dictSize; + int decodedSize; + if (dict && dictSize > 1 GB) { + /* the dictSize param is an int, avoid truncation / sign issues */ + dict += dictSize - 64 KB; + dictSize = 64 KB; + } + decodedSize = LZ4_decompress_safe_usingDict( + (const char*)selectedIn, (char*)dctx->tmpOut, + (int)dctx->tmpInTarget, (int)dctx->maxBlockSize, + dict, (int)dictSize); + if (decodedSize < 0) /* decompression failed */ + return err0r(LZ4F_ERROR_decompressionFailed); + if (dctx->frameInfo.contentChecksumFlag) + XXH32_update(&(dctx->xxh), dctx->tmpOut, (size_t)decodedSize); + if (dctx->frameInfo.contentSize) + dctx->frameRemainingSize -= (size_t)decodedSize; + dctx->tmpOutSize = (size_t)decodedSize; + dctx->tmpOutStart = 0; + dctx->dStage = dstage_flushOut; + } + /* fall-through */ + + case dstage_flushOut: /* flush decoded data from tmpOut to dstBuffer */ + DEBUGLOG(6, "dstage_flushOut"); + if (dstPtr != NULL) { + size_t const sizeToCopy = MIN(dctx->tmpOutSize - dctx->tmpOutStart, (size_t)(dstEnd-dstPtr)); + memcpy(dstPtr, dctx->tmpOut + dctx->tmpOutStart, sizeToCopy); + + /* dictionary management */ + if (dctx->frameInfo.blockMode == LZ4F_blockLinked) + LZ4F_updateDict(dctx, dstPtr, sizeToCopy, dstStart, 1 /*withinTmp*/); + + dctx->tmpOutStart += sizeToCopy; + dstPtr += sizeToCopy; + } + if (dctx->tmpOutStart == dctx->tmpOutSize) { /* all flushed */ + dctx->dStage = dstage_getBlockHeader; /* get next block */ + break; + } + /* could not flush everything : stop there, just request a block header */ + doAnotherStage = 0; + nextSrcSizeHint = BHSize; + break; + + case dstage_getSuffix: + if (dctx->frameRemainingSize) + return err0r(LZ4F_ERROR_frameSize_wrong); /* incorrect frame size decoded */ + if (!dctx->frameInfo.contentChecksumFlag) { /* no checksum, frame is completed */ + nextSrcSizeHint = 0; + LZ4F_resetDecompressionContext(dctx); + doAnotherStage = 0; + break; + } + if ((srcEnd - srcPtr) < 4) { /* not enough size for entire CRC */ + dctx->tmpInSize = 0; + dctx->dStage = dstage_storeSuffix; + } else { + selectedIn = srcPtr; + srcPtr += 4; + } + + if (dctx->dStage == dstage_storeSuffix) /* can be skipped */ + case dstage_storeSuffix: + { size_t const remainingInput = (size_t)(srcEnd - srcPtr); + size_t const wantedData = 4 - dctx->tmpInSize; + size_t const sizeToCopy = MIN(wantedData, remainingInput); + memcpy(dctx->tmpIn + dctx->tmpInSize, srcPtr, sizeToCopy); + srcPtr += sizeToCopy; + dctx->tmpInSize += sizeToCopy; + if (dctx->tmpInSize < 4) { /* not enough input to read complete suffix */ + nextSrcSizeHint = 4 - dctx->tmpInSize; + doAnotherStage=0; + break; + } + selectedIn = dctx->tmpIn; + } /* if (dctx->dStage == dstage_storeSuffix) */ + + /* case dstage_checkSuffix: */ /* no direct entry, avoid initialization risks */ + { U32 const readCRC = LZ4F_readLE32(selectedIn); + U32 const resultCRC = XXH32_digest(&(dctx->xxh)); +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + if (readCRC != resultCRC) + return err0r(LZ4F_ERROR_contentChecksum_invalid); +#else + (void)readCRC; + (void)resultCRC; +#endif + nextSrcSizeHint = 0; + LZ4F_resetDecompressionContext(dctx); + doAnotherStage = 0; + break; + } + + case dstage_getSFrameSize: + if ((srcEnd - srcPtr) >= 4) { + selectedIn = srcPtr; + srcPtr += 4; + } else { + /* not enough input to read cBlockSize field */ + dctx->tmpInSize = 4; + dctx->tmpInTarget = 8; + dctx->dStage = dstage_storeSFrameSize; + } + + if (dctx->dStage == dstage_storeSFrameSize) + case dstage_storeSFrameSize: + { size_t const sizeToCopy = MIN(dctx->tmpInTarget - dctx->tmpInSize, + (size_t)(srcEnd - srcPtr) ); + memcpy(dctx->header + dctx->tmpInSize, srcPtr, sizeToCopy); + srcPtr += sizeToCopy; + dctx->tmpInSize += sizeToCopy; + if (dctx->tmpInSize < dctx->tmpInTarget) { + /* not enough input to get full sBlockSize; wait for more */ + nextSrcSizeHint = dctx->tmpInTarget - dctx->tmpInSize; + doAnotherStage = 0; + break; + } + selectedIn = dctx->header + 4; + } /* if (dctx->dStage == dstage_storeSFrameSize) */ + + /* case dstage_decodeSFrameSize: */ /* no direct entry */ + { size_t const SFrameSize = LZ4F_readLE32(selectedIn); + dctx->frameInfo.contentSize = SFrameSize; + dctx->tmpInTarget = SFrameSize; + dctx->dStage = dstage_skipSkippable; + break; + } + + case dstage_skipSkippable: + { size_t const skipSize = MIN(dctx->tmpInTarget, (size_t)(srcEnd-srcPtr)); + srcPtr += skipSize; + dctx->tmpInTarget -= skipSize; + doAnotherStage = 0; + nextSrcSizeHint = dctx->tmpInTarget; + if (nextSrcSizeHint) break; /* still more to skip */ + /* frame fully skipped : prepare context for a new frame */ + LZ4F_resetDecompressionContext(dctx); + break; + } + } /* switch (dctx->dStage) */ + } /* while (doAnotherStage) */ + + /* preserve history within tmp whenever necessary */ + LZ4F_STATIC_ASSERT((unsigned)dstage_init == 2); + if ( (dctx->frameInfo.blockMode==LZ4F_blockLinked) /* next block will use up to 64KB from previous ones */ + && (dctx->dict != dctx->tmpOutBuffer) /* dictionary is not already within tmp */ + && (dctx->dict != NULL) /* dictionary exists */ + && (!decompressOptionsPtr->stableDst) /* cannot rely on dst data to remain there for next call */ + && ((unsigned)(dctx->dStage)-2 < (unsigned)(dstage_getSuffix)-2) ) /* valid stages : [init ... getSuffix[ */ + { + if (dctx->dStage == dstage_flushOut) { + size_t const preserveSize = (size_t)(dctx->tmpOut - dctx->tmpOutBuffer); + size_t copySize = 64 KB - dctx->tmpOutSize; + const BYTE* oldDictEnd = dctx->dict + dctx->dictSize - dctx->tmpOutStart; + if (dctx->tmpOutSize > 64 KB) copySize = 0; + if (copySize > preserveSize) copySize = preserveSize; + assert(dctx->tmpOutBuffer != NULL); + + memcpy(dctx->tmpOutBuffer + preserveSize - copySize, oldDictEnd - copySize, copySize); + + dctx->dict = dctx->tmpOutBuffer; + dctx->dictSize = preserveSize + dctx->tmpOutStart; + } else { + const BYTE* const oldDictEnd = dctx->dict + dctx->dictSize; + size_t const newDictSize = MIN(dctx->dictSize, 64 KB); + + memcpy(dctx->tmpOutBuffer, oldDictEnd - newDictSize, newDictSize); + + dctx->dict = dctx->tmpOutBuffer; + dctx->dictSize = newDictSize; + dctx->tmpOut = dctx->tmpOutBuffer + newDictSize; + } + } + + *srcSizePtr = (size_t)(srcPtr - srcStart); + *dstSizePtr = (size_t)(dstPtr - dstStart); + return nextSrcSizeHint; +} + +/*! LZ4F_decompress_usingDict() : + * Same as LZ4F_decompress(), using a predefined dictionary. + * Dictionary is used "in place", without any preprocessing. + * It must remain accessible throughout the entire frame decoding. + */ +size_t LZ4F_decompress_usingDict(LZ4F_dctx* dctx, + void* dstBuffer, size_t* dstSizePtr, + const void* srcBuffer, size_t* srcSizePtr, + const void* dict, size_t dictSize, + const LZ4F_decompressOptions_t* decompressOptionsPtr) +{ + if (dctx->dStage <= dstage_init) { + dctx->dict = (const BYTE*)dict; + dctx->dictSize = dictSize; + } + return LZ4F_decompress(dctx, dstBuffer, dstSizePtr, + srcBuffer, srcSizePtr, + decompressOptionsPtr); +} diff --git a/libCompression/lz4frame.h b/libCompression/lz4frame.h new file mode 100644 index 0000000..9ac849f --- /dev/null +++ b/libCompression/lz4frame.h @@ -0,0 +1,623 @@ +/* + LZ4 auto-framing library + Header File + Copyright (C) 2011-2020, Yann Collet. + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 source repository : https://github.com/lz4/lz4 + - LZ4 public forum : https://groups.google.com/forum/#!forum/lz4c +*/ + +/* LZ4F is a stand-alone API able to create and decode LZ4 frames + * conformant with specification v1.6.1 in doc/lz4_Frame_format.md . + * Generated frames are compatible with `lz4` CLI. + * + * LZ4F also offers streaming capabilities. + * + * lz4.h is not required when using lz4frame.h, + * except to extract common constant such as LZ4_VERSION_NUMBER. + * */ + +#ifndef LZ4F_H_09782039843 +#define LZ4F_H_09782039843 + +#if defined (__cplusplus) +extern "C" { +#endif + +/* --- Dependency --- */ +#include /* size_t */ + + +/** + Introduction + + lz4frame.h implements LZ4 frame specification (doc/lz4_Frame_format.md). + lz4frame.h provides frame compression functions that take care + of encoding standard metadata alongside LZ4-compressed blocks. +*/ + +/*-*************************************************************** + * Compiler specifics + *****************************************************************/ +/* LZ4_DLL_EXPORT : + * Enable exporting of functions when building a Windows DLL + * LZ4FLIB_VISIBILITY : + * Control library symbols visibility. + */ +#ifndef LZ4FLIB_VISIBILITY +# if defined(__GNUC__) && (__GNUC__ >= 4) +# define LZ4FLIB_VISIBILITY __attribute__ ((visibility ("default"))) +# else +# define LZ4FLIB_VISIBILITY +# endif +#endif +#if defined(LZ4_DLL_EXPORT) && (LZ4_DLL_EXPORT==1) +# define LZ4FLIB_API __declspec(dllexport) LZ4FLIB_VISIBILITY +#elif defined(LZ4_DLL_IMPORT) && (LZ4_DLL_IMPORT==1) +# define LZ4FLIB_API __declspec(dllimport) LZ4FLIB_VISIBILITY +#else +# define LZ4FLIB_API LZ4FLIB_VISIBILITY +#endif + +#ifdef LZ4F_DISABLE_DEPRECATE_WARNINGS +# define LZ4F_DEPRECATE(x) x +#else +# if defined(_MSC_VER) +# define LZ4F_DEPRECATE(x) x /* __declspec(deprecated) x - only works with C++ */ +# elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ >= 6)) +# define LZ4F_DEPRECATE(x) x __attribute__((deprecated)) +# else +# define LZ4F_DEPRECATE(x) x /* no deprecation warning for this compiler */ +# endif +#endif + + +/*-************************************ + * Error management + **************************************/ +typedef size_t LZ4F_errorCode_t; + +LZ4FLIB_API unsigned LZ4F_isError(LZ4F_errorCode_t code); /**< tells when a function result is an error code */ +LZ4FLIB_API const char* LZ4F_getErrorName(LZ4F_errorCode_t code); /**< return error code string; for debugging */ + + +/*-************************************ + * Frame compression types + ************************************* */ +/* #define LZ4F_ENABLE_OBSOLETE_ENUMS // uncomment to enable obsolete enums */ +#ifdef LZ4F_ENABLE_OBSOLETE_ENUMS +# define LZ4F_OBSOLETE_ENUM(x) , LZ4F_DEPRECATE(x) = LZ4F_##x +#else +# define LZ4F_OBSOLETE_ENUM(x) +#endif + +/* The larger the block size, the (slightly) better the compression ratio, + * though there are diminishing returns. + * Larger blocks also increase memory usage on both compression and decompression sides. + */ +typedef enum { + LZ4F_default=0, + LZ4F_max64KB=4, + LZ4F_max256KB=5, + LZ4F_max1MB=6, + LZ4F_max4MB=7 + LZ4F_OBSOLETE_ENUM(max64KB) + LZ4F_OBSOLETE_ENUM(max256KB) + LZ4F_OBSOLETE_ENUM(max1MB) + LZ4F_OBSOLETE_ENUM(max4MB) +} LZ4F_blockSizeID_t; + +/* Linked blocks sharply reduce inefficiencies when using small blocks, + * they compress better. + * However, some LZ4 decoders are only compatible with independent blocks */ +typedef enum { + LZ4F_blockLinked=0, + LZ4F_blockIndependent + LZ4F_OBSOLETE_ENUM(blockLinked) + LZ4F_OBSOLETE_ENUM(blockIndependent) +} LZ4F_blockMode_t; + +typedef enum { + LZ4F_noContentChecksum=0, + LZ4F_contentChecksumEnabled + LZ4F_OBSOLETE_ENUM(noContentChecksum) + LZ4F_OBSOLETE_ENUM(contentChecksumEnabled) +} LZ4F_contentChecksum_t; + +typedef enum { + LZ4F_noBlockChecksum=0, + LZ4F_blockChecksumEnabled +} LZ4F_blockChecksum_t; + +typedef enum { + LZ4F_frame=0, + LZ4F_skippableFrame + LZ4F_OBSOLETE_ENUM(skippableFrame) +} LZ4F_frameType_t; + +#ifdef LZ4F_ENABLE_OBSOLETE_ENUMS +typedef LZ4F_blockSizeID_t blockSizeID_t; +typedef LZ4F_blockMode_t blockMode_t; +typedef LZ4F_frameType_t frameType_t; +typedef LZ4F_contentChecksum_t contentChecksum_t; +#endif + +/*! LZ4F_frameInfo_t : + * makes it possible to set or read frame parameters. + * Structure must be first init to 0, using memset() or LZ4F_INIT_FRAMEINFO, + * setting all parameters to default. + * It's then possible to update selectively some parameters */ +typedef struct { + LZ4F_blockSizeID_t blockSizeID; /* max64KB, max256KB, max1MB, max4MB; 0 == default */ + LZ4F_blockMode_t blockMode; /* LZ4F_blockLinked, LZ4F_blockIndependent; 0 == default */ + LZ4F_contentChecksum_t contentChecksumFlag; /* 1: frame terminated with 32-bit checksum of decompressed data; 0: disabled (default) */ + LZ4F_frameType_t frameType; /* read-only field : LZ4F_frame or LZ4F_skippableFrame */ + unsigned long long contentSize; /* Size of uncompressed content ; 0 == unknown */ + unsigned dictID; /* Dictionary ID, sent by compressor to help decoder select correct dictionary; 0 == no dictID provided */ + LZ4F_blockChecksum_t blockChecksumFlag; /* 1: each block followed by a checksum of block's compressed data; 0: disabled (default) */ +} LZ4F_frameInfo_t; + +#define LZ4F_INIT_FRAMEINFO { LZ4F_default, LZ4F_blockLinked, LZ4F_noContentChecksum, LZ4F_frame, 0ULL, 0U, LZ4F_noBlockChecksum } /* v1.8.3+ */ + +/*! LZ4F_preferences_t : + * makes it possible to supply advanced compression instructions to streaming interface. + * Structure must be first init to 0, using memset() or LZ4F_INIT_PREFERENCES, + * setting all parameters to default. + * All reserved fields must be set to zero. */ +typedef struct { + LZ4F_frameInfo_t frameInfo; + int compressionLevel; /* 0: default (fast mode); values > LZ4HC_CLEVEL_MAX count as LZ4HC_CLEVEL_MAX; values < 0 trigger "fast acceleration" */ + unsigned autoFlush; /* 1: always flush; reduces usage of internal buffers */ + unsigned favorDecSpeed; /* 1: parser favors decompression speed vs compression ratio. Only works for high compression modes (>= LZ4HC_CLEVEL_OPT_MIN) */ /* v1.8.2+ */ + unsigned reserved[3]; /* must be zero for forward compatibility */ +} LZ4F_preferences_t; + +#define LZ4F_INIT_PREFERENCES { LZ4F_INIT_FRAMEINFO, 0, 0u, 0u, { 0u, 0u, 0u } } /* v1.8.3+ */ + + +/*-********************************* +* Simple compression function +***********************************/ + +LZ4FLIB_API int LZ4F_compressionLevel_max(void); /* v1.8.0+ */ + +/*! LZ4F_compressFrameBound() : + * Returns the maximum possible compressed size with LZ4F_compressFrame() given srcSize and preferences. + * `preferencesPtr` is optional. It can be replaced by NULL, in which case, the function will assume default preferences. + * Note : this result is only usable with LZ4F_compressFrame(). + * It may also be used with LZ4F_compressUpdate() _if no flush() operation_ is performed. + */ +LZ4FLIB_API size_t LZ4F_compressFrameBound(size_t srcSize, const LZ4F_preferences_t* preferencesPtr); + +/*! LZ4F_compressFrame() : + * Compress an entire srcBuffer into a valid LZ4 frame. + * dstCapacity MUST be >= LZ4F_compressFrameBound(srcSize, preferencesPtr). + * The LZ4F_preferences_t structure is optional : you can provide NULL as argument. All preferences will be set to default. + * @return : number of bytes written into dstBuffer. + * or an error code if it fails (can be tested using LZ4F_isError()) + */ +LZ4FLIB_API size_t LZ4F_compressFrame(void* dstBuffer, size_t dstCapacity, + const void* srcBuffer, size_t srcSize, + const LZ4F_preferences_t* preferencesPtr); + + +/*-*********************************** +* Advanced compression functions +*************************************/ +typedef struct LZ4F_cctx_s LZ4F_cctx; /* incomplete type */ +typedef LZ4F_cctx* LZ4F_compressionContext_t; /* for compatibility with previous API version */ + +typedef struct { + unsigned stableSrc; /* 1 == src content will remain present on future calls to LZ4F_compress(); skip copying src content within tmp buffer */ + unsigned reserved[3]; +} LZ4F_compressOptions_t; + +/*--- Resource Management ---*/ + +#define LZ4F_VERSION 100 /* This number can be used to check for an incompatible API breaking change */ +LZ4FLIB_API unsigned LZ4F_getVersion(void); + +/*! LZ4F_createCompressionContext() : + * The first thing to do is to create a compressionContext object, which will be used in all compression operations. + * This is achieved using LZ4F_createCompressionContext(), which takes as argument a version. + * The version provided MUST be LZ4F_VERSION. It is intended to track potential version mismatch, notably when using DLL. + * The function will provide a pointer to a fully allocated LZ4F_cctx object. + * If @return != zero, there was an error during context creation. + * Object can be released using LZ4F_freeCompressionContext(); + * Note: LZ4F_freeCompressionContext() works with NULL pointers (do nothing). + */ +LZ4FLIB_API LZ4F_errorCode_t LZ4F_createCompressionContext(LZ4F_cctx** cctxPtr, unsigned version); +LZ4FLIB_API LZ4F_errorCode_t LZ4F_freeCompressionContext(LZ4F_cctx* cctx); + + +/*---- Compression ----*/ + +#define LZ4F_HEADER_SIZE_MIN 7 /* LZ4 Frame header size can vary, depending on selected paramaters */ +#define LZ4F_HEADER_SIZE_MAX 19 + +/* Size in bytes of a block header in little-endian format. Highest bit indicates if block data is uncompressed */ +#define LZ4F_BLOCK_HEADER_SIZE 4 + +/* Size in bytes of a block checksum footer in little-endian format. */ +#define LZ4F_BLOCK_CHECKSUM_SIZE 4 + +/* Size in bytes of the content checksum. */ +#define LZ4F_CONTENT_CHECKSUM_SIZE 4 + +/*! LZ4F_compressBegin() : + * will write the frame header into dstBuffer. + * dstCapacity must be >= LZ4F_HEADER_SIZE_MAX bytes. + * `prefsPtr` is optional : you can provide NULL as argument, all preferences will then be set to default. + * @return : number of bytes written into dstBuffer for the header + * or an error code (which can be tested using LZ4F_isError()) + */ +LZ4FLIB_API size_t LZ4F_compressBegin(LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, + const LZ4F_preferences_t* prefsPtr); + +/*! LZ4F_compressBound() : + * Provides minimum dstCapacity required to guarantee success of + * LZ4F_compressUpdate(), given a srcSize and preferences, for a worst case scenario. + * When srcSize==0, LZ4F_compressBound() provides an upper bound for LZ4F_flush() and LZ4F_compressEnd() instead. + * Note that the result is only valid for a single invocation of LZ4F_compressUpdate(). + * When invoking LZ4F_compressUpdate() multiple times, + * if the output buffer is gradually filled up instead of emptied and re-used from its start, + * one must check if there is enough remaining capacity before each invocation, using LZ4F_compressBound(). + * @return is always the same for a srcSize and prefsPtr. + * prefsPtr is optional : when NULL is provided, preferences will be set to cover worst case scenario. + * tech details : + * @return if automatic flushing is not enabled, includes the possibility that internal buffer might already be filled by up to (blockSize-1) bytes. + * It also includes frame footer (ending + checksum), since it might be generated by LZ4F_compressEnd(). + * @return doesn't include frame header, as it was already generated by LZ4F_compressBegin(). + */ +LZ4FLIB_API size_t LZ4F_compressBound(size_t srcSize, const LZ4F_preferences_t* prefsPtr); + +/*! LZ4F_compressUpdate() : + * LZ4F_compressUpdate() can be called repetitively to compress as much data as necessary. + * Important rule: dstCapacity MUST be large enough to ensure operation success even in worst case situations. + * This value is provided by LZ4F_compressBound(). + * If this condition is not respected, LZ4F_compress() will fail (result is an errorCode). + * After an error, the state is left in a UB state, and must be re-initialized or freed. + * `cOptPtr` is optional : NULL can be provided, in which case all options are set to default. + * @return : number of bytes written into `dstBuffer` (it can be zero, meaning input data was just buffered). + * or an error code if it fails (which can be tested using LZ4F_isError()) + */ +LZ4FLIB_API size_t LZ4F_compressUpdate(LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, + const void* srcBuffer, size_t srcSize, + const LZ4F_compressOptions_t* cOptPtr); + +/*! LZ4F_flush() : + * When data must be generated and sent immediately, without waiting for a block to be completely filled, + * it's possible to call LZ4_flush(). It will immediately compress any data buffered within cctx. + * `dstCapacity` must be large enough to ensure the operation will be successful. + * `cOptPtr` is optional : it's possible to provide NULL, all options will be set to default. + * @return : nb of bytes written into dstBuffer (can be zero, when there is no data stored within cctx) + * or an error code if it fails (which can be tested using LZ4F_isError()) + * Note : LZ4F_flush() is guaranteed to be successful when dstCapacity >= LZ4F_compressBound(0, prefsPtr). + */ +LZ4FLIB_API size_t LZ4F_flush(LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, + const LZ4F_compressOptions_t* cOptPtr); + +/*! LZ4F_compressEnd() : + * To properly finish an LZ4 frame, invoke LZ4F_compressEnd(). + * It will flush whatever data remained within `cctx` (like LZ4_flush()) + * and properly finalize the frame, with an endMark and a checksum. + * `cOptPtr` is optional : NULL can be provided, in which case all options will be set to default. + * @return : nb of bytes written into dstBuffer, necessarily >= 4 (endMark), + * or an error code if it fails (which can be tested using LZ4F_isError()) + * Note : LZ4F_compressEnd() is guaranteed to be successful when dstCapacity >= LZ4F_compressBound(0, prefsPtr). + * A successful call to LZ4F_compressEnd() makes `cctx` available again for another compression task. + */ +LZ4FLIB_API size_t LZ4F_compressEnd(LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, + const LZ4F_compressOptions_t* cOptPtr); + + +/*-********************************* +* Decompression functions +***********************************/ +typedef struct LZ4F_dctx_s LZ4F_dctx; /* incomplete type */ +typedef LZ4F_dctx* LZ4F_decompressionContext_t; /* compatibility with previous API versions */ + +typedef struct { + unsigned stableDst; /* pledges that last 64KB decompressed data will remain available unmodified. This optimization skips storage operations in tmp buffers. */ + unsigned reserved[3]; /* must be set to zero for forward compatibility */ +} LZ4F_decompressOptions_t; + + +/* Resource management */ + +/*! LZ4F_createDecompressionContext() : + * Create an LZ4F_dctx object, to track all decompression operations. + * The version provided MUST be LZ4F_VERSION. + * The function provides a pointer to an allocated and initialized LZ4F_dctx object. + * The result is an errorCode, which can be tested using LZ4F_isError(). + * dctx memory can be released using LZ4F_freeDecompressionContext(); + * Result of LZ4F_freeDecompressionContext() indicates current state of decompressionContext when being released. + * That is, it should be == 0 if decompression has been completed fully and correctly. + */ +LZ4FLIB_API LZ4F_errorCode_t LZ4F_createDecompressionContext(LZ4F_dctx** dctxPtr, unsigned version); +LZ4FLIB_API LZ4F_errorCode_t LZ4F_freeDecompressionContext(LZ4F_dctx* dctx); + + +/*-*********************************** +* Streaming decompression functions +*************************************/ + +#define LZ4F_MIN_SIZE_TO_KNOW_HEADER_LENGTH 5 + +/*! LZ4F_headerSize() : v1.9.0+ + * Provide the header size of a frame starting at `src`. + * `srcSize` must be >= LZ4F_MIN_SIZE_TO_KNOW_HEADER_LENGTH, + * which is enough to decode the header length. + * @return : size of frame header + * or an error code, which can be tested using LZ4F_isError() + * note : Frame header size is variable, but is guaranteed to be + * >= LZ4F_HEADER_SIZE_MIN bytes, and <= LZ4F_HEADER_SIZE_MAX bytes. + */ +LZ4FLIB_API size_t LZ4F_headerSize(const void* src, size_t srcSize); + +/*! LZ4F_getFrameInfo() : + * This function extracts frame parameters (max blockSize, dictID, etc.). + * Its usage is optional: user can call LZ4F_decompress() directly. + * + * Extracted information will fill an existing LZ4F_frameInfo_t structure. + * This can be useful for allocation and dictionary identification purposes. + * + * LZ4F_getFrameInfo() can work in the following situations : + * + * 1) At the beginning of a new frame, before any invocation of LZ4F_decompress(). + * It will decode header from `srcBuffer`, + * consuming the header and starting the decoding process. + * + * Input size must be large enough to contain the full frame header. + * Frame header size can be known beforehand by LZ4F_headerSize(). + * Frame header size is variable, but is guaranteed to be >= LZ4F_HEADER_SIZE_MIN bytes, + * and not more than <= LZ4F_HEADER_SIZE_MAX bytes. + * Hence, blindly providing LZ4F_HEADER_SIZE_MAX bytes or more will always work. + * It's allowed to provide more input data than the header size, + * LZ4F_getFrameInfo() will only consume the header. + * + * If input size is not large enough, + * aka if it's smaller than header size, + * function will fail and return an error code. + * + * 2) After decoding has been started, + * it's possible to invoke LZ4F_getFrameInfo() anytime + * to extract already decoded frame parameters stored within dctx. + * + * Note that, if decoding has barely started, + * and not yet read enough information to decode the header, + * LZ4F_getFrameInfo() will fail. + * + * The number of bytes consumed from srcBuffer will be updated in *srcSizePtr (necessarily <= original value). + * LZ4F_getFrameInfo() only consumes bytes when decoding has not yet started, + * and when decoding the header has been successful. + * Decompression must then resume from (srcBuffer + *srcSizePtr). + * + * @return : a hint about how many srcSize bytes LZ4F_decompress() expects for next call, + * or an error code which can be tested using LZ4F_isError(). + * note 1 : in case of error, dctx is not modified. Decoding operation can resume from beginning safely. + * note 2 : frame parameters are *copied into* an already allocated LZ4F_frameInfo_t structure. + */ +LZ4FLIB_API size_t LZ4F_getFrameInfo(LZ4F_dctx* dctx, + LZ4F_frameInfo_t* frameInfoPtr, + const void* srcBuffer, size_t* srcSizePtr); + +/*! LZ4F_decompress() : + * Call this function repetitively to regenerate data compressed in `srcBuffer`. + * + * The function requires a valid dctx state. + * It will read up to *srcSizePtr bytes from srcBuffer, + * and decompress data into dstBuffer, of capacity *dstSizePtr. + * + * The nb of bytes consumed from srcBuffer will be written into *srcSizePtr (necessarily <= original value). + * The nb of bytes decompressed into dstBuffer will be written into *dstSizePtr (necessarily <= original value). + * + * The function does not necessarily read all input bytes, so always check value in *srcSizePtr. + * Unconsumed source data must be presented again in subsequent invocations. + * + * `dstBuffer` can freely change between each consecutive function invocation. + * `dstBuffer` content will be overwritten. + * + * @return : an hint of how many `srcSize` bytes LZ4F_decompress() expects for next call. + * Schematically, it's the size of the current (or remaining) compressed block + header of next block. + * Respecting the hint provides some small speed benefit, because it skips intermediate buffers. + * This is just a hint though, it's always possible to provide any srcSize. + * + * When a frame is fully decoded, @return will be 0 (no more data expected). + * When provided with more bytes than necessary to decode a frame, + * LZ4F_decompress() will stop reading exactly at end of current frame, and @return 0. + * + * If decompression failed, @return is an error code, which can be tested using LZ4F_isError(). + * After a decompression error, the `dctx` context is not resumable. + * Use LZ4F_resetDecompressionContext() to return to clean state. + * + * After a frame is fully decoded, dctx can be used again to decompress another frame. + */ +LZ4FLIB_API size_t LZ4F_decompress(LZ4F_dctx* dctx, + void* dstBuffer, size_t* dstSizePtr, + const void* srcBuffer, size_t* srcSizePtr, + const LZ4F_decompressOptions_t* dOptPtr); + + +/*! LZ4F_resetDecompressionContext() : added in v1.8.0 + * In case of an error, the context is left in "undefined" state. + * In which case, it's necessary to reset it, before re-using it. + * This method can also be used to abruptly stop any unfinished decompression, + * and start a new one using same context resources. */ +LZ4FLIB_API void LZ4F_resetDecompressionContext(LZ4F_dctx* dctx); /* always successful */ + + + +#if defined (__cplusplus) +} +#endif + +#endif /* LZ4F_H_09782039843 */ + +#if defined(LZ4F_STATIC_LINKING_ONLY) && !defined(LZ4F_H_STATIC_09782039843) +#define LZ4F_H_STATIC_09782039843 + +#if defined (__cplusplus) +extern "C" { +#endif + +/* These declarations are not stable and may change in the future. + * They are therefore only safe to depend on + * when the caller is statically linked against the library. + * To access their declarations, define LZ4F_STATIC_LINKING_ONLY. + * + * By default, these symbols aren't published into shared/dynamic libraries. + * You can override this behavior and force them to be published + * by defining LZ4F_PUBLISH_STATIC_FUNCTIONS. + * Use at your own risk. + */ +#ifdef LZ4F_PUBLISH_STATIC_FUNCTIONS +# define LZ4FLIB_STATIC_API LZ4FLIB_API +#else +# define LZ4FLIB_STATIC_API +#endif + + +/* --- Error List --- */ +#define LZ4F_LIST_ERRORS(ITEM) \ + ITEM(OK_NoError) \ + ITEM(ERROR_GENERIC) \ + ITEM(ERROR_maxBlockSize_invalid) \ + ITEM(ERROR_blockMode_invalid) \ + ITEM(ERROR_contentChecksumFlag_invalid) \ + ITEM(ERROR_compressionLevel_invalid) \ + ITEM(ERROR_headerVersion_wrong) \ + ITEM(ERROR_blockChecksum_invalid) \ + ITEM(ERROR_reservedFlag_set) \ + ITEM(ERROR_allocation_failed) \ + ITEM(ERROR_srcSize_tooLarge) \ + ITEM(ERROR_dstMaxSize_tooSmall) \ + ITEM(ERROR_frameHeader_incomplete) \ + ITEM(ERROR_frameType_unknown) \ + ITEM(ERROR_frameSize_wrong) \ + ITEM(ERROR_srcPtr_wrong) \ + ITEM(ERROR_decompressionFailed) \ + ITEM(ERROR_headerChecksum_invalid) \ + ITEM(ERROR_contentChecksum_invalid) \ + ITEM(ERROR_frameDecoding_alreadyStarted) \ + ITEM(ERROR_maxCode) + +#define LZ4F_GENERATE_ENUM(ENUM) LZ4F_##ENUM, + +/* enum list is exposed, to handle specific errors */ +typedef enum { LZ4F_LIST_ERRORS(LZ4F_GENERATE_ENUM) + _LZ4F_dummy_error_enum_for_c89_never_used } LZ4F_errorCodes; + +LZ4FLIB_STATIC_API LZ4F_errorCodes LZ4F_getErrorCode(size_t functionResult); + +LZ4FLIB_STATIC_API size_t LZ4F_getBlockSize(unsigned); + +/********************************** + * Bulk processing dictionary API + *********************************/ + +/* A Dictionary is useful for the compression of small messages (KB range). + * It dramatically improves compression efficiency. + * + * LZ4 can ingest any input as dictionary, though only the last 64 KB are useful. + * Best results are generally achieved by using Zstandard's Dictionary Builder + * to generate a high-quality dictionary from a set of samples. + * + * Loading a dictionary has a cost, since it involves construction of tables. + * The Bulk processing dictionary API makes it possible to share this cost + * over an arbitrary number of compression jobs, even concurrently, + * markedly improving compression latency for these cases. + * + * The same dictionary will have to be used on the decompression side + * for decoding to be successful. + * To help identify the correct dictionary at decoding stage, + * the frame header allows optional embedding of a dictID field. + */ +typedef struct LZ4F_CDict_s LZ4F_CDict; + +/*! LZ4_createCDict() : + * When compressing multiple messages / blocks using the same dictionary, it's recommended to load it just once. + * LZ4_createCDict() will create a digested dictionary, ready to start future compression operations without startup delay. + * LZ4_CDict can be created once and shared by multiple threads concurrently, since its usage is read-only. + * `dictBuffer` can be released after LZ4_CDict creation, since its content is copied within CDict */ +LZ4FLIB_STATIC_API LZ4F_CDict* LZ4F_createCDict(const void* dictBuffer, size_t dictSize); +LZ4FLIB_STATIC_API void LZ4F_freeCDict(LZ4F_CDict* CDict); + + +/*! LZ4_compressFrame_usingCDict() : + * Compress an entire srcBuffer into a valid LZ4 frame using a digested Dictionary. + * cctx must point to a context created by LZ4F_createCompressionContext(). + * If cdict==NULL, compress without a dictionary. + * dstBuffer MUST be >= LZ4F_compressFrameBound(srcSize, preferencesPtr). + * If this condition is not respected, function will fail (@return an errorCode). + * The LZ4F_preferences_t structure is optional : you may provide NULL as argument, + * but it's not recommended, as it's the only way to provide dictID in the frame header. + * @return : number of bytes written into dstBuffer. + * or an error code if it fails (can be tested using LZ4F_isError()) */ +LZ4FLIB_STATIC_API size_t LZ4F_compressFrame_usingCDict( + LZ4F_cctx* cctx, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize, + const LZ4F_CDict* cdict, + const LZ4F_preferences_t* preferencesPtr); + + +/*! LZ4F_compressBegin_usingCDict() : + * Inits streaming dictionary compression, and writes the frame header into dstBuffer. + * dstCapacity must be >= LZ4F_HEADER_SIZE_MAX bytes. + * `prefsPtr` is optional : you may provide NULL as argument, + * however, it's the only way to provide dictID in the frame header. + * @return : number of bytes written into dstBuffer for the header, + * or an error code (which can be tested using LZ4F_isError()) */ +LZ4FLIB_STATIC_API size_t LZ4F_compressBegin_usingCDict( + LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, + const LZ4F_CDict* cdict, + const LZ4F_preferences_t* prefsPtr); + + +/*! LZ4F_decompress_usingDict() : + * Same as LZ4F_decompress(), using a predefined dictionary. + * Dictionary is used "in place", without any preprocessing. + * It must remain accessible throughout the entire frame decoding. */ +LZ4FLIB_STATIC_API size_t LZ4F_decompress_usingDict( + LZ4F_dctx* dctxPtr, + void* dstBuffer, size_t* dstSizePtr, + const void* srcBuffer, size_t* srcSizePtr, + const void* dict, size_t dictSize, + const LZ4F_decompressOptions_t* decompressOptionsPtr); + +#if defined (__cplusplus) +} +#endif + +#endif /* defined(LZ4F_STATIC_LINKING_ONLY) && !defined(LZ4F_H_STATIC_09782039843) */ diff --git a/libCompression/lz4frame_static.h b/libCompression/lz4frame_static.h new file mode 100644 index 0000000..2b44a63 --- /dev/null +++ b/libCompression/lz4frame_static.h @@ -0,0 +1,47 @@ +/* + LZ4 auto-framing library + Header File for static linking only + Copyright (C) 2011-2020, Yann Collet. + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 source repository : https://github.com/lz4/lz4 + - LZ4 public forum : https://groups.google.com/forum/#!forum/lz4c +*/ + +#ifndef LZ4FRAME_STATIC_H_0398209384 +#define LZ4FRAME_STATIC_H_0398209384 + +/* The declarations that formerly were made here have been merged into + * lz4frame.h, protected by the LZ4F_STATIC_LINKING_ONLY macro. Going forward, + * it is recommended to simply include that header directly. + */ + +#define LZ4F_STATIC_LINKING_ONLY +#include "lz4frame.h" + +#endif /* LZ4FRAME_STATIC_H_0398209384 */ diff --git a/libCompression/lz4hc.c b/libCompression/lz4hc.c new file mode 100644 index 0000000..91fd2b0 --- /dev/null +++ b/libCompression/lz4hc.c @@ -0,0 +1,1621 @@ +/* + LZ4 HC - High Compression Mode of LZ4 + Copyright (C) 2011-2020, Yann Collet. + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 source repository : https://github.com/lz4/lz4 + - LZ4 public forum : https://groups.google.com/forum/#!forum/lz4c +*/ +/* note : lz4hc is not an independent module, it requires lz4.h/lz4.c for proper compilation */ + + +/* ************************************* +* Tuning Parameter +***************************************/ + +/*! HEAPMODE : + * Select how default compression function will allocate workplace memory, + * in stack (0:fastest), or in heap (1:requires malloc()). + * Since workplace is rather large, heap mode is recommended. + */ +#ifndef LZ4HC_HEAPMODE +# define LZ4HC_HEAPMODE 1 +#endif + + +/*=== Dependency ===*/ +#define LZ4_HC_STATIC_LINKING_ONLY +#include "lz4hc.h" + + +/*=== Common definitions ===*/ +#if defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wunused-function" +#endif +#if defined (__clang__) +# pragma clang diagnostic ignored "-Wunused-function" +#endif + +#define LZ4_COMMONDEFS_ONLY +#ifndef LZ4_SRC_INCLUDED +#include "lz4.c" /* LZ4_count, constants, mem */ +#endif + + +/*=== Enums ===*/ +typedef enum { noDictCtx, usingDictCtxHc } dictCtx_directive; + + +/*=== Constants ===*/ +#define OPTIMAL_ML (int)((ML_MASK-1)+MINMATCH) +#define LZ4_OPT_NUM (1<<12) + + +/*=== Macros ===*/ +#define MIN(a,b) ( (a) < (b) ? (a) : (b) ) +#define MAX(a,b) ( (a) > (b) ? (a) : (b) ) +#define HASH_FUNCTION(i) (((i) * 2654435761U) >> ((MINMATCH*8)-LZ4HC_HASH_LOG)) +#define DELTANEXTMAXD(p) chainTable[(p) & LZ4HC_MAXD_MASK] /* flexible, LZ4HC_MAXD dependent */ +#define DELTANEXTU16(table, pos) table[(U16)(pos)] /* faster */ +/* Make fields passed to, and updated by LZ4HC_encodeSequence explicit */ +#define UPDATABLE(ip, op, anchor) &ip, &op, &anchor + +static U32 LZ4HC_hashPtr(const void* ptr) { return HASH_FUNCTION(LZ4_read32(ptr)); } + + +/************************************** +* HC Compression +**************************************/ +static void LZ4HC_clearTables (LZ4HC_CCtx_internal* hc4) +{ + MEM_INIT(hc4->hashTable, 0, sizeof(hc4->hashTable)); + MEM_INIT(hc4->chainTable, 0xFF, sizeof(hc4->chainTable)); +} + +static void LZ4HC_init_internal (LZ4HC_CCtx_internal* hc4, const BYTE* start) +{ + uptrval startingOffset = (uptrval)(hc4->end - hc4->base); + if (startingOffset > 1 GB) { + LZ4HC_clearTables(hc4); + startingOffset = 0; + } + startingOffset += 64 KB; + hc4->nextToUpdate = (U32) startingOffset; + hc4->base = start - startingOffset; + hc4->end = start; + hc4->dictBase = start - startingOffset; + hc4->dictLimit = (U32) startingOffset; + hc4->lowLimit = (U32) startingOffset; +} + + +/* Update chains up to ip (excluded) */ +LZ4_FORCE_INLINE void LZ4HC_Insert (LZ4HC_CCtx_internal* hc4, const BYTE* ip) +{ + U16* const chainTable = hc4->chainTable; + U32* const hashTable = hc4->hashTable; + const BYTE* const base = hc4->base; + U32 const target = (U32)(ip - base); + U32 idx = hc4->nextToUpdate; + + while (idx < target) { + U32 const h = LZ4HC_hashPtr(base+idx); + size_t delta = idx - hashTable[h]; + if (delta>LZ4_DISTANCE_MAX) delta = LZ4_DISTANCE_MAX; + DELTANEXTU16(chainTable, idx) = (U16)delta; + hashTable[h] = idx; + idx++; + } + + hc4->nextToUpdate = target; +} + +/** LZ4HC_countBack() : + * @return : negative value, nb of common bytes before ip/match */ +LZ4_FORCE_INLINE +int LZ4HC_countBack(const BYTE* const ip, const BYTE* const match, + const BYTE* const iMin, const BYTE* const mMin) +{ + int back = 0; + int const min = (int)MAX(iMin - ip, mMin - match); + assert(min <= 0); + assert(ip >= iMin); assert((size_t)(ip-iMin) < (1U<<31)); + assert(match >= mMin); assert((size_t)(match - mMin) < (1U<<31)); + while ( (back > min) + && (ip[back-1] == match[back-1]) ) + back--; + return back; +} + +#if defined(_MSC_VER) +# define LZ4HC_rotl32(x,r) _rotl(x,r) +#else +# define LZ4HC_rotl32(x,r) ((x << r) | (x >> (32 - r))) +#endif + + +static U32 LZ4HC_rotatePattern(size_t const rotate, U32 const pattern) +{ + size_t const bitsToRotate = (rotate & (sizeof(pattern) - 1)) << 3; + if (bitsToRotate == 0) return pattern; + return LZ4HC_rotl32(pattern, (int)bitsToRotate); +} + +/* LZ4HC_countPattern() : + * pattern32 must be a sample of repetitive pattern of length 1, 2 or 4 (but not 3!) */ +static unsigned +LZ4HC_countPattern(const BYTE* ip, const BYTE* const iEnd, U32 const pattern32) +{ + const BYTE* const iStart = ip; + reg_t const pattern = (sizeof(pattern)==8) ? + (reg_t)pattern32 + (((reg_t)pattern32) << (sizeof(pattern)*4)) : pattern32; + + while (likely(ip < iEnd-(sizeof(pattern)-1))) { + reg_t const diff = LZ4_read_ARCH(ip) ^ pattern; + if (!diff) { ip+=sizeof(pattern); continue; } + ip += LZ4_NbCommonBytes(diff); + return (unsigned)(ip - iStart); + } + + if (LZ4_isLittleEndian()) { + reg_t patternByte = pattern; + while ((ip>= 8; + } + } else { /* big endian */ + U32 bitOffset = (sizeof(pattern)*8) - 8; + while (ip < iEnd) { + BYTE const byte = (BYTE)(pattern >> bitOffset); + if (*ip != byte) break; + ip ++; bitOffset -= 8; + } + } + + return (unsigned)(ip - iStart); +} + +/* LZ4HC_reverseCountPattern() : + * pattern must be a sample of repetitive pattern of length 1, 2 or 4 (but not 3!) + * read using natural platform endianess */ +static unsigned +LZ4HC_reverseCountPattern(const BYTE* ip, const BYTE* const iLow, U32 pattern) +{ + const BYTE* const iStart = ip; + + while (likely(ip >= iLow+4)) { + if (LZ4_read32(ip-4) != pattern) break; + ip -= 4; + } + { const BYTE* bytePtr = (const BYTE*)(&pattern) + 3; /* works for any endianess */ + while (likely(ip>iLow)) { + if (ip[-1] != *bytePtr) break; + ip--; bytePtr--; + } } + return (unsigned)(iStart - ip); +} + +/* LZ4HC_protectDictEnd() : + * Checks if the match is in the last 3 bytes of the dictionary, so reading the + * 4 byte MINMATCH would overflow. + * @returns true if the match index is okay. + */ +static int LZ4HC_protectDictEnd(U32 const dictLimit, U32 const matchIndex) +{ + return ((U32)((dictLimit - 1) - matchIndex) >= 3); +} + +typedef enum { rep_untested, rep_not, rep_confirmed } repeat_state_e; +typedef enum { favorCompressionRatio=0, favorDecompressionSpeed } HCfavor_e; + +LZ4_FORCE_INLINE int +LZ4HC_InsertAndGetWiderMatch ( + LZ4HC_CCtx_internal* hc4, + const BYTE* const ip, + const BYTE* const iLowLimit, + const BYTE* const iHighLimit, + int longest, + const BYTE** matchpos, + const BYTE** startpos, + const int maxNbAttempts, + const int patternAnalysis, + const int chainSwap, + const dictCtx_directive dict, + const HCfavor_e favorDecSpeed) +{ + U16* const chainTable = hc4->chainTable; + U32* const HashTable = hc4->hashTable; + const LZ4HC_CCtx_internal * const dictCtx = hc4->dictCtx; + const BYTE* const base = hc4->base; + const U32 dictLimit = hc4->dictLimit; + const BYTE* const lowPrefixPtr = base + dictLimit; + const U32 ipIndex = (U32)(ip - base); + const U32 lowestMatchIndex = (hc4->lowLimit + (LZ4_DISTANCE_MAX + 1) > ipIndex) ? hc4->lowLimit : ipIndex - LZ4_DISTANCE_MAX; + const BYTE* const dictBase = hc4->dictBase; + int const lookBackLength = (int)(ip-iLowLimit); + int nbAttempts = maxNbAttempts; + U32 matchChainPos = 0; + U32 const pattern = LZ4_read32(ip); + U32 matchIndex; + repeat_state_e repeat = rep_untested; + size_t srcPatternLength = 0; + + DEBUGLOG(7, "LZ4HC_InsertAndGetWiderMatch"); + /* First Match */ + LZ4HC_Insert(hc4, ip); + matchIndex = HashTable[LZ4HC_hashPtr(ip)]; + DEBUGLOG(7, "First match at index %u / %u (lowestMatchIndex)", + matchIndex, lowestMatchIndex); + + while ((matchIndex>=lowestMatchIndex) && (nbAttempts>0)) { + int matchLength=0; + nbAttempts--; + assert(matchIndex < ipIndex); + if (favorDecSpeed && (ipIndex - matchIndex < 8)) { + /* do nothing */ + } else if (matchIndex >= dictLimit) { /* within current Prefix */ + const BYTE* const matchPtr = base + matchIndex; + assert(matchPtr >= lowPrefixPtr); + assert(matchPtr < ip); + assert(longest >= 1); + if (LZ4_read16(iLowLimit + longest - 1) == LZ4_read16(matchPtr - lookBackLength + longest - 1)) { + if (LZ4_read32(matchPtr) == pattern) { + int const back = lookBackLength ? LZ4HC_countBack(ip, matchPtr, iLowLimit, lowPrefixPtr) : 0; + matchLength = MINMATCH + (int)LZ4_count(ip+MINMATCH, matchPtr+MINMATCH, iHighLimit); + matchLength -= back; + if (matchLength > longest) { + longest = matchLength; + *matchpos = matchPtr + back; + *startpos = ip + back; + } } } + } else { /* lowestMatchIndex <= matchIndex < dictLimit */ + const BYTE* const matchPtr = dictBase + matchIndex; + if (LZ4_read32(matchPtr) == pattern) { + const BYTE* const dictStart = dictBase + hc4->lowLimit; + int back = 0; + const BYTE* vLimit = ip + (dictLimit - matchIndex); + if (vLimit > iHighLimit) vLimit = iHighLimit; + matchLength = (int)LZ4_count(ip+MINMATCH, matchPtr+MINMATCH, vLimit) + MINMATCH; + if ((ip+matchLength == vLimit) && (vLimit < iHighLimit)) + matchLength += LZ4_count(ip+matchLength, lowPrefixPtr, iHighLimit); + back = lookBackLength ? LZ4HC_countBack(ip, matchPtr, iLowLimit, dictStart) : 0; + matchLength -= back; + if (matchLength > longest) { + longest = matchLength; + *matchpos = base + matchIndex + back; /* virtual pos, relative to ip, to retrieve offset */ + *startpos = ip + back; + } } } + + if (chainSwap && matchLength==longest) { /* better match => select a better chain */ + assert(lookBackLength==0); /* search forward only */ + if (matchIndex + (U32)longest <= ipIndex) { + int const kTrigger = 4; + U32 distanceToNextMatch = 1; + int const end = longest - MINMATCH + 1; + int step = 1; + int accel = 1 << kTrigger; + int pos; + for (pos = 0; pos < end; pos += step) { + U32 const candidateDist = DELTANEXTU16(chainTable, matchIndex + (U32)pos); + step = (accel++ >> kTrigger); + if (candidateDist > distanceToNextMatch) { + distanceToNextMatch = candidateDist; + matchChainPos = (U32)pos; + accel = 1 << kTrigger; + } + } + if (distanceToNextMatch > 1) { + if (distanceToNextMatch > matchIndex) break; /* avoid overflow */ + matchIndex -= distanceToNextMatch; + continue; + } } } + + { U32 const distNextMatch = DELTANEXTU16(chainTable, matchIndex); + if (patternAnalysis && distNextMatch==1 && matchChainPos==0) { + U32 const matchCandidateIdx = matchIndex-1; + /* may be a repeated pattern */ + if (repeat == rep_untested) { + if ( ((pattern & 0xFFFF) == (pattern >> 16)) + & ((pattern & 0xFF) == (pattern >> 24)) ) { + repeat = rep_confirmed; + srcPatternLength = LZ4HC_countPattern(ip+sizeof(pattern), iHighLimit, pattern) + sizeof(pattern); + } else { + repeat = rep_not; + } } + if ( (repeat == rep_confirmed) && (matchCandidateIdx >= lowestMatchIndex) + && LZ4HC_protectDictEnd(dictLimit, matchCandidateIdx) ) { + const int extDict = matchCandidateIdx < dictLimit; + const BYTE* const matchPtr = (extDict ? dictBase : base) + matchCandidateIdx; + if (LZ4_read32(matchPtr) == pattern) { /* good candidate */ + const BYTE* const dictStart = dictBase + hc4->lowLimit; + const BYTE* const iLimit = extDict ? dictBase + dictLimit : iHighLimit; + size_t forwardPatternLength = LZ4HC_countPattern(matchPtr+sizeof(pattern), iLimit, pattern) + sizeof(pattern); + if (extDict && matchPtr + forwardPatternLength == iLimit) { + U32 const rotatedPattern = LZ4HC_rotatePattern(forwardPatternLength, pattern); + forwardPatternLength += LZ4HC_countPattern(lowPrefixPtr, iHighLimit, rotatedPattern); + } + { const BYTE* const lowestMatchPtr = extDict ? dictStart : lowPrefixPtr; + size_t backLength = LZ4HC_reverseCountPattern(matchPtr, lowestMatchPtr, pattern); + size_t currentSegmentLength; + if (!extDict && matchPtr - backLength == lowPrefixPtr && hc4->lowLimit < dictLimit) { + U32 const rotatedPattern = LZ4HC_rotatePattern((U32)(-(int)backLength), pattern); + backLength += LZ4HC_reverseCountPattern(dictBase + dictLimit, dictStart, rotatedPattern); + } + /* Limit backLength not go further than lowestMatchIndex */ + backLength = matchCandidateIdx - MAX(matchCandidateIdx - (U32)backLength, lowestMatchIndex); + assert(matchCandidateIdx - backLength >= lowestMatchIndex); + currentSegmentLength = backLength + forwardPatternLength; + /* Adjust to end of pattern if the source pattern fits, otherwise the beginning of the pattern */ + if ( (currentSegmentLength >= srcPatternLength) /* current pattern segment large enough to contain full srcPatternLength */ + && (forwardPatternLength <= srcPatternLength) ) { /* haven't reached this position yet */ + U32 const newMatchIndex = matchCandidateIdx + (U32)forwardPatternLength - (U32)srcPatternLength; /* best position, full pattern, might be followed by more match */ + if (LZ4HC_protectDictEnd(dictLimit, newMatchIndex)) + matchIndex = newMatchIndex; + else { + /* Can only happen if started in the prefix */ + assert(newMatchIndex >= dictLimit - 3 && newMatchIndex < dictLimit && !extDict); + matchIndex = dictLimit; + } + } else { + U32 const newMatchIndex = matchCandidateIdx - (U32)backLength; /* farthest position in current segment, will find a match of length currentSegmentLength + maybe some back */ + if (!LZ4HC_protectDictEnd(dictLimit, newMatchIndex)) { + assert(newMatchIndex >= dictLimit - 3 && newMatchIndex < dictLimit && !extDict); + matchIndex = dictLimit; + } else { + matchIndex = newMatchIndex; + if (lookBackLength==0) { /* no back possible */ + size_t const maxML = MIN(currentSegmentLength, srcPatternLength); + if ((size_t)longest < maxML) { + assert(base + matchIndex != ip); + if ((size_t)(ip - base) - matchIndex > LZ4_DISTANCE_MAX) break; + assert(maxML < 2 GB); + longest = (int)maxML; + *matchpos = base + matchIndex; /* virtual pos, relative to ip, to retrieve offset */ + *startpos = ip; + } + { U32 const distToNextPattern = DELTANEXTU16(chainTable, matchIndex); + if (distToNextPattern > matchIndex) break; /* avoid overflow */ + matchIndex -= distToNextPattern; + } } } } } + continue; + } } + } } /* PA optimization */ + + /* follow current chain */ + matchIndex -= DELTANEXTU16(chainTable, matchIndex + matchChainPos); + + } /* while ((matchIndex>=lowestMatchIndex) && (nbAttempts)) */ + + if ( dict == usingDictCtxHc + && nbAttempts > 0 + && ipIndex - lowestMatchIndex < LZ4_DISTANCE_MAX) { + size_t const dictEndOffset = (size_t)(dictCtx->end - dictCtx->base); + U32 dictMatchIndex = dictCtx->hashTable[LZ4HC_hashPtr(ip)]; + assert(dictEndOffset <= 1 GB); + matchIndex = dictMatchIndex + lowestMatchIndex - (U32)dictEndOffset; + while (ipIndex - matchIndex <= LZ4_DISTANCE_MAX && nbAttempts--) { + const BYTE* const matchPtr = dictCtx->base + dictMatchIndex; + + if (LZ4_read32(matchPtr) == pattern) { + int mlt; + int back = 0; + const BYTE* vLimit = ip + (dictEndOffset - dictMatchIndex); + if (vLimit > iHighLimit) vLimit = iHighLimit; + mlt = (int)LZ4_count(ip+MINMATCH, matchPtr+MINMATCH, vLimit) + MINMATCH; + back = lookBackLength ? LZ4HC_countBack(ip, matchPtr, iLowLimit, dictCtx->base + dictCtx->dictLimit) : 0; + mlt -= back; + if (mlt > longest) { + longest = mlt; + *matchpos = base + matchIndex + back; + *startpos = ip + back; + } } + + { U32 const nextOffset = DELTANEXTU16(dictCtx->chainTable, dictMatchIndex); + dictMatchIndex -= nextOffset; + matchIndex -= nextOffset; + } } } + + return longest; +} + +LZ4_FORCE_INLINE +int LZ4HC_InsertAndFindBestMatch(LZ4HC_CCtx_internal* const hc4, /* Index table will be updated */ + const BYTE* const ip, const BYTE* const iLimit, + const BYTE** matchpos, + const int maxNbAttempts, + const int patternAnalysis, + const dictCtx_directive dict) +{ + const BYTE* uselessPtr = ip; + /* note : LZ4HC_InsertAndGetWiderMatch() is able to modify the starting position of a match (*startpos), + * but this won't be the case here, as we define iLowLimit==ip, + * so LZ4HC_InsertAndGetWiderMatch() won't be allowed to search past ip */ + return LZ4HC_InsertAndGetWiderMatch(hc4, ip, ip, iLimit, MINMATCH-1, matchpos, &uselessPtr, maxNbAttempts, patternAnalysis, 0 /*chainSwap*/, dict, favorCompressionRatio); +} + +/* LZ4HC_encodeSequence() : + * @return : 0 if ok, + * 1 if buffer issue detected */ +LZ4_FORCE_INLINE int LZ4HC_encodeSequence ( + const BYTE** _ip, + BYTE** _op, + const BYTE** _anchor, + int matchLength, + const BYTE* const match, + limitedOutput_directive limit, + BYTE* oend) +{ +#define ip (*_ip) +#define op (*_op) +#define anchor (*_anchor) + + size_t length; + BYTE* const token = op++; + +#if defined(LZ4_DEBUG) && (LZ4_DEBUG >= 6) + static const BYTE* start = NULL; + static U32 totalCost = 0; + U32 const pos = (start==NULL) ? 0 : (U32)(anchor - start); + U32 const ll = (U32)(ip - anchor); + U32 const llAdd = (ll>=15) ? ((ll-15) / 255) + 1 : 0; + U32 const mlAdd = (matchLength>=19) ? ((matchLength-19) / 255) + 1 : 0; + U32 const cost = 1 + llAdd + ll + 2 + mlAdd; + if (start==NULL) start = anchor; /* only works for single segment */ + /* g_debuglog_enable = (pos >= 2228) & (pos <= 2262); */ + DEBUGLOG(6, "pos:%7u -- literals:%4u, match:%4i, offset:%5u, cost:%4u + %5u", + pos, + (U32)(ip - anchor), matchLength, (U32)(ip-match), + cost, totalCost); + totalCost += cost; +#endif + + /* Encode Literal length */ + length = (size_t)(ip - anchor); + LZ4_STATIC_ASSERT(notLimited == 0); + /* Check output limit */ + if (limit && ((op + (length / 255) + length + (2 + 1 + LASTLITERALS)) > oend)) { + DEBUGLOG(6, "Not enough room to write %i literals (%i bytes remaining)", + (int)length, (int)(oend - op)); + return 1; + } + if (length >= RUN_MASK) { + size_t len = length - RUN_MASK; + *token = (RUN_MASK << ML_BITS); + for(; len >= 255 ; len -= 255) *op++ = 255; + *op++ = (BYTE)len; + } else { + *token = (BYTE)(length << ML_BITS); + } + + /* Copy Literals */ + LZ4_wildCopy8(op, anchor, op + length); + op += length; + + /* Encode Offset */ + assert( (ip - match) <= LZ4_DISTANCE_MAX ); /* note : consider providing offset as a value, rather than as a pointer difference */ + LZ4_writeLE16(op, (U16)(ip - match)); op += 2; + + /* Encode MatchLength */ + assert(matchLength >= MINMATCH); + length = (size_t)matchLength - MINMATCH; + if (limit && (op + (length / 255) + (1 + LASTLITERALS) > oend)) { + DEBUGLOG(6, "Not enough room to write match length"); + return 1; /* Check output limit */ + } + if (length >= ML_MASK) { + *token += ML_MASK; + length -= ML_MASK; + for(; length >= 510 ; length -= 510) { *op++ = 255; *op++ = 255; } + if (length >= 255) { length -= 255; *op++ = 255; } + *op++ = (BYTE)length; + } else { + *token += (BYTE)(length); + } + + /* Prepare next loop */ + ip += matchLength; + anchor = ip; + + return 0; +} +#undef ip +#undef op +#undef anchor + +LZ4_FORCE_INLINE int LZ4HC_compress_hashChain ( + LZ4HC_CCtx_internal* const ctx, + const char* const source, + char* const dest, + int* srcSizePtr, + int const maxOutputSize, + int maxNbAttempts, + const limitedOutput_directive limit, + const dictCtx_directive dict + ) +{ + const int inputSize = *srcSizePtr; + const int patternAnalysis = (maxNbAttempts > 128); /* levels 9+ */ + + const BYTE* ip = (const BYTE*) source; + const BYTE* anchor = ip; + const BYTE* const iend = ip + inputSize; + const BYTE* const mflimit = iend - MFLIMIT; + const BYTE* const matchlimit = (iend - LASTLITERALS); + + BYTE* optr = (BYTE*) dest; + BYTE* op = (BYTE*) dest; + BYTE* oend = op + maxOutputSize; + + int ml0, ml, ml2, ml3; + const BYTE* start0; + const BYTE* ref0; + const BYTE* ref = NULL; + const BYTE* start2 = NULL; + const BYTE* ref2 = NULL; + const BYTE* start3 = NULL; + const BYTE* ref3 = NULL; + + /* init */ + *srcSizePtr = 0; + if (limit == fillOutput) oend -= LASTLITERALS; /* Hack for support LZ4 format restriction */ + if (inputSize < LZ4_minLength) goto _last_literals; /* Input too small, no compression (all literals) */ + + /* Main Loop */ + while (ip <= mflimit) { + ml = LZ4HC_InsertAndFindBestMatch(ctx, ip, matchlimit, &ref, maxNbAttempts, patternAnalysis, dict); + if (ml encode ML1 */ + optr = op; + if (LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), ml, ref, limit, oend)) goto _dest_overflow; + continue; + } + + if (start0 < ip) { /* first match was skipped at least once */ + if (start2 < ip + ml0) { /* squeezing ML1 between ML0(original ML1) and ML2 */ + ip = start0; ref = ref0; ml = ml0; /* restore initial ML1 */ + } } + + /* Here, start0==ip */ + if ((start2 - ip) < 3) { /* First Match too small : removed */ + ml = ml2; + ip = start2; + ref =ref2; + goto _Search2; + } + +_Search3: + /* At this stage, we have : + * ml2 > ml1, and + * ip1+3 <= ip2 (usually < ip1+ml1) */ + if ((start2 - ip) < OPTIMAL_ML) { + int correction; + int new_ml = ml; + if (new_ml > OPTIMAL_ML) new_ml = OPTIMAL_ML; + if (ip+new_ml > start2 + ml2 - MINMATCH) new_ml = (int)(start2 - ip) + ml2 - MINMATCH; + correction = new_ml - (int)(start2 - ip); + if (correction > 0) { + start2 += correction; + ref2 += correction; + ml2 -= correction; + } + } + /* Now, we have start2 = ip+new_ml, with new_ml = min(ml, OPTIMAL_ML=18) */ + + if (start2 + ml2 <= mflimit) { + ml3 = LZ4HC_InsertAndGetWiderMatch(ctx, + start2 + ml2 - 3, start2, matchlimit, ml2, &ref3, &start3, + maxNbAttempts, patternAnalysis, 0, dict, favorCompressionRatio); + } else { + ml3 = ml2; + } + + if (ml3 == ml2) { /* No better match => encode ML1 and ML2 */ + /* ip & ref are known; Now for ml */ + if (start2 < ip+ml) ml = (int)(start2 - ip); + /* Now, encode 2 sequences */ + optr = op; + if (LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), ml, ref, limit, oend)) goto _dest_overflow; + ip = start2; + optr = op; + if (LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), ml2, ref2, limit, oend)) { + ml = ml2; + ref = ref2; + goto _dest_overflow; + } + continue; + } + + if (start3 < ip+ml+3) { /* Not enough space for match 2 : remove it */ + if (start3 >= (ip+ml)) { /* can write Seq1 immediately ==> Seq2 is removed, so Seq3 becomes Seq1 */ + if (start2 < ip+ml) { + int correction = (int)(ip+ml - start2); + start2 += correction; + ref2 += correction; + ml2 -= correction; + if (ml2 < MINMATCH) { + start2 = start3; + ref2 = ref3; + ml2 = ml3; + } + } + + optr = op; + if (LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), ml, ref, limit, oend)) goto _dest_overflow; + ip = start3; + ref = ref3; + ml = ml3; + + start0 = start2; + ref0 = ref2; + ml0 = ml2; + goto _Search2; + } + + start2 = start3; + ref2 = ref3; + ml2 = ml3; + goto _Search3; + } + + /* + * OK, now we have 3 ascending matches; + * let's write the first one ML1. + * ip & ref are known; Now decide ml. + */ + if (start2 < ip+ml) { + if ((start2 - ip) < OPTIMAL_ML) { + int correction; + if (ml > OPTIMAL_ML) ml = OPTIMAL_ML; + if (ip + ml > start2 + ml2 - MINMATCH) ml = (int)(start2 - ip) + ml2 - MINMATCH; + correction = ml - (int)(start2 - ip); + if (correction > 0) { + start2 += correction; + ref2 += correction; + ml2 -= correction; + } + } else { + ml = (int)(start2 - ip); + } + } + optr = op; + if (LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), ml, ref, limit, oend)) goto _dest_overflow; + + /* ML2 becomes ML1 */ + ip = start2; ref = ref2; ml = ml2; + + /* ML3 becomes ML2 */ + start2 = start3; ref2 = ref3; ml2 = ml3; + + /* let's find a new ML3 */ + goto _Search3; + } + +_last_literals: + /* Encode Last Literals */ + { size_t lastRunSize = (size_t)(iend - anchor); /* literals */ + size_t llAdd = (lastRunSize + 255 - RUN_MASK) / 255; + size_t const totalSize = 1 + llAdd + lastRunSize; + if (limit == fillOutput) oend += LASTLITERALS; /* restore correct value */ + if (limit && (op + totalSize > oend)) { + if (limit == limitedOutput) return 0; + /* adapt lastRunSize to fill 'dest' */ + lastRunSize = (size_t)(oend - op) - 1 /*token*/; + llAdd = (lastRunSize + 256 - RUN_MASK) / 256; + lastRunSize -= llAdd; + } + DEBUGLOG(6, "Final literal run : %i literals", (int)lastRunSize); + ip = anchor + lastRunSize; /* can be != iend if limit==fillOutput */ + + if (lastRunSize >= RUN_MASK) { + size_t accumulator = lastRunSize - RUN_MASK; + *op++ = (RUN_MASK << ML_BITS); + for(; accumulator >= 255 ; accumulator -= 255) *op++ = 255; + *op++ = (BYTE) accumulator; + } else { + *op++ = (BYTE)(lastRunSize << ML_BITS); + } + memcpy(op, anchor, lastRunSize); + op += lastRunSize; + } + + /* End */ + *srcSizePtr = (int) (((const char*)ip) - source); + return (int) (((char*)op)-dest); + +_dest_overflow: + if (limit == fillOutput) { + /* Assumption : ip, anchor, ml and ref must be set correctly */ + size_t const ll = (size_t)(ip - anchor); + size_t const ll_addbytes = (ll + 240) / 255; + size_t const ll_totalCost = 1 + ll_addbytes + ll; + BYTE* const maxLitPos = oend - 3; /* 2 for offset, 1 for token */ + DEBUGLOG(6, "Last sequence overflowing"); + op = optr; /* restore correct out pointer */ + if (op + ll_totalCost <= maxLitPos) { + /* ll validated; now adjust match length */ + size_t const bytesLeftForMl = (size_t)(maxLitPos - (op+ll_totalCost)); + size_t const maxMlSize = MINMATCH + (ML_MASK-1) + (bytesLeftForMl * 255); + assert(maxMlSize < INT_MAX); assert(ml >= 0); + if ((size_t)ml > maxMlSize) ml = (int)maxMlSize; + if ((oend + LASTLITERALS) - (op + ll_totalCost + 2) - 1 + ml >= MFLIMIT) { + LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), ml, ref, notLimited, oend); + } } + goto _last_literals; + } + /* compression failed */ + return 0; +} + + +static int LZ4HC_compress_optimal( LZ4HC_CCtx_internal* ctx, + const char* const source, char* dst, + int* srcSizePtr, int dstCapacity, + int const nbSearches, size_t sufficient_len, + const limitedOutput_directive limit, int const fullUpdate, + const dictCtx_directive dict, + const HCfavor_e favorDecSpeed); + + +LZ4_FORCE_INLINE int LZ4HC_compress_generic_internal ( + LZ4HC_CCtx_internal* const ctx, + const char* const src, + char* const dst, + int* const srcSizePtr, + int const dstCapacity, + int cLevel, + const limitedOutput_directive limit, + const dictCtx_directive dict + ) +{ + typedef enum { lz4hc, lz4opt } lz4hc_strat_e; + typedef struct { + lz4hc_strat_e strat; + int nbSearches; + U32 targetLength; + } cParams_t; + static const cParams_t clTable[LZ4HC_CLEVEL_MAX+1] = { + { lz4hc, 2, 16 }, /* 0, unused */ + { lz4hc, 2, 16 }, /* 1, unused */ + { lz4hc, 2, 16 }, /* 2, unused */ + { lz4hc, 4, 16 }, /* 3 */ + { lz4hc, 8, 16 }, /* 4 */ + { lz4hc, 16, 16 }, /* 5 */ + { lz4hc, 32, 16 }, /* 6 */ + { lz4hc, 64, 16 }, /* 7 */ + { lz4hc, 128, 16 }, /* 8 */ + { lz4hc, 256, 16 }, /* 9 */ + { lz4opt, 96, 64 }, /*10==LZ4HC_CLEVEL_OPT_MIN*/ + { lz4opt, 512,128 }, /*11 */ + { lz4opt,16384,LZ4_OPT_NUM }, /* 12==LZ4HC_CLEVEL_MAX */ + }; + + DEBUGLOG(4, "LZ4HC_compress_generic(ctx=%p, src=%p, srcSize=%d, limit=%d)", + ctx, src, *srcSizePtr, limit); + + if (limit == fillOutput && dstCapacity < 1) return 0; /* Impossible to store anything */ + if ((U32)*srcSizePtr > (U32)LZ4_MAX_INPUT_SIZE) return 0; /* Unsupported input size (too large or negative) */ + + ctx->end += *srcSizePtr; + if (cLevel < 1) cLevel = LZ4HC_CLEVEL_DEFAULT; /* note : convention is different from lz4frame, maybe something to review */ + cLevel = MIN(LZ4HC_CLEVEL_MAX, cLevel); + { cParams_t const cParam = clTable[cLevel]; + HCfavor_e const favor = ctx->favorDecSpeed ? favorDecompressionSpeed : favorCompressionRatio; + int result; + + if (cParam.strat == lz4hc) { + result = LZ4HC_compress_hashChain(ctx, + src, dst, srcSizePtr, dstCapacity, + cParam.nbSearches, limit, dict); + } else { + assert(cParam.strat == lz4opt); + result = LZ4HC_compress_optimal(ctx, + src, dst, srcSizePtr, dstCapacity, + cParam.nbSearches, cParam.targetLength, limit, + cLevel == LZ4HC_CLEVEL_MAX, /* ultra mode */ + dict, favor); + } + if (result <= 0) ctx->dirty = 1; + return result; + } +} + +static void LZ4HC_setExternalDict(LZ4HC_CCtx_internal* ctxPtr, const BYTE* newBlock); + +static int +LZ4HC_compress_generic_noDictCtx ( + LZ4HC_CCtx_internal* const ctx, + const char* const src, + char* const dst, + int* const srcSizePtr, + int const dstCapacity, + int cLevel, + limitedOutput_directive limit + ) +{ + assert(ctx->dictCtx == NULL); + return LZ4HC_compress_generic_internal(ctx, src, dst, srcSizePtr, dstCapacity, cLevel, limit, noDictCtx); +} + +static int +LZ4HC_compress_generic_dictCtx ( + LZ4HC_CCtx_internal* const ctx, + const char* const src, + char* const dst, + int* const srcSizePtr, + int const dstCapacity, + int cLevel, + limitedOutput_directive limit + ) +{ + const size_t position = (size_t)(ctx->end - ctx->base) - ctx->lowLimit; + assert(ctx->dictCtx != NULL); + if (position >= 64 KB) { + ctx->dictCtx = NULL; + return LZ4HC_compress_generic_noDictCtx(ctx, src, dst, srcSizePtr, dstCapacity, cLevel, limit); + } else if (position == 0 && *srcSizePtr > 4 KB) { + memcpy(ctx, ctx->dictCtx, sizeof(LZ4HC_CCtx_internal)); + LZ4HC_setExternalDict(ctx, (const BYTE *)src); + ctx->compressionLevel = (short)cLevel; + return LZ4HC_compress_generic_noDictCtx(ctx, src, dst, srcSizePtr, dstCapacity, cLevel, limit); + } else { + return LZ4HC_compress_generic_internal(ctx, src, dst, srcSizePtr, dstCapacity, cLevel, limit, usingDictCtxHc); + } +} + +static int +LZ4HC_compress_generic ( + LZ4HC_CCtx_internal* const ctx, + const char* const src, + char* const dst, + int* const srcSizePtr, + int const dstCapacity, + int cLevel, + limitedOutput_directive limit + ) +{ + if (ctx->dictCtx == NULL) { + return LZ4HC_compress_generic_noDictCtx(ctx, src, dst, srcSizePtr, dstCapacity, cLevel, limit); + } else { + return LZ4HC_compress_generic_dictCtx(ctx, src, dst, srcSizePtr, dstCapacity, cLevel, limit); + } +} + + +int LZ4_sizeofStateHC(void) { return (int)sizeof(LZ4_streamHC_t); } + +static size_t LZ4_streamHC_t_alignment(void) +{ +#if LZ4_ALIGN_TEST + typedef struct { char c; LZ4_streamHC_t t; } t_a; + return sizeof(t_a) - sizeof(LZ4_streamHC_t); +#else + return 1; /* effectively disabled */ +#endif +} + +/* state is presumed correctly initialized, + * in which case its size and alignment have already been validate */ +int LZ4_compress_HC_extStateHC_fastReset (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int compressionLevel) +{ + LZ4HC_CCtx_internal* const ctx = &((LZ4_streamHC_t*)state)->internal_donotuse; + if (!LZ4_isAligned(state, LZ4_streamHC_t_alignment())) return 0; + LZ4_resetStreamHC_fast((LZ4_streamHC_t*)state, compressionLevel); + LZ4HC_init_internal (ctx, (const BYTE*)src); + if (dstCapacity < LZ4_compressBound(srcSize)) + return LZ4HC_compress_generic (ctx, src, dst, &srcSize, dstCapacity, compressionLevel, limitedOutput); + else + return LZ4HC_compress_generic (ctx, src, dst, &srcSize, dstCapacity, compressionLevel, notLimited); +} + +int LZ4_compress_HC_extStateHC (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int compressionLevel) +{ + LZ4_streamHC_t* const ctx = LZ4_initStreamHC(state, sizeof(*ctx)); + if (ctx==NULL) return 0; /* init failure */ + return LZ4_compress_HC_extStateHC_fastReset(state, src, dst, srcSize, dstCapacity, compressionLevel); +} + +int LZ4_compress_HC(const char* src, char* dst, int srcSize, int dstCapacity, int compressionLevel) +{ + int cSize; +#if defined(LZ4HC_HEAPMODE) && LZ4HC_HEAPMODE==1 + LZ4_streamHC_t* const statePtr = (LZ4_streamHC_t*)ALLOC(sizeof(LZ4_streamHC_t)); + if (statePtr==NULL) return 0; +#else + LZ4_streamHC_t state; + LZ4_streamHC_t* const statePtr = &state; +#endif + cSize = LZ4_compress_HC_extStateHC(statePtr, src, dst, srcSize, dstCapacity, compressionLevel); +#if defined(LZ4HC_HEAPMODE) && LZ4HC_HEAPMODE==1 + FREEMEM(statePtr); +#endif + return cSize; +} + +/* state is presumed sized correctly (>= sizeof(LZ4_streamHC_t)) */ +int LZ4_compress_HC_destSize(void* state, const char* source, char* dest, int* sourceSizePtr, int targetDestSize, int cLevel) +{ + LZ4_streamHC_t* const ctx = LZ4_initStreamHC(state, sizeof(*ctx)); + if (ctx==NULL) return 0; /* init failure */ + LZ4HC_init_internal(&ctx->internal_donotuse, (const BYTE*) source); + LZ4_setCompressionLevel(ctx, cLevel); + return LZ4HC_compress_generic(&ctx->internal_donotuse, source, dest, sourceSizePtr, targetDestSize, cLevel, fillOutput); +} + + + +/************************************** +* Streaming Functions +**************************************/ +/* allocation */ +LZ4_streamHC_t* LZ4_createStreamHC(void) +{ + LZ4_streamHC_t* const state = + (LZ4_streamHC_t*)ALLOC_AND_ZERO(sizeof(LZ4_streamHC_t)); + if (state == NULL) return NULL; + LZ4_setCompressionLevel(state, LZ4HC_CLEVEL_DEFAULT); + return state; +} + +int LZ4_freeStreamHC (LZ4_streamHC_t* LZ4_streamHCPtr) +{ + DEBUGLOG(4, "LZ4_freeStreamHC(%p)", LZ4_streamHCPtr); + if (!LZ4_streamHCPtr) return 0; /* support free on NULL */ + FREEMEM(LZ4_streamHCPtr); + return 0; +} + + +LZ4_streamHC_t* LZ4_initStreamHC (void* buffer, size_t size) +{ + LZ4_streamHC_t* const LZ4_streamHCPtr = (LZ4_streamHC_t*)buffer; + /* if compilation fails here, LZ4_STREAMHCSIZE must be increased */ + LZ4_STATIC_ASSERT(sizeof(LZ4HC_CCtx_internal) <= LZ4_STREAMHCSIZE); + DEBUGLOG(4, "LZ4_initStreamHC(%p, %u)", buffer, (unsigned)size); + /* check conditions */ + if (buffer == NULL) return NULL; + if (size < sizeof(LZ4_streamHC_t)) return NULL; + if (!LZ4_isAligned(buffer, LZ4_streamHC_t_alignment())) return NULL; + /* init */ + { LZ4HC_CCtx_internal* const hcstate = &(LZ4_streamHCPtr->internal_donotuse); + MEM_INIT(hcstate, 0, sizeof(*hcstate)); } + LZ4_setCompressionLevel(LZ4_streamHCPtr, LZ4HC_CLEVEL_DEFAULT); + return LZ4_streamHCPtr; +} + +/* just a stub */ +void LZ4_resetStreamHC (LZ4_streamHC_t* LZ4_streamHCPtr, int compressionLevel) +{ + LZ4_initStreamHC(LZ4_streamHCPtr, sizeof(*LZ4_streamHCPtr)); + LZ4_setCompressionLevel(LZ4_streamHCPtr, compressionLevel); +} + +void LZ4_resetStreamHC_fast (LZ4_streamHC_t* LZ4_streamHCPtr, int compressionLevel) +{ + DEBUGLOG(4, "LZ4_resetStreamHC_fast(%p, %d)", LZ4_streamHCPtr, compressionLevel); + if (LZ4_streamHCPtr->internal_donotuse.dirty) { + LZ4_initStreamHC(LZ4_streamHCPtr, sizeof(*LZ4_streamHCPtr)); + } else { + /* preserve end - base : can trigger clearTable's threshold */ + if (LZ4_streamHCPtr->internal_donotuse.end != NULL) { + LZ4_streamHCPtr->internal_donotuse.end -= (uptrval)LZ4_streamHCPtr->internal_donotuse.base; + } else { + assert(LZ4_streamHCPtr->internal_donotuse.base == NULL); + } + LZ4_streamHCPtr->internal_donotuse.base = NULL; + LZ4_streamHCPtr->internal_donotuse.dictCtx = NULL; + } + LZ4_setCompressionLevel(LZ4_streamHCPtr, compressionLevel); +} + +void LZ4_setCompressionLevel(LZ4_streamHC_t* LZ4_streamHCPtr, int compressionLevel) +{ + DEBUGLOG(5, "LZ4_setCompressionLevel(%p, %d)", LZ4_streamHCPtr, compressionLevel); + if (compressionLevel < 1) compressionLevel = LZ4HC_CLEVEL_DEFAULT; + if (compressionLevel > LZ4HC_CLEVEL_MAX) compressionLevel = LZ4HC_CLEVEL_MAX; + LZ4_streamHCPtr->internal_donotuse.compressionLevel = (short)compressionLevel; +} + +void LZ4_favorDecompressionSpeed(LZ4_streamHC_t* LZ4_streamHCPtr, int favor) +{ + LZ4_streamHCPtr->internal_donotuse.favorDecSpeed = (favor!=0); +} + +/* LZ4_loadDictHC() : + * LZ4_streamHCPtr is presumed properly initialized */ +int LZ4_loadDictHC (LZ4_streamHC_t* LZ4_streamHCPtr, + const char* dictionary, int dictSize) +{ + LZ4HC_CCtx_internal* const ctxPtr = &LZ4_streamHCPtr->internal_donotuse; + DEBUGLOG(4, "LZ4_loadDictHC(ctx:%p, dict:%p, dictSize:%d)", LZ4_streamHCPtr, dictionary, dictSize); + assert(LZ4_streamHCPtr != NULL); + if (dictSize > 64 KB) { + dictionary += (size_t)dictSize - 64 KB; + dictSize = 64 KB; + } + /* need a full initialization, there are bad side-effects when using resetFast() */ + { int const cLevel = ctxPtr->compressionLevel; + LZ4_initStreamHC(LZ4_streamHCPtr, sizeof(*LZ4_streamHCPtr)); + LZ4_setCompressionLevel(LZ4_streamHCPtr, cLevel); + } + LZ4HC_init_internal (ctxPtr, (const BYTE*)dictionary); + ctxPtr->end = (const BYTE*)dictionary + dictSize; + if (dictSize >= 4) LZ4HC_Insert (ctxPtr, ctxPtr->end-3); + return dictSize; +} + +void LZ4_attach_HC_dictionary(LZ4_streamHC_t *working_stream, const LZ4_streamHC_t *dictionary_stream) { + working_stream->internal_donotuse.dictCtx = dictionary_stream != NULL ? &(dictionary_stream->internal_donotuse) : NULL; +} + +/* compression */ + +static void LZ4HC_setExternalDict(LZ4HC_CCtx_internal* ctxPtr, const BYTE* newBlock) +{ + DEBUGLOG(4, "LZ4HC_setExternalDict(%p, %p)", ctxPtr, newBlock); + if (ctxPtr->end >= ctxPtr->base + ctxPtr->dictLimit + 4) + LZ4HC_Insert (ctxPtr, ctxPtr->end-3); /* Referencing remaining dictionary content */ + + /* Only one memory segment for extDict, so any previous extDict is lost at this stage */ + ctxPtr->lowLimit = ctxPtr->dictLimit; + ctxPtr->dictLimit = (U32)(ctxPtr->end - ctxPtr->base); + ctxPtr->dictBase = ctxPtr->base; + ctxPtr->base = newBlock - ctxPtr->dictLimit; + ctxPtr->end = newBlock; + ctxPtr->nextToUpdate = ctxPtr->dictLimit; /* match referencing will resume from there */ + + /* cannot reference an extDict and a dictCtx at the same time */ + ctxPtr->dictCtx = NULL; +} + +static int +LZ4_compressHC_continue_generic (LZ4_streamHC_t* LZ4_streamHCPtr, + const char* src, char* dst, + int* srcSizePtr, int dstCapacity, + limitedOutput_directive limit) +{ + LZ4HC_CCtx_internal* const ctxPtr = &LZ4_streamHCPtr->internal_donotuse; + DEBUGLOG(5, "LZ4_compressHC_continue_generic(ctx=%p, src=%p, srcSize=%d, limit=%d)", + LZ4_streamHCPtr, src, *srcSizePtr, limit); + assert(ctxPtr != NULL); + /* auto-init if forgotten */ + if (ctxPtr->base == NULL) LZ4HC_init_internal (ctxPtr, (const BYTE*) src); + + /* Check overflow */ + if ((size_t)(ctxPtr->end - ctxPtr->base) > 2 GB) { + size_t dictSize = (size_t)(ctxPtr->end - ctxPtr->base) - ctxPtr->dictLimit; + if (dictSize > 64 KB) dictSize = 64 KB; + LZ4_loadDictHC(LZ4_streamHCPtr, (const char*)(ctxPtr->end) - dictSize, (int)dictSize); + } + + /* Check if blocks follow each other */ + if ((const BYTE*)src != ctxPtr->end) + LZ4HC_setExternalDict(ctxPtr, (const BYTE*)src); + + /* Check overlapping input/dictionary space */ + { const BYTE* sourceEnd = (const BYTE*) src + *srcSizePtr; + const BYTE* const dictBegin = ctxPtr->dictBase + ctxPtr->lowLimit; + const BYTE* const dictEnd = ctxPtr->dictBase + ctxPtr->dictLimit; + if ((sourceEnd > dictBegin) && ((const BYTE*)src < dictEnd)) { + if (sourceEnd > dictEnd) sourceEnd = dictEnd; + ctxPtr->lowLimit = (U32)(sourceEnd - ctxPtr->dictBase); + if (ctxPtr->dictLimit - ctxPtr->lowLimit < 4) ctxPtr->lowLimit = ctxPtr->dictLimit; + } } + + return LZ4HC_compress_generic (ctxPtr, src, dst, srcSizePtr, dstCapacity, ctxPtr->compressionLevel, limit); +} + +int LZ4_compress_HC_continue (LZ4_streamHC_t* LZ4_streamHCPtr, const char* src, char* dst, int srcSize, int dstCapacity) +{ + if (dstCapacity < LZ4_compressBound(srcSize)) + return LZ4_compressHC_continue_generic (LZ4_streamHCPtr, src, dst, &srcSize, dstCapacity, limitedOutput); + else + return LZ4_compressHC_continue_generic (LZ4_streamHCPtr, src, dst, &srcSize, dstCapacity, notLimited); +} + +int LZ4_compress_HC_continue_destSize (LZ4_streamHC_t* LZ4_streamHCPtr, const char* src, char* dst, int* srcSizePtr, int targetDestSize) +{ + return LZ4_compressHC_continue_generic(LZ4_streamHCPtr, src, dst, srcSizePtr, targetDestSize, fillOutput); +} + + + +/* LZ4_saveDictHC : + * save history content + * into a user-provided buffer + * which is then used to continue compression + */ +int LZ4_saveDictHC (LZ4_streamHC_t* LZ4_streamHCPtr, char* safeBuffer, int dictSize) +{ + LZ4HC_CCtx_internal* const streamPtr = &LZ4_streamHCPtr->internal_donotuse; + int const prefixSize = (int)(streamPtr->end - (streamPtr->base + streamPtr->dictLimit)); + DEBUGLOG(5, "LZ4_saveDictHC(%p, %p, %d)", LZ4_streamHCPtr, safeBuffer, dictSize); + assert(prefixSize >= 0); + if (dictSize > 64 KB) dictSize = 64 KB; + if (dictSize < 4) dictSize = 0; + if (dictSize > prefixSize) dictSize = prefixSize; + if (safeBuffer == NULL) assert(dictSize == 0); + if (dictSize > 0) + memmove(safeBuffer, streamPtr->end - dictSize, dictSize); + { U32 const endIndex = (U32)(streamPtr->end - streamPtr->base); + streamPtr->end = (const BYTE*)safeBuffer + dictSize; + streamPtr->base = streamPtr->end - endIndex; + streamPtr->dictLimit = endIndex - (U32)dictSize; + streamPtr->lowLimit = endIndex - (U32)dictSize; + if (streamPtr->nextToUpdate < streamPtr->dictLimit) + streamPtr->nextToUpdate = streamPtr->dictLimit; + } + return dictSize; +} + + +/*************************************************** +* Deprecated Functions +***************************************************/ + +/* These functions currently generate deprecation warnings */ + +/* Wrappers for deprecated compression functions */ +int LZ4_compressHC(const char* src, char* dst, int srcSize) { return LZ4_compress_HC (src, dst, srcSize, LZ4_compressBound(srcSize), 0); } +int LZ4_compressHC_limitedOutput(const char* src, char* dst, int srcSize, int maxDstSize) { return LZ4_compress_HC(src, dst, srcSize, maxDstSize, 0); } +int LZ4_compressHC2(const char* src, char* dst, int srcSize, int cLevel) { return LZ4_compress_HC (src, dst, srcSize, LZ4_compressBound(srcSize), cLevel); } +int LZ4_compressHC2_limitedOutput(const char* src, char* dst, int srcSize, int maxDstSize, int cLevel) { return LZ4_compress_HC(src, dst, srcSize, maxDstSize, cLevel); } +int LZ4_compressHC_withStateHC (void* state, const char* src, char* dst, int srcSize) { return LZ4_compress_HC_extStateHC (state, src, dst, srcSize, LZ4_compressBound(srcSize), 0); } +int LZ4_compressHC_limitedOutput_withStateHC (void* state, const char* src, char* dst, int srcSize, int maxDstSize) { return LZ4_compress_HC_extStateHC (state, src, dst, srcSize, maxDstSize, 0); } +int LZ4_compressHC2_withStateHC (void* state, const char* src, char* dst, int srcSize, int cLevel) { return LZ4_compress_HC_extStateHC(state, src, dst, srcSize, LZ4_compressBound(srcSize), cLevel); } +int LZ4_compressHC2_limitedOutput_withStateHC (void* state, const char* src, char* dst, int srcSize, int maxDstSize, int cLevel) { return LZ4_compress_HC_extStateHC(state, src, dst, srcSize, maxDstSize, cLevel); } +int LZ4_compressHC_continue (LZ4_streamHC_t* ctx, const char* src, char* dst, int srcSize) { return LZ4_compress_HC_continue (ctx, src, dst, srcSize, LZ4_compressBound(srcSize)); } +int LZ4_compressHC_limitedOutput_continue (LZ4_streamHC_t* ctx, const char* src, char* dst, int srcSize, int maxDstSize) { return LZ4_compress_HC_continue (ctx, src, dst, srcSize, maxDstSize); } + + +/* Deprecated streaming functions */ +int LZ4_sizeofStreamStateHC(void) { return LZ4_STREAMHCSIZE; } + +/* state is presumed correctly sized, aka >= sizeof(LZ4_streamHC_t) + * @return : 0 on success, !=0 if error */ +int LZ4_resetStreamStateHC(void* state, char* inputBuffer) +{ + LZ4_streamHC_t* const hc4 = LZ4_initStreamHC(state, sizeof(*hc4)); + if (hc4 == NULL) return 1; /* init failed */ + LZ4HC_init_internal (&hc4->internal_donotuse, (const BYTE*)inputBuffer); + return 0; +} + +void* LZ4_createHC (const char* inputBuffer) +{ + LZ4_streamHC_t* const hc4 = LZ4_createStreamHC(); + if (hc4 == NULL) return NULL; /* not enough memory */ + LZ4HC_init_internal (&hc4->internal_donotuse, (const BYTE*)inputBuffer); + return hc4; +} + +int LZ4_freeHC (void* LZ4HC_Data) +{ + if (!LZ4HC_Data) return 0; /* support free on NULL */ + FREEMEM(LZ4HC_Data); + return 0; +} + +int LZ4_compressHC2_continue (void* LZ4HC_Data, const char* src, char* dst, int srcSize, int cLevel) +{ + return LZ4HC_compress_generic (&((LZ4_streamHC_t*)LZ4HC_Data)->internal_donotuse, src, dst, &srcSize, 0, cLevel, notLimited); +} + +int LZ4_compressHC2_limitedOutput_continue (void* LZ4HC_Data, const char* src, char* dst, int srcSize, int dstCapacity, int cLevel) +{ + return LZ4HC_compress_generic (&((LZ4_streamHC_t*)LZ4HC_Data)->internal_donotuse, src, dst, &srcSize, dstCapacity, cLevel, limitedOutput); +} + +char* LZ4_slideInputBufferHC(void* LZ4HC_Data) +{ + LZ4_streamHC_t *ctx = (LZ4_streamHC_t*)LZ4HC_Data; + const BYTE *bufferStart = ctx->internal_donotuse.base + ctx->internal_donotuse.lowLimit; + LZ4_resetStreamHC_fast(ctx, ctx->internal_donotuse.compressionLevel); + /* avoid const char * -> char * conversion warning :( */ + return (char *)(uptrval)bufferStart; +} + + +/* ================================================ + * LZ4 Optimal parser (levels [LZ4HC_CLEVEL_OPT_MIN - LZ4HC_CLEVEL_MAX]) + * ===============================================*/ +typedef struct { + int price; + int off; + int mlen; + int litlen; +} LZ4HC_optimal_t; + +/* price in bytes */ +LZ4_FORCE_INLINE int LZ4HC_literalsPrice(int const litlen) +{ + int price = litlen; + assert(litlen >= 0); + if (litlen >= (int)RUN_MASK) + price += 1 + ((litlen-(int)RUN_MASK) / 255); + return price; +} + + +/* requires mlen >= MINMATCH */ +LZ4_FORCE_INLINE int LZ4HC_sequencePrice(int litlen, int mlen) +{ + int price = 1 + 2 ; /* token + 16-bit offset */ + assert(litlen >= 0); + assert(mlen >= MINMATCH); + + price += LZ4HC_literalsPrice(litlen); + + if (mlen >= (int)(ML_MASK+MINMATCH)) + price += 1 + ((mlen-(int)(ML_MASK+MINMATCH)) / 255); + + return price; +} + + +typedef struct { + int off; + int len; +} LZ4HC_match_t; + +LZ4_FORCE_INLINE LZ4HC_match_t +LZ4HC_FindLongerMatch(LZ4HC_CCtx_internal* const ctx, + const BYTE* ip, const BYTE* const iHighLimit, + int minLen, int nbSearches, + const dictCtx_directive dict, + const HCfavor_e favorDecSpeed) +{ + LZ4HC_match_t match = { 0 , 0 }; + const BYTE* matchPtr = NULL; + /* note : LZ4HC_InsertAndGetWiderMatch() is able to modify the starting position of a match (*startpos), + * but this won't be the case here, as we define iLowLimit==ip, + * so LZ4HC_InsertAndGetWiderMatch() won't be allowed to search past ip */ + int matchLength = LZ4HC_InsertAndGetWiderMatch(ctx, ip, ip, iHighLimit, minLen, &matchPtr, &ip, nbSearches, 1 /*patternAnalysis*/, 1 /*chainSwap*/, dict, favorDecSpeed); + if (matchLength <= minLen) return match; + if (favorDecSpeed) { + if ((matchLength>18) & (matchLength<=36)) matchLength=18; /* favor shortcut */ + } + match.len = matchLength; + match.off = (int)(ip-matchPtr); + return match; +} + + +static int LZ4HC_compress_optimal ( LZ4HC_CCtx_internal* ctx, + const char* const source, + char* dst, + int* srcSizePtr, + int dstCapacity, + int const nbSearches, + size_t sufficient_len, + const limitedOutput_directive limit, + int const fullUpdate, + const dictCtx_directive dict, + const HCfavor_e favorDecSpeed) +{ + int retval = 0; +#define TRAILING_LITERALS 3 +#if defined(LZ4HC_HEAPMODE) && LZ4HC_HEAPMODE==1 + LZ4HC_optimal_t* const opt = (LZ4HC_optimal_t*)ALLOC(sizeof(LZ4HC_optimal_t) * (LZ4_OPT_NUM + TRAILING_LITERALS)); +#else + LZ4HC_optimal_t opt[LZ4_OPT_NUM + TRAILING_LITERALS]; /* ~64 KB, which is a bit large for stack... */ +#endif + + const BYTE* ip = (const BYTE*) source; + const BYTE* anchor = ip; + const BYTE* const iend = ip + *srcSizePtr; + const BYTE* const mflimit = iend - MFLIMIT; + const BYTE* const matchlimit = iend - LASTLITERALS; + BYTE* op = (BYTE*) dst; + BYTE* opSaved = (BYTE*) dst; + BYTE* oend = op + dstCapacity; + int ovml = MINMATCH; /* overflow - last sequence */ + const BYTE* ovref = NULL; + + /* init */ +#if defined(LZ4HC_HEAPMODE) && LZ4HC_HEAPMODE==1 + if (opt == NULL) goto _return_label; +#endif + DEBUGLOG(5, "LZ4HC_compress_optimal(dst=%p, dstCapa=%u)", dst, (unsigned)dstCapacity); + *srcSizePtr = 0; + if (limit == fillOutput) oend -= LASTLITERALS; /* Hack for support LZ4 format restriction */ + if (sufficient_len >= LZ4_OPT_NUM) sufficient_len = LZ4_OPT_NUM-1; + + /* Main Loop */ + while (ip <= mflimit) { + int const llen = (int)(ip - anchor); + int best_mlen, best_off; + int cur, last_match_pos = 0; + + LZ4HC_match_t const firstMatch = LZ4HC_FindLongerMatch(ctx, ip, matchlimit, MINMATCH-1, nbSearches, dict, favorDecSpeed); + if (firstMatch.len==0) { ip++; continue; } + + if ((size_t)firstMatch.len > sufficient_len) { + /* good enough solution : immediate encoding */ + int const firstML = firstMatch.len; + const BYTE* const matchPos = ip - firstMatch.off; + opSaved = op; + if ( LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), firstML, matchPos, limit, oend) ) { /* updates ip, op and anchor */ + ovml = firstML; + ovref = matchPos; + goto _dest_overflow; + } + continue; + } + + /* set prices for first positions (literals) */ + { int rPos; + for (rPos = 0 ; rPos < MINMATCH ; rPos++) { + int const cost = LZ4HC_literalsPrice(llen + rPos); + opt[rPos].mlen = 1; + opt[rPos].off = 0; + opt[rPos].litlen = llen + rPos; + opt[rPos].price = cost; + DEBUGLOG(7, "rPos:%3i => price:%3i (litlen=%i) -- initial setup", + rPos, cost, opt[rPos].litlen); + } } + /* set prices using initial match */ + { int mlen = MINMATCH; + int const matchML = firstMatch.len; /* necessarily < sufficient_len < LZ4_OPT_NUM */ + int const offset = firstMatch.off; + assert(matchML < LZ4_OPT_NUM); + for ( ; mlen <= matchML ; mlen++) { + int const cost = LZ4HC_sequencePrice(llen, mlen); + opt[mlen].mlen = mlen; + opt[mlen].off = offset; + opt[mlen].litlen = llen; + opt[mlen].price = cost; + DEBUGLOG(7, "rPos:%3i => price:%3i (matchlen=%i) -- initial setup", + mlen, cost, mlen); + } } + last_match_pos = firstMatch.len; + { int addLit; + for (addLit = 1; addLit <= TRAILING_LITERALS; addLit ++) { + opt[last_match_pos+addLit].mlen = 1; /* literal */ + opt[last_match_pos+addLit].off = 0; + opt[last_match_pos+addLit].litlen = addLit; + opt[last_match_pos+addLit].price = opt[last_match_pos].price + LZ4HC_literalsPrice(addLit); + DEBUGLOG(7, "rPos:%3i => price:%3i (litlen=%i) -- initial setup", + last_match_pos+addLit, opt[last_match_pos+addLit].price, addLit); + } } + + /* check further positions */ + for (cur = 1; cur < last_match_pos; cur++) { + const BYTE* const curPtr = ip + cur; + LZ4HC_match_t newMatch; + + if (curPtr > mflimit) break; + DEBUGLOG(7, "rPos:%u[%u] vs [%u]%u", + cur, opt[cur].price, opt[cur+1].price, cur+1); + if (fullUpdate) { + /* not useful to search here if next position has same (or lower) cost */ + if ( (opt[cur+1].price <= opt[cur].price) + /* in some cases, next position has same cost, but cost rises sharply after, so a small match would still be beneficial */ + && (opt[cur+MINMATCH].price < opt[cur].price + 3/*min seq price*/) ) + continue; + } else { + /* not useful to search here if next position has same (or lower) cost */ + if (opt[cur+1].price <= opt[cur].price) continue; + } + + DEBUGLOG(7, "search at rPos:%u", cur); + if (fullUpdate) + newMatch = LZ4HC_FindLongerMatch(ctx, curPtr, matchlimit, MINMATCH-1, nbSearches, dict, favorDecSpeed); + else + /* only test matches of minimum length; slightly faster, but misses a few bytes */ + newMatch = LZ4HC_FindLongerMatch(ctx, curPtr, matchlimit, last_match_pos - cur, nbSearches, dict, favorDecSpeed); + if (!newMatch.len) continue; + + if ( ((size_t)newMatch.len > sufficient_len) + || (newMatch.len + cur >= LZ4_OPT_NUM) ) { + /* immediate encoding */ + best_mlen = newMatch.len; + best_off = newMatch.off; + last_match_pos = cur + 1; + goto encode; + } + + /* before match : set price with literals at beginning */ + { int const baseLitlen = opt[cur].litlen; + int litlen; + for (litlen = 1; litlen < MINMATCH; litlen++) { + int const price = opt[cur].price - LZ4HC_literalsPrice(baseLitlen) + LZ4HC_literalsPrice(baseLitlen+litlen); + int const pos = cur + litlen; + if (price < opt[pos].price) { + opt[pos].mlen = 1; /* literal */ + opt[pos].off = 0; + opt[pos].litlen = baseLitlen+litlen; + opt[pos].price = price; + DEBUGLOG(7, "rPos:%3i => price:%3i (litlen=%i)", + pos, price, opt[pos].litlen); + } } } + + /* set prices using match at position = cur */ + { int const matchML = newMatch.len; + int ml = MINMATCH; + + assert(cur + newMatch.len < LZ4_OPT_NUM); + for ( ; ml <= matchML ; ml++) { + int const pos = cur + ml; + int const offset = newMatch.off; + int price; + int ll; + DEBUGLOG(7, "testing price rPos %i (last_match_pos=%i)", + pos, last_match_pos); + if (opt[cur].mlen == 1) { + ll = opt[cur].litlen; + price = ((cur > ll) ? opt[cur - ll].price : 0) + + LZ4HC_sequencePrice(ll, ml); + } else { + ll = 0; + price = opt[cur].price + LZ4HC_sequencePrice(0, ml); + } + + assert((U32)favorDecSpeed <= 1); + if (pos > last_match_pos+TRAILING_LITERALS + || price <= opt[pos].price - (int)favorDecSpeed) { + DEBUGLOG(7, "rPos:%3i => price:%3i (matchlen=%i)", + pos, price, ml); + assert(pos < LZ4_OPT_NUM); + if ( (ml == matchML) /* last pos of last match */ + && (last_match_pos < pos) ) + last_match_pos = pos; + opt[pos].mlen = ml; + opt[pos].off = offset; + opt[pos].litlen = ll; + opt[pos].price = price; + } } } + /* complete following positions with literals */ + { int addLit; + for (addLit = 1; addLit <= TRAILING_LITERALS; addLit ++) { + opt[last_match_pos+addLit].mlen = 1; /* literal */ + opt[last_match_pos+addLit].off = 0; + opt[last_match_pos+addLit].litlen = addLit; + opt[last_match_pos+addLit].price = opt[last_match_pos].price + LZ4HC_literalsPrice(addLit); + DEBUGLOG(7, "rPos:%3i => price:%3i (litlen=%i)", last_match_pos+addLit, opt[last_match_pos+addLit].price, addLit); + } } + } /* for (cur = 1; cur <= last_match_pos; cur++) */ + + assert(last_match_pos < LZ4_OPT_NUM + TRAILING_LITERALS); + best_mlen = opt[last_match_pos].mlen; + best_off = opt[last_match_pos].off; + cur = last_match_pos - best_mlen; + +encode: /* cur, last_match_pos, best_mlen, best_off must be set */ + assert(cur < LZ4_OPT_NUM); + assert(last_match_pos >= 1); /* == 1 when only one candidate */ + DEBUGLOG(6, "reverse traversal, looking for shortest path (last_match_pos=%i)", last_match_pos); + { int candidate_pos = cur; + int selected_matchLength = best_mlen; + int selected_offset = best_off; + while (1) { /* from end to beginning */ + int const next_matchLength = opt[candidate_pos].mlen; /* can be 1, means literal */ + int const next_offset = opt[candidate_pos].off; + DEBUGLOG(7, "pos %i: sequence length %i", candidate_pos, selected_matchLength); + opt[candidate_pos].mlen = selected_matchLength; + opt[candidate_pos].off = selected_offset; + selected_matchLength = next_matchLength; + selected_offset = next_offset; + if (next_matchLength > candidate_pos) break; /* last match elected, first match to encode */ + assert(next_matchLength > 0); /* can be 1, means literal */ + candidate_pos -= next_matchLength; + } } + + /* encode all recorded sequences in order */ + { int rPos = 0; /* relative position (to ip) */ + while (rPos < last_match_pos) { + int const ml = opt[rPos].mlen; + int const offset = opt[rPos].off; + if (ml == 1) { ip++; rPos++; continue; } /* literal; note: can end up with several literals, in which case, skip them */ + rPos += ml; + assert(ml >= MINMATCH); + assert((offset >= 1) && (offset <= LZ4_DISTANCE_MAX)); + opSaved = op; + if ( LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), ml, ip - offset, limit, oend) ) { /* updates ip, op and anchor */ + ovml = ml; + ovref = ip - offset; + goto _dest_overflow; + } } } + } /* while (ip <= mflimit) */ + +_last_literals: + /* Encode Last Literals */ + { size_t lastRunSize = (size_t)(iend - anchor); /* literals */ + size_t llAdd = (lastRunSize + 255 - RUN_MASK) / 255; + size_t const totalSize = 1 + llAdd + lastRunSize; + if (limit == fillOutput) oend += LASTLITERALS; /* restore correct value */ + if (limit && (op + totalSize > oend)) { + if (limit == limitedOutput) { /* Check output limit */ + retval = 0; + goto _return_label; + } + /* adapt lastRunSize to fill 'dst' */ + lastRunSize = (size_t)(oend - op) - 1 /*token*/; + llAdd = (lastRunSize + 256 - RUN_MASK) / 256; + lastRunSize -= llAdd; + } + DEBUGLOG(6, "Final literal run : %i literals", (int)lastRunSize); + ip = anchor + lastRunSize; /* can be != iend if limit==fillOutput */ + + if (lastRunSize >= RUN_MASK) { + size_t accumulator = lastRunSize - RUN_MASK; + *op++ = (RUN_MASK << ML_BITS); + for(; accumulator >= 255 ; accumulator -= 255) *op++ = 255; + *op++ = (BYTE) accumulator; + } else { + *op++ = (BYTE)(lastRunSize << ML_BITS); + } + memcpy(op, anchor, lastRunSize); + op += lastRunSize; + } + + /* End */ + *srcSizePtr = (int) (((const char*)ip) - source); + retval = (int) ((char*)op-dst); + goto _return_label; + +_dest_overflow: +if (limit == fillOutput) { + /* Assumption : ip, anchor, ovml and ovref must be set correctly */ + size_t const ll = (size_t)(ip - anchor); + size_t const ll_addbytes = (ll + 240) / 255; + size_t const ll_totalCost = 1 + ll_addbytes + ll; + BYTE* const maxLitPos = oend - 3; /* 2 for offset, 1 for token */ + DEBUGLOG(6, "Last sequence overflowing (only %i bytes remaining)", (int)(oend-1-opSaved)); + op = opSaved; /* restore correct out pointer */ + if (op + ll_totalCost <= maxLitPos) { + /* ll validated; now adjust match length */ + size_t const bytesLeftForMl = (size_t)(maxLitPos - (op+ll_totalCost)); + size_t const maxMlSize = MINMATCH + (ML_MASK-1) + (bytesLeftForMl * 255); + assert(maxMlSize < INT_MAX); assert(ovml >= 0); + if ((size_t)ovml > maxMlSize) ovml = (int)maxMlSize; + if ((oend + LASTLITERALS) - (op + ll_totalCost + 2) - 1 + ovml >= MFLIMIT) { + DEBUGLOG(6, "Space to end : %i + ml (%i)", (int)((oend + LASTLITERALS) - (op + ll_totalCost + 2) - 1), ovml); + DEBUGLOG(6, "Before : ip = %p, anchor = %p", ip, anchor); + LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), ovml, ovref, notLimited, oend); + DEBUGLOG(6, "After : ip = %p, anchor = %p", ip, anchor); + } } + goto _last_literals; +} +_return_label: +#if defined(LZ4HC_HEAPMODE) && LZ4HC_HEAPMODE==1 + FREEMEM(opt); +#endif + return retval; +} diff --git a/libCompression/lz4hc.h b/libCompression/lz4hc.h new file mode 100644 index 0000000..b423990 --- /dev/null +++ b/libCompression/lz4hc.h @@ -0,0 +1,413 @@ +/* + LZ4 HC - High Compression Mode of LZ4 + Header File + Copyright (C) 2011-2020, Yann Collet. + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 source repository : https://github.com/lz4/lz4 + - LZ4 public forum : https://groups.google.com/forum/#!forum/lz4c +*/ +#ifndef LZ4_HC_H_19834876238432 +#define LZ4_HC_H_19834876238432 + +#if defined (__cplusplus) +extern "C" { +#endif + +/* --- Dependency --- */ +/* note : lz4hc requires lz4.h/lz4.c for compilation */ +#include "lz4.h" /* stddef, LZ4LIB_API, LZ4_DEPRECATED */ + + +/* --- Useful constants --- */ +#define LZ4HC_CLEVEL_MIN 3 +#define LZ4HC_CLEVEL_DEFAULT 9 +#define LZ4HC_CLEVEL_OPT_MIN 10 +#define LZ4HC_CLEVEL_MAX 12 + + +/*-************************************ + * Block Compression + **************************************/ +/*! LZ4_compress_HC() : + * Compress data from `src` into `dst`, using the powerful but slower "HC" algorithm. + * `dst` must be already allocated. + * Compression is guaranteed to succeed if `dstCapacity >= LZ4_compressBound(srcSize)` (see "lz4.h") + * Max supported `srcSize` value is LZ4_MAX_INPUT_SIZE (see "lz4.h") + * `compressionLevel` : any value between 1 and LZ4HC_CLEVEL_MAX will work. + * Values > LZ4HC_CLEVEL_MAX behave the same as LZ4HC_CLEVEL_MAX. + * @return : the number of bytes written into 'dst' + * or 0 if compression fails. + */ +LZ4LIB_API int LZ4_compress_HC (const char* src, char* dst, int srcSize, int dstCapacity, int compressionLevel); + + +/* Note : + * Decompression functions are provided within "lz4.h" (BSD license) + */ + + +/*! LZ4_compress_HC_extStateHC() : + * Same as LZ4_compress_HC(), but using an externally allocated memory segment for `state`. + * `state` size is provided by LZ4_sizeofStateHC(). + * Memory segment must be aligned on 8-bytes boundaries (which a normal malloc() should do properly). + */ +LZ4LIB_API int LZ4_sizeofStateHC(void); +LZ4LIB_API int LZ4_compress_HC_extStateHC(void* stateHC, const char* src, char* dst, int srcSize, int maxDstSize, int compressionLevel); + + +/*! LZ4_compress_HC_destSize() : v1.9.0+ + * Will compress as much data as possible from `src` + * to fit into `targetDstSize` budget. + * Result is provided in 2 parts : + * @return : the number of bytes written into 'dst' (necessarily <= targetDstSize) + * or 0 if compression fails. + * `srcSizePtr` : on success, *srcSizePtr is updated to indicate how much bytes were read from `src` + */ +LZ4LIB_API int LZ4_compress_HC_destSize(void* stateHC, + const char* src, char* dst, + int* srcSizePtr, int targetDstSize, + int compressionLevel); + + +/*-************************************ + * Streaming Compression + * Bufferless synchronous API + **************************************/ + typedef union LZ4_streamHC_u LZ4_streamHC_t; /* incomplete type (defined later) */ + +/*! LZ4_createStreamHC() and LZ4_freeStreamHC() : + * These functions create and release memory for LZ4 HC streaming state. + * Newly created states are automatically initialized. + * A same state can be used multiple times consecutively, + * starting with LZ4_resetStreamHC_fast() to start a new stream of blocks. + */ +LZ4LIB_API LZ4_streamHC_t* LZ4_createStreamHC(void); +LZ4LIB_API int LZ4_freeStreamHC (LZ4_streamHC_t* streamHCPtr); + +/* + These functions compress data in successive blocks of any size, + using previous blocks as dictionary, to improve compression ratio. + One key assumption is that previous blocks (up to 64 KB) remain read-accessible while compressing next blocks. + There is an exception for ring buffers, which can be smaller than 64 KB. + Ring-buffer scenario is automatically detected and handled within LZ4_compress_HC_continue(). + + Before starting compression, state must be allocated and properly initialized. + LZ4_createStreamHC() does both, though compression level is set to LZ4HC_CLEVEL_DEFAULT. + + Selecting the compression level can be done with LZ4_resetStreamHC_fast() (starts a new stream) + or LZ4_setCompressionLevel() (anytime, between blocks in the same stream) (experimental). + LZ4_resetStreamHC_fast() only works on states which have been properly initialized at least once, + which is automatically the case when state is created using LZ4_createStreamHC(). + + After reset, a first "fictional block" can be designated as initial dictionary, + using LZ4_loadDictHC() (Optional). + + Invoke LZ4_compress_HC_continue() to compress each successive block. + The number of blocks is unlimited. + Previous input blocks, including initial dictionary when present, + must remain accessible and unmodified during compression. + + It's allowed to update compression level anytime between blocks, + using LZ4_setCompressionLevel() (experimental). + + 'dst' buffer should be sized to handle worst case scenarios + (see LZ4_compressBound(), it ensures compression success). + In case of failure, the API does not guarantee recovery, + so the state _must_ be reset. + To ensure compression success + whenever `dst` buffer size cannot be made >= LZ4_compressBound(), + consider using LZ4_compress_HC_continue_destSize(). + + Whenever previous input blocks can't be preserved unmodified in-place during compression of next blocks, + it's possible to copy the last blocks into a more stable memory space, using LZ4_saveDictHC(). + Return value of LZ4_saveDictHC() is the size of dictionary effectively saved into 'safeBuffer' (<= 64 KB) + + After completing a streaming compression, + it's possible to start a new stream of blocks, using the same LZ4_streamHC_t state, + just by resetting it, using LZ4_resetStreamHC_fast(). +*/ + +LZ4LIB_API void LZ4_resetStreamHC_fast(LZ4_streamHC_t* streamHCPtr, int compressionLevel); /* v1.9.0+ */ +LZ4LIB_API int LZ4_loadDictHC (LZ4_streamHC_t* streamHCPtr, const char* dictionary, int dictSize); + +LZ4LIB_API int LZ4_compress_HC_continue (LZ4_streamHC_t* streamHCPtr, + const char* src, char* dst, + int srcSize, int maxDstSize); + +/*! LZ4_compress_HC_continue_destSize() : v1.9.0+ + * Similar to LZ4_compress_HC_continue(), + * but will read as much data as possible from `src` + * to fit into `targetDstSize` budget. + * Result is provided into 2 parts : + * @return : the number of bytes written into 'dst' (necessarily <= targetDstSize) + * or 0 if compression fails. + * `srcSizePtr` : on success, *srcSizePtr will be updated to indicate how much bytes were read from `src`. + * Note that this function may not consume the entire input. + */ +LZ4LIB_API int LZ4_compress_HC_continue_destSize(LZ4_streamHC_t* LZ4_streamHCPtr, + const char* src, char* dst, + int* srcSizePtr, int targetDstSize); + +LZ4LIB_API int LZ4_saveDictHC (LZ4_streamHC_t* streamHCPtr, char* safeBuffer, int maxDictSize); + + + +/*^********************************************** + * !!!!!! STATIC LINKING ONLY !!!!!! + ***********************************************/ + +/*-****************************************************************** + * PRIVATE DEFINITIONS : + * Do not use these definitions directly. + * They are merely exposed to allow static allocation of `LZ4_streamHC_t`. + * Declare an `LZ4_streamHC_t` directly, rather than any type below. + * Even then, only do so in the context of static linking, as definitions may change between versions. + ********************************************************************/ + +#define LZ4HC_DICTIONARY_LOGSIZE 16 +#define LZ4HC_MAXD (1<= LZ4HC_CLEVEL_OPT_MIN. + */ +LZ4LIB_STATIC_API void LZ4_favorDecompressionSpeed( + LZ4_streamHC_t* LZ4_streamHCPtr, int favor); + +/*! LZ4_resetStreamHC_fast() : v1.9.0+ + * When an LZ4_streamHC_t is known to be in a internally coherent state, + * it can often be prepared for a new compression with almost no work, only + * sometimes falling back to the full, expensive reset that is always required + * when the stream is in an indeterminate state (i.e., the reset performed by + * LZ4_resetStreamHC()). + * + * LZ4_streamHCs are guaranteed to be in a valid state when: + * - returned from LZ4_createStreamHC() + * - reset by LZ4_resetStreamHC() + * - memset(stream, 0, sizeof(LZ4_streamHC_t)) + * - the stream was in a valid state and was reset by LZ4_resetStreamHC_fast() + * - the stream was in a valid state and was then used in any compression call + * that returned success + * - the stream was in an indeterminate state and was used in a compression + * call that fully reset the state (LZ4_compress_HC_extStateHC()) and that + * returned success + * + * Note: + * A stream that was last used in a compression call that returned an error + * may be passed to this function. However, it will be fully reset, which will + * clear any existing history and settings from the context. + */ +LZ4LIB_STATIC_API void LZ4_resetStreamHC_fast( + LZ4_streamHC_t* LZ4_streamHCPtr, int compressionLevel); + +/*! LZ4_compress_HC_extStateHC_fastReset() : + * A variant of LZ4_compress_HC_extStateHC(). + * + * Using this variant avoids an expensive initialization step. It is only safe + * to call if the state buffer is known to be correctly initialized already + * (see above comment on LZ4_resetStreamHC_fast() for a definition of + * "correctly initialized"). From a high level, the difference is that this + * function initializes the provided state with a call to + * LZ4_resetStreamHC_fast() while LZ4_compress_HC_extStateHC() starts with a + * call to LZ4_resetStreamHC(). + */ +LZ4LIB_STATIC_API int LZ4_compress_HC_extStateHC_fastReset ( + void* state, + const char* src, char* dst, + int srcSize, int dstCapacity, + int compressionLevel); + +/*! LZ4_attach_HC_dictionary() : + * This is an experimental API that allows for the efficient use of a + * static dictionary many times. + * + * Rather than re-loading the dictionary buffer into a working context before + * each compression, or copying a pre-loaded dictionary's LZ4_streamHC_t into a + * working LZ4_streamHC_t, this function introduces a no-copy setup mechanism, + * in which the working stream references the dictionary stream in-place. + * + * Several assumptions are made about the state of the dictionary stream. + * Currently, only streams which have been prepared by LZ4_loadDictHC() should + * be expected to work. + * + * Alternatively, the provided dictionary stream pointer may be NULL, in which + * case any existing dictionary stream is unset. + * + * A dictionary should only be attached to a stream without any history (i.e., + * a stream that has just been reset). + * + * The dictionary will remain attached to the working stream only for the + * current stream session. Calls to LZ4_resetStreamHC(_fast) will remove the + * dictionary context association from the working stream. The dictionary + * stream (and source buffer) must remain in-place / accessible / unchanged + * through the lifetime of the stream session. + */ +LZ4LIB_STATIC_API void LZ4_attach_HC_dictionary( + LZ4_streamHC_t *working_stream, + const LZ4_streamHC_t *dictionary_stream); + +#if defined (__cplusplus) +} +#endif + +#endif /* LZ4_HC_SLO_098092834 */ +#endif /* LZ4_HC_STATIC_LINKING_ONLY */ diff --git a/libCompression/xxhash.c b/libCompression/xxhash.c new file mode 100644 index 0000000..ff28749 --- /dev/null +++ b/libCompression/xxhash.c @@ -0,0 +1,1030 @@ +/* +* xxHash - Fast Hash algorithm +* Copyright (C) 2012-2016, Yann Collet +* +* BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are +* met: +* +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above +* copyright notice, this list of conditions and the following disclaimer +* in the documentation and/or other materials provided with the +* distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +* You can contact the author at : +* - xxHash homepage: http://www.xxhash.com +* - xxHash source repository : https://github.com/Cyan4973/xxHash +*/ + + +/* ************************************* +* Tuning parameters +***************************************/ +/*!XXH_FORCE_MEMORY_ACCESS : + * By default, access to unaligned memory is controlled by `memcpy()`, which is safe and portable. + * Unfortunately, on some target/compiler combinations, the generated assembly is sub-optimal. + * The below switch allow to select different access method for improved performance. + * Method 0 (default) : use `memcpy()`. Safe and portable. + * Method 1 : `__packed` statement. It depends on compiler extension (ie, not portable). + * This method is safe if your compiler supports it, and *generally* as fast or faster than `memcpy`. + * Method 2 : direct access. This method doesn't depend on compiler but violate C standard. + * It can generate buggy code on targets which do not support unaligned memory accesses. + * But in some circumstances, it's the only known way to get the most performance (ie GCC + ARMv6) + * See http://stackoverflow.com/a/32095106/646947 for details. + * Prefer these methods in priority order (0 > 1 > 2) + */ +#ifndef XXH_FORCE_MEMORY_ACCESS /* can be defined externally, on command line for example */ +# if defined(__GNUC__) && ( defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) \ + || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) \ + || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) ) +# define XXH_FORCE_MEMORY_ACCESS 2 +# elif (defined(__INTEL_COMPILER) && !defined(_WIN32)) || \ + (defined(__GNUC__) && ( defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) \ + || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) \ + || defined(__ARM_ARCH_7S__) )) +# define XXH_FORCE_MEMORY_ACCESS 1 +# endif +#endif + +/*!XXH_ACCEPT_NULL_INPUT_POINTER : + * If input pointer is NULL, xxHash default behavior is to dereference it, triggering a segfault. + * When this macro is enabled, xxHash actively checks input for null pointer. + * It it is, result for null input pointers is the same as a null-length input. + */ +#ifndef XXH_ACCEPT_NULL_INPUT_POINTER /* can be defined externally */ +# define XXH_ACCEPT_NULL_INPUT_POINTER 0 +#endif + +/*!XXH_FORCE_NATIVE_FORMAT : + * By default, xxHash library provides endian-independent Hash values, based on little-endian convention. + * Results are therefore identical for little-endian and big-endian CPU. + * This comes at a performance cost for big-endian CPU, since some swapping is required to emulate little-endian format. + * Should endian-independence be of no importance for your application, you may set the #define below to 1, + * to improve speed for Big-endian CPU. + * This option has no impact on Little_Endian CPU. + */ +#ifndef XXH_FORCE_NATIVE_FORMAT /* can be defined externally */ +# define XXH_FORCE_NATIVE_FORMAT 0 +#endif + +/*!XXH_FORCE_ALIGN_CHECK : + * This is a minor performance trick, only useful with lots of very small keys. + * It means : check for aligned/unaligned input. + * The check costs one initial branch per hash; + * set it to 0 when the input is guaranteed to be aligned, + * or when alignment doesn't matter for performance. + */ +#ifndef XXH_FORCE_ALIGN_CHECK /* can be defined externally */ +# if defined(__i386) || defined(_M_IX86) || defined(__x86_64__) || defined(_M_X64) +# define XXH_FORCE_ALIGN_CHECK 0 +# else +# define XXH_FORCE_ALIGN_CHECK 1 +# endif +#endif + + +/* ************************************* +* Includes & Memory related functions +***************************************/ +/*! Modify the local functions below should you wish to use some other memory routines +* for malloc(), free() */ +#include +static void* XXH_malloc(size_t s) { return malloc(s); } +static void XXH_free (void* p) { free(p); } +/*! and for memcpy() */ +#include +static void* XXH_memcpy(void* dest, const void* src, size_t size) { return memcpy(dest,src,size); } + +#include /* assert */ + +#define XXH_STATIC_LINKING_ONLY +#include "xxhash.h" + + +/* ************************************* +* Compiler Specific Options +***************************************/ +#ifdef _MSC_VER /* Visual Studio */ +# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ +# define FORCE_INLINE static __forceinline +#else +# if defined (__cplusplus) || defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */ +# ifdef __GNUC__ +# define FORCE_INLINE static inline __attribute__((always_inline)) +# else +# define FORCE_INLINE static inline +# endif +# else +# define FORCE_INLINE static +# endif /* __STDC_VERSION__ */ +#endif + + +/* ************************************* +* Basic Types +***************************************/ +#ifndef MEM_MODULE +# if !defined (__VMS) \ + && (defined (__cplusplus) \ + || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) +# include + typedef uint8_t BYTE; + typedef uint16_t U16; + typedef uint32_t U32; +# else + typedef unsigned char BYTE; + typedef unsigned short U16; + typedef unsigned int U32; +# endif +#endif + +#if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==2)) + +/* Force direct memory access. Only works on CPU which support unaligned memory access in hardware */ +static U32 XXH_read32(const void* memPtr) { return *(const U32*) memPtr; } + +#elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==1)) + +/* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */ +/* currently only defined for gcc and icc */ +typedef union { U32 u32; } __attribute__((packed)) unalign; +static U32 XXH_read32(const void* ptr) { return ((const unalign*)ptr)->u32; } + +#else + +/* portable and safe solution. Generally efficient. + * see : http://stackoverflow.com/a/32095106/646947 + */ +static U32 XXH_read32(const void* memPtr) +{ + U32 val; + memcpy(&val, memPtr, sizeof(val)); + return val; +} + +#endif /* XXH_FORCE_DIRECT_MEMORY_ACCESS */ + + +/* **************************************** +* Compiler-specific Functions and Macros +******************************************/ +#define XXH_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) + +/* Note : although _rotl exists for minGW (GCC under windows), performance seems poor */ +#if defined(_MSC_VER) +# define XXH_rotl32(x,r) _rotl(x,r) +# define XXH_rotl64(x,r) _rotl64(x,r) +#else +# define XXH_rotl32(x,r) ((x << r) | (x >> (32 - r))) +# define XXH_rotl64(x,r) ((x << r) | (x >> (64 - r))) +#endif + +#if defined(_MSC_VER) /* Visual Studio */ +# define XXH_swap32 _byteswap_ulong +#elif XXH_GCC_VERSION >= 403 +# define XXH_swap32 __builtin_bswap32 +#else +static U32 XXH_swap32 (U32 x) +{ + return ((x << 24) & 0xff000000 ) | + ((x << 8) & 0x00ff0000 ) | + ((x >> 8) & 0x0000ff00 ) | + ((x >> 24) & 0x000000ff ); +} +#endif + + +/* ************************************* +* Architecture Macros +***************************************/ +typedef enum { XXH_bigEndian=0, XXH_littleEndian=1 } XXH_endianess; + +/* XXH_CPU_LITTLE_ENDIAN can be defined externally, for example on the compiler command line */ +#ifndef XXH_CPU_LITTLE_ENDIAN +static int XXH_isLittleEndian(void) +{ + const union { U32 u; BYTE c[4]; } one = { 1 }; /* don't use static : performance detrimental */ + return one.c[0]; +} +# define XXH_CPU_LITTLE_ENDIAN XXH_isLittleEndian() +#endif + + +/* *************************** +* Memory reads +*****************************/ +typedef enum { XXH_aligned, XXH_unaligned } XXH_alignment; + +FORCE_INLINE U32 XXH_readLE32_align(const void* ptr, XXH_endianess endian, XXH_alignment align) +{ + if (align==XXH_unaligned) + return endian==XXH_littleEndian ? XXH_read32(ptr) : XXH_swap32(XXH_read32(ptr)); + else + return endian==XXH_littleEndian ? *(const U32*)ptr : XXH_swap32(*(const U32*)ptr); +} + +FORCE_INLINE U32 XXH_readLE32(const void* ptr, XXH_endianess endian) +{ + return XXH_readLE32_align(ptr, endian, XXH_unaligned); +} + +static U32 XXH_readBE32(const void* ptr) +{ + return XXH_CPU_LITTLE_ENDIAN ? XXH_swap32(XXH_read32(ptr)) : XXH_read32(ptr); +} + + +/* ************************************* +* Macros +***************************************/ +#define XXH_STATIC_ASSERT(c) { enum { XXH_sa = 1/(int)(!!(c)) }; } /* use after variable declarations */ +XXH_PUBLIC_API unsigned XXH_versionNumber (void) { return XXH_VERSION_NUMBER; } + + +/* ******************************************************************* +* 32-bit hash functions +*********************************************************************/ +static const U32 PRIME32_1 = 2654435761U; +static const U32 PRIME32_2 = 2246822519U; +static const U32 PRIME32_3 = 3266489917U; +static const U32 PRIME32_4 = 668265263U; +static const U32 PRIME32_5 = 374761393U; + +static U32 XXH32_round(U32 seed, U32 input) +{ + seed += input * PRIME32_2; + seed = XXH_rotl32(seed, 13); + seed *= PRIME32_1; + return seed; +} + +/* mix all bits */ +static U32 XXH32_avalanche(U32 h32) +{ + h32 ^= h32 >> 15; + h32 *= PRIME32_2; + h32 ^= h32 >> 13; + h32 *= PRIME32_3; + h32 ^= h32 >> 16; + return(h32); +} + +#define XXH_get32bits(p) XXH_readLE32_align(p, endian, align) + +static U32 +XXH32_finalize(U32 h32, const void* ptr, size_t len, + XXH_endianess endian, XXH_alignment align) + +{ + const BYTE* p = (const BYTE*)ptr; + +#define PROCESS1 \ + h32 += (*p++) * PRIME32_5; \ + h32 = XXH_rotl32(h32, 11) * PRIME32_1 ; + +#define PROCESS4 \ + h32 += XXH_get32bits(p) * PRIME32_3; \ + p+=4; \ + h32 = XXH_rotl32(h32, 17) * PRIME32_4 ; + + switch(len&15) /* or switch(bEnd - p) */ + { + case 12: PROCESS4; + /* fallthrough */ + case 8: PROCESS4; + /* fallthrough */ + case 4: PROCESS4; + return XXH32_avalanche(h32); + + case 13: PROCESS4; + /* fallthrough */ + case 9: PROCESS4; + /* fallthrough */ + case 5: PROCESS4; + PROCESS1; + return XXH32_avalanche(h32); + + case 14: PROCESS4; + /* fallthrough */ + case 10: PROCESS4; + /* fallthrough */ + case 6: PROCESS4; + PROCESS1; + PROCESS1; + return XXH32_avalanche(h32); + + case 15: PROCESS4; + /* fallthrough */ + case 11: PROCESS4; + /* fallthrough */ + case 7: PROCESS4; + /* fallthrough */ + case 3: PROCESS1; + /* fallthrough */ + case 2: PROCESS1; + /* fallthrough */ + case 1: PROCESS1; + /* fallthrough */ + case 0: return XXH32_avalanche(h32); + } + assert(0); + return h32; /* reaching this point is deemed impossible */ +} + + +FORCE_INLINE U32 +XXH32_endian_align(const void* input, size_t len, U32 seed, + XXH_endianess endian, XXH_alignment align) +{ + const BYTE* p = (const BYTE*)input; + const BYTE* bEnd = p + len; + U32 h32; + +#if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1) + if (p==NULL) { + len=0; + bEnd=p=(const BYTE*)(size_t)16; + } +#endif + + if (len>=16) { + const BYTE* const limit = bEnd - 15; + U32 v1 = seed + PRIME32_1 + PRIME32_2; + U32 v2 = seed + PRIME32_2; + U32 v3 = seed + 0; + U32 v4 = seed - PRIME32_1; + + do { + v1 = XXH32_round(v1, XXH_get32bits(p)); p+=4; + v2 = XXH32_round(v2, XXH_get32bits(p)); p+=4; + v3 = XXH32_round(v3, XXH_get32bits(p)); p+=4; + v4 = XXH32_round(v4, XXH_get32bits(p)); p+=4; + } while (p < limit); + + h32 = XXH_rotl32(v1, 1) + XXH_rotl32(v2, 7) + + XXH_rotl32(v3, 12) + XXH_rotl32(v4, 18); + } else { + h32 = seed + PRIME32_5; + } + + h32 += (U32)len; + + return XXH32_finalize(h32, p, len&15, endian, align); +} + + +XXH_PUBLIC_API unsigned int XXH32 (const void* input, size_t len, unsigned int seed) +{ +#if 0 + /* Simple version, good for code maintenance, but unfortunately slow for small inputs */ + XXH32_state_t state; + XXH32_reset(&state, seed); + XXH32_update(&state, input, len); + return XXH32_digest(&state); +#else + XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; + + if (XXH_FORCE_ALIGN_CHECK) { + if ((((size_t)input) & 3) == 0) { /* Input is 4-bytes aligned, leverage the speed benefit */ + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH32_endian_align(input, len, seed, XXH_littleEndian, XXH_aligned); + else + return XXH32_endian_align(input, len, seed, XXH_bigEndian, XXH_aligned); + } } + + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH32_endian_align(input, len, seed, XXH_littleEndian, XXH_unaligned); + else + return XXH32_endian_align(input, len, seed, XXH_bigEndian, XXH_unaligned); +#endif +} + + + +/*====== Hash streaming ======*/ + +XXH_PUBLIC_API XXH32_state_t* XXH32_createState(void) +{ + return (XXH32_state_t*)XXH_malloc(sizeof(XXH32_state_t)); +} +XXH_PUBLIC_API XXH_errorcode XXH32_freeState(XXH32_state_t* statePtr) +{ + XXH_free(statePtr); + return XXH_OK; +} + +XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* dstState, const XXH32_state_t* srcState) +{ + memcpy(dstState, srcState, sizeof(*dstState)); +} + +XXH_PUBLIC_API XXH_errorcode XXH32_reset(XXH32_state_t* statePtr, unsigned int seed) +{ + XXH32_state_t state; /* using a local state to memcpy() in order to avoid strict-aliasing warnings */ + memset(&state, 0, sizeof(state)); + state.v1 = seed + PRIME32_1 + PRIME32_2; + state.v2 = seed + PRIME32_2; + state.v3 = seed + 0; + state.v4 = seed - PRIME32_1; + /* do not write into reserved, planned to be removed in a future version */ + memcpy(statePtr, &state, sizeof(state) - sizeof(state.reserved)); + return XXH_OK; +} + + +FORCE_INLINE XXH_errorcode +XXH32_update_endian(XXH32_state_t* state, const void* input, size_t len, XXH_endianess endian) +{ + if (input==NULL) +#if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1) + return XXH_OK; +#else + return XXH_ERROR; +#endif + + { const BYTE* p = (const BYTE*)input; + const BYTE* const bEnd = p + len; + + state->total_len_32 += (unsigned)len; + state->large_len |= (len>=16) | (state->total_len_32>=16); + + if (state->memsize + len < 16) { /* fill in tmp buffer */ + XXH_memcpy((BYTE*)(state->mem32) + state->memsize, input, len); + state->memsize += (unsigned)len; + return XXH_OK; + } + + if (state->memsize) { /* some data left from previous update */ + XXH_memcpy((BYTE*)(state->mem32) + state->memsize, input, 16-state->memsize); + { const U32* p32 = state->mem32; + state->v1 = XXH32_round(state->v1, XXH_readLE32(p32, endian)); p32++; + state->v2 = XXH32_round(state->v2, XXH_readLE32(p32, endian)); p32++; + state->v3 = XXH32_round(state->v3, XXH_readLE32(p32, endian)); p32++; + state->v4 = XXH32_round(state->v4, XXH_readLE32(p32, endian)); + } + p += 16-state->memsize; + state->memsize = 0; + } + + if (p <= bEnd-16) { + const BYTE* const limit = bEnd - 16; + U32 v1 = state->v1; + U32 v2 = state->v2; + U32 v3 = state->v3; + U32 v4 = state->v4; + + do { + v1 = XXH32_round(v1, XXH_readLE32(p, endian)); p+=4; + v2 = XXH32_round(v2, XXH_readLE32(p, endian)); p+=4; + v3 = XXH32_round(v3, XXH_readLE32(p, endian)); p+=4; + v4 = XXH32_round(v4, XXH_readLE32(p, endian)); p+=4; + } while (p<=limit); + + state->v1 = v1; + state->v2 = v2; + state->v3 = v3; + state->v4 = v4; + } + + if (p < bEnd) { + XXH_memcpy(state->mem32, p, (size_t)(bEnd-p)); + state->memsize = (unsigned)(bEnd-p); + } + } + + return XXH_OK; +} + + +XXH_PUBLIC_API XXH_errorcode XXH32_update (XXH32_state_t* state_in, const void* input, size_t len) +{ + XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; + + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH32_update_endian(state_in, input, len, XXH_littleEndian); + else + return XXH32_update_endian(state_in, input, len, XXH_bigEndian); +} + + +FORCE_INLINE U32 +XXH32_digest_endian (const XXH32_state_t* state, XXH_endianess endian) +{ + U32 h32; + + if (state->large_len) { + h32 = XXH_rotl32(state->v1, 1) + + XXH_rotl32(state->v2, 7) + + XXH_rotl32(state->v3, 12) + + XXH_rotl32(state->v4, 18); + } else { + h32 = state->v3 /* == seed */ + PRIME32_5; + } + + h32 += state->total_len_32; + + return XXH32_finalize(h32, state->mem32, state->memsize, endian, XXH_aligned); +} + + +XXH_PUBLIC_API unsigned int XXH32_digest (const XXH32_state_t* state_in) +{ + XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; + + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH32_digest_endian(state_in, XXH_littleEndian); + else + return XXH32_digest_endian(state_in, XXH_bigEndian); +} + + +/*====== Canonical representation ======*/ + +/*! Default XXH result types are basic unsigned 32 and 64 bits. +* The canonical representation follows human-readable write convention, aka big-endian (large digits first). +* These functions allow transformation of hash result into and from its canonical format. +* This way, hash values can be written into a file or buffer, remaining comparable across different systems. +*/ + +XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash) +{ + XXH_STATIC_ASSERT(sizeof(XXH32_canonical_t) == sizeof(XXH32_hash_t)); + if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap32(hash); + memcpy(dst, &hash, sizeof(*dst)); +} + +XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src) +{ + return XXH_readBE32(src); +} + + +#ifndef XXH_NO_LONG_LONG + +/* ******************************************************************* +* 64-bit hash functions +*********************************************************************/ + +/*====== Memory access ======*/ + +#ifndef MEM_MODULE +# define MEM_MODULE +# if !defined (__VMS) \ + && (defined (__cplusplus) \ + || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) +# include + typedef uint64_t U64; +# else + /* if compiler doesn't support unsigned long long, replace by another 64-bit type */ + typedef unsigned long long U64; +# endif +#endif + + +#if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==2)) + +/* Force direct memory access. Only works on CPU which support unaligned memory access in hardware */ +static U64 XXH_read64(const void* memPtr) { return *(const U64*) memPtr; } + +#elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==1)) + +/* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */ +/* currently only defined for gcc and icc */ +typedef union { U32 u32; U64 u64; } __attribute__((packed)) unalign64; +static U64 XXH_read64(const void* ptr) { return ((const unalign64*)ptr)->u64; } + +#else + +/* portable and safe solution. Generally efficient. + * see : http://stackoverflow.com/a/32095106/646947 + */ + +static U64 XXH_read64(const void* memPtr) +{ + U64 val; + memcpy(&val, memPtr, sizeof(val)); + return val; +} + +#endif /* XXH_FORCE_DIRECT_MEMORY_ACCESS */ + +#if defined(_MSC_VER) /* Visual Studio */ +# define XXH_swap64 _byteswap_uint64 +#elif XXH_GCC_VERSION >= 403 +# define XXH_swap64 __builtin_bswap64 +#else +static U64 XXH_swap64 (U64 x) +{ + return ((x << 56) & 0xff00000000000000ULL) | + ((x << 40) & 0x00ff000000000000ULL) | + ((x << 24) & 0x0000ff0000000000ULL) | + ((x << 8) & 0x000000ff00000000ULL) | + ((x >> 8) & 0x00000000ff000000ULL) | + ((x >> 24) & 0x0000000000ff0000ULL) | + ((x >> 40) & 0x000000000000ff00ULL) | + ((x >> 56) & 0x00000000000000ffULL); +} +#endif + +FORCE_INLINE U64 XXH_readLE64_align(const void* ptr, XXH_endianess endian, XXH_alignment align) +{ + if (align==XXH_unaligned) + return endian==XXH_littleEndian ? XXH_read64(ptr) : XXH_swap64(XXH_read64(ptr)); + else + return endian==XXH_littleEndian ? *(const U64*)ptr : XXH_swap64(*(const U64*)ptr); +} + +FORCE_INLINE U64 XXH_readLE64(const void* ptr, XXH_endianess endian) +{ + return XXH_readLE64_align(ptr, endian, XXH_unaligned); +} + +static U64 XXH_readBE64(const void* ptr) +{ + return XXH_CPU_LITTLE_ENDIAN ? XXH_swap64(XXH_read64(ptr)) : XXH_read64(ptr); +} + + +/*====== xxh64 ======*/ + +static const U64 PRIME64_1 = 11400714785074694791ULL; +static const U64 PRIME64_2 = 14029467366897019727ULL; +static const U64 PRIME64_3 = 1609587929392839161ULL; +static const U64 PRIME64_4 = 9650029242287828579ULL; +static const U64 PRIME64_5 = 2870177450012600261ULL; + +static U64 XXH64_round(U64 acc, U64 input) +{ + acc += input * PRIME64_2; + acc = XXH_rotl64(acc, 31); + acc *= PRIME64_1; + return acc; +} + +static U64 XXH64_mergeRound(U64 acc, U64 val) +{ + val = XXH64_round(0, val); + acc ^= val; + acc = acc * PRIME64_1 + PRIME64_4; + return acc; +} + +static U64 XXH64_avalanche(U64 h64) +{ + h64 ^= h64 >> 33; + h64 *= PRIME64_2; + h64 ^= h64 >> 29; + h64 *= PRIME64_3; + h64 ^= h64 >> 32; + return h64; +} + + +#define XXH_get64bits(p) XXH_readLE64_align(p, endian, align) + +static U64 +XXH64_finalize(U64 h64, const void* ptr, size_t len, + XXH_endianess endian, XXH_alignment align) +{ + const BYTE* p = (const BYTE*)ptr; + +#define PROCESS1_64 \ + h64 ^= (*p++) * PRIME64_5; \ + h64 = XXH_rotl64(h64, 11) * PRIME64_1; + +#define PROCESS4_64 \ + h64 ^= (U64)(XXH_get32bits(p)) * PRIME64_1; \ + p+=4; \ + h64 = XXH_rotl64(h64, 23) * PRIME64_2 + PRIME64_3; + +#define PROCESS8_64 { \ + U64 const k1 = XXH64_round(0, XXH_get64bits(p)); \ + p+=8; \ + h64 ^= k1; \ + h64 = XXH_rotl64(h64,27) * PRIME64_1 + PRIME64_4; \ +} + + switch(len&31) { + case 24: PROCESS8_64; + /* fallthrough */ + case 16: PROCESS8_64; + /* fallthrough */ + case 8: PROCESS8_64; + return XXH64_avalanche(h64); + + case 28: PROCESS8_64; + /* fallthrough */ + case 20: PROCESS8_64; + /* fallthrough */ + case 12: PROCESS8_64; + /* fallthrough */ + case 4: PROCESS4_64; + return XXH64_avalanche(h64); + + case 25: PROCESS8_64; + /* fallthrough */ + case 17: PROCESS8_64; + /* fallthrough */ + case 9: PROCESS8_64; + PROCESS1_64; + return XXH64_avalanche(h64); + + case 29: PROCESS8_64; + /* fallthrough */ + case 21: PROCESS8_64; + /* fallthrough */ + case 13: PROCESS8_64; + /* fallthrough */ + case 5: PROCESS4_64; + PROCESS1_64; + return XXH64_avalanche(h64); + + case 26: PROCESS8_64; + /* fallthrough */ + case 18: PROCESS8_64; + /* fallthrough */ + case 10: PROCESS8_64; + PROCESS1_64; + PROCESS1_64; + return XXH64_avalanche(h64); + + case 30: PROCESS8_64; + /* fallthrough */ + case 22: PROCESS8_64; + /* fallthrough */ + case 14: PROCESS8_64; + /* fallthrough */ + case 6: PROCESS4_64; + PROCESS1_64; + PROCESS1_64; + return XXH64_avalanche(h64); + + case 27: PROCESS8_64; + /* fallthrough */ + case 19: PROCESS8_64; + /* fallthrough */ + case 11: PROCESS8_64; + PROCESS1_64; + PROCESS1_64; + PROCESS1_64; + return XXH64_avalanche(h64); + + case 31: PROCESS8_64; + /* fallthrough */ + case 23: PROCESS8_64; + /* fallthrough */ + case 15: PROCESS8_64; + /* fallthrough */ + case 7: PROCESS4_64; + /* fallthrough */ + case 3: PROCESS1_64; + /* fallthrough */ + case 2: PROCESS1_64; + /* fallthrough */ + case 1: PROCESS1_64; + /* fallthrough */ + case 0: return XXH64_avalanche(h64); + } + + /* impossible to reach */ + assert(0); + return 0; /* unreachable, but some compilers complain without it */ +} + +FORCE_INLINE U64 +XXH64_endian_align(const void* input, size_t len, U64 seed, + XXH_endianess endian, XXH_alignment align) +{ + const BYTE* p = (const BYTE*)input; + const BYTE* bEnd = p + len; + U64 h64; + +#if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1) + if (p==NULL) { + len=0; + bEnd=p=(const BYTE*)(size_t)32; + } +#endif + + if (len>=32) { + const BYTE* const limit = bEnd - 32; + U64 v1 = seed + PRIME64_1 + PRIME64_2; + U64 v2 = seed + PRIME64_2; + U64 v3 = seed + 0; + U64 v4 = seed - PRIME64_1; + + do { + v1 = XXH64_round(v1, XXH_get64bits(p)); p+=8; + v2 = XXH64_round(v2, XXH_get64bits(p)); p+=8; + v3 = XXH64_round(v3, XXH_get64bits(p)); p+=8; + v4 = XXH64_round(v4, XXH_get64bits(p)); p+=8; + } while (p<=limit); + + h64 = XXH_rotl64(v1, 1) + XXH_rotl64(v2, 7) + XXH_rotl64(v3, 12) + XXH_rotl64(v4, 18); + h64 = XXH64_mergeRound(h64, v1); + h64 = XXH64_mergeRound(h64, v2); + h64 = XXH64_mergeRound(h64, v3); + h64 = XXH64_mergeRound(h64, v4); + + } else { + h64 = seed + PRIME64_5; + } + + h64 += (U64) len; + + return XXH64_finalize(h64, p, len, endian, align); +} + + +XXH_PUBLIC_API unsigned long long XXH64 (const void* input, size_t len, unsigned long long seed) +{ +#if 0 + /* Simple version, good for code maintenance, but unfortunately slow for small inputs */ + XXH64_state_t state; + XXH64_reset(&state, seed); + XXH64_update(&state, input, len); + return XXH64_digest(&state); +#else + XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; + + if (XXH_FORCE_ALIGN_CHECK) { + if ((((size_t)input) & 7)==0) { /* Input is aligned, let's leverage the speed advantage */ + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH64_endian_align(input, len, seed, XXH_littleEndian, XXH_aligned); + else + return XXH64_endian_align(input, len, seed, XXH_bigEndian, XXH_aligned); + } } + + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH64_endian_align(input, len, seed, XXH_littleEndian, XXH_unaligned); + else + return XXH64_endian_align(input, len, seed, XXH_bigEndian, XXH_unaligned); +#endif +} + +/*====== Hash Streaming ======*/ + +XXH_PUBLIC_API XXH64_state_t* XXH64_createState(void) +{ + return (XXH64_state_t*)XXH_malloc(sizeof(XXH64_state_t)); +} +XXH_PUBLIC_API XXH_errorcode XXH64_freeState(XXH64_state_t* statePtr) +{ + XXH_free(statePtr); + return XXH_OK; +} + +XXH_PUBLIC_API void XXH64_copyState(XXH64_state_t* dstState, const XXH64_state_t* srcState) +{ + memcpy(dstState, srcState, sizeof(*dstState)); +} + +XXH_PUBLIC_API XXH_errorcode XXH64_reset(XXH64_state_t* statePtr, unsigned long long seed) +{ + XXH64_state_t state; /* using a local state to memcpy() in order to avoid strict-aliasing warnings */ + memset(&state, 0, sizeof(state)); + state.v1 = seed + PRIME64_1 + PRIME64_2; + state.v2 = seed + PRIME64_2; + state.v3 = seed + 0; + state.v4 = seed - PRIME64_1; + /* do not write into reserved, planned to be removed in a future version */ + memcpy(statePtr, &state, sizeof(state) - sizeof(state.reserved)); + return XXH_OK; +} + +FORCE_INLINE XXH_errorcode +XXH64_update_endian (XXH64_state_t* state, const void* input, size_t len, XXH_endianess endian) +{ + if (input==NULL) +#if defined(XXH_ACCEPT_NULL_INPUT_POINTER) && (XXH_ACCEPT_NULL_INPUT_POINTER>=1) + return XXH_OK; +#else + return XXH_ERROR; +#endif + + { const BYTE* p = (const BYTE*)input; + const BYTE* const bEnd = p + len; + + state->total_len += len; + + if (state->memsize + len < 32) { /* fill in tmp buffer */ + XXH_memcpy(((BYTE*)state->mem64) + state->memsize, input, len); + state->memsize += (U32)len; + return XXH_OK; + } + + if (state->memsize) { /* tmp buffer is full */ + XXH_memcpy(((BYTE*)state->mem64) + state->memsize, input, 32-state->memsize); + state->v1 = XXH64_round(state->v1, XXH_readLE64(state->mem64+0, endian)); + state->v2 = XXH64_round(state->v2, XXH_readLE64(state->mem64+1, endian)); + state->v3 = XXH64_round(state->v3, XXH_readLE64(state->mem64+2, endian)); + state->v4 = XXH64_round(state->v4, XXH_readLE64(state->mem64+3, endian)); + p += 32-state->memsize; + state->memsize = 0; + } + + if (p+32 <= bEnd) { + const BYTE* const limit = bEnd - 32; + U64 v1 = state->v1; + U64 v2 = state->v2; + U64 v3 = state->v3; + U64 v4 = state->v4; + + do { + v1 = XXH64_round(v1, XXH_readLE64(p, endian)); p+=8; + v2 = XXH64_round(v2, XXH_readLE64(p, endian)); p+=8; + v3 = XXH64_round(v3, XXH_readLE64(p, endian)); p+=8; + v4 = XXH64_round(v4, XXH_readLE64(p, endian)); p+=8; + } while (p<=limit); + + state->v1 = v1; + state->v2 = v2; + state->v3 = v3; + state->v4 = v4; + } + + if (p < bEnd) { + XXH_memcpy(state->mem64, p, (size_t)(bEnd-p)); + state->memsize = (unsigned)(bEnd-p); + } + } + + return XXH_OK; +} + +XXH_PUBLIC_API XXH_errorcode XXH64_update (XXH64_state_t* state_in, const void* input, size_t len) +{ + XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; + + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH64_update_endian(state_in, input, len, XXH_littleEndian); + else + return XXH64_update_endian(state_in, input, len, XXH_bigEndian); +} + +FORCE_INLINE U64 XXH64_digest_endian (const XXH64_state_t* state, XXH_endianess endian) +{ + U64 h64; + + if (state->total_len >= 32) { + U64 const v1 = state->v1; + U64 const v2 = state->v2; + U64 const v3 = state->v3; + U64 const v4 = state->v4; + + h64 = XXH_rotl64(v1, 1) + XXH_rotl64(v2, 7) + XXH_rotl64(v3, 12) + XXH_rotl64(v4, 18); + h64 = XXH64_mergeRound(h64, v1); + h64 = XXH64_mergeRound(h64, v2); + h64 = XXH64_mergeRound(h64, v3); + h64 = XXH64_mergeRound(h64, v4); + } else { + h64 = state->v3 /*seed*/ + PRIME64_5; + } + + h64 += (U64) state->total_len; + + return XXH64_finalize(h64, state->mem64, (size_t)state->total_len, endian, XXH_aligned); +} + +XXH_PUBLIC_API unsigned long long XXH64_digest (const XXH64_state_t* state_in) +{ + XXH_endianess endian_detected = (XXH_endianess)XXH_CPU_LITTLE_ENDIAN; + + if ((endian_detected==XXH_littleEndian) || XXH_FORCE_NATIVE_FORMAT) + return XXH64_digest_endian(state_in, XXH_littleEndian); + else + return XXH64_digest_endian(state_in, XXH_bigEndian); +} + + +/*====== Canonical representation ======*/ + +XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH64_canonical_t* dst, XXH64_hash_t hash) +{ + XXH_STATIC_ASSERT(sizeof(XXH64_canonical_t) == sizeof(XXH64_hash_t)); + if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap64(hash); + memcpy(dst, &hash, sizeof(*dst)); +} + +XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src) +{ + return XXH_readBE64(src); +} + +#endif /* XXH_NO_LONG_LONG */ diff --git a/libCompression/xxhash.h b/libCompression/xxhash.h new file mode 100644 index 0000000..d6bad94 --- /dev/null +++ b/libCompression/xxhash.h @@ -0,0 +1,328 @@ +/* + xxHash - Extremely Fast Hash algorithm + Header File + Copyright (C) 2012-2016, Yann Collet. + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - xxHash source repository : https://github.com/Cyan4973/xxHash +*/ + +/* Notice extracted from xxHash homepage : + +xxHash is an extremely fast Hash algorithm, running at RAM speed limits. +It also successfully passes all tests from the SMHasher suite. + +Comparison (single thread, Windows Seven 32 bits, using SMHasher on a Core 2 Duo @3GHz) + +Name Speed Q.Score Author +xxHash 5.4 GB/s 10 +CrapWow 3.2 GB/s 2 Andrew +MumurHash 3a 2.7 GB/s 10 Austin Appleby +SpookyHash 2.0 GB/s 10 Bob Jenkins +SBox 1.4 GB/s 9 Bret Mulvey +Lookup3 1.2 GB/s 9 Bob Jenkins +SuperFastHash 1.2 GB/s 1 Paul Hsieh +CityHash64 1.05 GB/s 10 Pike & Alakuijala +FNV 0.55 GB/s 5 Fowler, Noll, Vo +CRC32 0.43 GB/s 9 +MD5-32 0.33 GB/s 10 Ronald L. Rivest +SHA1-32 0.28 GB/s 10 + +Q.Score is a measure of quality of the hash function. +It depends on successfully passing SMHasher test set. +10 is a perfect score. + +A 64-bit version, named XXH64, is available since r35. +It offers much better speed, but for 64-bit applications only. +Name Speed on 64 bits Speed on 32 bits +XXH64 13.8 GB/s 1.9 GB/s +XXH32 6.8 GB/s 6.0 GB/s +*/ + +#ifndef XXHASH_H_5627135585666179 +#define XXHASH_H_5627135585666179 1 + +#if defined (__cplusplus) +extern "C" { +#endif + + +/* **************************** +* Definitions +******************************/ +#include /* size_t */ +typedef enum { XXH_OK=0, XXH_ERROR } XXH_errorcode; + + +/* **************************** + * API modifier + ******************************/ +/** XXH_INLINE_ALL (and XXH_PRIVATE_API) + * This is useful to include xxhash functions in `static` mode + * in order to inline them, and remove their symbol from the public list. + * Inlining can offer dramatic performance improvement on small keys. + * Methodology : + * #define XXH_INLINE_ALL + * #include "xxhash.h" + * `xxhash.c` is automatically included. + * It's not useful to compile and link it as a separate module. + */ +#if defined(XXH_INLINE_ALL) || defined(XXH_PRIVATE_API) +# ifndef XXH_STATIC_LINKING_ONLY +# define XXH_STATIC_LINKING_ONLY +# endif +# if defined(__GNUC__) +# define XXH_PUBLIC_API static __inline __attribute__((unused)) +# elif defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) +# define XXH_PUBLIC_API static inline +# elif defined(_MSC_VER) +# define XXH_PUBLIC_API static __inline +# else + /* this version may generate warnings for unused static functions */ +# define XXH_PUBLIC_API static +# endif +#else +# define XXH_PUBLIC_API /* do nothing */ +#endif /* XXH_INLINE_ALL || XXH_PRIVATE_API */ + +/*! XXH_NAMESPACE, aka Namespace Emulation : + * + * If you want to include _and expose_ xxHash functions from within your own library, + * but also want to avoid symbol collisions with other libraries which may also include xxHash, + * + * you can use XXH_NAMESPACE, to automatically prefix any public symbol from xxhash library + * with the value of XXH_NAMESPACE (therefore, avoid NULL and numeric values). + * + * Note that no change is required within the calling program as long as it includes `xxhash.h` : + * regular symbol name will be automatically translated by this header. + */ +#ifdef XXH_NAMESPACE +# define XXH_CAT(A,B) A##B +# define XXH_NAME2(A,B) XXH_CAT(A,B) +# define XXH_versionNumber XXH_NAME2(XXH_NAMESPACE, XXH_versionNumber) +# define XXH32 XXH_NAME2(XXH_NAMESPACE, XXH32) +# define XXH32_createState XXH_NAME2(XXH_NAMESPACE, XXH32_createState) +# define XXH32_freeState XXH_NAME2(XXH_NAMESPACE, XXH32_freeState) +# define XXH32_reset XXH_NAME2(XXH_NAMESPACE, XXH32_reset) +# define XXH32_update XXH_NAME2(XXH_NAMESPACE, XXH32_update) +# define XXH32_digest XXH_NAME2(XXH_NAMESPACE, XXH32_digest) +# define XXH32_copyState XXH_NAME2(XXH_NAMESPACE, XXH32_copyState) +# define XXH32_canonicalFromHash XXH_NAME2(XXH_NAMESPACE, XXH32_canonicalFromHash) +# define XXH32_hashFromCanonical XXH_NAME2(XXH_NAMESPACE, XXH32_hashFromCanonical) +# define XXH64 XXH_NAME2(XXH_NAMESPACE, XXH64) +# define XXH64_createState XXH_NAME2(XXH_NAMESPACE, XXH64_createState) +# define XXH64_freeState XXH_NAME2(XXH_NAMESPACE, XXH64_freeState) +# define XXH64_reset XXH_NAME2(XXH_NAMESPACE, XXH64_reset) +# define XXH64_update XXH_NAME2(XXH_NAMESPACE, XXH64_update) +# define XXH64_digest XXH_NAME2(XXH_NAMESPACE, XXH64_digest) +# define XXH64_copyState XXH_NAME2(XXH_NAMESPACE, XXH64_copyState) +# define XXH64_canonicalFromHash XXH_NAME2(XXH_NAMESPACE, XXH64_canonicalFromHash) +# define XXH64_hashFromCanonical XXH_NAME2(XXH_NAMESPACE, XXH64_hashFromCanonical) +#endif + + +/* ************************************* +* Version +***************************************/ +#define XXH_VERSION_MAJOR 0 +#define XXH_VERSION_MINOR 6 +#define XXH_VERSION_RELEASE 5 +#define XXH_VERSION_NUMBER (XXH_VERSION_MAJOR *100*100 + XXH_VERSION_MINOR *100 + XXH_VERSION_RELEASE) +XXH_PUBLIC_API unsigned XXH_versionNumber (void); + + +/*-********************************************************************** +* 32-bit hash +************************************************************************/ +typedef unsigned int XXH32_hash_t; + +/*! XXH32() : + Calculate the 32-bit hash of sequence "length" bytes stored at memory address "input". + The memory between input & input+length must be valid (allocated and read-accessible). + "seed" can be used to alter the result predictably. + Speed on Core 2 Duo @ 3 GHz (single thread, SMHasher benchmark) : 5.4 GB/s */ +XXH_PUBLIC_API XXH32_hash_t XXH32 (const void* input, size_t length, unsigned int seed); + +/*====== Streaming ======*/ +typedef struct XXH32_state_s XXH32_state_t; /* incomplete type */ +XXH_PUBLIC_API XXH32_state_t* XXH32_createState(void); +XXH_PUBLIC_API XXH_errorcode XXH32_freeState(XXH32_state_t* statePtr); +XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* dst_state, const XXH32_state_t* src_state); + +XXH_PUBLIC_API XXH_errorcode XXH32_reset (XXH32_state_t* statePtr, unsigned int seed); +XXH_PUBLIC_API XXH_errorcode XXH32_update (XXH32_state_t* statePtr, const void* input, size_t length); +XXH_PUBLIC_API XXH32_hash_t XXH32_digest (const XXH32_state_t* statePtr); + +/* + * Streaming functions generate the xxHash of an input provided in multiple segments. + * Note that, for small input, they are slower than single-call functions, due to state management. + * For small inputs, prefer `XXH32()` and `XXH64()`, which are better optimized. + * + * XXH state must first be allocated, using XXH*_createState() . + * + * Start a new hash by initializing state with a seed, using XXH*_reset(). + * + * Then, feed the hash state by calling XXH*_update() as many times as necessary. + * The function returns an error code, with 0 meaning OK, and any other value meaning there is an error. + * + * Finally, a hash value can be produced anytime, by using XXH*_digest(). + * This function returns the nn-bits hash as an int or long long. + * + * It's still possible to continue inserting input into the hash state after a digest, + * and generate some new hashes later on, by calling again XXH*_digest(). + * + * When done, free XXH state space if it was allocated dynamically. + */ + +/*====== Canonical representation ======*/ + +typedef struct { unsigned char digest[4]; } XXH32_canonical_t; +XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash); +XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src); + +/* Default result type for XXH functions are primitive unsigned 32 and 64 bits. + * The canonical representation uses human-readable write convention, aka big-endian (large digits first). + * These functions allow transformation of hash result into and from its canonical format. + * This way, hash values can be written into a file / memory, and remain comparable on different systems and programs. + */ + + +#ifndef XXH_NO_LONG_LONG +/*-********************************************************************** +* 64-bit hash +************************************************************************/ +typedef unsigned long long XXH64_hash_t; + +/*! XXH64() : + Calculate the 64-bit hash of sequence of length "len" stored at memory address "input". + "seed" can be used to alter the result predictably. + This function runs faster on 64-bit systems, but slower on 32-bit systems (see benchmark). +*/ +XXH_PUBLIC_API XXH64_hash_t XXH64 (const void* input, size_t length, unsigned long long seed); + +/*====== Streaming ======*/ +typedef struct XXH64_state_s XXH64_state_t; /* incomplete type */ +XXH_PUBLIC_API XXH64_state_t* XXH64_createState(void); +XXH_PUBLIC_API XXH_errorcode XXH64_freeState(XXH64_state_t* statePtr); +XXH_PUBLIC_API void XXH64_copyState(XXH64_state_t* dst_state, const XXH64_state_t* src_state); + +XXH_PUBLIC_API XXH_errorcode XXH64_reset (XXH64_state_t* statePtr, unsigned long long seed); +XXH_PUBLIC_API XXH_errorcode XXH64_update (XXH64_state_t* statePtr, const void* input, size_t length); +XXH_PUBLIC_API XXH64_hash_t XXH64_digest (const XXH64_state_t* statePtr); + +/*====== Canonical representation ======*/ +typedef struct { unsigned char digest[8]; } XXH64_canonical_t; +XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH64_canonical_t* dst, XXH64_hash_t hash); +XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(const XXH64_canonical_t* src); +#endif /* XXH_NO_LONG_LONG */ + + + +#ifdef XXH_STATIC_LINKING_ONLY + +/* ================================================================================================ + This section contains declarations which are not guaranteed to remain stable. + They may change in future versions, becoming incompatible with a different version of the library. + These declarations should only be used with static linking. + Never use them in association with dynamic linking ! +=================================================================================================== */ + +/* These definitions are only present to allow + * static allocation of XXH state, on stack or in a struct for example. + * Never **ever** use members directly. */ + +#if !defined (__VMS) \ + && (defined (__cplusplus) \ + || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) +# include + +struct XXH32_state_s { + uint32_t total_len_32; + uint32_t large_len; + uint32_t v1; + uint32_t v2; + uint32_t v3; + uint32_t v4; + uint32_t mem32[4]; + uint32_t memsize; + uint32_t reserved; /* never read nor write, might be removed in a future version */ +}; /* typedef'd to XXH32_state_t */ + +struct XXH64_state_s { + uint64_t total_len; + uint64_t v1; + uint64_t v2; + uint64_t v3; + uint64_t v4; + uint64_t mem64[4]; + uint32_t memsize; + uint32_t reserved[2]; /* never read nor write, might be removed in a future version */ +}; /* typedef'd to XXH64_state_t */ + +# else + +struct XXH32_state_s { + unsigned total_len_32; + unsigned large_len; + unsigned v1; + unsigned v2; + unsigned v3; + unsigned v4; + unsigned mem32[4]; + unsigned memsize; + unsigned reserved; /* never read nor write, might be removed in a future version */ +}; /* typedef'd to XXH32_state_t */ + +# ifndef XXH_NO_LONG_LONG /* remove 64-bit support */ +struct XXH64_state_s { + unsigned long long total_len; + unsigned long long v1; + unsigned long long v2; + unsigned long long v3; + unsigned long long v4; + unsigned long long mem64[4]; + unsigned memsize; + unsigned reserved[2]; /* never read nor write, might be removed in a future version */ +}; /* typedef'd to XXH64_state_t */ +# endif + +# endif + + +#if defined(XXH_INLINE_ALL) || defined(XXH_PRIVATE_API) +# include "xxhash.c" /* include xxhash function bodies as `static`, for inlining */ +#endif + +#endif /* XXH_STATIC_LINKING_ONLY */ + + +#if defined (__cplusplus) +} +#endif + +#endif /* XXHASH_H_5627135585666179 */ diff --git a/libStringConverter/CMakeLists.txt b/libStringConverter/CMakeLists.txt new file mode 100644 index 0000000..2702521 --- /dev/null +++ b/libStringConverter/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library (libStringConverter STATIC convert.cpp) +target_include_directories (libStringConverter PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + diff --git a/libStringConverter/convert.cpp b/libStringConverter/convert.cpp new file mode 100644 index 0000000..52c83db --- /dev/null +++ b/libStringConverter/convert.cpp @@ -0,0 +1,59 @@ +#include "convert.h" +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include + +const char *_ConvertCPP_EmptyCHAR = ""; +const wchar_t *_ConvertCPP_EmptyWCHAR = L""; +char *_WideToMultiByte(const wchar_t *wi, size_t &len) +{ + if (!wi) + return nullptr; + int wcLen = (int)(wcslen(wi) & 0x7FFFFFFF); + int mbLen = WideCharToMultiByte(CP_UTF8, 0, wi, wcLen, NULL, 0, NULL, NULL); + char *ret = new char[mbLen + 1]; + WideCharToMultiByte(CP_UTF8, 0, wi, wcLen, ret, mbLen, NULL, NULL); + ret[mbLen] = 0; + len = (size_t)mbLen; + return ret; +} +wchar_t *_MultiByteToWide(const char *mb, size_t &len) +{ + if (!mb) + return nullptr; + int mbLen = (int)(strlen(mb) & 0x7FFFFFFF); + int wcLen = MultiByteToWideChar(CP_UTF8, 0, mb, mbLen, NULL, 0); + wchar_t *ret = new wchar_t[wcLen + 1]; + MultiByteToWideChar(CP_UTF8, 0, mb, mbLen, ret, wcLen); + ret[wcLen] = 0; + len = (size_t)wcLen; + return ret; +} +wchar_t *_WideToWide(const wchar_t *wi, size_t &len) +{ + if (!wi) + return nullptr; + len = wcslen(wi); + wchar_t *ret = new wchar_t[len + 1]; + wcscpy(ret, wi); + return ret; +} +char *_MultiByteToMultiByte(const char *mb, size_t &len) +{ + if (!mb) + return nullptr; + len = strlen(mb); + char *ret = new char[len + 1]; + strcpy(ret, mb); + return ret; +} +void _FreeCHAR(char *c) +{ + if (c != nullptr) + delete[] c; +} +void _FreeWCHAR(WCHAR *c) +{ + if (c != nullptr) + delete[] c; +} diff --git a/libStringConverter/convert.h b/libStringConverter/convert.h new file mode 100644 index 0000000..25c4d09 --- /dev/null +++ b/libStringConverter/convert.h @@ -0,0 +1,144 @@ +#pragma once +#ifndef TCHAR +#define _TCHAR_CUSTOM +#ifdef _UNICODE +#define TCHAR wchar_t +#else +#define TCHAR char +#endif +#endif +#include + +//UTF-8 <-> UTF-16 conversion utility functions. +//-> "Multi Byte" refers to UTF-8 here, even though Win32 defines it as the regional one-byte-per-character code page. +//-> TCHAR refers to either UTF-8 or UTF-16. +// The latter is used if Win32 Unicode support is enabled (_UNICODE). + +//len : Length in characters, excluding the null terminator. + +char *_WideToMultiByte(const wchar_t *wi, size_t &len); +wchar_t *_MultiByteToWide(const char *mb, size_t &len); +wchar_t *_WideToWide(const wchar_t *wi, size_t &len); +char *_MultiByteToMultiByte(const char *mb, size_t &len); +void _FreeCHAR(char *c); +void _FreeWCHAR(wchar_t *c); + +#ifdef _UNICODE +inline TCHAR *_WideToTCHAR(const wchar_t *wi, size_t &len) +{ + return _WideToWide(wi, len); +} +inline TCHAR *_MultiByteToTCHAR(const char *mb, size_t &len) +{ + return _MultiByteToWide(mb, len); +} +inline wchar_t *_TCHARToWide(const TCHAR *tc, size_t &len) +{ + return _WideToWide(tc, len); +} +inline char *_TCHARToMultiByte(const TCHAR *tc, size_t &len) +{ + return _WideToMultiByte(tc, len); +} +inline void _FreeTCHAR(TCHAR *tc) +{ + _FreeWCHAR(tc); +} +#else +inline TCHAR *_WideToTCHAR(const wchar_t *wi, size_t &len) +{ + return _WideToMultiByte(wi, len); +} +inline TCHAR *_MultiByteToTCHAR(const char *mb, size_t &len) +{ + return _MultiByteToMultiByte(mb, len); +} +inline wchar_t *_TCHARToWide(const TCHAR *tc, size_t &len) +{ + return _MultiByteToWide(tc, len); +} +inline char *_TCHARToMultiByte(const TCHAR *tc, size_t &len) +{ + return _MultiByteToMultiByte(tc, len); +} +inline void _FreeTCHAR(TCHAR *tc) +{ + _FreeCHAR(tc); +} +#endif + +inline std::unique_ptr unique_WideToTCHAR(const wchar_t *wi, size_t &len) +{ + return std::unique_ptr(_WideToTCHAR(wi, len), _FreeTCHAR); +} +inline std::unique_ptr unique_WideToTCHAR(const wchar_t *wi) +{ + size_t unused; + return unique_WideToTCHAR(wi, unused); +} + +inline std::unique_ptr unique_MultiByteToTCHAR(const char *mb, size_t &len) +{ + return std::unique_ptr(_MultiByteToTCHAR(mb, len), _FreeTCHAR); +} +inline std::unique_ptr unique_MultiByteToTCHAR(const char *mb) +{ + size_t unused; + return unique_MultiByteToTCHAR(mb, unused); +} + +inline std::unique_ptr unique_TCHARToMultiByte(const TCHAR *tc, size_t &len) +{ + return std::unique_ptr(_TCHARToMultiByte(tc, len), _FreeCHAR); +} +inline std::unique_ptr unique_TCHARToMultiByte(const TCHAR *tc) +{ + size_t unused; + return unique_TCHARToMultiByte(tc, unused); +} + + +inline std::unique_ptr unique_WideToMultiByte(const wchar_t *wc, size_t &len) +{ + return std::unique_ptr(_WideToMultiByte(wc, len), _FreeCHAR); +} +inline std::unique_ptr unique_WideToMultiByte(const wchar_t *wc) +{ + size_t unused; + return unique_WideToMultiByte(wc, unused); +} + +inline std::unique_ptr unique_MultiByteToWide(const char *mb, size_t &len) +{ + return std::unique_ptr(_MultiByteToWide(mb, len), _FreeWCHAR); +} +inline std::unique_ptr unique_MultiByteToWide(const char *mb) +{ + size_t unused; + return unique_MultiByteToWide(mb, unused); +} + +inline std::unique_ptr unique_WideToWide(const wchar_t *wc, size_t &len) +{ + return std::unique_ptr(_WideToWide(wc, len), _FreeWCHAR); +} +inline std::unique_ptr unique_WideToWide(const wchar_t *wc) +{ + size_t unused; + return unique_WideToWide(wc, unused); +} + +inline std::unique_ptr unique_MultiByteToMultiByte(const char *mb, size_t &len) +{ + return std::unique_ptr(_MultiByteToMultiByte(mb, len), _FreeCHAR); +} +inline std::unique_ptr unique_MultiByteToMultiByte(const char *mb) +{ + size_t unused; + return unique_MultiByteToMultiByte(mb, unused); +} + +#ifdef _TCHAR_CUSTOM +#undef TCHAR +#undef _TCHAR_CUSTOM +#endif \ No newline at end of file