mirror of
https://github.com/MihailRis/voxelcore.git
synced 2026-10-10 21:41:50 +00:00
Merge branch 'MihailRis:main' into main
This commit is contained in:
commit
2c3c217fef
71 changed files with 3978 additions and 786 deletions
8
.github/workflows/appimage.yml
vendored
8
.github/workflows/appimage.yml
vendored
|
|
@ -31,7 +31,7 @@ jobs:
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y build-essential libglfw3-dev libglfw3 libglew-dev libglew2.2 \
|
sudo apt-get install -y build-essential libglfw3-dev libglfw3 libglew-dev libglew2.2 \
|
||||||
libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev libvorbis-dev \
|
libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev libvorbis-dev \
|
||||||
libcurl4-openssl-dev libgtest-dev libfreetype6-dev cmake squashfs-tools valgrind
|
libcurl4-openssl-dev libssl-dev libgtest-dev libfreetype6-dev cmake squashfs-tools valgrind
|
||||||
# fix luajit paths
|
# fix luajit paths
|
||||||
sudo ln -s /usr/lib/x86_64-linux-gnu/libluajit-5.1.a /usr/lib/x86_64-linux-gnu/liblua5.1.a
|
sudo ln -s /usr/lib/x86_64-linux-gnu/libluajit-5.1.a /usr/lib/x86_64-linux-gnu/liblua5.1.a
|
||||||
sudo ln -s /usr/include/luajit-2.1 /usr/include/lua
|
sudo ln -s /usr/include/luajit-2.1 /usr/include/lua
|
||||||
|
|
@ -46,13 +46,13 @@ jobs:
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cmake --build build -t install
|
run: cmake --build build -t install
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: ctest --test-dir build
|
run: ctest --output-on-failure --test-dir build
|
||||||
- name: Run engine tests
|
- name: Run engine tests
|
||||||
timeout-minutes: 1
|
timeout-minutes: 5
|
||||||
run: |
|
run: |
|
||||||
chmod +x build/VoxelEngine
|
chmod +x build/VoxelEngine
|
||||||
chmod +x AppDir/usr/bin/vctest
|
chmod +x AppDir/usr/bin/vctest
|
||||||
AppDir/usr/bin/vctest -e build/VoxelEngine -d dev/tests -u build
|
AppDir/usr/bin/vctest -e build/VoxelEngine -d dev/tests -u build --memchecker valgrind
|
||||||
- name: Build AppImage
|
- name: Build AppImage
|
||||||
uses: AppImageCrafters/build-appimage-action@fe2205a4d6056be47051f7b1b3811106e9814910
|
uses: AppImageCrafters/build-appimage-action@fe2205a4d6056be47051f7b1b3811106e9814910
|
||||||
env:
|
env:
|
||||||
|
|
|
||||||
2
.github/workflows/cmake.yml
vendored
2
.github/workflows/cmake.yml
vendored
|
|
@ -39,7 +39,7 @@ jobs:
|
||||||
# make && make install INSTALL_INC=/usr/include/lua
|
# make && make install INSTALL_INC=/usr/include/lua
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install libglfw3-dev libglfw3 libglew-dev libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev libvorbis-dev libgtest-dev libcurl4-openssl-dev libfreetype6-dev
|
sudo apt-get install libglfw3-dev libglfw3 libglew-dev libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev libvorbis-dev libgtest-dev libcurl4-openssl-dev libssl-dev libfreetype6-dev
|
||||||
# fix luajit paths
|
# fix luajit paths
|
||||||
sudo ln -s /usr/lib/x86_64-linux-gnu/libluajit-5.1.a /usr/lib/x86_64-linux-gnu/liblua-5.1.a
|
sudo ln -s /usr/lib/x86_64-linux-gnu/libluajit-5.1.a /usr/lib/x86_64-linux-gnu/liblua-5.1.a
|
||||||
sudo ln -s /usr/include/luajit-2.1 /usr/include/lua
|
sudo ln -s /usr/include/luajit-2.1 /usr/include/lua
|
||||||
|
|
|
||||||
8
.github/workflows/macos.yml
vendored
8
.github/workflows/macos.yml
vendored
|
|
@ -23,13 +23,13 @@ jobs:
|
||||||
|
|
||||||
- name: Install dependencies from brew
|
- name: Install dependencies from brew
|
||||||
run: |
|
run: |
|
||||||
brew install glfw3 glew libpng openal-soft luajit libvorbis skypjack/entt/entt googletest glm freetype
|
brew install glfw3 glew libpng openal-soft luajit libvorbis skypjack/entt/entt googletest glm freetype
|
||||||
|
|
||||||
- name: Configure
|
- name: Configure
|
||||||
run: |
|
run: |
|
||||||
cmake -DOPENAL_INCLUDE_DIR=$(brew --prefix openal-soft)/include \
|
cmake -DOPENAL_INCLUDE_DIR=$(brew --prefix openal-soft)/include \
|
||||||
-DOPENAL_LIBRARY=$(brew --prefix openal-soft)/lib/libopenal.dylib \
|
-DOPENAL_LIBRARY=$(brew --prefix openal-soft)/lib/libopenal.dylib \
|
||||||
-S . -B build -DCMAKE_BUILD_TYPE=Release -DVOXELENGINE_BUILD_TESTS=ON -DVOXELENGINE_BUILD_APPDIR=1 -DVC_BUILD_NAME="${{ inputs.build_name }}"
|
-S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo -DVOXELENGINE_BUILD_TESTS=ON -DVOXELENGINE_BUILD_APPDIR=1 -DVC_BUILD_NAME="${{ inputs.build_name }}"
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
|
|
@ -45,11 +45,11 @@ jobs:
|
||||||
run: ctest --output-on-failure --test-dir build
|
run: ctest --output-on-failure --test-dir build
|
||||||
|
|
||||||
- name: Run engine tests
|
- name: Run engine tests
|
||||||
timeout-minutes: 1
|
timeout-minutes: 5
|
||||||
run: |
|
run: |
|
||||||
chmod +x build/VoxelEngine
|
chmod +x build/VoxelEngine
|
||||||
chmod +x AppDir/usr/bin/vctest
|
chmod +x AppDir/usr/bin/vctest
|
||||||
AppDir/usr/bin/vctest -e build/VoxelEngine -d dev/tests -u build --output-always
|
AppDir/usr/bin/vctest -e build/VoxelEngine -d dev/tests -u build --output-always --debugger "lldb -batch -o 'run' -o 'thread backtrace all' -o 'quit' -- "
|
||||||
- name: Create DMG
|
- name: Create DMG
|
||||||
run: |
|
run: |
|
||||||
mkdir VoxelEngineDmgContent
|
mkdir VoxelEngineDmgContent
|
||||||
|
|
|
||||||
2
.github/workflows/windows.yml
vendored
2
.github/workflows/windows.yml
vendored
|
|
@ -38,7 +38,7 @@ jobs:
|
||||||
- name: Run engine tests
|
- name: Run engine tests
|
||||||
run: |
|
run: |
|
||||||
build/vctest/RelWithDebInfo/vctest.exe -e build/RelWithDebInfo/VoxelEngine.exe -d dev/tests -u build
|
build/vctest/RelWithDebInfo/vctest.exe -e build/RelWithDebInfo/VoxelEngine.exe -d dev/tests -u build
|
||||||
timeout-minutes: 1
|
timeout-minutes: 5
|
||||||
- name: Package for Windows
|
- name: Package for Windows
|
||||||
run: |
|
run: |
|
||||||
mkdir packaged
|
mkdir packaged
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ RUN apt-get update && apt-get install --no-install-recommends -y \
|
||||||
libvorbis-dev \
|
libvorbis-dev \
|
||||||
libcurl4-openssl-dev \
|
libcurl4-openssl-dev \
|
||||||
libfreetype6-dev \
|
libfreetype6-dev \
|
||||||
|
libssl-dev \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
wget \
|
wget \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
|
||||||
41
dev/tests/crypto.lua
Normal file
41
dev/tests/crypto.lua
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
assert(crypto.API_VERSION == 1)
|
||||||
|
local features = crypto.features()
|
||||||
|
assert(features.api_version == 1)
|
||||||
|
assert(features.backend == "OpenSSL")
|
||||||
|
|
||||||
|
local binary = "a\0b"
|
||||||
|
assert(crypto.hash("SHA256", binary) == crypto.sha256(binary))
|
||||||
|
local stream = crypto.hash_new("SHA256")
|
||||||
|
assert(stream:update("a"):update("\0b"):final() == crypto.sha256(binary))
|
||||||
|
stream:reset():update("abc")
|
||||||
|
assert(stream:final() == crypto.sha256("abc"))
|
||||||
|
|
||||||
|
local private, public = crypto.ed25519_keypair()
|
||||||
|
assert(crypto.ed25519_public(private) == public)
|
||||||
|
local signature = crypto.ed25519_sign(private, binary)
|
||||||
|
assert(crypto.ed25519_verify(public, binary, signature) == true)
|
||||||
|
local ok, error_code = crypto.ed25519_verify(public, "changed", signature)
|
||||||
|
assert(ok == false and error_code == "invalid_signature")
|
||||||
|
|
||||||
|
local x_private_a, x_public_a = crypto.x25519_keypair()
|
||||||
|
local x_private_b, x_public_b = crypto.x25519_keypair()
|
||||||
|
assert(crypto.x25519(x_private_a, x_public_b) ==
|
||||||
|
crypto.x25519_shared(x_private_b, x_public_a))
|
||||||
|
|
||||||
|
local p_private_a, p_public_a = crypto.p256_keypair()
|
||||||
|
local p_private_b, p_public_b = crypto.p256_keypair()
|
||||||
|
assert(crypto.p256_public(p_private_a) == p_public_a)
|
||||||
|
assert(crypto.p256_shared(p_private_a, p_public_b) ==
|
||||||
|
crypto.p256_shared(p_private_b, p_public_a))
|
||||||
|
|
||||||
|
local key = crypto.random_bytes(16)
|
||||||
|
local nonce = crypto.random_bytes(12)
|
||||||
|
local ciphertext = crypto.aes_gcm_encrypt(key, nonce, "aad", binary)
|
||||||
|
assert(crypto.aes_gcm_decrypt(key, nonce, "aad", ciphertext) == binary)
|
||||||
|
local plaintext, decrypt_error = crypto.aes_gcm_decrypt(
|
||||||
|
key, nonce, "changed", ciphertext
|
||||||
|
)
|
||||||
|
assert(plaintext == nil and decrypt_error == "authentication_failed")
|
||||||
|
|
||||||
|
assert(#crypto.pbkdf2("SHA256", "password", "salt", 1, 32) == 32)
|
||||||
|
assert(#crypto.scrypt("password", "salt", 16, 1, 1, 32) == 32)
|
||||||
|
|
@ -8,4 +8,4 @@ end, function (code)
|
||||||
response_received = true
|
response_received = true
|
||||||
end)
|
end)
|
||||||
|
|
||||||
app.sleep_until(function () return response_received end, nil, 10)
|
app.sleep_until(function () return response_received end, nil, 5)
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,11 @@ Defines the entity's material (same as for blocks).
|
||||||
|
|
||||||
The default value is the same as the entity name. Determines which skeleton will be used by the entity. See [rigging](rigging.md).
|
The default value is the same as the entity name. Determines which skeleton will be used by the entity. See [rigging](rigging.md).
|
||||||
|
|
||||||
|
### *lighting-mode*
|
||||||
|
|
||||||
|
- *bone-based* - lighting is calculated for each skeletal bone separately.
|
||||||
|
- *solid* - lighting is calculated for the entire entity.
|
||||||
|
|
||||||
## Saving/Loading
|
## Saving/Loading
|
||||||
|
|
||||||
In addition to custom components, the engine automatically saves data from the built-in ones: transform, rigidbody, skeleton.
|
In addition to custom components, the engine automatically saves data from the built-in ones: transform, rigidbody, skeleton.
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ Subsections:
|
||||||
- [bjson, json, toml, yaml](scripting/filesystem.md)
|
- [bjson, json, toml, yaml](scripting/filesystem.md)
|
||||||
- [block](scripting/builtins/libblock.md)
|
- [block](scripting/builtins/libblock.md)
|
||||||
- [byteutil](scripting/builtins/libbyteutil.md)
|
- [byteutil](scripting/builtins/libbyteutil.md)
|
||||||
|
- [crypto](scripting/builtins/libcrypto.md)
|
||||||
- [cameras](scripting/builtins/libcameras.md)
|
- [cameras](scripting/builtins/libcameras.md)
|
||||||
- [ctypes](scripting/builtins/libctypes.md)
|
- [ctypes](scripting/builtins/libctypes.md)
|
||||||
- [entities](scripting/builtins/libentities.md)
|
- [entities](scripting/builtins/libentities.md)
|
||||||
|
|
|
||||||
144
doc/en/scripting/builtins/libcrypto.md
Normal file
144
doc/en/scripting/builtins/libcrypto.md
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
# *crypto* library
|
||||||
|
|
||||||
|
The library provides common cryptographic functions
|
||||||
|
|
||||||
|
## Hashing
|
||||||
|
|
||||||
|
```lua
|
||||||
|
crypto.sha256(data: str) -> str
|
||||||
|
crypto.sha384(data: str) -> str
|
||||||
|
crypto.sha512(data: str) -> str
|
||||||
|
crypto.md5(data: str) -> str
|
||||||
|
|
||||||
|
crypto.hash(hash: str, data: str) -> str
|
||||||
|
crypto.hmac(hash: str, key: str, data: str) -> str
|
||||||
|
```
|
||||||
|
|
||||||
|
Supported names are `SHA256`, `SHA384`, `SHA512` and `MD5`
|
||||||
|
|
||||||
|
MD5 is needed for compatibility with old formats. SHA256 is better for new data
|
||||||
|
|
||||||
|
Large files can be passed in parts
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local hash = crypto.hash_new("SHA256")
|
||||||
|
|
||||||
|
hash:update(part1)
|
||||||
|
hash:update(part2)
|
||||||
|
|
||||||
|
local result = hash:final()
|
||||||
|
```
|
||||||
|
|
||||||
|
The context can be cleared with `reset` after `final`
|
||||||
|
|
||||||
|
## Signatures
|
||||||
|
|
||||||
|
```lua
|
||||||
|
crypto.ed25519_keypair() -> private_key, public_key
|
||||||
|
crypto.ed25519_public(private_key: str) -> str
|
||||||
|
crypto.ed25519_sign(private_key: str, message: str) -> str
|
||||||
|
crypto.ed25519_verify(public_key: str, message: str, signature: str)
|
||||||
|
-> true
|
||||||
|
-> false, error
|
||||||
|
```
|
||||||
|
|
||||||
|
Ed25519 keys are 32 bytes long. A signature is 64 bytes long
|
||||||
|
|
||||||
|
```lua
|
||||||
|
crypto.ecdsa_keypair(curve: str) -> private_key, public_key
|
||||||
|
crypto.ecdsa_public(curve: str, private_key: str) -> str
|
||||||
|
crypto.ecdsa_sign(curve: str, private_key: str, message: str, hash: str) -> str
|
||||||
|
crypto.ecdsa_verify(curve: str, public_key: str, message: str,
|
||||||
|
signature: str, hash: str)
|
||||||
|
-> true
|
||||||
|
-> false, error
|
||||||
|
```
|
||||||
|
|
||||||
|
Available curves are `P-256`, `P-384` and `P-521`
|
||||||
|
|
||||||
|
An ECDSA public key is stored as an uncompressed SEC1 point. A signature is
|
||||||
|
stored in DER format
|
||||||
|
|
||||||
|
```lua
|
||||||
|
crypto.rsa_pkcs1_verify(hash: str, modulus: str, exponent: str,
|
||||||
|
message: str, signature: str)
|
||||||
|
-> true
|
||||||
|
-> false, error
|
||||||
|
|
||||||
|
crypto.rsa_pss_verify(hash: str, modulus: str, exponent: str,
|
||||||
|
message: str, signature: str, salt_length: int)
|
||||||
|
-> true
|
||||||
|
-> false, error
|
||||||
|
```
|
||||||
|
|
||||||
|
RSA modulus and exponent are passed in big endian. A `salt_length` value of `-1`
|
||||||
|
uses the hash size
|
||||||
|
|
||||||
|
## Key exchange
|
||||||
|
|
||||||
|
```lua
|
||||||
|
crypto.x25519_keypair() -> private_key, public_key
|
||||||
|
crypto.x25519_public(private_key: str) -> str
|
||||||
|
crypto.x25519(private_key: str, peer_public_key: str) -> str
|
||||||
|
crypto.x25519_shared(private_key: str, peer_public_key: str) -> str
|
||||||
|
|
||||||
|
crypto.p256_keypair() -> private_key, public_key
|
||||||
|
crypto.p256_public(private_key: str) -> str
|
||||||
|
crypto.p256_shared(private_key: str, peer_public_key: str) -> str
|
||||||
|
```
|
||||||
|
|
||||||
|
`x25519` and `x25519_shared` perform the same operation
|
||||||
|
|
||||||
|
The shared secret should not be used as a ready key. Pass it through HKDF first
|
||||||
|
|
||||||
|
## Encryption
|
||||||
|
|
||||||
|
```lua
|
||||||
|
crypto.aes_gcm_encrypt(key: str, nonce: str, aad: str, plaintext: str) -> str
|
||||||
|
crypto.aes_gcm_decrypt(key: str, nonce: str, aad: str, ciphertext: str)
|
||||||
|
-> plaintext
|
||||||
|
-> nil, error
|
||||||
|
|
||||||
|
crypto.chacha20_poly1305_encrypt(key: str, nonce: str, aad: str,
|
||||||
|
plaintext: str) -> str
|
||||||
|
crypto.chacha20_poly1305_decrypt(key: str, nonce: str, aad: str,
|
||||||
|
ciphertext: str)
|
||||||
|
-> plaintext
|
||||||
|
-> nil, error
|
||||||
|
```
|
||||||
|
|
||||||
|
AES GCM accepts 16 and 32 byte keys
|
||||||
|
|
||||||
|
ChaCha20 Poly1305 uses a 32 byte key and a 12 byte nonce
|
||||||
|
|
||||||
|
A 16 byte tag is appended to encrypted data
|
||||||
|
|
||||||
|
Never reuse a nonce with the same key
|
||||||
|
|
||||||
|
## Other functions
|
||||||
|
|
||||||
|
```lua
|
||||||
|
crypto.random_bytes(length: int) -> str
|
||||||
|
crypto.constant_time_equal(left: str, right: str) -> bool
|
||||||
|
|
||||||
|
crypto.hkdf_extract(hash: str, salt: str, ikm: str) -> str
|
||||||
|
crypto.hkdf_expand(hash: str, prk: str, info: str, length: int) -> str
|
||||||
|
|
||||||
|
crypto.pbkdf2(hash: str, password: str, salt: str,
|
||||||
|
iterations: int, length: int) -> str
|
||||||
|
crypto.scrypt(password: str, salt: str, n: int, r: int, p: int,
|
||||||
|
length: int, [optional]max_memory: int=0) -> str
|
||||||
|
|
||||||
|
crypto.features() -> table
|
||||||
|
```
|
||||||
|
|
||||||
|
Use scrypt or PBKDF2 with a random salt for passwords. A regular SHA256 hash is
|
||||||
|
not suitable for passwords
|
||||||
|
|
||||||
|
`features` returns the OpenSSL version and a list of available features
|
||||||
|
|
||||||
|
An invalid signature returns `false, "invalid_signature"`
|
||||||
|
|
||||||
|
An invalid encryption tag returns `nil, "authentication_failed"`
|
||||||
|
|
||||||
|
Invalid arguments raise a regular Lua error
|
||||||
|
|
@ -61,6 +61,12 @@ gui.set_active_frame(
|
||||||
-- Used for custom projection (e.g., in 3D)
|
-- Used for custom projection (e.g., in 3D)
|
||||||
[optional] cursorLocator: function() -> number, number
|
[optional] cursorLocator: function() -> number, number
|
||||||
)
|
)
|
||||||
|
|
||||||
|
-- Creates a screenshot of a frame as a Canvas object if the frame ID is specified, or the entire window if nil.
|
||||||
|
gui.screenshot(
|
||||||
|
-- ID of the frame created via gui.create_frame
|
||||||
|
[optional] frameId: str
|
||||||
|
) -> Canvas | nil
|
||||||
```
|
```
|
||||||
|
|
||||||
## Markup
|
## Markup
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,39 @@ A library for working with the network.
|
||||||
|
|
||||||
## HTTP requests
|
## HTTP requests
|
||||||
|
|
||||||
|
There is a configurable function `network.request` that allows performing HTTP requests with various methods (GET, POST, PUT, DELETE, etc.) and configuring headers, request body, timeout, and other parameters.
|
||||||
|
|
||||||
|
> To pass binary data in the request body, use a byte array (Bytearray) or a string. In `on_response`, the `body` string can be converted to a byte array using `Bytearray(response.body)`.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
network.request(
|
||||||
|
url: string,
|
||||||
|
parameters: {
|
||||||
|
-- Request method (GET, POST, PUT, DELETE, etc.)
|
||||||
|
method: string,
|
||||||
|
-- Request body as a string or Bytearray
|
||||||
|
body: table|string,
|
||||||
|
-- List of additional request headers
|
||||||
|
headers: table<string>,
|
||||||
|
-- Timeout in milliseconds
|
||||||
|
timeout: int,
|
||||||
|
-- Whether to verify the SSL certificate
|
||||||
|
verify_ssl: boolean,
|
||||||
|
-- Function called when a response is received
|
||||||
|
on_response: function(response: {
|
||||||
|
-- HTTP response status code
|
||||||
|
status: int,
|
||||||
|
-- Response body as a string
|
||||||
|
body: string,
|
||||||
|
-- List of response headers
|
||||||
|
headers: table<string>
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Simplified functions for GET and POST requests
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
-- Performs a GET request to the specified URL.
|
-- Performs a GET request to the specified URL.
|
||||||
network.get(
|
network.get(
|
||||||
|
|
|
||||||
|
|
@ -190,10 +190,14 @@ rig:is_visible([optional] index: int) -> bool
|
||||||
rig:set_visible([optional] index: int, status: bool)
|
rig:set_visible([optional] index: int, status: bool)
|
||||||
|
|
||||||
-- Returns the color of the entity
|
-- Returns the color of the entity
|
||||||
rig:get_color() -> vec3
|
rig:get_color() -> vec4
|
||||||
|
-- Return the color of the bone by index
|
||||||
|
rig:get_color(index: int) -> vec4
|
||||||
|
|
||||||
-- Sets the color of the entity
|
-- Sets the color of the entity
|
||||||
rig:set_color(color: vec3)
|
rig:set_color(color: vec3 | vec4)
|
||||||
|
-- Sets the color of the bone by index
|
||||||
|
rig:set_color(color: vec3 | vec4, index: int)
|
||||||
```
|
```
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,11 @@
|
||||||
|
|
||||||
Значение по-умолчанию совпадает с именем сущности. Определяет то, какой скелет будет использоваться сущностью. См. [риггинг](rigging.md).
|
Значение по-умолчанию совпадает с именем сущности. Определяет то, какой скелет будет использоваться сущностью. См. [риггинг](rigging.md).
|
||||||
|
|
||||||
|
### Тип освещения - *lighting-mode*
|
||||||
|
|
||||||
|
- *bone-based* - освещение рассчитывается для каждой кости скелета отдельно.
|
||||||
|
- *solid* - освещение рассчитывается для всей сущности в целом.
|
||||||
|
|
||||||
## Сохранение/загрузка
|
## Сохранение/загрузка
|
||||||
|
|
||||||
Кроме данных пользовательских компонентов, движок автоматически сохраняет данные встроенных: transform, rigidbody, skeleton.
|
Кроме данных пользовательских компонентов, движок автоматически сохраняет данные встроенных: transform, rigidbody, skeleton.
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
- [bjson, json, toml, yaml](scripting/filesystem.md)
|
- [bjson, json, toml, yaml](scripting/filesystem.md)
|
||||||
- [block](scripting/builtins/libblock.md)
|
- [block](scripting/builtins/libblock.md)
|
||||||
- [byteutil](scripting/builtins/libbyteutil.md)
|
- [byteutil](scripting/builtins/libbyteutil.md)
|
||||||
|
- [crypto](scripting/builtins/libcrypto.md)
|
||||||
- [cameras](scripting/builtins/libcameras.md)
|
- [cameras](scripting/builtins/libcameras.md)
|
||||||
- [ctypes](scripting/builtins/libctypes.md)
|
- [ctypes](scripting/builtins/libctypes.md)
|
||||||
- [entities](scripting/builtins/libentities.md)
|
- [entities](scripting/builtins/libentities.md)
|
||||||
|
|
|
||||||
146
doc/ru/scripting/builtins/libcrypto.md
Normal file
146
doc/ru/scripting/builtins/libcrypto.md
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
# Библиотека *crypto*
|
||||||
|
|
||||||
|
Библиотека предоставляет основные криптографические функции
|
||||||
|
|
||||||
|
## Хеширование
|
||||||
|
|
||||||
|
```lua
|
||||||
|
crypto.sha256(data: str) -> str
|
||||||
|
crypto.sha384(data: str) -> str
|
||||||
|
crypto.sha512(data: str) -> str
|
||||||
|
crypto.md5(data: str) -> str
|
||||||
|
|
||||||
|
crypto.hash(hash: str, data: str) -> str
|
||||||
|
crypto.hmac(hash: str, key: str, data: str) -> str
|
||||||
|
```
|
||||||
|
|
||||||
|
Поддерживаются `SHA256`, `SHA384`, `SHA512` и `MD5`
|
||||||
|
|
||||||
|
MD5 нужен для совместимости со старыми форматами. Для новых данных лучше
|
||||||
|
использовать SHA256
|
||||||
|
|
||||||
|
Большой файл можно передавать частями
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local hash = crypto.hash_new("SHA256")
|
||||||
|
|
||||||
|
hash:update(part1)
|
||||||
|
hash:update(part2)
|
||||||
|
|
||||||
|
local result = hash:final()
|
||||||
|
```
|
||||||
|
|
||||||
|
После `final` контекст можно очистить через `reset`
|
||||||
|
|
||||||
|
## Подписи
|
||||||
|
|
||||||
|
```lua
|
||||||
|
crypto.ed25519_keypair() -> private_key, public_key
|
||||||
|
crypto.ed25519_public(private_key: str) -> str
|
||||||
|
crypto.ed25519_sign(private_key: str, message: str) -> str
|
||||||
|
crypto.ed25519_verify(public_key: str, message: str, signature: str)
|
||||||
|
-> true
|
||||||
|
-> false, error
|
||||||
|
```
|
||||||
|
|
||||||
|
Ключи Ed25519 имеют длину 32 байта. Подпись имеет длину 64 байта
|
||||||
|
|
||||||
|
```lua
|
||||||
|
crypto.ecdsa_keypair(curve: str) -> private_key, public_key
|
||||||
|
crypto.ecdsa_public(curve: str, private_key: str) -> str
|
||||||
|
crypto.ecdsa_sign(curve: str, private_key: str, message: str, hash: str) -> str
|
||||||
|
crypto.ecdsa_verify(curve: str, public_key: str, message: str,
|
||||||
|
signature: str, hash: str)
|
||||||
|
-> true
|
||||||
|
-> false, error
|
||||||
|
```
|
||||||
|
|
||||||
|
Доступны кривые `P-256`, `P-384` и `P-521`
|
||||||
|
|
||||||
|
Публичный ключ ECDSA записывается как несжатая SEC1 точка. Подпись хранится в
|
||||||
|
DER формате
|
||||||
|
|
||||||
|
```lua
|
||||||
|
crypto.rsa_pkcs1_verify(hash: str, modulus: str, exponent: str,
|
||||||
|
message: str, signature: str)
|
||||||
|
-> true
|
||||||
|
-> false, error
|
||||||
|
|
||||||
|
crypto.rsa_pss_verify(hash: str, modulus: str, exponent: str,
|
||||||
|
message: str, signature: str, salt_length: int)
|
||||||
|
-> true
|
||||||
|
-> false, error
|
||||||
|
```
|
||||||
|
|
||||||
|
Модуль и экспонента RSA передаются в big endian. Значение `salt_length = -1`
|
||||||
|
использует размер хеша
|
||||||
|
|
||||||
|
## Обмен ключами
|
||||||
|
|
||||||
|
```lua
|
||||||
|
crypto.x25519_keypair() -> private_key, public_key
|
||||||
|
crypto.x25519_public(private_key: str) -> str
|
||||||
|
crypto.x25519(private_key: str, peer_public_key: str) -> str
|
||||||
|
crypto.x25519_shared(private_key: str, peer_public_key: str) -> str
|
||||||
|
|
||||||
|
crypto.p256_keypair() -> private_key, public_key
|
||||||
|
crypto.p256_public(private_key: str) -> str
|
||||||
|
crypto.p256_shared(private_key: str, peer_public_key: str) -> str
|
||||||
|
```
|
||||||
|
|
||||||
|
`x25519` и `x25519_shared` выполняют одну и ту же операцию
|
||||||
|
|
||||||
|
Общий секрет не стоит использовать как готовый ключ. Сначала его нужно
|
||||||
|
обработать через HKDF
|
||||||
|
|
||||||
|
## Шифрование
|
||||||
|
|
||||||
|
```lua
|
||||||
|
crypto.aes_gcm_encrypt(key: str, nonce: str, aad: str, plaintext: str) -> str
|
||||||
|
crypto.aes_gcm_decrypt(key: str, nonce: str, aad: str, ciphertext: str)
|
||||||
|
-> plaintext
|
||||||
|
-> nil, error
|
||||||
|
|
||||||
|
crypto.chacha20_poly1305_encrypt(key: str, nonce: str, aad: str,
|
||||||
|
plaintext: str) -> str
|
||||||
|
crypto.chacha20_poly1305_decrypt(key: str, nonce: str, aad: str,
|
||||||
|
ciphertext: str)
|
||||||
|
-> plaintext
|
||||||
|
-> nil, error
|
||||||
|
```
|
||||||
|
|
||||||
|
AES GCM принимает ключи размером 16 или 32 байта
|
||||||
|
|
||||||
|
ChaCha20 Poly1305 использует ключ размером 32 байта и nonce размером 12 байт
|
||||||
|
|
||||||
|
В конец зашифрованных данных добавляется тег размером 16 байт
|
||||||
|
|
||||||
|
Нельзя повторно использовать один nonce с тем же ключом
|
||||||
|
|
||||||
|
## Остальные функции
|
||||||
|
|
||||||
|
```lua
|
||||||
|
crypto.random_bytes(length: int) -> str
|
||||||
|
crypto.constant_time_equal(left: str, right: str) -> bool
|
||||||
|
|
||||||
|
crypto.hkdf_extract(hash: str, salt: str, ikm: str) -> str
|
||||||
|
crypto.hkdf_expand(hash: str, prk: str, info: str, length: int) -> str
|
||||||
|
|
||||||
|
crypto.pbkdf2(hash: str, password: str, salt: str,
|
||||||
|
iterations: int, length: int) -> str
|
||||||
|
crypto.scrypt(password: str, salt: str, n: int, r: int, p: int,
|
||||||
|
length: int, [опционально]max_memory: int=0) -> str
|
||||||
|
|
||||||
|
crypto.features() -> table
|
||||||
|
```
|
||||||
|
|
||||||
|
Для хранения паролей следует использовать scrypt или PBKDF2 со случайной
|
||||||
|
солью. Обычный SHA256 для паролей не подходит
|
||||||
|
|
||||||
|
`features` возвращает версию OpenSSL и список доступных возможностей
|
||||||
|
|
||||||
|
Неверная подпись возвращает `false, "invalid_signature"`
|
||||||
|
|
||||||
|
Неверный тег при расшифровке возвращает `nil, "authentication_failed"`
|
||||||
|
|
||||||
|
Ошибки в аргументах вызывают обычную Lua ошибку
|
||||||
|
|
@ -69,6 +69,12 @@ gui.set_active_frame(
|
||||||
-- Используется для пользовательской проекции (например в 3D)
|
-- Используется для пользовательской проекции (например в 3D)
|
||||||
[опционально] cursorLocator: function() -> number, number
|
[опционально] cursorLocator: function() -> number, number
|
||||||
)
|
)
|
||||||
|
|
||||||
|
-- Создаёт снимок фрейма в виде объекта Canvas если указан id фрейма, или всего окна, в случае nil.
|
||||||
|
gui.screenshot(
|
||||||
|
-- id фрейма, созданного через gui.create_frame
|
||||||
|
[опционально] frameId: str
|
||||||
|
) -> Canvas | nil
|
||||||
```
|
```
|
||||||
|
|
||||||
## Разметка
|
## Разметка
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,39 @@
|
||||||
|
|
||||||
## HTTP-Запросы
|
## HTTP-Запросы
|
||||||
|
|
||||||
|
Существует настраиваемая функция `network.request`, которая позволяет выполнять HTTP-запросы с различными методами (GET, POST, PUT, DELETE и т.д.) и настраивать заголовки, тело запроса, таймаут и другие параметры.
|
||||||
|
|
||||||
|
> Для передачи двоичных данных в теле запроса, используйте массив байт (Bytearray) или строку. В `on_response` строку `body` можно преобразовать в массив байт с помощью `Bytearray(response.body)`.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
network.request(
|
||||||
|
url: string,
|
||||||
|
parameters: {
|
||||||
|
-- Метод запроса (GET, POST, PUT, DELETE и т.д.)
|
||||||
|
method: string,
|
||||||
|
-- Тело запроса в виде строки или массива байт (Bytearray)
|
||||||
|
body: table|string,
|
||||||
|
-- Список дополнительных заголовков запроса
|
||||||
|
headers: table<string>,
|
||||||
|
-- Таймаут в миллисекундах
|
||||||
|
timeout: int,
|
||||||
|
-- Проверять ли SSL-сертификат
|
||||||
|
verify_ssl: boolean,
|
||||||
|
-- Функция, вызываемая при получении ответа
|
||||||
|
on_response: function(response: {
|
||||||
|
-- HTTP-код ответа
|
||||||
|
status: int,
|
||||||
|
-- Тело ответа в виде строки
|
||||||
|
body: string,
|
||||||
|
-- Список заголовков ответа
|
||||||
|
headers: table<string>
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Упрощённые функции для GET и POST запросов
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
-- Выполняет GET запрос к указанному URL.
|
-- Выполняет GET запрос к указанному URL.
|
||||||
network.get(
|
network.get(
|
||||||
|
|
|
||||||
|
|
@ -179,10 +179,14 @@ rig:is_visible([optional] index: int) -> bool
|
||||||
rig:set_visible([optional] index: int, status: bool)
|
rig:set_visible([optional] index: int, status: bool)
|
||||||
|
|
||||||
-- Возвращает цвет сущности
|
-- Возвращает цвет сущности
|
||||||
rig:get_color() -> vec3
|
rig:get_color() -> vec4
|
||||||
|
-- Возвращает цвет кости по индексу
|
||||||
|
rig:get_color(index: int) -> vec4
|
||||||
|
|
||||||
-- Устанавливает цвет сущности
|
-- Устанавливает цвет сущности
|
||||||
rig:set_color(color: vec3)
|
rig:set_color(color: vec3 | vec4)
|
||||||
|
-- Устанавливает цвет кости по индексу
|
||||||
|
rig:set_color(color: vec3 | vec4, index: int)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Пользовательские компоненты
|
## Пользовательские компоненты
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,18 @@ VCA файл состоит из набора директив, порядок
|
||||||
|
|
||||||
Мета-информация применяется глобально. Поведение не зависит от её положения в файле, но рекомендуется располагать её в начале.
|
Мета-информация применяется глобально. Поведение не зависит от её положения в файле, но рекомендуется располагать её в начале.
|
||||||
|
|
||||||
|
## Дополнительно
|
||||||
|
|
||||||
|
- `curve` - объявляет пользовательский тип кривой. Указывается имя кривой `name` и выражение вычисления значения `func`.
|
||||||
|
|
||||||
|
В выражении доступны два ключа `kl`, `kr` и значение `t` в диапазоне [0..1].
|
||||||
|
|
||||||
|
Пример:
|
||||||
|
|
||||||
|
`@curve test-linear func (kl.value + (kr.value - kl.value) * t)`
|
||||||
|
|
||||||
|
Пользовательские кривые указываются с префиксом `.`. Пример: `@move by y curve .test-linear {...}`.
|
||||||
|
|
||||||
## Кривые с ключевыми кадрами
|
## Кривые с ключевыми кадрами
|
||||||
|
|
||||||
Требуется через атрибут `curve` указать тип кривой:
|
Требуется через атрибут `curve` указать тип кривой:
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@
|
||||||
openal
|
openal
|
||||||
luajit
|
luajit
|
||||||
curl
|
curl
|
||||||
|
openssl
|
||||||
entt
|
entt
|
||||||
mesa
|
mesa
|
||||||
freeglut
|
freeglut
|
||||||
|
|
|
||||||
|
|
@ -208,7 +208,7 @@ function Version.__less(ver1, ver2)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Version.__greater_or_equal(ver1, ver2)
|
function Version.__greater_or_equal(ver1, ver2)
|
||||||
return not Version.__less(ver2, ver1)
|
return not Version.__less(ver1, ver2)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Version.__less_or_equal(ver1, ver2)
|
function Version.__less_or_equal(ver1, ver2)
|
||||||
|
|
@ -227,6 +227,11 @@ function Version.compare(op, ver1, ver2)
|
||||||
ver1 = string.split(ver1, ".")
|
ver1 = string.split(ver1, ".")
|
||||||
ver2 = string.split(ver2, ".")
|
ver2 = string.split(ver2, ".")
|
||||||
|
|
||||||
|
for i = 1, 3 do
|
||||||
|
ver1[i] = tonumber(ver1[i]) or 0
|
||||||
|
ver2[i] = tonumber(ver2[i]) or 0
|
||||||
|
end
|
||||||
|
|
||||||
local comparison_func = Version.operators[op];
|
local comparison_func = Version.operators[op];
|
||||||
|
|
||||||
if comparison_func then
|
if comparison_func then
|
||||||
|
|
@ -252,7 +257,7 @@ end
|
||||||
|
|
||||||
local function compare_version(op, dependent_version, actual_version)
|
local function compare_version(op, dependent_version, actual_version)
|
||||||
if Version.matches_pattern(dependent_version) and Version.matches_pattern(actual_version) then
|
if Version.matches_pattern(dependent_version) and Version.matches_pattern(actual_version) then
|
||||||
return Version.compare(op, dep_ver, actual_version)
|
return Version.compare(op, actual_version, dependent_version)
|
||||||
elseif dependent_version == "*" or dependent_version == actual_version then
|
elseif dependent_version == "*" or dependent_version == actual_version then
|
||||||
return true
|
return true
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,5 @@
|
||||||
local internals = __vc_internals
|
local internals = __vc_internals
|
||||||
|
|
||||||
local this = {
|
|
||||||
CH_TRANSLATE = 1,
|
|
||||||
CH_ROTATE = 2,
|
|
||||||
CH_SCALE = 3,
|
|
||||||
CH_ZOOM = 4,
|
|
||||||
|
|
||||||
INT_CONST = 1,
|
|
||||||
INT_LINEAR = 2,
|
|
||||||
INT_BEZIER = 3,
|
|
||||||
|
|
||||||
TRACE_CODEGEN = false,
|
|
||||||
}
|
|
||||||
|
|
||||||
local INT_CONST = this.INT_CONST
|
|
||||||
local INT_BEZIER = this.INT_BEZIER
|
|
||||||
|
|
||||||
local function bezier(a, b, c, d, u)
|
local function bezier(a, b, c, d, u)
|
||||||
local s = 1 - u
|
local s = 1 - u
|
||||||
return s * s * s * a +
|
return s * s * s * a +
|
||||||
|
|
@ -53,264 +37,23 @@ local function bezier_interpolation(k0, k1, t)
|
||||||
return bezier(k0.value, k0.ry, k1.ly, k1.value, u)
|
return bezier(k0.value, k0.ry, k1.ly, k1.value, u)
|
||||||
end
|
end
|
||||||
|
|
||||||
local patterns = {
|
local this = {
|
||||||
{name="sint", pattern="sin(t)"},
|
CH_TRANSLATE = 1,
|
||||||
{name="sint2", pattern="sin(t * 2)"},
|
CH_ROTATE = 2,
|
||||||
}
|
CH_SCALE = 3,
|
||||||
local exclude_patters = {
|
CH_ZOOM = 4,
|
||||||
"end",
|
|
||||||
(string.pattern_safe("'")),
|
|
||||||
(string.pattern_safe('"')),
|
|
||||||
(string.pattern_safe("--")),
|
|
||||||
(string.pattern_safe("..")),
|
|
||||||
}
|
|
||||||
|
|
||||||
-- TODO: replace with actual expression -> lua translator
|
INT_CONST = 1,
|
||||||
local function process_expression(src, memoised)
|
INT_LINEAR = 2,
|
||||||
for i, pattern in ipairs(exclude_patters) do
|
INT_BEZIER = 3,
|
||||||
if src:find(pattern) then
|
INT_CUSTOM = 4,
|
||||||
debug.print(exclude_patters)
|
|
||||||
error("invalid syntax "..string.escape(src))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
for i, pattern in ipairs(patterns) do
|
|
||||||
local pattern_safe = string.pattern_safe(pattern.pattern)
|
|
||||||
if src:find(pattern_safe) then
|
|
||||||
memoised[pattern.name] = pattern.pattern
|
|
||||||
src = src:gsub(pattern_safe, pattern.name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return src
|
|
||||||
end
|
|
||||||
|
|
||||||
local function key_neighbors(keys, frame)
|
TRACE_CODEGEN = false,
|
||||||
local left = 1
|
|
||||||
local right = #keys
|
|
||||||
|
|
||||||
while left <= right do
|
maths = {
|
||||||
local mid = math.floor((left + right) / 2)
|
bezier_interpolation = bezier_interpolation
|
||||||
|
|
||||||
if keys[mid].frame < frame then
|
|
||||||
left = mid + 1
|
|
||||||
elseif keys[mid].frame > frame then
|
|
||||||
right = mid - 1
|
|
||||||
else
|
|
||||||
return mid, mid
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if left > #keys then
|
|
||||||
left = #keys
|
|
||||||
end
|
|
||||||
return right, left
|
|
||||||
end
|
|
||||||
|
|
||||||
local env = {
|
|
||||||
mat4 = mat4,
|
|
||||||
e = math.exp(1),
|
|
||||||
X = {1, 0, 0},
|
|
||||||
Y = {0, 1, 0},
|
|
||||||
Z = {0, 0, 1},
|
|
||||||
DST = mat4.idt(),
|
|
||||||
value_at = function(keys, frame, interp)
|
|
||||||
local left, right = key_neighbors(keys, frame)
|
|
||||||
if left == right then
|
|
||||||
return keys[left].value
|
|
||||||
end
|
|
||||||
left = keys[left]
|
|
||||||
if interp == INT_CONST then
|
|
||||||
return left.value
|
|
||||||
end
|
|
||||||
right = keys[right]
|
|
||||||
if left == nil then
|
|
||||||
return right.value
|
|
||||||
end
|
|
||||||
local t = (frame - left.frame) / (right.frame - left.frame)
|
|
||||||
if interp == INT_BEZIER then
|
|
||||||
return bezier_interpolation(left, right, t)
|
|
||||||
end
|
|
||||||
return left.value * (1.0 - t) + right.value * t
|
|
||||||
end,
|
|
||||||
set_matrix = function(target, matrix)
|
|
||||||
local info = mat4.decompose(matrix)
|
|
||||||
if info then
|
|
||||||
local pos = info.translation
|
|
||||||
local rot = info.rotation
|
|
||||||
local scale = info.scale
|
|
||||||
if target.set_pos then
|
|
||||||
target:set_pos(pos)
|
|
||||||
end
|
|
||||||
if target.set_rot then
|
|
||||||
target:set_rot(rot)
|
|
||||||
end
|
|
||||||
if target.set_scale then
|
|
||||||
target:set_scale(scale)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
dump = debug.print
|
|
||||||
}
|
|
||||||
|
|
||||||
local math_funcs = {
|
|
||||||
"sqrt", "min", "max", "deg", "rad", "log", "log10", "floor", "ceil", "sin",
|
|
||||||
"tan", "noise", "noise2", "sign", "round", "exp", "pi", "e"
|
|
||||||
}
|
|
||||||
for _, name in ipairs(math_funcs) do
|
|
||||||
env[name] = math[name]
|
|
||||||
end
|
|
||||||
|
|
||||||
local function codegen_track(raw_track, lineset, memoised, keysets, use_tsf)
|
|
||||||
local lines = lineset.lines
|
|
||||||
local code = ""
|
|
||||||
local has_tsf = false
|
|
||||||
local translation = {false, false, false}
|
|
||||||
local rotation = {false, false, false}
|
|
||||||
local scale = {false, false, false}
|
|
||||||
for i, line in ipairs(lines) do
|
|
||||||
if line.expression then
|
|
||||||
code = code .. "\n local l" .. i .. " = (" ..
|
|
||||||
process_expression(line.expression, memoised) .. ")"
|
|
||||||
elseif line.keys then
|
|
||||||
local target_keysets = keysets[lineset.target_name]
|
|
||||||
if not target_keysets then
|
|
||||||
target_keysets = {}
|
|
||||||
keysets[lineset.target_name] = target_keysets
|
|
||||||
end
|
|
||||||
target_keysets[i] = line.keys
|
|
||||||
code = code .. string.format(
|
|
||||||
"\n local l%d = value_at(keysets['%s'][%d], t * %s, %s)",
|
|
||||||
i, lineset.target_name, i, raw_track.fps, line.interp)
|
|
||||||
end
|
|
||||||
|
|
||||||
if line.channel == this.CH_TRANSLATE then
|
|
||||||
translation[line.axis] = i
|
|
||||||
has_tsf = true
|
|
||||||
elseif line.channel == this.CH_ROTATE then
|
|
||||||
rotation[line.axis] = i
|
|
||||||
has_tsf = true
|
|
||||||
elseif line.channel == this.CH_SCALE then
|
|
||||||
scale[line.axis] = i
|
|
||||||
has_tsf = true
|
|
||||||
elseif line.channel == this.CH_ZOOM then
|
|
||||||
code = code .. "\n zoom = l" .. i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if not has_tsf or not use_tsf then
|
|
||||||
return code
|
|
||||||
end
|
|
||||||
|
|
||||||
code = code .. "\n mat4.idt(dst)"
|
|
||||||
if translation[1] or translation[2] or translation[3] then
|
|
||||||
code = code .. "\n mat4.translate(dst, {" ..
|
|
||||||
(translation[1] and ("l" .. translation[1]) or '0').. ", " ..
|
|
||||||
(translation[2] and ("l" .. translation[2]) or '0').. ", " ..
|
|
||||||
(translation[3] and ("l" .. translation[3]) or '0').. "}, dst)"
|
|
||||||
end
|
|
||||||
|
|
||||||
local axis_names = {"X", "Y", "Z"}
|
|
||||||
local axis_indices = {X=1, Y=2, Z=3}
|
|
||||||
local rotation_order = raw_track.rotation_order
|
|
||||||
for i=1,3 do
|
|
||||||
local axis = axis_indices[rotation_order[i]]
|
|
||||||
local var = rotation[axis]
|
|
||||||
if var then
|
|
||||||
code = code .. "\n mat4.rotate(dst, " .. axis_names[axis] ..
|
|
||||||
", l" .. var .. ", dst)"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if scale[1] or scale[2] or scale[3] then
|
|
||||||
code = code .. "\n mat4.scale(dst, {" ..
|
|
||||||
(scale[1] and ("l" .. scale[1]) or '1').. ", " ..
|
|
||||||
(scale[2] and ("l" .. scale[2]) or '1').. ", " ..
|
|
||||||
(scale[3] and ("l" .. scale[3]) or '1').. "}, dst)"
|
|
||||||
end
|
|
||||||
|
|
||||||
return code
|
|
||||||
end
|
|
||||||
|
|
||||||
local function codegen_rig_target(raw_track, memoised, keysets)
|
|
||||||
local code = "\n if target.set_matrix and target.index then\n"
|
|
||||||
code = code .. " local dst = DST\n"
|
|
||||||
for bone, lineset in pairs(raw_track.linesets) do
|
|
||||||
if lineset.target_type ~= "bone" then
|
|
||||||
goto continue
|
|
||||||
end
|
|
||||||
local lineset_code = codegen_track(
|
|
||||||
raw_track, lineset, memoised, keysets, true)
|
|
||||||
|
|
||||||
code = code .. "\n do" .. lineset_code .. "\n end\n" ..
|
|
||||||
" target:set_matrix(target:index(" .. string.escape(bone) .. "), dst)\n"
|
|
||||||
::continue::
|
|
||||||
end
|
|
||||||
return code .. " end"
|
|
||||||
end
|
|
||||||
|
|
||||||
local function codegen_object_target(raw_track, memoised, keysets)
|
|
||||||
local code = "\n if target.set_pos then\n"
|
|
||||||
code = code .. " local dst = DST\n"
|
|
||||||
local lineset = raw_track.linesets[""]
|
|
||||||
if not lineset then
|
|
||||||
return ""
|
|
||||||
end
|
|
||||||
local lineset_code = codegen_track(
|
|
||||||
raw_track, lineset, memoised, keysets, true)
|
|
||||||
code = code .. "\n do" .. lineset_code .. "\n end\n"
|
|
||||||
.. " set_matrix(target, dst)\n"
|
|
||||||
return code .. " end"
|
|
||||||
end
|
|
||||||
|
|
||||||
local function codegen_camera_target(raw_track, memoised, keysets)
|
|
||||||
local code = "\n if target.set_zoom then\n"
|
|
||||||
code = code .. " local zoom = 1.0\n"
|
|
||||||
local lineset = raw_track.linesets[""]
|
|
||||||
if not lineset then
|
|
||||||
return ""
|
|
||||||
end
|
|
||||||
local lineset_code = codegen_track(
|
|
||||||
raw_track, lineset, memoised, keysets, false)
|
|
||||||
code = code .. "\n do" .. lineset_code .. "\n end\n"
|
|
||||||
.. " target:set_zoom(zoom)\n"
|
|
||||||
return code .. " end"
|
|
||||||
end
|
|
||||||
|
|
||||||
function this.compile_track(raw_track, track_name)
|
|
||||||
local code = ""
|
|
||||||
local memoised = {}
|
|
||||||
local keysets = {}
|
|
||||||
|
|
||||||
code = code .. codegen_rig_target(raw_track, memoised, keysets)
|
|
||||||
code = code .. codegen_object_target(raw_track, memoised, keysets)
|
|
||||||
code = code .. codegen_camera_target(raw_track, memoised, keysets)
|
|
||||||
|
|
||||||
local memoised_code = ""
|
|
||||||
for name, expression in pairs(memoised) do
|
|
||||||
memoised_code = memoised_code .. "\n local " .. name .. " = "
|
|
||||||
.. expression
|
|
||||||
end
|
|
||||||
|
|
||||||
if #memoised_code > 0 then
|
|
||||||
code = memoised_code .. "\n" .. code
|
|
||||||
end
|
|
||||||
|
|
||||||
local src = "return function(target, t, m)\n"
|
|
||||||
.. code .. "\nend"
|
|
||||||
|
|
||||||
if this.TRACE_CODEGEN then
|
|
||||||
debug.log("["..
|
|
||||||
string.escape(track_name or "nil").." codegen trace]:\n"..src)
|
|
||||||
end
|
|
||||||
|
|
||||||
local generator, err = load(
|
|
||||||
src, "<expr>", "bt", table.extend({keysets = keysets}, env))
|
|
||||||
if not generator then
|
|
||||||
error(err)
|
|
||||||
end
|
|
||||||
return {
|
|
||||||
duration = raw_track.duration,
|
|
||||||
func = generator(),
|
|
||||||
}
|
}
|
||||||
end
|
}
|
||||||
|
|
||||||
local loaded_tracks = {}
|
local loaded_tracks = {}
|
||||||
local backup_tracks = {}
|
local backup_tracks = {}
|
||||||
|
|
@ -330,8 +73,35 @@ function this.action(func)
|
||||||
table.insert(running_actions, coroutine.create(func))
|
table.insert(running_actions, coroutine.create(func))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local PlayingTrack = {
|
||||||
|
__index = {
|
||||||
|
stop = function(self)
|
||||||
|
table.remove_value(playing_tracks, self)
|
||||||
|
self.__timer = 0.0
|
||||||
|
self.__playing = false
|
||||||
|
end,
|
||||||
|
pause = function(self)
|
||||||
|
table.remove_value(playing_tracks, self)
|
||||||
|
self.__playing = false
|
||||||
|
end,
|
||||||
|
resume = function(self)
|
||||||
|
if not self.__playing then
|
||||||
|
table.insert(playing_tracks, self)
|
||||||
|
self.__playing = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function this.play(name, target)
|
function this.play(name, target)
|
||||||
table.insert(playing_tracks, {name=name, target=target, timer=0.0})
|
local track = setmetatable({
|
||||||
|
name = name,
|
||||||
|
target = target,
|
||||||
|
__timer = 0.0,
|
||||||
|
__playing = true,
|
||||||
|
}, PlayingTrack)
|
||||||
|
table.insert(playing_tracks, track)
|
||||||
|
return track
|
||||||
end
|
end
|
||||||
|
|
||||||
function internals.on_animation_frame()
|
function internals.on_animation_frame()
|
||||||
|
|
@ -353,11 +123,11 @@ function internals.on_animation_frame()
|
||||||
debug.error("animation track not found: "..track_info.name)
|
debug.error("animation track not found: "..track_info.name)
|
||||||
table.remove(playing_tracks, i)
|
table.remove(playing_tracks, i)
|
||||||
else
|
else
|
||||||
track_info.timer = track_info.timer + delta
|
track_info.__timer = track_info.__timer + delta
|
||||||
if track_info.timer > track.duration then
|
if track_info.__timer > track.duration then
|
||||||
table.remove(playing_tracks, i)
|
table.remove(playing_tracks, i)
|
||||||
else
|
else
|
||||||
track.func(track_info.target, track_info.timer)
|
track.func(track_info.target, track_info.__timer)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
298
res/modules/internal/animation_codegen.lua
Normal file
298
res/modules/internal/animation_codegen.lua
Normal file
|
|
@ -0,0 +1,298 @@
|
||||||
|
local internals = __vc_internals
|
||||||
|
|
||||||
|
local INT_CONST = animation.INT_CONST
|
||||||
|
local INT_BEZIER = animation.INT_BEZIER
|
||||||
|
|
||||||
|
local bezier_interpolation = animation.maths.bezier_interpolation
|
||||||
|
|
||||||
|
local patterns = {}
|
||||||
|
local exclude_patters = {
|
||||||
|
"end",
|
||||||
|
(string.pattern_safe("'")),
|
||||||
|
(string.pattern_safe('"')),
|
||||||
|
(string.pattern_safe("--")),
|
||||||
|
(string.pattern_safe("..")),
|
||||||
|
}
|
||||||
|
|
||||||
|
-- TODO: replace with actual expression -> lua translator
|
||||||
|
local function process_expression(src, memoised)
|
||||||
|
for i, pattern in ipairs(exclude_patters) do
|
||||||
|
if src:find(pattern) then
|
||||||
|
debug.print(exclude_patters)
|
||||||
|
error("invalid syntax "..string.escape(src))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for i, pattern in ipairs(patterns) do
|
||||||
|
local pattern_safe = string.pattern_safe(pattern.pattern)
|
||||||
|
if src:find(pattern_safe) then
|
||||||
|
memoised[pattern.name] = pattern.pattern
|
||||||
|
src = src:gsub(pattern_safe, pattern.name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return src
|
||||||
|
end
|
||||||
|
|
||||||
|
local function key_neighbors(keys, frame)
|
||||||
|
local left = 1
|
||||||
|
local right = #keys
|
||||||
|
|
||||||
|
while left <= right do
|
||||||
|
local mid = math.floor((left + right) / 2)
|
||||||
|
|
||||||
|
if keys[mid].frame < frame then
|
||||||
|
left = mid + 1
|
||||||
|
elseif keys[mid].frame > frame then
|
||||||
|
right = mid - 1
|
||||||
|
else
|
||||||
|
return mid, mid
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if left > #keys then
|
||||||
|
left = #keys
|
||||||
|
end
|
||||||
|
return right, left
|
||||||
|
end
|
||||||
|
|
||||||
|
local env = {
|
||||||
|
mat4 = mat4,
|
||||||
|
e = math.exp(1),
|
||||||
|
X = {1, 0, 0},
|
||||||
|
Y = {0, 1, 0},
|
||||||
|
Z = {0, 0, 1},
|
||||||
|
DST = mat4.idt(),
|
||||||
|
value_at = function(keys, frame, interp)
|
||||||
|
local left, right = key_neighbors(keys, frame)
|
||||||
|
if left == right then
|
||||||
|
return keys[left].value
|
||||||
|
end
|
||||||
|
left = keys[left]
|
||||||
|
if interp == INT_CONST then
|
||||||
|
return left.value
|
||||||
|
end
|
||||||
|
right = keys[right]
|
||||||
|
if left == nil then
|
||||||
|
return right.value
|
||||||
|
end
|
||||||
|
local t = (frame - left.frame) / (right.frame - left.frame)
|
||||||
|
if interp == INT_BEZIER then
|
||||||
|
return bezier_interpolation(left, right, t)
|
||||||
|
elseif type(interp) == "function" then
|
||||||
|
return interp(left.value, right.value, t)
|
||||||
|
end
|
||||||
|
return left.value * (1.0 - t) + right.value * t
|
||||||
|
end,
|
||||||
|
value_at_custom = function(keys, frame, func)
|
||||||
|
local left, right = key_neighbors(keys, frame)
|
||||||
|
if left == right then
|
||||||
|
return keys[left].value
|
||||||
|
end
|
||||||
|
left = keys[left]
|
||||||
|
right = keys[right]
|
||||||
|
if left == nil then
|
||||||
|
return right.value
|
||||||
|
end
|
||||||
|
local t = (frame - left.frame) / (right.frame - left.frame)
|
||||||
|
return func(left, right, t)
|
||||||
|
end,
|
||||||
|
set_matrix = function(target, matrix)
|
||||||
|
local info = mat4.decompose(matrix)
|
||||||
|
if info then
|
||||||
|
local pos = info.translation
|
||||||
|
local rot = info.rotation
|
||||||
|
local scale = info.scale
|
||||||
|
if target.set_pos then
|
||||||
|
target:set_pos(pos)
|
||||||
|
end
|
||||||
|
if target.set_rot then
|
||||||
|
target:set_rot(rot)
|
||||||
|
end
|
||||||
|
if target.set_scale then
|
||||||
|
target:set_scale(scale)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
dump = debug.print
|
||||||
|
}
|
||||||
|
|
||||||
|
local math_funcs = {
|
||||||
|
"sqrt", "min", "max", "deg", "rad", "log", "log10", "floor", "ceil", "sin",
|
||||||
|
"tan", "noise", "noise2", "sign", "round", "exp", "pi", "e"
|
||||||
|
}
|
||||||
|
for _, name in ipairs(math_funcs) do
|
||||||
|
env[name] = math[name]
|
||||||
|
end
|
||||||
|
|
||||||
|
local function codegen_track(raw_track, lineset, memoised, keysets, use_tsf)
|
||||||
|
local lines = lineset.lines
|
||||||
|
local code = ""
|
||||||
|
local has_tsf = false
|
||||||
|
local translation = {false, false, false}
|
||||||
|
local rotation = {false, false, false}
|
||||||
|
local scale = {false, false, false}
|
||||||
|
for i, line in ipairs(lines) do
|
||||||
|
if line.expression then
|
||||||
|
code = code .. "\n local l" .. i .. " = (" ..
|
||||||
|
process_expression(line.expression, memoised) .. ")"
|
||||||
|
elseif line.keys then
|
||||||
|
local target_keysets = keysets[lineset.target_name]
|
||||||
|
if not target_keysets then
|
||||||
|
target_keysets = {}
|
||||||
|
keysets[lineset.target_name] = target_keysets
|
||||||
|
end
|
||||||
|
target_keysets[i] = line.keys
|
||||||
|
|
||||||
|
if line.curve_func then
|
||||||
|
local valueat = string.format("curves[%s]", string.escape(line.curve_func))
|
||||||
|
|
||||||
|
code = code .. string.format(
|
||||||
|
"\n local l%d = value_at_custom(keysets['%s'][%d], t * %s, %s)",
|
||||||
|
i, lineset.target_name, i, raw_track.fps, valueat)
|
||||||
|
else
|
||||||
|
code = code .. string.format(
|
||||||
|
"\n local l%d = value_at(keysets['%s'][%d], t * %s, %s)",
|
||||||
|
i, lineset.target_name, i, raw_track.fps, line.interp)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if line.channel == animation.CH_TRANSLATE then
|
||||||
|
translation[line.axis] = i
|
||||||
|
has_tsf = true
|
||||||
|
elseif line.channel == animation.CH_ROTATE then
|
||||||
|
rotation[line.axis] = i
|
||||||
|
has_tsf = true
|
||||||
|
elseif line.channel == animation.CH_SCALE then
|
||||||
|
scale[line.axis] = i
|
||||||
|
has_tsf = true
|
||||||
|
elseif line.channel == animation.CH_ZOOM then
|
||||||
|
code = code .. "\n zoom = l" .. i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not has_tsf or not use_tsf then
|
||||||
|
return code
|
||||||
|
end
|
||||||
|
|
||||||
|
code = code .. "\n mat4.idt(dst)"
|
||||||
|
if translation[1] or translation[2] or translation[3] then
|
||||||
|
code = code .. "\n mat4.translate(dst, {" ..
|
||||||
|
(translation[1] and ("l" .. translation[1]) or '0').. ", " ..
|
||||||
|
(translation[2] and ("l" .. translation[2]) or '0').. ", " ..
|
||||||
|
(translation[3] and ("l" .. translation[3]) or '0').. "}, dst)"
|
||||||
|
end
|
||||||
|
|
||||||
|
local axis_names = {"X", "Y", "Z"}
|
||||||
|
local axis_indices = {X=1, Y=2, Z=3}
|
||||||
|
local rotation_order = raw_track.rotation_order or "XYZ"
|
||||||
|
for i=1,3 do
|
||||||
|
local axis = axis_indices[rotation_order[i]]
|
||||||
|
local var = rotation[axis]
|
||||||
|
if var then
|
||||||
|
code = code .. "\n mat4.rotate(dst, " .. axis_names[axis] ..
|
||||||
|
", l" .. var .. ", dst)"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if scale[1] or scale[2] or scale[3] then
|
||||||
|
code = code .. "\n mat4.scale(dst, {" ..
|
||||||
|
(scale[1] and ("l" .. scale[1]) or '1').. ", " ..
|
||||||
|
(scale[2] and ("l" .. scale[2]) or '1').. ", " ..
|
||||||
|
(scale[3] and ("l" .. scale[3]) or '1').. "}, dst)"
|
||||||
|
end
|
||||||
|
|
||||||
|
return code
|
||||||
|
end
|
||||||
|
|
||||||
|
local function codegen_rig_target(raw_track, context)
|
||||||
|
local code = "\n if target.set_matrix and target.index then\n"
|
||||||
|
code = code .. " local dst = DST\n"
|
||||||
|
for bone, lineset in pairs(raw_track.linesets) do
|
||||||
|
if lineset.target_type ~= "bone" then
|
||||||
|
goto continue
|
||||||
|
end
|
||||||
|
local lineset_code = codegen_track(
|
||||||
|
raw_track, lineset, context.memoised, context.keysets, true)
|
||||||
|
|
||||||
|
code = code .. "\n do" .. lineset_code .. "\n end\n" ..
|
||||||
|
" target:set_matrix(target:index(" .. string.escape(bone) .. "), dst)\n"
|
||||||
|
::continue::
|
||||||
|
end
|
||||||
|
return code .. " end"
|
||||||
|
end
|
||||||
|
|
||||||
|
local function codegen_object_target(raw_track, context)
|
||||||
|
local code = "\n if target.set_pos then\n"
|
||||||
|
code = code .. " local dst = DST\n"
|
||||||
|
local lineset = raw_track.linesets[""]
|
||||||
|
if not lineset then
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
local lineset_code = codegen_track(
|
||||||
|
raw_track, lineset, context.memoised, context.keysets, true)
|
||||||
|
code = code .. "\n do" .. lineset_code .. "\n end\n"
|
||||||
|
.. " set_matrix(target, dst)\n"
|
||||||
|
return code .. " end"
|
||||||
|
end
|
||||||
|
|
||||||
|
local function codegen_camera_target(raw_track, context)
|
||||||
|
local code = "\n if target.set_zoom then\n"
|
||||||
|
code = code .. " local zoom = 1.0\n"
|
||||||
|
local lineset = raw_track.linesets[""]
|
||||||
|
if not lineset then
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
local lineset_code = codegen_track(
|
||||||
|
raw_track, lineset, context.memoised, context.keysets, false)
|
||||||
|
code = code .. "\n do" .. lineset_code .. "\n end\n"
|
||||||
|
.. " target:set_zoom(zoom)\n"
|
||||||
|
return code .. " end"
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function internals.compile_animation_track(raw_track, track_name)
|
||||||
|
local code = ""
|
||||||
|
local context = {
|
||||||
|
memoised = {},
|
||||||
|
keysets = {},
|
||||||
|
curves = {},
|
||||||
|
}
|
||||||
|
for name, curve in pairs(raw_track.curves) do
|
||||||
|
context.curves[name] = load(string.format(
|
||||||
|
"return function(kl, kr, t) return %s end",
|
||||||
|
process_expression(curve.func, context.memoised)
|
||||||
|
), "<curve>", "t", env)()
|
||||||
|
end
|
||||||
|
|
||||||
|
code = code .. codegen_rig_target(raw_track, context)
|
||||||
|
code = code .. codegen_object_target(raw_track, context)
|
||||||
|
code = code .. codegen_camera_target(raw_track, context)
|
||||||
|
|
||||||
|
local memoised_code = ""
|
||||||
|
for name, expression in pairs(context.memoised) do
|
||||||
|
memoised_code = memoised_code .. "\n local " .. name .. " = "
|
||||||
|
.. expression
|
||||||
|
end
|
||||||
|
|
||||||
|
if #memoised_code > 0 then
|
||||||
|
code = memoised_code .. "\n" .. code
|
||||||
|
end
|
||||||
|
|
||||||
|
local src = "return function(target, t, m)\n"
|
||||||
|
.. code .. "\nend"
|
||||||
|
|
||||||
|
if animation.TRACE_CODEGEN then
|
||||||
|
debug.log("["..
|
||||||
|
string.escape(track_name or "nil").." codegen trace]:\n"..src)
|
||||||
|
end
|
||||||
|
|
||||||
|
local generator, err = load(
|
||||||
|
src, "<expr>", "bt", table.extend({
|
||||||
|
keysets = context.keysets, curves = context.curves
|
||||||
|
}, env))
|
||||||
|
if not generator then
|
||||||
|
error(err)
|
||||||
|
end
|
||||||
|
return {
|
||||||
|
duration = raw_track.duration,
|
||||||
|
func = generator(),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
local internals = __vc_internals
|
local internals = __vc_internals
|
||||||
|
|
||||||
local DEFAULT_FPS = 60
|
local DEFAULT_FPS = 60
|
||||||
local INT_BEZIER = animation.INT_BEZIER
|
|
||||||
|
|
||||||
local action_to_channel = {
|
local action_to_channel = {
|
||||||
move = animation.CH_TRANSLATE,
|
move = animation.CH_TRANSLATE,
|
||||||
|
|
@ -25,18 +24,23 @@ local function parse_configure(raw_track, node)
|
||||||
elseif node.duration then
|
elseif node.duration then
|
||||||
raw_track.duration = node.duration
|
raw_track.duration = node.duration
|
||||||
end
|
end
|
||||||
raw_track.rotation_order = string.upper(node["rotation-order"]) or "XYZ"
|
raw_track.rotation_order = string.upper(node["rotation-order"] or "XYZ")
|
||||||
end
|
end
|
||||||
|
|
||||||
local function parse_curve(line, node)
|
local function parse_curve(line, node)
|
||||||
line.interp = curve_to_interp[node.curve]
|
if node.curve:starts_with(".") then
|
||||||
|
line.interp = animation.INT_CUSTOM
|
||||||
|
line.curve_func = node.curve:sub(2)
|
||||||
|
else
|
||||||
|
line.interp = curve_to_interp[node.curve]
|
||||||
|
end
|
||||||
line.keys = {}
|
line.keys = {}
|
||||||
for j, key_node in ipairs(node) do
|
for j, key_node in ipairs(node) do
|
||||||
local keyframe = {
|
local keyframe = {
|
||||||
frame = tonumber(key_node.frame),
|
frame = tonumber(key_node.frame),
|
||||||
value = tonumber(key_node.value),
|
value = tonumber(key_node.value),
|
||||||
}
|
}
|
||||||
if line.interp == INT_BEZIER then
|
if line.interp == animation.INT_BEZIER then
|
||||||
keyframe.lx = tonumber(key_node.lx)
|
keyframe.lx = tonumber(key_node.lx)
|
||||||
keyframe.ly = tonumber(key_node.ly)
|
keyframe.ly = tonumber(key_node.ly)
|
||||||
keyframe.rx = tonumber(key_node.rx)
|
keyframe.rx = tonumber(key_node.rx)
|
||||||
|
|
@ -51,6 +55,7 @@ local function parse_track(root)
|
||||||
duration = math.huge,
|
duration = math.huge,
|
||||||
fps = DEFAULT_FPS,
|
fps = DEFAULT_FPS,
|
||||||
linesets = {},
|
linesets = {},
|
||||||
|
curves = {},
|
||||||
}
|
}
|
||||||
local linesets = raw_track.linesets
|
local linesets = raw_track.linesets
|
||||||
for i, node in ipairs(root) do
|
for i, node in ipairs(root) do
|
||||||
|
|
@ -61,6 +66,9 @@ local function parse_track(root)
|
||||||
if tag == "configure" then
|
if tag == "configure" then
|
||||||
parse_configure(raw_track, node)
|
parse_configure(raw_track, node)
|
||||||
goto continue
|
goto continue
|
||||||
|
elseif tag == "curve" then
|
||||||
|
raw_track.curves[node.name] = node
|
||||||
|
goto continue
|
||||||
end
|
end
|
||||||
|
|
||||||
local target_type = nil
|
local target_type = nil
|
||||||
|
|
@ -98,7 +106,7 @@ end
|
||||||
|
|
||||||
local function load_vca(source, filepath)
|
local function load_vca(source, filepath)
|
||||||
local raw_track = parse_track(xml.parse_vcd(source, "track"))
|
local raw_track = parse_track(xml.parse_vcd(source, "track"))
|
||||||
return animation.compile_track(raw_track, filepath)
|
return internals.compile_animation_track(raw_track, filepath)
|
||||||
end
|
end
|
||||||
|
|
||||||
function internals.load_vca_animation(filepath, source, identifier)
|
function internals.load_vca_animation(filepath, source, identifier)
|
||||||
|
|
|
||||||
|
|
@ -58,8 +58,8 @@ local Skeleton = {__index={
|
||||||
index=function(self, s) return __skeleton.index(self.eid, s) end,
|
index=function(self, s) return __skeleton.index(self.eid, s) end,
|
||||||
is_visible=function(self, i) return __skeleton.is_visible(self.eid, i) end,
|
is_visible=function(self, i) return __skeleton.is_visible(self.eid, i) end,
|
||||||
set_visible=function(self, i, b) return __skeleton.set_visible(self.eid, i, b) end,
|
set_visible=function(self, i, b) return __skeleton.set_visible(self.eid, i, b) end,
|
||||||
get_color=function(self) return __skeleton.get_color(self.eid) end,
|
get_color=function(self, ...) return __skeleton.get_color(self.eid, ...) end,
|
||||||
set_color=function(self, color) return __skeleton.set_color(self.eid, color) end,
|
set_color=function(self, color, ...) return __skeleton.set_color(self.eid, color, ...) end,
|
||||||
set_interpolated=function(self, b) return __skeleton.set_interpolated(self.eid, b) end,
|
set_interpolated=function(self, b) return __skeleton.set_interpolated(self.eid, b) end,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,40 +98,80 @@ local _udp_server_callbacks = {}
|
||||||
local _udp_client_datagram_callbacks = {}
|
local _udp_client_datagram_callbacks = {}
|
||||||
local _udp_client_open_callbacks = {}
|
local _udp_client_open_callbacks = {}
|
||||||
local _http_response_callbacks = {}
|
local _http_response_callbacks = {}
|
||||||
local _http_error_callbacks = {}
|
|
||||||
|
local http_request = network.__request
|
||||||
|
local open_tcp = network.__open_tcp
|
||||||
|
local open_udp = network.__open_udp
|
||||||
|
local connect_tcp = network.__connect_tcp
|
||||||
|
local connect_udp = network.__connect_udp
|
||||||
|
network.__request = nil
|
||||||
|
network.__open_tcp = nil
|
||||||
|
network.__open_udp = nil
|
||||||
|
network.__connect_tcp = nil
|
||||||
|
network.__connect_udp = nil
|
||||||
|
|
||||||
|
local function request(url, params)
|
||||||
|
local id = http_request(url, params)
|
||||||
|
if params.on_response then
|
||||||
|
_http_response_callbacks[id] = params.on_response
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
network.request = request
|
||||||
|
|
||||||
network.get = function(url, callback, errorCallback, headers)
|
network.get = function(url, callback, errorCallback, headers)
|
||||||
local id = network.__get(url, headers)
|
return request(url, {
|
||||||
if callback then
|
method = "GET",
|
||||||
_http_response_callbacks[id] = callback
|
headers = headers,
|
||||||
end
|
on_response = function(response)
|
||||||
if errorCallback then
|
if response.status / 100 == 2 then
|
||||||
_http_error_callbacks[id] = errorCallback
|
return callback(response.body)
|
||||||
end
|
else
|
||||||
|
return errorCallback(response.status, response.body)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
on_error = function(response)
|
||||||
|
errorCallback(response.status, response.body)
|
||||||
|
end,
|
||||||
|
follow_location = true,
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
network.get_binary = function(url, callback, errorCallback, headers)
|
network.get_binary = function(url, callback, errorCallback, headers)
|
||||||
local id = network.__get_binary(url, headers)
|
return request(url, {
|
||||||
if callback then
|
method = "GET",
|
||||||
_http_response_callbacks[id] = callback
|
headers = headers,
|
||||||
end
|
on_response = callback and (function (response)
|
||||||
if errorCallback then
|
if response.code / 100 == 2 then
|
||||||
_http_error_callbacks[id] = errorCallback
|
return callback(Bytearray(response.body))
|
||||||
end
|
else
|
||||||
|
return errorCallback(response.status, response.body)
|
||||||
|
end
|
||||||
|
end),
|
||||||
|
follow_location = true,
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
network.post = function(url, data, callback, errorCallback, headers)
|
network.post = function(url, body, callback, errorCallback, headers)
|
||||||
local id = network.__post(url, data, headers)
|
return request(url, {
|
||||||
if callback then
|
method = "POST",
|
||||||
_http_response_callbacks[id] = callback
|
headers = table.extend({
|
||||||
end
|
"Content-Type: application/json"
|
||||||
if errorCallback then
|
}, headers),
|
||||||
_http_error_callbacks[id] = errorCallback
|
body = body,
|
||||||
end
|
on_response = function(response)
|
||||||
|
if response.code / 100 == 2 then
|
||||||
|
return callback(Bytearray(response.body))
|
||||||
|
else
|
||||||
|
return errorCallback(response.status, response.body)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
follow_location = true,
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
network.tcp_open = function (port, handler)
|
network.tcp_open = function (port, handler)
|
||||||
local socket = setmetatable({id=network.__open_tcp(port)}, ServerSocket)
|
local socket = setmetatable({id=open_tcp(port)}, ServerSocket)
|
||||||
|
|
||||||
_tcp_server_callbacks[socket.id] = function(id)
|
_tcp_server_callbacks[socket.id] = function(id)
|
||||||
handler(setmetatable({id=id}, Socket))
|
handler(setmetatable({id=id}, Socket))
|
||||||
|
|
@ -141,7 +181,7 @@ end
|
||||||
|
|
||||||
network.tcp_connect = function(address, port, callback, errorCallback)
|
network.tcp_connect = function(address, port, callback, errorCallback)
|
||||||
local socket = setmetatable({id=0}, Socket)
|
local socket = setmetatable({id=0}, Socket)
|
||||||
socket.id = network.__connect_tcp(address, port)
|
socket.id = connect_tcp(address, port)
|
||||||
_tcp_client_callbacks[socket.id] = function() callback(socket) end
|
_tcp_client_callbacks[socket.id] = function() callback(socket) end
|
||||||
if errorCallback then
|
if errorCallback then
|
||||||
_tcp_client_error_callbacks[socket.id] = function(message) errorCallback(socket, message) end
|
_tcp_client_error_callbacks[socket.id] = function(message) errorCallback(socket, message) end
|
||||||
|
|
@ -154,7 +194,7 @@ network.udp_open = function (port, datagramHandler)
|
||||||
error "udp server cannot be opened without datagram handler"
|
error "udp server cannot be opened without datagram handler"
|
||||||
end
|
end
|
||||||
|
|
||||||
local socket = setmetatable({id=network.__open_udp(port)}, DatagramServerSocket)
|
local socket = setmetatable({id=open_udp(port)}, DatagramServerSocket)
|
||||||
|
|
||||||
_udp_server_callbacks[socket.id] = function(address, port, data)
|
_udp_server_callbacks[socket.id] = function(address, port, data)
|
||||||
datagramHandler(address, port, data, socket)
|
datagramHandler(address, port, data, socket)
|
||||||
|
|
@ -169,7 +209,7 @@ network.udp_connect = function (address, port, datagramHandler, openCallback)
|
||||||
end
|
end
|
||||||
|
|
||||||
local socket = setmetatable({id=0}, WriteableSocket)
|
local socket = setmetatable({id=0}, WriteableSocket)
|
||||||
socket.id = network.__connect_udp(address, port)
|
socket.id = connect_udp(address, port)
|
||||||
|
|
||||||
_udp_client_datagram_callbacks[socket.id] = datagramHandler
|
_udp_client_datagram_callbacks[socket.id] = datagramHandler
|
||||||
if openCallback then
|
if openCallback then
|
||||||
|
|
@ -239,20 +279,10 @@ network.__process_events = function()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
elseif etype == RESPONSE then
|
elseif etype == RESPONSE then
|
||||||
if event[2] / 100 == 2 then
|
local callback = _http_response_callbacks[event[3]]
|
||||||
local callback = _http_response_callbacks[event[3]]
|
_http_response_callbacks[event[3]] = nil
|
||||||
_http_response_callbacks[event[3]] = nil
|
if callback then
|
||||||
_http_error_callbacks[event[3]] = nil
|
callback(event[4])
|
||||||
if callback then
|
|
||||||
callback(event[4])
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local callback = _http_error_callbacks[event[3]]
|
|
||||||
_http_response_callbacks[event[3]] = nil
|
|
||||||
_http_error_callbacks[event[3]] = nil
|
|
||||||
if callback then
|
|
||||||
callback(event[2], event[4])
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -334,6 +334,7 @@ world.raycast = entities.__world_raycast
|
||||||
entities.__world_raycast = nil
|
entities.__world_raycast = nil
|
||||||
|
|
||||||
animation = require "core:animation"
|
animation = require "core:animation"
|
||||||
|
require "core:internal/animation_codegen"
|
||||||
require "core:internal/formats/vca"
|
require "core:internal/formats/vca"
|
||||||
|
|
||||||
__vc_scripts_registry = require "core:internal/scripts_registry"
|
__vc_scripts_registry = require "core:internal/scripts_registry"
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
layout (location = 0) in vec3 v_position;
|
layout (location = 0) in vec3 v_position;
|
||||||
layout (location = 1) in vec2 v_texCoord;
|
layout (location = 1) in vec2 v_texCoord;
|
||||||
layout (location = 2) in vec3 v_color;
|
layout (location = 2) in vec4 v_color;
|
||||||
layout (location = 3) in vec4 v_light;
|
layout (location = 3) in vec4 v_light;
|
||||||
layout (location = 4) in vec4 v_normal;
|
layout (location = 4) in vec4 v_normal;
|
||||||
|
|
||||||
|
|
@ -29,8 +29,8 @@ void main() {
|
||||||
|
|
||||||
a_dir = a_modelpos.xyz - u_cameraPos;
|
a_dir = a_modelpos.xyz - u_cameraPos;
|
||||||
vec3 skyLightColor = pick_sky_color(u_skybox, u_dayTime, u_minSkyLight);
|
vec3 skyLightColor = pick_sky_color(u_skybox, u_dayTime, u_minSkyLight);
|
||||||
a_color.rgb = max(a_color.rgb, skyLightColor.rgb * v_light.a) * v_color;
|
a_color.rgb = max(a_color.rgb, skyLightColor.rgb * v_light.a) * v_color.rgb;
|
||||||
a_color.a = u_opacity;
|
a_color.a = u_opacity * v_color.a;
|
||||||
|
|
||||||
mat4 viewmodel = u_view * u_model;
|
mat4 viewmodel = u_view * u_model;
|
||||||
a_distance = length(viewmodel * vec4(pos3d, 0.0));
|
a_distance = length(viewmodel * vec4(pos3d, 0.0));
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ endif()
|
||||||
find_package(ZLIB REQUIRED)
|
find_package(ZLIB REQUIRED)
|
||||||
find_package(PNG REQUIRED)
|
find_package(PNG REQUIRED)
|
||||||
find_package(CURL REQUIRED)
|
find_package(CURL REQUIRED)
|
||||||
|
find_package(OpenSSL 3.0 REQUIRED COMPONENTS Crypto)
|
||||||
find_package(glfw3 REQUIRED)
|
find_package(glfw3 REQUIRED)
|
||||||
find_package(Freetype REQUIRED)
|
find_package(Freetype REQUIRED)
|
||||||
if(NOT APPLE)
|
if(NOT APPLE)
|
||||||
|
|
@ -86,12 +87,13 @@ target_link_libraries(
|
||||||
ZLIB::ZLIB
|
ZLIB::ZLIB
|
||||||
PNG::PNG
|
PNG::PNG
|
||||||
CURL::libcurl
|
CURL::libcurl
|
||||||
|
OpenSSL::Crypto
|
||||||
OpenAL::OpenAL
|
OpenAL::OpenAL
|
||||||
Vorbis::vorbis
|
Vorbis::vorbis
|
||||||
Vorbis::vorbisfile
|
Vorbis::vorbisfile
|
||||||
luajit::luajit
|
|
||||||
Freetype::Freetype
|
Freetype::Freetype
|
||||||
PUBLIC glm::glm # Need public for src/delegates.hpp, which including to
|
PUBLIC luajit::luajit
|
||||||
|
glm::glm # Need public for src/delegates.hpp, which including to
|
||||||
# main.cpp
|
# main.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,8 @@ Skeleton::Skeleton(std::shared_ptr<const SkeletonConfig> config)
|
||||||
flags(config ? config->getBones().size() : 0),
|
flags(config ? config->getBones().size() : 0),
|
||||||
textures(),
|
textures(),
|
||||||
modelOverrides(config ? config->getBones().size() : 0),
|
modelOverrides(config ? config->getBones().size() : 0),
|
||||||
visible(true) {
|
visible(true),
|
||||||
|
boneTints(config ? config->getBones().size() : 0, glm::vec4(1.0f)) {
|
||||||
if (config == nullptr) {
|
if (config == nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -99,6 +100,7 @@ void Skeleton::setConfig(std::shared_ptr<const SkeletonConfig> rigConfig) {
|
||||||
|
|
||||||
modelOverrides.resize(bonesCount);
|
modelOverrides.resize(bonesCount);
|
||||||
flags.resize(bonesCount);
|
flags.resize(bonesCount);
|
||||||
|
boneTints.resize(bonesCount, glm::vec4(1.0f));
|
||||||
|
|
||||||
for (size_t i = 0; i < bonesCount; i++) {
|
for (size_t i = 0; i < bonesCount; i++) {
|
||||||
flags[i].visible = true;
|
flags[i].visible = true;
|
||||||
|
|
@ -175,6 +177,7 @@ void SkeletonConfig::render(
|
||||||
const Assets& assets,
|
const Assets& assets,
|
||||||
ModelBatch& batch,
|
ModelBatch& batch,
|
||||||
Skeleton& skeleton,
|
Skeleton& skeleton,
|
||||||
|
ModelLightingMode lightingMode,
|
||||||
const glm::mat3& rotation,
|
const glm::mat3& rotation,
|
||||||
const glm::vec3& position,
|
const glm::vec3& position,
|
||||||
const glm::vec3& scale
|
const glm::vec3& scale
|
||||||
|
|
@ -201,16 +204,24 @@ void SkeletonConfig::render(
|
||||||
if (auto foundOverride = modelOverride.model.lock()) {
|
if (auto foundOverride = modelOverride.model.lock()) {
|
||||||
model = foundOverride.get();
|
model = foundOverride.get();
|
||||||
}
|
}
|
||||||
if (model) {
|
if (model == nullptr) {
|
||||||
batch.draw(
|
if (!node->model.name.empty()) {
|
||||||
skeleton.calculated.matrices[i],
|
node->model.updateFlag = true;
|
||||||
skeleton.tint,
|
}
|
||||||
model,
|
continue;
|
||||||
&skeleton.textures
|
|
||||||
);
|
|
||||||
} else if (!node->model.name.empty()) {
|
|
||||||
node->model.updateFlag = true;
|
|
||||||
}
|
}
|
||||||
|
batch.draw(
|
||||||
|
skeleton.calculated.matrices[i],
|
||||||
|
skeleton.tint * skeleton.boneTints[i],
|
||||||
|
lightingMode == ModelLightingMode::SOLID
|
||||||
|
? position - glm::vec3(
|
||||||
|
skeleton.calculated.matrices[i] *
|
||||||
|
glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)
|
||||||
|
)
|
||||||
|
: glm::vec3(),
|
||||||
|
model,
|
||||||
|
&skeleton.textures
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
#include "typedefs.hpp"
|
#include "typedefs.hpp"
|
||||||
#include "data/dv_fwd.hpp"
|
#include "data/dv_fwd.hpp"
|
||||||
#include "util/Interpolation.hpp"
|
#include "util/Interpolation.hpp"
|
||||||
|
#include "util/EnumMetadata.hpp"
|
||||||
|
|
||||||
class Assets;
|
class Assets;
|
||||||
class ModelBatch;
|
class ModelBatch;
|
||||||
|
|
@ -19,6 +20,16 @@ namespace model {
|
||||||
struct Model;
|
struct Model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class ModelLightingMode {
|
||||||
|
BONE_BASED,
|
||||||
|
SOLID,
|
||||||
|
};
|
||||||
|
|
||||||
|
VC_ENUM_METADATA(ModelLightingMode)
|
||||||
|
{"bone-based", ModelLightingMode::BONE_BASED},
|
||||||
|
{"solid", ModelLightingMode::SOLID},
|
||||||
|
VC_ENUM_END
|
||||||
|
|
||||||
namespace rigging {
|
namespace rigging {
|
||||||
struct Skeleton;
|
struct Skeleton;
|
||||||
class SkeletonConfig;
|
class SkeletonConfig;
|
||||||
|
|
@ -85,7 +96,8 @@ namespace rigging {
|
||||||
std::unordered_map<std::string, std::string> textures;
|
std::unordered_map<std::string, std::string> textures;
|
||||||
std::vector<ModelReference> modelOverrides;
|
std::vector<ModelReference> modelOverrides;
|
||||||
bool visible;
|
bool visible;
|
||||||
glm::vec3 tint {1.0f, 1.0f, 1.0f};
|
glm::vec4 tint {1.0f, 1.0f, 1.0f, 1.0f};
|
||||||
|
std::vector<glm::vec4> boneTints;
|
||||||
|
|
||||||
util::VecInterpolation<3, float> interpolation {false};
|
util::VecInterpolation<3, float> interpolation {false};
|
||||||
|
|
||||||
|
|
@ -131,6 +143,7 @@ namespace rigging {
|
||||||
const Assets& assets,
|
const Assets& assets,
|
||||||
ModelBatch& batch,
|
ModelBatch& batch,
|
||||||
Skeleton& skeleton,
|
Skeleton& skeleton,
|
||||||
|
ModelLightingMode lightingMode,
|
||||||
const glm::mat3& rotation,
|
const glm::mat3& rotation,
|
||||||
const glm::vec3& position,
|
const glm::vec3& position,
|
||||||
const glm::vec3& scale
|
const glm::vec3& scale
|
||||||
|
|
|
||||||
|
|
@ -94,4 +94,8 @@ template<> void ContentUnitLoader<EntityDef>::loadUnit(
|
||||||
root.at("mass").get(def.mass);
|
root.at("mass").get(def.mass);
|
||||||
root.at("elasticity").get(def.elasticity);
|
root.at("elasticity").get(def.elasticity);
|
||||||
root.at("step-height").get(def.stepHeight);
|
root.at("step-height").get(def.stepHeight);
|
||||||
|
|
||||||
|
std::string lightingModeName;
|
||||||
|
root.at("lighting-mode").get(lightingModeName);
|
||||||
|
ModelLightingModeMeta.getItem(lightingModeName, def.lightingMode);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1247
src/crypto/Crypto.cpp
Normal file
1247
src/crypto/Crypto.cpp
Normal file
File diff suppressed because it is too large
Load diff
185
src/crypto/Crypto.hpp
Normal file
185
src/crypto/Crypto.hpp
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace crypto {
|
||||||
|
constexpr int API_VERSION = 1;
|
||||||
|
using Bytes = std::vector<std::uint8_t>;
|
||||||
|
|
||||||
|
enum class ErrorCode {
|
||||||
|
InvalidArgument,
|
||||||
|
InvalidKey,
|
||||||
|
InvalidSignature,
|
||||||
|
AuthenticationFailed,
|
||||||
|
UnsupportedAlgorithm,
|
||||||
|
OutputTooLarge,
|
||||||
|
InvalidState,
|
||||||
|
BackendError
|
||||||
|
};
|
||||||
|
|
||||||
|
class Error : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
Error(ErrorCode code, const std::string& message);
|
||||||
|
ErrorCode code() const noexcept;
|
||||||
|
const char* codeName() const noexcept;
|
||||||
|
private:
|
||||||
|
ErrorCode errorCode;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct KeyPair {
|
||||||
|
Bytes privateKey;
|
||||||
|
Bytes publicKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Features {
|
||||||
|
bool sha256;
|
||||||
|
bool sha384;
|
||||||
|
bool sha512;
|
||||||
|
bool md5;
|
||||||
|
bool hmac;
|
||||||
|
bool ed25519;
|
||||||
|
bool ecdsa;
|
||||||
|
bool rsa;
|
||||||
|
bool x25519;
|
||||||
|
bool p256;
|
||||||
|
bool aes128Gcm;
|
||||||
|
bool aes256Gcm;
|
||||||
|
bool chacha20Poly1305;
|
||||||
|
bool random;
|
||||||
|
bool hkdf;
|
||||||
|
bool pbkdf2;
|
||||||
|
bool scrypt;
|
||||||
|
};
|
||||||
|
|
||||||
|
class HashContext {
|
||||||
|
public:
|
||||||
|
explicit HashContext(std::string_view hash);
|
||||||
|
~HashContext();
|
||||||
|
HashContext(HashContext&&) noexcept;
|
||||||
|
HashContext& operator=(HashContext&&) noexcept;
|
||||||
|
HashContext(const HashContext&) = delete;
|
||||||
|
HashContext& operator=(const HashContext&) = delete;
|
||||||
|
|
||||||
|
void update(std::string_view data);
|
||||||
|
Bytes final();
|
||||||
|
void reset();
|
||||||
|
private:
|
||||||
|
struct Impl;
|
||||||
|
std::unique_ptr<Impl> impl;
|
||||||
|
};
|
||||||
|
|
||||||
|
Bytes digest(std::string_view hash, std::string_view data);
|
||||||
|
Bytes hmac(
|
||||||
|
std::string_view hash, std::string_view key, std::string_view data
|
||||||
|
);
|
||||||
|
|
||||||
|
KeyPair ed25519KeyPair();
|
||||||
|
Bytes ed25519Public(std::string_view privateKey);
|
||||||
|
Bytes ed25519Sign(std::string_view privateKey, std::string_view message);
|
||||||
|
|
||||||
|
bool ed25519Verify(
|
||||||
|
std::string_view publicKey,
|
||||||
|
std::string_view message,
|
||||||
|
std::string_view signature
|
||||||
|
);
|
||||||
|
bool ecdsaVerify(
|
||||||
|
std::string_view curve,
|
||||||
|
std::string_view publicKey,
|
||||||
|
std::string_view message,
|
||||||
|
std::string_view signature,
|
||||||
|
std::string_view hash
|
||||||
|
);
|
||||||
|
KeyPair ecdsaKeyPair(std::string_view curve);
|
||||||
|
Bytes ecdsaPublic(std::string_view curve, std::string_view privateKey);
|
||||||
|
Bytes ecdsaSign(
|
||||||
|
std::string_view curve,
|
||||||
|
std::string_view privateKey,
|
||||||
|
std::string_view message,
|
||||||
|
std::string_view hash
|
||||||
|
);
|
||||||
|
bool rsaPkcs1Verify(
|
||||||
|
std::string_view hash,
|
||||||
|
std::string_view modulus,
|
||||||
|
std::string_view exponent,
|
||||||
|
std::string_view message,
|
||||||
|
std::string_view signature
|
||||||
|
);
|
||||||
|
bool rsaPssVerify(
|
||||||
|
std::string_view hash,
|
||||||
|
std::string_view modulus,
|
||||||
|
std::string_view exponent,
|
||||||
|
std::string_view message,
|
||||||
|
std::string_view signature,
|
||||||
|
int saltLength
|
||||||
|
);
|
||||||
|
|
||||||
|
KeyPair x25519KeyPair();
|
||||||
|
Bytes x25519Public(std::string_view privateKey);
|
||||||
|
Bytes x25519(std::string_view privateKey, std::string_view peerPublicKey);
|
||||||
|
Bytes p256Public(std::string_view privateKey);
|
||||||
|
Bytes p256Shared(
|
||||||
|
std::string_view privateKey, std::string_view peerPublicKey
|
||||||
|
);
|
||||||
|
|
||||||
|
Bytes aesGcmEncrypt(
|
||||||
|
std::string_view key,
|
||||||
|
std::string_view nonce,
|
||||||
|
std::string_view aad,
|
||||||
|
std::string_view plaintext
|
||||||
|
);
|
||||||
|
Bytes aesGcmDecrypt(
|
||||||
|
std::string_view key,
|
||||||
|
std::string_view nonce,
|
||||||
|
std::string_view aad,
|
||||||
|
std::string_view ciphertextWithTag
|
||||||
|
);
|
||||||
|
Bytes chacha20Poly1305Encrypt(
|
||||||
|
std::string_view key,
|
||||||
|
std::string_view nonce,
|
||||||
|
std::string_view aad,
|
||||||
|
std::string_view plaintext
|
||||||
|
);
|
||||||
|
Bytes chacha20Poly1305Decrypt(
|
||||||
|
std::string_view key,
|
||||||
|
std::string_view nonce,
|
||||||
|
std::string_view aad,
|
||||||
|
std::string_view ciphertextWithTag
|
||||||
|
);
|
||||||
|
|
||||||
|
Bytes randomBytes(std::size_t length);
|
||||||
|
bool constantTimeEqual(std::string_view left, std::string_view right);
|
||||||
|
Bytes hkdfExtract(
|
||||||
|
std::string_view hash, std::string_view salt, std::string_view ikm
|
||||||
|
);
|
||||||
|
Bytes hkdfExpand(
|
||||||
|
std::string_view hash,
|
||||||
|
std::string_view prk,
|
||||||
|
std::string_view info,
|
||||||
|
std::size_t length
|
||||||
|
);
|
||||||
|
Bytes pbkdf2(
|
||||||
|
std::string_view hash,
|
||||||
|
std::string_view password,
|
||||||
|
std::string_view salt,
|
||||||
|
std::uint32_t iterations,
|
||||||
|
std::size_t length
|
||||||
|
);
|
||||||
|
Bytes scrypt(
|
||||||
|
std::string_view password,
|
||||||
|
std::string_view salt,
|
||||||
|
std::uint64_t n,
|
||||||
|
std::uint64_t r,
|
||||||
|
std::uint64_t p,
|
||||||
|
std::size_t length,
|
||||||
|
std::uint64_t maxMemory = 0
|
||||||
|
);
|
||||||
|
|
||||||
|
const char* backendVersion();
|
||||||
|
Features features();
|
||||||
|
}
|
||||||
|
|
@ -117,6 +117,13 @@ std::shared_ptr<Texture> Framebuffer::getSharedTexture() const {
|
||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<ImageData> Framebuffer::readData() const {
|
||||||
|
if (!texture) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return texture->readData();
|
||||||
|
}
|
||||||
|
|
||||||
uint Framebuffer::getWidth() const {
|
uint Framebuffer::getWidth() const {
|
||||||
return width;
|
return width;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
class Texture;
|
class Texture;
|
||||||
|
class ImageData;
|
||||||
|
|
||||||
class Framebuffer : public Bindable {
|
class Framebuffer : public Bindable {
|
||||||
uint fbo;
|
uint fbo;
|
||||||
|
|
@ -36,6 +37,8 @@ public:
|
||||||
|
|
||||||
std::shared_ptr<Texture> getSharedTexture() const;
|
std::shared_ptr<Texture> getSharedTexture() const;
|
||||||
|
|
||||||
|
std::unique_ptr<ImageData> readData() const;
|
||||||
|
|
||||||
/// @brief Get framebuffer width
|
/// @brief Get framebuffer width
|
||||||
uint getWidth() const;
|
uint getWidth() const;
|
||||||
/// @brief Get framebuffer height
|
/// @brief Get framebuffer height
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,7 @@ void BlockWrapsRenderer::draw(const DrawContext& pctx) {
|
||||||
u64id_t BlockWrapsRenderer::add(
|
u64id_t BlockWrapsRenderer::add(
|
||||||
const glm::ivec3& position,
|
const glm::ivec3& position,
|
||||||
const std::string& texture,
|
const std::string& texture,
|
||||||
const glm::vec3& tint,
|
const glm::vec4& tint,
|
||||||
float emission
|
float emission
|
||||||
) {
|
) {
|
||||||
u64id_t id = nextWrapper++;
|
u64id_t id = nextWrapper++;
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ class DrawContext;
|
||||||
struct BlockWrapper {
|
struct BlockWrapper {
|
||||||
glm::ivec3 position;
|
glm::ivec3 position;
|
||||||
std::array<std::string, 6> textureFaces {};
|
std::array<std::string, 6> textureFaces {};
|
||||||
std::array<glm::vec3, 6> tints {};
|
std::array<glm::vec4, 6> tints {};
|
||||||
float emission = 0.0f;
|
float emission = 0.0f;
|
||||||
|
|
||||||
// --- render cache ---
|
// --- render cache ---
|
||||||
|
|
@ -56,7 +56,7 @@ public:
|
||||||
u64id_t add(
|
u64id_t add(
|
||||||
const glm::ivec3& position,
|
const glm::ivec3& position,
|
||||||
const std::string& texture,
|
const std::string& texture,
|
||||||
const glm::vec3& tint,
|
const glm::vec4& tint,
|
||||||
float emission
|
float emission
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -357,20 +357,30 @@ void BlocksRenderer::blockCustomModel(
|
||||||
const auto& vcoord = vertex.coord - 0.5f;
|
const auto& vcoord = vertex.coord - 0.5f;
|
||||||
|
|
||||||
glm::vec4 aoColor {1.0f, 1.0f, 1.0f, 1.0f};
|
glm::vec4 aoColor {1.0f, 1.0f, 1.0f, 1.0f};
|
||||||
if (shading && ao) {
|
if (shading) {
|
||||||
const float eps = 0.05f;
|
const float eps = 0.05f;
|
||||||
auto p = coord + vcoord.x * X + vcoord.y * Y + vcoord.z * Z +
|
auto p = coord + vcoord.x * X + vcoord.y * Y + vcoord.z * Z +
|
||||||
r * 0.5f + t * 0.5f + n * eps;
|
r * 0.5f + t * 0.5f + n * eps;
|
||||||
auto p1 = p + n * eps;
|
if (ao) {
|
||||||
auto p2 = p + n * 0.5f;
|
auto p1 = p + n * eps;
|
||||||
aoColor = pickSoftLight(p1.x, p1.y, p1.z, glm::ivec3(r), glm::ivec3(t));
|
auto p2 = p + n * 0.5f;
|
||||||
if (!block.lightPassing) {
|
aoColor = pickSoftLight(p1.x, p1.y, p1.z, glm::ivec3(r), glm::ivec3(t));
|
||||||
aoColor = glm::max(
|
if (!block.lightPassing) {
|
||||||
aoColor,
|
aoColor = glm::max(
|
||||||
pickSoftLight(
|
aoColor,
|
||||||
p2.x, p2.y, p2.z, glm::ivec3(r), glm::ivec3(t)
|
pickSoftLight(
|
||||||
)
|
p2.x, p2.y, p2.z, glm::ivec3(r), glm::ivec3(t)
|
||||||
);
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
aoColor = pickLight(glm::floor(p + 0.5f) + 0.5f);
|
||||||
|
if (!block.lightPassing) {
|
||||||
|
aoColor = glm::max(
|
||||||
|
aoColor,
|
||||||
|
pickLight(glm::floor(p + n + 0.5f) + 0.5f)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this->vertex(
|
this->vertex(
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ void HandsRenderer::render(const Camera& camera) {
|
||||||
assets,
|
assets,
|
||||||
modelBatch,
|
modelBatch,
|
||||||
skeleton,
|
skeleton,
|
||||||
|
ModelLightingMode::SOLID,
|
||||||
glm::mat3(1.0f),
|
glm::mat3(1.0f),
|
||||||
glm::vec3(),
|
glm::vec3(),
|
||||||
glm::vec3(1.0f)
|
glm::vec3(1.0f)
|
||||||
|
|
|
||||||
|
|
@ -82,12 +82,75 @@ inline glm::vec4 do_tint(float value) {
|
||||||
return {value, value, value, 1.0f};
|
return {value, value, value, 1.0f};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainBatch::quad(
|
||||||
|
const glm::vec3& pos,
|
||||||
|
const glm::vec3& right,
|
||||||
|
const glm::vec3& up,
|
||||||
|
const glm::vec3& normal,
|
||||||
|
const glm::vec2& size,
|
||||||
|
const glm::vec4& light,
|
||||||
|
const glm::vec4& tint,
|
||||||
|
const UVRegion& subregion,
|
||||||
|
float emission
|
||||||
|
) {
|
||||||
|
prepare(6);
|
||||||
|
vertex(
|
||||||
|
pos - right * size.x * 0.5f - up * size.y * 0.5f,
|
||||||
|
{subregion.u1, subregion.v1},
|
||||||
|
light,
|
||||||
|
tint,
|
||||||
|
normal,
|
||||||
|
emission
|
||||||
|
);
|
||||||
|
vertex(
|
||||||
|
pos + right * size.x * 0.5f - up * size.y * 0.5f,
|
||||||
|
{subregion.u2, subregion.v1},
|
||||||
|
light,
|
||||||
|
tint,
|
||||||
|
normal,
|
||||||
|
emission
|
||||||
|
);
|
||||||
|
vertex(
|
||||||
|
pos + right * size.x * 0.5f + up * size.y * 0.5f,
|
||||||
|
{subregion.u2, subregion.v2},
|
||||||
|
light,
|
||||||
|
tint,
|
||||||
|
normal,
|
||||||
|
emission
|
||||||
|
);
|
||||||
|
|
||||||
|
vertex(
|
||||||
|
pos - right * size.x * 0.5f - up * size.y * 0.5f,
|
||||||
|
{subregion.u1, subregion.v1},
|
||||||
|
light,
|
||||||
|
tint,
|
||||||
|
normal,
|
||||||
|
emission
|
||||||
|
);
|
||||||
|
vertex(
|
||||||
|
pos + right * size.x * 0.5f + up * size.y * 0.5f,
|
||||||
|
{subregion.u2, subregion.v2},
|
||||||
|
light,
|
||||||
|
tint,
|
||||||
|
normal,
|
||||||
|
emission
|
||||||
|
);
|
||||||
|
vertex(
|
||||||
|
pos - right * size.x * 0.5f + up * size.y * 0.5f,
|
||||||
|
{subregion.u1, subregion.v2},
|
||||||
|
light,
|
||||||
|
tint,
|
||||||
|
normal,
|
||||||
|
emission
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void MainBatch::cube(
|
void MainBatch::cube(
|
||||||
const glm::vec3& coord,
|
const glm::vec3& coord,
|
||||||
const glm::vec3& size,
|
const glm::vec3& size,
|
||||||
const UVRegion(&texfaces)[6],
|
const UVRegion(&texfaces)[6],
|
||||||
const glm::vec4& lights,
|
const glm::vec4& lights,
|
||||||
const glm::vec3 tints[],
|
const glm::vec4 tints[],
|
||||||
float emission,
|
float emission,
|
||||||
uint8_t cullingBits
|
uint8_t cullingBits
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -17,30 +17,20 @@ class Chunks;
|
||||||
struct MainBatchVertex {
|
struct MainBatchVertex {
|
||||||
glm::vec3 position;
|
glm::vec3 position;
|
||||||
glm::vec2 uv;
|
glm::vec2 uv;
|
||||||
glm::vec3 tint;
|
glm::vec4 tint;
|
||||||
std::array<uint8_t, 4> color;
|
std::array<uint8_t, 4> color;
|
||||||
std::array<uint8_t, 4> normal;
|
std::array<uint8_t, 4> normal;
|
||||||
|
|
||||||
static constexpr VertexAttribute ATTRIBUTES[] = {
|
static constexpr VertexAttribute ATTRIBUTES[] = {
|
||||||
{VertexAttribute::Type::FLOAT, false, 3},
|
{VertexAttribute::Type::FLOAT, false, 3},
|
||||||
{VertexAttribute::Type::FLOAT, false, 2},
|
{VertexAttribute::Type::FLOAT, false, 2},
|
||||||
{VertexAttribute::Type::FLOAT, false, 3},
|
{VertexAttribute::Type::FLOAT, false, 4},
|
||||||
{VertexAttribute::Type::UNSIGNED_BYTE, true, 4},
|
{VertexAttribute::Type::UNSIGNED_BYTE, true, 4},
|
||||||
{VertexAttribute::Type::UNSIGNED_BYTE, true, 4},
|
{VertexAttribute::Type::UNSIGNED_BYTE, true, 4},
|
||||||
{{}, 0}};
|
{{}, 0}};
|
||||||
};
|
};
|
||||||
|
|
||||||
class MainBatch {
|
class MainBatch {
|
||||||
std::unique_ptr<MainBatchVertex[]> const buffer;
|
|
||||||
size_t const capacity;
|
|
||||||
size_t index;
|
|
||||||
|
|
||||||
UVRegion region {0.0f, 0.0f, 1.0f, 1.0f};
|
|
||||||
|
|
||||||
std::unique_ptr<Mesh<MainBatchVertex>> mesh;
|
|
||||||
std::unique_ptr<Texture> blank;
|
|
||||||
|
|
||||||
const Texture* texture = nullptr;
|
|
||||||
public:
|
public:
|
||||||
/// xyz, uv, color, compressed lights
|
/// xyz, uv, color, compressed lights
|
||||||
|
|
||||||
|
|
@ -63,7 +53,7 @@ public:
|
||||||
const glm::vec3& pos,
|
const glm::vec3& pos,
|
||||||
const glm::vec2& uv,
|
const glm::vec2& uv,
|
||||||
const glm::vec4& light,
|
const glm::vec4& light,
|
||||||
const glm::vec3& tint,
|
const glm::vec4& tint,
|
||||||
const glm::vec3& normal,
|
const glm::vec3& normal,
|
||||||
float emission
|
float emission
|
||||||
) {
|
) {
|
||||||
|
|
@ -84,76 +74,36 @@ public:
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void quad(
|
void quad(
|
||||||
const glm::vec3& pos,
|
const glm::vec3& pos,
|
||||||
const glm::vec3& right,
|
const glm::vec3& right,
|
||||||
const glm::vec3& up,
|
const glm::vec3& up,
|
||||||
const glm::vec3& normal,
|
const glm::vec3& normal,
|
||||||
const glm::vec2& size,
|
const glm::vec2& size,
|
||||||
const glm::vec4& light,
|
const glm::vec4& light,
|
||||||
const glm::vec3& tint,
|
const glm::vec4& tint,
|
||||||
const UVRegion& subregion,
|
const UVRegion& subregion,
|
||||||
float emission = 0.0f
|
float emission = 0.0f
|
||||||
) {
|
);
|
||||||
prepare(6);
|
|
||||||
vertex(
|
|
||||||
pos - right * size.x * 0.5f - up * size.y * 0.5f,
|
|
||||||
{subregion.u1, subregion.v1},
|
|
||||||
light,
|
|
||||||
tint,
|
|
||||||
normal,
|
|
||||||
emission
|
|
||||||
);
|
|
||||||
vertex(
|
|
||||||
pos + right * size.x * 0.5f - up * size.y * 0.5f,
|
|
||||||
{subregion.u2, subregion.v1},
|
|
||||||
light,
|
|
||||||
tint,
|
|
||||||
normal,
|
|
||||||
emission
|
|
||||||
);
|
|
||||||
vertex(
|
|
||||||
pos + right * size.x * 0.5f + up * size.y * 0.5f,
|
|
||||||
{subregion.u2, subregion.v2},
|
|
||||||
light,
|
|
||||||
tint,
|
|
||||||
normal,
|
|
||||||
emission
|
|
||||||
);
|
|
||||||
|
|
||||||
vertex(
|
|
||||||
pos - right * size.x * 0.5f - up * size.y * 0.5f,
|
|
||||||
{subregion.u1, subregion.v1},
|
|
||||||
light,
|
|
||||||
tint,
|
|
||||||
normal,
|
|
||||||
emission
|
|
||||||
);
|
|
||||||
vertex(
|
|
||||||
pos + right * size.x * 0.5f + up * size.y * 0.5f,
|
|
||||||
{subregion.u2, subregion.v2},
|
|
||||||
light,
|
|
||||||
tint,
|
|
||||||
normal,
|
|
||||||
emission
|
|
||||||
);
|
|
||||||
vertex(
|
|
||||||
pos - right * size.x * 0.5f + up * size.y * 0.5f,
|
|
||||||
{subregion.u1, subregion.v2},
|
|
||||||
light,
|
|
||||||
tint,
|
|
||||||
normal,
|
|
||||||
emission
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void cube(
|
void cube(
|
||||||
const glm::vec3& coord,
|
const glm::vec3& coord,
|
||||||
const glm::vec3& size,
|
const glm::vec3& size,
|
||||||
const UVRegion(&texfaces)[6],
|
const UVRegion(&texfaces)[6],
|
||||||
const glm::vec4& lights,
|
const glm::vec4& lights,
|
||||||
const glm::vec3 tints[],
|
const glm::vec4 tints[],
|
||||||
float emission,
|
float emission,
|
||||||
uint8_t cullingBits = 0xFF
|
uint8_t cullingBits = 0xFF
|
||||||
);
|
);
|
||||||
|
private:
|
||||||
|
std::unique_ptr<MainBatchVertex[]> const buffer;
|
||||||
|
size_t const capacity;
|
||||||
|
size_t index;
|
||||||
|
|
||||||
|
UVRegion region {0.0f, 0.0f, 1.0f, 1.0f};
|
||||||
|
|
||||||
|
std::unique_ptr<Mesh<MainBatchVertex>> mesh;
|
||||||
|
std::unique_ptr<Texture> blank;
|
||||||
|
|
||||||
|
const Texture* texture = nullptr;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -61,8 +61,8 @@ void ModelBatch::draw(
|
||||||
const model::Mesh& mesh,
|
const model::Mesh& mesh,
|
||||||
const glm::mat4& matrix,
|
const glm::mat4& matrix,
|
||||||
const glm::mat3& rotation,
|
const glm::mat3& rotation,
|
||||||
glm::vec3 tint,
|
const glm::vec4& tint,
|
||||||
const texture_names_map* varTextures,
|
const TextureNamesMap* varTextures,
|
||||||
bool backlight
|
bool backlight
|
||||||
) {
|
) {
|
||||||
setTexture(mesh.texture, varTextures);
|
setTexture(mesh.texture, varTextures);
|
||||||
|
|
@ -72,7 +72,7 @@ void ModelBatch::draw(
|
||||||
glm::vec4 lights(1, 1, 1, 0);
|
glm::vec4 lights(1, 1, 1, 0);
|
||||||
if (mesh.shading) {
|
if (mesh.shading) {
|
||||||
glm::vec3 gpos = matrix * glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
|
glm::vec3 gpos = matrix * glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
gpos += lightsOffset;
|
gpos += lightsOffset + localLightsOffset;
|
||||||
lights = MainBatch::sampleLight(gpos, chunks, backlight);
|
lights = MainBatch::sampleLight(gpos, chunks, backlight);
|
||||||
}
|
}
|
||||||
for (size_t i = 0; i < vcount / 3; i++) {
|
for (size_t i = 0; i < vcount / 3; i++) {
|
||||||
|
|
@ -97,14 +97,21 @@ void ModelBatch::draw(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModelBatch::draw(glm::mat4 matrix,
|
void ModelBatch::draw(
|
||||||
glm::vec3 tint,
|
const glm::mat4& matrix,
|
||||||
const model::Model* model,
|
const glm::vec4& tint,
|
||||||
const texture_names_map* varTextures) {
|
const glm::vec3& lightSampleOffset,
|
||||||
|
const model::Model* model,
|
||||||
|
const TextureNamesMap* varTextures
|
||||||
|
) {
|
||||||
for (const auto& mesh : model->meshes) {
|
for (const auto& mesh : model->meshes) {
|
||||||
entries.push_back({
|
entries.push_back(DrawEntry {
|
||||||
matrix, extract_rotation(matrix), tint, &mesh, varTextures
|
matrix,
|
||||||
});
|
extract_rotation(matrix),
|
||||||
|
tint,
|
||||||
|
lightSampleOffset,
|
||||||
|
&mesh,
|
||||||
|
varTextures});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -116,6 +123,7 @@ void ModelBatch::render() {
|
||||||
);
|
);
|
||||||
bool backlight = settings.graphics.backlight.get();
|
bool backlight = settings.graphics.backlight.get();
|
||||||
for (auto& entry : entries) {
|
for (auto& entry : entries) {
|
||||||
|
localLightsOffset = entry.lightSampleOffset;
|
||||||
draw(
|
draw(
|
||||||
*entry.mesh,
|
*entry.mesh,
|
||||||
entry.matrix,
|
entry.matrix,
|
||||||
|
|
@ -124,6 +132,7 @@ void ModelBatch::render() {
|
||||||
entry.varTextures,
|
entry.varTextures,
|
||||||
backlight
|
backlight
|
||||||
);
|
);
|
||||||
|
localLightsOffset = {};
|
||||||
}
|
}
|
||||||
batch->flush();
|
batch->flush();
|
||||||
entries.clear();
|
entries.clear();
|
||||||
|
|
@ -134,7 +143,7 @@ void ModelBatch::setLightsOffset(const glm::vec3& offset) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModelBatch::setTexture(const std::string& name,
|
void ModelBatch::setTexture(const std::string& name,
|
||||||
const texture_names_map* varTextures) {
|
const TextureNamesMap* varTextures) {
|
||||||
if (varTextures && !name.empty() && name.at(0) == '$') {
|
if (varTextures && !name.empty() && name.at(0) == '$') {
|
||||||
const auto& found = varTextures->find(name);
|
const auto& found = varTextures->find(name);
|
||||||
if (found == varTextures->end()) {
|
if (found == varTextures->end()) {
|
||||||
|
|
|
||||||
|
|
@ -18,37 +18,9 @@ namespace model {
|
||||||
struct Model;
|
struct Model;
|
||||||
}
|
}
|
||||||
|
|
||||||
using texture_names_map = std::unordered_map<std::string, std::string>;
|
using TextureNamesMap = std::unordered_map<std::string, std::string>;
|
||||||
|
|
||||||
class ModelBatch {
|
class ModelBatch {
|
||||||
const Assets& assets;
|
|
||||||
const Chunks& chunks;
|
|
||||||
|
|
||||||
const EngineSettings& settings;
|
|
||||||
glm::vec3 lightsOffset {};
|
|
||||||
|
|
||||||
static inline glm::vec3 SUN_VECTOR {0.411934f, 0.863868f, -0.279161f};
|
|
||||||
|
|
||||||
std::unique_ptr<MainBatch> batch;
|
|
||||||
|
|
||||||
void draw(const model::Mesh& mesh,
|
|
||||||
const glm::mat4& matrix,
|
|
||||||
const glm::mat3& rotation,
|
|
||||||
glm::vec3 tint,
|
|
||||||
const texture_names_map* varTextures,
|
|
||||||
bool backlight);
|
|
||||||
|
|
||||||
void setTexture(const std::string& name,
|
|
||||||
const texture_names_map* varTextures);
|
|
||||||
|
|
||||||
struct DrawEntry {
|
|
||||||
glm::mat4 matrix;
|
|
||||||
glm::mat3 rotation;
|
|
||||||
glm::vec3 tint;
|
|
||||||
const model::Mesh* mesh;
|
|
||||||
const texture_names_map* varTextures;
|
|
||||||
};
|
|
||||||
std::vector<DrawEntry> entries;
|
|
||||||
public:
|
public:
|
||||||
ModelBatch(
|
ModelBatch(
|
||||||
size_t capacity,
|
size_t capacity,
|
||||||
|
|
@ -58,11 +30,47 @@ public:
|
||||||
);
|
);
|
||||||
~ModelBatch();
|
~ModelBatch();
|
||||||
|
|
||||||
void draw(glm::mat4 matrix,
|
void draw(
|
||||||
glm::vec3 tint,
|
const glm::mat4& matrix,
|
||||||
const model::Model* model,
|
const glm::vec4& tint,
|
||||||
const texture_names_map* varTextures);
|
const glm::vec3& lightSampleOffset,
|
||||||
|
const model::Model* model,
|
||||||
|
const TextureNamesMap* varTextures
|
||||||
|
);
|
||||||
void render();
|
void render();
|
||||||
|
|
||||||
void setLightsOffset(const glm::vec3& offset);
|
void setLightsOffset(const glm::vec3& offset);
|
||||||
|
private:
|
||||||
|
const Assets& assets;
|
||||||
|
const Chunks& chunks;
|
||||||
|
|
||||||
|
const EngineSettings& settings;
|
||||||
|
glm::vec3 lightsOffset {};
|
||||||
|
glm::vec3 localLightsOffset {};
|
||||||
|
|
||||||
|
static inline glm::vec3 SUN_VECTOR {0.411934f, 0.863868f, -0.279161f};
|
||||||
|
|
||||||
|
std::unique_ptr<MainBatch> batch;
|
||||||
|
|
||||||
|
void draw(
|
||||||
|
const model::Mesh& mesh,
|
||||||
|
const glm::mat4& matrix,
|
||||||
|
const glm::mat3& rotation,
|
||||||
|
const glm::vec4& tint,
|
||||||
|
const TextureNamesMap* varTextures,
|
||||||
|
bool backlight
|
||||||
|
);
|
||||||
|
|
||||||
|
void setTexture(const std::string& name,
|
||||||
|
const TextureNamesMap* varTextures);
|
||||||
|
|
||||||
|
struct DrawEntry {
|
||||||
|
glm::mat4 matrix;
|
||||||
|
glm::mat3 rotation;
|
||||||
|
glm::vec4 tint;
|
||||||
|
glm::vec3 lightSampleOffset;
|
||||||
|
const model::Mesh* mesh;
|
||||||
|
const TextureNamesMap* varTextures;
|
||||||
|
};
|
||||||
|
std::vector<DrawEntry> entries;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,7 @@ void ParticlesRenderer::renderParticle(
|
||||||
-camera.front,
|
-camera.front,
|
||||||
preset.size * scale,
|
preset.size * scale,
|
||||||
light,
|
light,
|
||||||
glm::vec3(1.0f),
|
glm::vec4(1.0f),
|
||||||
particle.region,
|
particle.region,
|
||||||
preset.lighting ? 0.0f : 1.0f
|
preset.lighting ? 0.0f : 1.0f
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,7 @@ void PrecipitationRenderer::render(
|
||||||
glm::cross(glm::vec3(0, 1, 0), face.right),
|
glm::cross(glm::vec3(0, 1, 0), face.right),
|
||||||
FACE_SIZE,
|
FACE_SIZE,
|
||||||
light_at(chunks, pos.x, y, pos.z),
|
light_at(chunks, pos.x, y, pos.z),
|
||||||
glm::vec3(1.0f),
|
glm::vec4(1.0f),
|
||||||
calc_uv(pos, face.right, timer, weather)
|
calc_uv(pos, face.right, timer, weather)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -374,6 +374,14 @@ std::shared_ptr<gui::Frame> GUI::getActiveFrame() const {
|
||||||
return activeFrame;
|
return activeFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<gui::Frame> GUI::getFrame(const std::string& id) {
|
||||||
|
const auto& found = frames.find(id);
|
||||||
|
if (found == frames.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return found->second;
|
||||||
|
}
|
||||||
|
|
||||||
void GUI::remove(UINode* node) noexcept {
|
void GUI::remove(UINode* node) noexcept {
|
||||||
container->remove(node);
|
container->remove(node);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,8 @@ namespace gui {
|
||||||
|
|
||||||
std::shared_ptr<Frame> getActiveFrame() const;
|
std::shared_ptr<Frame> getActiveFrame() const;
|
||||||
|
|
||||||
|
std::shared_ptr<Frame> getFrame(const std::string& id);
|
||||||
|
|
||||||
/// @brief Remove node from the main container
|
/// @brief Remove node from the main container
|
||||||
void remove(UINode* node) noexcept;
|
void remove(UINode* node) noexcept;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -66,3 +66,10 @@ const std::string& gui::Frame::getOutputTexture() const {
|
||||||
const std::string& gui::Frame::getFrameId() const {
|
const std::string& gui::Frame::getFrameId() const {
|
||||||
return frameId;
|
return frameId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<ImageData> gui::Frame::takeScreenshot() const {
|
||||||
|
if (fbo == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return fbo->readData();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
class Framebuffer;
|
class Framebuffer;
|
||||||
class UiDocument;
|
class UiDocument;
|
||||||
|
class ImageData;
|
||||||
|
|
||||||
namespace gui {
|
namespace gui {
|
||||||
class Frame final : public Container {
|
class Frame final : public Container {
|
||||||
|
|
@ -18,6 +19,8 @@ namespace gui {
|
||||||
const std::string& getOutputTexture() const;
|
const std::string& getOutputTexture() const;
|
||||||
|
|
||||||
const std::string& getFrameId() const;
|
const std::string& getFrameId() const;
|
||||||
|
|
||||||
|
std::unique_ptr<ImageData> takeScreenshot() const;
|
||||||
private:
|
private:
|
||||||
std::string frameId;
|
std::string frameId;
|
||||||
std::unique_ptr<Framebuffer> fbo;
|
std::unique_ptr<Framebuffer> fbo;
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ extern const luaL_Reg byteutillib[];
|
||||||
extern const luaL_Reg cameralib[];
|
extern const luaL_Reg cameralib[];
|
||||||
extern const luaL_Reg consolelib[];
|
extern const luaL_Reg consolelib[];
|
||||||
extern const luaL_Reg corelib[];
|
extern const luaL_Reg corelib[];
|
||||||
|
extern const luaL_Reg cryptolib[];
|
||||||
extern const luaL_Reg entitylib[];
|
extern const luaL_Reg entitylib[];
|
||||||
extern const luaL_Reg filelib[];
|
extern const luaL_Reg filelib[];
|
||||||
extern const luaL_Reg generationlib[];
|
extern const luaL_Reg generationlib[];
|
||||||
|
|
@ -93,3 +94,4 @@ namespace lua {
|
||||||
}
|
}
|
||||||
|
|
||||||
void initialize_libs_extends(lua::State* L);
|
void initialize_libs_extends(lua::State* L);
|
||||||
|
void initialize_cryptolib(lua::State* L);
|
||||||
|
|
|
||||||
|
|
@ -147,8 +147,19 @@ static int l_set_visible(lua::State* L) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static glm::vec4 to_color(lua::State* L, int idx) {
|
||||||
|
if (lua::objlen(L, idx) > 3) {
|
||||||
|
return lua::tovec4(L, idx);
|
||||||
|
}
|
||||||
|
return glm::vec4(lua::tovec3(L, idx), 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
static int l_get_color(lua::State* L) {
|
static int l_get_color(lua::State* L) {
|
||||||
if (auto skeleton = get_skeleton(L)) {
|
if (auto skeleton = get_skeleton(L)) {
|
||||||
|
if (lua::isnumber(L, 2)) {
|
||||||
|
auto index = index_range_check(*skeleton, lua::tointeger(L, 2));
|
||||||
|
return lua::pushvec(L, skeleton->boneTints[index]);
|
||||||
|
}
|
||||||
return lua::pushvec(L, skeleton->tint);
|
return lua::pushvec(L, skeleton->tint);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -156,7 +167,13 @@ static int l_get_color(lua::State* L) {
|
||||||
|
|
||||||
static int l_set_color(lua::State* L) {
|
static int l_set_color(lua::State* L) {
|
||||||
if (auto skeleton = get_skeleton(L)) {
|
if (auto skeleton = get_skeleton(L)) {
|
||||||
skeleton->tint = lua::tovec3(L, 2);
|
auto color = to_color(L, 2);
|
||||||
|
if (lua::isnumber(L, 3)) {
|
||||||
|
auto index = index_range_check(*skeleton, lua::tointeger(L, 3));
|
||||||
|
skeleton->boneTints[index] = color;
|
||||||
|
} else {
|
||||||
|
skeleton->tint = color;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,11 @@ static int l_wrap(lua::State* L) {
|
||||||
auto position = lua::tovec3(L, 1);
|
auto position = lua::tovec3(L, 1);
|
||||||
std::string texture = lua::require_string(L, 2);
|
std::string texture = lua::require_string(L, 2);
|
||||||
float emission = lua::isnumber(L, 3) ? lua::tonumber(L, 3) : 1.0f;
|
float emission = lua::isnumber(L, 3) ? lua::tonumber(L, 3) : 1.0f;
|
||||||
glm::vec3 tint = lua::istable(L, 4) ? lua::tovec3(L, 4) : glm::vec3(1.0f);
|
glm::vec4 tint =
|
||||||
|
lua::istable(L, 4)
|
||||||
|
? (lua::objlen(L, 4) > 3 ? lua::tovec4(L, 4)
|
||||||
|
: glm::vec4(lua::tovec3(L, 4), 1.0f))
|
||||||
|
: glm::vec4(1.0f);
|
||||||
|
|
||||||
return lua::pushinteger(
|
return lua::pushinteger(
|
||||||
L,
|
L,
|
||||||
|
|
@ -72,9 +76,12 @@ static int l_set_tints(lua::State* L) {
|
||||||
if (auto wrapper = renderer->blockWraps->get(lua::tointeger(L, 1))) {
|
if (auto wrapper = renderer->blockWraps->get(lua::tointeger(L, 1))) {
|
||||||
for (int i = 0; i < wrapper->textureFaces.size(); i++) {
|
for (int i = 0; i < wrapper->textureFaces.size(); i++) {
|
||||||
if (lua::isnil(L, 2 + i)) {
|
if (lua::isnil(L, 2 + i)) {
|
||||||
wrapper->tints[i] = glm::vec3(1.0f);
|
wrapper->tints[i] = glm::vec4(1.0f);
|
||||||
} else {
|
} else {
|
||||||
wrapper->tints[i] = lua::tovec3(L, 2 + i);
|
wrapper->tints[i] =
|
||||||
|
lua::objlen(L, 2 + i) > 3
|
||||||
|
? lua::tovec4(L, 2 + i)
|
||||||
|
: glm::vec4(lua::tovec3(L, 2 + i), 1.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
540
src/logic/scripting/lua/libs/libcrypto.cpp
Normal file
540
src/logic/scripting/lua/libs/libcrypto.cpp
Normal file
|
|
@ -0,0 +1,540 @@
|
||||||
|
#include <climits>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <new>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
#include "api_lua.hpp"
|
||||||
|
#include "crypto/Crypto.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
using HashContext = crypto::HashContext;
|
||||||
|
|
||||||
|
constexpr const char* HASH_CONTEXT_METATABLE =
|
||||||
|
"voxelcore.crypto.HashContext";
|
||||||
|
|
||||||
|
int pushBytes(lua::State* L, const crypto::Bytes& value) {
|
||||||
|
return lua::pushlstring(L, value.data(), value.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
int pushFailure(lua::State* L, bool nil, const char* error) {
|
||||||
|
nil ? lua::pushnil(L) : lua::pushboolean(L, false);
|
||||||
|
lua::pushstring(L, error);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pushKeyPair(lua::State* L, const crypto::KeyPair& keyPair) {
|
||||||
|
pushBytes(L, keyPair.privateKey);
|
||||||
|
pushBytes(L, keyPair.publicKey);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Operation>
|
||||||
|
int verify(lua::State* L, Operation operation) {
|
||||||
|
try {
|
||||||
|
if (operation()) return lua::pushboolean(L, true);
|
||||||
|
return pushFailure(L, false, "invalid_signature");
|
||||||
|
} catch (const crypto::Error& error) {
|
||||||
|
return pushFailure(L, false, error.codeName());
|
||||||
|
} catch (const std::exception& error) {
|
||||||
|
return pushFailure(L, false, "backend_error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Operation>
|
||||||
|
int decrypt(lua::State* L, Operation operation) {
|
||||||
|
try {
|
||||||
|
return pushBytes(L, operation());
|
||||||
|
} catch (const crypto::Error& error) {
|
||||||
|
return pushFailure(L, true, error.codeName());
|
||||||
|
} catch (const std::exception& error) {
|
||||||
|
return pushFailure(L, true, "backend_error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t requireSize(lua::State* L, int index, const char* name) {
|
||||||
|
if (lua::type(L, index) != LUA_TNUMBER) {
|
||||||
|
throw std::runtime_error(std::string(name) + " must be a number");
|
||||||
|
}
|
||||||
|
const lua::Number number = lua::tonumber(L, index);
|
||||||
|
const lua::Integer value = lua::tointeger(L, index);
|
||||||
|
if (number != static_cast<lua::Number>(value)) {
|
||||||
|
throw std::runtime_error(std::string(name) + " must be an integer");
|
||||||
|
}
|
||||||
|
if (value < 0) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
std::string(name) + " must be non-negative"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return static_cast<std::size_t>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
int requireInt(lua::State* L, int index, const char* name) {
|
||||||
|
if (lua::type(L, index) != LUA_TNUMBER) {
|
||||||
|
throw std::runtime_error(std::string(name) + " must be a number");
|
||||||
|
}
|
||||||
|
const lua::Number number = lua::tonumber(L, index);
|
||||||
|
const lua::Integer value = lua::tointeger(L, index);
|
||||||
|
if (number != static_cast<lua::Number>(value)) {
|
||||||
|
throw std::runtime_error(std::string(name) + " must be an integer");
|
||||||
|
}
|
||||||
|
if (value < INT_MIN || value > INT_MAX) {
|
||||||
|
throw std::runtime_error(std::string(name) + " is out of range");
|
||||||
|
}
|
||||||
|
return static_cast<int>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
int hash(lua::State* L, std::string_view name) {
|
||||||
|
lua::check_argc(L, 1);
|
||||||
|
return pushBytes(L, crypto::digest(name, lua::require_lstring(L, 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_sha256(lua::State* L) {
|
||||||
|
return hash(L, "SHA256");
|
||||||
|
}
|
||||||
|
int l_sha384(lua::State* L) {
|
||||||
|
return hash(L, "SHA384");
|
||||||
|
}
|
||||||
|
int l_sha512(lua::State* L) {
|
||||||
|
return hash(L, "SHA512");
|
||||||
|
}
|
||||||
|
int l_md5(lua::State* L) {
|
||||||
|
return hash(L, "MD5");
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_hash(lua::State* L) {
|
||||||
|
lua::check_argc(L, 2);
|
||||||
|
return pushBytes(
|
||||||
|
L,
|
||||||
|
crypto::digest(
|
||||||
|
lua::require_lstring(L, 1), lua::require_lstring(L, 2)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
crypto::HashContext* requireHashContext(lua::State* L) {
|
||||||
|
return static_cast<crypto::HashContext*>(
|
||||||
|
luaL_checkudata(L, 1, HASH_CONTEXT_METATABLE)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_hash_context_gc(lua::State* L) {
|
||||||
|
requireHashContext(L)->~HashContext();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_hash_context_update(lua::State* L) {
|
||||||
|
lua::check_argc(L, 2);
|
||||||
|
requireHashContext(L)->update(lua::require_lstring(L, 2));
|
||||||
|
lua::pushvalue(L, 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_hash_context_final(lua::State* L) {
|
||||||
|
lua::check_argc(L, 1);
|
||||||
|
return pushBytes(L, requireHashContext(L)->final());
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_hash_context_reset(lua::State* L) {
|
||||||
|
lua::check_argc(L, 1);
|
||||||
|
requireHashContext(L)->reset();
|
||||||
|
lua::pushvalue(L, 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ensureHashContextMetatable(lua::State* L) {
|
||||||
|
if (luaL_newmetatable(L, HASH_CONTEXT_METATABLE)) {
|
||||||
|
lua::pushcfunction(L, lua::wrap<l_hash_context_gc>);
|
||||||
|
lua::setfield(L, "__gc");
|
||||||
|
lua::pushcfunction(L, lua::wrap<l_hash_context_update>);
|
||||||
|
lua::setfield(L, "update");
|
||||||
|
lua::pushcfunction(L, lua::wrap<l_hash_context_final>);
|
||||||
|
lua::setfield(L, "final");
|
||||||
|
lua::pushcfunction(L, lua::wrap<l_hash_context_reset>);
|
||||||
|
lua::setfield(L, "reset");
|
||||||
|
lua::pushvalue(L, -1);
|
||||||
|
lua::setfield(L, "__index");
|
||||||
|
}
|
||||||
|
lua::pop(L);
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_hash_new(lua::State* L) {
|
||||||
|
lua::check_argc(L, 1);
|
||||||
|
const auto name = lua::require_lstring(L, 1);
|
||||||
|
ensureHashContextMetatable(L);
|
||||||
|
void* memory = lua_newuserdata(L, sizeof(crypto::HashContext));
|
||||||
|
new (memory) crypto::HashContext(name);
|
||||||
|
luaL_getmetatable(L, HASH_CONTEXT_METATABLE);
|
||||||
|
lua_setmetatable(L, -2);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_hmac(lua::State* L) {
|
||||||
|
lua::check_argc(L, 3);
|
||||||
|
return pushBytes(
|
||||||
|
L,
|
||||||
|
crypto::hmac(
|
||||||
|
lua::require_lstring(L, 1),
|
||||||
|
lua::require_lstring(L, 2),
|
||||||
|
lua::require_lstring(L, 3)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_ed25519_verify(lua::State* L) {
|
||||||
|
lua::check_argc(L, 3);
|
||||||
|
const auto key = lua::require_lstring(L, 1);
|
||||||
|
const auto message = lua::require_lstring(L, 2);
|
||||||
|
const auto signature = lua::require_lstring(L, 3);
|
||||||
|
return verify(L, [=] {
|
||||||
|
return crypto::ed25519Verify(key, message, signature);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_ed25519_keypair(lua::State* L) {
|
||||||
|
lua::check_argc(L, 0);
|
||||||
|
return pushKeyPair(L, crypto::ed25519KeyPair());
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_ed25519_public(lua::State* L) {
|
||||||
|
lua::check_argc(L, 1);
|
||||||
|
return pushBytes(L, crypto::ed25519Public(lua::require_lstring(L, 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_ed25519_sign(lua::State* L) {
|
||||||
|
lua::check_argc(L, 2);
|
||||||
|
return pushBytes(
|
||||||
|
L,
|
||||||
|
crypto::ed25519Sign(
|
||||||
|
lua::require_lstring(L, 1), lua::require_lstring(L, 2)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_ecdsa_verify(lua::State* L) {
|
||||||
|
lua::check_argc(L, 5);
|
||||||
|
const auto curve = lua::require_lstring(L, 1);
|
||||||
|
const auto key = lua::require_lstring(L, 2);
|
||||||
|
const auto message = lua::require_lstring(L, 3);
|
||||||
|
const auto signature = lua::require_lstring(L, 4);
|
||||||
|
const auto hashName = lua::require_lstring(L, 5);
|
||||||
|
return verify(L, [=] {
|
||||||
|
return crypto::ecdsaVerify(
|
||||||
|
curve, key, message, signature, hashName
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_ecdsa_keypair(lua::State* L) {
|
||||||
|
lua::check_argc(L, 1);
|
||||||
|
return pushKeyPair(L, crypto::ecdsaKeyPair(lua::require_lstring(L, 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_ecdsa_public(lua::State* L) {
|
||||||
|
lua::check_argc(L, 2);
|
||||||
|
return pushBytes(
|
||||||
|
L,
|
||||||
|
crypto::ecdsaPublic(
|
||||||
|
lua::require_lstring(L, 1), lua::require_lstring(L, 2)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_ecdsa_sign(lua::State* L) {
|
||||||
|
lua::check_argc(L, 4);
|
||||||
|
return pushBytes(
|
||||||
|
L,
|
||||||
|
crypto::ecdsaSign(
|
||||||
|
lua::require_lstring(L, 1),
|
||||||
|
lua::require_lstring(L, 2),
|
||||||
|
lua::require_lstring(L, 3),
|
||||||
|
lua::require_lstring(L, 4)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_rsa_pkcs1_verify(lua::State* L) {
|
||||||
|
lua::check_argc(L, 5);
|
||||||
|
const auto hashName = lua::require_lstring(L, 1);
|
||||||
|
const auto modulus = lua::require_lstring(L, 2);
|
||||||
|
const auto exponent = lua::require_lstring(L, 3);
|
||||||
|
const auto message = lua::require_lstring(L, 4);
|
||||||
|
const auto signature = lua::require_lstring(L, 5);
|
||||||
|
return verify(L, [=] {
|
||||||
|
return crypto::rsaPkcs1Verify(
|
||||||
|
hashName, modulus, exponent, message, signature
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_rsa_pss_verify(lua::State* L) {
|
||||||
|
lua::check_argc(L, 6);
|
||||||
|
const auto hashName = lua::require_lstring(L, 1);
|
||||||
|
const auto modulus = lua::require_lstring(L, 2);
|
||||||
|
const auto exponent = lua::require_lstring(L, 3);
|
||||||
|
const auto message = lua::require_lstring(L, 4);
|
||||||
|
const auto signature = lua::require_lstring(L, 5);
|
||||||
|
const int saltLength = requireInt(L, 6, "salt_length");
|
||||||
|
return verify(L, [=] {
|
||||||
|
return crypto::rsaPssVerify(
|
||||||
|
hashName, modulus, exponent, message, signature, saltLength
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_x25519_public(lua::State* L) {
|
||||||
|
lua::check_argc(L, 1);
|
||||||
|
return pushBytes(L, crypto::x25519Public(lua::require_lstring(L, 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_x25519_keypair(lua::State* L) {
|
||||||
|
lua::check_argc(L, 0);
|
||||||
|
return pushKeyPair(L, crypto::x25519KeyPair());
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_x25519(lua::State* L) {
|
||||||
|
lua::check_argc(L, 2);
|
||||||
|
return pushBytes(
|
||||||
|
L,
|
||||||
|
crypto::x25519(
|
||||||
|
lua::require_lstring(L, 1), lua::require_lstring(L, 2)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_p256_public(lua::State* L) {
|
||||||
|
lua::check_argc(L, 1);
|
||||||
|
return pushBytes(L, crypto::p256Public(lua::require_lstring(L, 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_p256_keypair(lua::State* L) {
|
||||||
|
lua::check_argc(L, 0);
|
||||||
|
return pushKeyPair(L, crypto::ecdsaKeyPair("P-256"));
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_p256_shared(lua::State* L) {
|
||||||
|
lua::check_argc(L, 2);
|
||||||
|
return pushBytes(
|
||||||
|
L,
|
||||||
|
crypto::p256Shared(
|
||||||
|
lua::require_lstring(L, 1), lua::require_lstring(L, 2)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_aes_gcm_encrypt(lua::State* L) {
|
||||||
|
lua::check_argc(L, 4);
|
||||||
|
return pushBytes(
|
||||||
|
L,
|
||||||
|
crypto::aesGcmEncrypt(
|
||||||
|
lua::require_lstring(L, 1),
|
||||||
|
lua::require_lstring(L, 2),
|
||||||
|
lua::require_lstring(L, 3),
|
||||||
|
lua::require_lstring(L, 4)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_aes_gcm_decrypt(lua::State* L) {
|
||||||
|
lua::check_argc(L, 4);
|
||||||
|
const auto key = lua::require_lstring(L, 1);
|
||||||
|
const auto nonce = lua::require_lstring(L, 2);
|
||||||
|
const auto aad = lua::require_lstring(L, 3);
|
||||||
|
const auto ciphertext = lua::require_lstring(L, 4);
|
||||||
|
return decrypt(L, [=] {
|
||||||
|
return crypto::aesGcmDecrypt(key, nonce, aad, ciphertext);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_chacha20_poly1305_encrypt(lua::State* L) {
|
||||||
|
lua::check_argc(L, 4);
|
||||||
|
return pushBytes(
|
||||||
|
L,
|
||||||
|
crypto::chacha20Poly1305Encrypt(
|
||||||
|
lua::require_lstring(L, 1),
|
||||||
|
lua::require_lstring(L, 2),
|
||||||
|
lua::require_lstring(L, 3),
|
||||||
|
lua::require_lstring(L, 4)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_chacha20_poly1305_decrypt(lua::State* L) {
|
||||||
|
lua::check_argc(L, 4);
|
||||||
|
const auto key = lua::require_lstring(L, 1);
|
||||||
|
const auto nonce = lua::require_lstring(L, 2);
|
||||||
|
const auto aad = lua::require_lstring(L, 3);
|
||||||
|
const auto ciphertext = lua::require_lstring(L, 4);
|
||||||
|
return decrypt(L, [=] {
|
||||||
|
return crypto::chacha20Poly1305Decrypt(key, nonce, aad, ciphertext);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_random_bytes(lua::State* L) {
|
||||||
|
lua::check_argc(L, 1);
|
||||||
|
return pushBytes(L, crypto::randomBytes(requireSize(L, 1, "length")));
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_constant_time_equal(lua::State* L) {
|
||||||
|
lua::check_argc(L, 2);
|
||||||
|
return lua::pushboolean(
|
||||||
|
L,
|
||||||
|
crypto::constantTimeEqual(
|
||||||
|
lua::require_lstring(L, 1), lua::require_lstring(L, 2)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_hkdf_extract(lua::State* L) {
|
||||||
|
lua::check_argc(L, 3);
|
||||||
|
return pushBytes(
|
||||||
|
L,
|
||||||
|
crypto::hkdfExtract(
|
||||||
|
lua::require_lstring(L, 1),
|
||||||
|
lua::require_lstring(L, 2),
|
||||||
|
lua::require_lstring(L, 3)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_hkdf_expand(lua::State* L) {
|
||||||
|
lua::check_argc(L, 4);
|
||||||
|
return pushBytes(
|
||||||
|
L,
|
||||||
|
crypto::hkdfExpand(
|
||||||
|
lua::require_lstring(L, 1),
|
||||||
|
lua::require_lstring(L, 2),
|
||||||
|
lua::require_lstring(L, 3),
|
||||||
|
requireSize(L, 4, "length")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_pbkdf2(lua::State* L) {
|
||||||
|
lua::check_argc(L, 5);
|
||||||
|
const auto iterations = requireSize(L, 4, "iterations");
|
||||||
|
if (iterations == 0 || iterations > UINT32_MAX) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"iterations must be between 1 and 4294967295"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return pushBytes(
|
||||||
|
L,
|
||||||
|
crypto::pbkdf2(
|
||||||
|
lua::require_lstring(L, 1),
|
||||||
|
lua::require_lstring(L, 2),
|
||||||
|
lua::require_lstring(L, 3),
|
||||||
|
static_cast<std::uint32_t>(iterations),
|
||||||
|
requireSize(L, 5, "length")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_scrypt(lua::State* L) {
|
||||||
|
const auto argc = lua::check_argc(L, 6, 7);
|
||||||
|
const auto maxMemory =
|
||||||
|
argc == 7 ? requireSize(L, 7, "max_memory") : std::size_t {0};
|
||||||
|
return pushBytes(
|
||||||
|
L,
|
||||||
|
crypto::scrypt(
|
||||||
|
lua::require_lstring(L, 1),
|
||||||
|
lua::require_lstring(L, 2),
|
||||||
|
requireSize(L, 3, "n"),
|
||||||
|
requireSize(L, 4, "r"),
|
||||||
|
requireSize(L, 5, "p"),
|
||||||
|
requireSize(L, 6, "length"),
|
||||||
|
maxMemory
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setFeature(lua::State* L, const char* name, bool available) {
|
||||||
|
lua::pushboolean(L, available);
|
||||||
|
lua::setfield(L, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_features(lua::State* L) {
|
||||||
|
lua::check_argc(L, 0);
|
||||||
|
const auto features = crypto::features();
|
||||||
|
lua::createtable(L, 0, 26);
|
||||||
|
lua::pushstring(L, "OpenSSL");
|
||||||
|
lua::setfield(L, "backend");
|
||||||
|
lua::pushstring(L, crypto::backendVersion());
|
||||||
|
lua::setfield(L, "backend_version");
|
||||||
|
lua::pushinteger(L, crypto::API_VERSION);
|
||||||
|
lua::setfield(L, "api_version");
|
||||||
|
setFeature(L, "sha256", features.sha256);
|
||||||
|
setFeature(L, "sha384", features.sha384);
|
||||||
|
setFeature(L, "sha512", features.sha512);
|
||||||
|
setFeature(L, "md5", features.md5);
|
||||||
|
setFeature(L, "hmac", features.hmac);
|
||||||
|
setFeature(L, "streaming_hash", features.sha256);
|
||||||
|
setFeature(L, "ed25519", features.ed25519);
|
||||||
|
setFeature(L, "ed25519_verify", features.ed25519);
|
||||||
|
setFeature(L, "ecdsa", features.ecdsa);
|
||||||
|
setFeature(L, "ecdsa_verify", features.ecdsa);
|
||||||
|
setFeature(L, "rsa_pkcs1_verify", features.rsa);
|
||||||
|
setFeature(L, "rsa_pss_verify", features.rsa);
|
||||||
|
setFeature(L, "x25519", features.x25519);
|
||||||
|
setFeature(L, "p256", features.p256);
|
||||||
|
setFeature(L, "aes_128_gcm", features.aes128Gcm);
|
||||||
|
setFeature(L, "aes_256_gcm", features.aes256Gcm);
|
||||||
|
setFeature(L, "aes_gcm", features.aes128Gcm && features.aes256Gcm);
|
||||||
|
setFeature(L, "chacha20_poly1305", features.chacha20Poly1305);
|
||||||
|
setFeature(L, "random_bytes", features.random);
|
||||||
|
setFeature(L, "constant_time_equal", true);
|
||||||
|
setFeature(L, "hkdf", features.hkdf);
|
||||||
|
setFeature(L, "pbkdf2", features.pbkdf2);
|
||||||
|
setFeature(L, "scrypt", features.scrypt);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const luaL_Reg cryptolib[] = {
|
||||||
|
{"sha256", lua::wrap<l_sha256>},
|
||||||
|
{"sha384", lua::wrap<l_sha384>},
|
||||||
|
{"sha512", lua::wrap<l_sha512>},
|
||||||
|
{"md5", lua::wrap<l_md5>},
|
||||||
|
{"hash", lua::wrap<l_hash>},
|
||||||
|
{"hash_new", lua::wrap<l_hash_new>},
|
||||||
|
{"hmac", lua::wrap<l_hmac>},
|
||||||
|
{"ed25519_keypair", lua::wrap<l_ed25519_keypair>},
|
||||||
|
{"ed25519_public", lua::wrap<l_ed25519_public>},
|
||||||
|
{"ed25519_sign", lua::wrap<l_ed25519_sign>},
|
||||||
|
{"ed25519_verify", lua::wrap<l_ed25519_verify>},
|
||||||
|
{"ecdsa_keypair", lua::wrap<l_ecdsa_keypair>},
|
||||||
|
{"ecdsa_public", lua::wrap<l_ecdsa_public>},
|
||||||
|
{"ecdsa_sign", lua::wrap<l_ecdsa_sign>},
|
||||||
|
{"ecdsa_verify", lua::wrap<l_ecdsa_verify>},
|
||||||
|
{"rsa_pkcs1_verify", lua::wrap<l_rsa_pkcs1_verify>},
|
||||||
|
{"rsa_pss_verify", lua::wrap<l_rsa_pss_verify>},
|
||||||
|
{"x25519_keypair", lua::wrap<l_x25519_keypair>},
|
||||||
|
{"x25519_public", lua::wrap<l_x25519_public>},
|
||||||
|
{"x25519", lua::wrap<l_x25519>},
|
||||||
|
{"x25519_shared", lua::wrap<l_x25519>},
|
||||||
|
{"p256_keypair", lua::wrap<l_p256_keypair>},
|
||||||
|
{"p256_public", lua::wrap<l_p256_public>},
|
||||||
|
{"p256_shared", lua::wrap<l_p256_shared>},
|
||||||
|
{"aes_gcm_encrypt", lua::wrap<l_aes_gcm_encrypt>},
|
||||||
|
{"aes_gcm_decrypt", lua::wrap<l_aes_gcm_decrypt>},
|
||||||
|
{"chacha20_poly1305_encrypt", lua::wrap<l_chacha20_poly1305_encrypt>},
|
||||||
|
{"chacha20_poly1305_decrypt", lua::wrap<l_chacha20_poly1305_decrypt>},
|
||||||
|
{"random_bytes", lua::wrap<l_random_bytes>},
|
||||||
|
{"constant_time_equal", lua::wrap<l_constant_time_equal>},
|
||||||
|
{"hkdf_extract", lua::wrap<l_hkdf_extract>},
|
||||||
|
{"hkdf_expand", lua::wrap<l_hkdf_expand>},
|
||||||
|
{"pbkdf2", lua::wrap<l_pbkdf2>},
|
||||||
|
{"scrypt", lua::wrap<l_scrypt>},
|
||||||
|
{"features", lua::wrap<l_features>},
|
||||||
|
{NULL, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
|
void initialize_cryptolib(lua::State* L) {
|
||||||
|
ensureHashContextMetatable(L);
|
||||||
|
if (lua::getglobal(L, "crypto")) {
|
||||||
|
lua::pushinteger(L, crypto::API_VERSION);
|
||||||
|
lua::setfield(L, "API_VERSION");
|
||||||
|
lua::pop(L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -25,6 +25,7 @@
|
||||||
#include "items/Inventories.hpp"
|
#include "items/Inventories.hpp"
|
||||||
#include "util/stringutil.hpp"
|
#include "util/stringutil.hpp"
|
||||||
#include "world/Level.hpp"
|
#include "world/Level.hpp"
|
||||||
|
#include "window/Window.hpp"
|
||||||
#include "../usertypes/lua_type_canvas.hpp"
|
#include "../usertypes/lua_type_canvas.hpp"
|
||||||
|
|
||||||
using namespace gui;
|
using namespace gui;
|
||||||
|
|
@ -1218,7 +1219,26 @@ static int l_get_active_frame(lua::State* L) {
|
||||||
return lua::pushstring(L, frame->getId());
|
return lua::pushstring(L, frame->getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int l_screenshot(lua::State* L) {
|
||||||
|
if (engine->isHeadless()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
std::unique_ptr<ImageData> image;
|
||||||
|
if (lua::isstring(L, 1)) {
|
||||||
|
auto& gui = engine->getGUI();
|
||||||
|
auto frame = gui.getFrame(lua::require_string(L, 1));
|
||||||
|
if (frame == nullptr) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
image = frame->takeScreenshot();
|
||||||
|
} else {
|
||||||
|
image = engine->getWindow().takeScreenshot();
|
||||||
|
}
|
||||||
|
return lua::newuserdata<lua::LuaCanvas>(L, nullptr, std::move(image));
|
||||||
|
}
|
||||||
|
|
||||||
const luaL_Reg guilib[] = {
|
const luaL_Reg guilib[] = {
|
||||||
|
{"screenshot", lua::wrap<l_screenshot>},
|
||||||
{"get_viewport", lua::wrap<l_gui_getviewport>},
|
{"get_viewport", lua::wrap<l_gui_getviewport>},
|
||||||
{"getattr", lua::wrap<l_gui_getattr>},
|
{"getattr", lua::wrap<l_gui_getattr>},
|
||||||
{"setattr", lua::wrap<l_gui_setattr>},
|
{"setattr", lua::wrap<l_gui_setattr>},
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
#include "engine/Engine.hpp"
|
#include "engine/Engine.hpp"
|
||||||
#include "network/Network.hpp"
|
#include "network/Network.hpp"
|
||||||
#include "devtools/Project.hpp"
|
#include "devtools/Project.hpp"
|
||||||
|
#include "util/stringutil.hpp"
|
||||||
|
|
||||||
#include <variant>
|
#include <variant>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
@ -27,7 +28,7 @@ struct ResponseEventDto {
|
||||||
int status;
|
int status;
|
||||||
bool binary;
|
bool binary;
|
||||||
int requestId;
|
int requestId;
|
||||||
std::vector<char> bytes;
|
network::HttpResponse response;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum NetworkDatagramSide {
|
enum NetworkDatagramSide {
|
||||||
|
|
@ -85,74 +86,55 @@ static std::vector<std::string> read_headers(lua::State* L, int index) {
|
||||||
|
|
||||||
static int request_id = 1;
|
static int request_id = 1;
|
||||||
|
|
||||||
static int perform_get(lua::State* L, network::Network& network, bool binary) {
|
static int l_request(lua::State* L, network::Network& network) {
|
||||||
std::string url(lua::require_lstring(L, 1));
|
network::HttpRequest request {};
|
||||||
auto headers = read_headers(L, 2);
|
request.url = lua::require_lstring(L, 1);
|
||||||
|
|
||||||
int currentRequestId = request_id++;
|
if (!lua::istable(L, 2)) {
|
||||||
|
throw std::runtime_error("table expected as argument #2");
|
||||||
network.get(
|
}
|
||||||
url,
|
if (lua::getfield(L, "method", 2)) {
|
||||||
[currentRequestId, binary](std::vector<char> bytes) {
|
request.method = lua::require_string(L, -1);
|
||||||
push_event(NetworkEvent(
|
lua::pop(L);
|
||||||
RESPONSE,
|
}
|
||||||
ResponseEventDto {
|
if (lua::getfield(L, "headers", 2)) {
|
||||||
200, binary, currentRequestId, std::move(bytes)}
|
request.headers = read_headers(L, -1);
|
||||||
));
|
lua::pop(L);
|
||||||
},
|
}
|
||||||
[currentRequestId, binary](int code, std::vector<char> bytes) {
|
if (lua::getfield(L, "body", 2)) {
|
||||||
push_event(NetworkEvent(
|
if (lua::type(L, -1) == LUA_TCDATA) {
|
||||||
RESPONSE,
|
request.body = lua::bytearray_as_string(L, -1);
|
||||||
ResponseEventDto {
|
} else {
|
||||||
code, binary, currentRequestId, std::move(bytes)}
|
request.body = lua::require_lstring(L, -1);
|
||||||
));
|
}
|
||||||
},
|
lua::pop(L);
|
||||||
std::move(headers)
|
}
|
||||||
);
|
if (lua::getfield(L, "follow_location", 2)) {
|
||||||
return lua::pushinteger(L, currentRequestId);
|
request.followLocation = lua::toboolean(L, -1);
|
||||||
}
|
lua::pop(L);
|
||||||
|
}
|
||||||
static int l_get(lua::State* L, network::Network& network) {
|
if (lua::getfield(L, "timeout_ms", 2)) {
|
||||||
return perform_get(L, network, false);
|
request.timeoutMs = lua::tointeger(L, -1);
|
||||||
}
|
lua::pop(L);
|
||||||
|
}
|
||||||
static int l_get_binary(lua::State* L, network::Network& network) {
|
if (lua::getfield(L, "verify_ssl", 2)) {
|
||||||
return perform_get(L, network, true);
|
request.verifySSL = lua::toboolean(L, -1);
|
||||||
}
|
lua::pop(L);
|
||||||
|
|
||||||
static int l_post(lua::State* L, network::Network& network) {
|
|
||||||
std::string url(lua::require_lstring(L, 1));
|
|
||||||
auto data = lua::tovalue(L, 2);
|
|
||||||
|
|
||||||
std::string string;
|
|
||||||
if (data.isString()) {
|
|
||||||
string = data.asString();
|
|
||||||
} else {
|
|
||||||
string = json::stringify(data, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto headers = read_headers(L, 3);
|
|
||||||
int currentRequestId = request_id++;
|
int currentRequestId = request_id++;
|
||||||
|
request.onResponse = [currentRequestId](network::HttpResponse response) {
|
||||||
|
push_event(NetworkEvent(
|
||||||
|
RESPONSE,
|
||||||
|
ResponseEventDto {
|
||||||
|
response.status,
|
||||||
|
false,
|
||||||
|
currentRequestId,
|
||||||
|
std::move(response)}
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
network.post(
|
network.request(std::move(request));
|
||||||
url,
|
|
||||||
string,
|
|
||||||
[currentRequestId](std::vector<char> bytes) {
|
|
||||||
push_event(NetworkEvent(
|
|
||||||
RESPONSE,
|
|
||||||
ResponseEventDto {
|
|
||||||
200, false, currentRequestId, std::move(bytes)}
|
|
||||||
));
|
|
||||||
},
|
|
||||||
[currentRequestId](int code, std::vector<char> bytes) {
|
|
||||||
push_event(NetworkEvent(
|
|
||||||
RESPONSE,
|
|
||||||
ResponseEventDto {
|
|
||||||
code, false, currentRequestId, std::move(bytes)}
|
|
||||||
));
|
|
||||||
},
|
|
||||||
std::move(headers)
|
|
||||||
);
|
|
||||||
return lua::pushinteger(L, currentRequestId);
|
return lua::pushinteger(L, currentRequestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -519,11 +501,31 @@ static int l_pull_events(lua::State* L) {
|
||||||
lua::pushinteger(L, dto.requestId);
|
lua::pushinteger(L, dto.requestId);
|
||||||
lua::rawseti(L, 3);
|
lua::rawseti(L, 3);
|
||||||
|
|
||||||
|
lua::createtable(L, 0, 2);
|
||||||
|
lua::pushinteger(L, dto.response.status);
|
||||||
|
lua::setfield(L, "status");
|
||||||
|
|
||||||
if (dto.binary) {
|
if (dto.binary) {
|
||||||
lua::create_bytearray(L, dto.bytes.data(), dto.bytes.size());
|
lua::create_bytearray(
|
||||||
|
L, dto.response.body.data(), dto.response.body.size()
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
lua::pushlstring(L, std::string_view(dto.bytes.data(), dto.bytes.size()));
|
lua::pushlstring(
|
||||||
|
L,
|
||||||
|
std::string_view(
|
||||||
|
dto.response.body.data(), dto.response.body.size()
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
lua::setfield(L, "body");
|
||||||
|
|
||||||
|
lua::createtable(L, dto.response.headers.size(), 0);
|
||||||
|
for (int i = 0; i < dto.response.headers.size(); i++) {
|
||||||
|
lua::pushlstring(L, dto.response.headers[i]);
|
||||||
|
lua::rawseti(L, i + 1);
|
||||||
|
}
|
||||||
|
lua::setfield(L, "headers");
|
||||||
|
|
||||||
lua::rawseti(L, 4);
|
lua::rawseti(L, 4);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -562,9 +564,7 @@ int wrap(lua_State* L) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const luaL_Reg networklib[] = {
|
const luaL_Reg networklib[] = {
|
||||||
{"__get", wrap<l_get>},
|
{"__request", wrap<l_request>},
|
||||||
{"__get_binary", wrap<l_get_binary>},
|
|
||||||
{"__post", wrap<l_post>},
|
|
||||||
{"get_total_upload", wrap<l_get_total_upload>},
|
{"get_total_upload", wrap<l_get_total_upload>},
|
||||||
{"get_total_download", wrap<l_get_total_download>},
|
{"get_total_download", wrap<l_get_total_download>},
|
||||||
{"find_free_port", wrap<l_find_free_port>},
|
{"find_free_port", wrap<l_find_free_port>},
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,8 @@ static void create_libs(State* L, StateType stateType) {
|
||||||
openlib(L, "bjson", bjsonlib);
|
openlib(L, "bjson", bjsonlib);
|
||||||
openlib(L, "block", blocklib);
|
openlib(L, "block", blocklib);
|
||||||
openlib(L, "byteutil", byteutillib);
|
openlib(L, "byteutil", byteutillib);
|
||||||
|
openlib(L, "crypto", cryptolib);
|
||||||
|
initialize_cryptolib(L);
|
||||||
openlib(L, "file", filelib);
|
openlib(L, "file", filelib);
|
||||||
openlib(L, "generation", generationlib);
|
openlib(L, "generation", generationlib);
|
||||||
openlib(L, "item", itemlib);
|
openlib(L, "item", itemlib);
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
#define NOMINMAX
|
#define NOMINMAX
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
#include <queue>
|
#include <algorithm>
|
||||||
|
|
||||||
using namespace network;
|
using namespace network;
|
||||||
|
|
||||||
|
|
@ -23,97 +23,96 @@ static size_t write_callback(
|
||||||
return size * nmemb;
|
return size * nmemb;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class RequestType {
|
static size_t header_callback(
|
||||||
GET, POST
|
char* buffer, size_t size, size_t nitems, void* userdata
|
||||||
};
|
) {
|
||||||
|
auto* headers = static_cast<std::vector<std::string>*>(userdata);
|
||||||
|
size_t len = size * nitems;
|
||||||
|
std::string header(buffer, len);
|
||||||
|
|
||||||
struct Request {
|
while (!header.empty() &&
|
||||||
RequestType type;
|
(header.back() == '\r' || header.back() == '\n')) {
|
||||||
std::string url;
|
header.pop_back();
|
||||||
OnResponse onResponse;
|
}
|
||||||
OnReject onReject;
|
|
||||||
long maxSize;
|
headers->push_back(std::move(header));
|
||||||
bool followLocation = false;
|
return len;
|
||||||
std::string data;
|
}
|
||||||
|
|
||||||
|
struct ProcessingRequest {
|
||||||
|
CURLM* multiHandle;
|
||||||
|
CURL* curl;
|
||||||
|
HttpRequest request;
|
||||||
|
std::vector<char> buffer;
|
||||||
std::vector<std::string> headers;
|
std::vector<std::string> headers;
|
||||||
|
|
||||||
|
ProcessingRequest(CURLM* multiHandle) : multiHandle(multiHandle) {
|
||||||
|
curl = curl_easy_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessingRequest(const ProcessingRequest&) = delete;
|
||||||
|
ProcessingRequest& operator=(const ProcessingRequest&) = delete;
|
||||||
|
|
||||||
|
ProcessingRequest(ProcessingRequest&& other) noexcept
|
||||||
|
: multiHandle(other.multiHandle),
|
||||||
|
curl(other.curl),
|
||||||
|
request(std::move(other.request)),
|
||||||
|
buffer(std::move(other.buffer)),
|
||||||
|
headers(std::move(other.headers)) {
|
||||||
|
other.curl = nullptr;
|
||||||
|
other.multiHandle = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessingRequest& operator=(ProcessingRequest&& other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
if (curl) {
|
||||||
|
curl_multi_remove_handle(multiHandle, curl);
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
}
|
||||||
|
multiHandle = other.multiHandle;
|
||||||
|
curl = other.curl;
|
||||||
|
request = std::move(other.request);
|
||||||
|
buffer = std::move(other.buffer);
|
||||||
|
headers = std::move(other.headers);
|
||||||
|
other.curl = nullptr;
|
||||||
|
other.multiHandle = nullptr;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
~ProcessingRequest() {
|
||||||
|
if (curl) {
|
||||||
|
curl_multi_remove_handle(multiHandle, curl);
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class CurlRequests : public Requests {
|
class CurlRequests : public Requests {
|
||||||
CURLM* multiHandle;
|
CURLM* multiHandle;
|
||||||
CURL* curl;
|
std::vector<std::unique_ptr<ProcessingRequest>> requests;
|
||||||
|
|
||||||
size_t totalUpload = 0;
|
size_t totalUpload = 0;
|
||||||
size_t totalDownload = 0;
|
size_t totalDownload = 0;
|
||||||
|
|
||||||
OnResponse onResponse;
|
|
||||||
OnReject onReject;
|
|
||||||
std::vector<char> buffer;
|
|
||||||
std::string url;
|
|
||||||
|
|
||||||
std::queue<Request> requests;
|
|
||||||
public:
|
public:
|
||||||
CurlRequests(CURLM* multiHandle, CURL* curl)
|
CurlRequests(CURLM* multiHandle) : multiHandle(multiHandle) {
|
||||||
: multiHandle(multiHandle), curl(curl) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual ~CurlRequests() {
|
virtual ~CurlRequests() {
|
||||||
curl_multi_remove_handle(multiHandle, curl);
|
requests.clear();
|
||||||
curl_easy_cleanup(curl);
|
|
||||||
curl_multi_cleanup(multiHandle);
|
curl_multi_cleanup(multiHandle);
|
||||||
}
|
}
|
||||||
void get(
|
|
||||||
const std::string& url,
|
void request(HttpRequest request) override {
|
||||||
OnResponse onResponse,
|
|
||||||
OnReject onReject,
|
|
||||||
std::vector<std::string> headers,
|
|
||||||
long maxSize
|
|
||||||
) override {
|
|
||||||
Request request {
|
|
||||||
RequestType::GET,
|
|
||||||
url,
|
|
||||||
onResponse,
|
|
||||||
onReject,
|
|
||||||
maxSize,
|
|
||||||
true,
|
|
||||||
"",
|
|
||||||
std::move(headers)};
|
|
||||||
processRequest(std::move(request));
|
processRequest(std::move(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
void post(
|
void processRequest(HttpRequest request) {
|
||||||
const std::string& url,
|
auto entry = std::make_unique<ProcessingRequest>(multiHandle);
|
||||||
const std::string& data,
|
auto curl = entry->curl;
|
||||||
OnResponse onResponse,
|
|
||||||
OnReject onReject=nullptr,
|
|
||||||
std::vector<std::string> headers = {},
|
|
||||||
long maxSize=0
|
|
||||||
) override {
|
|
||||||
Request request {
|
|
||||||
RequestType::POST,
|
|
||||||
url,
|
|
||||||
onResponse,
|
|
||||||
onReject,
|
|
||||||
maxSize,
|
|
||||||
false,
|
|
||||||
"",
|
|
||||||
std::move(headers)};
|
|
||||||
request.data = data;
|
|
||||||
processRequest(std::move(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
void processRequest(Request request) {
|
curl_easy_setopt(curl, CURLOPT_URL, request.url.c_str());
|
||||||
if (!url.empty()) {
|
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, request.method.c_str());
|
||||||
requests.push(request);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onResponse = request.onResponse;
|
|
||||||
onReject = request.onReject;
|
|
||||||
url = request.url;
|
|
||||||
|
|
||||||
buffer.clear();
|
|
||||||
|
|
||||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
|
||||||
curl_easy_setopt(curl, CURLOPT_POST, request.type == RequestType::POST);
|
|
||||||
|
|
||||||
curl_slist* hs = nullptr;
|
curl_slist* hs = nullptr;
|
||||||
|
|
||||||
|
|
@ -121,22 +120,25 @@ public:
|
||||||
hs = curl_slist_append(hs, header.c_str());
|
hs = curl_slist_append(hs, header.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (request.type) {
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, static_cast<long>(request.body.length()));
|
||||||
case RequestType::GET:
|
if (!request.body.empty()) {
|
||||||
break;
|
curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, request.body.data());
|
||||||
case RequestType::POST:
|
|
||||||
hs = curl_slist_append(hs, "Content-Type: application/json");
|
|
||||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, request.data.length());
|
|
||||||
curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, request.data.c_str());
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw std::runtime_error("not implemented");
|
|
||||||
}
|
}
|
||||||
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, static_cast<long>(request.verifySSL));
|
||||||
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, static_cast<long>(request.verifySSL));
|
||||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hs);
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hs);
|
||||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, request.followLocation);
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, static_cast<long>(request.followLocation));
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &entry->buffer);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &entry->headers);
|
||||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/7.81.0");
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/7.81.0");
|
||||||
|
if (request.timeoutMs > 0) {
|
||||||
|
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, request.timeoutMs);
|
||||||
|
}
|
||||||
|
#ifndef NDEBUG
|
||||||
|
// curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
|
||||||
|
#endif
|
||||||
if (request.maxSize == 0) {
|
if (request.maxSize == 0) {
|
||||||
curl_easy_setopt(
|
curl_easy_setopt(
|
||||||
curl, CURLOPT_MAXFILESIZE, std::numeric_limits<long>::max()
|
curl, CURLOPT_MAXFILESIZE, std::numeric_limits<long>::max()
|
||||||
|
|
@ -144,69 +146,105 @@ public:
|
||||||
} else {
|
} else {
|
||||||
curl_easy_setopt(curl, CURLOPT_MAXFILESIZE, request.maxSize);
|
curl_easy_setopt(curl, CURLOPT_MAXFILESIZE, request.maxSize);
|
||||||
}
|
}
|
||||||
curl_multi_add_handle(multiHandle, curl);
|
{
|
||||||
int running;
|
CURLMcode res = curl_multi_add_handle(multiHandle, curl);
|
||||||
CURLMcode res = curl_multi_perform(multiHandle, &running);
|
if (res != CURLM_OK) {
|
||||||
if (res != CURLM_OK) {
|
auto message = curl_multi_strerror(res);
|
||||||
auto message = curl_multi_strerror(res);
|
logger.error() << message << " (" << request.url << ")";
|
||||||
logger.error() << message << " (" << url << ")";
|
if (request.onResponse) {
|
||||||
if (onReject) {
|
request.onResponse({0, {}, {}});
|
||||||
onReject(HTTP_BAD_GATEWAY, {});
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
url = "";
|
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
int running;
|
||||||
|
CURLMcode res = curl_multi_perform(multiHandle, &running);
|
||||||
|
if (res != CURLM_OK) {
|
||||||
|
auto message = curl_multi_strerror(res);
|
||||||
|
logger.error() << message << " (" << request.url << ")";
|
||||||
|
if (request.onResponse) {
|
||||||
|
request.onResponse({0, {}, {}});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entry->request = std::move(request);
|
||||||
|
requests.push_back(std::move(entry));
|
||||||
}
|
}
|
||||||
|
|
||||||
void update() override {
|
void update() override {
|
||||||
int messagesLeft;
|
if (CURLMcode res = curl_multi_poll(
|
||||||
int running;
|
multiHandle,
|
||||||
CURLMsg* msg;
|
nullptr,
|
||||||
CURLMcode res = curl_multi_perform(multiHandle, &running);
|
0,
|
||||||
if (res != CURLM_OK) {
|
0,
|
||||||
|
nullptr
|
||||||
|
)) {
|
||||||
auto message = curl_multi_strerror(res);
|
auto message = curl_multi_strerror(res);
|
||||||
logger.error() << message << " (" << url << ")";
|
logger.error() << message;
|
||||||
if (onReject) {
|
|
||||||
onReject(HTTP_BAD_GATEWAY, {});
|
|
||||||
}
|
|
||||||
curl_multi_remove_handle(multiHandle, curl);
|
|
||||||
url = "";
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ((msg = curl_multi_info_read(multiHandle, &messagesLeft)) != nullptr) {
|
|
||||||
if(msg->msg == CURLMSG_DONE) {
|
int messagesLeft;
|
||||||
curl_multi_remove_handle(multiHandle, curl);
|
int running;
|
||||||
}
|
if (CURLMcode res = curl_multi_perform(multiHandle, &running)) {
|
||||||
int response;
|
auto message = curl_multi_strerror(res);
|
||||||
curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &response);
|
logger.error() << message;
|
||||||
if (response == HTTP_OK) {
|
return;
|
||||||
long size;
|
|
||||||
if (!curl_easy_getinfo(curl, CURLINFO_REQUEST_SIZE, &size)) {
|
|
||||||
totalUpload += size;
|
|
||||||
}
|
|
||||||
if (!curl_easy_getinfo(curl, CURLINFO_HEADER_SIZE, &size)) {
|
|
||||||
totalDownload += size;
|
|
||||||
}
|
|
||||||
totalDownload += buffer.size();
|
|
||||||
if (onResponse) {
|
|
||||||
onResponse(std::move(buffer));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.error()
|
|
||||||
<< "response code " << response << " (" << url << ")"
|
|
||||||
<< (buffer.empty()
|
|
||||||
? ""
|
|
||||||
: std::to_string(buffer.size()) + " byte(s)");
|
|
||||||
totalDownload += buffer.size();
|
|
||||||
if (onReject) {
|
|
||||||
onReject(response, std::move(buffer));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
url = "";
|
|
||||||
}
|
}
|
||||||
if (url.empty() && !requests.empty()) {
|
CURLMsg* msg = curl_multi_info_read(multiHandle, &messagesLeft);
|
||||||
auto request = std::move(requests.front());
|
if (msg == nullptr || msg->msg != CURLMSG_DONE) {
|
||||||
requests.pop();
|
return;
|
||||||
processRequest(std::move(request));
|
}
|
||||||
|
auto curl = msg->easy_handle;
|
||||||
|
auto found = std::find_if(
|
||||||
|
requests.begin(),
|
||||||
|
requests.end(),
|
||||||
|
[curl](const std::unique_ptr<ProcessingRequest>& entry) {
|
||||||
|
return entry && entry->curl == curl;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (requests.empty() || found == requests.end()) {
|
||||||
|
logger.error() << "could not find request for cURL handle";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto entry = std::move(*found);
|
||||||
|
auto& req = entry->request;
|
||||||
|
requests.erase(found);
|
||||||
|
|
||||||
|
long response = -1;
|
||||||
|
CURLcode result = msg->data.result;
|
||||||
|
curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &response);
|
||||||
|
auto headers = std::move(entry->headers);
|
||||||
|
if (response == 0) {
|
||||||
|
auto message = std::string(curl_easy_strerror(result));
|
||||||
|
logger.error() << message << " (" << req.url << ")";
|
||||||
|
if (req.onResponse) {
|
||||||
|
req.onResponse(
|
||||||
|
{static_cast<int>(response),
|
||||||
|
std::move(headers),
|
||||||
|
std::vector<char>(
|
||||||
|
message.data(), message.data() + message.size()
|
||||||
|
)}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
long size;
|
||||||
|
if (!curl_easy_getinfo(curl, CURLINFO_REQUEST_SIZE, &size)) {
|
||||||
|
totalUpload += size;
|
||||||
|
}
|
||||||
|
if (!curl_easy_getinfo(curl, CURLINFO_HEADER_SIZE, &size)) {
|
||||||
|
totalDownload += size;
|
||||||
|
}
|
||||||
|
totalDownload += entry->buffer.size();
|
||||||
|
if (req.onResponse) {
|
||||||
|
req.onResponse({
|
||||||
|
static_cast<int>(response),
|
||||||
|
std::move(headers),
|
||||||
|
std::move(entry->buffer),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -219,16 +257,11 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::unique_ptr<CurlRequests> create() {
|
static std::unique_ptr<CurlRequests> create() {
|
||||||
auto curl = curl_easy_init();
|
|
||||||
if (curl == nullptr) {
|
|
||||||
throw std::runtime_error("could not initialzie cURL");
|
|
||||||
}
|
|
||||||
auto multiHandle = curl_multi_init();
|
auto multiHandle = curl_multi_init();
|
||||||
if (multiHandle == nullptr) {
|
if (multiHandle == nullptr) {
|
||||||
curl_easy_cleanup(curl);
|
throw std::runtime_error("could not initialize cURL-multi");
|
||||||
throw std::runtime_error("could not initialzie cURL-multi");
|
|
||||||
}
|
}
|
||||||
return std::make_unique<CurlRequests>(multiHandle, curl);
|
return std::make_unique<CurlRequests>(multiHandle);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,27 +51,8 @@ Network::Network(std::unique_ptr<Requests> requests)
|
||||||
|
|
||||||
Network::~Network() = default;
|
Network::~Network() = default;
|
||||||
|
|
||||||
void Network::get(
|
void Network::request(HttpRequest request) {
|
||||||
const std::string& url,
|
requests->request(std::move(request));
|
||||||
OnResponse onResponse,
|
|
||||||
OnReject onReject,
|
|
||||||
std::vector<std::string> headers,
|
|
||||||
long maxSize
|
|
||||||
) {
|
|
||||||
requests->get(url, onResponse, onReject, std::move(headers), maxSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Network::post(
|
|
||||||
const std::string& url,
|
|
||||||
const std::string& fieldsData,
|
|
||||||
OnResponse onResponse,
|
|
||||||
OnReject onReject,
|
|
||||||
std::vector<std::string> headers,
|
|
||||||
long maxSize
|
|
||||||
) {
|
|
||||||
requests->post(
|
|
||||||
url, fieldsData, onResponse, onReject, std::move(headers), maxSize
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Connection* Network::getConnection(u64id_t id, bool includePrivate) {
|
Connection* Network::getConnection(u64id_t id, bool includePrivate) {
|
||||||
|
|
|
||||||
|
|
@ -68,22 +68,7 @@ namespace network {
|
||||||
Network(std::unique_ptr<Requests> requests);
|
Network(std::unique_ptr<Requests> requests);
|
||||||
~Network();
|
~Network();
|
||||||
|
|
||||||
void get(
|
void request(HttpRequest request);
|
||||||
const std::string& url,
|
|
||||||
OnResponse onResponse,
|
|
||||||
OnReject onReject = nullptr,
|
|
||||||
std::vector<std::string> headers = {},
|
|
||||||
long maxSize=0
|
|
||||||
);
|
|
||||||
|
|
||||||
void post(
|
|
||||||
const std::string& url,
|
|
||||||
const std::string& fieldsData,
|
|
||||||
OnResponse onResponse,
|
|
||||||
OnReject onReject = nullptr,
|
|
||||||
std::vector<std::string> headers = {},
|
|
||||||
long maxSize=0
|
|
||||||
);
|
|
||||||
|
|
||||||
[[nodiscard]] Connection* getConnection(u64id_t id, bool includePrivate);
|
[[nodiscard]] Connection* getConnection(u64id_t id, bool includePrivate);
|
||||||
[[nodiscard]] Server* getServer(u64id_t id, bool includePrivate) const;
|
[[nodiscard]] Server* getServer(u64id_t id, bool includePrivate) const;
|
||||||
|
|
|
||||||
|
|
@ -10,33 +10,38 @@
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
namespace network {
|
namespace network {
|
||||||
using OnResponse = std::function<void(std::vector<char>)>;
|
struct HttpResponse;
|
||||||
using OnReject = std::function<void(int, std::vector<char>)>;
|
|
||||||
|
using OnResponse = std::function<void(HttpResponse)>;
|
||||||
using ConnectCallback = std::function<void(u64id_t, u64id_t)>;
|
using ConnectCallback = std::function<void(u64id_t, u64id_t)>;
|
||||||
using ConnectErrorCallback = std::function<void(u64id_t, std::string)>;
|
using ConnectErrorCallback = std::function<void(u64id_t, std::string)>;
|
||||||
using ServerDatagramCallback = std::function<void(u64id_t sid, const std::string& addr, int port, const char* buffer, size_t length)>;
|
using ServerDatagramCallback = std::function<void(u64id_t sid, const std::string& addr, int port, const char* buffer, size_t length)>;
|
||||||
using ClientDatagramCallback = std::function<void(u64id_t cid, const char* buffer, size_t length)>;
|
using ClientDatagramCallback = std::function<void(u64id_t cid, const char* buffer, size_t length)>;
|
||||||
|
|
||||||
|
struct HttpRequest {
|
||||||
|
std::string method;
|
||||||
|
std::string url;
|
||||||
|
std::string body;
|
||||||
|
std::vector<std::string> headers;
|
||||||
|
|
||||||
|
OnResponse onResponse;
|
||||||
|
bool followLocation = false;
|
||||||
|
bool verifySSL = true;
|
||||||
|
long maxSize = -1;
|
||||||
|
long timeoutMs = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HttpResponse {
|
||||||
|
int status;
|
||||||
|
std::vector<std::string> headers;
|
||||||
|
std::vector<char> body;
|
||||||
|
};
|
||||||
|
|
||||||
class Requests {
|
class Requests {
|
||||||
public:
|
public:
|
||||||
virtual ~Requests() {}
|
virtual ~Requests() {}
|
||||||
|
|
||||||
virtual void get(
|
virtual void request(HttpRequest request) = 0;
|
||||||
const std::string& url,
|
|
||||||
OnResponse onResponse,
|
|
||||||
OnReject onReject=nullptr,
|
|
||||||
std::vector<std::string> headers = {},
|
|
||||||
long maxSize=0
|
|
||||||
) = 0;
|
|
||||||
|
|
||||||
virtual void post(
|
|
||||||
const std::string& url,
|
|
||||||
const std::string& data,
|
|
||||||
OnResponse onResponse,
|
|
||||||
OnReject onReject=nullptr,
|
|
||||||
std::vector<std::string> headers = {},
|
|
||||||
long maxSize=0
|
|
||||||
) = 0;
|
|
||||||
|
|
||||||
[[nodiscard]] virtual size_t getTotalUpload() const = 0;
|
[[nodiscard]] virtual size_t getTotalUpload() const = 0;
|
||||||
[[nodiscard]] virtual size_t getTotalDownload() const = 0;
|
[[nodiscard]] virtual size_t getTotalDownload() const = 0;
|
||||||
|
|
|
||||||
|
|
@ -485,6 +485,7 @@ void Entities::render(
|
||||||
if (eid.uid == fpsEntity) {
|
if (eid.uid == fpsEntity) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
const auto& def = eid.def;
|
||||||
const auto& pos = transform.pos;
|
const auto& pos = transform.pos;
|
||||||
const auto& size = transform.size;
|
const auto& size = transform.size;
|
||||||
if (frustum && !frustum->isBoxVisible(pos - size, pos + size)) {
|
if (frustum && !frustum->isBoxVisible(pos - size, pos + size)) {
|
||||||
|
|
@ -494,7 +495,13 @@ void Entities::render(
|
||||||
const auto& rigConfig = skeleton.config;
|
const auto& rigConfig = skeleton.config;
|
||||||
if (rigConfig) {
|
if (rigConfig) {
|
||||||
rigConfig->render(
|
rigConfig->render(
|
||||||
assets, batch, skeleton, transform.rot, pos, size
|
assets,
|
||||||
|
batch,
|
||||||
|
skeleton,
|
||||||
|
def.lightingMode,
|
||||||
|
transform.rot,
|
||||||
|
pos,
|
||||||
|
size
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,4 +14,5 @@ void EntityDef::cloneTo(EntityDef& dst) {
|
||||||
dst.elasticity = elasticity;
|
dst.elasticity = elasticity;
|
||||||
dst.stepHeight = stepHeight;
|
dst.stepHeight = stepHeight;
|
||||||
dst.save = save;
|
dst.save = save;
|
||||||
|
dst.lightingMode = lightingMode;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,7 @@
|
||||||
#include "data/dv.hpp"
|
#include "data/dv.hpp"
|
||||||
#include "maths/aabb.hpp"
|
#include "maths/aabb.hpp"
|
||||||
#include "physics/Hitbox.hpp"
|
#include "physics/Hitbox.hpp"
|
||||||
|
#include "animation/rigging.hpp"
|
||||||
namespace rigging {
|
|
||||||
class SkeletonConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ComponentInstance {
|
struct ComponentInstance {
|
||||||
std::string component;
|
std::string component;
|
||||||
|
|
@ -42,6 +39,9 @@ struct EntityDef {
|
||||||
/// @brief Solid entity material material
|
/// @brief Solid entity material material
|
||||||
std::string material;
|
std::string material;
|
||||||
|
|
||||||
|
/// @brief Model lighting mode
|
||||||
|
ModelLightingMode lightingMode = ModelLightingMode::SOLID;
|
||||||
|
|
||||||
/// @brief Can the entity be interacted with cursor (on_attack, on_use)
|
/// @brief Can the entity be interacted with cursor (on_attack, on_use)
|
||||||
bool selectable = true;
|
bool selectable = true;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -543,6 +543,14 @@ static std::basic_string<CharT> upper_case(const std::basic_string<CharT>& str)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string util::lower_case(const std::string& str) {
|
||||||
|
return ::lower_case(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string util::upper_case(const std::string& str) {
|
||||||
|
return ::upper_case(str);
|
||||||
|
}
|
||||||
|
|
||||||
std::wstring util::lower_case(const std::wstring& str) {
|
std::wstring util::lower_case(const std::wstring& str) {
|
||||||
return ::lower_case(str);
|
return ::lower_case(str);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,8 @@ namespace util {
|
||||||
|
|
||||||
std::string capitalized(const std::string& str);
|
std::string capitalized(const std::string& str);
|
||||||
|
|
||||||
|
std::string lower_case(const std::string& str);
|
||||||
|
std::string upper_case(const std::string& str);
|
||||||
std::wstring lower_case(const std::wstring& str);
|
std::wstring lower_case(const std::wstring& str);
|
||||||
std::wstring upper_case(const std::wstring& str);
|
std::wstring upper_case(const std::wstring& str);
|
||||||
std::wstring capitalized(const std::wstring& str);
|
std::wstring capitalized(const std::wstring& str);
|
||||||
|
|
|
||||||
479
test/crypto/Crypto.cpp
Normal file
479
test/crypto/Crypto.cpp
Normal file
|
|
@ -0,0 +1,479 @@
|
||||||
|
#include "crypto/Crypto.hpp"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <openssl/bn.h>
|
||||||
|
#include <openssl/core_names.h>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include <openssl/rsa.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
using PkeyHandle = std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>;
|
||||||
|
using MdCtxHandle = std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)>;
|
||||||
|
|
||||||
|
crypto::Bytes hex(const std::string& value) {
|
||||||
|
crypto::Bytes result;
|
||||||
|
result.reserve(value.size() / 2);
|
||||||
|
for (std::size_t i = 0; i < value.size(); i += 2) {
|
||||||
|
result.push_back(
|
||||||
|
static_cast<std::uint8_t>(
|
||||||
|
std::stoul(value.substr(i, 2), nullptr, 16)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string binary(const crypto::Bytes& value) {
|
||||||
|
return {reinterpret_cast<const char*>(value.data()), value.size()};
|
||||||
|
}
|
||||||
|
|
||||||
|
crypto::Bytes sign(
|
||||||
|
EVP_PKEY* key,
|
||||||
|
const EVP_MD* digest,
|
||||||
|
std::string_view message,
|
||||||
|
int rsaPadding = 0,
|
||||||
|
int saltLength = 0
|
||||||
|
) {
|
||||||
|
MdCtxHandle ctx(EVP_MD_CTX_new(), EVP_MD_CTX_free);
|
||||||
|
EVP_PKEY_CTX* pkeyCtx = nullptr;
|
||||||
|
EXPECT_NE(ctx, nullptr);
|
||||||
|
EXPECT_EQ(
|
||||||
|
EVP_DigestSignInit(ctx.get(), &pkeyCtx, digest, nullptr, key), 1
|
||||||
|
);
|
||||||
|
if (rsaPadding != 0) {
|
||||||
|
EXPECT_GT(EVP_PKEY_CTX_set_rsa_padding(pkeyCtx, rsaPadding), 0);
|
||||||
|
if (rsaPadding == RSA_PKCS1_PSS_PADDING) {
|
||||||
|
EXPECT_GT(
|
||||||
|
EVP_PKEY_CTX_set_rsa_pss_saltlen(pkeyCtx, saltLength), 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EXPECT_EQ(
|
||||||
|
EVP_DigestSignUpdate(ctx.get(), message.data(), message.size()), 1
|
||||||
|
);
|
||||||
|
std::size_t size = 0;
|
||||||
|
EXPECT_EQ(EVP_DigestSignFinal(ctx.get(), nullptr, &size), 1);
|
||||||
|
crypto::Bytes signature(size);
|
||||||
|
EXPECT_EQ(EVP_DigestSignFinal(ctx.get(), signature.data(), &size), 1);
|
||||||
|
signature.resize(size);
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
crypto::Bytes keyInteger(EVP_PKEY* key, const char* name) {
|
||||||
|
BIGNUM* raw = nullptr;
|
||||||
|
EXPECT_EQ(EVP_PKEY_get_bn_param(key, name, &raw), 1);
|
||||||
|
std::unique_ptr<BIGNUM, decltype(&BN_free)> value(raw, BN_free);
|
||||||
|
crypto::Bytes result(
|
||||||
|
static_cast<std::size_t>(BN_num_bytes(value.get()))
|
||||||
|
);
|
||||||
|
BN_bn2bin(value.get(), result.data());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, HashesAndHmacMatchKnownVectors) {
|
||||||
|
EXPECT_EQ(
|
||||||
|
crypto::digest("SHA256", "abc"),
|
||||||
|
hex("ba7816bf8f01cfea414140de5dae2223"
|
||||||
|
"b00361a396177a9cb410ff61f20015ad")
|
||||||
|
);
|
||||||
|
EXPECT_EQ(
|
||||||
|
crypto::digest("SHA384", "abc"),
|
||||||
|
hex("cb00753f45a35e8bb5a03d699ac65007"
|
||||||
|
"272c32ab0eded1631a8b605a43ff5bed"
|
||||||
|
"8086072ba1e7cc2358baeca134c825a7")
|
||||||
|
);
|
||||||
|
EXPECT_EQ(
|
||||||
|
crypto::digest("SHA512", "abc"),
|
||||||
|
hex("ddaf35a193617abacc417349ae204131"
|
||||||
|
"12e6fa4e89a97ea20a9eeee64b55d39"
|
||||||
|
"a2192992a274fc1a836ba3c23a3feebbd"
|
||||||
|
"454d4423643ce80e2a9ac94fa54ca49f")
|
||||||
|
);
|
||||||
|
EXPECT_EQ(
|
||||||
|
crypto::digest("MD5", "abc"), hex("900150983cd24fb0d6963f7d28e17f72")
|
||||||
|
);
|
||||||
|
const crypto::Bytes key(20, 0x0b);
|
||||||
|
EXPECT_EQ(
|
||||||
|
crypto::hmac("SHA256", binary(key), "Hi There"),
|
||||||
|
hex("b0344c61d8db38535ca8afceaf0bf12b"
|
||||||
|
"881dc200c9833da726e9376c2e32cff7")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, StreamingHashCanBeResetAndRejectsUpdateAfterFinal) {
|
||||||
|
crypto::HashContext context("SHA256");
|
||||||
|
context.update("a");
|
||||||
|
context.update(std::string("b\0c", 3));
|
||||||
|
EXPECT_EQ(
|
||||||
|
context.final(), crypto::digest("SHA256", std::string("ab\0c", 4))
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
context.update("more");
|
||||||
|
FAIL() << "update after final must fail";
|
||||||
|
} catch (const crypto::Error& error) {
|
||||||
|
EXPECT_EQ(error.code(), crypto::ErrorCode::InvalidState);
|
||||||
|
}
|
||||||
|
context.reset();
|
||||||
|
context.update("abc");
|
||||||
|
EXPECT_EQ(context.final(), crypto::digest("SHA256", "abc"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, Ed25519MatchesRfc8032Vector) {
|
||||||
|
auto publicKey =
|
||||||
|
hex("d75a980182b10ab7d54bfed3c964073a"
|
||||||
|
"0ee172f3daa62325af021a68f707511a");
|
||||||
|
auto signature =
|
||||||
|
hex("e5564300c360ac729086e2cc806e828a"
|
||||||
|
"84877f1eb8e5d974d873e06522490155"
|
||||||
|
"5fb8821590a33bacc61e39701cf9b46b"
|
||||||
|
"d25bf5f0595bbe24655141438e7a100b");
|
||||||
|
EXPECT_TRUE(
|
||||||
|
crypto::ed25519Verify(binary(publicKey), "", binary(signature))
|
||||||
|
);
|
||||||
|
signature[0] ^= 1;
|
||||||
|
EXPECT_FALSE(
|
||||||
|
crypto::ed25519Verify(binary(publicKey), "", binary(signature))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, Ed25519GeneratesKeysAndSigns) {
|
||||||
|
const auto keyPair = crypto::ed25519KeyPair();
|
||||||
|
ASSERT_EQ(keyPair.privateKey.size(), std::size_t {32});
|
||||||
|
ASSERT_EQ(keyPair.publicKey.size(), std::size_t {32});
|
||||||
|
EXPECT_EQ(
|
||||||
|
crypto::ed25519Public(binary(keyPair.privateKey)), keyPair.publicKey
|
||||||
|
);
|
||||||
|
const auto signature =
|
||||||
|
crypto::ed25519Sign(binary(keyPair.privateKey), std::string("a\0b", 3));
|
||||||
|
EXPECT_TRUE(
|
||||||
|
crypto::ed25519Verify(
|
||||||
|
binary(keyPair.publicKey), std::string("a\0b", 3), binary(signature)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, EcdsaVerifiesAllSupportedCurves) {
|
||||||
|
struct Case {
|
||||||
|
const char* curve;
|
||||||
|
const char* opensslCurve;
|
||||||
|
const EVP_MD* digest;
|
||||||
|
const char* hash;
|
||||||
|
std::size_t coordinateSize;
|
||||||
|
};
|
||||||
|
const Case cases[] {
|
||||||
|
{"P-256", "prime256v1", EVP_sha256(), "SHA256", 32},
|
||||||
|
{"P-384", "secp384r1", EVP_sha384(), "SHA384", 48},
|
||||||
|
{"P-521", "secp521r1", EVP_sha512(), "SHA512", 66}
|
||||||
|
};
|
||||||
|
for (const auto& item : cases) {
|
||||||
|
SCOPED_TRACE(item.curve);
|
||||||
|
PkeyHandle key(
|
||||||
|
EVP_PKEY_Q_keygen(nullptr, nullptr, "EC", item.opensslCurve),
|
||||||
|
EVP_PKEY_free
|
||||||
|
);
|
||||||
|
ASSERT_NE(key, nullptr);
|
||||||
|
BIGNUM* rawX = nullptr;
|
||||||
|
BIGNUM* rawY = nullptr;
|
||||||
|
ASSERT_EQ(
|
||||||
|
EVP_PKEY_get_bn_param(key.get(), OSSL_PKEY_PARAM_EC_PUB_X, &rawX), 1
|
||||||
|
);
|
||||||
|
ASSERT_EQ(
|
||||||
|
EVP_PKEY_get_bn_param(key.get(), OSSL_PKEY_PARAM_EC_PUB_Y, &rawY), 1
|
||||||
|
);
|
||||||
|
std::unique_ptr<BIGNUM, decltype(&BN_free)> x(rawX, BN_free);
|
||||||
|
std::unique_ptr<BIGNUM, decltype(&BN_free)> y(rawY, BN_free);
|
||||||
|
crypto::Bytes publicKey(item.coordinateSize * 2 + 1);
|
||||||
|
publicKey[0] = 0x04;
|
||||||
|
ASSERT_EQ(
|
||||||
|
BN_bn2binpad(
|
||||||
|
x.get(), publicKey.data() + 1, item.coordinateSize
|
||||||
|
),
|
||||||
|
static_cast<int>(item.coordinateSize)
|
||||||
|
);
|
||||||
|
ASSERT_EQ(
|
||||||
|
BN_bn2binpad(
|
||||||
|
y.get(),
|
||||||
|
publicKey.data() + 1 + item.coordinateSize,
|
||||||
|
item.coordinateSize
|
||||||
|
),
|
||||||
|
static_cast<int>(item.coordinateSize)
|
||||||
|
);
|
||||||
|
auto signature = sign(key.get(), item.digest, "message");
|
||||||
|
EXPECT_TRUE(
|
||||||
|
crypto::ecdsaVerify(
|
||||||
|
item.curve,
|
||||||
|
binary(publicKey),
|
||||||
|
"message",
|
||||||
|
binary(signature),
|
||||||
|
item.hash
|
||||||
|
)
|
||||||
|
);
|
||||||
|
EXPECT_FALSE(
|
||||||
|
crypto::ecdsaVerify(
|
||||||
|
item.curve,
|
||||||
|
binary(publicKey),
|
||||||
|
"tampered",
|
||||||
|
binary(signature),
|
||||||
|
item.hash
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, EcdsaGeneratesKeysAndSignsOnAllSupportedCurves) {
|
||||||
|
struct Case {
|
||||||
|
const char* curve;
|
||||||
|
const char* hash;
|
||||||
|
std::size_t privateSize;
|
||||||
|
std::size_t publicSize;
|
||||||
|
};
|
||||||
|
const Case cases[] {
|
||||||
|
{"P-256", "SHA256", 32, 65},
|
||||||
|
{"P-384", "SHA384", 48, 97},
|
||||||
|
{"P-521", "SHA512", 66, 133}
|
||||||
|
};
|
||||||
|
for (const auto& item : cases) {
|
||||||
|
SCOPED_TRACE(item.curve);
|
||||||
|
const auto keyPair = crypto::ecdsaKeyPair(item.curve);
|
||||||
|
EXPECT_EQ(keyPair.privateKey.size(), item.privateSize);
|
||||||
|
EXPECT_EQ(keyPair.publicKey.size(), item.publicSize);
|
||||||
|
EXPECT_EQ(
|
||||||
|
crypto::ecdsaPublic(item.curve, binary(keyPair.privateKey)),
|
||||||
|
keyPair.publicKey
|
||||||
|
);
|
||||||
|
const auto signature = crypto::ecdsaSign(
|
||||||
|
item.curve, binary(keyPair.privateKey), "message", item.hash
|
||||||
|
);
|
||||||
|
EXPECT_TRUE(
|
||||||
|
crypto::ecdsaVerify(
|
||||||
|
item.curve,
|
||||||
|
binary(keyPair.publicKey),
|
||||||
|
"message",
|
||||||
|
binary(signature),
|
||||||
|
item.hash
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, RsaVerifiesPkcs1AndPssSignatures) {
|
||||||
|
PkeyHandle key(
|
||||||
|
EVP_PKEY_Q_keygen(nullptr, nullptr, "RSA", 2048), EVP_PKEY_free
|
||||||
|
);
|
||||||
|
ASSERT_NE(key, nullptr);
|
||||||
|
const auto modulus = keyInteger(key.get(), OSSL_PKEY_PARAM_RSA_N);
|
||||||
|
const auto exponent = keyInteger(key.get(), OSSL_PKEY_PARAM_RSA_E);
|
||||||
|
|
||||||
|
auto pkcs1 = sign(key.get(), EVP_sha256(), "message", RSA_PKCS1_PADDING);
|
||||||
|
EXPECT_TRUE(
|
||||||
|
crypto::rsaPkcs1Verify(
|
||||||
|
"SHA256",
|
||||||
|
binary(modulus),
|
||||||
|
binary(exponent),
|
||||||
|
"message",
|
||||||
|
binary(pkcs1)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
EXPECT_FALSE(
|
||||||
|
crypto::rsaPkcs1Verify(
|
||||||
|
"SHA256",
|
||||||
|
binary(modulus),
|
||||||
|
binary(exponent),
|
||||||
|
"tampered",
|
||||||
|
binary(pkcs1)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
auto pss = sign(
|
||||||
|
key.get(),
|
||||||
|
EVP_sha256(),
|
||||||
|
"message",
|
||||||
|
RSA_PKCS1_PSS_PADDING,
|
||||||
|
RSA_PSS_SALTLEN_DIGEST
|
||||||
|
);
|
||||||
|
EXPECT_TRUE(
|
||||||
|
crypto::rsaPssVerify(
|
||||||
|
"SHA256",
|
||||||
|
binary(modulus),
|
||||||
|
binary(exponent),
|
||||||
|
"message",
|
||||||
|
binary(pss),
|
||||||
|
-1
|
||||||
|
)
|
||||||
|
);
|
||||||
|
EXPECT_FALSE(
|
||||||
|
crypto::rsaPssVerify(
|
||||||
|
"SHA256",
|
||||||
|
binary(modulus),
|
||||||
|
binary(exponent),
|
||||||
|
"tampered",
|
||||||
|
binary(pss),
|
||||||
|
-1
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, X25519MatchesRfc7748PublicKeyVector) {
|
||||||
|
const auto privateKey =
|
||||||
|
hex("77076d0a7318a57d3c16c17251b26645"
|
||||||
|
"df4c2f87ebc0992ab177fba51db92c2a");
|
||||||
|
EXPECT_EQ(
|
||||||
|
crypto::x25519Public(binary(privateKey)),
|
||||||
|
hex("8520f0098930a754748b7ddcb43ef75a0"
|
||||||
|
"dbf3a0d26381af4eba4a98eaa9b4e6a")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, X25519GeneratedKeyPairsDeriveTheSameSecret) {
|
||||||
|
const auto alice = crypto::x25519KeyPair();
|
||||||
|
const auto bob = crypto::x25519KeyPair();
|
||||||
|
EXPECT_EQ(alice.privateKey.size(), std::size_t {32});
|
||||||
|
EXPECT_EQ(alice.publicKey.size(), std::size_t {32});
|
||||||
|
EXPECT_EQ(crypto::x25519Public(binary(alice.privateKey)), alice.publicKey);
|
||||||
|
EXPECT_EQ(
|
||||||
|
crypto::x25519(binary(alice.privateKey), binary(bob.publicKey)),
|
||||||
|
crypto::x25519(binary(bob.privateKey), binary(alice.publicKey))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, P256DerivesPublicKeyAndSharedSecret) {
|
||||||
|
crypto::Bytes privateA(32, 0);
|
||||||
|
crypto::Bytes privateB(32, 0);
|
||||||
|
privateA.back() = 1;
|
||||||
|
privateB.back() = 2;
|
||||||
|
const auto publicA = crypto::p256Public(binary(privateA));
|
||||||
|
const auto publicB = crypto::p256Public(binary(privateB));
|
||||||
|
EXPECT_EQ(
|
||||||
|
publicA,
|
||||||
|
hex("046b17d1f2e12c4247f8bce6e563a440"
|
||||||
|
"f277037d812deb33a0f4a13945d898c296"
|
||||||
|
"4fe342e2fe1a7f9b8ee7eb4a7c0f9e16"
|
||||||
|
"2bce33576b315ececbb6406837bf51f5")
|
||||||
|
);
|
||||||
|
EXPECT_EQ(
|
||||||
|
crypto::p256Shared(binary(privateA), binary(publicB)),
|
||||||
|
crypto::p256Shared(binary(privateB), binary(publicA))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, AesGcmMatchesKnownVectorAndRejectsTampering) {
|
||||||
|
const crypto::Bytes key(16, 0);
|
||||||
|
const crypto::Bytes nonce(12, 0);
|
||||||
|
const crypto::Bytes plaintext(16, 0);
|
||||||
|
auto ciphertext = crypto::aesGcmEncrypt(
|
||||||
|
binary(key), binary(nonce), "", binary(plaintext)
|
||||||
|
);
|
||||||
|
EXPECT_EQ(
|
||||||
|
ciphertext,
|
||||||
|
hex("0388dace60b6a392f328c2b971b2fe78"
|
||||||
|
"ab6e47d42cec13bdf53a67b21257bddf")
|
||||||
|
);
|
||||||
|
EXPECT_EQ(
|
||||||
|
crypto::aesGcmDecrypt(
|
||||||
|
binary(key), binary(nonce), "", binary(ciphertext)
|
||||||
|
),
|
||||||
|
plaintext
|
||||||
|
);
|
||||||
|
ciphertext.back() ^= 1;
|
||||||
|
EXPECT_THROW(
|
||||||
|
crypto::aesGcmDecrypt(
|
||||||
|
binary(key), binary(nonce), "", binary(ciphertext)
|
||||||
|
),
|
||||||
|
std::runtime_error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, Chacha20Poly1305RoundTripsAndRejectsTampering) {
|
||||||
|
const crypto::Bytes key(32, 0);
|
||||||
|
const crypto::Bytes nonce(12, 0);
|
||||||
|
auto ciphertext = crypto::chacha20Poly1305Encrypt(
|
||||||
|
binary(key), binary(nonce), "aad", "plaintext"
|
||||||
|
);
|
||||||
|
EXPECT_EQ(
|
||||||
|
binary(
|
||||||
|
crypto::chacha20Poly1305Decrypt(
|
||||||
|
binary(key), binary(nonce), "aad", binary(ciphertext)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"plaintext"
|
||||||
|
);
|
||||||
|
ciphertext.back() ^= 1;
|
||||||
|
EXPECT_THROW(
|
||||||
|
crypto::chacha20Poly1305Decrypt(
|
||||||
|
binary(key), binary(nonce), "aad", binary(ciphertext)
|
||||||
|
),
|
||||||
|
std::runtime_error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, HkdfMatchesRfc5869Vector) {
|
||||||
|
const crypto::Bytes ikm(22, 0x0b);
|
||||||
|
const auto salt = hex("000102030405060708090a0b0c");
|
||||||
|
const auto info = hex("f0f1f2f3f4f5f6f7f8f9");
|
||||||
|
const auto prk = crypto::hkdfExtract("SHA256", binary(salt), binary(ikm));
|
||||||
|
EXPECT_EQ(
|
||||||
|
prk,
|
||||||
|
hex("077709362c2e32df0ddc3f0dc47bba63"
|
||||||
|
"90b6c73bb50f9c3122ec844ad7c2b3e5")
|
||||||
|
);
|
||||||
|
EXPECT_EQ(
|
||||||
|
crypto::hkdfExpand("SHA256", binary(prk), binary(info), 42),
|
||||||
|
hex("3cb25f25faacd57a90434f64d0362f2a"
|
||||||
|
"2d2d0a90cf1a5a4c5db02d56ecc4c5bf"
|
||||||
|
"34007208d5b887185865")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, PasswordKdfsMatchKnownVectors) {
|
||||||
|
EXPECT_EQ(
|
||||||
|
crypto::pbkdf2("SHA256", "password", "salt", 1, 32),
|
||||||
|
hex("120fb6cffcf8b32c43e7225256c4f837"
|
||||||
|
"a86548c92ccc35480805987cb70be17b")
|
||||||
|
);
|
||||||
|
EXPECT_EQ(
|
||||||
|
crypto::scrypt("", "", 16, 1, 1, 64),
|
||||||
|
hex("77d6576238657b203b19ca42c18a0497"
|
||||||
|
"f16b4844e3074ae8dfdffa3fede21442f"
|
||||||
|
"cd0069ded0948f8326a753a0fc81f17e"
|
||||||
|
"8d3e0fb2e0d3628cf35e20c38d18906")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, ReportsRuntimeCapabilitiesAndStableErrors) {
|
||||||
|
const auto available = crypto::features();
|
||||||
|
EXPECT_TRUE(available.sha256);
|
||||||
|
EXPECT_TRUE(available.ed25519);
|
||||||
|
EXPECT_TRUE(available.ecdsa);
|
||||||
|
EXPECT_TRUE(available.x25519);
|
||||||
|
EXPECT_TRUE(available.aes128Gcm);
|
||||||
|
EXPECT_TRUE(available.pbkdf2);
|
||||||
|
EXPECT_TRUE(available.scrypt);
|
||||||
|
EXPECT_EQ(crypto::API_VERSION, 1);
|
||||||
|
|
||||||
|
const crypto::Bytes key(16, 0);
|
||||||
|
const crypto::Bytes nonce(12, 0);
|
||||||
|
auto ciphertext =
|
||||||
|
crypto::aesGcmEncrypt(binary(key), binary(nonce), "", "secret");
|
||||||
|
ciphertext.back() ^= 1;
|
||||||
|
try {
|
||||||
|
crypto::aesGcmDecrypt(
|
||||||
|
binary(key), binary(nonce), "", binary(ciphertext)
|
||||||
|
);
|
||||||
|
FAIL() << "tampered ciphertext must fail";
|
||||||
|
} catch (const crypto::Error& error) {
|
||||||
|
EXPECT_EQ(error.code(), crypto::ErrorCode::AuthenticationFailed);
|
||||||
|
EXPECT_STREQ(error.codeName(), "authentication_failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Crypto, UtilitiesHandleBinaryData) {
|
||||||
|
const std::string binaryValue("a\0b", 3);
|
||||||
|
EXPECT_TRUE(crypto::constantTimeEqual(binaryValue, binaryValue));
|
||||||
|
EXPECT_FALSE(crypto::constantTimeEqual(binaryValue, "a"));
|
||||||
|
EXPECT_FALSE(crypto::constantTimeEqual("left", "lest"));
|
||||||
|
EXPECT_EQ(crypto::randomBytes(32).size(), std::size_t {32});
|
||||||
|
}
|
||||||
|
|
@ -6,18 +6,21 @@
|
||||||
TEST(curltest, curltest) {
|
TEST(curltest, curltest) {
|
||||||
NetworkSettings settings {};
|
NetworkSettings settings {};
|
||||||
auto network = network::Network::create(settings);
|
auto network = network::Network::create(settings);
|
||||||
network->get(
|
|
||||||
"https://raw.githubusercontent.com/MihailRis/VoxelEngine-Cpp/refs/"
|
network::HttpRequest request {};
|
||||||
"heads/curl/res/content/base/blocks/lamp.json",
|
request.url = "https://raw.githubusercontent.com/MihailRis/VoxelEngine-Cpp/refs/"
|
||||||
[](std::vector<char> data) {
|
"heads/curl/res/content/base/blocks/lamp.json";
|
||||||
if (data.empty()) {
|
request.onResponse = [](network::HttpResponse response) {
|
||||||
return;
|
if (response.body.empty()) {
|
||||||
}
|
return;
|
||||||
auto view = std::string_view(data.data(), data.size());
|
}
|
||||||
auto value = json::parse(view);
|
auto view =
|
||||||
std::cout << value << std::endl;
|
std::string_view(response.body.data(), response.body.size());
|
||||||
}, [](auto, auto){}
|
auto value = json::parse(view);
|
||||||
);
|
std::cout << value << std::endl;
|
||||||
|
};
|
||||||
|
network->request(std::move(request));
|
||||||
|
|
||||||
std::cout << "upload: " << network->getTotalUpload() << " B" << std::endl;
|
std::cout << "upload: " << network->getTotalUpload() << " B" << std::endl;
|
||||||
std::cout << "download: " << network->getTotalDownload() << " B" << std::endl;
|
std::cout << "download: " << network->getTotalDownload() << " B" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
"entt",
|
"entt",
|
||||||
"gtest",
|
"gtest",
|
||||||
"curl",
|
"curl",
|
||||||
"freetype"
|
"freetype",
|
||||||
|
"openssl"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,8 @@ struct Config {
|
||||||
fs::path directory;
|
fs::path directory;
|
||||||
fs::path resDir {"res"};
|
fs::path resDir {"res"};
|
||||||
fs::path workingDir {"."};
|
fs::path workingDir {"."};
|
||||||
std::string memchecker = "valgrind";
|
std::string memchecker = "";
|
||||||
|
std::string debugger = "";
|
||||||
bool outputAlways = false;
|
bool outputAlways = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -30,6 +31,7 @@ static bool perform_keyword(
|
||||||
std::cout << " --tests <path>, -d <path> = tests directory path\n";
|
std::cout << " --tests <path>, -d <path> = tests directory path\n";
|
||||||
std::cout << " --res <path>, -r <path> = 'res' directory path\n";
|
std::cout << " --res <path>, -r <path> = 'res' directory path\n";
|
||||||
std::cout << " --user <path>, -u <path> = user directory path\n";
|
std::cout << " --user <path>, -u <path> = user directory path\n";
|
||||||
|
std::cout << " --debugger <command> = debugger with run command\n";
|
||||||
std::cout << " --memchecker <path> = path to valgrind\n";
|
std::cout << " --memchecker <path> = path to valgrind\n";
|
||||||
std::cout << " --output-always = always show tests output\n";
|
std::cout << " --output-always = always show tests output\n";
|
||||||
std::cout << std::endl;
|
std::cout << std::endl;
|
||||||
|
|
@ -44,6 +46,8 @@ static bool perform_keyword(
|
||||||
config.workingDir = fs::path(reader.next());
|
config.workingDir = fs::path(reader.next());
|
||||||
} else if (keyword == "--output-always") {
|
} else if (keyword == "--output-always") {
|
||||||
config.outputAlways = true;
|
config.outputAlways = true;
|
||||||
|
} else if (keyword == "--debugger") {
|
||||||
|
config.debugger = reader.next();
|
||||||
} else if (keyword == "--memchecker") {
|
} else if (keyword == "--memchecker") {
|
||||||
config.memchecker = reader.next();
|
config.memchecker = reader.next();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -172,7 +176,7 @@ static std::string fix_path(std::string s) {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool run_test(const Config& config, const fs::path& path, bool memcheck = false) {
|
static bool run_test(const Config& config, const fs::path& path, bool debugRun = false) {
|
||||||
using std::chrono::duration_cast;
|
using std::chrono::duration_cast;
|
||||||
using std::chrono::high_resolution_clock;
|
using std::chrono::high_resolution_clock;
|
||||||
using std::chrono::milliseconds;
|
using std::chrono::milliseconds;
|
||||||
|
|
@ -182,9 +186,16 @@ static bool run_test(const Config& config, const fs::path& path, bool memcheck =
|
||||||
|
|
||||||
auto name = path.stem();
|
auto name = path.stem();
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
if (memcheck) {
|
if (debugRun) {
|
||||||
ss << config.memchecker << " --log-file="
|
if (!config.memchecker.empty()) {
|
||||||
<< fix_path(memcheckLogFile.string()) << " ";
|
ss << config.memchecker;
|
||||||
|
ss << " --log-file=" << fix_path(memcheckLogFile.string());
|
||||||
|
ss << " ";
|
||||||
|
}
|
||||||
|
if (!config.debugger.empty()) {
|
||||||
|
ss << config.debugger;
|
||||||
|
ss << " ";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ss << fs::canonical(config.executable) << " --headless";
|
ss << fs::canonical(config.executable) << " --headless";
|
||||||
ss << " --test " << fix_path(path.string());
|
ss << " --test " << fix_path(path.string());
|
||||||
|
|
@ -203,7 +214,7 @@ static bool run_test(const Config& config, const fs::path& path, bool memcheck =
|
||||||
.count();
|
.count();
|
||||||
|
|
||||||
if (code) {
|
if (code) {
|
||||||
if (memcheck) {
|
if (debugRun) {
|
||||||
// valgrind-specific output
|
// valgrind-specific output
|
||||||
display_segfault_valgrind(memcheckLogFile, name, std::cerr);
|
display_segfault_valgrind(memcheckLogFile, name, std::cerr);
|
||||||
fs::remove(memcheckLogFile);
|
fs::remove(memcheckLogFile);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue