diff --git a/.github/workflows/deploy_docs.yml b/.github/workflows/deploy_docs.yml
index 6e691de18..c34e99bba 100644
--- a/.github/workflows/deploy_docs.yml
+++ b/.github/workflows/deploy_docs.yml
@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Create & Deploy Docs
- uses: DenverCoder1/doxygen-github-pages-action@v1.3.1
+ uses: DenverCoder1/doxygen-github-pages-action@v2.0.0
with:
github_token: ${{secrets.GITHUB_TOKEN}}
branch: docs
diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml
new file mode 100644
index 000000000..9dd7538cd
--- /dev/null
+++ b/.github/workflows/wheels.yml
@@ -0,0 +1,102 @@
+name: Build Wheels
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+ workflow_dispatch:
+ inputs:
+ release:
+ type: boolean
+ required: true
+ default: false
+ description: 'Push the wheels to PyPI'
+ release:
+ types: [published]
+jobs:
+ build_sdist:
+ name: Build SDist
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: '${{github.workspace}}/lang/python'
+ steps:
+ - name: Checkout Repository
+ uses: actions/checkout@v4
+ with:
+ submodules: recursive
+
+ - name: Build SDist
+ run: |
+ pipx run build --sdist
+
+ - name: Check Metadata
+ run: |
+ pipx run twine check dist/*
+
+ - name: Upload Artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: dist-sdist
+ path: ${{github.workspace}}/lang/python/dist/*.tar.gz
+
+# Linux build fails because the compiler's not new enough
+# macOS build fails because it keeps trying to build a universal binary
+#
+# build_wheels:
+# strategy:
+# fail-fast: false
+# matrix:
+# os: [windows-latest, ubuntu-latest, macos-latest]
+# name: Build Wheels For ${{matrix.os}}
+# runs-on: ${{matrix.os}}
+# steps:
+# - name: Checkout Repository
+# uses: actions/checkout@v4
+# with:
+# submodules: true
+#
+# - name: Build Wheels
+# uses: pypa/cibuildwheel@v2.19
+# with:
+# package-dir: '${{github.workspace}}/lang/python'
+# output-dir: '${{github.workspace}}/lang/python/wheelhouse'
+#
+# - name: Verify Clean Directory
+# shell: bash
+# run: |
+# git diff --exit-code
+#
+# - name: Upload Wheels
+# uses: actions/upload-artifact@v4
+# with:
+# path: ${{github.workspace}}/lang/python/wheelhouse/*.whl
+# name: dist-${{matrix.os}}
+
+ merge_wheels:
+ name: Merge Wheels
+ runs-on: ubuntu-latest
+ needs:
+ - build_sdist
+# - build_wheels
+ steps:
+ - name: Merge Artifacts
+ uses: actions/upload-artifact/merge@v4
+ with:
+ name: dist
+ pattern: dist-*
+
+ upload_release:
+ name: Upload a Release
+ if: (github.event_name == 'release' && github.event.action == 'published') || (github.event_name == 'workflow_dispatch' && inputs.release)
+ needs: [merge_wheels]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/setup-python@v5
+ - uses: actions/download-artifact@v4
+ with:
+ path: dist
+ - uses: pypa/gh-action-pypi-publish@release/v1
+ with:
+ user: __token__
+ password: ${{secrets.PYPI_TOKEN}}
diff --git a/.gitignore b/.gitignore
index 0c0169e33..148b32c84 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,11 @@ out/
docs/html/
test/res/
test/Helpers.h
+
+# Python
+.mypy_cache/
+__pycache__/
+*.pyd
+*.pyi
+*.typed
+*.whl
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0106c49b1..ddb12ba6d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.25 FATAL_ERROR)
# Set defaults before project call
if(PROJECT_IS_TOP_LEVEL)
- set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64")
+ set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64" CACHE INTERNAL "" FORCE)
endif()
@@ -101,15 +101,16 @@ endif()
# Python bindings, part 1
if(SOURCEPP_BUILD_PYTHON_WRAPPERS)
set(SOURCEPP_PYTHON_NAME "${PROJECT_NAME}_python")
- find_package(Python REQUIRED COMPONENTS Interpreter Development.Module)
+ find_package(Python REQUIRED
+ COMPONENTS Interpreter Development.Module
+ OPTIONAL_COMPONENTS Development.SABIModule)
FetchContent_Declare(
- pybind11
- GIT_REPOSITORY "https://github.com/pybind/pybind11.git"
- GIT_TAG "v2.13.6")
- FetchContent_MakeAvailable(pybind11)
+ nanobind
+ GIT_REPOSITORY "https://github.com/wjakob/nanobind.git"
+ GIT_TAG "origin/master")
+ FetchContent_MakeAvailable(nanobind)
set(${SOURCEPP_PYTHON_NAME}_SOURCES "")
set(${SOURCEPP_PYTHON_NAME}_DEFINES "")
- list(APPEND ${SOURCEPP_PYTHON_NAME}_DEPS pybind11::headers)
endif()
@@ -143,7 +144,6 @@ endif()
# Benchmarks
if(SOURCEPP_BUILD_BENCHMARKS)
set(SOURCEPP_BENCH_NAME "${PROJECT_NAME}_bench")
- include(FetchContent)
FetchContent_Declare(
benchmark
GIT_REPOSITORY https://github.com/google/benchmark.git
@@ -167,7 +167,7 @@ add_sourcepp_library(steampp C PYTHON ) # sourcepp::steampp
add_sourcepp_library(toolpp ) # sourcepp::toolpp
add_sourcepp_library(vcryptpp C CSHARP PYTHON ) # sourcepp::vcryptpp
add_sourcepp_library(vpkpp C CSHARP NO_TEST ) # sourcepp::vpkpp
-add_sourcepp_library(vtfpp BENCH) # sourcepp::vtfpp
+add_sourcepp_library(vtfpp PYTHON BENCH) # sourcepp::vtfpp
# Tests, part 2
@@ -182,16 +182,52 @@ endif()
# Python bindings, part 2
if(SOURCEPP_BUILD_PYTHON_WRAPPERS)
- python_add_library(${SOURCEPP_PYTHON_NAME} MODULE "${CMAKE_CURRENT_SOURCE_DIR}/lang/python/src/sourcepp.cpp" ${${SOURCEPP_PYTHON_NAME}_SOURCES} WITH_SOABI)
- set_target_properties(${SOURCEPP_PYTHON_NAME} PROPERTIES PREFIX "_" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/lang/python/dist/sourcepp")
+ nanobind_add_module(${SOURCEPP_PYTHON_NAME} NB_STATIC STABLE_ABI LTO
+ "${CMAKE_CURRENT_SOURCE_DIR}/lang/python/src/sourcepp.cpp"
+ ${${SOURCEPP_PYTHON_NAME}_SOURCES})
+ set_target_properties(${SOURCEPP_PYTHON_NAME} PROPERTIES
+ OUTPUT_NAME "_${PROJECT_NAME}_impl"
+ LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/lang/python/src/sourcepp"
+ LIBRARY_OUTPUT_DIRECTORY_DEBUG "${CMAKE_CURRENT_SOURCE_DIR}/lang/python/src/sourcepp"
+ LIBRARY_OUTPUT_DIRECTORY_RELEASE "${CMAKE_CURRENT_SOURCE_DIR}/lang/python/src/sourcepp")
target_compile_definitions(${SOURCEPP_PYTHON_NAME} PRIVATE ${${SOURCEPP_PYTHON_NAME}_DEFINES})
target_link_libraries(${SOURCEPP_PYTHON_NAME} PRIVATE ${${SOURCEPP_PYTHON_NAME}_DEPS})
+
+ add_custom_target(${SOURCEPP_PYTHON_NAME}_all)
+ add_dependencies(${SOURCEPP_PYTHON_NAME}_all ${SOURCEPP_PYTHON_NAME})
+
+ # We need to manually write out each module :(
+ set(${SOURCEPP_PYTHON_NAME}_MODULES
+ "sourcepp"
+ "sourcepp._sourcepp_impl"
+ "sourcepp._sourcepp_impl.gamepp"
+ "sourcepp._sourcepp_impl.sourcepp"
+ "sourcepp._sourcepp_impl.sourcepp.math"
+ "sourcepp._sourcepp_impl.steampp"
+ "sourcepp._sourcepp_impl.vcryptpp"
+ "sourcepp._sourcepp_impl.vcryptpp.VFONT"
+ "sourcepp._sourcepp_impl.vcryptpp.VICE"
+ "sourcepp._sourcepp_impl.vtfpp"
+ "sourcepp._sourcepp_impl.vtfpp.ImageFormatDetails"
+ "sourcepp._sourcepp_impl.vtfpp.ImageDimensions"
+ "sourcepp._sourcepp_impl.vtfpp.ImageConversion")
+ foreach(MODULE ${${SOURCEPP_PYTHON_NAME}_MODULES})
+ string(REPLACE "." "/" MODULE_DIR "${MODULE}")
+ string(REPLACE "." "_" MODULE_NAME_NORMALIZED "${MODULE}")
+ set(MODULE_NAME_NORMALIZED "${MODULE_NAME_NORMALIZED}_stub")
+ nanobind_add_stub("${SOURCEPP_PYTHON_NAME}_stub_${MODULE_NAME_NORMALIZED}"
+ DEPENDS ${SOURCEPP_PYTHON_NAME}
+ MODULE "${MODULE}"
+ OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/lang/python/src/${MODULE_DIR}.pyi"
+ PYTHON_PATH "${CMAKE_CURRENT_SOURCE_DIR}/lang/python/src")
+ add_dependencies(${SOURCEPP_PYTHON_NAME}_all ${SOURCEPP_PYTHON_NAME}_stub_${MODULE_NAME_NORMALIZED})
+ endforeach()
endif()
# Print options
print_options(OPTIONS
USE_BSPPP USE_DMXPP USE_GAMEPP USE_KVPP USE_MDLPP USE_STEAMPP USE_TOOLPP USE_VCRYPTPP USE_VPKPP USE_VTFPP
- BUILD_BENCHMARKS BUILD_C_WRAPPERS BUILD_PYTHON_WRAPPERS BUILD_WITH_OPENCL BUILD_WITH_TBB BUILD_WITH_THREADS BUILD_TESTS BUILD_WIN7_COMPAT
+ BUILD_BENCHMARKS BUILD_C_WRAPPERS BUILD_CSHARP_WRAPPERS BUILD_PYTHON_WRAPPERS BUILD_WITH_OPENCL BUILD_WITH_TBB BUILD_WITH_THREADS BUILD_TESTS BUILD_WIN7_COMPAT
LINK_STATIC_MSVC_RUNTIME
VPKPP_SUPPORT_VPK_V54)
diff --git a/README.md b/README.md
index 1544e8e6a..6fc49a144 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,7 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one
- bsppp |
+ bsppp* |
BSP v17-27 |
✅ |
✅ |
@@ -30,7 +30,7 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one
- dmxpp |
+ dmxpp* |
DMX Binary v1-5 |
✅ |
❌ |
@@ -53,15 +53,15 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one
kvpp |
- KeyValues v1* |
+ KeyValues Text v1† |
✅ |
✅ |
|
- mdlpp |
- MDL v44-49† |
+ mdlpp* |
+ MDL v44-49 |
✅ |
❌ |
|
@@ -217,7 +217,7 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one
BMP |
✅ |
✅ |
- |
+ Python |
@@ -290,9 +290,16 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one
-(\*) Many text-based formats in Source are close to (if not identical to) KeyValues v1, such as [VDF](https://developer.valvesoftware.com/wiki/VDF), [VMT](https://developer.valvesoftware.com/wiki/VMT), and [VMF](https://developer.valvesoftware.com/wiki/VMF_(Valve_Map_Format)).
+(\*) These libraries are incomplete and still in development. Their interfaces are unstable and will likely change in the future.
+Libraries not starred should be considered stable, and their existing interfaces will not change much if at all. Note that wrappers
+only exist for stable libraries.
-(†) The MDL parser is not complete. It is usable in its current state, but it does not currently parse more complex components like animations. This parser is still in development.
+(†) Many text-based formats in Source are close to (if not identical to) KeyValues v1, such as [VMT](https://developer.valvesoftware.com/wiki/VMT) and [VMF](https://developer.valvesoftware.com/wiki/VMF_(Valve_Map_Format)).
+
+## Wrappers
+
+Wrappers for libraries considered complete exist for C, C#, and/or Python, depending on the library. The Python wrappers can be
+found on PyPI in the `sourcepp` package.
## Special Thanks
diff --git a/THIRDPARTY_LEGAL_NOTICES.txt b/THIRDPARTY_LEGAL_NOTICES.txt
index 44cfc395a..a6d198797 100644
--- a/THIRDPARTY_LEGAL_NOTICES.txt
+++ b/THIRDPARTY_LEGAL_NOTICES.txt
@@ -222,6 +222,37 @@ freely, subject to the following restrictions:
3. This notice may not be removed or altered from any source distribution.
+--------------- nanobind ---------------
+
+Copyright (c) 2022 Wenzel Jakob .
+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 the copyright holder 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 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.
+
+
--------------- stb ---------------
Copyright (c) 2017 Sean Barrett
diff --git a/docs/index.md b/docs/index.md
index 01fe38c94..1217602e3 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -22,14 +22,14 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one
Wrappers |
- bsppp |
+ bsppp* |
BSP v17-27 |
✅ |
✅ |
|
- dmxpp |
+ dmxpp* |
DMX Binary v1-5 |
✅ |
❌ |
@@ -49,14 +49,14 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one
kvpp |
- KeyValues v1* |
+ KeyValues Text v1† |
✅ |
✅ |
|
- mdlpp |
- MDL v44-49† |
+ mdlpp* |
+ MDL v44-49 |
✅ |
❌ |
|
@@ -190,7 +190,7 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one
BMP |
✅ |
✅ |
- |
+ Python |
| EXR v1 |
@@ -253,9 +253,16 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one
\endhtmlonly
-(\*) Many text-based formats in Source are close to (if not identical to) KeyValues v1, such as [VDF](https://developer.valvesoftware.com/wiki/VDF), [VMT](https://developer.valvesoftware.com/wiki/VMT), and [VMF](https://developer.valvesoftware.com/wiki/VMF_(Valve_Map_Format)).
+(\*) These libraries are incomplete and still in development. Their interfaces are unstable and will likely change in the future.
+Libraries not starred should be considered stable, and their existing interfaces will not change much if at all. Note that wrappers
+only exist for stable libraries.
-(†) The MDL parser is not complete. It is usable in its current state, but it does not currently parse more complex components like animations. This parser is still in development.
+(†) Many text-based formats in Source are close to (if not identical to) KeyValues v1, such as [VMT](https://developer.valvesoftware.com/wiki/VMT) and [VMF](https://developer.valvesoftware.com/wiki/VMF_(Valve_Map_Format)).
+
+## Wrappers
+
+Wrappers for libraries considered complete exist for C, C#, and/or Python, depending on the library. The Python wrappers can be
+found on PyPI in the `sourcepp` package.
## Special Thanks
diff --git a/ext/_ext.cmake b/ext/_ext.cmake
index 4c8f80d55..530c62412 100644
--- a/ext/_ext.cmake
+++ b/ext/_ext.cmake
@@ -42,11 +42,11 @@ endif()
# minizip-ng (guard this behind vpkpp because this is a big dependency)
-if(SOURCEPP_USE_VPKPP AND NOT TARGET MINIZIP::minizip)
+if((SOURCEPP_USE_VPKPP OR SOURCEPP_USE_VTFPP) AND NOT TARGET MINIZIP::minizip)
set(MZ_COMPAT OFF CACHE INTERNAL "")
# todo: guard liblzma/xz force-enable behind BSP compression option
set(MZ_LZMA ON CACHE INTERNAL "" FORCE)
- if(SOURCEPP_VPKPP_SUPPORT_VPK_V54)
+ if(SOURCEPP_USE_VTFPP OR SOURCEPP_VPKPP_SUPPORT_VPK_V54)
set(MZ_ZSTD ON CACHE INTERNAL "" FORCE)
endif()
set(MZ_FETCH_LIBS ON CACHE INTERNAL "" FORCE)
diff --git a/ext/compressonator/CMakeLists.txt b/ext/compressonator/CMakeLists.txt
index a4757f151..22cc64f15 100644
--- a/ext/compressonator/CMakeLists.txt
+++ b/ext/compressonator/CMakeLists.txt
@@ -1,4 +1,6 @@
-set(COMPRESSONATOR_DIR ${CMAKE_CURRENT_LIST_DIR} CACHE INTERNAL "")
+include_guard(GLOBAL)
+
+set(COMPRESSONATOR_DIR "${CMAKE_CURRENT_LIST_DIR}" CACHE INTERNAL "")
function(target_link_compressonator TARGET)
if(WIN32)
@@ -20,8 +22,8 @@ function(target_link_compressonator TARGET)
endif()
elseif(APPLE)
target_link_libraries(${TARGET} PRIVATE
- "${COMPRESSONATOR_DIR}/lib/macOS/libCompressonator$<$:d>.a"
- "${COMPRESSONATOR_DIR}/lib/macOS/libCMP_Core$<$:d>.a")
+ "${COMPRESSONATOR_DIR}/lib/macOS_arm64/libCompressonator$<$:d>.a"
+ "${COMPRESSONATOR_DIR}/lib/macOS_arm64/libCMP_Core$<$:d>.a")
elseif(UNIX)
target_link_libraries(${TARGET} PRIVATE
"${COMPRESSONATOR_DIR}/lib/linux_x86_64/libCompressonator$<$:d>.a"
diff --git a/ext/compressonator/lib/macOS/libCMP_Core.a b/ext/compressonator/lib/macOS_arm64/libCMP_Core.a
similarity index 98%
rename from ext/compressonator/lib/macOS/libCMP_Core.a
rename to ext/compressonator/lib/macOS_arm64/libCMP_Core.a
index b334e5f1f..69947a390 100644
Binary files a/ext/compressonator/lib/macOS/libCMP_Core.a and b/ext/compressonator/lib/macOS_arm64/libCMP_Core.a differ
diff --git a/ext/compressonator/lib/macOS/libCMP_Cored.a b/ext/compressonator/lib/macOS_arm64/libCMP_Cored.a
similarity index 51%
rename from ext/compressonator/lib/macOS/libCMP_Cored.a
rename to ext/compressonator/lib/macOS_arm64/libCMP_Cored.a
index 523764d44..68c2458ee 100644
Binary files a/ext/compressonator/lib/macOS/libCMP_Cored.a and b/ext/compressonator/lib/macOS_arm64/libCMP_Cored.a differ
diff --git a/ext/compressonator/lib/macOS/libCompressonator.a b/ext/compressonator/lib/macOS_arm64/libCompressonator.a
similarity index 89%
rename from ext/compressonator/lib/macOS/libCompressonator.a
rename to ext/compressonator/lib/macOS_arm64/libCompressonator.a
index 6ac269e9e..2fa0c0702 100644
Binary files a/ext/compressonator/lib/macOS/libCompressonator.a and b/ext/compressonator/lib/macOS_arm64/libCompressonator.a differ
diff --git a/ext/compressonator/lib/macOS/libCompressonatord.a b/ext/compressonator/lib/macOS_arm64/libCompressonatord.a
similarity index 61%
rename from ext/compressonator/lib/macOS/libCompressonatord.a
rename to ext/compressonator/lib/macOS_arm64/libCompressonatord.a
index 5c5e41bf9..203957a5b 100644
Binary files a/ext/compressonator/lib/macOS/libCompressonatord.a and b/ext/compressonator/lib/macOS_arm64/libCompressonatord.a differ
diff --git a/ext/compressonator/lib/win_x86_64/CMP_Core_MD.lib b/ext/compressonator/lib/win_x86_64/CMP_Core_MD.lib
index 0efe277be..9efa4b405 100644
Binary files a/ext/compressonator/lib/win_x86_64/CMP_Core_MD.lib and b/ext/compressonator/lib/win_x86_64/CMP_Core_MD.lib differ
diff --git a/ext/compressonator/lib/win_x86_64/CMP_Core_MD_AVX.lib b/ext/compressonator/lib/win_x86_64/CMP_Core_MD_AVX.lib
index b578fb151..4fc8a1a28 100644
Binary files a/ext/compressonator/lib/win_x86_64/CMP_Core_MD_AVX.lib and b/ext/compressonator/lib/win_x86_64/CMP_Core_MD_AVX.lib differ
diff --git a/ext/compressonator/lib/win_x86_64/CMP_Core_MD_AVX512.lib b/ext/compressonator/lib/win_x86_64/CMP_Core_MD_AVX512.lib
index c08519b47..25f22e752 100644
Binary files a/ext/compressonator/lib/win_x86_64/CMP_Core_MD_AVX512.lib and b/ext/compressonator/lib/win_x86_64/CMP_Core_MD_AVX512.lib differ
diff --git a/ext/compressonator/lib/win_x86_64/CMP_Core_MD_AVX512d.lib b/ext/compressonator/lib/win_x86_64/CMP_Core_MD_AVX512d.lib
index 97202b42f..e10065294 100644
Binary files a/ext/compressonator/lib/win_x86_64/CMP_Core_MD_AVX512d.lib and b/ext/compressonator/lib/win_x86_64/CMP_Core_MD_AVX512d.lib differ
diff --git a/ext/compressonator/lib/win_x86_64/CMP_Core_MD_AVXd.lib b/ext/compressonator/lib/win_x86_64/CMP_Core_MD_AVXd.lib
index 99a1b33cc..482bcc8e5 100644
Binary files a/ext/compressonator/lib/win_x86_64/CMP_Core_MD_AVXd.lib and b/ext/compressonator/lib/win_x86_64/CMP_Core_MD_AVXd.lib differ
diff --git a/ext/compressonator/lib/win_x86_64/CMP_Core_MD_SSE.lib b/ext/compressonator/lib/win_x86_64/CMP_Core_MD_SSE.lib
index 014aebdd5..de5062d49 100644
Binary files a/ext/compressonator/lib/win_x86_64/CMP_Core_MD_SSE.lib and b/ext/compressonator/lib/win_x86_64/CMP_Core_MD_SSE.lib differ
diff --git a/ext/compressonator/lib/win_x86_64/CMP_Core_MD_SSEd.lib b/ext/compressonator/lib/win_x86_64/CMP_Core_MD_SSEd.lib
index 61d410a9a..f3c42d72f 100644
Binary files a/ext/compressonator/lib/win_x86_64/CMP_Core_MD_SSEd.lib and b/ext/compressonator/lib/win_x86_64/CMP_Core_MD_SSEd.lib differ
diff --git a/ext/compressonator/lib/win_x86_64/CMP_Core_MDd.lib b/ext/compressonator/lib/win_x86_64/CMP_Core_MDd.lib
index 95c3ef771..0d6f7aa66 100644
Binary files a/ext/compressonator/lib/win_x86_64/CMP_Core_MDd.lib and b/ext/compressonator/lib/win_x86_64/CMP_Core_MDd.lib differ
diff --git a/ext/compressonator/lib/win_x86_64/CMP_Core_MT.lib b/ext/compressonator/lib/win_x86_64/CMP_Core_MT.lib
index 62660ca70..7a56db967 100644
Binary files a/ext/compressonator/lib/win_x86_64/CMP_Core_MT.lib and b/ext/compressonator/lib/win_x86_64/CMP_Core_MT.lib differ
diff --git a/ext/compressonator/lib/win_x86_64/CMP_Core_MT_AVX.lib b/ext/compressonator/lib/win_x86_64/CMP_Core_MT_AVX.lib
index 87defa8f6..f0b4cfc27 100644
Binary files a/ext/compressonator/lib/win_x86_64/CMP_Core_MT_AVX.lib and b/ext/compressonator/lib/win_x86_64/CMP_Core_MT_AVX.lib differ
diff --git a/ext/compressonator/lib/win_x86_64/CMP_Core_MT_AVX512.lib b/ext/compressonator/lib/win_x86_64/CMP_Core_MT_AVX512.lib
index 7a8681eff..3150b8ed9 100644
Binary files a/ext/compressonator/lib/win_x86_64/CMP_Core_MT_AVX512.lib and b/ext/compressonator/lib/win_x86_64/CMP_Core_MT_AVX512.lib differ
diff --git a/ext/compressonator/lib/win_x86_64/CMP_Core_MT_AVX512d.lib b/ext/compressonator/lib/win_x86_64/CMP_Core_MT_AVX512d.lib
index 41ef859f2..af3fbcc5e 100644
Binary files a/ext/compressonator/lib/win_x86_64/CMP_Core_MT_AVX512d.lib and b/ext/compressonator/lib/win_x86_64/CMP_Core_MT_AVX512d.lib differ
diff --git a/ext/compressonator/lib/win_x86_64/CMP_Core_MT_AVXd.lib b/ext/compressonator/lib/win_x86_64/CMP_Core_MT_AVXd.lib
index 9db8d8d7c..932ae1f1b 100644
Binary files a/ext/compressonator/lib/win_x86_64/CMP_Core_MT_AVXd.lib and b/ext/compressonator/lib/win_x86_64/CMP_Core_MT_AVXd.lib differ
diff --git a/ext/compressonator/lib/win_x86_64/CMP_Core_MT_SSE.lib b/ext/compressonator/lib/win_x86_64/CMP_Core_MT_SSE.lib
index 2bf68a0ee..05f719b66 100644
Binary files a/ext/compressonator/lib/win_x86_64/CMP_Core_MT_SSE.lib and b/ext/compressonator/lib/win_x86_64/CMP_Core_MT_SSE.lib differ
diff --git a/ext/compressonator/lib/win_x86_64/CMP_Core_MT_SSEd.lib b/ext/compressonator/lib/win_x86_64/CMP_Core_MT_SSEd.lib
index 538cee480..b720f1c7a 100644
Binary files a/ext/compressonator/lib/win_x86_64/CMP_Core_MT_SSEd.lib and b/ext/compressonator/lib/win_x86_64/CMP_Core_MT_SSEd.lib differ
diff --git a/ext/compressonator/lib/win_x86_64/CMP_Core_MTd.lib b/ext/compressonator/lib/win_x86_64/CMP_Core_MTd.lib
index 1a21c9d03..5c19763da 100644
Binary files a/ext/compressonator/lib/win_x86_64/CMP_Core_MTd.lib and b/ext/compressonator/lib/win_x86_64/CMP_Core_MTd.lib differ
diff --git a/ext/compressonator/lib/win_x86_64/Compressonator_MD.lib b/ext/compressonator/lib/win_x86_64/Compressonator_MD.lib
index 1c3f6b54c..f6cb96cda 100644
Binary files a/ext/compressonator/lib/win_x86_64/Compressonator_MD.lib and b/ext/compressonator/lib/win_x86_64/Compressonator_MD.lib differ
diff --git a/ext/compressonator/lib/win_x86_64/Compressonator_MDd.lib b/ext/compressonator/lib/win_x86_64/Compressonator_MDd.lib
index 46aef8db7..79d3aad1c 100644
Binary files a/ext/compressonator/lib/win_x86_64/Compressonator_MDd.lib and b/ext/compressonator/lib/win_x86_64/Compressonator_MDd.lib differ
diff --git a/ext/compressonator/lib/win_x86_64/Compressonator_MT.lib b/ext/compressonator/lib/win_x86_64/Compressonator_MT.lib
index 49197349a..9f7df3aae 100644
Binary files a/ext/compressonator/lib/win_x86_64/Compressonator_MT.lib and b/ext/compressonator/lib/win_x86_64/Compressonator_MT.lib differ
diff --git a/ext/compressonator/lib/win_x86_64/Compressonator_MTd.lib b/ext/compressonator/lib/win_x86_64/Compressonator_MTd.lib
index d863c69f8..2b3d81cc3 100644
Binary files a/ext/compressonator/lib/win_x86_64/Compressonator_MTd.lib and b/ext/compressonator/lib/win_x86_64/Compressonator_MTd.lib differ
diff --git a/include/vtfpp/PPL.h b/include/vtfpp/PPL.h
index 6fa1a1541..18a8329c4 100644
--- a/include/vtfpp/PPL.h
+++ b/include/vtfpp/PPL.h
@@ -53,7 +53,7 @@ class PPL {
bool setImage(std::span imageData, ImageFormat format_, uint32_t width, uint32_t height, uint32_t resizedWidth, uint32_t resizedHeight, uint32_t lod = 0, ImageConversion::ResizeFilter filter = ImageConversion::ResizeFilter::BILINEAR);
- bool setImage(const std::string& imagePath, uint32_t lod);
+ bool setImage(const std::string& imagePath, uint32_t lod = 0);
bool setImage(const std::string& imagePath, uint32_t resizedWidth, uint32_t resizedHeight, uint32_t lod = 0, ImageConversion::ResizeFilter filter = ImageConversion::ResizeFilter::BILINEAR);
diff --git a/include/vtfpp/VTF.h b/include/vtfpp/VTF.h
index a8d9bf556..54eeddb89 100644
--- a/include/vtfpp/VTF.h
+++ b/include/vtfpp/VTF.h
@@ -18,6 +18,11 @@ namespace vtfpp {
constexpr uint32_t VTF_SIGNATURE = sourcepp::parser::binary::makeFourCC("VTF\0");
+enum class CompressionMethod : int16_t {
+ DEFLATE = 8,
+ ZSTD = 93,
+};
+
struct Resource {
enum Type : uint32_t {
TYPE_UNKNOWN = 0, // Unknown
@@ -30,12 +35,7 @@ struct Resource {
TYPE_KEYVALUES_DATA = sourcepp::parser::binary::makeFourCC("KVD\0"),
TYPE_AUX_COMPRESSION = sourcepp::parser::binary::makeFourCC("AXC\0"),
};
- static constexpr std::array TYPE_ARRAY_ORDER{
- // These don't really matter
- Resource::TYPE_CRC, Resource::TYPE_EXTENDED_FLAGS, Resource::TYPE_LOD_CONTROL_INFO, Resource::TYPE_KEYVALUES_DATA, Resource::TYPE_PARTICLE_SHEET_DATA,
- // These matter
- Resource::TYPE_THUMBNAIL_DATA, Resource::TYPE_AUX_COMPRESSION, Resource::TYPE_IMAGE_DATA,
- };
+ static const std::array& getOrder();
enum Flags : uint8_t {
FLAG_NONE = 0,
@@ -71,8 +71,16 @@ struct Resource {
return std::get(this->convertData());
}
- [[nodiscard]] int32_t getDataAsAuxCompressionLevel() const {
- return static_cast(std::get>(this->convertData())[1]);
+ [[nodiscard]] int16_t getDataAsAuxCompressionLevel() const {
+ return static_cast(std::get>(this->convertData())[1] & 0xffff);
+ }
+
+ [[nodiscard]] CompressionMethod getDataAsAuxCompressionMethod() const {
+ auto method = static_cast((std::get>(this->convertData())[1] & 0xffff0000) >> 16);
+ if (method <= 0) {
+ return CompressionMethod::DEFLATE;
+ }
+ return static_cast(method);
}
[[nodiscard]] uint32_t getDataAsAuxCompressionLength(uint8_t mip, uint8_t mipCount, uint16_t frame, uint16_t frameCount, uint16_t face, uint16_t faceCount) const {
@@ -159,7 +167,8 @@ class VTF {
bool createMips = true;
bool createThumbnail = true;
bool createReflectivity = true;
- uint8_t compressionLevel = 6;
+ int16_t compressionLevel = -1;
+ CompressionMethod compressionMethod = CompressionMethod::ZSTD;
float bumpMapScale = 1.f;
};
@@ -217,6 +226,10 @@ class VTF {
void setImageResizeMethods(ImageConversion::ResizeMethod imageWidthResizeMethod_, ImageConversion::ResizeMethod imageHeightResizeMethod_);
+ void setImageWidthResizeMethod(ImageConversion::ResizeMethod imageWidthResizeMethod_);
+
+ void setImageHeightResizeMethod(ImageConversion::ResizeMethod imageHeightResizeMethod_);
+
[[nodiscard]] uint16_t getWidth(uint8_t mip = 0) const;
[[nodiscard]] uint16_t getHeight(uint8_t mip = 0) const;
@@ -299,13 +312,17 @@ class VTF {
void removeExtendedFlagsResource();
- void setKeyValuesData(const std::string& value);
+ void setKeyValuesDataResource(const std::string& value);
+
+ void removeKeyValuesDataResource();
- void removeKeyValuesData();
+ [[nodiscard]] int16_t getCompressionLevel() const;
- [[nodiscard]] uint8_t getCompressionLevel() const;
+ void setCompressionLevel(int16_t newCompressionLevel);
- void setCompressionLevel(uint8_t newCompressionLevel);
+ [[nodiscard]] CompressionMethod getCompressionMethod() const;
+
+ void setCompressionMethod(CompressionMethod newCompressionMethod);
[[nodiscard]] bool hasImageData() const;
@@ -333,6 +350,8 @@ class VTF {
[[nodiscard]] std::vector getThumbnailDataAsRGBA8888() const;
+ void setThumbnail(std::span imageData_, ImageFormat format_, uint16_t width_, uint16_t height_);
+
void computeThumbnail(ImageConversion::ResizeFilter filter = ImageConversion::ResizeFilter::BILINEAR);
void removeThumbnail();
@@ -396,7 +415,8 @@ class VTF {
//uint8_t _padding3[4];
// These aren't in the header, these are for VTF modification
- uint8_t compressionLevel = 0;
+ int16_t compressionLevel = 0;
+ CompressionMethod compressionMethod = CompressionMethod::ZSTD;
ImageConversion::ResizeMethod imageWidthResizeMethod = ImageConversion::ResizeMethod::POWER_OF_TWO_BIGGER;
ImageConversion::ResizeMethod imageHeightResizeMethod = ImageConversion::ResizeMethod::POWER_OF_TWO_BIGGER;
};
diff --git a/lang/python/.gitignore b/lang/python/.gitignore
index 0c986d628..d88da90dd 100644
--- a/lang/python/.gitignore
+++ b/lang/python/.gitignore
@@ -1,4 +1,8 @@
-# Build Files
-__pycache__/
-dist/sourcepp/*
-!dist/sourcepp/__init__.py
+# Keep most Python-specific stuff in the root .gitignore
+# because scikit-build-core will use it to ignore everything
+# you want to ship!!!!!!!! This took like an hour to figure
+# out, thanks folks :D
+
+# Anyway
+
+wheelhouse/
diff --git a/lang/python/CMakeLists.txt b/lang/python/CMakeLists.txt
new file mode 100644
index 000000000..069cb3677
--- /dev/null
+++ b/lang/python/CMakeLists.txt
@@ -0,0 +1,39 @@
+# Load this to build the sourcepp Python package
+
+cmake_minimum_required(VERSION 3.25 FATAL_ERROR)
+set(CMAKE_OSX_ARCHITECTURES "arm64" CACHE INTERNAL "" FORCE)
+project(sourcepp_python)
+
+if (NOT SKBUILD)
+ message(WARNING "\
+This CMake file is meant to be executed using 'scikit-build-core'.
+Running it directly will almost certainly not produce the desired
+result. If you are a user trying to install this package, use the
+command below, which will install all necessary build dependencies,
+compile the package in an isolated environment, and then install it.
+=====================================================================
+ $ pip install .
+=====================================================================
+If you are a software developer, and this is your own package, then
+it is usually much more efficient to install the build dependencies
+in your environment once and use the following command that avoids
+a costly creation of a new virtual environment at every compilation:
+=====================================================================
+ $ pip install nanobind scikit-build-core[pyproject]
+ $ pip install --no-build-isolation -ve .
+=====================================================================
+You may optionally add -Ceditable.rebuild=true to auto-rebuild when
+the package is imported. Otherwise, you need to rerun the above
+after editing C++ files.")
+endif()
+
+set(CMAKE_POSITION_INDEPENDENT_CODE ON)
+
+# As weird as this looks, this is necessary for sdist wheel
+include(FetchContent)
+FetchContent_Declare(
+ sourcepp
+ GIT_REPOSITORY "https://github.com/craftablescience/sourcepp.git"
+ GIT_TAG "origin/main")
+set(SOURCEPP_BUILD_PYTHON_WRAPPERS ON CACHE INTERNAL "" FORCE)
+FetchContent_MakeAvailable(sourcepp)
diff --git a/lang/python/dist/sourcepp/__init__.py b/lang/python/dist/sourcepp/__init__.py
deleted file mode 100644
index d8f3097c9..000000000
--- a/lang/python/dist/sourcepp/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from __future__ import annotations
-
-from ._sourcepp_python import __author__, __doc__, __version__, gamepp, sourcepp, steampp, vcryptpp
-
-__all__ = ['__author__', '__doc__', '__version__', 'gamepp', 'sourcepp', 'steampp', 'vcryptpp']
diff --git a/lang/python/pyproject.toml b/lang/python/pyproject.toml
new file mode 100644
index 000000000..e086660d6
--- /dev/null
+++ b/lang/python/pyproject.toml
@@ -0,0 +1,53 @@
+[build-system]
+requires = ["scikit-build-core >=0.10.7", "nanobind >=1.3.2"]
+build-backend = "scikit_build_core.build"
+
+
+[project]
+name = "sourcepp"
+version = "2024.11.5dev1" # change __init__.py when this is updated
+authors = [{ name = "craftablescience", email = "lauralewisdev@gmail.com" }]
+maintainers = [{ name = "craftablescience", email = "lauralewisdev@gmail.com" }]
+description = "Several modern C++20 libraries for sanely parsing Valve formats."
+readme = "../../README.md"
+requires-python = ">=3.8"
+classifiers = [
+ "License :: OSI Approved :: MIT License",
+ "Intended Audience :: Developers",
+ "Programming Language :: Python :: 3 :: Only",
+ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
+]
+
+[project.urls]
+"homepage" = "https://github.com/craftablescience/sourcepp"
+"repository" = "https://github.com/craftablescience/sourcepp"
+"issue tracker" = "https://github.com/craftablescience/sourcepp/issues"
+"funding" = "https://ko-fi.com/craftablescience"
+
+[tool.scikit-build]
+minimum-version = "build-system.requires"
+build-dir = "build/{wheel_tag}"
+build.targets = ["sourcepp_python_all"]
+wheel.py-api = "cp312"
+wheel.license-files = ["../../LICENSE", "../../THIRDPARTY_LEGAL_NOTICES.txt"]
+build.verbose = true
+logging.level = "INFO"
+
+
+[tool.cibuildwheel]
+archs = ["auto64"]
+build-verbosity = 1
+#test-command = "pytest {project}/test"
+#test-requires = "pytest"
+#test-skip="cp38-macosx_*:arm64"
+
+[tool.cibuildwheel.macos]
+archs = ["arm64"]
+
+[tool.cibuildwheel.macos.environment]
+MACOSX_DEPLOYMENT_TARGET = "14.7"
diff --git a/lang/python/src/gamepp.h b/lang/python/src/gamepp.h
index 1d390f20e..8ac53f9a7 100644
--- a/lang/python/src/gamepp.h
+++ b/lang/python/src/gamepp.h
@@ -1,9 +1,11 @@
#pragma once
-#include
-#include
+#include
+#include
+#include
+#include
-namespace py = pybind11;
+namespace py = nanobind;
#include
@@ -14,16 +16,16 @@ inline void register_python(py::module_& m) {
using namespace gamepp;
py::class_(gamepp, "GameInstance")
- .def_static("find", &GameInstance::find, py::arg("window_name_override") = "")
- .def_property_readonly("window_title", &GameInstance::getWindowTitle)
- .def_property_readonly("window_pos", &GameInstance::getWindowPos)
- .def_property_readonly("window_size", &GameInstance::getWindowSize)
- .def("command", &GameInstance::command)
- .def("input_begin", &GameInstance::inputBegin)
- .def("input_end", &GameInstance::inputEnd)
- .def("input_once", &GameInstance::inputOnce)
- .def("input_hold", &GameInstance::inputHold)
- .def("wait", &GameInstance::wait);
+ .def_static("find", &GameInstance::find, py::arg("window_name_override") = "")
+ .def_prop_ro("window_title", &GameInstance::getWindowTitle)
+ .def_prop_ro("window_pos", &GameInstance::getWindowPos)
+ .def_prop_ro("window_size", &GameInstance::getWindowSize)
+ .def("command", &GameInstance::command, py::arg("command"))
+ .def("input_begin", &GameInstance::inputBegin, py::arg("input"))
+ .def("input_end", &GameInstance::inputEnd, py::arg("input"))
+ .def("input_once", &GameInstance::inputOnce, py::arg("input"))
+ .def("input_hold", &GameInstance::inputHold, py::arg("input"), py::arg("sec"))
+ .def("wait", &GameInstance::wait, py::arg("sec"));
}
} // namespace gamepp
diff --git a/lang/python/src/sourcepp.cpp b/lang/python/src/sourcepp.cpp
index 353151646..ff7935af8 100644
--- a/lang/python/src/sourcepp.cpp
+++ b/lang/python/src/sourcepp.cpp
@@ -12,11 +12,12 @@
#include "vcryptpp.h"
#endif
-PYBIND11_MODULE(_sourcepp_python, m) {
- m.doc() = "SourcePP: A Python wrapper around several modern C++20 libraries for sanely parsing Valve's formats.";
+#ifdef VTFPP
+#include "vtfpp.h"
+#endif
- m.attr("__author__") = "craftablescience";
- m.attr("__version__") = "dev";
+NB_MODULE(_sourcepp_impl, m) {
+ m.doc() = "SourcePP: A Python wrapper around several modern C++20 libraries for sanely parsing Valve's formats.";
sourcepp::register_python(m);
@@ -37,4 +38,10 @@ PYBIND11_MODULE(_sourcepp_python, m) {
#else
m.def_submodule("vcryptpp");
#endif
+
+#ifdef VTFPP
+ vtfpp::register_python(m);
+#else
+ m.def_submodule("vtfpp");
+#endif
}
diff --git a/lang/python/src/sourcepp.h b/lang/python/src/sourcepp.h
index b2908ae0e..079c37ae0 100644
--- a/lang/python/src/sourcepp.h
+++ b/lang/python/src/sourcepp.h
@@ -2,11 +2,10 @@
#include
-#include
-#include
-#include
+#include
+#include
-namespace py = pybind11;
+namespace py = nanobind;
#include
@@ -21,10 +20,7 @@ inline void register_python(py::module_& m) {
using namespace math;
const auto registerVecType = [&math](std::string_view name) {
- py::class_(math, name.data(), pybind11::buffer_protocol())
- .def_buffer([](V& v) -> py::buffer_info {
- return {v.data(), static_cast(v.size())};
- })
+ py::class_(math, name.data())
.def("__len__", &V::size)
.def("__setitem__", [](V& self, uint8_t index, typename V::value_type val) { self[index] = val; })
.def("__getitem__", [](V& self, uint8_t index) { return self[index]; })
diff --git a/lang/python/src/sourcepp/__init__.py b/lang/python/src/sourcepp/__init__.py
new file mode 100644
index 000000000..2cac8b428
--- /dev/null
+++ b/lang/python/src/sourcepp/__init__.py
@@ -0,0 +1,5 @@
+from ._sourcepp_impl import __doc__, gamepp, sourcepp, steampp, vcryptpp, vtfpp
+
+__author__ = "craftablescience"
+__version__ = "2024.11.5dev1" # change pyproject.toml version when this is updated
+__all__ = ['__author__', '__doc__', '__version__', 'gamepp', 'sourcepp', 'steampp', 'vcryptpp', 'vtfpp']
diff --git a/lang/python/src/steampp.h b/lang/python/src/steampp.h
index 12497d7aa..b1ff5d7fa 100644
--- a/lang/python/src/steampp.h
+++ b/lang/python/src/steampp.h
@@ -1,9 +1,11 @@
#pragma once
-#include
-#include
+#include
+#include
+#include
+#include
-namespace py = pybind11;
+namespace py = nanobind;
#include
@@ -14,21 +16,21 @@ inline void register_python(py::module_& m) {
using namespace steampp;
py::class_(steampp, "Steam")
- .def(py::init<>())
- .def_property_readonly("install_dir", &Steam::getInstallDir)
- .def_property_readonly("library_dirs", &Steam::getLibraryDirs)
- .def_property_readonly("sourcemod_dir", &Steam::getSourceModDir)
- .def_property_readonly("installed_apps", &Steam::getInstalledApps)
- .def("is_app_installed", &Steam::isAppInstalled)
- .def("get_app_name", &Steam::getAppName)
- .def("get_app_install_dir", &Steam::getAppInstallDir)
- .def("get_app_icon_path", &Steam::getAppIconPath)
- .def("get_app_logo_path", &Steam::getAppLogoPath)
- .def("get_app_box_art_path", &Steam::getAppBoxArtPath)
- .def("get_app_store_art_path", &Steam::getAppStoreArtPath)
- .def("is_app_using_source_engine", &Steam::isAppUsingSourceEngine)
- .def("is_app_using_source_2_engine", &Steam::isAppUsingSource2Engine)
- .def_property_readonly("valid", &Steam::operator bool);
+ .def(py::init<>())
+ .def_prop_ro("install_dir", &Steam::getInstallDir)
+ .def_prop_ro("library_dirs", &Steam::getLibraryDirs)
+ .def_prop_ro("sourcemod_dir", &Steam::getSourceModDir)
+ .def_prop_ro("installed_apps", &Steam::getInstalledApps)
+ .def("is_app_installed", &Steam::isAppInstalled, py::arg("appID"))
+ .def("get_app_name", &Steam::getAppName, py::arg("appID"))
+ .def("get_app_install_dir", &Steam::getAppInstallDir, py::arg("appID"))
+ .def("get_app_icon_path", &Steam::getAppIconPath, py::arg("appID"))
+ .def("get_app_logo_path", &Steam::getAppLogoPath, py::arg("appID"))
+ .def("get_app_box_art_path", &Steam::getAppBoxArtPath, py::arg("appID"))
+ .def("get_app_store_art_path", &Steam::getAppStoreArtPath, py::arg("appID"))
+ .def("is_app_using_source_engine", &Steam::isAppUsingSourceEngine, py::arg("appID"))
+ .def("is_app_using_source_2_engine", &Steam::isAppUsingSource2Engine, py::arg("appID"))
+ .def_prop_ro("__bool__", &Steam::operator bool, py::is_operator());
}
} // namespace steampp
diff --git a/lang/python/src/vcryptpp.h b/lang/python/src/vcryptpp.h
index 636a80aea..1ae642783 100644
--- a/lang/python/src/vcryptpp.h
+++ b/lang/python/src/vcryptpp.h
@@ -1,9 +1,9 @@
#pragma once
-#include
-#include
+#include
+#include
-namespace py = pybind11;
+namespace py = nanobind;
#include
@@ -21,10 +21,9 @@ inline void register_python(py::module_& m) {
VFONT.attr("MAGIC") = MAGIC;
- VFONT.def("decrypt_bytes", [](const py::bytes& data) -> py::bytes {
- const std::string_view dataView{data};
- const auto d = decrypt({reinterpret_cast(dataView.data()), dataView.size()});
- return {reinterpret_cast(d.data()), d.size()};
+ VFONT.def("decrypt_bytes", [](const py::bytes& data) {
+ const auto d = decrypt({reinterpret_cast(data.data()), data.size()});
+ return py::bytes{d.data(), d.size()};
}, py::arg("data"));
}
@@ -36,40 +35,39 @@ inline void register_python(py::module_& m) {
auto KnownCodes = VICE.def_submodule("KnownCodes");
using namespace KnownCodes;
- KnownCodes.attr("DEFAULT") = DEFAULT;
- KnownCodes.attr("CONTAGION_WEAPONS") = CONTAGION_WEAPONS;
- KnownCodes.attr("CONTAGION_SCRIPTS") = CONTAGION_SCRIPTS;
- KnownCodes.attr("COUNTER_STRIKE_SOURCE") = COUNTER_STRIKE_SOURCE;
+ KnownCodes.attr("DEFAULT") = DEFAULT;
+ KnownCodes.attr("CONTAGION_WEAPONS") = CONTAGION_WEAPONS;
+ KnownCodes.attr("CONTAGION_SCRIPTS") = CONTAGION_SCRIPTS;
+ KnownCodes.attr("COUNTER_STRIKE_SOURCE") = COUNTER_STRIKE_SOURCE;
KnownCodes.attr("COUNTER_STRIKE_GLOBAL_OFFENSIVE") = COUNTER_STRIKE_GLOBAL_OFFENSIVE;
- KnownCodes.attr("COUNTER_STRIKE_2") = COUNTER_STRIKE_2;
- KnownCodes.attr("COUNTER_STRIKE_PROMOD") = COUNTER_STRIKE_PROMOD;
- KnownCodes.attr("DAY_OF_DEFEAT_SOURCE") = DAY_OF_DEFEAT_SOURCE;
- KnownCodes.attr("DYSTOPIA_1_2") = DYSTOPIA_1_2;
- KnownCodes.attr("DYSTOPIA_1_3") = DYSTOPIA_1_3;
- KnownCodes.attr("GOLDEN_EYE_SOURCE") = GOLDEN_EYE_SOURCE;
- KnownCodes.attr("HALF_LIFE_2_CTF") = HALF_LIFE_2_CTF;
- KnownCodes.attr("HALF_LIFE_2_DM") = HALF_LIFE_2_DM;
- KnownCodes.attr("INSURGENCY") = INSURGENCY;
- KnownCodes.attr("LEFT_4_DEAD_2") = LEFT_4_DEAD_2;
- KnownCodes.attr("NO_MORE_ROOM_IN_HELL") = NO_MORE_ROOM_IN_HELL;
- KnownCodes.attr("NUCLEAR_DAWN") = NUCLEAR_DAWN;
- KnownCodes.attr("TACTICAL_INTERVENTION") = TACTICAL_INTERVENTION;
- KnownCodes.attr("TEAM_FORTRESS_2") = TEAM_FORTRESS_2;
- KnownCodes.attr("TEAM_FORTRESS_2_ITEMS") = TEAM_FORTRESS_2_ITEMS;
- KnownCodes.attr("THE_SHIP") = THE_SHIP;
- KnownCodes.attr("ZOMBIE_PANIC_SOURCE") = ZOMBIE_PANIC_SOURCE;
-
- KnownCodes.attr("EKV_GPU_DEFAULT") = EKV_GPU_DEFAULT;
- KnownCodes.attr("EKV_GPU_ALIEN_SWARM") = EKV_GPU_ALIEN_SWARM;
+ KnownCodes.attr("COUNTER_STRIKE_2") = COUNTER_STRIKE_2;
+ KnownCodes.attr("COUNTER_STRIKE_PROMOD") = COUNTER_STRIKE_PROMOD;
+ KnownCodes.attr("DAY_OF_DEFEAT_SOURCE") = DAY_OF_DEFEAT_SOURCE;
+ KnownCodes.attr("DYSTOPIA_1_2") = DYSTOPIA_1_2;
+ KnownCodes.attr("DYSTOPIA_1_3") = DYSTOPIA_1_3;
+ KnownCodes.attr("GOLDEN_EYE_SOURCE") = GOLDEN_EYE_SOURCE;
+ KnownCodes.attr("HALF_LIFE_2_CTF") = HALF_LIFE_2_CTF;
+ KnownCodes.attr("HALF_LIFE_2_DM") = HALF_LIFE_2_DM;
+ KnownCodes.attr("INSURGENCY") = INSURGENCY;
+ KnownCodes.attr("LEFT_4_DEAD_2") = LEFT_4_DEAD_2;
+ KnownCodes.attr("NO_MORE_ROOM_IN_HELL") = NO_MORE_ROOM_IN_HELL;
+ KnownCodes.attr("NUCLEAR_DAWN") = NUCLEAR_DAWN;
+ KnownCodes.attr("TACTICAL_INTERVENTION") = TACTICAL_INTERVENTION;
+ KnownCodes.attr("TEAM_FORTRESS_2") = TEAM_FORTRESS_2;
+ KnownCodes.attr("TEAM_FORTRESS_2_ITEMS") = TEAM_FORTRESS_2_ITEMS;
+ KnownCodes.attr("THE_SHIP") = THE_SHIP;
+ KnownCodes.attr("ZOMBIE_PANIC_SOURCE") = ZOMBIE_PANIC_SOURCE;
+
+ KnownCodes.attr("EKV_GPU_DEFAULT") = EKV_GPU_DEFAULT;
+ KnownCodes.attr("EKV_GPU_ALIEN_SWARM") = EKV_GPU_ALIEN_SWARM;
KnownCodes.attr("EKV_GPU_LEFT_4_DEAD_1") = EKV_GPU_LEFT_4_DEAD_1;
KnownCodes.attr("EKV_GPU_LEFT_4_DEAD_2") = EKV_GPU_LEFT_4_DEAD_2;
- KnownCodes.attr("EKV_GPU_PORTAL_2") = EKV_GPU_PORTAL_2;
+ KnownCodes.attr("EKV_GPU_PORTAL_2") = EKV_GPU_PORTAL_2;
}
- VICE.def("decrypt_bytes", [](const py::bytes& data, std::string_view code = KnownCodes::DEFAULT) -> py::bytes {
- const std::string_view dataView{data};
- const auto d = decrypt({reinterpret_cast(dataView.data()), dataView.size()}, code);
- return {reinterpret_cast(d.data()), d.size()};
+ VICE.def("decrypt_bytes", [](const py::bytes& data, std::string_view code = KnownCodes::DEFAULT) {
+ const auto d = decrypt({reinterpret_cast(data.data()), data.size()}, code);
+ return py::bytes{d.data(), d.size()};
}, py::arg("data"), py::arg("code") = KnownCodes::DEFAULT);
VICE.def("decrypt_str", [](std::string_view data, std::string_view code = KnownCodes::DEFAULT) -> std::string {
@@ -77,15 +75,14 @@ inline void register_python(py::module_& m) {
return {reinterpret_cast(d.data()), d.size()};
}, py::arg("data"), py::arg("code") = KnownCodes::DEFAULT);
- VICE.def("encrypt_bytes", [](const py::bytes& data, std::string_view code = KnownCodes::DEFAULT) -> py::bytes {
- const std::string_view dataView{data};
- const auto e = encrypt({reinterpret_cast(dataView.data()), dataView.size()}, code);
- return {reinterpret_cast(e.data()), e.size()};
+ VICE.def("encrypt_bytes", [](const py::bytes& data, std::string_view code = KnownCodes::DEFAULT) {
+ const auto d = encrypt({reinterpret_cast(data.data()), data.size()}, code);
+ return py::bytes{d.data(), d.size()};
}, py::arg("data"), py::arg("code") = KnownCodes::DEFAULT);
VICE.def("encrypt_str", [](std::string_view data, std::string_view code = KnownCodes::DEFAULT) -> std::string {
- const auto e = decrypt({reinterpret_cast(data.data()), data.size()}, code);
- return {reinterpret_cast(e.data()), e.size()};
+ const auto d = encrypt({reinterpret_cast(data.data()), data.size()}, code);
+ return {reinterpret_cast(d.data()), d.size()};
}, py::arg("data"), py::arg("code") = KnownCodes::DEFAULT);
}
}
diff --git a/lang/python/src/vtfpp.h b/lang/python/src/vtfpp.h
new file mode 100644
index 000000000..05a4b7a0e
--- /dev/null
+++ b/lang/python/src/vtfpp.h
@@ -0,0 +1,436 @@
+#pragma once
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+namespace py = nanobind;
+
+#include
+
+namespace vtfpp {
+
+void register_python(py::module_& m) {
+ using namespace vtfpp;
+ auto vtfpp = m.def_submodule("vtfpp");
+
+ py::enum_(vtfpp, "ImageFormat")
+ .value("RGBA8888", ImageFormat::RGBA8888)
+ .value("ABGR8888", ImageFormat::ABGR8888)
+ .value("RGB888", ImageFormat::RGB888)
+ .value("BGR888", ImageFormat::BGR888)
+ .value("RGB565", ImageFormat::RGB565)
+ .value("I8", ImageFormat::I8)
+ .value("IA88", ImageFormat::IA88)
+ .value("P8", ImageFormat::P8)
+ .value("A8", ImageFormat::A8)
+ .value("RGB888_BLUESCREEN", ImageFormat::RGB888_BLUESCREEN)
+ .value("BGR888_BLUESCREEN", ImageFormat::BGR888_BLUESCREEN)
+ .value("ARGB8888", ImageFormat::ARGB8888)
+ .value("BGRA8888", ImageFormat::BGRA8888)
+ .value("DXT1", ImageFormat::DXT1)
+ .value("DXT3", ImageFormat::DXT3)
+ .value("DXT5", ImageFormat::DXT5)
+ .value("BGRX8888", ImageFormat::BGRX8888)
+ .value("BGR565", ImageFormat::BGR565)
+ .value("BGRX5551", ImageFormat::BGRX5551)
+ .value("BGRA4444", ImageFormat::BGRA4444)
+ .value("DXT1_ONE_BIT_ALPHA", ImageFormat::DXT1_ONE_BIT_ALPHA)
+ .value("BGRA5551", ImageFormat::BGRA5551)
+ .value("UV88", ImageFormat::UV88)
+ .value("UVWQ8888", ImageFormat::UVWQ8888)
+ .value("RGBA16161616F", ImageFormat::RGBA16161616F)
+ .value("RGBA16161616", ImageFormat::RGBA16161616)
+ .value("UVLX8888", ImageFormat::UVLX8888)
+ .value("R32F", ImageFormat::R32F)
+ .value("RGB323232F", ImageFormat::RGB323232F)
+ .value("RGBA32323232F", ImageFormat::RGBA32323232F)
+ .value("RG1616F", ImageFormat::RG1616F)
+ .value("RG3232F", ImageFormat::RG3232F)
+ .value("RGBX8888", ImageFormat::RGBX8888)
+ .value("EMPTY", ImageFormat::EMPTY)
+ .value("ATI2N", ImageFormat::ATI2N)
+ .value("ATI1N", ImageFormat::ATI1N)
+ .value("RGBA1010102", ImageFormat::RGBA1010102)
+ .value("BGRA1010102", ImageFormat::BGRA1010102)
+ .value("R16F", ImageFormat::R16F)
+ .value("R8", ImageFormat::R8)
+ .value("BC7", ImageFormat::BC7)
+ .value("BC6H", ImageFormat::BC6H)
+ .export_values();
+
+ {
+ using namespace ImageFormatDetails;
+ auto ImageFormatDetails = vtfpp.def_submodule("ImageFormatDetails");
+
+ ImageFormatDetails.def("red", &red, py::arg("format"));
+ ImageFormatDetails.def("decompressedRed", &decompressedRed, py::arg("format"));
+ ImageFormatDetails.def("green", &green, py::arg("format"));
+ ImageFormatDetails.def("decompressedGreen", &decompressedGreen, py::arg("format"));
+ ImageFormatDetails.def("blue", &blue, py::arg("format"));
+ ImageFormatDetails.def("decompressedBlue", &decompressedBlue, py::arg("format"));
+ ImageFormatDetails.def("alpha", &alpha, py::arg("format"));
+ ImageFormatDetails.def("decompressedAlpha", &decompressedAlpha, py::arg("format"));
+ ImageFormatDetails.def("bpp", &bpp, py::arg("format"));
+ ImageFormatDetails.def("containerFormat", &containerFormat, py::arg("format"));
+ ImageFormatDetails.def("large", &large, py::arg("format"));
+ ImageFormatDetails.def("decimal", &decimal, py::arg("format"));
+ ImageFormatDetails.def("compressed", &compressed, py::arg("format"));
+ ImageFormatDetails.def("transparent", &transparent, py::arg("format"));
+ ImageFormatDetails.def("opaque", &opaque, py::arg("format"));
+
+ ImageFormatDetails.def("get_data_length", py::overload_cast(&getDataLength), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("slice_count") = 1);
+ ImageFormatDetails.def("get_data_length_extended", py::overload_cast(&getDataLength), py::arg("format"), py::arg("mip_count"), py::arg("frame_count"), py::arg("face_count"), py::arg("width"), py::arg("height"), py::arg("slice_count"));
+ ImageFormatDetails.def("get_data_position", [](ImageFormat format, uint8_t mip, uint8_t mipCount, uint16_t frame, uint16_t frameCount, uint8_t face, uint8_t faceCount, uint16_t width, uint16_t height, uint16_t slice = 0, uint16_t sliceCount = 1) -> std::pair {
+ uint32_t offset, length;
+ if (getDataPosition(offset, length, format, mip, mipCount, frame, frameCount, face, faceCount, width, height, slice, sliceCount)) {
+ return {offset, length};
+ }
+ return {0, 0};
+ }, py::arg("format"), py::arg("mip"), py::arg("mip_count"), py::arg("frame"), py::arg("frame_count"), py::arg("face"), py::arg("face_count"), py::arg("width"), py::arg("height"), py::arg("slice") = 0, py::arg("slice_count") = 1);
+ }
+
+ {
+ using namespace ImageDimensions;
+ auto ImageDimensions = vtfpp.def_submodule("ImageDimensions");
+
+ ImageDimensions.def("get_mip_dim", &getMipDim, py::arg("mip"), py::arg("dim"));
+ ImageDimensions.def("get_recommended_mip_count_for_dims", &getRecommendedMipCountForDims, py::arg("format"), py::arg("width"), py::arg("height"));
+ }
+
+ // Skip ImagePixel, difficult to bind
+
+ {
+ using namespace ImageConversion;
+ auto ImageConversion = vtfpp.def_submodule("ImageConversion");
+
+ // todo(python): still need to bind the following:
+ ImageConversion.def("convert_image_data_to_format", [](const py::bytes& imageData, ImageFormat oldFormat, ImageFormat newFormat, uint16_t width, uint16_t height) {
+ const auto d = convertImageDataToFormat({reinterpret_cast(imageData.data()), imageData.size()}, oldFormat, newFormat, width, height);
+ return py::bytes{d.data(), d.size()};
+ }, py::arg("image_data"), py::arg("old_format"), py::arg("new_format"), py::arg("width"), py::arg("height"));
+
+ ImageConversion.def("convert_several_image_data_to_format", [](const py::bytes& imageData, ImageFormat oldFormat, ImageFormat newFormat, uint8_t mipCount, uint16_t frameCount, uint16_t faceCount, uint16_t width, uint16_t height, uint16_t sliceCount) {
+ const auto d = convertSeveralImageDataToFormat({reinterpret_cast(imageData.data()), imageData.size()}, oldFormat, newFormat, mipCount, frameCount, faceCount, width, height, sliceCount);
+ return py::bytes{d.data(), d.size()};
+ }, py::arg("image_data"), py::arg("old_format"), py::arg("new_format"), py::arg("mip_count"), py::arg("frame_count"), py::arg("face_count"), py::arg("width"), py::arg("height"), py::arg("slice_count"));
+
+ py::enum_(ImageConversion, "FileFormat")
+ .value("DEFAULT", FileFormat::DEFAULT)
+ .value("PNG", FileFormat::PNG)
+ .value("JPEG", FileFormat::JPEG)
+ .value("BMP", FileFormat::BMP)
+ .value("TGA", FileFormat::TGA)
+ .value("HDR", FileFormat::HDR)
+ .export_values();
+
+ ImageConversion.def("get_default_file_format_for_image_format", &getDefaultFileFormatForImageFormat, py::arg("format"));
+
+ ImageConversion.def("convert_image_data_to_file", [](const py::bytes& imageData, ImageFormat format, uint16_t width, uint16_t height, FileFormat fileFormat = FileFormat::DEFAULT) {
+ const auto d = convertImageDataToFile({reinterpret_cast(imageData.data()), imageData.size()}, format, width, height, fileFormat);
+ return py::bytes{d.data(), d.size()};
+ }, py::arg("image_data"), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("file_format") = FileFormat::DEFAULT);
+
+ ImageConversion.def("convert_file_to_image_data", [](const py::bytes& fileData) -> std::tuple {
+ ImageFormat format;
+ int width, height, frame;
+ const auto d = convertFileToImageData({reinterpret_cast(fileData.data()), fileData.size()}, format, width, height, frame);
+ return {py::bytes{d.data(), d.size()}, format, width, height, frame};
+ }, py::arg("file_data"));
+
+ py::enum_(ImageConversion, "ResizeEdge")
+ .value("CLAMP", ResizeEdge::CLAMP)
+ .value("REFLECT", ResizeEdge::REFLECT)
+ .value("WRAP", ResizeEdge::WRAP)
+ .value("ZERO", ResizeEdge::ZERO)
+ .export_values();
+
+ py::enum_(ImageConversion, "ResizeFilter")
+ .value("DEFAULT", ResizeFilter::DEFAULT)
+ .value("BOX", ResizeFilter::BOX)
+ .value("BILINEAR", ResizeFilter::BILINEAR)
+ .value("CUBIC_BSPLINE", ResizeFilter::CUBIC_BSPLINE)
+ .value("CATMULLROM", ResizeFilter::CATMULLROM)
+ .value("MITCHELL", ResizeFilter::MITCHELL)
+ .export_values();
+
+ py::enum_(ImageConversion, "ResizeMethod")
+ .value("NONE", ResizeMethod::NONE)
+ .value("POWER_OF_TWO_BIGGER", ResizeMethod::POWER_OF_TWO_BIGGER)
+ .value("POWER_OF_TWO_SMALLER", ResizeMethod::POWER_OF_TWO_SMALLER)
+ .value("POWER_OF_TWO_NEAREST", ResizeMethod::POWER_OF_TWO_NEAREST)
+ .export_values();
+
+ ImageConversion.def("get_resized_dim", &getResizedDim, py::arg("n"), py::arg("resize_method"));
+ ImageConversion.def("get_resized_dims", [](uint16_t width, ResizeMethod widthResize, uint16_t height, ResizeMethod heightResize) -> std::pair {
+ setResizedDims(width, widthResize, height, heightResize);
+ return {width, height};
+ }, py::arg("width"), py::arg("resize_width"), py::arg("height"), py::arg("resize_height"));
+
+ ImageConversion.def("resize_image_data", [](const py::bytes& imageData, ImageFormat format, uint16_t width, uint16_t newWidth, uint16_t height, uint16_t newHeight, bool srgb, ResizeFilter filter, ResizeEdge edge = ResizeEdge::CLAMP) {
+ const auto d = resizeImageData({reinterpret_cast(imageData.data()), imageData.size()}, format, width, newWidth, height, newHeight, srgb, filter, edge);
+ return py::bytes{d.data(), d.size()};
+ }, py::arg("image_data"), py::arg("format"), py::arg("width"), py::arg("new_width"), py::arg("height"), py::arg("new_height"), py::arg("srgb"), py::arg("filter"), py::arg("edge") = ResizeEdge::CLAMP);
+
+ ImageConversion.def("resize_image_data_strict", [](const py::bytes& imageData, ImageFormat format, uint16_t width, uint16_t newWidth, ResizeMethod widthResize, uint16_t height, uint16_t newHeight, ResizeMethod heightResize, bool srgb, ResizeFilter filter, ResizeEdge edge = ResizeEdge::CLAMP) -> std::tuple {
+ uint16_t widthOut, heightOut;
+ const auto d = resizeImageDataStrict({reinterpret_cast(imageData.data()), imageData.size()}, format, width, newWidth, widthOut, widthResize, height, newHeight, heightOut, heightResize, srgb, filter, edge);
+ return {py::bytes{d.data(), d.size()}, widthOut, heightOut};
+ }, py::arg("image_data"), py::arg("format"), py::arg("width"), py::arg("new_width"), py::arg("width_resize"), py::arg("height"), py::arg("new_height"), py::arg("height_resize"), py::arg("srgb"), py::arg("filter"), py::arg("edge") = ResizeEdge::CLAMP);
+
+ // Skip extractChannelFromImageData, difficult to bind
+ }
+
+ py::class_(vtfpp, "PPLImage")
+ .def_ro("width", &PPL::Image::width)
+ .def_ro("height", &PPL::Image::height)
+ .def_prop_ro("data", [](const PPL::Image& self) {
+ return py::bytes{self.data.data(), self.data.size()};
+ });
+
+ py::class_(vtfpp, "PPL")
+ .def(py::init(), py::arg("checksum"), py::arg("format") = ImageFormat::RGB888, py::arg("version") = 0)
+ .def("__init__", [](PPL* self, const py::bytes& pplData) {
+ return new(self) PPL{{reinterpret_cast(pplData.data()), pplData.size()}};
+ }, py::arg("ppl_data"))
+ .def(py::init(), py::arg("path"))
+ .def("__bool__", &PPL::operator bool, py::is_operator())
+ .def_prop_rw("version", &PPL::getVersion, &PPL::setVersion)
+ .def_prop_rw("checksum", &PPL::getChecksum, &PPL::setChecksum)
+ .def_prop_rw("format", &PPL::getFormat, &PPL::setFormat)
+ .def("has_image_for_lod", &PPL::hasImageForLOD, py::arg("lod"))
+ .def_prop_ro("image_lods", &PPL::getImageLODs)
+ .def("get_image_raw", [](const PPL& self, uint32_t lod = 0) -> std::optional {
+ const auto* image = self.getImageRaw(lod);
+ if (!image) {
+ return std::nullopt;
+ }
+ return *image;
+ }, py::arg("lod"))
+ .def("get_image_as", &PPL::getImageAs, py::arg("new_format"), py::arg("lod"))
+ .def("get_image_as_rgb888", &PPL::getImageAsRGB888, py::arg("lod"))
+ .def("set_image", [](PPL& self, const py::bytes& imageData, ImageFormat format, uint32_t width, uint32_t height, uint32_t lod = 0) {
+ self.setImage({reinterpret_cast(imageData.data()), imageData.size()}, format, width, height, lod);
+ }, py::arg("imageData"), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("lod") = 0)
+ .def("set_image_resized", [](PPL& self, const py::bytes& imageData, ImageFormat format, uint32_t width, uint32_t height, uint32_t resizedWidth, uint32_t resizedHeight, uint32_t lod = 0, ImageConversion::ResizeFilter filter = ImageConversion::ResizeFilter::BILINEAR) {
+ self.setImage({reinterpret_cast(imageData.data()), imageData.size()}, format, width, height, resizedWidth, resizedHeight, lod, filter);
+ }, py::arg("imageData"), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("resized_width"), py::arg("resized_height"), py::arg("lod") = 0, py::arg("filter") = ImageConversion::ResizeFilter::BILINEAR)
+ .def("set_image_from_file", py::overload_cast(&PPL::setImage), py::arg("image_path"), py::arg("lod") = 0)
+ .def("set_image_resized_from_file", py::overload_cast(&PPL::setImage), py::arg("image_path"), py::arg("resized_width"), py::arg("resized_height"), py::arg("lod") = 0, py::arg("filter") = ImageConversion::ResizeFilter::BILINEAR)
+ .def("save_image", [](const PPL& self, uint32_t lod = 0, ImageConversion::FileFormat fileFormat = ImageConversion::FileFormat::DEFAULT) {
+ const auto d = self.saveImageToFile(lod, fileFormat);
+ return py::bytes{d.data(), d.size()};
+ }, py::arg("lod") = 0, py::arg("file_format") = ImageConversion::FileFormat::DEFAULT)
+ .def("save_image_to_file", py::overload_cast(&PPL::saveImageToFile, py::const_), py::arg("image_path"), py::arg("lod") = 0, py::arg("file_format") = ImageConversion::FileFormat::DEFAULT)
+ .def("bake", [](PPL& self) {
+ const auto d = self.bake();
+ return py::bytes{d.data(), d.size()};
+ })
+ .def("bake_to_file", py::overload_cast(&PPL::bake), py::arg("ppl_path"));
+
+ vtfpp.attr("VTF_SIGNATURE") = VTF_SIGNATURE;
+
+ py::enum_(vtfpp, "CompressionMethod")
+ .value("DEFLATE", CompressionMethod::DEFLATE)
+ .value("ZSTD", CompressionMethod::ZSTD)
+ .export_values();
+
+ py::enum_(vtfpp, "ResourceType")
+ .value("UNKNOWN", Resource::TYPE_UNKNOWN)
+ .value("THUMBNAIL_DATA", Resource::TYPE_THUMBNAIL_DATA)
+ .value("IMAGE_DATA", Resource::TYPE_IMAGE_DATA)
+ .value("PARTICLE_SHEET_DATA", Resource::TYPE_PARTICLE_SHEET_DATA)
+ .value("CRC", Resource::TYPE_CRC)
+ .value("LOD_CONTROL_INFO", Resource::TYPE_LOD_CONTROL_INFO)
+ .value("EXTENDED_FLAGS", Resource::TYPE_EXTENDED_FLAGS)
+ .value("KEYVALUES_DATA", Resource::TYPE_KEYVALUES_DATA)
+ .value("AUX_COMPRESSION", Resource::TYPE_AUX_COMPRESSION)
+ .export_values();
+
+ py::enum_(vtfpp, "ResourceFlags")
+ .value("NONE", Resource::FLAG_NONE)
+ .value("LOCAL_DATA", Resource::FLAG_LOCAL_DATA)
+ .export_values();
+
+ // Skip Resource, mostly useless outside C++
+
+ py::enum_(vtfpp, "VTFFlags")
+ .value("NONE", VTF::FLAG_NONE)
+ .value("POINT_SAMPLE", VTF::FLAG_POINT_SAMPLE)
+ .value("TRILINEAR", VTF::FLAG_TRILINEAR)
+ .value("CLAMP_S", VTF::FLAG_CLAMP_S)
+ .value("CLAMP_T", VTF::FLAG_CLAMP_T)
+ .value("ANISOTROPIC", VTF::FLAG_ANISOTROPIC)
+ .value("HINT_DXT5", VTF::FLAG_HINT_DXT5)
+ .value("SRGB", VTF::FLAG_SRGB)
+ .value("NO_COMPRESS", VTF::FLAG_NO_COMPRESS)
+ .value("NORMAL", VTF::FLAG_NORMAL)
+ .value("NO_MIP", VTF::FLAG_NO_MIP)
+ .value("NO_LOD", VTF::FLAG_NO_LOD)
+ .value("MIN_MIP", VTF::FLAG_MIN_MIP)
+ .value("PROCEDURAL", VTF::FLAG_PROCEDURAL)
+ .value("ONE_BIT_ALPHA", VTF::FLAG_ONE_BIT_ALPHA)
+ .value("MULTI_BIT_ALPHA", VTF::FLAG_MULTI_BIT_ALPHA)
+ .value("ENVMAP", VTF::FLAG_ENVMAP)
+ .value("RENDERTARGET", VTF::FLAG_RENDERTARGET)
+ .value("DEPTH_RENDERTARGET", VTF::FLAG_DEPTH_RENDERTARGET)
+ .value("NO_DEBUG_OVERRIDE", VTF::FLAG_NO_DEBUG_OVERRIDE)
+ .value("SINGLE_COPY", VTF::FLAG_SINGLE_COPY)
+ .value("ONE_OVER_MIP_LEVEL_IN_ALPHA", VTF::FLAG_ONE_OVER_MIP_LEVEL_IN_ALPHA)
+ .value("PREMULTIPLY_COLOR_BY_ONE_OVER_MIP_LEVEL", VTF::FLAG_PREMULTIPLY_COLOR_BY_ONE_OVER_MIP_LEVEL)
+ .value("NORMAL_TO_DUDV", VTF::FLAG_NORMAL_TO_DUDV)
+ .value("ALPHA_TEST_MIP_GENERATION", VTF::FLAG_ALPHA_TEST_MIP_GENERATION)
+ .value("NO_DEPTH_BUFFER", VTF::FLAG_NO_DEPTH_BUFFER)
+ .value("NICE_FILTERED", VTF::FLAG_NICE_FILTERED)
+ .value("CLAMP_U", VTF::FLAG_CLAMP_U)
+ .value("VERTEX_TEXTURE", VTF::FLAG_VERTEX_TEXTURE)
+ .value("SSBUMP", VTF::FLAG_SSBUMP)
+ .value("UNFILTERABLE_OK", VTF::FLAG_UNFILTERABLE_OK)
+ .value("BORDER", VTF::FLAG_BORDER)
+ .value("SPECVAR_RED", VTF::FLAG_SPECVAR_RED)
+ .value("SPECVAR_ALPHA", VTF::FLAG_SPECVAR_ALPHA)
+ .export_values();
+
+ py::class_(vtfpp, "VTFCreationOptions")
+ .def(py::init<>())
+ .def_rw("major_version", &VTF::CreationOptions::majorVersion)
+ .def_rw("minor_version", &VTF::CreationOptions::minorVersion)
+ .def_rw("output_format", &VTF::CreationOptions::outputFormat)
+ .def_rw("width_resize_method", &VTF::CreationOptions::widthResizeMethod)
+ .def_rw("height_resize_method", &VTF::CreationOptions::heightResizeMethod)
+ .def_rw("filter", &VTF::CreationOptions::filter)
+ .def_rw("flags", &VTF::CreationOptions::flags)
+ .def_rw("initial_frame_count", &VTF::CreationOptions::initialFrameCount)
+ .def_rw("start_frame", &VTF::CreationOptions::startFrame)
+ .def_rw("is_cubemap", &VTF::CreationOptions::isCubeMap)
+ .def_rw("has_spheremap", &VTF::CreationOptions::hasSphereMap)
+ .def_rw("initial_slice_count", &VTF::CreationOptions::initialSliceCount)
+ .def_rw("create_mips", &VTF::CreationOptions::createMips)
+ .def_rw("create_thumbnail", &VTF::CreationOptions::createThumbnail)
+ .def_rw("create_reflectivity", &VTF::CreationOptions::createReflectivity)
+ .def_rw("compression_level", &VTF::CreationOptions::compressionLevel)
+ .def_rw("compression_method", &VTF::CreationOptions::compressionMethod)
+ .def_rw("bumpmap_scale", &VTF::CreationOptions::bumpMapScale);
+
+ py::class_(vtfpp, "VTF")
+ .def_ro_static("FLAG_MASK_GENERATED", &VTF::FLAG_MASK_GENERATED)
+ .def_ro_static("FORMAT_UNCHANGED", &VTF::FORMAT_UNCHANGED)
+ .def_ro_static("FORMAT_DEFAULT", &VTF::FORMAT_DEFAULT)
+ .def_ro_static("MAX_RESOURCES", &VTF::MAX_RESOURCES)
+ .def(py::init<>())
+ .def("__init__", [](VTF* self, const py::bytes& vtfData, bool parseHeaderOnly = false) {
+ return new(self) VTF{std::span{reinterpret_cast(vtfData.data()), vtfData.size()}, parseHeaderOnly};
+ }, py::arg("vtf_data"), py::arg("parse_header_only") = false)
+ .def(py::init(), py::arg("vtf_path"), py::arg("parse_header_only") = false)
+ .def("__bool__", &VTF::operator bool, py::is_operator())
+ .def_static("create_and_bake", [](const py::bytes& imageData, ImageFormat format, uint16_t width, uint16_t height, const std::string& vtfPath, VTF::CreationOptions options) {
+ VTF::create({reinterpret_cast(imageData.data()), imageData.size()}, format, width, height, vtfPath, options);
+ }, py::arg("image_data"), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("vtf_path"), py::arg("creation_options") = VTF::CreationOptions{})
+ .def_static("create_blank_and_bake", py::overload_cast(&VTF::create), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("vtf_path"), py::arg("creation_options") = VTF::CreationOptions{})
+ .def_static("create", [](const py::bytes& imageData, ImageFormat format, uint16_t width, uint16_t height, VTF::CreationOptions options) {
+ return VTF::create({reinterpret_cast(imageData.data()), imageData.size()}, format, width, height, options);
+ }, py::arg("image_data"), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("creation_options") = VTF::CreationOptions{})
+ .def_static("create_blank", py::overload_cast(&VTF::create), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("creation_options") = VTF::CreationOptions{})
+ .def_static("create_from_file_and_bake", py::overload_cast(&VTF::create), py::arg("image_path"), py::arg("vtf_path"), py::arg("creation_options") = VTF::CreationOptions{})
+ .def_static("create_from_file", py::overload_cast(&VTF::create), py::arg("image_path"), py::arg("creation_options") = VTF::CreationOptions{})
+ .def_prop_rw("version_major", &VTF::getMajorVersion, &VTF::setMajorVersion)
+ .def_prop_rw("version_minor", &VTF::getMinorVersion, &VTF::setMinorVersion)
+ .def_prop_rw("image_width_resize_method", &VTF::getImageWidthResizeMethod, &VTF::setImageWidthResizeMethod)
+ .def_prop_rw("image_height_resize_method", &VTF::getImageHeightResizeMethod, &VTF::setImageHeightResizeMethod)
+ .def_prop_ro("width", &VTF::getWidth)
+ .def("width_for_mip", [](const VTF& self, uint8_t mip = 0) { return self.getWidth(mip); }, py::arg("mip") = 0)
+ .def_prop_ro("height", &VTF::getHeight)
+ .def("height_for_mip", [](const VTF& self, uint8_t mip = 0) { return self.getHeight(mip); }, py::arg("mip") = 0)
+ .def("set_size", &VTF::setSize, py::arg("width"), py::arg("height"), py::arg("filter"))
+ .def_prop_rw("flags", &VTF::getFlags, &VTF::setFlags)
+ .def("add_flags", &VTF::addFlags, py::arg("flags"))
+ .def("remove_flags", &VTF::removeFlags, py::arg("flags"))
+ .def_prop_ro("format", &VTF::getFormat)
+ .def("set_format", &VTF::setFormat, py::arg("new_format"), py::arg("filter") = ImageConversion::ResizeFilter::BILINEAR)
+ .def_prop_rw("mip_count", &VTF::getMipCount, &VTF::setMipCount)
+ .def("set_recommended_mip_count", &VTF::setRecommendedMipCount)
+ .def("compute_mips", &VTF::computeMips, py::arg("filter") = ImageConversion::ResizeFilter::BILINEAR)
+ .def_prop_rw("frame_count", &VTF::getFrameCount, &VTF::setFrameCount)
+ .def_prop_ro("face_count", &VTF::getFaceCount)
+ .def("set_face_count", &VTF::setFaceCount, py::arg("is_cubemap"), py::arg("has_spheremap") = false)
+ .def_prop_rw("slice_count", &VTF::getSliceCount, &VTF::setSliceCount)
+ .def("set_frame_face_and_slice_count", &VTF::setFrameFaceAndSliceCount, py::arg("new_frame_count"), py::arg("is_cubemap"), py::arg("has_spheremap") = false, py::arg("new_slice_count") = 1)
+ .def_prop_rw("start_frame", &VTF::getStartFrame, &VTF::setStartFrame)
+ .def_prop_rw("reflectivity", &VTF::getReflectivity, &VTF::setReflectivity)
+ .def("compute_reflectivity", &VTF::computeReflectivity)
+ .def_prop_rw("bumpmap_scale", &VTF::getBumpMapScale, &VTF::setBumpMapScale)
+ .def_prop_ro("thumbnail_format", &VTF::getThumbnailFormat)
+ .def_prop_ro("thumbnail_width", &VTF::getThumbnailWidth)
+ .def_prop_ro("thumbnail_height", &VTF::getThumbnailHeight)
+ // Skip getResources
+ // Skip getResource
+ .def("set_particle_sheet_resource", [](VTF& self, const py::bytes& value) { return self.setParticleSheetResource({reinterpret_cast(value.data()), value.size()}); }, py::arg("value"))
+ .def("remove_particle_sheet_resource", &VTF::removeParticleSheetResource)
+ .def("set_crc_resource", &VTF::setCRCResource, py::arg("value"))
+ .def("remove_crc_resource", &VTF::removeCRCResource)
+ .def("set_lod_resource", &VTF::setLODResource, py::arg("u"), py::arg("v"))
+ .def("remove_lod_resource", &VTF::removeLODResource)
+ .def("set_extended_flags_resource", &VTF::setExtendedFlagsResource, py::arg("value"))
+ .def("remove_extended_flags_resource", &VTF::removeExtendedFlagsResource)
+ .def("set_keyvalues_data_resource", &VTF::setKeyValuesDataResource, py::arg("value"))
+ .def("remove_keyvalues_data_resource", &VTF::removeKeyValuesDataResource)
+ .def_prop_rw("compression_level", &VTF::getCompressionLevel, &VTF::setCompressionLevel)
+ .def_prop_rw("compression_method", &VTF::getCompressionMethod, &VTF::setCompressionMethod)
+ .def("has_image_data", &VTF::hasImageData)
+ .def("image_data_is_srgb", &VTF::imageDataIsSRGB)
+ .def("get_image_data_raw", [](const VTF& self, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) {
+ const auto d = self.getImageDataRaw(mip, frame, face, slice);
+ return py::bytes{d.data(), d.size()};
+ }, py::arg("mip") = 0, py::arg("frame") = 0, py::arg("face") = 0, py::arg("slice") = 0)
+ .def("get_image_data_as", [](const VTF& self, ImageFormat newFormat, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) {
+ const auto d = self.getImageDataAs(newFormat, mip, frame, face, slice);
+ return py::bytes{d.data(), d.size()};
+ }, py::arg("new_format"), py::arg("mip") = 0, py::arg("frame") = 0, py::arg("face") = 0, py::arg("slice") = 0)
+ .def("get_image_data_as_rgba8888", [](const VTF& self, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) {
+ const auto d = self.getImageDataAsRGBA8888(mip, frame, face, slice);
+ return py::bytes{d.data(), d.size()};
+ }, py::arg("mip") = 0, py::arg("frame") = 0, py::arg("face") = 0, py::arg("slice") = 0)
+ .def("set_image", [](VTF& self, const py::bytes& imageData, ImageFormat format, uint16_t width, uint16_t height, ImageConversion::ResizeFilter filter = ImageConversion::ResizeFilter::BILINEAR, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) {
+ return self.setImage({reinterpret_cast(imageData.data()), imageData.size()}, format, width, height, filter, mip, frame, face, slice);
+ }, py::arg("image_data"), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("filter"), py::arg("mip") = 0, py::arg("frame") = 0, py::arg("face") = 0, py::arg("slice") = 0)
+ .def("set_image_from_file", py::overload_cast(&VTF::setImage), py::arg("image_path"), py::arg("filter") = ImageConversion::ResizeFilter::BILINEAR, py::arg("mip") = 0, py::arg("frame") = 0, py::arg("face") = 0, py::arg("slice") = 0)
+ .def("save_image", [](const VTF& self, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0, ImageConversion::FileFormat fileFormat = ImageConversion::FileFormat::DEFAULT) {
+ const auto d = self.saveImageToFile(mip, frame, face, slice, fileFormat);
+ return py::bytes{d.data(), d.size()};
+ }, py::arg("mip") = 0, py::arg("frame") = 0, py::arg("face") = 0, py::arg("slice") = 0, py::arg("file_format") = ImageConversion::FileFormat::DEFAULT)
+ .def("save_image_to_file", py::overload_cast(&VTF::saveImageToFile, py::const_), py::arg("image_path"), py::arg("mip") = 0, py::arg("frame") = 0, py::arg("face") = 0, py::arg("slice") = 0, py::arg("file_format") = ImageConversion::FileFormat::DEFAULT)
+ .def("has_thumbnail_data", &VTF::hasThumbnailData)
+ .def("get_thumbnail_data_raw", [](const VTF& self) {
+ const auto d = self.getThumbnailDataRaw();
+ return py::bytes{d.data(), d.size()};
+ })
+ .def("get_thumbnail_data_as", [](const VTF& self, ImageFormat newFormat) {
+ const auto d = self.getThumbnailDataAs(newFormat);
+ return py::bytes{d.data(), d.size()};
+ }, py::arg("new_format"))
+ .def("get_thumbnail_data_as_rgba8888", [](const VTF& self) {
+ const auto d = self.getThumbnailDataAsRGBA8888();
+ return py::bytes{d.data(), d.size()};
+ })
+ .def("set_thumbnail", [](VTF& self, const py::bytes& imageData, ImageFormat format, uint16_t width, uint16_t height) {
+ return self.setThumbnail({reinterpret_cast(imageData.data()), imageData.size()}, format, width, height);
+ }, py::arg("image_data"), py::arg("format"), py::arg("width"), py::arg("height"))
+ .def("compute_thumbnail", &VTF::computeThumbnail, py::arg("filter") = ImageConversion::ResizeFilter::BILINEAR)
+ .def("remove_thumbnail", &VTF::removeThumbnail)
+ .def("save_thumbnail", [](const VTF& self, ImageConversion::FileFormat fileFormat = ImageConversion::FileFormat::DEFAULT) {
+ const auto d = self.saveThumbnailToFile(fileFormat);
+ return py::bytes{d.data(), d.size()};
+ }, py::arg("file_format") = ImageConversion::FileFormat::DEFAULT)
+ .def("save_thumbnail_to_file", py::overload_cast(&VTF::saveThumbnailToFile, py::const_), py::arg("image_path"), py::arg("file_format") = ImageConversion::FileFormat::DEFAULT)
+ .def("bake", [](const VTF& self) {
+ const auto d = self.bake();
+ return py::bytes{d.data(), d.size()};
+ })
+ .def("bake_to_file", py::overload_cast(&VTF::bake, py::const_), py::arg("vtf_path"));
+}
+
+} // namespace vtfpp
diff --git a/src/vpkpp/_vpkpp.cmake b/src/vpkpp/_vpkpp.cmake
index 320ebe3ff..e264861d9 100644
--- a/src/vpkpp/_vpkpp.cmake
+++ b/src/vpkpp/_vpkpp.cmake
@@ -1,5 +1,5 @@
add_pretty_parser(vpkpp
- DEPS cryptopp::cryptopp MINIZIP::minizip sourcepp::bsppp sourcepp::kvpp
+ DEPS cryptopp::cryptopp libzstd_static MINIZIP::minizip sourcepp::bsppp sourcepp::kvpp
DEPS_PUBLIC tsl::hat_trie
PRECOMPILED_HEADERS
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/BSP.h"
@@ -32,6 +32,5 @@ add_pretty_parser(vpkpp
"${CMAKE_CURRENT_LIST_DIR}/PackFile.cpp")
if(SOURCEPP_VPKPP_SUPPORT_VPK_V54)
- target_link_libraries(vpkpp PRIVATE libzstd_static)
target_compile_definitions(vpkpp PRIVATE VPKPP_SUPPORT_VPK_V54)
endif()
diff --git a/src/vtfpp/VTF.cpp b/src/vtfpp/VTF.cpp
index 598290449..6da2a5165 100644
--- a/src/vtfpp/VTF.cpp
+++ b/src/vtfpp/VTF.cpp
@@ -11,6 +11,7 @@
#include
#include
+#include
#include
@@ -19,25 +20,66 @@ using namespace vtfpp;
namespace {
-std::vector compressData(std::span data, int level) {
- mz_ulong compressedSize = mz_compressBound(data.size());
- std::vector out(compressedSize);
+std::vector compressData(std::span data, int16_t level, CompressionMethod method) {
+ switch (method) {
+ using enum CompressionMethod;
+ case DEFLATE: {
+ mz_ulong compressedSize = mz_compressBound(data.size());
+ std::vector out(compressedSize);
+
+ int status = MZ_OK;
+ while ((status = mz_compress2(reinterpret_cast(out.data()), &compressedSize, reinterpret_cast(data.data()), data.size(), level)) == MZ_BUF_ERROR) {
+ compressedSize *= 2;
+ out.resize(compressedSize);
+ }
- int status = MZ_OK;
- while ((status = mz_compress2(reinterpret_cast(out.data()), &compressedSize, reinterpret_cast(data.data()), data.size(), level)) == MZ_BUF_ERROR) {
- compressedSize *= 2;
- out.resize(compressedSize);
- }
+ if (status != MZ_OK) {
+ return {};
+ }
+ out.resize(compressedSize);
+ return out;
+ }
+ case ZSTD: {
+ if (level < 0) {
+ level = 6;
+ }
- if (status != MZ_OK) {
- return {};
+ auto expectedSize = ZSTD_compressBound(data.size());
+ std::vector out(expectedSize);
+
+ auto compressedSize = ZSTD_compress(out.data(), expectedSize, data.data(), data.size(), level);
+ if (ZSTD_isError(compressedSize)) {
+ return {};
+ }
+
+ out.resize(compressedSize);
+ return out;
+ }
}
- out.resize(compressedSize);
- return out;
+ return {};
}
} // namespace
+const std::array& Resource::getOrder() {
+ static constinit std::array typeArray{
+ TYPE_THUMBNAIL_DATA,
+ TYPE_IMAGE_DATA,
+ TYPE_PARTICLE_SHEET_DATA,
+ TYPE_CRC,
+ TYPE_LOD_CONTROL_INFO,
+ TYPE_EXTENDED_FLAGS,
+ TYPE_KEYVALUES_DATA,
+ TYPE_AUX_COMPRESSION,
+ };
+ static bool unsorted = true;
+ if (unsorted) {
+ std::sort(typeArray.begin(), typeArray.end());
+ unsorted = false;
+ }
+ return typeArray;
+}
+
Resource::ConvertedData Resource::convertData() const {
switch (this->type) {
case TYPE_CRC:
@@ -146,17 +188,17 @@ VTF::VTF(std::vector&& vtfData, bool parseHeaderOnly)
Resource* lastResource = nullptr;
for (int i = 0; i < resourceCount; i++) {
- auto& [type, flags, data] = this->resources.emplace_back();
+ auto& [type, flags_, data_] = this->resources.emplace_back();
auto typeAndFlags = stream.read();
type = static_cast(typeAndFlags & 0xffffff); // last 3 bytes
- flags = static_cast(typeAndFlags >> 24); // first byte
- data = stream.read_span(4);
+ flags_ = static_cast(typeAndFlags >> 24); // first byte
+ data_ = stream.read_span(4);
- if (!(flags & Resource::FLAG_LOCAL_DATA)) {
+ if (!(flags_ & Resource::FLAG_LOCAL_DATA)) {
if (lastResource) {
auto lastOffset = *reinterpret_cast(lastResource->data.data());
- auto currentOffset = *reinterpret_cast(data.data());
+ auto currentOffset = *reinterpret_cast(data_.data());
auto curPos = stream.tell();
stream.seek(lastOffset);
@@ -191,9 +233,20 @@ VTF::VTF(std::vector&& vtfData, bool parseHeaderOnly)
if (uint32_t newOffset, newLength; ImageFormatDetails::getDataPosition(newOffset, newLength, this->format, i, this->mipCount, j, this->frameCount, k, faceCount, this->width, this->height, 0, this->getSliceCount())) {
// Keep in mind that slices are compressed together
mz_ulong decompressedImageDataSize = newLength * this->sliceCount;
- if (mz_uncompress(reinterpret_cast(decompressedImageData.data() + newOffset), &decompressedImageDataSize, reinterpret_cast(imageResource->data.data() + oldOffset), oldLength) != MZ_OK) {
- this->opened = false;
- return;
+ switch (auxResource->getDataAsAuxCompressionMethod()) {
+ using enum CompressionMethod;
+ case DEFLATE:
+ if (mz_uncompress(reinterpret_cast(decompressedImageData.data() + newOffset), &decompressedImageDataSize, reinterpret_cast(imageResource->data.data() + oldOffset), oldLength) != MZ_OK) {
+ this->opened = false;
+ return;
+ }
+ break;
+ case ZSTD:
+ if (auto decompressedSize = ZSTD_decompress(reinterpret_cast(decompressedImageData.data() + newOffset), decompressedImageDataSize, reinterpret_cast(imageResource->data.data() + oldOffset), oldLength); ZSTD_isError(decompressedSize) || decompressedSize != decompressedImageDataSize) {
+ this->opened = false;
+ return;
+ }
+ break;
}
}
oldOffset += oldLength;
@@ -227,7 +280,8 @@ VTF::VTF(std::vector&& vtfData, bool parseHeaderOnly)
}
if (const auto* resource = this->getResource(Resource::TYPE_AUX_COMPRESSION)) {
- this->compressionLevel = static_cast(resource->getDataAsAuxCompressionLevel());
+ this->compressionLevel = resource->getDataAsAuxCompressionLevel();
+ this->compressionMethod = resource->getDataAsAuxCompressionMethod();
this->removeResourceInternal(Resource::TYPE_AUX_COMPRESSION);
}
}
@@ -263,13 +317,14 @@ VTF& VTF::operator=(const VTF& other) {
this->resources.clear();
for (const auto& [otherType, otherFlags, otherData] : other.resources) {
- auto& [type, flags, data] = this->resources.emplace_back();
+ auto& [type, flags_, data_] = this->resources.emplace_back();
type = otherType;
- flags = otherFlags;
- data = {this->data.data() + (otherData.data() - other.data.data()), otherData.size()};
+ flags_ = otherFlags;
+ data_ = {this->data.data() + (otherData.data() - other.data.data()), otherData.size()};
}
this->compressionLevel = other.compressionLevel;
+ this->compressionMethod = other.compressionMethod;
this->imageWidthResizeMethod = other.imageWidthResizeMethod;
this->imageHeightResizeMethod = other.imageHeightResizeMethod;
@@ -315,6 +370,7 @@ void VTF::createInternal(VTF& writer, CreationOptions options) {
}
writer.setFormat(options.outputFormat);
writer.setCompressionLevel(options.compressionLevel);
+ writer.setCompressionMethod(options.compressionMethod);
}
void VTF::create(std::span imageData, ImageFormat format, uint16_t width, uint16_t height, const std::string& vtfPath, CreationOptions options) {
@@ -410,6 +466,14 @@ void VTF::setImageResizeMethods(ImageConversion::ResizeMethod imageWidthResizeMe
this->imageHeightResizeMethod = imageHeightResizeMethod_;
}
+void VTF::setImageWidthResizeMethod(ImageConversion::ResizeMethod imageWidthResizeMethod_) {
+ this->imageWidthResizeMethod = imageWidthResizeMethod_;
+}
+
+void VTF::setImageHeightResizeMethod(ImageConversion::ResizeMethod imageHeightResizeMethod_) {
+ this->imageHeightResizeMethod = imageHeightResizeMethod_;
+}
+
uint16_t VTF::getWidth(uint8_t mip) const {
return mip > 0 ? ImageDimensions::getMipDim(mip, this->width) : this->width;
}
@@ -551,7 +615,7 @@ void VTF::computeMips(ImageConversion::ResizeFilter filter) {
}
#ifdef SOURCEPP_BUILD_WITH_THREADS
}));
- if (std::thread::hardware_concurrency() > 0 && futures.size() >= std::thread::hardware_concurrency() * 2) {
+ if (std::thread::hardware_concurrency() > 0 && futures.size() >= std::thread::hardware_concurrency()) {
for (auto& future : futures) {
future.get();
}
@@ -689,7 +753,7 @@ void VTF::computeReflectivity() {
futures.push_back(std::async(std::launch::async, [this, j, k, l] {
return getReflectivityForImage(*this, j, k, l);
}));
- if (std::thread::hardware_concurrency() > 0 && futures.size() >= std::thread::hardware_concurrency() * 2) {
+ if (std::thread::hardware_concurrency() > 0 && futures.size() >= std::thread::hardware_concurrency()) {
for (auto& future : futures) {
this->reflectivity += future.get();
}
@@ -784,7 +848,7 @@ void VTF::setResourceInternal(Resource::Type type, std::span da
this->data.clear();
BufferStream writer{this->data};
- for (auto resourceType : Resource::TYPE_ARRAY_ORDER) {
+ for (auto resourceType : Resource::getOrder()) {
if (!resourceData.contains(resourceType)) {
continue;
}
@@ -933,7 +997,7 @@ void VTF::removeExtendedFlagsResource() {
this->removeResourceInternal(Resource::TYPE_EXTENDED_FLAGS);
}
-void VTF::setKeyValuesData(const std::string& value) {
+void VTF::setKeyValuesDataResource(const std::string& value) {
std::vector keyValuesData;
BufferStream writer{keyValuesData};
@@ -944,18 +1008,26 @@ void VTF::setKeyValuesData(const std::string& value) {
this->setResourceInternal(Resource::TYPE_KEYVALUES_DATA, keyValuesData);
}
-void VTF::removeKeyValuesData() {
+void VTF::removeKeyValuesDataResource() {
this->removeResourceInternal(Resource::TYPE_KEYVALUES_DATA);
}
-uint8_t VTF::getCompressionLevel() const {
+int16_t VTF::getCompressionLevel() const {
return this->compressionLevel;
}
-void VTF::setCompressionLevel(uint8_t newCompressionLevel) {
+void VTF::setCompressionLevel(int16_t newCompressionLevel) {
this->compressionLevel = newCompressionLevel;
}
+CompressionMethod VTF::getCompressionMethod() const {
+ return this->compressionMethod;
+}
+
+void VTF::setCompressionMethod(CompressionMethod newCompressionMethod) {
+ this->compressionMethod = newCompressionMethod;
+}
+
bool VTF::hasImageData() const {
return this->format != ImageFormat::EMPTY && this->width > 0 && this->height > 0;
}
@@ -986,6 +1058,10 @@ std::vector VTF::getImageDataAsRGBA8888(uint8_t mip, uint16_t frame,
}
bool VTF::setImage(std::span imageData_, ImageFormat format_, uint16_t width_, uint16_t height_, ImageConversion::ResizeFilter filter, uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice) {
+ if (imageData_.empty()) {
+ return false;
+ }
+
if (!this->hasImageData()) {
uint16_t resizedWidth = width_, resizedHeight = height_;
ImageConversion::setResizedDims(resizedWidth, this->imageWidthResizeMethod, resizedHeight, this->imageHeightResizeMethod);
@@ -1013,11 +1089,13 @@ bool VTF::setImage(std::span imageData_, ImageFormat format_, u
}
if (uint32_t offset, length; ImageFormatDetails::getDataPosition(offset, length, this->format, mip, this->mipCount, frame, this->frameCount, face, faceCount, this->width, this->height, slice, this->sliceCount)) {
std::vector image{imageData_.begin(), imageData_.end()};
- if (this->format != format_) {
- image = ImageConversion::convertImageDataToFormat(image, format_, this->format, this->width, this->height);
+ const auto newWidth = ImageDimensions::getMipDim(mip, this->width);
+ const auto newHeight = ImageDimensions::getMipDim(mip, this->height);
+ if (width_ != newWidth || height_ != newHeight) {
+ image = ImageConversion::resizeImageData(image, format_, width_, newWidth, height_, newHeight, this->imageDataIsSRGB(), filter);
}
- if (width_ != ImageDimensions::getMipDim(mip, this->width) || height_ != ImageDimensions::getMipDim(mip, this->height)) {
- image = ImageConversion::resizeImageData(image, this->format, width_, ImageDimensions::getMipDim(mip, this->width), height_, ImageDimensions::getMipDim(mip, this->height), this->imageDataIsSRGB(), filter);
+ if (format_ != this->format) {
+ image = ImageConversion::convertImageDataToFormat(image, format_, this->format, newWidth, newHeight);
}
std::memcpy(imageResource->data.data() + offset, image.data(), image.size());
}
@@ -1030,7 +1108,7 @@ bool VTF::setImage(const std::string& imagePath, ImageConversion::ResizeFilter f
auto imageData_ = ImageConversion::convertFileToImageData(fs::readFileBuffer(imagePath), inputFormat, inputWidth, inputHeight, inputFrameCount);
// Unable to decode file
- if (inputFormat == ImageFormat::EMPTY || !inputWidth || !inputHeight || !inputFrameCount) {
+ if (imageData_.empty() || inputFormat == ImageFormat::EMPTY || !inputWidth || !inputHeight || !inputFrameCount) {
return false;
}
@@ -1088,6 +1166,16 @@ std::vector VTF::getThumbnailDataAsRGBA8888() const {
return this->getThumbnailDataAs(ImageFormat::RGBA8888);
}
+void VTF::setThumbnail(std::span imageData_, ImageFormat format_, uint16_t width_, uint16_t height_) {
+ if (format_ != this->thumbnailFormat) {
+ this->setResourceInternal(Resource::TYPE_THUMBNAIL_DATA, ImageConversion::convertImageDataToFormat(imageData_, format_, this->thumbnailFormat, width_, height_));
+ } else {
+ this->setResourceInternal(Resource::TYPE_THUMBNAIL_DATA, imageData_);
+ }
+ this->thumbnailWidth = width_;
+ this->thumbnailHeight = height_;
+}
+
void VTF::computeThumbnail(ImageConversion::ResizeFilter filter) {
if (!this->hasImageData()) {
return;
@@ -1169,19 +1257,20 @@ std::vector VTF::bake() const {
auxCompressionResourceData.resize((this->mipCount * this->frameCount * faceCount + 2) * sizeof(uint32_t));
BufferStream auxWriter{auxCompressionResourceData, false};
- // Format of aux resource is as follows, with each item being a 4 byte integer:
+ // Format of aux resource is as follows, with each item of unspecified type being a 4 byte integer:
// - Size of resource in bytes, not counting this int
- // - Compression level
+ // - Compression level, method (2 byte integers)
// - (X times) Size of each mip-face-frame combo
auxWriter
.write(auxCompressionResourceData.size() - sizeof(uint32_t))
- .write(this->compressionLevel);
+ .write(this->compressionLevel)
+ .write(this->compressionMethod);
for (int i = this->mipCount - 1; i >= 0; i--) {
for (int j = 0; j < this->frameCount; j++) {
for (int k = 0; k < faceCount; k++) {
if (uint32_t offset, length; ImageFormatDetails::getDataPosition(offset, length, this->format, i, this->mipCount, j, this->frameCount, k, faceCount, this->width, this->height, 0, this->sliceCount)) {
- auto compressedData = ::compressData({imageResource->data.data() + offset, length * this->sliceCount}, this->compressionLevel);
+ auto compressedData = ::compressData({imageResource->data.data() + offset, length * this->sliceCount}, this->compressionLevel, this->compressionMethod);
compressedImageResourceData.insert(compressedImageResourceData.end(), compressedData.begin(), compressedData.end());
auxWriter.write(compressedData.size());
}
@@ -1214,7 +1303,7 @@ std::vector VTF::bake() const {
writer_.write(data);
writer_.seek_u(resourceOffsetPos).write(resourceOffsetValue);
};
- for (const auto resourceType : Resource::TYPE_ARRAY_ORDER) {
+ for (const auto resourceType : Resource::getOrder()) {
if (hasAuxCompression && resourceType == Resource::TYPE_AUX_COMPRESSION) {
writeNonLocalResource(writer, resourceType, auxCompressionResourceData);
} else if (hasAuxCompression && resourceType == Resource::TYPE_IMAGE_DATA) {
diff --git a/src/vtfpp/_vtfpp.cmake b/src/vtfpp/_vtfpp.cmake
index c180bd685..bb3527974 100644
--- a/src/vtfpp/_vtfpp.cmake
+++ b/src/vtfpp/_vtfpp.cmake
@@ -1,5 +1,5 @@
add_pretty_parser(vtfpp
- DEPS miniz sourcepp_stb sourcepp_tinyexr
+ DEPS miniz libzstd_static sourcepp_stb sourcepp_tinyexr
PRECOMPILED_HEADERS
"${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/ImageConversion.h"
"${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/ImageFormats.h"
diff --git a/test/vtfpp.cpp b/test/vtfpp.cpp
index a3b7b40f3..425be5391 100644
--- a/test/vtfpp.cpp
+++ b/test/vtfpp.cpp
@@ -836,6 +836,7 @@ TEST(vtfpp, read_v76_c9) {
EXPECT_EQ(vtf.getThumbnailWidth(), 16);
EXPECT_EQ(vtf.getThumbnailHeight(), 16);
EXPECT_EQ(vtf.getCompressionLevel(), 9);
+ EXPECT_EQ(vtf.getCompressionMethod(), CompressionMethod::DEFLATE);
// Resources
EXPECT_EQ(vtf.getResources().size(), 2);
@@ -876,7 +877,8 @@ TEST(vtfpp, write_v76_c6) {
EXPECT_EQ(vtf.getThumbnailFormat(), ImageFormat::DXT1);
EXPECT_EQ(vtf.getThumbnailWidth(), 16);
EXPECT_EQ(vtf.getThumbnailHeight(), 16);
- EXPECT_EQ(vtf.getCompressionLevel(), 6);
+ EXPECT_EQ(vtf.getCompressionLevel(), -1);
+ EXPECT_EQ(vtf.getCompressionMethod(), CompressionMethod::ZSTD);
}
TEST(vtfpp, read_v76_nomip_c9) {
@@ -903,6 +905,7 @@ TEST(vtfpp, read_v76_nomip_c9) {
EXPECT_EQ(vtf.getThumbnailWidth(), 16);
EXPECT_EQ(vtf.getThumbnailHeight(), 16);
EXPECT_EQ(vtf.getCompressionLevel(), 9);
+ EXPECT_EQ(vtf.getCompressionMethod(), CompressionMethod::DEFLATE);
// Resources
EXPECT_EQ(vtf.getResources().size(), 2);