From 7ce4caaa14ca44e3636c9549b1218df499f6f048 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Mon, 10 Jul 2023 16:55:44 +1000 Subject: [PATCH 01/34] Add Opus library --- .gitmodules | 3 +++ source/extern/CMakeLists.txt | 10 ++++++++++ source/extern/opus | 1 + 3 files changed, 14 insertions(+) create mode 100644 .gitmodules create mode 160000 source/extern/opus diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..64e8013 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "source/extern/opus"] + path = source/extern/opus + url = https://github.com/xiph/opus diff --git a/source/extern/CMakeLists.txt b/source/extern/CMakeLists.txt index e96b1b1..0195b8f 100644 --- a/source/extern/CMakeLists.txt +++ b/source/extern/CMakeLists.txt @@ -1,5 +1,14 @@ +SET (OPUS_INSTALL_PKG_CONFIG_MODULE OFF) +SET (OPUS_INSTALL_CMAKE_CONFIG_MODULE OFF) +SET (OPUS_X86_MAY_HAVE_AVX OFF) +SET (OPUS_X86_MAY_HAVE_SSE4_1 OFF) +SET (OPUS_STACK_PROTECTOR OFF) + +ADD_SUBDIRECTORY (opus) + INCLUDE_DIRECTORIES ( ${STAR_EXTERN_INCLUDES} + opus fmt lua ) @@ -60,3 +69,4 @@ SET (star_extern_SOURCES ) ADD_LIBRARY (star_extern OBJECT ${star_extern_SOURCES} ${star_extern_HEADERS}) +TARGET_LINK_LIBRARIES(star_extern opus) \ No newline at end of file diff --git a/source/extern/opus b/source/extern/opus new file mode 160000 index 0000000..9fc8fc4 --- /dev/null +++ b/source/extern/opus @@ -0,0 +1 @@ +Subproject commit 9fc8fc4cf432640f284113ba502ee027268b0d9f From 81343bc06fc08d8a3ba441e6038c9be1ed093fdc Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Wed, 12 Jul 2023 15:13:30 +1000 Subject: [PATCH 02/34] Add Curve25519 --- source/core/CMakeLists.txt | 2 + source/core/StarCurve25519.cpp | 50 + source/core/StarCurve25519.hpp | 25 + source/extern/CMakeLists.txt | 14 +- .../extern/curve25519/include/curve25519_dh.h | 53 + .../curve25519/include/ed25519_signature.h | 98 ++ .../curve25519/include/external_calls.h | 36 + source/extern/curve25519/source/BaseTypes.h | 121 ++ .../extern/curve25519/source/base_folding8.h | 1288 +++++++++++++++++ .../extern/curve25519/source/curve25519_dh.c | 208 +++ .../curve25519/source/curve25519_mehdi.c | 410 ++++++ .../curve25519/source/curve25519_mehdi.h | 175 +++ .../curve25519/source/curve25519_order.c | 155 ++ .../curve25519/source/curve25519_utils.c | 153 ++ .../extern/curve25519/source/custom_blind.c | 27 + .../extern/curve25519/source/custom_blind.h | 11 + .../extern/curve25519/source/ed25519_sign.c | 419 ++++++ .../extern/curve25519/source/ed25519_verify.c | 313 ++++ source/extern/curve25519/source/sha512.c | 294 ++++ source/extern/curve25519/source/sha512.h | 92 ++ 20 files changed, 3942 insertions(+), 2 deletions(-) create mode 100644 source/core/StarCurve25519.cpp create mode 100644 source/core/StarCurve25519.hpp create mode 100644 source/extern/curve25519/include/curve25519_dh.h create mode 100644 source/extern/curve25519/include/ed25519_signature.h create mode 100644 source/extern/curve25519/include/external_calls.h create mode 100644 source/extern/curve25519/source/BaseTypes.h create mode 100644 source/extern/curve25519/source/base_folding8.h create mode 100644 source/extern/curve25519/source/curve25519_dh.c create mode 100644 source/extern/curve25519/source/curve25519_mehdi.c create mode 100644 source/extern/curve25519/source/curve25519_mehdi.h create mode 100644 source/extern/curve25519/source/curve25519_order.c create mode 100644 source/extern/curve25519/source/curve25519_utils.c create mode 100644 source/extern/curve25519/source/custom_blind.c create mode 100644 source/extern/curve25519/source/custom_blind.h create mode 100644 source/extern/curve25519/source/ed25519_sign.c create mode 100644 source/extern/curve25519/source/ed25519_verify.c create mode 100644 source/extern/curve25519/source/sha512.c create mode 100644 source/extern/curve25519/source/sha512.h diff --git a/source/core/CMakeLists.txt b/source/core/CMakeLists.txt index 08701d7..1fd2aac 100644 --- a/source/core/CMakeLists.txt +++ b/source/core/CMakeLists.txt @@ -21,6 +21,7 @@ SET (star_core_HEADERS StarColor.hpp StarCompression.hpp StarConfig.hpp + StarCurve25519.hpp StarDataStream.hpp StarDataStreamDevices.hpp StarDataStreamExtra.hpp @@ -133,6 +134,7 @@ SET (star_core_SOURCES StarByteArray.cpp StarColor.cpp StarCompression.cpp + StarCurve25519.cpp StarDataStream.cpp StarDataStreamDevices.cpp StarDirectives.cpp diff --git a/source/core/StarCurve25519.cpp b/source/core/StarCurve25519.cpp new file mode 100644 index 0000000..f8f5451 --- /dev/null +++ b/source/core/StarCurve25519.cpp @@ -0,0 +1,50 @@ +#include "StarCurve25519.hpp" +#include "StarRandom.hpp" +#include "StarLogging.hpp" + +#include "curve25519/include/curve25519_dh.h" +#include "curve25519/include/ed25519_signature.h" + +namespace Star::Curve25519 { + +struct KeySet { + PrivateKey privateKey; + PublicKey publicKey; + + KeySet() { + SecretKey secret; + Random::randBytes(SecretKeySize).copyTo((char*)secret.data()); + + secret[0] &= 248; + secret[31] &= 127; + secret[31] |= 64; + + ed25519_CreateKeyPair(privateKey.data(), publicKey.data(), nullptr, secret.data()); + + Logger::info("Generated Curve25519 key-pair"); + } +}; + +static KeySet const& staticKeys() { + static KeySet keys; + + return keys; +} + +PrivateKey const& privateKey() { return staticKeys().privateKey; } + + + +Signature sign(void* data, size_t len) { + Signature signature; + ed25519_SignMessage(signature.data(), privateKey().data(), nullptr, (unsigned char*)data, len); + return signature; +} + +bool verify(uint8_t const* signature, uint8_t const* publicKey, void* data, size_t len) { + return ed25519_VerifySignature(signature, publicKey, (unsigned char*)data, len); +} + +PublicKey const& publicKey() { return staticKeys().publicKey; } + +} \ No newline at end of file diff --git a/source/core/StarCurve25519.hpp b/source/core/StarCurve25519.hpp new file mode 100644 index 0000000..15fe4d1 --- /dev/null +++ b/source/core/StarCurve25519.hpp @@ -0,0 +1,25 @@ +#ifndef STAR_CURVE_25519_HPP +#define STAR_CURVE_25519_HPP +#include "StarEncode.hpp" +#include "StarByteArray.hpp" +#include "StarArray.hpp" + +namespace Star::Curve25519 { + +constexpr size_t PublicKeySize = 32; +constexpr size_t SecretKeySize = 32; +constexpr size_t PrivateKeySize = 64; +constexpr size_t SignatureSize = 64; + +typedef Array PublicKey; +typedef Array SecretKey; +typedef Array PrivateKey; +typedef Array Signature; + +PublicKey const& publicKey(); +Signature sign(void* data, size_t len); +bool verify(uint8_t const* signature, uint8_t const* publicKey, void* data, size_t len); + +} + +#endif \ No newline at end of file diff --git a/source/extern/CMakeLists.txt b/source/extern/CMakeLists.txt index 0195b8f..e356c95 100644 --- a/source/extern/CMakeLists.txt +++ b/source/extern/CMakeLists.txt @@ -3,17 +3,19 @@ SET (OPUS_INSTALL_CMAKE_CONFIG_MODULE OFF) SET (OPUS_X86_MAY_HAVE_AVX OFF) SET (OPUS_X86_MAY_HAVE_SSE4_1 OFF) SET (OPUS_STACK_PROTECTOR OFF) - ADD_SUBDIRECTORY (opus) INCLUDE_DIRECTORIES ( ${STAR_EXTERN_INCLUDES} - opus + opus/include fmt lua ) SET (star_extern_HEADERS + curve25519/include/curve25519_dh.h + curve25519/include/ed25519_signature.h + curve25519/include/external_calls.h fmt/core.h fmt/format.h fmt/format-inl.h @@ -31,6 +33,14 @@ SET (star_extern_HEADERS ) SET (star_extern_SOURCES + curve25519/source/sha512.c + curve25519/source/curve25519_dh.c + curve25519/source/curve25519_mehdi.c + curve25519/source/curve25519_order.c + curve25519/source/curve25519_utils.c + curve25519/source/custom_blind.c + curve25519/source/ed25519_sign.c + curve25519/source/ed25519_verify.c xxhash.c fmt/format.cc lua/lapi.c diff --git a/source/extern/curve25519/include/curve25519_dh.h b/source/extern/curve25519/include/curve25519_dh.h new file mode 100644 index 0000000..258084b --- /dev/null +++ b/source/extern/curve25519/include/curve25519_dh.h @@ -0,0 +1,53 @@ +/* The MIT License (MIT) + * + * Copyright (c) 2015 mehdi sotoodeh + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __curve25519_dh_key_exchange_h__ +#define __curve25519_dh_key_exchange_h__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Return public key associated with sk */ +/* sk will be trimmed on return */ +void curve25519_dh_CalculatePublicKey( + unsigned char *pk, /* [32-bytes] OUT: Public key */ + unsigned char *sk); /* [32-bytes] IN/OUT: Your secret key */ + +/* Faster alternative for curve25519_dh_CalculatePublicKey */ +/* sk will be trimmed on return */ +void curve25519_dh_CalculatePublicKey_fast( + unsigned char *pk, /* [32-bytes] OUT: Public key */ + unsigned char *sk); /* [32-bytes] IN/OUT: Your secret key */ + +/* sk will be trimmed on return */ +void curve25519_dh_CreateSharedKey( + unsigned char *shared, /* [32-bytes] OUT: Created shared key */ + const unsigned char *pk, /* [32-bytes] IN: Other side's public key */ + unsigned char *sk); /* [32-bytes] IN/OUT: Your secret key */ + +#ifdef __cplusplus +} +#endif +#endif /* __curve25519_dh_key_exchange_h__ */ \ No newline at end of file diff --git a/source/extern/curve25519/include/ed25519_signature.h b/source/extern/curve25519/include/ed25519_signature.h new file mode 100644 index 0000000..5831d9d --- /dev/null +++ b/source/extern/curve25519/include/ed25519_signature.h @@ -0,0 +1,98 @@ +/* The MIT License (MIT) + * + * Copyright (c) 2015 mehdi sotoodeh + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __ed25519_signature_h__ +#define __ed25519_signature_h__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* -- ed25519-sign ------------------------------------------------------------- */ + +#define ed25519_public_key_size 32 +#define ed25519_secret_key_size 32 +#define ed25519_private_key_size 64 +#define ed25519_signature_size 64 + +/* Generate public key associated with the secret key */ +void ed25519_CreateKeyPair( + unsigned char *pubKey, /* OUT: public key */ + unsigned char *privKey, /* OUT: private key */ + const void *blinding, /* IN: [optional] null or blinding context */ + const unsigned char *sk); /* IN: secret key (32 bytes) */ + +/* Generate message signature */ +void ed25519_SignMessage( + unsigned char *signature, /* OUT:[64 bytes] signature (R,S) */ + const unsigned char *privKey, /* IN: [64 bytes] private key (sk,pk) */ + const void *blinding, /* IN: [optional] null or blinding context */ + const unsigned char *msg, /* IN: [msg_size bytes] message to sign */ + size_t msg_size); /* IN: size of message */ + +void *ed25519_Blinding_Init( + void *context, /* IO: null or ptr blinding context */ + const unsigned char *seed, /* IN: [size bytes] random blinding seed */ + size_t size); /* IN: size of blinding seed */ + +void ed25519_Blinding_Finish( + void *context); /* IN: blinding context */ + +/* -- ed25519-verify ----------------------------------------------------------- */ + +/* Single-phased signature validation. + Returns 1 for SUCCESS and 0 for FAILURE +*/ +int ed25519_VerifySignature( + const unsigned char *signature, /* IN: [64 bytes] signature (R,S) */ + const unsigned char *publicKey, /* IN: [32 bytes] public key */ + const unsigned char *msg, /* IN: [msg_size bytes] message to sign */ + size_t msg_size); /* IN: size of message */ + +/* First part of two-phase signature validation. + This function creates context specifc to a given public key. + Needs to be called once per public key +*/ +void * ed25519_Verify_Init( + void *context, /* IO: null or verify context to use */ + const unsigned char *publicKey); /* IN: [32 bytes] public key */ + +/* Second part of two-phase signature validation. + Input context is output of ed25519_Verify_Init() for associated public key. + Call it once for each message/signature pairs + Returns 1 for SUCCESS and 0 for FAILURE +*/ +int ed25519_Verify_Check( + const void *context, /* IN: created by ed25519_Verify_Init */ + const unsigned char *signature, /* IN: signature (R,S) */ + const unsigned char *msg, /* IN: message to sign */ + size_t msg_size); /* IN: size of message */ + +/* Free up context memory */ +void ed25519_Verify_Finish(void *ctx); + +#ifdef __cplusplus +} +#endif +#endif /* __ed25519_signature_h__ */ \ No newline at end of file diff --git a/source/extern/curve25519/include/external_calls.h b/source/extern/curve25519/include/external_calls.h new file mode 100644 index 0000000..7513b5f --- /dev/null +++ b/source/extern/curve25519/include/external_calls.h @@ -0,0 +1,36 @@ +/* The MIT License (MIT) + * + * Copyright (c) 2015 mehdi sotoodeh + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __external_calls_h__ +#define __external_calls_h__ + +#include +#include + +#define mem_alloc(size) malloc(size) +#define mem_free(addr) free(addr) +#define mem_clear(addr,size) memset(addr,0,size) +#define mem_fill(addr,data,size) memset(addr,data,size) + +#endif /* __external_calls_h__ */ diff --git a/source/extern/curve25519/source/BaseTypes.h b/source/extern/curve25519/source/BaseTypes.h new file mode 100644 index 0000000..8d2e9a5 --- /dev/null +++ b/source/extern/curve25519/source/BaseTypes.h @@ -0,0 +1,121 @@ +/* The MIT License (MIT) + * + * Copyright (c) 2015 mehdi sotoodeh + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __curve25519_base_type_h__ +#define __curve25519_base_type_h__ + +#include + +/* Little-endian as default */ +#ifndef ECP_CONFIG_BIG_ENDIAN +#define ECP_CONFIG_LITTLE_ENDIAN +#endif + +typedef unsigned char U8; +typedef signed char S8; + +#if defined(_MSC_VER) +typedef unsigned __int16 U16; +typedef signed __int16 S16; +typedef unsigned __int32 U32; +typedef signed __int32 S32; +typedef unsigned __int64 U64; +typedef signed __int64 S64; +#else +typedef uint16_t U16; +typedef int16_t S16; +typedef uint32_t U32; +typedef int32_t S32; +typedef uint64_t U64; +typedef int64_t S64; +#endif + +typedef unsigned int SZ; + +#ifdef ECP_CONFIG_BIG_ENDIAN +typedef union +{ + U16 u16; + S16 s16; + U8 bytes[2]; + struct { U8 b1, b0; } u8; + struct { S8 b1; U8 b0; } s8; +} M16; +typedef union +{ + U32 u32; + S32 s32; + U8 bytes[4]; + struct { U16 w1, w0; } u16; + struct { S16 w1; U16 w0; } s16; + struct { U8 b3, b2, b1, b0; } u8; + struct { M16 hi, lo; } m16; +} M32; +typedef union +{ + U64 u64; + S64 s64; + U8 bytes[8]; + struct { U32 hi, lo; } u32; + struct { S32 hi; U32 lo; } s32; + struct { U16 w3, w2, w1, w0; } u16; + struct { U8 b7, b6, b5, b4, b3, b2, b1, b0; } u8; + struct { M32 hi, lo; } m32; +} M64; +#else +typedef union +{ + U16 u16; + S16 s16; + U8 bytes[2]; + struct { U8 b0, b1; } u8; + struct { U8 b0; S8 b1; } s8; +} M16; +typedef union +{ + U32 u32; + S32 s32; + U8 bytes[4]; + struct { U16 w0, w1; } u16; + struct { U16 w0; S16 w1; } s16; + struct { U8 b0, b1, b2, b3; } u8; + struct { M16 lo, hi; } m16; +} M32; +typedef union +{ + U64 u64; + S64 s64; + U8 bytes[8]; + struct { U32 lo, hi; } u32; + struct { U32 lo; S32 hi; } s32; + struct { U16 w0, w1, w2, w3; } u16; + struct { U8 b0, b1, b2, b3, b4, b5, b6, b7; } u8; + struct { M32 lo, hi; } m32; +} M64; +#endif + +#define IN +#define OUT + +#endif diff --git a/source/extern/curve25519/source/base_folding8.h b/source/extern/curve25519/source/base_folding8.h new file mode 100644 index 0000000..a851175 --- /dev/null +++ b/source/extern/curve25519/source/base_folding8.h @@ -0,0 +1,1288 @@ +/* + This table is generated from all 256 permutations of SUM(P0,P1,..,P7) + where P_i = 2^(32i)*BasePoint +*/ + +const PA_POINT _w_base_folding8[256] = +{ + { /* P{0} */ + W256(0x00000001,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000), + W256(0x00000001,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000), + W256(0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000) + }, + { /* P{1} */ + W256(0xF58C3B85,0x2FBC93C6,0xFB8C0E19,0xCF932DC6,0x643D42C2,0x270B4898,0x33D4BA65,0x07CF9D3A), + W256(0xD740913E,0x9D103905,0xD140BEB3,0xFD399F05,0x688F8A09,0xA5C18434,0x98F81267,0x44FD2F92), + W256(0x877AAA68,0xABC91205,0xCCAAC49E,0x26D9E823,0xDD43598C,0x5A1B7DCB,0x9F0C65A8,0x6F117B68) + }, + { /* P{2} */ + W256(0x7B85C5E8,0x8765B69F,0xD168BAB2,0x6FF0678B,0x1D330F9B,0x3A70E77C,0xB0AF8E7C,0x3A5F6D51), + W256(0xA60DAC5F,0x61368756,0xEBABDC57,0x17E02F6A,0x4CCE0F7D,0x7F193F2D,0x89ECDCF0,0x20234A77), + W256(0x7178B252,0x76D20DB6,0xD51ED160,0x071C34F9,0xB3E41170,0xF62A4A20,0x3CFFE366,0x7CD68235) + }, + { /* P{3} */ + W256(0xA76C971F,0x812D6A74,0x0739F84D,0x1CECA25F,0x4102B810,0x27D06C3C,0x031906F7,0x7EDC1CC6), + W256(0x43BA112A,0x500519AD,0xAEBD3AD9,0xEBDA0D43,0x3E2CF3F0,0xF3F6B90C,0xA60CD4F5,0x0DE1829B), + W256(0x2AB6034B,0xF5472C0E,0xE480F342,0xBD4DBF55,0x0CCED266,0xD443D271,0x9C2DB4F7,0x4FE5CA42) + }, + { /* P{4} */ + W256(0x77D1F515,0xCD2A65E7,0x8FAA60F1,0x54899187,0xDABC06E5,0xB1B73BBC,0xA97CC9FB,0x654878CB), + W256(0x8DF6B0FE,0x51138EC7,0xE575F51B,0x5397DA89,0x717AF1B9,0x09207A1D,0x2B20D650,0x2102FDBA), + W256(0x055CE6A1,0x969EE405,0x1251AD29,0x36BCA768,0xAA7DA415,0x3A1AF517,0x29ECB2BA,0x0AD725DB) + }, + { /* P{5} */ + W256(0x601E59E8,0x0055C585,0x66480E60,0x8793342B,0xFE45E44C,0x3E14AAD0,0x4813CF2B,0x26EAD8E6), + W256(0x9C8462A4,0xCB75B8B6,0x67D31CD7,0x2DD86FC5,0x881342F6,0xCD1972EC,0x0FC12F2F,0x0975B597), + W256(0xDA5BA743,0x63CF2303,0x52F1BA6E,0x04BF9D81,0xAA7367DA,0x333790D0,0x9DF6C5EA,0x53467047) + }, + { /* P{6} */ + W256(0x7738657C,0x0AF0993C,0x47181BD2,0xA9FB4A47,0x3E9EC14A,0x55DDE7AB,0x37C8321C,0x0B9EC7F6), + W256(0x88035653,0x0AE97F7D,0x8D25068E,0x17FB7B10,0x3C1E8C9A,0x6328FC0E,0x50FE219B,0x5441585E), + W256(0x640F0146,0xCB52864C,0x317EF0D3,0x94562266,0x1595BB5D,0xB876F4C4,0xAF7F7713,0x4ED2F874) + }, + { /* P{7} */ + W256(0x74D2CDA7,0xFEB687D8,0xAE27FB22,0xB8042DC1,0x2C93ADCC,0x1564556D,0xE3A17816,0x11442D28), + W256(0x07F0C5D4,0xCEC33585,0x95B01C20,0xFB9E7EF9,0xDEA13143,0x0A869383,0xF5F8C59B,0x16C63A61), + W256(0xD6FED546,0xDED6638C,0x3803190E,0x1ABDB60B,0x6A614DA3,0xE0C56F15,0x9EDC6E88,0x04C54856) + }, + { /* P{8} */ + W256(0x12DDB0A4,0xD598639C,0xC024866B,0xA5D19F30,0x58FCE460,0xD17C2F03,0x2E095E8A,0x07A19515), + W256(0x9C2EC4DE,0x296FA9C5,0x4F84F3CB,0xBC8B61BF,0x17A8F908,0x1C7706D9,0x7AD3255D,0x63B795FC), + W256(0x389E5FC8,0xA8368F02,0xCF8DE43B,0x90433B02,0xC5412643,0xAFA1FD5D,0x032F0137,0x3E8FE83D) + }, + { /* P{9} */ + W256(0x17D3A339,0xB5F7F69A,0x36A01F1E,0x805E6145,0x9B01A221,0x08E62B6D,0x38F1898F,0x0AF83250), + W256(0xBDF01E71,0xBCC2BE89,0xC3280D0F,0x6758F9E1,0xCCF36C58,0xF2A56CDE,0x80FAACC0,0x1CDFBF7E), + W256(0x39BFB3B2,0x730688FB,0x07C06F81,0xF493C376,0x8DB1BA83,0xBDA608C2,0x63ECCA60,0x2990E0A2) + }, + { /* P{10} */ + W256(0x59DC4791,0xFCA8EA41,0x8B3AA058,0x0FAE3DAB,0x4EE996EB,0xBE13396F,0x51936C6F,0x379D09BB), + W256(0xF614AFFB,0xB6601A1B,0x210392EA,0x8360C886,0x56349198,0x4867333C,0xF049C42C,0x03224A6F), + W256(0x6FB88974,0x5E267A30,0x4F8FB990,0xBEB84F82,0x18C57B0D,0x6029B7B9,0xA2357DF1,0x60670BBE) + }, + { /* P{11} */ + W256(0xEC02DCD8,0xC8E97558,0x734215B0,0x70E6D52D,0xC97F37CC,0x5AD04683,0xC1D685AB,0x2C610AB3), + W256(0xE10C024F,0xABC333F4,0x672386CC,0x4F8149E4,0x4E7BF204,0x3BAD171C,0xE78F5067,0x4CEA190B), + W256(0x02F3325F,0x8D8D3A79,0xBF8ABDE9,0x549CBD48,0x11D19A0C,0x9DA1A0F6,0x372C817C,0x718BF518) + }, + { /* P{12} */ + W256(0x2381AD37,0x21C5E9F3,0xB96CD050,0xC340283E,0x3150ABCA,0xFBDEE471,0xC8FC9DB7,0x31B24DEE), + W256(0x2B76CCBB,0x2C160EED,0x54D82775,0x6DD5C36B,0xD5E0A5E9,0x7F278210,0xC59CF540,0x076FC47C), + W256(0x5F61DF81,0xBDF661D8,0x4EAEC5BD,0xFBB57FE5,0xE97CF86E,0x5AA7105E,0xD8D3CD95,0x00A448B6) + }, + { /* P{13} */ + W256(0x972952B6,0x584444A6,0xF3BA527C,0x1A2E4AF7,0xC1E4ED75,0x5AD4FFF4,0x3E78BA6F,0x4C05A24B), + W256(0xBF346A2E,0x124C55B2,0x3935E365,0xD103A43D,0xCB1495FA,0x057EC4BC,0x2715BAED,0x00ED1078), + W256(0xD51A6122,0xA0ACDB33,0x1637632C,0x371BC5B6,0xB00D94A1,0x8E34D701,0x7AF603FA,0x55A768B2) + }, + { /* P{14} */ + W256(0xB26499E1,0xA18985D8,0x3CEB5996,0xC1BC6208,0x5DF6F244,0x831CDAB3,0x630B203C,0x649754AF), + W256(0xB81E44C9,0xA3FBD80E,0xFCFBB70A,0xF7CB98EF,0xCFF94F56,0xF4AA5E2A,0x126D04AE,0x688D5981), + W256(0x8CBD0FFE,0xB3B29F90,0x269A99DB,0xFA623FB6,0xF70E7D34,0xB4B95B97,0x2A21E650,0x0D4247A0) + }, + { /* P{15} */ + W256(0x312ECED9,0x9EA81138,0xFD0E579D,0x85E9D4D4,0x23D68E7C,0x2308BA50,0xF6983F2F,0x36B2632B), + W256(0xF87A7D71,0x19A9F3F1,0x78B0D72D,0x60077D68,0x01A59BBF,0x8921D761,0xF8407391,0x262E84E2), + W256(0xD3285253,0xCDC70136,0x014124E2,0x7FFF27A4,0xDB2A2DF9,0xE29AC913,0x66338730,0x1D3BDC38) + }, + { /* P{16} */ + W256(0xACAD8EA2,0x583B04BF,0x148BE884,0x29B743E8,0x0810C5DB,0x2B1E583B,0x8EB3BBAA,0x2B5449E5), + W256(0xEB3DBE47,0x5F3A7562,0x8EBDA0B8,0xF7EA3854,0x45747299,0x00C3E531,0x1627D551,0x1304E9E7), + W256(0x6ADC9CFE,0x789814D2,0x8B48DD0B,0x3C1BAB3F,0xF979C60A,0xDA0FE1FF,0x7C2DD693,0x4468DE2D) + }, + { /* P{17} */ + W256(0xE3BC6748,0x2118278D,0xD0B20EF7,0xE71FFD60,0xC67BB198,0xF551BE51,0xD0543D4D,0x26A13664), + W256(0x13A339EE,0x29522D3B,0x6CD89529,0x85522550,0xACF4F0F1,0xDFEA3AD4,0x7942742E,0x49D76BBA), + W256(0x8D56E61D,0x14FA4233,0xC351299A,0x191D3946,0xA7ADB185,0x247D576D,0xA8FCEDC2,0x4E1FAFE3) + }, + { /* P{18} */ + W256(0x6B45EA9A,0x4B413B87,0xFA00AC34,0xF6C48949,0x81AC11D2,0x1A1F2362,0xDD980A49,0x30F3DDE7), + W256(0xBCCBC178,0x2D8BA97F,0x519FE02D,0xAEB46997,0xB79E5C67,0xB84B8FD5,0x143A7698,0x5B91B0FB), + W256(0x3C1C04D5,0xCA900BC5,0xA39B659C,0xF169E39F,0x08A5AFF2,0x8D2ACF06,0x7C985580,0x26416E52) + }, + { /* P{19} */ + W256(0xC3366BDF,0x9E6C7295,0xB28D0A8F,0x4808B22D,0xA7082921,0xF7375D06,0x7280CB9D,0x072AE441), + W256(0x7E73663A,0xAD4E9526,0x6402BF65,0xC26E23FF,0x374ECB55,0x34E1DAB6,0xAC128163,0x4F1EADF2), + W256(0xC7581FDD,0xDF30E1BB,0x61A63C23,0xB3E73F2E,0xA16D9C3D,0xA750CFD5,0x497B7242,0x719F9FCF) + }, + { /* P{20} */ + W256(0x236A044C,0x15E7053D,0x3B8D87E3,0x3CDDBCB1,0xD321A828,0x519960D2,0x0FC5BBA4,0x4E559A0F), + W256(0x9C12701C,0xFE00E876,0x039C3B5F,0x95DCDC0A,0x0C02EB1B,0xC169454B,0x5F87530C,0x727021D3), + W256(0x27DF241E,0xA5710407,0xB2900D36,0xDF45EFAA,0x60A69ADE,0xFE6EDB5C,0x07BBC01D,0x64FCB730) + }, + { /* P{21} */ + W256(0x6FD390CA,0x38EF58CC,0x171A98FC,0xEF786575,0xC442D65F,0x8850B78F,0x6FD086EF,0x6F34C66D), + W256(0x3898DC04,0x93F3CBB4,0x4307B727,0x0791FFB2,0xCE34981D,0xD7BD8096,0x8B849F6D,0x0B598B8E), + W256(0x0CC2F689,0x11CFC18A,0xB529CE2A,0x81114607,0xC00B5940,0x0A9BC046,0xB1AC66C8,0x412128B0) + }, + { /* P{22} */ + W256(0xA3D84743,0x8B5F16B3,0x20EB9FF7,0x809C9763,0xDF9462C3,0xABC50529,0xC4CC6F52,0x1C648CE1), + W256(0xE2EB0307,0xF2674254,0x2DA3AA98,0xA835DF64,0x07DD382C,0x377941CE,0xF60597C6,0x09603B84), + W256(0xC9E55150,0x1840B2D2,0x390C7082,0x0CEFA12F,0xB6D2A82F,0xCB58A4A0,0xE4BF3457,0x4EC62E6E) + }, + { /* P{23} */ + W256(0xF571047D,0xCF391089,0x001D01B2,0xADA730B7,0xB8DABA3D,0x18871AA4,0xF61CCCE8,0x34D6411B), + W256(0x9F79EF24,0x0C14A627,0x68276CE3,0x1E44AF93,0xAF5E5CEB,0xB72EF4FF,0x4AC56322,0x72237BAD), + W256(0x37C0DC1F,0x9E293ED7,0xBEFB66DB,0xE165F9D6,0x325DD9F3,0xAC1B10FC,0x8009D865,0x3E5ECBE4) + }, + { /* P{24} */ + W256(0xE8B81044,0x59733185,0x108BAC60,0xE2E029E4,0x6908254B,0x9D9DEDF4,0xBFC317ED,0x4F8D3ADF), + W256(0x25AF0DCC,0x078B7334,0x5067BA11,0x0472D1D3,0x38191434,0x37848B52,0xBD371A71,0x543107B9), + W256(0xC08D3984,0x6B64607C,0xC89F5B2C,0xD44F8EB9,0x77523565,0x805CD88B,0xAE0B9CCB,0x0CE8C056) + }, + { /* P{25} */ + W256(0xB3819AB4,0xB8B66511,0xC418154B,0x5309B011,0x60E07CAF,0x514F7CB5,0x66A3F80B,0x47FD0855), + W256(0x76E05CAA,0xBA4EADCC,0x3A54A171,0x9D40E878,0x26DA9727,0x7C9542ED,0x2F23CDCA,0x46440C7A), + W256(0x431334CF,0x80102AB0,0x58C0CE70,0xCD1B2787,0x0154B9BB,0xDA3CCBEC,0x740E3E26,0x233A3339) + }, + { /* P{26} */ + W256(0x03FF1BB4,0x66B1DB86,0x4F427346,0xECCBFD7B,0x730E1064,0xF5C0EF08,0x98AC2743,0x6AD4EF70), + W256(0xA78B10E4,0x6B409241,0x7A431C57,0xD3313951,0x6D66DF43,0xF2423B52,0x5B0B4EC0,0x223EE393), + W256(0x9067E8CC,0xA882277C,0x10A85F5E,0x4C113D37,0x569CE51F,0x77A224C5,0x23FF2F06,0x77B028B6) + }, + { /* P{27} */ + W256(0x0D549385,0x5B209F06,0x63C235E0,0xA08976F7,0x5A3054FA,0x3D4C47B6,0xCEEC9D5F,0x60658F6D), + W256(0x06156933,0x3CC5FE1E,0x7C4D221A,0x56CB4582,0x6BDF48A7,0x913CDCDD,0x913F6B94,0x1FF62032), + W256(0x7095B31B,0xE5E35AFA,0xB528BD84,0xADEBE9A8,0x111204F7,0x774AC00F,0xAF32BA8A,0x73473323) + }, + { /* P{28} */ + W256(0xF4F67B48,0x6CC34769,0x25194E16,0xECA62A7C,0x9E71DE23,0x1D302A91,0xA0CA9C66,0x3B64FCF1), + W256(0x58B1B8C0,0x6C0F0B9C,0x293BB326,0x3795DB5D,0x6B411555,0x5C8E6A48,0x65336921,0x29275DB6), + W256(0x0E8B907B,0x30C8F772,0xD18547B0,0x7DACFAAB,0xFFED56E8,0xA786345F,0x089751C9,0x7418665A) + }, + { /* P{29} */ + W256(0xA1DAE8E1,0xCCE6BDF4,0x9BAFBB60,0xD56EB17C,0x1C286590,0x3DD92F76,0x43F38AED,0x5E5C02DA), + W256(0x366C497D,0x0B884A8E,0x50F8071B,0x2D68611C,0x4F7D7AD7,0x6FEDE5E9,0xC9190A34,0x1508210F), + W256(0xA50625CC,0x380903E6,0x2DE33381,0xF4E3C123,0x6DBD4874,0x3CE70681,0xB66B3A9C,0x3437DE93) + }, + { /* P{30} */ + W256(0x410A634F,0xDCB489C7,0xC5F43C21,0xB966CA7B,0x627E497D,0xC8151DD3,0x444C095B,0x7EA1541E), + W256(0x0EE54670,0xB443ACB8,0xB8F40C8B,0x34AD3FF2,0x689D8B4C,0xBA037921,0x1A082A71,0x405952A4), + W256(0x5142ABC6,0x56B8D889,0x540DE993,0x86ED618F,0xCC7FBF1E,0x72A0CD17,0x468B4E24,0x044745CA) + }, + { /* P{31} */ + W256(0xF5BB9F3B,0x58FA4EBE,0xB99C8573,0x1977B7DD,0x81D0E028,0x07EBEDBB,0xC9609C12,0x6491BA77), + W256(0xFF8604B6,0x2FBD585A,0xC0B44E60,0x9FE55125,0xA3AEAC8A,0x9BA69F90,0x15BE487D,0x05393A06), + W256(0xBB6B377E,0x67CD556F,0x17735944,0xA89D57FF,0x0C137883,0x38DB2167,0x90BC96CB,0x3B77872F) + }, + { /* P{32} */ + W256(0x305B2F51,0x96EEBFFB,0x889596B8,0xD3F938AD,0x46D5DD25,0xF0F52DC7,0xBB3A0095,0x57968290), + W256(0x8C58AEDC,0x4637974E,0xABF041A4,0xB9EF22FB,0xE980718A,0xE185D956,0xB143A8A6,0x2F1B78FA), + W256(0x0A20E101,0xF71AB843,0x24F0EC47,0xF393658D,0x6EE2EED1,0xCF7509A8,0xDC2AA3E1,0x7DC43E35) + }, + { /* P{33} */ + W256(0x8E260E07,0x39B3240A,0x62871AC8,0x852074F4,0xA44E0939,0xE2B30D7B,0xA0ABAAE8,0x46024D80), + W256(0xD3C313BF,0xB2B999B9,0x6E51EBB2,0x2D5ABD52,0xEE0E550F,0x90BBDBBB,0x98CCCE19,0x68DA131C), + W256(0x54A41B0A,0xEC23DE8E,0xB782780D,0xCF7B9637,0x939E311E,0x3CFD7049,0x0EA55972,0x597A7817) + }, + { /* P{34} */ + W256(0x06BAAFDA,0xBC045FDE,0xFB5A4416,0x47739AE0,0x5D2EE4A6,0x7C8463D8,0x04374AD2,0x2203EAC5), + W256(0xA05C01E7,0x0EFF92FE,0x895FB7CC,0x75F7039C,0x9404E0F8,0x8D6A8217,0x8A9A737A,0x573560A5), + W256(0x59DD3897,0x86782D19,0x70D83750,0x60123667,0xFCE034C3,0x9B25D884,0x01E23583,0x3E1460C5) + }, + { /* P{35} */ + W256(0x016C8B1C,0x0532F811,0xD136BA45,0x2E95ED3F,0xE61135AB,0x63A63935,0x998DDB46,0x4528B81A), + W256(0xB2E76CB2,0xBB03507C,0x6FA0507E,0x4974972D,0x909195F3,0xAC4EF385,0x584F70A3,0x06057D24), + W256(0x8C90F978,0x54A5662F,0x2F7B10C4,0xCBC6CCF5,0x865DB58B,0x448B019B,0x95F99DED,0x42420EE3) + }, + { /* P{36} */ + W256(0xB5714CEB,0x30BAF789,0x974FD937,0xB8AA475F,0x0F057B03,0x433FA765,0x4E4B8810,0x1F64562F), + W256(0x95C2FD9C,0x563EE272,0xA9B8DC68,0x5DD036C4,0x036007BA,0x7C530EF9,0xDBD2061B,0x39F11784), + W256(0x37138FAB,0xAB6C4500,0x34F6DADC,0xC27F3315,0x8F069781,0x0210194C,0x9E2F307A,0x50E31631) + }, + { /* P{37} */ + W256(0xC3B0007B,0x472EA0BC,0x45EB13C5,0xE17CA69E,0x819BFA26,0x6278A077,0xB21B3D0A,0x09FFC1DA), + W256(0x8DB01743,0x335F8D6C,0x476C43B0,0x2B131C1C,0x92DDC403,0x6FC74962,0x1B87720C,0x4FB1E3E1), + W256(0x93B99DD4,0xE7355CBC,0x68EDCAF2,0xD1C37535,0x113C1F1A,0xF8C2A48A,0x7D5EB4B0,0x78A7A057) + }, + { /* P{38} */ + W256(0x60456CBB,0xBE511469,0x8D5D8C2E,0x1F30B9FD,0xB4C04589,0xB87B3385,0x4351534C,0x554247D4), + W256(0x55283C9E,0x101625E2,0xC1E61FB8,0xFAD2395D,0x6B7D5564,0xC3534059,0x96E1D1F2,0x521864BF), + W256(0xB9332CC5,0xF4FFC63A,0xA52BD4A8,0x918219B2,0x841911C2,0xB9DA028E,0xC36C5963,0x6B1FC8BC) + }, + { /* P{39} */ + W256(0xFF5EFDCF,0xC69C8CBA,0x8EF90996,0x2911C701,0x86929CD5,0xC9245FA6,0x403C2507,0x680DFC97), + W256(0xC62E53B7,0x86C5EE26,0x217CC5D7,0x5F38E8D3,0x3BE42BCE,0xBE8F603D,0x0C2B9113,0x1FA5774D), + W256(0x2670BEED,0x89ECBFFB,0x6138312F,0x02950E5D,0x20860EA8,0xD4961611,0xE64C338F,0x1F301F21) + }, + { /* P{40} */ + W256(0x75E3FB78,0x39D004C9,0x29B114E0,0x18B6E865,0xD16C3194,0x38EBE25F,0x9E79F5CF,0x607D2344), + W256(0x2338841D,0xCE240F2C,0x8631E5D1,0xF0ECCD75,0xF18A552D,0xA57EE5F1,0x1C9B3233,0x7AB87BC8), + W256(0x1B17760E,0x7E7F7150,0xE158A26E,0xB6632A86,0xBE0AC1D5,0x39EA5398,0x503BA911,0x68A56D9D) + }, + { /* P{41} */ + W256(0xE76FDB4D,0x23CB640C,0x3F0908FE,0x47E31113,0xD0B34BD7,0xCDCBAC03,0x837C25B8,0x7F82DC07), + W256(0x71A8C4DB,0xD8E96E52,0x7CFDB8C9,0x23294A50,0x8AF236C6,0x46960665,0xBA025941,0x399A2E1D), + W256(0x3A40D717,0xF4286F37,0xA6C7AB20,0x1B14D057,0x52F67CCA,0x83A1C822,0x1B1CA416,0x7CC9285E) + }, + { /* P{42} */ + W256(0xC8A5BC9B,0xFFE05716,0x465E9F43,0x066A99E4,0x4A91D729,0x5F1F7C8B,0x1C435B3B,0x24102BD9), + W256(0xF4CA0F68,0xF20B8F9E,0xE0AEF35B,0xDC05F306,0x2D531E9A,0xB4666F04,0x1B535EE9,0x383FF5D3), + W256(0x442B19AA,0x84DCD7CB,0xE6B5984D,0x4118DB5F,0x21A7DEC1,0x4FE5DD46,0xD86EF74F,0x1CC49092) + }, + { /* P{43} */ + W256(0xB44C3A2F,0x8B29913A,0xAA52080F,0xCD694C5D,0x5ADD88D4,0x18F93889,0x901BC6A7,0x63EAAAC5), + W256(0xB5715B8C,0x1D1D5492,0xD839A828,0xA4200D1D,0x2B183D76,0xBCAC4824,0x4C296927,0x0CBB60C5), + W256(0x4BBEB387,0x80FBCDFE,0xEC5778E7,0x2A83408C,0x34339AF7,0x1F78D31B,0xC8A8EF0D,0x3F9D64C8) + }, + { /* P{44} */ + W256(0x811A5A5A,0x9DCEEBBE,0x92764FEE,0xA3266706,0x3CAF98AA,0x90359A3C,0xFF96A438,0x2B83D213), + W256(0xD47173AB,0xFACDC91F,0x5D2F974B,0xADEA7B2C,0x6FDF8E35,0xC13603D0,0xC639EF42,0x30435392), + W256(0x8CC47E2C,0x1263D1AC,0x0BA468D7,0xE21C9E3E,0xF0204559,0x537A318F,0x2FB95AC5,0x10AA737E) + }, + { /* P{45} */ + W256(0x2006DE8F,0x85B007AA,0xF6BB993C,0xBDE46C9D,0xFEB58E38,0x7BC28481,0x6212A44A,0x78B9FDC3), + W256(0xDECB7244,0xA5EA6C05,0x4E0D68B8,0xD24015A7,0x9C10E916,0x546C84B6,0x7BAC682C,0x2B5447C6), + W256(0x71687F56,0x7365F032,0xD4E60EA8,0x8289C896,0xE6A09396,0x419A7CB6,0xD6400EF0,0x3DD7F389) + }, + { /* P{46} */ + W256(0xA70E980D,0xCC2FD8E0,0x8381B94F,0x6DDF7029,0x480C7F27,0xF50664B1,0x231AC201,0x49058F81), + W256(0x2B002594,0x068ACF53,0x51BDE407,0x2DB03C16,0xA06CB4BD,0xAE564BDB,0x345E692A,0x4CEF9672), + W256(0x64EF121E,0x83F6ACCF,0x03C67508,0xB662C5CD,0x5E8EC922,0x0096E089,0x6365F801,0x651F8062) + }, + { /* P{47} */ + W256(0x446A6929,0x16527736,0x6A71196E,0x065115B5,0xF29C4EAB,0xDB7CE2F7,0x77CCE053,0x21E2AE16), + W256(0x163B9183,0x07F1B268,0x8E5270C2,0x53A98108,0xFBAA6979,0x6C820AF9,0xFA55E6E9,0x5962BC13), + W256(0xE021520D,0x287E4E5C,0x94C883CD,0x6863C664,0x5790868F,0x58163F9D,0x7E31C55A,0x753907EC) + }, + { /* P{48} */ + W256(0x7B784751,0x9AA6B27C,0x478EBAA4,0x324BEC2C,0x6AEC068F,0xE9ED08E6,0x1289EA7F,0x2201F338), + W256(0x1D82D9E5,0x88B6F93F,0x38AF97B8,0xC753DB55,0x2390C879,0x1BF3B161,0x3633ED25,0x138E33B3), + W256(0x7B9CD213,0xB6B55F37,0x20636109,0x2898D18F,0xC10D1935,0x0B5507EE,0xE7915B1C,0x09F8A06A) + }, + { /* P{49} */ + W256(0xA156BEE1,0x2D49686E,0x1C1DA03B,0x2324E89C,0xAC203D50,0xD04411D1,0x1BA09051,0x4C676623), + W256(0xAAD02187,0xD3AFC637,0x4A427480,0xDBD9DF22,0x0A76A644,0xA1667506,0x219197E9,0x4D4F7873), + W256(0x7C9FE358,0x62CCF2C6,0x62498039,0x9247D76E,0xD7566ABE,0xF38B6CD8,0xC416D3CE,0x75B11CF8) + }, + { /* P{50} */ + W256(0xD35FFB34,0x263738AE,0x08F9D4CE,0x832A1BE5,0xB79AB05B,0x85A8A919,0xA2783D74,0x249A81E3), + W256(0x1829704F,0x715BB053,0x543DA8B4,0x1EC6AC53,0x12478E5E,0xFDA431A3,0x496D50A6,0x499021E4), + W256(0x548CDCB6,0x899A7EE9,0x22E94B30,0x6C1DD7B6,0xCC3BC5CA,0xC83AC3B4,0xDD006024,0x7058121D) + }, + { /* P{51} */ + W256(0xD0A8E8DA,0x655ADE93,0xF3B0B528,0x54028BC0,0x7068729D,0xFEEB9E37,0x63D404AB,0x2F6D0357), + W256(0x57911CD7,0x319E7E40,0x4A053A89,0x48CD0556,0x1711B185,0x8DB0F64A,0x3CC73FD4,0x23F42F87), + W256(0xFD5FEE8E,0x0E966846,0x34D2C0AE,0x5E0BB862,0x80FD5963,0x60D4F63B,0xD8E4DBBC,0x68DD930A) + }, + { /* P{52} */ + W256(0x49349375,0x9FC17ECB,0x4158C55C,0x5E732FFB,0xFBDC6DED,0x8759BFAB,0x7B86639D,0x0A052F82), + W256(0x777962B3,0xE04CC8D1,0x63464020,0x8835C295,0x10F7A751,0x7D2E941F,0x6EE28E97,0x50400CC1), + W256(0xE09B6BB2,0x654A5E35,0x7997775E,0x63A31E9C,0x3416C504,0x78190EB8,0x2BB6F3A0,0x304DBDF5) + }, + { /* P{53} */ + W256(0x9FCA639C,0xC0F200E9,0x35F7F0E3,0x54D06B32,0x363B7D83,0xED9121BC,0x4ACDABF0,0x3E5EA2D9), + W256(0x95849740,0x3F317C55,0xBD56A2F2,0x639A7ED4,0x529C3A7A,0x1CCAA5F1,0x0989B5B0,0x5652C4F5), + W256(0xB99E3A9F,0x1BB512BB,0xBEC8021C,0x4EFC5E35,0x687C6E35,0x1BEEDE8C,0xFC28959C,0x6D7F7CD4) + }, + { /* P{54} */ + W256(0xD6AE865A,0x37F602FE,0x5644C995,0x308FBCA2,0x77EAEAE2,0x0ECC084C,0xB4CDC74F,0x7DB8FE19), + W256(0x1377ADD9,0x2641E359,0xB1A41953,0x3F7E961C,0x401F2F9F,0x968AFEE2,0x5A37BBEF,0x2E92A64E), + W256(0x93ACC7FC,0xF20F4797,0x40BAB1E7,0xC5485755,0x7F03710D,0x7C83E40E,0x6F44A6EE,0x69F8FD42) + }, + { /* P{55} */ + W256(0x56A50DDA,0xF381AD3B,0xF2E4A6FA,0xD6DE8923,0x64D7DCDC,0xB3759362,0x7C2E641E,0x3EAD5CD6), + W256(0x845B8D5C,0x910AFC9F,0xA1A43909,0x714AC6E6,0x06B29A0B,0x80F27D93,0x1E41F09A,0x6532988A), + W256(0xC88AE13C,0x552C67DF,0xBF55730F,0x8F591C65,0x2FE7A905,0x5B519E2C,0x77E7A32F,0x6E7F879A) + }, + { /* P{56} */ + W256(0x6D2128F5,0x70E89575,0x113E2943,0x11B2E65D,0x502A27E3,0xCC14522F,0x5223D989,0x0E6258C5), + W256(0x82EC85AE,0x5F939035,0x8535FD78,0xAD15CF95,0xF786332F,0x756CDB90,0x5DA65E0C,0x2D18D5ED), + W256(0x9BDFC49A,0x8AD6B63E,0xE7F7C76D,0xA1B3286A,0x56D495F6,0xB0E423D8,0xACD6FBC6,0x1461D531) + }, + { /* P{57} */ + W256(0x136B2904,0x277B0FF7,0xA8D39066,0x6F94FDAE,0x56101BD9,0xE9A20FDC,0xB892705E,0x4226AB45), + W256(0x5F57047B,0x9770DCB1,0xC5CF4123,0xAA2F99A9,0xBBB7F32C,0xCA99DA38,0x6A654557,0x5B74A519), + W256(0x494DB970,0x2C3AA0D1,0xAF7CA799,0xF8FDB612,0xC7B28AE3,0xD6E75DEE,0x982B6B0B,0x0CA5047C) + }, + { /* P{58} */ + W256(0x5D5AB1F3,0xA096097E,0x30D14C4D,0x55E6AC6B,0x40B47347,0x0E787B5B,0x7172E37E,0x27FDEB5F), + W256(0x03373C3F,0x5F328AD3,0x3B3038A6,0x2E79AD7E,0xDE222F9D,0x39DC8F94,0x1B18555A,0x34F4B6D1), + W256(0xFCA72FBA,0xFBD02A20,0x4FCADB53,0x2827ACB2,0xEE77588C,0xEBC9A4E0,0xED302543,0x7AFCF628) + }, + { /* P{59} */ + W256(0x5E1A397A,0xB85A436C,0xABFD5DC6,0xE51B3D09,0x351E57A6,0x09509CB9,0xB3C8F3DB,0x25A1C873), + W256(0x23FD941E,0x86F2BEBE,0xFC6E6286,0xF568F06C,0x2E4D97A3,0x736AB7BC,0x999BB98D,0x682C7169), + W256(0x0D5C4162,0x0B156F55,0x2754D6C2,0x2482776F,0xBDC504AD,0xC2D1F94D,0xD5C203CA,0x1D018E5E) + }, + { /* P{60} */ + W256(0x29ED5B5A,0x43BF7735,0x9BE0D0DA,0x5EDD0E51,0x6DA65344,0x6D426429,0xC489C0ED,0x34A7969B), + W256(0xCD5D792C,0x1643AB04,0x7627E597,0xD91D4D11,0xC4545CEA,0x42679317,0x268698CC,0x4D661E38), + W256(0xD7CA94C6,0xE6FFBA43,0x5BFFBCE7,0xFD395C38,0x26310616,0x27907028,0x96F73C17,0x5E78150A) + }, + { /* P{61} */ + W256(0xFA433F7C,0x763966FC,0x6903B07E,0xB90424C0,0xCE43E341,0x2A9F9B88,0xA8EDA21A,0x55D4C871), + W256(0x88047FFE,0x4860D96D,0x97CCFDAB,0xD5A0D550,0x768E3FDA,0x05DB5A13,0x5FF6230E,0x3F33F026), + W256(0x183AA8B6,0x48698D99,0xE3871279,0x8BEBA46A,0x7767EE98,0x40F762BC,0x6913B8DB,0x26BD664E) + }, + { /* P{62} */ + W256(0xCBF721F9,0x6E61C09C,0xD47EF6E1,0xAF380D23,0x338EAB76,0x82784AE2,0x167F4FDE,0x31816AC9), + W256(0xC6BDB362,0xDEE539F6,0x9960C22F,0x591D4144,0xA3A085C7,0xA5B1E10D,0x2FDFA6FE,0x2EF298A4), + W256(0xA6B817C5,0xEA4492C9,0x961B2033,0x72CD8C86,0x88F3A779,0x83FABA60,0xBBB5E2C7,0x66012E6A) + }, + { /* P{63} */ + W256(0x2F14C065,0x3289BB63,0x95E2F595,0xD4A81CFE,0xABF8B5B3,0xB616CBDE,0x7A49FEFF,0x5C907F1F), + W256(0x1A7C3ADE,0x17B5D117,0x8F5FE973,0xEDE22672,0x54A3C3E3,0x184C3ADF,0x642DFDCA,0x40DDEDBB), + W256(0x79BC659F,0x5F43C051,0x2EBE945F,0x5324FAE0,0x6798EB6A,0xC35FBCBE,0x2F0245E7,0x065E8BC2) + }, + { /* P{64} */ + W256(0xC80C1AC0,0xA66DCC9D,0x1B38A436,0x97A05CF4,0x95DBD7C6,0xA7EBF3BE,0x8D7E7DAB,0x7DA0B8F6), + W256(0x385675A6,0xEF782014,0xAAFDA9E8,0xA2649F30,0x5CDFA8CB,0x4CD1EB50,0x1D4DC0B3,0x46115ABA), + W256(0xC3B5DA76,0xD40F1953,0x21119E9B,0x1DAC6F73,0xFEB25960,0x03CC6021,0x83674B4B,0x5A5F887E) + }, + { /* P{65} */ + W256(0x0CA2C1F4,0x0A8D6018,0xCC68DF40,0x815EB0DB,0xB82F4E99,0xD7E67A47,0x607F15C0,0x45A02890), + W256(0xFD41F184,0xFEF366D1,0x01CFE11E,0x8B694A11,0x0150A74D,0x4B39E15E,0x6AD351BA,0x4013F03D), + W256(0x6EE065CC,0xBD0282DC,0x224AE646,0x36B994FD,0xFEBCE874,0x534E9AD8,0xD9F06E4F,0x482255C1) + }, + { /* P{66} */ + W256(0x05928A7A,0x1E6B0A56,0x25DB60C2,0x1D5B9151,0xCFC4755B,0xFA8792C0,0x55427D2E,0x2086F7FE), + W256(0x8D2CA3EA,0x0204AC4E,0xEA1927C7,0x299BD9AA,0xF325CDC0,0x0E6883EA,0x65B65B44,0x62E98840), + W256(0x77FAABDB,0x7775A789,0x6BBB5798,0x054B6258,0x9568BA16,0x210A7EBC,0xE60AB511,0x1DAD6E30) + }, + { /* P{67} */ + W256(0x628ED648,0xEF459F08,0x90E1486A,0x57A3C57B,0xEB699406,0x54EB20D2,0x7C2A796F,0x298E3AE2), + W256(0x080F1721,0xE221B34A,0xC1128E4F,0x394438A7,0x7D18ECFE,0xC370CE8B,0xBF53150B,0x55C5E6A7), + W256(0xB03C4AB0,0xFEFB6E91,0x20FEE7C4,0x54B38D6F,0x3C3D04AC,0x395A19AF,0x7A8B9170,0x79DDF3A9) + }, + { /* P{68} */ + W256(0x71CEF800,0x3C03EACF,0xCA8AFEBB,0x90367544,0x6A29C477,0x383FEA28,0xBC655462,0x4E8593B0), + W256(0xA3E5638C,0x12DE114A,0x29C4F20D,0xBA2A4AA9,0x7B8B13A3,0x56B0D29D,0x7B9B7944,0x6BB91A49), + W256(0xC5E7D206,0x2A49E646,0x9263C445,0xB13EF9CD,0xEDAB529E,0x50AB6CE8,0xB0EBE39B,0x20CF7D79) + }, + { /* P{69} */ + W256(0x8AE75C48,0xCBD28F4E,0x44000B60,0x3CDE0291,0x98BC2170,0x373BB9C8,0x9F570886,0x7C118853), + W256(0xF0FE7DCA,0x7DB4939D,0xCBA951CE,0xF50EB90F,0x357E1D1D,0x098BE61C,0x8899469D,0x02356237), + W256(0xE15A4C03,0x20F6EFFA,0x3C778E05,0x2F470A94,0xFC99DE67,0x79F50A03,0xD1061483,0x38D20188) + }, + { /* P{70} */ + W256(0x118CA612,0x05B6B816,0x2ACB38EE,0x76293423,0xD4C85D20,0x3ADDAB78,0xF26BE705,0x2872E796), + W256(0x0CE21195,0x0F2D95B4,0xE160884E,0x14029E27,0x6A05BAE9,0x3DCD49F7,0x7BEB7CA6,0x23B7DE35), + W256(0xC0C99A90,0x78D00CBA,0x964DD704,0xC48A93F4,0x61B7DA2A,0xA010301C,0x3F0DA51E,0x4E307A03) + }, + { /* P{71} */ + W256(0x7D90930B,0xE3906810,0x0926081A,0xC4D5C019,0x7EEE2811,0xEAC88198,0x7166A974,0x369E7A03), + W256(0xF32A7AA7,0x4EB04D2E,0x23486432,0xC333E926,0x5BB4BBD2,0x67F2859D,0x362DB995,0x340611CE), + W256(0x69C77C9C,0xDDE8F5CA,0x5612A945,0x9B8AE19D,0x3B0B7046,0xF989FEFB,0x2FA594D5,0x3DD66B1A) + }, + { /* P{72} */ + W256(0x1F67006F,0x4DAA10A2,0xD2CAC6BB,0x1BBD786E,0x34EF1AAA,0x5668E51B,0x84ABC92E,0x76CE2370), + W256(0x6942F699,0x775B57C5,0x62952644,0x44DD2B3C,0xD6B3AD0D,0xCCC2E090,0x929D4430,0x3448386F), + W256(0x50B99804,0x2F971DB3,0x59461C34,0x1CEE220F,0x7B5586D2,0xF0AE8F96,0xCBB727F9,0x742CA51F) + }, + { /* P{73} */ + W256(0x40A109E2,0xA97D2D27,0x0C5ADE76,0xEF72A564,0x47F89224,0xB3153E4D,0xA9C2453C,0x0757179E), + W256(0x241A5382,0x0D4300BD,0x5E919492,0xA0309E32,0x1C896343,0x8D4B6E26,0xD576B0C2,0x0EAE2D6E), + W256(0xCB862B52,0xD6DFBA4E,0x1106ACEE,0x96868E97,0x86E7A06E,0x25641606,0x9FAE1824,0x6257151A) + }, + { /* P{74} */ + W256(0x2182D28E,0x6C99D2AC,0xEFAA861B,0x047A4911,0x971AEE9D,0x10593C99,0x87CD88EB,0x17202829), + W256(0xF6293845,0xD5393FB2,0xA3CBEFD0,0xE297EEF3,0x0E6D7860,0x5054C953,0xF5DAC84D,0x4849B391), + W256(0xCB38DBA7,0xCC291020,0x7E4405F2,0xC3AF4D40,0xE9008947,0xE74B6343,0x9C6676FE,0x0AE47713) + }, + { /* P{75} */ + W256(0x808FC0C7,0xADDC19FB,0xB97F1833,0x833F2820,0xD3527F9A,0x3CAE0E4E,0x193E98CF,0x38AE17A2), + W256(0x66C546D6,0x78352D6F,0xF767B9B0,0x6ACEF560,0x7943D1B3,0x8BFCB4A4,0x7CE7A57F,0x58F6A175), + W256(0x418194A5,0x9DCC21A4,0x460E3A36,0xDC49B44E,0x197DD7E2,0x7091CA3D,0xD8E367DA,0x3BB4DFBA) + }, + { /* P{76} */ + W256(0xB44209AF,0x12C0FF19,0x0B46DC9C,0x2BC53CCE,0x19CA4BCC,0x4A6A7C71,0x98F3D04A,0x5C11757F), + W256(0x20CC12B6,0x13ED0213,0xC82AEF76,0x3ADAD589,0x02AD8434,0x8DBAC085,0xE5D56909,0x1314F390), + W256(0xEBD6C1D7,0x26145F55,0x7FE3F892,0xA18896B4,0xDD90AB3A,0xE8DE0D1F,0x2453BD12,0x3D6C810D) + }, + { /* P{77} */ + W256(0x21980C7F,0x7D53625F,0x48E60711,0xEB61D5C7,0x6CF4DA80,0x733A560C,0x1102454D,0x1CCA65EA), + W256(0x9A758D78,0x0E0C5959,0x8649A297,0x1C89BBB3,0xC29AC6E3,0xB1AB7D29,0x0475FA29,0x326436E8), + W256(0x123B09E6,0xD0F89EDB,0x9FE8D47B,0xCF0FD49D,0x8C24F02B,0x4A9D6867,0x96DF0A45,0x3968DF72) + }, + { /* P{78} */ + W256(0x2BCA971F,0xE3BC39CA,0x9BDCF7AF,0x19E76DF7,0x2A65A57C,0xB3741495,0x4E5A0FA4,0x7E6FDF27), + W256(0xD9F7828F,0x1D8E23B0,0x891FDD75,0x0DA52D29,0x20971626,0x3895641B,0x06756E8D,0x4C5B05A2), + W256(0x9A392A60,0x3D16F130,0x814139D4,0xD588033F,0x65382C75,0xAA29B40C,0xE844E19F,0x10657167) + }, + { /* P{79} */ + W256(0x93B015F5,0x45DF1875,0x41C1CAAF,0xA4B41336,0xA362D173,0x7525F9CD,0xCE1EE76C,0x096B4489), + W256(0xF22F1FDA,0x466C16C7,0x4C8A69BD,0xDED7DF05,0x50D4BE1B,0xAAA47933,0x1AA0A635,0x5419A886), + W256(0x1F50992C,0x4922CA3F,0x1C926D3E,0xECFD2A63,0x783AFB74,0x2A958B98,0x10A0A934,0x5245B5E7) + }, + { /* P{80} */ + W256(0x0E6315DF,0x23E811AD,0xE2AEB290,0x0B650D05,0xA75D586C,0xB7BA0F59,0x5E1F4DEE,0x043EEDD4), + W256(0xC7073217,0xF6C147F2,0xF3AFD20C,0xC651B919,0x7041F802,0x258FDBFD,0x4F45073E,0x173C4FA9), + W256(0x928DF9C4,0x3D71EA60,0x3373562D,0x5B7E7806,0xA29552B2,0xD9B0514C,0x993CC472,0x1E2A7024) + }, + { /* P{81} */ + W256(0xD45C811F,0x601A0FBC,0x92EC0803,0x24B7BC7D,0x17D2407F,0xA0CAE62B,0x06225B26,0x5FCB43EE), + W256(0x3509FBA4,0x310509B9,0x05631B75,0x0D8DB376,0x52401C87,0x97DECCBA,0x11B2E773,0x044649F4), + W256(0x9598215F,0x0C0D24AD,0xCC36628C,0x1B7F9026,0x7016DCEA,0x338E2F55,0x5CC0E58F,0x0C8A1BFA) + }, + { /* P{82} */ + W256(0x5A498632,0xAD0E8D3E,0x6664C2FD,0xBC5472D1,0x9B093759,0xD8856491,0x1C6A902E,0x0EC4DCE3), + W256(0xA53B3119,0x0A7FB14C,0xAE58286F,0xAFCFD591,0x380EDD5F,0x75C643F5,0xC4BB9C9B,0x76023803), + W256(0x698035B7,0x902B8965,0xD5D7E42A,0x7948C088,0xCB0EE751,0xDE7471DF,0xCC9CB0B9,0x0976A7D9) + }, + { /* P{83} */ + W256(0x8C69BF9A,0xED893067,0x395A26FA,0xF6277CB9,0xB2423BC3,0xB1B45AE5,0x73B49B63,0x38D6FEA1), + W256(0x36C79869,0x5BB02BE4,0x13405DAA,0x0FE7B602,0xD83397CF,0x78E9E6AD,0x35CE3966,0x0E146C28), + W256(0x4B0725F5,0x91A0862A,0x60233A46,0x1E299354,0x331CD827,0x770C6408,0x04FA705A,0x0C3B6E0B) + }, + { /* P{84} */ + W256(0x681D104C,0x8DE703B5,0x1263CB45,0x3D2F7A59,0x1CE56C63,0xAE710C17,0xFCC3E6CA,0x6B857C7E), + W256(0x8B2801C0,0x79D256B4,0x3C400FC4,0x7E9FBEAC,0x4733BA41,0xA751AB1D,0xDD418ACA,0x09DE2BF5), + W256(0xEFF0687F,0x3BF10FF3,0xF1E37BA2,0x5EBAEA34,0x1D66034D,0xE49E6126,0xC3B242CA,0x5B466E2A) + }, + { /* P{85} */ + W256(0x47FBB842,0x137EEB67,0x60811A8B,0x79DF5C75,0x71F8C89A,0x5A2BA76F,0x3BC8FFC2,0x09952A56), + W256(0xDC7EF83C,0xA2A8CB4B,0x5F93C226,0x96B5C6FA,0x0664E3A5,0xD4EBEB1B,0xE5C6CF2F,0x409B4ADC), + W256(0x834350C4,0x44D53DB9,0xA5F505B4,0x89299305,0x5949FF2F,0xFB22FAA2,0x04657D64,0x69B968A7) + }, + { /* P{86} */ + W256(0xF3B83F1F,0x6C9CE663,0x9CE2AE57,0xF12E893F,0x32C4C3D0,0x04EFC972,0xE69732B9,0x5F58FB4D), + W256(0x5348092F,0x04676E04,0x6FB6C492,0x0EACBD68,0xA6D70147,0x56091566,0x5ACA06DF,0x62D7BFA6), + W256(0xE2407206,0x3963D9E7,0x785795E1,0x0EEAD76F,0xA63BF4FB,0xE134918E,0xD3447FF9,0x65E052AF) + }, + { /* P{87} */ + W256(0x9A105205,0x56626C08,0x4CA91191,0x6A68847E,0x0AAC0CD7,0xACC899C9,0x2FE2A51F,0x38FFE58E), + W256(0x7D397715,0x2DA20831,0x716771BB,0xB3A08734,0xDAB3B832,0x5E5EAF8C,0x02F0FEDA,0x735FDC72), + W256(0x843BCFE6,0x95242682,0x3C2FC05F,0x6CD54F0A,0xA8EA74AA,0x10835128,0x2EE8A03C,0x019812EB) + }, + { /* P{88} */ + W256(0x43495738,0x77257B3A,0xC51D872E,0xB8EA4BE4,0x703993BF,0x415ABAF7,0x987DF031,0x5EB64903), + W256(0x187C9965,0x5780874C,0x9132CA0C,0xA1C3F5F5,0x5E6AFD30,0x5AE60162,0x39C2A855,0x74EBFE78), + W256(0x4A608A8B,0x8A1C5144,0x8857DBB1,0xEC97A2F3,0x793F5123,0xA4EA18A3,0xE1F42817,0x42E2A9EF) + }, + { /* P{89} */ + W256(0xD77E277E,0xB5CE6F2C,0x72CF0CA0,0x3277A6DA,0x4815441D,0xA39DD9BA,0xE44F7D25,0x2CAE3BAB), + W256(0x6EBA994D,0x5B6E0A46,0x39936244,0x1C1E7168,0x9456F39E,0x648B80AE,0x45E72DC7,0x37298A12), + W256(0x1B1A6A67,0x2DF48B8B,0x625B13BB,0x66613C4E,0x8CFEB944,0x613D1530,0x599E09D5,0x78BF5DC2) + }, + { /* P{90} */ + W256(0x0104B715,0x508D0974,0xF497CB8A,0x40DA1307,0xED9C492B,0x4DBFF163,0x23943739,0x4BD7AF7A), + W256(0x409D02B6,0x28D96765,0x945A433A,0x2666F83F,0xA708399D,0x4B644B7F,0x566FB51D,0x1158FBB4), + W256(0xB0CCD019,0x4350A962,0xA9863F9B,0xC3308270,0xE057038A,0xD103FBAA,0xB3015D8B,0x745E1908) + }, + { /* P{91} */ + W256(0xAE62FE0A,0x1D965A13,0x6AE6B9C0,0xE4237E73,0xA13A16EB,0xD41A5B18,0x4E6C256E,0x3421A686), + W256(0x43DDCA02,0xE07CA9F7,0x73B5945D,0x0C61C726,0x03365CA0,0xCBC82731,0x2E1B8B3F,0x33B2AA2F), + W256(0x70FCE4D1,0xA4CAC550,0x4B9B5F95,0x21409408,0x2A1D42C0,0x1F5C791E,0x4764BF69,0x1E7958CF) + }, + { /* P{92} */ + W256(0xC787A659,0xBA9F9510,0x03ED5493,0xD788B543,0xB0A5569B,0x683B5FC0,0xC0FE3929,0x797C8E25), + W256(0x1330964E,0xD80DE638,0xE21238BE,0x420FD04A,0xA052A0B1,0x07A1B3E3,0xB9F68790,0x61E8F308), + W256(0xF1396F65,0x31BD7F2B,0x2419860B,0xB111846F,0x41030B36,0x87B636D1,0xEA50D463,0x30E41FCE) + }, + { /* P{93} */ + W256(0x119B232B,0x49D07989,0x352B78B8,0x7AF92508,0x25D656C0,0x29981276,0xC20DBF1F,0x23B4E084), + W256(0xEEF5658F,0x3F963735,0xCAF6CD78,0x9EE0DEBB,0x487F1590,0xFEAD3BC0,0xA2DA3BA8,0x70330BFE), + W256(0xBB29C84B,0x68618AEE,0x44F6B1AE,0x886C8D3D,0x38AF923F,0x55D8FBB7,0x0CD689DE,0x45838D3B) + }, + { /* P{94} */ + W256(0x61295FB1,0x84AC4FE3,0x7BED5A8C,0x1CDFD675,0xA88212DB,0xE99428BC,0x34EB0AFA,0x373E0B2A), + W256(0xCBBD642C,0x6AE452DF,0xE72F33E7,0xE5426167,0xC0C30398,0x4434CD39,0x16AC6121,0x33004DAD), + W256(0x707BDEB2,0x5B436C32,0x54093925,0x73F4492A,0x34121367,0x20F34A25,0x625DA7C5,0x3C15A2BA) + }, + { /* P{95} */ + W256(0x8A61C57E,0x86A977F5,0xBF8613F0,0x0E4069D5,0xB0D80789,0xE24A7E40,0x0B4F5BF0,0x0B1CEC76), + W256(0x1DC892A0,0xA413AF86,0x1BFCB0D1,0x4CF5CB84,0xC0DC4FDC,0x25C0F20E,0x50B64C66,0x0D65FCA5), + W256(0x5B05E655,0xEB413F93,0x3EF0CC03,0xE36B4C6F,0x0DF058CD,0xB256D88E,0x13722755,0x07ADCBA1) + }, + { /* P{96} */ + W256(0x2F4951DF,0x79D7AF89,0xAFA6EB6D,0x88A06EB5,0x24C568E4,0x5831563C,0xAEB26CED,0x66707A19), + W256(0x747396E1,0x6C1310E2,0x4CEDE7CE,0x0EE40062,0x2D45E29B,0x9A3D7602,0x3F0CF648,0x204CBD04), + W256(0x379E42AE,0x2B2454E8,0xF12C7764,0x90C8758C,0x65E7C6E6,0x6C5FD2CF,0xCF80A50E,0x289E7F37) + }, + { /* P{97} */ + W256(0x4708CCA4,0xA16C8426,0xE436AA70,0xB6980348,0x4600A6E9,0x4F2683D0,0x3E6F41EB,0x47095BED), + W256(0xEA0EC072,0x9A971F3D,0xCFF14F17,0x787A1C8A,0x49AA1AFF,0xE1F6197C,0xBC0551F0,0x24705841), + W256(0x135BE2B4,0x659AEEEF,0xFB75B6DC,0x6CCDAD89,0x6B16E15C,0x014F9D4F,0x03E29B5C,0x1E7B503E) + }, + { /* P{98} */ + W256(0x3FD31B95,0xBD217B56,0x4342853B,0xFD09FB02,0x4901931A,0x52ECC0D0,0xB46FBAD3,0x531F7220), + W256(0xC00BA975,0xB0E55E3C,0xC8225FB0,0x8937C6C0,0xFD60E6FE,0x00846F52,0xBF5AB08C,0x159452D6), + W256(0x5688F03F,0xC98DE446,0x0E6107EA,0x89FF073B,0x9BF173AC,0x3648578E,0x78073A29,0x3789DF68) + }, + { /* P{99} */ + W256(0xE03E12F7,0xB11D5529,0xDDF46F16,0x75EB970F,0xF885F334,0xBE1F0518,0x68E343C7,0x2D27012B), + W256(0xA91ECFFD,0xE90965D7,0x8234D517,0x63BBDD90,0x51719D88,0x9380DF56,0xC8F44A15,0x2D096724), + W256(0xE2CDC6D4,0x7267E789,0x3672F87A,0x714F8BE2,0xEA9B45D5,0x55CD7495,0x9943EDEE,0x1F9F9744) + }, + { /* P{100} */ + W256(0x9B58521C,0x5FCB17C8,0x6645C49C,0x7DCDA917,0x5917BD77,0x3136EFEB,0x3673C3C1,0x7D5EF8A9), + W256(0x8666DD69,0x7F6D6501,0x2780C2CA,0xB013D32E,0x3789882B,0xA607D0B1,0xB5554A65,0x6E693BD4), + W256(0x513DD959,0x6337649C,0xA7717EA3,0xF3489531,0x2E946717,0x0BBB78C7,0x814BCCE5,0x3357CA52) + }, + { /* P{101} */ + W256(0x3FF58085,0x7E720B52,0xEC6A424A,0x8FB70C10,0x3FCB64A8,0x925FA8DF,0xACC6CE63,0x437E113D), + W256(0x01FA6A5D,0x88C0114B,0x19004A46,0xC168D1CB,0x2E00CBC1,0x4D148F64,0x2441151B,0x58C5D1D9), + W256(0x4F3F6B4C,0xD5A8D662,0x951AAB88,0xBCEBDD2D,0xC2603ACE,0xADFD5E27,0x5DD3E15F,0x70EA3233) + }, + { /* P{102} */ + W256(0xA9684D92,0x9514265D,0xE74D2BBD,0x55419CF8,0x2948C133,0x34451D1D,0xEB44B9AD,0x2420B420), + W256(0x7D7D252D,0x7B9F2EC1,0x9A152EE8,0x49B2EB29,0x4E9359AF,0x11A954C5,0xB369BB94,0x2F4D538E), + W256(0xE2BB55BB,0x2CA37418,0x0BF6B294,0x5819D100,0x2832F057,0x9E192E13,0x2281B8A2,0x3B99EF9D) + }, + { /* P{103} */ + W256(0x56F4B677,0x792E12E4,0x0BD62435,0xE0E7DD51,0x621ECAAC,0x53A50096,0xBF8DD44F,0x651B2093), + W256(0x5BB90B64,0x7C34EA99,0x498827DD,0xED6B5C4F,0x7E0CAC91,0xBD2C09FB,0xFC3F6C76,0x2E87C003), + W256(0xCB39F86D,0xB9A05483,0x7E208EAF,0x396CAF95,0x92D84129,0xA49F69B4,0xF0AED850,0x5BD3CFDC) + }, + { /* P{104} */ + W256(0x1106F4C8,0xA81C6089,0x48EAC4C5,0xCEA547C6,0xB65A4461,0xBABC5E19,0x914405BC,0x1EDD6BA7), + W256(0xB7A10A14,0x512E6FB2,0x91FDE918,0xE5A5ED19,0x30352098,0x9C7AE072,0xA86BCBAA,0x7480322F), + W256(0xB0B208C5,0x67D298B8,0xFC05876A,0x16C79B83,0x1B09584B,0xF2D11CE7,0x6D0F2822,0x71867995) + }, + { /* P{105} */ + W256(0x78A0ED66,0x5BF82D60,0x9B62E796,0x897F84EC,0x351EBDDB,0xC93D0B6F,0x978DC6A1,0x2A530E07), + W256(0xCC5B4C55,0x25DF69AF,0x4B1E7B43,0xD299FDAC,0xDC8026EB,0xBCB7E7F4,0xF671A838,0x0F51FEE0), + W256(0xE051A08A,0xE3ABC271,0x43549C50,0x5E9C303C,0x6E751049,0x971EE5C9,0x0990FDDE,0x6AF8CE48) + }, + { /* P{106} */ + W256(0x85072941,0x0D2EC63E,0x7629E4DC,0xEF34D76F,0x3EEBA243,0x4D299FAD,0x1D0E272F,0x1C0C8BA8), + W256(0x7DE8947F,0x49AC8B60,0xE6B60D1A,0xA598DABF,0x262BF2C7,0x7FCD7397,0x8B05C98E,0x01417670), + W256(0x8786E66E,0x39098578,0xA155B485,0xB2ABCBFD,0x9C793F7E,0x1ABBEE0E,0x969224B2,0x493EDFAA) + }, + { /* P{107} */ + W256(0x7AD767F2,0x249E0A04,0xA3912F40,0x1311C2F6,0x3F0934DE,0x897EDF4F,0x1F5F9664,0x0109D1CC), + W256(0x36FD7583,0xC6DB180F,0x6A3F0D70,0xD9504D7C,0x6B2DC20E,0xE317B1C2,0x1E0C2804,0x0331DC46), + W256(0xCF2BAB83,0x213C7DA0,0x56F00288,0x72C760FD,0x044FF105,0x4E13D9F2,0x59EA9372,0x54C1C1F1) + }, + { /* P{108} */ + W256(0xCCFF195A,0xD9439624,0x04AA2DC2,0xA69A217B,0x77CDEBAA,0x7B581381,0x6C36F14D,0x727062C3), + W256(0xF156D356,0xE38363B2,0x1A7A6C40,0xE2FCA0EC,0x336D9CDD,0xF3236173,0x54708983,0x50F85285), + W256(0xF65BE459,0xF7067E62,0xFBB87C7C,0xE22AB98A,0x8127A332,0xCD2ABF42,0x41243874,0x474F259F) + }, + { /* P{109} */ + W256(0xB082F07E,0xBC2D1E80,0xA25756BF,0x368CEA6E,0x7AF12C86,0x53A67CB5,0xB385EF92,0x17186EE8), + W256(0x7895403B,0x3A022D64,0x3EAFD6D5,0xDF7178FB,0xC50D54FC,0x1BCFC70A,0x4569F3B1,0x36E9B22D), + W256(0xEE005D40,0xBB82CDB2,0x01DB4779,0xB99D5B39,0xAEF8C235,0x291F080B,0x4E3B4D18,0x5326C57B) + }, + { /* P{110} */ + W256(0xF01D5B98,0x0BFA2499,0x8EC91D4E,0xDDB2533F,0x1BBF5701,0x7168D7E7,0xC9FAFE4A,0x7D9E5AF9), + W256(0x7310593B,0x7AB3AB44,0xAE863030,0xAFC3FA66,0xDBD73272,0x45E5DDFB,0xC668CCEC,0x65401219), + W256(0x9B773F39,0xA57C099F,0x7940844F,0xF8F0223B,0x83AA97C0,0xEA7C8A46,0x50335D21,0x6860DDE4) + }, + { /* P{111} */ + W256(0x8A54D845,0x3F2358CD,0x2203028A,0x5E1CBB1B,0x11FD3994,0x63F1D822,0x4308D8AB,0x37401E65), + W256(0x07B41D27,0x1E9ADCE4,0x880A16DA,0x343AE381,0x4FC4494A,0x49B82BE4,0x2726AEC1,0x6FD66D34), + W256(0x7CFE6583,0xECA26010,0x63921D49,0xF981D584,0xBB516BF2,0x1B69D8D3,0xA669E323,0x461C28C6) + }, + { /* P{112} */ + W256(0x01C923E9,0xCD69DB0A,0xB2EC7A51,0xC095FCCB,0x15518043,0x6BD7C778,0xB6BDE853,0x0DA33C93), + W256(0x349903CD,0xF3BFFB0A,0x664C68F5,0x0362F6BD,0x7D7ACE8E,0xB75C72E1,0xC26DE303,0x12813206), + W256(0x4D21FB37,0x8E82EB86,0xD706FCA0,0x784E6AA7,0x7986F343,0xCE5C5019,0x86222C9A,0x749A29CB) + }, + { /* P{113} */ + W256(0xF14EE1F4,0x5698F7BE,0xA93C6B27,0xAF410069,0x2F72E02D,0xC3B2308B,0x0697A906,0x5853C9F3), + W256(0x23578DAE,0x2E289B15,0x71F6E01F,0xF7D9EF78,0x663AA29A,0x8D660848,0x9DC8931C,0x0FFAD667), + W256(0x93B961F1,0x0DA98E06,0x4EAB4629,0x12244EA7,0x4B3814DE,0xAA09AC0D,0x160E7061,0x10FB7F2F) + }, + { /* P{114} */ + W256(0xD952ABD2,0x759DA5DF,0xCB26C4FD,0x78203568,0x8637EFF6,0xFB64F1C6,0x084E0E5D,0x5EFBE968), + W256(0x96B7C16F,0xA9527912,0x63B993F8,0x742EA244,0x61EAE9C1,0x7235F335,0x7585A4AC,0x2E5EAC3B), + W256(0x91A749C5,0x1B726B38,0xDAB40E67,0xB5586AA3,0x580CC602,0x88099153,0xC51BEF09,0x3CC1AFA4) + }, + { /* P{115} */ + W256(0xACD6C32E,0x6F976A4A,0xCCF43AC4,0x305329E5,0x086F0CA4,0xFE7D4816,0xE2E9B1B5,0x007CF519), + W256(0x68F99EF6,0x3CE79C3E,0xD9E039D6,0x325B0AA5,0xFE49C5B4,0x82CF5B6B,0x1BC2886B,0x056E559E), + W256(0xE4FDF1EE,0x5A7DF532,0xFF4DA4C2,0x36F0DCBB,0x1F12517B,0x61CB3FAE,0xDEB2ECC2,0x5590F3AC) + }, + { /* P{116} */ + W256(0x806F5FDC,0x4172840A,0xE1C0A075,0x80CF2E21,0x491792D5,0x084594C3,0xF2D73480,0x3563A178), + W256(0x9452D82B,0x7CBD6B00,0xFD61AFF5,0x38C36968,0xDCC1B605,0xF1E876AA,0xEF44FF57,0x2BE54637), + W256(0xD94DF57A,0x2521D70F,0x38867486,0xC1166884,0xFE418AF3,0x35555E4C,0xD2B4FE50,0x5E7E4DDB) + }, + { /* P{117} */ + W256(0x42A5E164,0x863C1237,0x40FE626B,0xF8044410,0x5EE943C0,0x9EACA686,0x90EAA131,0x440BE3EC), + W256(0xCF866787,0x5913D7BF,0x5537B933,0x94DFD2A4,0xFE16E927,0x26E14EDC,0xC1E88451,0x15024E35), + W256(0xC56B7601,0x6E258350,0x6AA0350A,0x01142CEB,0x704EBD76,0x3FC64777,0x6C64969A,0x212D6D6F) + }, + { /* P{118} */ + W256(0x2270F29E,0x3F6A655C,0x77AB8171,0xAE2799FC,0x9F8A1E3A,0xFD162B36,0x90A5CE4A,0x47ECD96A), + W256(0x7409DC66,0x77A8BECC,0x9D3ED1D2,0x431DDD34,0x86E266F8,0xC62942FA,0xFF103BC2,0x46E5D5CE), + W256(0xA228B0B5,0xEF425960,0xFCC50DA6,0xBA65476D,0x3CB98B55,0x41739C39,0x2154E94B,0x0CAD13FA) + }, + { /* P{119} */ + W256(0x36820D13,0x9EE97BE6,0x70164070,0x2C08B8FF,0xFA54210F,0x6DE8F2E3,0xB360F28B,0x79107CEA), + W256(0x782D32C8,0x4C23EF35,0x4A46B57A,0x9767DDA2,0xD4EBA29E,0x72BD6514,0xD2EAC7E9,0x299A9AAD), + W256(0x86F7386D,0xD01AE2D3,0x219BB9EA,0xFFD4B450,0xA8774383,0x702D677D,0x5329101B,0x6FF36351) + }, + { /* P{120} */ + W256(0x7B3587A1,0x0E0C4745,0x91769720,0xC8421582,0xFE5627C2,0x91003F56,0xE85CA61C,0x6A2AEC01), + W256(0xA1A52002,0xFDFD082D,0xEAC7EA40,0x4CCD6F41,0x130B9C5A,0x22C95FC6,0x1FFB2792,0x768AC9E5), + W256(0x0063C3C8,0xEDB5A4A2,0xB73FD668,0xDB4937BD,0x22D13CF7,0xE2F7B924,0x503454EC,0x1778DB95) + }, + { /* P{121} */ + W256(0x7BD82D09,0x0F46D1AB,0x9E94D4AF,0x1A9308E3,0x09848426,0x589E4FED,0x750656D6,0x24B29DA0), + W256(0x6D03FFE7,0x9191F66C,0xB277C2B7,0x526D9AD8,0x0CA63F40,0x9198592E,0x70681952,0x5A850587), + W256(0x682446B9,0x4543AA9D,0xD95EE3DA,0xA6873BD0,0x05E3DA81,0x1352667C,0x17331E0E,0x728A4838) + }, + { /* P{122} */ + W256(0xD60A2084,0x77DDD129,0x8E3A7568,0x3BC7ADEE,0x8ED6ECD9,0x3D228C35,0x49D97F1D,0x54B5C9A9), + W256(0x5108C512,0xE8967BFD,0x2E2A9B05,0x74FB7C21,0x3A686776,0x7C9A0D25,0x0F17BF89,0x7DE8499E), + W256(0xE7EA37A3,0x9FE207C4,0x962C41D7,0x7F881301,0x582DF624,0x0DB13977,0x3C04302F,0x37BAA92E) + }, + { /* P{123} */ + W256(0xD6BB0652,0x6EB6315A,0xBB775227,0x94458890,0x239C7BDC,0x98FC0A5B,0xD4CCAB9A,0x599BAA3E), + W256(0x8ADCDB1E,0x266B206C,0xFCAA28D2,0x68C72F11,0x8C0AE8B4,0x16EB534E,0x9F0F7437,0x5C3DE1C0), + W256(0x33666249,0xDD43E25A,0x8415DE01,0xA4F8DD6C,0xABCC2DAF,0x5503235F,0xB80B04F8,0x2E43C2F7) + }, + { /* P{124} */ + W256(0x140E43E3,0xA3E3B738,0xC8F5A0DB,0x51847226,0xF9CA4102,0x3AB9407D,0x83005256,0x6DB8BB5B), + W256(0x2B9A38DC,0x1D5544CA,0xF0EC30C3,0x24B58309,0xF41BAB99,0xB8882607,0x48A09817,0x11DBAF2A), + W256(0x39E7695C,0x1538256F,0xB7290D01,0xE7E39640,0xD2582546,0xAA24A1EA,0x76A6AB17,0x1B0C351B) + }, + { /* P{125} */ + W256(0xB78294AD,0x7550EC45,0xFFAE747F,0xEE25B2B4,0xC8BD244E,0x65613F77,0xFF601899,0x544F0F82), + W256(0x0C0270A9,0xC68B1D07,0x986EA7DE,0x6681AE6C,0x5D0C0161,0xE8DCABBD,0x2E487553,0x5AA30E06), + W256(0x2D9B8E57,0x8E41DA8D,0x0728E94D,0x67A7574C,0xB69717B4,0x1B6310D8,0x18E8E439,0x338CC7BE) + }, + { /* P{126} */ + W256(0xDD00B10A,0x7EF87E29,0xA69E8401,0x46D7F7FF,0x5576BE17,0x10A99A87,0xF16E56B4,0x2BEABC55), + W256(0x6449EE75,0x868F3F7F,0x55B7A8AF,0xCD769000,0x8DB11E10,0x4ABCCA72,0x0AC3050C,0x2D193F9F), + W256(0x22824C89,0x7C5CCF47,0x07BAF05F,0xAA79B001,0x79F0E84C,0x05E488EE,0x62C70EDB,0x59CDA4E5) + }, + { /* P{127} */ + W256(0x33DA2FDE,0x404B413E,0xF66C3990,0xC3F5A183,0x6DFCF1A7,0xE33F99A0,0x22CC7500,0x541D736F), + W256(0x26BD487A,0xC8F25650,0xB1851D00,0xA80AC2EF,0x739ADCA1,0xE30BEA78,0xC6D25F17,0x13F5094D), + W256(0x4B9FF45F,0x67C7D337,0x5B287BB4,0xC49E7A0F,0x8E115FA2,0xB6F2A758,0xDA626D0A,0x66E18672) + }, + { /* P{128} */ + W256(0x193B877F,0xBB2E00C9,0xE0DC506B,0xECE3A890,0x36DE649F,0xECF3B7C0,0x98DE9E1A,0x5F460408), + W256(0x832FCEDB,0x739D8845,0xAE6BF863,0xFA38D6C9,0xB74FFEF7,0x32BC0DCA,0x14BCE45E,0x73937E88), + W256(0x297BF48D,0xB9037116,0xD4F06834,0xA9D13B22,0x4696BDC6,0xE1971557,0x91D5E835,0x2CF8A4E8) + }, + { /* P{129} */ + W256(0x955EC3D0,0x226343E5,0x9D175A1B,0x7EFC48D8,0xA18B1A3C,0x98385297,0x636C4322,0x5492BEA1), + W256(0x0D0EDBA7,0x6224329D,0x8E04A966,0x46778080,0x618D99F6,0xC62CE016,0x408F8D31,0x2F810078), + W256(0x9065F588,0xE9D5690A,0x35160408,0x68C463A0,0x9E8BAA87,0x2DB1CABB,0xFB39AE79,0x2E19E362) + }, + { /* P{130} */ + W256(0x40B81EDC,0xB131A877,0xC50A6E37,0x2529CCE1,0x3D86DF40,0xF87178C4,0x72690814,0x3AA294B9), + W256(0x083AC1C0,0x31746C23,0x1C3DB42F,0x3DAC7B51,0x228A9F80,0xD4F12B16,0xB1806C0A,0x40730AB9), + W256(0x0552A87D,0x152402F2,0x46352008,0xE69F2C75,0x3DA75873,0x09D51A51,0x428677AB,0x4E71CD08) + }, + { /* P{131} */ + W256(0xCD63A5CF,0x34ED76D0,0x1F9660CF,0x74DFC195,0xE1127868,0x54F23FF7,0x52D2D691,0x09EF2D3F), + W256(0x00004A14,0x9473F1CD,0x2D3E101F,0x7ACDB1FD,0x3F14D3B1,0xBADF67DA,0xDD5FA9B5,0x720361D4), + W256(0x07B12573,0x75E9D3EF,0xD4A699B6,0x07776372,0xB4D16DDD,0x25745C88,0xE7729DB5,0x2C42D3A4) + }, + { /* P{132} */ + W256(0x87A8ED1B,0xFB4FB8BF,0x3562C0C1,0xE61DFF95,0x2EB7C3F6,0x7E0099FB,0x8338B347,0x4BFD51DB), + W256(0xD66DABA4,0x2CB2FE4F,0x89AA7F00,0x10A18070,0x714AED31,0xB4F37238,0x4E9867F4,0x205B2E2C), + W256(0xBDB17451,0x3FD78085,0xEF06458D,0xF4AEAAA7,0x8C5836F4,0xC871854B,0x1115B50D,0x0FFD138D) + }, + { /* P{133} */ + W256(0x0AF1F0D8,0x9DF50673,0xB11F9B70,0x75535041,0x99EC8B4D,0xE8C33680,0x81CA5E23,0x250AD1F2), + W256(0x3E11BE9A,0x0580F7EC,0xC90A1D21,0x0973D59F,0x33E48155,0x9F32C03C,0x27B19D76,0x4AACA156), + W256(0x1577E699,0x03D16C21,0x8E5F2C2C,0x89A46964,0xC56666A5,0x86374D4C,0x46BB7004,0x741323B8) + }, + { /* P{134} */ + W256(0xA9B25300,0xEC4F3DED,0xD085E701,0xCBBABA10,0xA1518C1F,0xC24F4DAE,0xFA6EB46D,0x4DBE8C6F), + W256(0x04A28695,0x4091C103,0xB40DB506,0xEBA17E70,0xDD1102CC,0xF5BC279C,0x23C81CCC,0x16643587), + W256(0x43273AD2,0xA24E5171,0x44A356EF,0x60F47882,0xBB30B209,0x1E1754B3,0x82488C52,0x2DE4A5E2) + }, + { /* P{135} */ + W256(0xC7F31CE4,0x2FF8BA76,0x73599089,0x71CCB658,0x4B8FF7E3,0x5B79D720,0xD7E029FC,0x201E2DA6), + W256(0x9958D609,0xBBF5BBF8,0xE1A1359D,0x30981D35,0xB78D1571,0xE326CC62,0x4BB2DE35,0x154C89FC), + W256(0xA485927F,0x950868CB,0x99309DBC,0x5A65136C,0x69C7A60E,0x2EA9E9F2,0x7431685E,0x18F69D0C) + }, + { /* P{136} */ + W256(0xD78789AC,0x084ABC56,0x4C34A837,0x5659A950,0xC8E32D7B,0xFCBC29CC,0x13A35552,0x490BAD92), + W256(0xE038D4E9,0x9B3F9942,0x6CD96AA5,0xEDA655EC,0xF0B1D81F,0xB2A94883,0x3A65A94A,0x38639CCF), + W256(0xA7817C8B,0x2AF87C80,0xCD31C019,0x3290308B,0xA7BE274B,0xC389CFAE,0x10BFE889,0x6A125AEB) + }, + { /* P{137} */ + W256(0x3D24E162,0xE20267B6,0x68A6D572,0xCA586924,0x03ADC37A,0xD498C9E7,0x9BD5EB1B,0x202D4526), + W256(0x94AAEA83,0x67FF3389,0xDF086F32,0xBC661418,0x8278692B,0x4F6E34A2,0x171D88ED,0x3405D3A9), + W256(0xF3D71F71,0x0349F89C,0xD391896E,0x6C59EC91,0x60E3B489,0xC601B735,0xD16A68C3,0x37EEFB40) + }, + { /* P{138} */ + W256(0x3BA3C672,0xF56E4142,0xE9368956,0x573E3974,0xE7A5B7B3,0xD6A2DF8E,0x4A134A97,0x3147F866), + W256(0x8C5FB918,0x961CD052,0x645EBC80,0x8120A7A9,0x0F1E6013,0xD448452A,0x06AA1A4C,0x05D53ACA), + W256(0xDAED85E1,0xC6C34026,0xB2D4B1E0,0x3CFB0BD6,0x370B87CB,0x0F12416C,0xAA945A13,0x0224A2EB) + }, + { /* P{139} */ + W256(0xB7294205,0xE526547C,0x78F2614F,0x0B8E75ED,0xF001D89A,0xD06A714B,0x4EFA41C1,0x6AF7C939), + W256(0xFA893CC6,0x67FABBAE,0x849AA0E9,0x6701DB39,0x8BC4C43A,0x01718CF6,0xDC8B76F5,0x0E5F734B), + W256(0x3CB66A17,0x0540E82D,0x8D07D837,0xD8D957EF,0xD8C809E2,0x9BBD0223,0xC06C287E,0x57D04A0A) + }, + { /* P{140} */ + W256(0x7E348382,0x305C39E3,0x151F8FF5,0xA6961D62,0x3E75B0C5,0x9B3BA96E,0x41A21DA3,0x1185B2C0), + W256(0x080A91B1,0xE20AC913,0x7F570A48,0x1F5176F9,0xADD984B8,0x5ED8EA61,0x0FBFCFBB,0x79613D64), + W256(0x2A195A23,0x7211BDE4,0x61B1B2BD,0xD6B973D1,0xED88D400,0xBA6EACDC,0xF6CB238D,0x04C90947) + }, + { /* P{141} */ + W256(0x829F065E,0x7F39CA1A,0xB1AED2EC,0xBE497D51,0x44CF5F3F,0x7CD059B8,0x3DF51B03,0x69BCCD33), + W256(0x9BF83FF4,0x210A1FB2,0x0A04DA21,0x8A6A0F76,0x5F656141,0xF0C749BF,0xEB8BC7D9,0x00439D12), + W256(0x3D9E0C3A,0xAE9A651B,0x6D13FD33,0x0AB0D68D,0x035AB3A2,0xFE6951A3,0xC39650A1,0x29A6A9CD) + }, + { /* P{142} */ + W256(0x7B6B016F,0x39FD96E5,0x078B082F,0x5ABF92ED,0xFF012931,0xC905F0E6,0xEE141164,0x762D88A2), + W256(0xC8D474CC,0x2A6C73C8,0x4240C83C,0xBDD88157,0xBFB840CA,0x6482D3D9,0x577FA278,0x42E006C7), + W256(0x7B427448,0x2BC628BB,0x2E34AD6D,0xC30A541B,0xD6008DFD,0xBF692762,0xB43988BF,0x7BE4CBA5) + }, + { /* P{143} */ + W256(0xD185CAE3,0x0610B7C9,0x393E1EEA,0x22BCF7F5,0x50B5A498,0x078F20B6,0x75DABCA8,0x528D0C4E), + W256(0x7B7D5D33,0x1CBB13CC,0x897F473A,0x48C2338C,0x38DAB8C7,0x92D5A13D,0x92780AAA,0x73B88C74), + W256(0x80E27FB5,0x0F091F80,0xB49773AA,0xD839AA99,0xE18637C9,0xD83CCE6A,0x9C92E15B,0x03946587) + }, + { /* P{144} */ + W256(0xFDCDBA71,0x089F56A4,0x923939BE,0xDD032610,0xCD3FECDE,0x2FFD1803,0x2852B233,0x3508C355), + W256(0x0C6804C2,0x976AA014,0x04B12D0A,0x37C40803,0x1F0473FF,0x10287E28,0xDC487F40,0x3D2910DD), + W256(0x8C0E2780,0xE0156E4E,0x147B72AD,0x96963476,0x15F7D223,0x35D6E9BE,0x732F5BBC,0x7A8CE344) + }, + { /* P{145} */ + W256(0x9510C49A,0x49192B17,0x463D436B,0x8375825D,0x0BF30FBA,0x6F749404,0xFB3957BA,0x32A3650E), + W256(0x686D3B9F,0x875624D2,0xD010EC63,0x7D2508CC,0x0C70A6E4,0xE63EBB26,0x1AE53B8A,0x5A031778), + W256(0xD70C0232,0xE15D831C,0xFEF230E6,0x21810B5E,0x443CEC71,0x062533B6,0x3ED57767,0x7950DF2A) + }, + { /* P{146} */ + W256(0x237D9833,0xE6180436,0xEB38F533,0x525696EF,0x5C27BAC7,0x09B10FCD,0x3BA1B69D,0x608EA1CF), + W256(0xE717A6E0,0xA056AC6F,0x7B5F666D,0x00A1B311,0x79035A10,0x5E3C4492,0xFAF62B07,0x72C0E858), + W256(0xD207E130,0x07D035BB,0x50117EDE,0xAF7053B5,0x912C5C81,0xFEEC01C7,0x886E8D51,0x1A1FAC04) + }, + { /* P{147} */ + W256(0xF01E858E,0x1912EE7C,0x9ACD0FA7,0x592C8BCC,0x8450BDCF,0x753F805D,0x70AF3575,0x71BED116), + W256(0xC2B406D2,0x01184DF2,0x6F206147,0xF76B3C56,0xA3822F1E,0x57139091,0x793C5D31,0x5CC2B0D9), + W256(0xD85FD4B2,0x8F19FBD2,0xD4D2CE75,0x59F1A77F,0xC51EF80B,0x85A6259E,0x0C738FD2,0x7E12A2DE) + }, + { /* P{148} */ + W256(0xD34F712F,0x559AD49C,0xD8FB35E5,0x4EA4D1EF,0xF723BAAC,0x5909AD24,0xC4704F23,0x3183C84A), + W256(0xFE1998A0,0x4EC3D6C0,0x7FC3BCCF,0xB9BE0FED,0xC92A1F39,0x6F7CC86F,0xFBA3B48A,0x0A10C4DF), + W256(0x45995411,0x31FD07B5,0xC069D22D,0xC9FABF61,0xE0252BEB,0x766B85BB,0x2188A218,0x10E588A7) + }, + { /* P{149} */ + W256(0x82FFEF83,0x74D498CF,0xFECD2263,0xECE755A2,0xF493EF3E,0xC281538A,0xF2303FB5,0x54D171EB), + W256(0x563DD509,0xA63C9137,0x15C99B21,0xF7EFAE07,0xA6D3FC6D,0x64780364,0x51D6089F,0x1F331518), + W256(0x25C78ADD,0xB871CEB1,0x3C04C296,0xC0D7481E,0x3A0F2833,0x7DE88611,0x5B7672F8,0x6E1CF0A4) + }, + { /* P{150} */ + W256(0xBA0B03CC,0xE78948AA,0x58B6B3F2,0x46A3C561,0x1BE481F6,0x0E964066,0x8C25C3EF,0x3664E01C), + W256(0xA0A886B6,0x198599BB,0x876645F2,0xB6BD8FE7,0x8B7591D0,0x2417725C,0xEE1935D1,0x3A08AAE7), + W256(0x1534AF52,0xDC1CBF91,0xF7AF099B,0xF4FC0066,0x88137747,0x97397E9A,0xE80E74F4,0x349B9669) + }, + { /* P{151} */ + W256(0xEF471FFB,0xDD240E0A,0xA7856EF8,0x57702113,0xD957EF9C,0xB8E69C9F,0xBD3C4500,0x79ADD1B2), + W256(0x80847F79,0xD90137DD,0xBAF8BFDA,0x71D9AFFE,0x4F245BCD,0x8B749F1E,0xB2AE8BF6,0x642F1CF2), + W256(0x706C0E4C,0x9744E5E5,0x24FE9139,0xB0E8006D,0xC7E8AD1C,0xA69BAED0,0xD6FFB0D6,0x4CCB9693) + }, + { /* P{152} */ + W256(0x245D71BB,0x7E2177F1,0xE461CD75,0x4BED3A32,0xC81E1C67,0x48EFF412,0xBAF0A0E1,0x0759D1EA), + W256(0xAC5573A1,0x694F0A87,0x8D490435,0x86F3EDAC,0x69CC424F,0x8D05AA8C,0xD3C5308B,0x3285F2CD), + W256(0xCB0CA64E,0x615777A6,0xF5A307B6,0x4B2BCC26,0x819E835F,0x7E2A4B4E,0xD949088F,0x60A0D80B) + }, + { /* P{153} */ + W256(0x40DAAB62,0x033E6D42,0xD01BE421,0x07C1B46C,0xAD6293B4,0x826FF014,0x65A1773A,0x7D7BB82E), + W256(0x510D12E8,0xBE064448,0x9C8CBA8A,0x76AE88E1,0x591BE232,0x14357377,0x1B5D7B44,0x28A272D7), + W256(0x869E6F11,0xFE3AB0D4,0xFA8B0BFD,0xA5125FFB,0x453B4759,0x6B7075C9,0x2F32F6A8,0x0F03FF74) + }, + { /* P{154} */ + W256(0x3C94139B,0x97949B17,0x077870C7,0x24983AD3,0xA52B4D16,0xF170D9E9,0xD359BD78,0x1427C7B4), + W256(0x9DE5470B,0xC227D0A3,0x8F7B3712,0x0DBB9165,0x2C7FEFF1,0xAF2CE780,0xAE5534FF,0x60DF4EA5), + W256(0x98CE5195,0xD1536153,0x6A72C08F,0xA48EBEF1,0x39CF6449,0x4F621910,0x0A9FBF8A,0x0284779D) + }, + { /* P{155} */ + W256(0x0497BEDF,0x451E38F3,0x895E5DE6,0x24CF56E1,0x92B28E61,0xC2B0A79B,0x44ED80DE,0x47360AEE), + W256(0xD88C8292,0x04DEFEA1,0xB3CDD5DE,0x839768C3,0x52DAC506,0xE6ADBB65,0x7EAC3D52,0x1B7C8B7E), + W256(0x9E1212B4,0xB1A2C143,0x19527854,0x2A2101C1,0x604E4C45,0x2AAC510B,0xFE1736A5,0x61A9CC16) + }, + { /* P{156} */ + W256(0xB14F5809,0x79FD59E3,0xD56A8126,0x2267267F,0x2BBB00CB,0xEF28666F,0x70348032,0x04E64717), + W256(0xC1977F4F,0xE8E260C9,0x29041772,0xA0286C18,0xCB1F6094,0x38E868BB,0xA701F2D2,0x5EC36955), + W256(0xAEAE57D6,0x0F66A28B,0xCEF451D2,0x03629DE7,0x36D14CDA,0x261B2919,0xDC7D2447,0x61079D97) + }, + { /* P{157} */ + W256(0x9B8F0086,0xC2B8F399,0x9868469C,0x410ACE9F,0x0A12C56E,0xCD834C27,0x5D423DD2,0x004D7BA3), + W256(0x45813608,0x2A6177D8,0xE38DFFEF,0x09B400F5,0x805633D9,0x1754A95F,0x57E41E84,0x514113B9), + W256(0xDD05FA4E,0xDDAF0EA9,0xB53379A2,0xFC381A60,0xC2D4C1C4,0x2D0F18DF,0xE1897BF5,0x271FA439) + }, + { /* P{158} */ + W256(0xDCEBE6AD,0xE252270F,0x049EFC21,0x1E1D8802,0x8A4EAFC2,0xAEFDDEC9,0x0119A165,0x2F35657F), + W256(0xB3FFCA5D,0x7833FDA9,0x3C5CD47B,0xCD8F493A,0x18647670,0x3A68F5F4,0xD69044AA,0x57DEA19A), + W256(0x967ED429,0x4D255CED,0x822A1E3A,0x50651B4C,0xA0F56F58,0x9A93F1C0,0x2A033C0F,0x26DA368D) + }, + { /* P{159} */ + W256(0x4ED2FCBF,0x6416F27A,0x3A355DDE,0xE2B4CC90,0x6F7CBC57,0xD110663F,0x41497CCB,0x46C5572B), + W256(0xA7B1D798,0x17E813D3,0x6B76BCC6,0xF9A26A8F,0x31E5CA85,0x124C1BB7,0x8BBAEBD3,0x71B4D300), + W256(0x83EDE334,0x1FD1C0AA,0x85CB7B8C,0xFA257C09,0x96F02600,0x13CCC95B,0xC80C67B0,0x5EE86AEC) + }, + { /* P{160} */ + W256(0xF3FDDFE3,0x77102749,0x79BEAC52,0x093B1317,0xBABA54C7,0x2C1A5CC1,0x82ED20F9,0x7CC2EFFD), + W256(0x627C7B1C,0xFE5E30FE,0x8D82FC2F,0x7D6DE30D,0x1C1BF394,0x6B7981B1,0x955690EF,0x6EF79537), + W256(0x38AC1D9D,0x317BD4C9,0xD43A48C8,0x120B22AB,0x25BA47C7,0xD373691A,0xDDE79E2D,0x444F44EB) + }, + { /* P{161} */ + W256(0xD05F2D3C,0x7CA77213,0x0C380077,0x8A61A13F,0xC2C06C3B,0x3B6956CA,0xAAB52726,0x21EE01F2), + W256(0xB1F4422D,0x7AA57251,0x2356B185,0x210F8984,0x093E1544,0x8DB759F1,0x6CCB7827,0x0E520424), + W256(0x7210D454,0xE99CCC6C,0x8DBED73A,0x9278B219,0xC984A72B,0xC404D1C3,0x32746A36,0x14AFA6B5) + }, + { /* P{162} */ + W256(0x2BF4376A,0x6FF950EA,0x5B86DB4A,0x970D30F6,0xD2EE3EB1,0xFD04F109,0xB4B8924C,0x23654DED), + W256(0xAA496A92,0xBB24631D,0x560F5B8E,0x9A4DCCAA,0xFAD2D247,0x964C615B,0x65681C6A,0x0CF07C2D), + W256(0xD9512BC6,0x6B1E6C89,0xC7992AE0,0xFF307335,0x78620616,0x1E41B439,0x73477C30,0x5F5527F0) + }, + { /* P{163} */ + W256(0x366F2064,0x0F59397B,0x00FFBDFD,0xCA579E04,0x6DBD3E97,0x4627C074,0xEB010236,0x101AE639), + W256(0xDF923A45,0x72743E1A,0x1B73554C,0xA0BD821A,0x5CB5B0A0,0xA643C963,0x01173DD7,0x48EA9EAF), + W256(0x1472D9E0,0x42BF06ED,0x572013EF,0x6C5B1064,0x28F3A65C,0x71536C86,0x95C00785,0x0AEC3037) + }, + { /* P{164} */ + W256(0x843606BB,0x6CBCDDEC,0x1B031E0A,0x47208C7A,0x73C8628E,0x804710D3,0x8C6389BB,0x24767BCE), + W256(0x4DEAB9F3,0x5921911D,0xD2EE2C08,0xB29306F0,0x36213424,0xEBE05D52,0x487FFF46,0x6FC95025), + W256(0x52E0D95A,0xEAE74B37,0x01319900,0x88FC646D,0x6D6482B1,0xA4648C1C,0xC69F70C4,0x185EF4B1) + }, + { /* P{165} */ + W256(0x626330A6,0x07BB7B82,0x102512BE,0x290663CB,0x69FCC45D,0x35126923,0x70F829AA,0x6360F825), + W256(0x673DEA49,0xDA1C29B7,0x7ACDB58D,0x5C791FA2,0x691CD92D,0x5E538AAC,0x986252B8,0x19D69F88), + W256(0xADFC8D6D,0x82021D93,0x784C8063,0x9DD0FDB3,0xE64ADCCE,0x383F70A2,0x4333E288,0x5F391338) + }, + { /* P{166} */ + W256(0x9AD6DAD0,0x5AF51908,0x3F313DAA,0x3F696E0B,0x87370A41,0xC73864CF,0x0EF8379F,0x729D8F12), + W256(0x3C528CB3,0xFC02938C,0x139EAC04,0x8A0C04B4,0x6303418A,0xD580E5FB,0xAC77136C,0x58A3EF09), + W256(0x25CF504B,0xB13FA0DF,0x5187842B,0x787038F0,0xF3929B12,0xFC5E7DDF,0xCD706AF1,0x4D698742) + }, + { /* P{167} */ + W256(0xDBE7C0FB,0xF08F2F01,0x6B294170,0xEAB65C3E,0xF7D940DC,0x761EF854,0xAD5649AB,0x0ED7AD78), + W256(0xA94FABD9,0x3C18C4F5,0x5D4DECBD,0x09A0B15C,0x767627D1,0xD93CEF9B,0x7DFA8237,0x46B61CF5), + W256(0x12288FD0,0x4324E59D,0x5329A7B3,0x2CB47445,0xD9E94476,0x3B3AD463,0xA3243D8A,0x47BE3633) + }, + { /* P{168} */ + W256(0x6BD68F25,0xA3819393,0xFC692AA7,0x921A6473,0x102C552B,0xFBAAB570,0x0DA1E96D,0x22260E70), + W256(0xDBC441A8,0xD2DD61E7,0x3004A0F1,0xF5A113DB,0xF70504CE,0x49F05E44,0x3B07B9A0,0x2A59CD57), + W256(0xE116AB23,0x7D785536,0xE3FEE820,0x2D2FFA28,0xC62F75CE,0xF8D8FFF8,0x15C34F43,0x7626171B) + }, + { /* P{169} */ + W256(0xC7F57505,0xD6887915,0x22D43E1A,0x03F31B61,0x4C4C6736,0xEBA0ACCD,0x839A3696,0x028D5C21), + W256(0x1AA0566B,0x4C2B8A63,0x7DF7C625,0xCEEB045F,0x2BDA1196,0x5A5C972E,0x631D1AD0,0x6B8B1FB5), + W256(0x2D0CD5B7,0x855229F0,0xC0948473,0x5965AA97,0x7766CAFF,0xD7B3004F,0xFCBCC3A6,0x0D9D4EB5) + }, + { /* P{170} */ + W256(0x2C8CDC4A,0x2E9B3E1C,0x39292B8F,0xF301BDDB,0x6F45B448,0x908063DB,0x25C80DFF,0x03D2A9B1), + W256(0x72684FB0,0x3ECADB0A,0x18C4F682,0x6DDFB9ED,0x6DBBBAEF,0xA94247E4,0xADB3D562,0x2C63DF06), + W256(0xBA8DD8FC,0xFD788E43,0x5EB4E217,0x30FAE1C2,0x00568551,0xBD60C01E,0xF35C4364,0x066F5248) + }, + { /* P{171} */ + W256(0xD486EEFE,0x780D2DC1,0x64578DCB,0xDDF38E29,0x7430FBBC,0x1246A3AB,0xA8CD4997,0x4D86A09B), + W256(0x101AFA1F,0x9BF5440C,0xD49D4402,0x2FF63482,0x6E5AF731,0xE31EBEE7,0x417F08F9,0x3BBDF993), + W256(0x546B1AC3,0x441EA7B8,0x483A5D2D,0xF37D7E19,0xBA1E5334,0x00EDF00E,0x144B8647,0x2AA79702) + }, + { /* P{172} */ + W256(0xBD027592,0x32C35992,0xCA53E5D6,0x040CF4A5,0xE0463DD4,0x89B6026A,0x62F20B12,0x52D53FFF), + W256(0x8C16314B,0xAC40C7EF,0xC5723C00,0x9B3D64E9,0x2ED1B06D,0xE9D3DE86,0x62AD53D1,0x6F302028), + W256(0xD94095DD,0x174EEAB8,0xA5969D4F,0x613FF392,0x8E5A87B0,0x045D9A4F,0xA9223A7C,0x02B2BD7E) + }, + { /* P{173} */ + W256(0xB995530D,0x6881A0DF,0x4BED5FAA,0x6E83A1C8,0xA2C68798,0xDC3A5D25,0x7558CE6A,0x147ED190), + W256(0x762DD969,0xDB936784,0xD457BA9C,0x465BA33C,0x91EAF814,0x355491A6,0x4CCE2B18,0x7B1CAAAD), + W256(0x04FF82FF,0x72311A1A,0x63CB8D77,0xDF236226,0xD3F8974A,0xB5355304,0xF77D8471,0x7411C190) + }, + { /* P{174} */ + W256(0x064182AC,0x6CA9D966,0x49D1AA8E,0x1F3FB4AE,0xE1CFA08E,0x52D36BE5,0x8ADD0C82,0x1D5F5586), + W256(0xBE030055,0xD776B5CA,0xF8C69C37,0x912F1B63,0xBF914632,0x493AB77C,0xCC3CDA8D,0x748A0934), + W256(0x3E4787E2,0xD0A44507,0xDF698DE5,0x59559AB0,0x6859C01B,0x75D35556,0x11FFF4BB,0x0B3B867F) + }, + { /* P{175} */ + W256(0x69D85F9F,0x7EBC9686,0x93669D5C,0x73F25922,0xBC8C1531,0x0E003301,0x995C9C5B,0x711ADD89), + W256(0xAAFAB7AD,0xAD4A3101,0x684BCE24,0xF2957758,0xA594EEF7,0xA2D2CA0A,0xEFB47DF6,0x1089CBCB), + W256(0x91DBD7BD,0x031BF980,0xCE5D7DF8,0x516D988E,0x85288BE5,0xAB63762D,0xEEB64B4E,0x7E70E158) + }, + { /* P{176} */ + W256(0xC22FC777,0x8558038D,0x9D6072DE,0xF1D4EED5,0xE6005486,0x08290FFB,0x3DA24681,0x4A3A9AC2), + W256(0x2F1518F2,0xDCC797FD,0xB7963F13,0x619D3A39,0xF4A7A3B8,0xFFEE1A8C,0xB05FC1BC,0x35B6ADFE), + W256(0x94C5A6AB,0x4252EDAA,0xA251C3ED,0xF43C48BA,0xF4AE7037,0xF62E9831,0xB185B22F,0x0F5E3B9D) + }, + { /* P{177} */ + W256(0xA77B1206,0x04A3B229,0x32729905,0x73FDCA12,0x8C540CCB,0x5C40E645,0x27160146,0x486FB196), + W256(0x9F8877D9,0x6E4FA000,0x94DC3BBF,0x86AE024C,0x5FBF9C12,0xD8F74EFB,0xED93E8F0,0x7FA9E45B), + W256(0x9F91B00A,0xAA44B5B7,0xC8E0E05D,0x7191AADD,0x209AFDEE,0x381527F8,0x338CCBA6,0x6A143F29) + }, + { /* P{178} */ + W256(0x8EFE5F3A,0x535C23E6,0x6118BFAD,0xC95059E5,0xC89EE4A2,0xB4077EBB,0x3953B360,0x3A5905CE), + W256(0xB2EB2A77,0x02E8B328,0xA5A66D2D,0x23B9D06D,0x0F2C344D,0x0E778BC3,0xA2531B50,0x5C916EF3), + W256(0x96C8547A,0xC2EDB168,0xF32371B3,0x37284976,0x521BD71E,0x87F4FCC0,0x518D4D5F,0x25E95A25) + }, + { /* P{179} */ + W256(0xC59CCE86,0xA784CFBD,0xEFE84612,0xDE20DF51,0xD6D09035,0xC74DC0C3,0xC62F2772,0x1CE27D48), + W256(0x6900DB2D,0xDEB818F0,0x38DD986A,0xC3E266CB,0x5B65FF3A,0x1E22DF7F,0xD08609C2,0x6E3CDA36), + W256(0xEA68DB73,0xB67FE90B,0xA3A5B89D,0x31DE909B,0x3DF076F6,0x12E3020A,0x417F0AB1,0x6F329A5B) + }, + { /* P{180} */ + W256(0xEB9E5375,0xAA18E903,0x06B0AEBA,0xD02C3CC0,0xB5A31EA4,0xD3CE459E,0xD1DAE60C,0x570EDE91), + W256(0xC5C320D1,0xDC239AE1,0xF867C4E4,0xBC34B40D,0xB870C507,0x172D49E5,0x8EB1F1BF,0x033F1C73), + W256(0x79D70924,0x6D90FBF2,0x4F12E885,0x9E2843D3,0xE8F279C9,0x84C95865,0xB2593B63,0x3ACEE246) + }, + { /* P{181} */ + W256(0x7F00E7EB,0xA16CCF7C,0x5D2F2453,0x44D48A1C,0xFE858D77,0xE6A70EBB,0x00C3F6CC,0x1C7E8EDD), + W256(0x76F6F17A,0x5A4124F4,0xCA560CCF,0x5016EA68,0xC33B4658,0x74AA0A4F,0xB36D2380,0x5E5D38AD), + W256(0x3383A423,0x10E9863F,0x505CFA1D,0xEE18B967,0x90A3DCF0,0xEE2C2402,0xA9FFE46E,0x695425FF) + }, + { /* P{182} */ + W256(0x89DDA3DC,0x2CA6C190,0x41DC9EAD,0xCB471B4D,0xDAD19A35,0x871F7B14,0x8765CE86,0x68AEED64), + W256(0x88964A3B,0x5D43801F,0x63C01E8E,0x4729A1AC,0x2ABA8D3B,0x060B46BC,0xDD261858,0x1E0DF677), + W256(0xDF365E9D,0x7BD27536,0x06B14BF0,0xD1FD7DC4,0xCE4A5E1F,0xEE218B17,0x8FD166A5,0x78B98EEE) + }, + { /* P{183} */ + W256(0xE899A9EF,0xA7F04383,0x69457248,0xEA99F9F2,0xEB3C8884,0xB999D0C0,0x1C6A5651,0x71D2B090), + W256(0x5203596A,0x323E8634,0xC2CE6BF9,0x35149575,0x0A01DF70,0xB3424075,0xA34C1460,0x24BA40CC), + W256(0xB864D752,0x3A8695C3,0x8A15F21C,0xA918A66E,0xD8591BF3,0xB568E03A,0x349DCE00,0x71A0109B) + }, + { /* P{184} */ + W256(0x79F24806,0xBD431510,0x631CAB26,0x185162C4,0x7F839E2B,0x804D9373,0x4AAA443D,0x6C92E218), + W256(0xCFA352E1,0x3258FE15,0x5DA8A720,0x3729A963,0x36CF728A,0x094E2EE0,0x92288216,0x3CCE260B), + W256(0x8E60F089,0x6BDD1913,0x78BD3671,0xA496BBEE,0x9AD2F4FE,0x51CD5046,0x400836CD,0x5B4188A3) + }, + { /* P{185} */ + W256(0x2A0A8EC5,0x2E950A83,0x1E681A60,0xCB8F9932,0x52279D7B,0xDD9D8005,0x09C83204,0x14A74EFB), + W256(0x62318B07,0xA3733C55,0xE83EA099,0xAFD22928,0xD27D5C8C,0xEAA02A3B,0x235C0E94,0x33C2A00D), + W256(0x4B576DBA,0xB2946DD4,0xD8B53F91,0x21E413DB,0x2BE355CC,0xCA632A5F,0xCB961EDF,0x71ECBE0E) + }, + { /* P{186} */ + W256(0x545707DE,0x407AED1C,0x674278B5,0xD93C6610,0x309C6338,0x16B3BDDE,0x1125BFBF,0x41A4AB63), + W256(0x1764BAD8,0xA46DC40B,0x18D9BDD6,0x48A207E5,0xAC541818,0xB82EE200,0xDF85A2CE,0x3A1FF80E), + W256(0x9C9860ED,0xB2190243,0xCF7F2882,0x1613409E,0xC178472D,0x37220E85,0xAC8DBD3A,0x32CFB2AE) + }, + { /* P{187} */ + W256(0x29805697,0xB25E7710,0xCA8FB69E,0xD7CDEF18,0xABC42411,0x04C6DFE4,0x7CC589C1,0x778B5DB8), + W256(0x05AFC320,0x33438F9D,0x9FADD4DD,0x7A573360,0x3F1B9745,0xB7D73732,0xC77145BF,0x6BE47B8D), + W256(0xD61248BC,0x1F178641,0xC6CD5075,0x416D5A3B,0x46BA559E,0x879D3B34,0x0F83E801,0x72ED9485) + }, + { /* P{188} */ + W256(0x2D59F526,0x1638B393,0xAF1CE3E3,0x013EA2EC,0x4228D072,0x510A1FCC,0xFBB99466,0x388AAD93), + W256(0xBE203EA7,0x420CED3E,0xE565EBB2,0x64542DCE,0x0396360B,0x2114D04D,0x22EDAC34,0x07C5598F), + W256(0x80586512,0xFF6534D5,0xFAD70EC0,0x4FFFA5A4,0x2B0EB2C0,0xD4F1BB11,0x0F3597FB,0x506ED52F) + }, + { /* P{189} */ + W256(0x78638C57,0xE534A5A1,0x0CD5CA53,0x30EB2B0A,0xF9B39956,0xED53F11B,0x75581672,0x7C102DC8), + W256(0xA045F0CF,0x003C16FD,0xD76CD5CE,0x52CE729F,0x458A2035,0x3AC6C289,0x845CE344,0x26584C06), + W256(0xA6FA9A6B,0xE0404454,0x4792BAA1,0xADBD8457,0x2C58377B,0x6620EEF3,0xB0896918,0x37A562BA) + }, + { /* P{190} */ + W256(0xD44592E0,0x557A8B50,0x8142E64F,0x680E7764,0xC8265D83,0x3D778549,0x98101691,0x028CCAC0), + W256(0x2794FCA4,0x0CC1BC85,0x25A418A0,0xB76C830E,0x2C7D2445,0xCA12BCF6,0x2EBFD751,0x254DC753), + W256(0xD6F443BB,0x22FB9B36,0x83BEBC96,0x0FBD567E,0xE71352C2,0x07F1DA95,0x25B2AC21,0x7AF3B154) + }, + { /* P{191} */ + W256(0x85407E5A,0x8D12481A,0x7C7DA4F3,0xBFF0AF2E,0x1F78174B,0x4B529492,0x929CAF23,0x3A341284), + W256(0xB37EA56D,0x6DFDF91C,0xA6322508,0x844FE3D5,0xA01C7E92,0xCCCB3C9B,0x9CE2CA2D,0x0A1EEB11), + W256(0xB8DCE9D6,0xD14AFB05,0x2086ABCB,0x93612770,0x978A592A,0x28BD9665,0xF04DAB40,0x508171F7) + }, + { /* P{192} */ + W256(0xCD52CC12,0x8EF4F973,0x02FD31BA,0xBB0DF0E3,0x6FF47A51,0x5E14DF02,0xBE09D912,0x09867330), + W256(0xD63F6AC3,0x471CECD2,0xE006F7E5,0x0D189E50,0x7C53490D,0xC5D653D7,0x720A1D58,0x11994587), + W256(0x042830C3,0x159456A8,0xEC968CEE,0x4ADDE4BA,0xEA1F266F,0x1EECBE25,0x3CB564C2,0x28B6FD85) + }, + { /* P{193} */ + W256(0x0F77F81F,0x83C99A1E,0xB468D0E2,0x6210474E,0xE6C8C914,0x47F5C79D,0x930FBB04,0x1EF81E9F), + W256(0x40D54F15,0x54687519,0xDC0FB02A,0xF25DDE44,0xF03DD8E3,0xEF69233A,0xA1BB8F0D,0x118F2566), + W256(0x9341B419,0x578B7A8D,0x0957A946,0x6268CBE6,0x87AA2F02,0x935236CC,0x286055D7,0x2776A859) + }, + { /* P{194} */ + W256(0x329B2FDB,0x48143D7A,0xC223AA1C,0xBFF719E2,0x9C3A8DEB,0xB31BB4D2,0x634C45AD,0x0F8D065C), + W256(0xCCDE3BE1,0x4C9F1FC7,0x90C32EB4,0x24355055,0xE2D2F279,0xC580CAFD,0xC5E561EC,0x6F6C8FAF), + W256(0xA1722BA6,0x8A6CE79C,0x4D71598D,0x2E0E5271,0x7CD608EC,0xDD784D48,0x65DD681F,0x6D6A7605) + }, + { /* P{195} */ + W256(0x67D0431B,0x68D9F28B,0x9CF3A930,0xC76D2814,0x0D71BCB6,0x56928F21,0xBEAE381E,0x171C31CC), + W256(0x03AAFB2F,0xF90BCD5C,0x815934C6,0xD8F88081,0xF0D10CFF,0x1F78F418,0xD6871CAC,0x02A080DD), + W256(0x8E0FA607,0x8F144AAD,0x59157879,0x6FBFDA7B,0xAD515CEC,0x1A501EF4,0x2280DDB3,0x6B4088AC) + }, + { /* P{196} */ + W256(0x8050A9B3,0x969142AA,0x57911E94,0xCEFD6D4A,0x75B77969,0x3D2D1A07,0xE8D20F48,0x147F20FB), + W256(0xBE55CE98,0xCA043E96,0xB754AE8E,0xA6FAF419,0x184D0C43,0x4E7B4F8A,0x627E08B2,0x1F3E7406), + W256(0x6D195F8E,0x4260F29A,0xA5E9BA6C,0x74048DA6,0x25E1B139,0xCDC8F2C1,0x629DDBBE,0x038AB2C7) + }, + { /* P{197} */ + W256(0x7248E519,0x1F79EEDC,0x021DF483,0x6B3156E5,0x92559021,0x7B416790,0x0C02BB33,0x31CF66C1), + W256(0xA7DF6991,0x80641606,0x983C009B,0x16E4A154,0x33439B23,0xAEF405E0,0x3B59D939,0x38DBD5FA), + W256(0x4FA75AD0,0xEA714C2C,0xAD4BB08F,0x611AC296,0xB94F3A1C,0x7FC710EE,0x89A71DAC,0x0047A912) + }, + { /* P{198} */ + W256(0x2EEAE5B2,0xBC270AD5,0x88A30147,0x960205AB,0x77FDA1A5,0x83D7459F,0x9A174564,0x2DF4E051), + W256(0xC6892A5B,0x31235505,0xF1DFA44B,0x7A9BF56B,0xC5166795,0x741AF28F,0xE0ACFEF7,0x683D4E51), + W256(0x4D9F5CC8,0x21AFA244,0xFA1E2E70,0x703C9308,0xC54D90DC,0x9F846432,0x2671BD0A,0x6F0FAB9F) + }, + { /* P{199} */ + W256(0x8B6D507A,0x9306EFDF,0x24A4A4AF,0x8A858E58,0xC3AE6195,0x8D5239CD,0x128D97AB,0x7378CCD4), + W256(0xC37E5D11,0x685B27DC,0xE6053657,0x5A9823AF,0x87C245F4,0xA7AEE368,0xD4DD09A1,0x5F6B1088), + W256(0xF5BF02F1,0xA06ACCF6,0x4884CE04,0x0D077178,0x72FB231D,0xFAE1F25B,0x9D8846E3,0x49DAF881) + }, + { /* P{200} */ + W256(0x7C4AE902,0x474BD681,0x43A99BF3,0xF002198A,0x64DD2E2B,0x8DFBC6BB,0xC02C6D59,0x2254069E), + W256(0x6DD1C7DF,0xFDECFCFB,0x0A18991D,0xD91B028B,0xC4D0CD0E,0x74E4247F,0x5B8B5ADD,0x2F95C694), + W256(0x1F7A3922,0xABED4139,0x63649DC3,0xB6A36833,0xA91C5E11,0xAA21A8B1,0x6D6027C5,0x2D9CF438) + }, + { /* P{201} */ + W256(0x24388BC3,0x50468B76,0x087E73D2,0x851CE5FD,0x191C2AED,0x36AE4762,0xB011A87A,0x0A60BBE0), + W256(0xF225F04C,0xAD19CFCA,0xDEAB77A4,0x845DFF33,0xFDCAAF08,0x0D9FA0BE,0x841EFE92,0x5E52EFA6), + W256(0x4C7DD5C8,0x4545A8C7,0xB14A069E,0x5BF56FFF,0x86429545,0xC76C1AF7,0x3055A690,0x670C07E9) + }, + { /* P{202} */ + W256(0x44B91294,0x8F9EB100,0xE1542FED,0x67478749,0x535DFB48,0xB29B469B,0xE2E25F76,0x264A3066), + W256(0xF83F9EB8,0x0CB27F60,0x8CD82B0B,0x56ADFF6C,0xB03FB08A,0x037CCDA8,0xD2CF0873,0x42183695), + W256(0xA872B3A1,0x0B717AC4,0x3D88D1B6,0xA15A62D2,0x26D780DA,0x922E4324,0x6E905FE5,0x732113B2) + }, + { /* P{203} */ + W256(0x7EBBFBB9,0x1FF3A45B,0xD1282547,0xA8D86BF7,0x1BE1D08C,0xB0D70C8C,0xB21D19ED,0x06D2A8CF), + W256(0x340A1440,0x254AB26B,0xB7FAF723,0x3B55DB81,0x6F1B0DB0,0xD46C2720,0xD54F357E,0x78EFB14C), + W256(0xBA181D54,0x68E9BD0E,0x3D5A5AB0,0x13B4D6BF,0x0EAE1FAE,0x00B66EA2,0xDBE5E1BC,0x265D02EE) + }, + { /* P{204} */ + W256(0xB50C5365,0x2A0DF4F8,0xC691A617,0x804EB0A8,0xA48D86D7,0xD243F6CE,0x27F6FE1D,0x16276E24), + W256(0x7A6C53E4,0x4992E44E,0xAB8B7DE2,0xA53FF2B1,0x165AD0DE,0xCD0AAACD,0x608625E4,0x30F58C44), + W256(0xB5136309,0x866D31D0,0x5593D3BD,0x7E074B40,0x1F16AEC0,0x29C71C28,0x573FB2F2,0x2EBBACAE) + }, + { /* P{205} */ + W256(0x3555106D,0xEDC5296F,0x8321A7E2,0x4D434B2D,0xE894362E,0xC0070068,0x6D6B4542,0x6582A961), + W256(0xFB09C2A0,0x213E29A3,0x7508A69A,0xB609D379,0x350AE36C,0xC18C2EBB,0x2B4BD901,0x1E187F5B), + W256(0xB07EB9CA,0x6D2241CB,0x6F5E89CE,0x49644E09,0x738C0D4C,0xC54D97C8,0x4AD173B9,0x53FE0D2E) + }, + { /* P{206} */ + W256(0x71B8EED7,0x97F3291D,0x4789E136,0x18C9E991,0xC8CDAACA,0x08550516,0x88719EFD,0x05395EC3), + W256(0x2690EF5F,0x8D59447D,0x8D9006D4,0x7BBBE17C,0x0E7A90BF,0xEF233017,0x02F22ABE,0x2A748B93), + W256(0x3402CA3A,0xBBEAA6FE,0x97BF31CE,0x5FAA5A42,0x029BAADC,0x15B0D145,0x1E21F9F4,0x5B892389) + }, + { /* P{207} */ + W256(0x9AA4B0D6,0xAF367522,0x761EE007,0x20C3BE00,0x6338301C,0x03EF1816,0x936BD202,0x47BAB7A2), + W256(0x7BB8E420,0x3B1E3A0B,0xB63A34C1,0xF05B0CBB,0xF7980BDA,0xAAE01AE4,0x409046B7,0x64B18FB2), + W256(0x432FFE0B,0x0DFBF734,0xEC8DDD34,0x3DBEE455,0x66EF9396,0x4A74149F,0x5ED6E73C,0x63A250D1) + }, + { /* P{208} */ + W256(0x8128F86B,0x98E51B90,0xE6DD7E30,0x263F13E5,0x9689B811,0xD06ACE31,0x4F23EE94,0x57329348), + W256(0x1A4756D4,0x024FCFF5,0x6B882E91,0xB020630A,0x2075F533,0xB5D5F703,0x78B94DE6,0x324509D0), + W256(0x62140EA0,0x6775CD99,0x365B3EF0,0xD642D377,0x3F65CB56,0x601901BA,0xCF83F05B,0x2B939509) + }, + { /* P{209} */ + W256(0x6D6A253C,0x7E5E99FF,0x30A1733C,0xD97F397A,0xAB987888,0x670E3805,0xF8852227,0x030D83CB), + W256(0x6E1239E2,0x8B92FEDE,0x161B082A,0x71ACFF21,0x84B89C7C,0x237048D2,0x65C1D212,0x0AE59618), + W256(0x8FD54819,0xD8DAF9CB,0xD693E577,0x39E291D5,0xABC8FCC2,0x182476F6,0x20B3E13A,0x4D40AF27) + }, + { /* P{210} */ + W256(0xBF688F75,0xA1F94B8B,0x4405CC0C,0x77FB6190,0xC85681AA,0xAEF8D012,0x8B9E8DEB,0x745C2F62), + W256(0x264A3EE3,0xDD4F3641,0x75FDE909,0x66835F13,0xD6B2E022,0xB54A1863,0xF4BA14FA,0x5B427CF0), + W256(0xA292DB19,0x7077EA22,0x44A509E2,0xC78565E5,0x5A325BAE,0xBA708AD0,0x92E8D98B,0x08B85FE9) + }, + { /* P{211} */ + W256(0x3C2A3FC6,0x1119EF6D,0xFD6996A5,0xD5FF6C26,0x0B8A76D4,0x77B8CBB2,0x31E72E57,0x5EFFD1C4), + W256(0x301D8692,0x3C5098EE,0x8281B7F5,0x3AAA542C,0xA0C369E2,0x218C79A2,0x4C179097,0x6419A7CA), + W256(0x59C60441,0x18EA4F78,0x99EA645C,0x7B83A616,0x67151F89,0x8EF0FB59,0x894AACE6,0x5E39C0A8) + }, + { /* P{212} */ + W256(0x53BB5DE7,0x0739FB15,0xBD9B28AE,0xA216864A,0xBA0679E8,0xFB9AD874,0xC38FB07C,0x2218CBA5), + W256(0x7CF82652,0xD6AC0CA8,0x6C7FAE03,0x57869CD5,0x2D89124E,0xBCE83209,0xC4C878E1,0x4DF95D3F), + W256(0xB5927B72,0x59F7733F,0x61D984C5,0x6BE2FEA3,0xBAE6BFE3,0x0DFDF83D,0x86C56E0F,0x212806BE) + }, + { /* P{213} */ + W256(0x77C9B45B,0x2E28325D,0xE9846205,0xDF743DF4,0xA85F1015,0x85601B41,0x5E52839D,0x725C2A23), + W256(0x2640B516,0x087B45FE,0xE44C0B2B,0x6150594D,0x707E28F4,0xBA23332A,0x870E6B6B,0x3D262BB0), + W256(0x02400F16,0xA5585EC0,0xA7FC7C05,0xCD8E00ED,0xF56D3635,0x5AEAF2B1,0x4D374520,0x55DEFAD8) + }, + { /* P{214} */ + W256(0x03FDD76A,0x8DB456D8,0xB283770C,0x5B9E20FF,0xB98218A6,0x26FE8032,0xFB32B99D,0x2F6060DD), + W256(0xB5788D49,0xA26CB9E5,0x049962C5,0xA71302F4,0x564A2C87,0x2452441F,0x3FDFC76F,0x1A7D4AA3), + W256(0x009052ED,0x28072A25,0xC3C8EAAE,0x9A949B5D,0x1CCFDD1A,0x6B8C4C5C,0xE7D97235,0x7D80EA9F) + }, + { /* P{215} */ + W256(0x1AB2966C,0x3A8A63CF,0x6B8E081E,0x21390040,0x0FC10C06,0x75CD30FA,0xBAF97E6E,0x753E0BBE), + W256(0x9B2594BE,0x8D82476B,0xA6B5C9C3,0xF40F7B1E,0xA37A28D4,0xE21F8B31,0x89C5E37A,0x1FB2AF1A), + W256(0xB9D8A7A5,0x99DBC21E,0xE18019F6,0xEF895A84,0x0C75CEF7,0x4E2DFE2C,0x1778AB0F,0x5BE903EB) + }, + { /* P{216} */ + W256(0x1AE40D9F,0xCA7012D2,0x30854715,0x9871980F,0xBB143A88,0xD11CCC26,0xF56EC92F,0x66DFF11F), + W256(0x438ABF0A,0xA10B7043,0x5F23E52C,0xB0D19E86,0x3F32327C,0xB4199F0E,0x2C4E9F71,0x55EFB013), + W256(0xA0438592,0x1B583AA8,0x3EC1BD80,0x45CA9CDC,0x6283D900,0x146AA005,0x5FEA8E37,0x3D4C06CD) + }, + { /* P{217} */ + W256(0xC6427729,0xA864C021,0xA8FF6F61,0xD47A8465,0xBEA8CF54,0x307E2D76,0xF1E6AA68,0x3989530F), + W256(0x0C07DCB4,0x3A7CA532,0x555EA8BD,0x594847EB,0x9291248D,0x49B68908,0xF3238C74,0x52542CDD), + W256(0xAA940777,0xC8A55DBD,0xF602D143,0x84FC512E,0x7AFD5A3B,0x5837062A,0x2C32672C,0x7C3601CA) + }, + { /* P{218} */ + W256(0x2A98B263,0x8EADBC06,0x4ECBBEA9,0x134752D9,0x674D813A,0x3808EF6A,0x8EF4FC28,0x1439727E), + W256(0x7A068932,0xF05C508A,0x5073D890,0xD1E9F2C7,0xC75E6DDE,0x54748A79,0x4C059128,0x307685C7), + W256(0xC15DAC95,0x4FE76DC1,0x8C08202D,0xCEC2C534,0x0A41138A,0xF1476E54,0xE3E1D667,0x35961D0E) + }, + { /* P{219} */ + W256(0x9B209918,0x4BBF1D5F,0x45B9707B,0xCC66599F,0xE8027A49,0x6836ED77,0x58725C1D,0x14FDF6A6), + W256(0xC5584125,0x7035B550,0xA4396714,0x47CADBFD,0x842F7962,0xF5E3D12D,0xEAE771CB,0x5BBA73F7), + W256(0xD88B7241,0xBE0FCDF3,0x12C1C616,0xEF43E3EE,0x9520131E,0x242CE337,0x5142263F,0x780900A8) + }, + { /* P{220} */ + W256(0xEEC0B00A,0xE15B351E,0xF40560E2,0x05EBDE80,0x4BF3D79C,0x54FC1531,0x420E3B9C,0x25ACF601), + W256(0x3B286403,0xF607D3A5,0x1BCBB5A3,0xDF4CAC2D,0xA728DE9E,0xC3F2619C,0xD4610144,0x4E6BC727), + W256(0x9FE2D6FB,0x1C635C8C,0x8D4C5E93,0xE7045E08,0xD4DBC6AC,0x1FEC2422,0x2CF86CB9,0x7A66CD4C) + }, + { /* P{221} */ + W256(0x3650EE88,0x6B03097A,0x49AA5C27,0xD648B909,0x2EACCA57,0xA71404FB,0x1D0B2598,0x0B3E2F2E), + W256(0x429B19A0,0xD5C62B19,0xEDB7FBC2,0xBE74D35B,0x1501B39C,0xCB5ECD51,0xCD68F30C,0x520AFA73), + W256(0x9B94F78A,0x13D10D7D,0xF275FCEB,0xDD41952C,0xFAF7428D,0xD07381C1,0x554E6426,0x5F35320C) + }, + { /* P{222} */ + W256(0x42498232,0x1D5FBB2B,0x64617B01,0xAF964CD7,0x683F21E0,0xC73BD158,0xC6A76A1F,0x3F5BCCC5), + W256(0x90E427F3,0xE8698232,0xF566E95F,0xC6B82BE5,0x3AB95DD5,0xC5515D07,0xD23B78D7,0x7FD106BE), + W256(0x3F0206E9,0xBE1AF04A,0x81AAFADD,0x2A63D678,0xC0797D2B,0x28AEC8F0,0xC1CDF03A,0x5E60065B) + }, + { /* P{223} */ + W256(0xE1A07256,0xAB40F73D,0x1DCA0BC8,0x53070930,0xCCE7012D,0xC0CF5CE6,0x7DB88CEC,0x382861FA), + W256(0x8F6343A8,0x2628F9CD,0xEC504B17,0x9A9D3259,0x211E7AA1,0xEB95F901,0x0CE7A6B4,0x2A3C2862), + W256(0xB50D49A2,0x1FC22389,0x598CE6EE,0x016C7980,0x766BDA55,0x1D36F19A,0x1A3397CD,0x0EA73A03) + }, + { /* P{224} */ + W256(0x921F8AAB,0x3D34E768,0x6EEE4795,0x3746BEA3,0x59FA3CF7,0xEE02D1AA,0xDAEF126B,0x3273E234), + W256(0xD35A83CD,0x5740CA72,0x01ABEA1B,0x0CDE0160,0xECC21B16,0xFB7A7F6A,0xB048AC23,0x01BF2D13), + W256(0x71CE9AB2,0x3C4A8BBA,0xB61C7208,0xF73C2EB3,0xB8F9E211,0x73859EF5,0x6D310CF1,0x6A321B74) + }, + { /* P{225} */ + W256(0x67109E02,0x604E1E04,0x08F2D4B9,0x8085E5ED,0xC9753421,0xE0B5D551,0x3FA55DB6,0x5F89495A), + W256(0x87E6E9CA,0x75D6C8B4,0x036B42C6,0x843F057B,0x0CBBB0CF,0xB93110BC,0xEC836A5D,0x142A78F8), + W256(0x9EFE3033,0x5092782D,0x989329D1,0xD22832A0,0x30F98D17,0xA47C5891,0x8B27A411,0x0C8A1651) + }, + { /* P{226} */ + W256(0x2D02E7BC,0x8C7BAE65,0x0CF6B274,0xEA3FA9EE,0xCB7CF44D,0x2380CE11,0x2CAE4701,0x0D4B1E8E), + W256(0x9E42A45A,0x6BAC21A9,0x951EAFA0,0x30E796DB,0x2D65766A,0xFB9BA2D3,0x70457FA6,0x61C09DE2), + W256(0x0C9487B9,0xC8A7D67A,0xFFDE5271,0x485058E7,0x900E3D73,0x8D4C7C77,0xD77CE7FD,0x65FBB4FA) + }, + { /* P{227} */ + W256(0xA7A629E8,0x2133F962,0x79A0D1B5,0x5C9CB549,0x065F8C08,0x73904BB0,0xFAD0E729,0x4C4ED976), + W256(0xAA47117E,0x51A01FA8,0x024B909C,0xB2EBBDB0,0x5A972DF4,0xEA98D10F,0xE0A11B75,0x67C48354), + W256(0xDBF660EF,0x3A993910,0xC8DA689F,0x45E54784,0xBD91EF05,0x7BB3DE28,0xCCF3B029,0x402FF4EC) + }, + { /* P{228} */ + W256(0xD6230EBB,0x1D35D625,0x52DB2B63,0xD6337703,0x49D27E43,0xEB986558,0x222E4495,0x6DFE341F), + W256(0x08B7BDBC,0xC69F489B,0x83679268,0x77812954,0xC79B057C,0x8E7DB0BA,0x38DAAFE8,0x516F4ADF), + W256(0x063D2B1E,0x1252DDF7,0xECC83029,0x3BBAC44D,0xD75B2461,0x35B88786,0xBC3FCD97,0x67A510B7) + }, + { /* P{229} */ + W256(0x76E5A91F,0x72B274BC,0xE4E2FB58,0x740896FD,0x4398D057,0x3DB49CBC,0x67BF7C11,0x27877B97), + W256(0x249AD55E,0x2AA0FCEB,0x1EDDD923,0xA6F928ED,0x43A7F8A7,0xC8E42402,0x5403A69A,0x615D6EEB), + W256(0xE331CEF9,0x1AD54899,0x6B042736,0xCBC382F2,0xD766BBAA,0xC1FA864C,0xD8807D0B,0x1BD72747) + }, + { /* P{230} */ + W256(0xD41947D6,0x5A0EE465,0xD666A96D,0xB9280AF4,0x481E9302,0xD511A413,0x2085229C,0x1F446D47), + W256(0x87182E8C,0xF3F6BDF7,0x4EF9320D,0x1425737B,0x82810BB9,0x6C45A7BE,0xA27D07A6,0x3ABE9D28), + W256(0x3DC9C9D1,0xA797B1F7,0x6557305D,0x8779591B,0x86CA651F,0x5D50C612,0x570C11C1,0x25A59857) + }, + { /* P{231} */ + W256(0xEFA598FB,0x30CE8AC0,0x9FB323F4,0xD4BC57BE,0x25E14200,0xCD8AB81C,0xD81FE2AC,0x51311426), + W256(0x8EB09162,0xF43FF7B2,0x4B73FD5E,0xFA5B38C3,0x9017C42B,0x6A23D0B8,0x0CFD6207,0x455BFC85), + W256(0x67D08476,0x63B74D58,0x59B9F88B,0x3132DC57,0xD1C53732,0x01AEAC8C,0xFB5116B1,0x4F799CB2) + }, + { /* P{232} */ + W256(0x0A37B21A,0xC22D0128,0x70D97402,0x49ED37DD,0xD66B2847,0x3D37F157,0x0AEC3F5B,0x6A2B356A), + W256(0x71AF1BBB,0x39680D77,0x466DBA37,0xBA9EB095,0xD8E2A8AD,0x381319B3,0x21E7D92D,0x64E6A9AB), + W256(0xA2CC3D15,0xE63DC0EA,0x3CBF1FA6,0xB16BEDA7,0xF673E230,0x1C68E13C,0x416ABA4C,0x7966A776) + }, + { /* P{233} */ + W256(0x3D82DA00,0xB558ABA4,0x1AB1358B,0x47D3988B,0x7929EBA2,0xA1983740,0xD0BA5596,0x2FBADD1F), + W256(0x3C7ABD11,0x787D7303,0xCF018842,0x69196D20,0x7FBAFA24,0x6F050716,0x83651BAE,0x066548FF), + W256(0xCE0F5195,0x07BC0BBF,0xA136C3B4,0x3B4A0144,0x877423BC,0x13FDEF15,0xA9A62435,0x15B8D97B) + }, + { /* P{234} */ + W256(0x439B69A5,0x10842629,0x14F24042,0x23EAF650,0x6E0B55DF,0x9587F46C,0x1E1543F4,0x7CC8E271), + W256(0x74252CA8,0xB6B9DA76,0xA9C3ED11,0x5299EC18,0x0FD15B94,0xF6A7059D,0x8E8DE417,0x0B39D342), + W256(0x7356040B,0xCF729D4E,0xB3A71F12,0xA187A711,0x876626FD,0x017FBCCB,0x314C6E1F,0x4C62D119) + }, + { /* P{235} */ + W256(0x929993E0,0xF6E6C5E0,0xD0ACF6A2,0x848D3E2E,0x7BE75B7E,0x9CADD048,0x9872704B,0x05D76FDB), + W256(0x415F92E0,0xA067F291,0x866A63D5,0x8213233B,0xDBB61C6B,0xA310BD28,0x4D19886E,0x58C7F658), + W256(0x18432A70,0x0D9C95C8,0x6FBDED5B,0x7B43333F,0x032F2BA8,0x1A86ACB8,0xB3C5E5C4,0x1420E549) + }, + { /* P{236} */ + W256(0x53DCE318,0x6462D6A7,0xF7B16DF5,0x24D923D6,0x02A99281,0xAD2E2E76,0x277FBE41,0x2E6DBA46), + W256(0xF3CF928F,0xF9B31CEF,0xFD306987,0xE330D91A,0xBC042D05,0x2C212C31,0x414D6A2E,0x27414B23), + W256(0x413B240E,0x80C26D33,0xA0B89246,0xA53193AE,0xB2EAF649,0x4E1A8487,0xE3B4C292,0x12082216) + }, + { /* P{237} */ + W256(0xF2E69EF1,0xFF3F7F45,0xE2AD789C,0x5504F08F,0xAA245200,0xB0FB7E50,0xC6FE94E0,0x166F05BD), + W256(0x01805A82,0x1F29FAC8,0x66A06CFD,0xA4FEA307,0x1E96F1B1,0x05F50EE6,0xE4685E28,0x43A107E2), + W256(0x1D28D94C,0x0597CD66,0x9EC745F2,0xDC3B2D1E,0xE546E123,0xF8257AA6,0x117C9A30,0x02D21113) + }, + { /* P{238} */ + W256(0x8F37293A,0x84808259,0xDB95552B,0x0B0A87FA,0x2E59A403,0x73963F88,0x8EBD7260,0x36D75CDD), + W256(0xC258F5D4,0x4D242941,0x4015ED2C,0x8DD203E7,0x949C2C89,0x008C9429,0x38ED2A3C,0x0052B3E3), + W256(0xABFE286E,0x7AAAF491,0xA4EC546A,0x97D15582,0x1E9810EF,0x5D4EB295,0xE8784F3C,0x46F21171) + }, + { /* P{239} */ + W256(0xF130CD06,0xD6E2272E,0xA5FCA011,0xBE6B24D6,0x0CC83C25,0xB243EB1F,0x0BC8FC42,0x3B664099), + W256(0x900FB0C1,0xC009F143,0x54ECFBAF,0xB3BDEEC4,0xDB64D788,0x22EE7D00,0x33CE3DF1,0x026B0962), + W256(0x202DCCF2,0x12F86676,0xB996E643,0x732A8168,0x4C422C52,0xF54FF9DC,0xB75C7A10,0x26B11500) + }, + { /* P{240} */ + W256(0xF191A8DC,0x26788EFA,0xE7263590,0xE8DFCCFC,0xAA2026D1,0x157C9362,0x4A5D144F,0x4E32CAAD), + W256(0x563ADB50,0xEC9B891D,0x1F671DCE,0x8D78B1DB,0x282D197C,0x69617115,0x5573A978,0x1AE2643A), + W256(0x4FCF2434,0x09636FB7,0xAF995A40,0xDD881E20,0x5F91C2B7,0xCE211A89,0xFD4900AA,0x6A50999C) + }, + { /* P{241} */ + W256(0x916D6651,0xA901A64E,0xC8CF8F39,0xB968B5A2,0xB8AA4E43,0x5D33A4A9,0x6E1E792D,0x2F5EA746), + W256(0xE8D4A1B7,0x89A32E63,0x4D6CF0C6,0x81F8E9DB,0x4039B74A,0x216CC7B7,0x9ABF60C3,0x3B19C562), + W256(0x8737934E,0xE81E1449,0x65C399F4,0x6B3499B6,0xB51E6884,0xD7978199,0xB4895825,0x105BC430) + }, + { /* P{242} */ + W256(0xF621F64F,0xE96B4AA0,0xE5323071,0xE10D4EFF,0x14199139,0xCFBCC47D,0xE03E31C0,0x05F714E8), + W256(0x12A2CA2B,0xE990577A,0x81C27939,0xC6BFA1A7,0xAD3F21B2,0x9BFF68DC,0x40615194,0x2ED19571), + W256(0x7FEB4A3D,0xB0F981E7,0x137C3028,0xDD112BD4,0x88DA7346,0xF8F3EBC5,0x676E052A,0x380C6FBB) + }, + { /* P{243} */ + W256(0x3F0D7841,0xE23740A6,0xD710A7FC,0x4F44E3A0,0x5C773E57,0xB0E2CDB5,0x242057A1,0x2767944E), + W256(0xDCCF6FA1,0x1788CBB3,0x5CA89DAA,0x60664ABE,0xB2D8D3F5,0xF2615418,0x9A87A9AA,0x2CB8CC24), + W256(0x49A9A2E4,0x76F79F1B,0x0D3E6934,0x7761490D,0xE338299E,0x94ECA394,0x423FA351,0x4C66D193) + }, + { /* P{244} */ + W256(0x7B167FB3,0x29860741,0xA96F4D50,0xD8EE7462,0x05FA8A5C,0x915860D0,0x6ABFE43E,0x7A2DBAB4), + W256(0x15756360,0x055E4148,0xF13335E8,0xB92D55DE,0x0E7A7E61,0xDF4F2F24,0x40D9CB0D,0x6EC868CA), + W256(0x99CB6197,0xAFE1D51D,0x08D20839,0x6CD0BE13,0x57381600,0xE193E85D,0xE7BF8571,0x4A122E9B) + }, + { /* P{245} */ + W256(0xE945B433,0xF5ACB918,0x79FC5FE1,0x1AE37FFA,0xED7BA805,0x911BC333,0xFAE89E01,0x6B8F213A), + W256(0xE3F61F56,0xA5AB485B,0xC5BCC9AF,0x6BD0E91A,0xE1F420CB,0xB149A0BC,0x047FC073,0x29486D5D), + W256(0xD4D2F3C5,0xB2D4DA5A,0xE50034AB,0xD0E3F287,0x85EF0211,0x1AEC0BB1,0xFCE4DE1E,0x7A17675C) + }, + { /* P{246} */ + W256(0x706F8116,0xB96B2855,0x7CCC4854,0x1382F1D6,0x92A438EB,0x532583AD,0x17757420,0x71662521), + W256(0xB79C5ECE,0xF0D8FB4F,0x7A504844,0x85ED103E,0xE20450C9,0xF0D85B2F,0xD72F1427,0x44B642CF), + W256(0xE06C0AE0,0xA55DAE4D,0x931CDD9E,0xD7D3EF74,0x7EB1EB19,0x8BB2E9E8,0x02376355,0x1F237963) + }, + { /* P{247} */ + W256(0x2E03C97E,0x4F29137A,0x3BC118F1,0x6F7520CC,0x007645D9,0x3C3678A9,0x759754EF,0x3F939E7F), + W256(0xF3408157,0x0B4A4B89,0x98BB6E01,0xD91B244D,0x92F96920,0x3C9CF5CD,0xFF26E2B2,0x055C9EB8), + W256(0x321FC3BF,0x555539A7,0x85BE27F6,0xA30E2C05,0xB912F285,0xE7CFAE16,0x88C26918,0x5CC126CC) + }, + { /* P{248} */ + W256(0x90D994DB,0x0E8307EB,0x6E890359,0xE7BB920A,0x35E90551,0x16AF416A,0x13531C29,0x3A28C2AE), + W256(0xE1054BC8,0x677BCEF0,0xC1A3CC47,0xE84D0129,0x736EFA12,0x7B1C8DD2,0x553FA1B2,0x4C9F0B71), + W256(0x302A6AF3,0xF928DA27,0x765EED6E,0x45821985,0x9F16381F,0x1DA41C9A,0x5A8B665E,0x7F757149) + }, + { /* P{249} */ + W256(0x6ECEA774,0x338C379B,0x00E29B08,0xD70DADD8,0x424479CE,0x63F15548,0x1250212A,0x79116D86), + W256(0x01D4A737,0x4BD56A4F,0x40AA9569,0xD5F275E7,0x03E1A57A,0x5164AEFE,0x243BD46B,0x6A7834C3), + W256(0x5BBA7838,0xB11B32F2,0xD630DDC6,0x8E04C3BE,0x6FCC10B6,0xA8FFF171,0x8B186B7D,0x17002BD0) + }, + { /* P{250} */ + W256(0x4EC4BE53,0xA3AB2298,0x2CA3CE50,0x6576E77E,0xE33F81FF,0x741BE0F6,0xA2B99EC0,0x222CAC2C), + W256(0xD9D254D9,0x1BF02D8C,0x0FD963DF,0x060693FC,0xDFCF5D5F,0x6E475F0F,0xA566B430,0x3A4CCEC9), + W256(0x28233251,0x5972D894,0x8BE4A49E,0xEAC18119,0x0D68BF4C,0x22A09297,0xFEAA3B93,0x0D6013F7) + }, + { /* P{251} */ + W256(0xEB928119,0x2CD6A70C,0x45BF3F92,0x2692EDE4,0x90BF49DA,0x8C5CACA7,0xE24118D8,0x7E4D74B6), + W256(0x809DCC58,0xA18731C1,0x4030EF96,0x11434D05,0x9FCBD010,0xE68DA982,0xCC0C179A,0x636EE3FA), + W256(0xC3E9BB85,0xCD9CE414,0xB637120A,0xC10E619E,0x9A75EE40,0x75BE1521,0x2735AFEF,0x30508BFA) + }, + { /* P{252} */ + W256(0x16BB0D37,0x4D530053,0x03CC5483,0xBAC24D52,0x5DC0CB4E,0x1F28E20C,0x8D0672E0,0x1630777E), + W256(0x66E30DF1,0x95F51413,0x9A0FDC58,0x5F411EC0,0x563EA93A,0x20F50861,0xAF225922,0x43003BE0), + W256(0xBB5A5DD1,0xF2273B68,0x2811BBE7,0xDAF11C93,0xF51B2B74,0x953A0A86,0xC7580CC5,0x09FF24C1) + }, + { /* P{253} */ + W256(0x4F3BB0BA,0x82DC02B1,0xC31E249E,0xD9941C8F,0x632D7957,0x2C7A4850,0x7599B474,0x35228A4F), + W256(0x47EC2A0E,0x3E20BAB8,0x8D305391,0x40022AC3,0xC65117FA,0x2A093A82,0x3FAFA224,0x4978381C), + W256(0xF6B009C7,0x59AC5693,0xB989B083,0x38EBC33E,0xBFA62030,0xBFAF9A7C,0x3BF74AB3,0x2E233E3A) + }, + { /* P{254} */ + W256(0x1F661A2F,0x667D59D9,0xE0B13ADD,0x90C091B0,0xB9CFEECD,0x4509A47F,0xEBDC5481,0x390E4209), + W256(0x60DBDD3D,0xC6720314,0xC22C8799,0xFD6BC073,0x94F8C734,0x46FD56CB,0xEE0F1343,0x79AB3492), + W256(0xB8C2A508,0x0B2B4597,0xBA5A1C4F,0x71F8FBE0,0x349EE41B,0x403B3743,0x6D7DB9DF,0x05041B7A) + }, + { /* P{255} */ + W256(0x94BB6B90,0x9E2B2EFB,0x4A6A3FE1,0xF5D4C66E,0xD578B4ED,0x82AF5828,0xA6068797,0x6793D1AF), + W256(0x8AF07244,0x9A55C9C6,0x41DD90A4,0x867F3F98,0xF66845FC,0xD4894F77,0x414914D7,0x2D01687D), + W256(0xD61358BC,0x61F36185,0xDD3DD058,0xE51BD50F,0x6C1A8B59,0x13D47700,0x98B5A8BC,0x16C7949A) + } +}; diff --git a/source/extern/curve25519/source/curve25519_dh.c b/source/extern/curve25519/source/curve25519_dh.c new file mode 100644 index 0000000..600174a --- /dev/null +++ b/source/extern/curve25519/source/curve25519_dh.c @@ -0,0 +1,208 @@ +/* The MIT License (MIT) + * + * Copyright (c) 2015 mehdi sotoodeh + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "../include/external_calls.h" +#include "curve25519_mehdi.h" + +typedef struct +{ + U_WORD X[K_WORDS]; /* x = X/Z */ + U_WORD Z[K_WORDS]; /* */ +} XZ_POINT; + +extern const U_WORD _w_P[K_WORDS]; +extern EDP_BLINDING_CTX edp_custom_blinding; + +/* x coordinate of base point */ +const U8 ecp_BasePoint[32] = { + 9,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0 }; +/* Y = X + X */ +void ecp_MontDouble(XZ_POINT *Y, const XZ_POINT *X) +{ + U_WORD A[K_WORDS], B[K_WORDS]; + /* x2 = (x+z)^2 * (x-z)^2 */ + /* z2 = ((x+z)^2 - (x-z)^2)*((x+z)^2 + ((A-2)/4)((x+z)^2 - (x-z)^2)) */ + ecp_AddReduce(A, X->X, X->Z); /* A = (x+z) */ + ecp_SubReduce(B, X->X, X->Z); /* B = (x-z) */ + ecp_SqrReduce(A, A); /* A = (x+z)^2 */ + ecp_SqrReduce(B, B); /* B = (x-z)^2 */ + ecp_MulReduce(Y->X, A, B); /* x2 = (x+z)^2 * (x-z)^2 */ + ecp_SubReduce(B, A, B); /* B = (x+z)^2 - (x-z)^2 */ + /* (486662-2)/4 = 121665 */ + ecp_WordMulAddReduce(A, A, 121665, B); + ecp_MulReduce(Y->Z, A, B); /* z2 = (B)*((x+z)^2 + ((A-2)/4)(B)) */ +} + +/* return P = P + Q, Q = 2Q */ +void ecp_Mont(XZ_POINT *P, XZ_POINT *Q, IN const U_WORD *Base) +{ + U_WORD A[K_WORDS], B[K_WORDS], C[K_WORDS], D[K_WORDS], E[K_WORDS]; + /* x3 = ((x1-z1)(x2+z2) + (x1+z1)(x2-z2))^2*zb zb=1 */ + /* z3 = ((x1-z1)(x2+z2) - (x1+z1)(x2-z2))^2*xb xb=Base */ + ecp_SubReduce(A, P->X, P->Z); /* A = x1-z1 */ + ecp_AddReduce(B, P->X, P->Z); /* B = x1+z1 */ + ecp_SubReduce(C, Q->X, Q->Z); /* C = x2-z2 */ + ecp_AddReduce(D, Q->X, Q->Z); /* D = x2+z2 */ + ecp_MulReduce(A, A, D); /* A = (x1-z1)(x2+z2) */ + ecp_MulReduce(B, B, C); /* B = (x1+z1)(x2-z2) */ + ecp_AddReduce(E, A, B); /* E = (x1-z1)(x2+z2) + (x1+z1)(x2-z2) */ + ecp_SubReduce(B, A, B); /* B = (x1-z1)(x2+z2) - (x1+z1)(x2-z2) */ + ecp_SqrReduce(P->X, E); /* x3 = ((x1-z1)(x2+z2) + (x1+z1)(x2-z2))^2 */ + ecp_SqrReduce(A, B); /* A = ((x1-z1)(x2+z2) - (x1+z1)(x2-z2))^2 */ + ecp_MulReduce(P->Z, A, Base); /* z3 = ((x1-z1)(x2+z2) - (x1+z1)(x2-z2))^2*Base */ + + /* x4 = (x2+z2)^2 * (x2-z2)^2 */ + /* z4 = ((x2+z2)^2 - (x2-z2)^2)*((x2+z2)^2 + 121665((x2+z2)^2 - (x2-z2)^2)) */ + /* C = (x2-z2) */ + /* D = (x2+z2) */ + ecp_SqrReduce(A, D); /* A = (x2+z2)^2 */ + ecp_SqrReduce(B, C); /* B = (x2-z2)^2 */ + ecp_MulReduce(Q->X, A, B); /* x4 = (x2+z2)^2 * (x2-z2)^2 */ + ecp_SubReduce(B, A, B); /* B = (x2+z2)^2 - (x2-z2)^2 */ + ecp_WordMulAddReduce(A, A, 121665, B); + ecp_MulReduce(Q->Z, A, B); /* z4 = B*((x2+z2)^2 + 121665*B) */ +} + +/* Constant-time measure: */ +/* Use different set of parameters for bit=0 or bit=1 with no conditional jump */ +/* */ +#define ECP_MONT(n) j = (k >> n) & 1; ecp_Mont(PP[j], QP[j], X) + +/* -------------------------------------------------------------------------- */ +/* Return point Q = k*P */ +/* K in a little-endian byte array */ +void ecp_PointMultiply( + OUT U8 *PublicKey, + IN const U8 *BasePoint, + IN const U8 *SecretKey, + IN int len) +{ + int i, j, k; + U_WORD X[K_WORDS]; + XZ_POINT P, Q, *PP[2], *QP[2]; + + ecp_BytesToWords(X, BasePoint); + + /* 1: P = (2k+1)G, Q = (2k+2)G */ + /* 0: Q = (2k+1)G, P = (2k)G */ + + /* Find first non-zero bit */ + while (len-- > 0) + { + k = SecretKey[len]; + for (i = 0; i < 8; i++, k <<= 1) + { + /* P = kG, Q = (k+1)G */ + if (k & 0x80) + { + /* We have first non-zero bit + // This is always bit 254 for keys created according to the spec. + // Start with randomized base point + */ + + ecp_Add(P.Z, X, edp_custom_blinding.zr); /* P.Z = random */ + ecp_MulReduce(P.X, X, P.Z); + ecp_MontDouble(&Q, &P); + + PP[1] = &P; PP[0] = &Q; + QP[1] = &Q; QP[0] = &P; + + /* Everything we reference in the below loop are on the stack + // and already touched (cached) + */ + + while (++i < 8) { k <<= 1; ECP_MONT(7); } + while (len > 0) + { + k = SecretKey[--len]; + ECP_MONT(7); + ECP_MONT(6); + ECP_MONT(5); + ECP_MONT(4); + ECP_MONT(3); + ECP_MONT(2); + ECP_MONT(1); + ECP_MONT(0); + } + + ecp_Inverse(Q.Z, P.Z); + ecp_MulMod(X, P.X, Q.Z); + ecp_WordsToBytes(PublicKey, X); + return; + } + } + } + /* K is 0 */ + mem_fill(PublicKey, 0, 32); +} + +/* -- DH key exchange interfaces ----------------------------------------- */ + +/* Return R = a*P where P is curve25519 base point */ +void x25519_BasePointMultiply(OUT U8 *r, IN const U8 *sk) +{ + Ext_POINT S; + U_WORD t[K_WORDS]; + + ecp_BytesToWords(t, sk); + edp_BasePointMult(&S, t, edp_custom_blinding.zr); + + /* Convert ed25519 point to x25519 point */ + + /* u = (1 + y)/(1 - y) = (Z + Y)/(Z - Y) */ + + ecp_AddReduce(S.t, S.z, S.y); + ecp_SubReduce(S.z, S.z, S.y); + ecp_Inverse(S.z, S.z); + ecp_MulMod(S.t, S.t, S.z); + ecp_WordsToBytes(r, S.t); +} + +/* Return public key associated with sk */ +void curve25519_dh_CalculatePublicKey_fast( + unsigned char *pk, /* [32-bytes] OUT: Public key */ + unsigned char *sk) /* [32-bytes] IN/OUT: Your secret key */ +{ + ecp_TrimSecretKey(sk); + /* Use faster method */ + x25519_BasePointMultiply(pk, sk); +} + +/* Return public key associated with sk */ +void curve25519_dh_CalculatePublicKey( + unsigned char *pk, /* [32-bytes] OUT: Public key */ + unsigned char *sk) /* [32-bytes] IN/OUT: Your secret key */ +{ + ecp_TrimSecretKey(sk); + ecp_PointMultiply(pk, ecp_BasePoint, sk, 32); +} + +/* Create a shared secret */ +void curve25519_dh_CreateSharedKey( + unsigned char *shared, /* [32-bytes] OUT: Created shared key */ + const unsigned char *pk, /* [32-bytes] IN: Other side's public key */ + unsigned char *sk) /* [32-bytes] IN/OUT: Your secret key */ +{ + ecp_TrimSecretKey(sk); + ecp_PointMultiply(shared, pk, sk, 32); +} diff --git a/source/extern/curve25519/source/curve25519_mehdi.c b/source/extern/curve25519/source/curve25519_mehdi.c new file mode 100644 index 0000000..0d95d64 --- /dev/null +++ b/source/extern/curve25519/source/curve25519_mehdi.c @@ -0,0 +1,410 @@ +/* The MIT License (MIT) + * + * Copyright (c) 2015 mehdi sotoodeh + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "../include/external_calls.h" +#include "curve25519_mehdi.h" + +/* + The curve used is y2 = x^3 + 486662x^2 + x, a Montgomery curve, over + the prime field defined by the prime number 2^255 - 19, and it uses the + base point x = 9. + Protocol uses compressed elliptic point (only X coordinates), so it + allows for efficient use of the Montgomery ladder for ECDH, using only + XZ coordinates. + + The curve is birationally equivalent to Ed25519 (Twisted Edwards curve). + + b = 256 + p = 2**255 - 19 + l = 2**252 + 27742317777372353535851937790883648493 + + This library is a constant-time implementation of field operations +*/ + +typedef struct +{ + U32 X[8]; /* x = X/Z */ + U32 Z[8]; /* */ +} XZ_POINT; + +const U32 _w_P[8] = { + 0xFFFFFFED,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF, + 0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0x7FFFFFFF +}; + +/* Maximum number of prime p that fits into 256-bits */ +const U32 _w_maxP[8] = { /* 2*P < 2**256 */ + 0xFFFFFFDA,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF, + 0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF +}; + +void ecp_SetValue(U32* X, U32 value) +{ + X[0] = value; + X[1] = X[2] = X[3] = X[4] = X[5] = X[6] = X[7] = 0; +} + +/* Y = X */ +void ecp_Copy(U32* Y, const U32* X) +{ + memcpy(Y, X, 8*sizeof(U32)); +} + +int ecp_CmpNE(const U32* X, const U32* Y) +{ + return ((X[0] ^ Y[0]) | (X[1] ^ Y[1]) | (X[2] ^ Y[2]) | (X[3] ^ Y[3]) | + (X[4] ^ Y[4]) | (X[5] ^ Y[5]) | (X[6] ^ Y[6]) | (X[7] ^ Y[7])); +} + +int ecp_CmpLT(const U32* X, const U32* Y) +{ + U32 T[8]; + return ecp_Sub(T, X, Y); +} + +#define ECP_ADD_C0(Y,X,V) c.u64 = (U64)(X) + (V); Y = c.u32.lo; +#define ECP_ADD_C1(Y,X) c.u64 = (U64)(X) + c.u32.hi; Y = c.u32.lo; + +#define ECP_SUB_C0(Y,X,V) c.s64 = (U64)(X) - (V); Y = c.u32.lo; +#define ECP_SUB_C1(Y,X) c.s64 = (U64)(X) + (S64)c.s32.hi; Y = c.u32.lo; + +#define ECP_MULSET_W0(Y,b,X) c.u64 = (U64)(b)*(X); Y = c.u32.lo; +#define ECP_MULSET_W1(Y,b,X) c.u64 = (U64)(b)*(X) + c.u32.hi; Y = c.u32.lo; + +#define ECP_MULADD_W0(Z,Y,b,X) c.u64 = (U64)(b)*(X) + (Y); Z = c.u32.lo; +#define ECP_MULADD_W1(Z,Y,b,X) c.u64 = (U64)(b)*(X) + (U64)(Y) + c.u32.hi; Z = c.u32.lo; + +#define ECP_ADD32(Z,X,Y) c.u64 = (U64)(X) + (Y); Z = c.u32.lo; +#define ECP_ADC32(Z,X,Y) c.u64 = (U64)(X) + (U64)(Y) + c.u32.hi; Z = c.u32.lo; +#define ECP_SUB32(Z,X,Y) b.s64 = (S64)(X) - (Y); Z = b.s32.lo; +#define ECP_SBC32(Z,X,Y) b.s64 = (S64)(X) - (U64)(Y) + b.s32.hi; Z = b.s32.lo; + +/* Computes Z = X+Y */ +U32 ecp_Add(U32* Z, const U32* X, const U32* Y) +{ + M64 c; + + ECP_ADD32(Z[0], X[0], Y[0]); + ECP_ADC32(Z[1], X[1], Y[1]); + ECP_ADC32(Z[2], X[2], Y[2]); + ECP_ADC32(Z[3], X[3], Y[3]); + ECP_ADC32(Z[4], X[4], Y[4]); + ECP_ADC32(Z[5], X[5], Y[5]); + ECP_ADC32(Z[6], X[6], Y[6]); + ECP_ADC32(Z[7], X[7], Y[7]); + return c.u32.hi; +} + +/* Computes Z = X-Y */ +S32 ecp_Sub(U32* Z, const U32* X, const U32* Y) +{ + M64 b; + ECP_SUB32(Z[0], X[0], Y[0]); + ECP_SBC32(Z[1], X[1], Y[1]); + ECP_SBC32(Z[2], X[2], Y[2]); + ECP_SBC32(Z[3], X[3], Y[3]); + ECP_SBC32(Z[4], X[4], Y[4]); + ECP_SBC32(Z[5], X[5], Y[5]); + ECP_SBC32(Z[6], X[6], Y[6]); + ECP_SBC32(Z[7], X[7], Y[7]); + return b.s32.hi; +} + +/* Computes Z = X+Y mod P */ +void ecp_AddReduce(U32* Z, const U32* X, const U32* Y) +{ + M64 c; + c.u32.hi = ecp_Add(Z, X, Y) * 38; + + /* Z += c.u32.hi * 38 */ + ECP_ADD_C0(Z[0], Z[0], c.u32.hi); + ECP_ADD_C1(Z[1], Z[1]); + ECP_ADD_C1(Z[2], Z[2]); + ECP_ADD_C1(Z[3], Z[3]); + ECP_ADD_C1(Z[4], Z[4]); + ECP_ADD_C1(Z[5], Z[5]); + ECP_ADD_C1(Z[6], Z[6]); + ECP_ADD_C1(Z[7], Z[7]); + + /* One more carry at most */ + ECP_ADD_C0(Z[0], Z[0], c.u32.hi*38); + ECP_ADD_C1(Z[1], Z[1]); + ECP_ADD_C1(Z[2], Z[2]); + ECP_ADD_C1(Z[3], Z[3]); + ECP_ADD_C1(Z[4], Z[4]); + ECP_ADD_C1(Z[5], Z[5]); + ECP_ADD_C1(Z[6], Z[6]); + ECP_ADD_C1(Z[7], Z[7]); +} + +/* Computes Z = X-Y mod P */ +void ecp_SubReduce(U32* Z, const U32* X, const U32* Y) +{ + M64 c; + c.u32.hi = ecp_Sub(Z, X, Y) & 38; + + ECP_SUB_C0(Z[0], Z[0], c.u32.hi); + ECP_SUB_C1(Z[1], Z[1]); + ECP_SUB_C1(Z[2], Z[2]); + ECP_SUB_C1(Z[3], Z[3]); + ECP_SUB_C1(Z[4], Z[4]); + ECP_SUB_C1(Z[5], Z[5]); + ECP_SUB_C1(Z[6], Z[6]); + ECP_SUB_C1(Z[7], Z[7]); + + ECP_SUB_C0(Z[0], Z[0], c.u32.hi & 38); + ECP_SUB_C1(Z[1], Z[1]); + ECP_SUB_C1(Z[2], Z[2]); + ECP_SUB_C1(Z[3], Z[3]); + ECP_SUB_C1(Z[4], Z[4]); + ECP_SUB_C1(Z[5], Z[5]); + ECP_SUB_C1(Z[6], Z[6]); + ECP_SUB_C1(Z[7], Z[7]); +} + +void ecp_Mod(U32 *X) +{ + U32 T[8]; + U32 c = (U32)ecp_Sub(X, X, _w_P); + + /* set T = 0 if c=0, else T = P */ + + T[0] = c & 0xFFFFFFED; + T[1] = T[2] = T[3] = T[4] = T[5] = T[6] = c; + T[7] = c >> 1; + + ecp_Add(X, X, T); /* X += 0 or P */ + + /* In case there is another P there */ + + c = (U32)ecp_Sub(X, X, _w_P); + + /* set T = 0 if c=0, else T = P */ + + T[0] = c & 0xFFFFFFED; + T[1] = T[2] = T[3] = T[4] = T[5] = T[6] = c; + T[7] = c >> 1; + + ecp_Add(X, X, T); /* X += 0 or P */ +} + +/* Computes Y = b*X */ +static void ecp_mul_set(U32* Y, U32 b, const U32* X) +{ + M64 c; + ECP_MULSET_W0(Y[0], b, X[0]); + ECP_MULSET_W1(Y[1], b, X[1]); + ECP_MULSET_W1(Y[2], b, X[2]); + ECP_MULSET_W1(Y[3], b, X[3]); + ECP_MULSET_W1(Y[4], b, X[4]); + ECP_MULSET_W1(Y[5], b, X[5]); + ECP_MULSET_W1(Y[6], b, X[6]); + ECP_MULSET_W1(Y[7], b, X[7]); + Y[8] = c.u32.hi; +} + +/* Computes Y += b*X */ +/* Addition is performed on lower 8-words of Y */ +static void ecp_mul_add(U32* Y, U32 b, const U32* X) +{ + M64 c; + ECP_MULADD_W0(Y[0], Y[0], b, X[0]); + ECP_MULADD_W1(Y[1], Y[1], b, X[1]); + ECP_MULADD_W1(Y[2], Y[2], b, X[2]); + ECP_MULADD_W1(Y[3], Y[3], b, X[3]); + ECP_MULADD_W1(Y[4], Y[4], b, X[4]); + ECP_MULADD_W1(Y[5], Y[5], b, X[5]); + ECP_MULADD_W1(Y[6], Y[6], b, X[6]); + ECP_MULADD_W1(Y[7], Y[7], b, X[7]); + Y[8] = c.u32.hi; +} + +/* Computes Z = Y + b*X and return carry */ +void ecp_WordMulAddReduce(U32 *Z, const U32* Y, U32 b, const U32* X) +{ + M64 c; + ECP_MULADD_W0(Z[0], Y[0], b, X[0]); + ECP_MULADD_W1(Z[1], Y[1], b, X[1]); + ECP_MULADD_W1(Z[2], Y[2], b, X[2]); + ECP_MULADD_W1(Z[3], Y[3], b, X[3]); + ECP_MULADD_W1(Z[4], Y[4], b, X[4]); + ECP_MULADD_W1(Z[5], Y[5], b, X[5]); + ECP_MULADD_W1(Z[6], Y[6], b, X[6]); + ECP_MULADD_W1(Z[7], Y[7], b, X[7]); + + /* Z += c.u32.hi * 38 */ + ECP_MULADD_W0(Z[0], Z[0], c.u32.hi, 38); + ECP_ADD_C1(Z[1], Z[1]); + ECP_ADD_C1(Z[2], Z[2]); + ECP_ADD_C1(Z[3], Z[3]); + ECP_ADD_C1(Z[4], Z[4]); + ECP_ADD_C1(Z[5], Z[5]); + ECP_ADD_C1(Z[6], Z[6]); + ECP_ADD_C1(Z[7], Z[7]); + + /* One more time at most */ + ECP_MULADD_W0(Z[0], Z[0], c.u32.hi, 38); + ECP_ADD_C1(Z[1], Z[1]); + ECP_ADD_C1(Z[2], Z[2]); + ECP_ADD_C1(Z[3], Z[3]); + ECP_ADD_C1(Z[4], Z[4]); + ECP_ADD_C1(Z[5], Z[5]); + ECP_ADD_C1(Z[6], Z[6]); + ECP_ADD_C1(Z[7], Z[7]); +} + +/* Computes Z = X*Y mod P. */ +/* Output fits into 8 words but could be greater than P */ +void ecp_MulReduce(U32* Z, const U32* X, const U32* Y) +{ + U32 T[16]; + + ecp_mul_set(T+0, X[0], Y); + ecp_mul_add(T+1, X[1], Y); + ecp_mul_add(T+2, X[2], Y); + ecp_mul_add(T+3, X[3], Y); + ecp_mul_add(T+4, X[4], Y); + ecp_mul_add(T+5, X[5], Y); + ecp_mul_add(T+6, X[6], Y); + ecp_mul_add(T+7, X[7], Y); + + /* We have T = X*Y, now do the reduction in size */ + + ecp_WordMulAddReduce(Z, T, 38, T+8); +} + +/* Computes Z = X*Y */ +void ecp_Mul(U32* Z, const U32* X, const U32* Y) +{ + ecp_mul_set(Z+0, X[0], Y); + ecp_mul_add(Z+1, X[1], Y); + ecp_mul_add(Z+2, X[2], Y); + ecp_mul_add(Z+3, X[3], Y); + ecp_mul_add(Z+4, X[4], Y); + ecp_mul_add(Z+5, X[5], Y); + ecp_mul_add(Z+6, X[6], Y); + ecp_mul_add(Z+7, X[7], Y); +} + +/* Computes Z = X*Y mod P. */ +void ecp_SqrReduce(U32* Y, const U32* X) +{ + /* TBD: Implementation is based on multiply */ + /* Optimize for squaring */ + + U32 T[16]; + + ecp_mul_set(T+0, X[0], X); + ecp_mul_add(T+1, X[1], X); + ecp_mul_add(T+2, X[2], X); + ecp_mul_add(T+3, X[3], X); + ecp_mul_add(T+4, X[4], X); + ecp_mul_add(T+5, X[5], X); + ecp_mul_add(T+6, X[6], X); + ecp_mul_add(T+7, X[7], X); + + /* We have T = X*X, now do the reduction in size */ + + ecp_WordMulAddReduce(Y, T, 38, T+8); +} + +/* Computes Z = X*Y mod P. */ +void ecp_MulMod(U32* Z, const U32* X, const U32* Y) +{ + ecp_MulReduce(Z, X, Y); + ecp_Mod(Z); +} + +/* Courtesy of DJB */ +/* Return out = 1/z mod P */ +void ecp_Inverse(U32 *out, const U32 *z) +{ + int i; + U32 t0[8],t1[8],z2[8],z9[8],z11[8]; + U32 z2_5_0[8],z2_10_0[8],z2_20_0[8],z2_50_0[8],z2_100_0[8]; + + /* 2 */ ecp_SqrReduce(z2,z); + /* 4 */ ecp_SqrReduce(t1,z2); + /* 8 */ ecp_SqrReduce(t0,t1); + /* 9 */ ecp_MulReduce(z9,t0,z); + /* 11 */ ecp_MulReduce(z11,z9,z2); + /* 22 */ ecp_SqrReduce(t0,z11); + /* 2^5 - 2^0 = 31 */ ecp_MulReduce(z2_5_0,t0,z9); + + /* 2^6 - 2^1 */ ecp_SqrReduce(t0,z2_5_0); + /* 2^7 - 2^2 */ ecp_SqrReduce(t1,t0); + /* 2^8 - 2^3 */ ecp_SqrReduce(t0,t1); + /* 2^9 - 2^4 */ ecp_SqrReduce(t1,t0); + /* 2^10 - 2^5 */ ecp_SqrReduce(t0,t1); + /* 2^10 - 2^0 */ ecp_MulReduce(z2_10_0,t0,z2_5_0); + + /* 2^11 - 2^1 */ ecp_SqrReduce(t0,z2_10_0); + /* 2^12 - 2^2 */ ecp_SqrReduce(t1,t0); + /* 2^20 - 2^10 */ for (i = 2;i < 10;i += 2) { + ecp_SqrReduce(t0,t1); + ecp_SqrReduce(t1,t0); } + /* 2^20 - 2^0 */ ecp_MulReduce(z2_20_0,t1,z2_10_0); + + /* 2^21 - 2^1 */ ecp_SqrReduce(t0,z2_20_0); + /* 2^22 - 2^2 */ ecp_SqrReduce(t1,t0); + /* 2^40 - 2^20 */ for (i = 2;i < 20;i += 2) { + ecp_SqrReduce(t0,t1); + ecp_SqrReduce(t1,t0); } + /* 2^40 - 2^0 */ ecp_MulReduce(t0,t1,z2_20_0); + + /* 2^41 - 2^1 */ ecp_SqrReduce(t1,t0); + /* 2^42 - 2^2 */ ecp_SqrReduce(t0,t1); + /* 2^50 - 2^10 */ for (i = 2;i < 10;i += 2) { + ecp_SqrReduce(t1,t0); + ecp_SqrReduce(t0,t1); } + /* 2^50 - 2^0 */ ecp_MulReduce(z2_50_0,t0,z2_10_0); + + /* 2^51 - 2^1 */ ecp_SqrReduce(t0,z2_50_0); + /* 2^52 - 2^2 */ ecp_SqrReduce(t1,t0); + /* 2^100 - 2^50 */ for (i = 2;i < 50;i += 2) { + ecp_SqrReduce(t0,t1); + ecp_SqrReduce(t1,t0); } + /* 2^100 - 2^0 */ ecp_MulReduce(z2_100_0,t1,z2_50_0); + + /* 2^101 - 2^1 */ ecp_SqrReduce(t1,z2_100_0); + /* 2^102 - 2^2 */ ecp_SqrReduce(t0,t1); + /* 2^200 - 2^100 */ for (i = 2;i < 100;i += 2) { + ecp_SqrReduce(t1,t0); + ecp_SqrReduce(t0,t1); } + /* 2^200 - 2^0 */ ecp_MulReduce(t1,t0,z2_100_0); + + /* 2^201 - 2^1 */ ecp_SqrReduce(t0,t1); + /* 2^202 - 2^2 */ ecp_SqrReduce(t1,t0); + /* 2^250 - 2^50 */ for (i = 2;i < 50;i += 2) { + ecp_SqrReduce(t0,t1); + ecp_SqrReduce(t1,t0); } + /* 2^250 - 2^0 */ ecp_MulReduce(t0,t1,z2_50_0); + + /* 2^251 - 2^1 */ ecp_SqrReduce(t1,t0); + /* 2^252 - 2^2 */ ecp_SqrReduce(t0,t1); + /* 2^253 - 2^3 */ ecp_SqrReduce(t1,t0); + /* 2^254 - 2^4 */ ecp_SqrReduce(t0,t1); + /* 2^255 - 2^5 */ ecp_SqrReduce(t1,t0); + /* 2^255 - 21 */ ecp_MulReduce(out,t1,z11); +} + diff --git a/source/extern/curve25519/source/curve25519_mehdi.h b/source/extern/curve25519/source/curve25519_mehdi.h new file mode 100644 index 0000000..bb251e5 --- /dev/null +++ b/source/extern/curve25519/source/curve25519_mehdi.h @@ -0,0 +1,175 @@ +/* The MIT License (MIT) + * + * Copyright (c) 2015 mehdi sotoodeh + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __curve25519_mehdi_h__ +#define __curve25519_mehdi_h__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "BaseTypes.h" + +#define ECP_VERSION_STR "1.2.0" + +#ifdef USE_ASM_LIB +#define U_WORD U64 +#define S_WORD S64 +#define WORDSIZE_64 +#define W64(lo,hi) ((U64)hi<<32)+lo +#else +#define U_WORD U32 +#define S_WORD S32 +#define WORDSIZE_32 +#define W64(lo,hi) lo,hi +#endif + +#define K_BYTES 32 +#define K_WORDS (K_BYTES/sizeof(U_WORD)) + +#define W256(x0,x1,x2,x3,x4,x5,x6,x7) {W64(x0,x1),W64(x2,x3),W64(x4,x5),W64(x6,x7)} + +/* Affine coordinates */ +typedef struct { + U_WORD x[K_WORDS]; + U_WORD y[K_WORDS]; +} Affine_POINT; + +/* Projective coordinates */ +typedef struct { + U_WORD x[K_WORDS]; /* x/z */ + U_WORD y[K_WORDS]; /* y/z */ + U_WORD z[K_WORDS]; + U_WORD t[K_WORDS]; /* xy/z */ +} Ext_POINT; + +/* pre-computed, extended point */ +typedef struct +{ + U_WORD YpX[K_WORDS]; /* Y+X */ + U_WORD YmX[K_WORDS]; /* Y-X */ + U_WORD T2d[K_WORDS]; /* 2d*T */ + U_WORD Z2[K_WORDS]; /* 2*Z */ +} PE_POINT; + +/* pre-computed, Affine point */ +typedef struct +{ + U_WORD YpX[K_WORDS]; /* Y+X */ + U_WORD YmX[K_WORDS]; /* Y-X */ + U_WORD T2d[K_WORDS]; /* 2d*T */ +} PA_POINT; + +typedef struct { + U_WORD bl[K_WORDS]; + U_WORD zr[K_WORDS]; + PE_POINT BP; +} EDP_BLINDING_CTX; + +extern const U8 ecp_BasePoint[K_BYTES]; + +/* Return point Q = k*P */ +void ecp_PointMultiply(OUT U8 *Q, IN const U8 *P, IN const U8 *K, IN int len); + +/* Set low and high bits */ +void ecp_TrimSecretKey(U8 *X); + +/* -- utils ----------------------------------------------------------------- */ + +/* Convert big-endian byte array to little-endian byte array and vice versa */ +U8* ecp_ReverseByteOrder(OUT U8 *Y, IN const U8 *X); +/* Convert little-endian byte array to little-endian word array */ +U_WORD* ecp_BytesToWords(OUT U_WORD *Y, IN const U8 *X); +/* Convert little-endian word array to little-endian byte array */ +U8* ecp_WordsToBytes(OUT U8 *Y, IN const U_WORD *X); +U8* ecp_EncodeInt(OUT U8 *Y, IN const U_WORD *X, IN U8 parity); +U8 ecp_DecodeInt(OUT U_WORD *Y, IN const U8 *X); + +/* -- base point order ------------------------------------------------------ */ + +/* Z = (X*Y)/R mod BPO */ +void eco_MontMul(OUT U_WORD *Z, IN const U_WORD *X, IN const U_WORD *Y); +/* Return Y = X*R mod BPO */ +void eco_ToMont(OUT U_WORD *Y, IN const U_WORD *X); +/* Return Y = X/R mod BPO */ +void eco_FromMont(OUT U_WORD *Y, IN const U_WORD *X); +/* Calculate Y = X**E mod BPO */ +void eco_ExpModBPO(OUT U_WORD *Y, IN const U_WORD *X, IN const U8 *E, IN int bytes); +/* Calculate Y = 1/X mod BPO */ +void eco_InvModBPO(OUT U_WORD *Y, IN const U_WORD *X); +/* Z = X*Y mod BPO */ +void eco_MulReduce(OUT U_WORD *Z, IN const U_WORD *X, IN const U_WORD *Y); +/* Return Y = D mod BPO where D is 512-bit big-endian byte array (i.e SHA512 digest) */ +void eco_DigestToWords( OUT U_WORD *Y, IN const U8 *D); +/* Z = X + Y mod BPO */ +void eco_AddReduce(OUT U_WORD *Z, IN const U_WORD *X, IN const U_WORD *Y); +/* X mod BPO */ +void eco_Mod(U_WORD *X); + +#define ed25519_PackPoint(buff, Y, parity) ecp_EncodeInt(buff, Y, (U8)(parity & 1)) + +/* -- big-number ------------------------------------------------------------ */ +U_WORD ecp_Add(U_WORD* Z, const U_WORD* X, const U_WORD* Y); +S_WORD ecp_Sub(U_WORD* Z, const U_WORD* X, const U_WORD* Y); +void ecp_SetValue(U_WORD* X, U_WORD value); +void ecp_Copy(U_WORD* Y, const U_WORD* X); +void ecp_AddReduce(U_WORD* Z, const U_WORD* X, const U_WORD* Y); +void ecp_SubReduce(U_WORD* Z, const U_WORD* X, const U_WORD* Y); +void ecp_MulReduce(U_WORD* Z, const U_WORD* X, const U_WORD* Y); +void ecp_SqrReduce(U_WORD* Y, const U_WORD* X); +void ecp_ModExp2523(U_WORD *Y, const U_WORD *X); +void ecp_Inverse(U_WORD *out, const U_WORD *z); +void ecp_MulMod(U_WORD* Z, const U_WORD* X, const U_WORD* Y); +void ecp_Mul(U_WORD* Z, const U_WORD* X, const U_WORD* Y); +/* Computes Y = b*X */ +void ecp_WordMulSet(U_WORD *Y, U_WORD b, const U_WORD* X); +/* Computes Z = Y + b*X and return carry */ +U_WORD ecp_WordMulAdd(U_WORD *Z, const U_WORD* Y, U_WORD b, const U_WORD* X); +/* Computes Z = Y + b*X */ +void ecp_WordMulAddReduce(U_WORD *Z, const U_WORD* Y, U_WORD b, const U_WORD* X); +void ecp_Mod(U_WORD* X); +int ecp_CmpNE(const U_WORD* X, const U_WORD* Y); +int ecp_CmpLT(const U_WORD* X, const U_WORD* Y); +/* Calculate: Y = [b:X] mod BPO */ +void eco_ReduceHiWord(U_WORD* Y, U_WORD b, const U_WORD* X); + +/* -- ed25519 --------------------------------------------------------------- */ +void ed25519_UnpackPoint(Affine_POINT *r, const unsigned char *p); +void ed25519_CalculateX(OUT U_WORD *X, IN const U_WORD *Y, U_WORD parity); +void edp_AddAffinePoint(Ext_POINT *p, const PA_POINT *q); +void edp_AddBasePoint(Ext_POINT *p); +void edp_AddPoint(Ext_POINT *r, const Ext_POINT *p, const PE_POINT *q); +void edp_DoublePoint(Ext_POINT *p); +void edp_ComputePermTable(PE_POINT *qtable, Ext_POINT *Q); +void edp_ExtPoint2PE(PE_POINT *r, const Ext_POINT *p); +void edp_BasePointMult(OUT Ext_POINT *S, IN const U_WORD *sk, IN const U_WORD *R); +void edp_BasePointMultiply(OUT Affine_POINT *Q, IN const U_WORD *sk, + IN const void *blinding); +void ecp_4Folds(U8* Y, const U_WORD* X); +void ecp_8Folds(U8* Y, const U_WORD* X); + +#ifdef __cplusplus +} +#endif +#endif /* __curve25519_mehdi_h__ */ \ No newline at end of file diff --git a/source/extern/curve25519/source/curve25519_order.c b/source/extern/curve25519/source/curve25519_order.c new file mode 100644 index 0000000..3abb3c3 --- /dev/null +++ b/source/extern/curve25519/source/curve25519_order.c @@ -0,0 +1,155 @@ +/* The MIT License (MIT) + * + * Copyright (c) 2015 mehdi sotoodeh + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "curve25519_mehdi.h" + +/* + This library provides support for mod BPO (Base Point Order) operations + + BPO = 2**252 + 27742317777372353535851937790883648493 + BPO = 0x1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED + + If you keep adding points together, the result repeats every BPO times. + Based on this, you may use: + + public_key = (private_key mod BPO)*BasePoint + Split key example: + k1 = random() + k2 = 1/k1 mod BPO --> k1*k2 = 1 mod BPO + P1 = k1*P0 --> P2 = k2*P1 = k2*k1*P0 = P0 + See selftest code for some examples of BPO usage + + This library is used for implementation of EdDSA sign/verify. +*/ + +const U_WORD _w_NxBPO[16][K_WORDS] = { /* n*BPO */ + W256(0,0,0,0,0,0,0,0), + W256(0x5CF5D3ED,0x5812631A,0xA2F79CD6,0x14DEF9DE,0,0,0,0x10000000), + W256(0xB9EBA7DA,0xB024C634,0x45EF39AC,0x29BDF3BD,0,0,0,0x20000000), + W256(0x16E17BC7,0x0837294F,0xE8E6D683,0x3E9CED9B,0,0,0,0x30000000), + W256(0x73D74FB4,0x60498C69,0x8BDE7359,0x537BE77A,0,0,0,0x40000000), + W256(0xD0CD23A1,0xB85BEF83,0x2ED6102F,0x685AE159,0,0,0,0x50000000), + W256(0x2DC2F78E,0x106E529E,0xD1CDAD06,0x7D39DB37,0,0,0,0x60000000), + W256(0x8AB8CB7B,0x6880B5B8,0x74C549DC,0x9218D516,0,0,0,0x70000000), + W256(0xE7AE9F68,0xC09318D2,0x17BCE6B2,0xA6F7CEF5,0,0,0,0x80000000), + W256(0x44A47355,0x18A57BED,0xBAB48389,0xBBD6C8D3,0,0,0,0x90000000), + W256(0xA19A4742,0x70B7DF07,0x5DAC205F,0xD0B5C2B2,0,0,0,0xA0000000), + W256(0xFE901B2F,0xC8CA4221,0x00A3BD35,0xE594BC91,0,0,0,0xB0000000), + W256(0x5B85EF1C,0x20DCA53C,0xA39B5A0C,0xFA73B66F,0,0,0,0xC0000000), + W256(0xB87BC309,0x78EF0856,0x4692F6E2,0x0F52B04E,1,0,0,0xD0000000), + W256(0x157196F6,0xD1016B71,0xE98A93B8,0x2431AA2C,1,0,0,0xE0000000), + W256(0x72676AE3,0x2913CE8B,0x8C82308F,0x3910A40B,1,0,0,0xF0000000) +}; + +#define minusR_0 0xCF5D3ED0 +#define minusR_1 0x812631A5 +#define minusR_2 0x2F79CD65 +#define minusR_3 0x4DEF9DEA +#define minusR_4 1 +#define minusR_5 0 +#define minusR_6 0 +#define minusR_7 0 + +/* Calculate: Y = [b:X] mod BPO +// For R = 2^256, we calculate Y = b*R + X mod BPO +// Since -R mod BPO is only 129-bits, it reduces number of multiplications if +// we calculate: Y = X - b*(-R) mod BPO instead +// Note that b*(-R) is 161-bits at most and does not need reduction. +*/ +void eco_ReduceHiWord(U32* Y, U32 b, const U32* X) +{ + M64 c; + U32 T[8]; + + /* Set T = b*(-R) */ + + c.u64 = (U64)b*minusR_0; + T[0] = c.u32.lo; + c.u64 = (U64)b*minusR_1 + c.u32.hi; + T[1] = c.u32.lo; + c.u64 = (U64)b*minusR_2 + c.u32.hi; + T[2] = c.u32.lo; + c.u64 = (U64)b*minusR_3 + c.u32.hi; + T[3] = c.u32.lo; + c.u64 = (U64)b + c.u32.hi; + T[4] = c.u32.lo; + T[5] = c.u32.hi; + T[6] = 0; + T[7] = 0; + + /* Y = X - T */ + c.s32.hi = ecp_Sub(Y, X, T); + + /* Add BPO if there is a borrow */ + + ecp_Add(Y, Y, _w_NxBPO[c.s32.hi & 1]); +} + +/* Z = X*Y mod BPO */ +void eco_MulReduce(OUT U32 *Z, IN const U32 *X, IN const U32 *Y) +{ + U32 T[16]; + ecp_Mul(T, X, Y); /* T = X*Y */ + eco_ReduceHiWord(T+7, T[15], T+7); + eco_ReduceHiWord(T+6, T[14], T+6); + eco_ReduceHiWord(T+5, T[13], T+5); + eco_ReduceHiWord(T+4, T[12], T+4); + eco_ReduceHiWord(T+3, T[11], T+3); + eco_ReduceHiWord(T+2, T[10], T+2); + eco_ReduceHiWord(T+1, T[9], T+1); + eco_ReduceHiWord(Z, T[8], T+0); +} + +/* X mod BPO */ +void eco_Mod(U32 *X) +{ + S32 c = ecp_Sub(X, X, _w_NxBPO[X[7] >> 28]); + ecp_Add(X, X, _w_NxBPO[c & 1]); +} + +/* Z = X + Y mod BPO */ +void eco_AddReduce(OUT U32 *Z, IN const U32 *X, IN const U32 *Y) +{ + U32 c = ecp_Add(Z, X, Y); + eco_ReduceHiWord(Z, c, Z); +} + +/* Return Y = D mod BPO where D is 512-bit message digest (i.e SHA512 digest) */ +void eco_DigestToWords( OUT U32 *Y, IN const U8 *md) +{ + U32 T[16]; + + /* We use digest value as little-endian byte array. */ + ecp_BytesToWords(T, md); + ecp_BytesToWords(T+8, md+32); + + eco_ReduceHiWord(T+7, T[15], T+7); + eco_ReduceHiWord(T+6, T[14], T+6); + eco_ReduceHiWord(T+5, T[13], T+5); + eco_ReduceHiWord(T+4, T[12], T+4); + eco_ReduceHiWord(T+3, T[11], T+3); + eco_ReduceHiWord(T+2, T[10], T+2); + eco_ReduceHiWord(T+1, T[9], T+1); + eco_ReduceHiWord(Y, T[8], T+0); +} diff --git a/source/extern/curve25519/source/curve25519_utils.c b/source/extern/curve25519/source/curve25519_utils.c new file mode 100644 index 0000000..c6ffe96 --- /dev/null +++ b/source/extern/curve25519/source/curve25519_utils.c @@ -0,0 +1,153 @@ +/* The MIT License (MIT) + * + * Copyright (c) 2015 mehdi sotoodeh + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "curve25519_mehdi.h" + +/* Trim private key */ +void ecp_TrimSecretKey(U8 *X) +{ + X[0] &= 0xf8; + X[31] = (X[31] | 0x40) & 0x7f; +} + +/* Convert big-endian byte array to little-endian byte array and vice versa */ +U8* ecp_ReverseByteOrder(OUT U8 *Y, IN const U8 *X) +{ + int i; + for (i = 0; i < 32; i++) Y[i] = X[31-i]; + return Y; +} + +/* Convert little-endian byte array to little-endian word array */ +U32* ecp_BytesToWords(OUT U32 *Y, IN const U8 *X) +{ + int i; + M32 m; + + for (i = 0; i < 8; i++) + { + m.u8.b0 = *X++; + m.u8.b1 = *X++; + m.u8.b2 = *X++; + m.u8.b3 = *X++; + + Y[i] = m.u32; + } + return Y; +} + +/* Convert little-endian word array to little-endian byte array */ +U8* ecp_WordsToBytes(OUT U8 *Y, IN const U32 *X) +{ + int i; + M32 m; + + for (i = 0; i < 32;) + { + m.u32 = *X++; + Y[i++] = m.u8.b0; + Y[i++] = m.u8.b1; + Y[i++] = m.u8.b2; + Y[i++] = m.u8.b3; + } + return Y; +} + +U8* ecp_EncodeInt(OUT U8 *Y, IN const U32 *X, IN U8 parity) +{ + int i; + M32 m; + + for (i = 0; i < 28;) + { + m.u32 = *X++; + Y[i++] = m.u8.b0; + Y[i++] = m.u8.b1; + Y[i++] = m.u8.b2; + Y[i++] = m.u8.b3; + } + + m.u32 = *X; + Y[28] = m.u8.b0; + Y[29] = m.u8.b1; + Y[30] = m.u8.b2; + Y[31] = (U8)((m.u8.b3 & 0x7f) | (parity << 7)); + + return Y; +} + +U8 ecp_DecodeInt(OUT U32 *Y, IN const U8 *X) +{ + int i; + M32 m; + + for (i = 0; i < 7; i++) + { + m.u8.b0 = *X++; + m.u8.b1 = *X++; + m.u8.b2 = *X++; + m.u8.b3 = *X++; + + Y[i] = m.u32; + } + + m.u8.b0 = *X++; + m.u8.b1 = *X++; + m.u8.b2 = *X++; + m.u8.b3 = *X & 0x7f; + + Y[7] = m.u32; + + return (U8)((*X >> 7) & 1); +} + +void ecp_4Folds(U8* Y, const U32* X) +{ + int i, j; + U8 a, b; + for (i = 32; i-- > 0; Y++) + { + a = 0; + b = 0; + for (j = 8; j > 1;) + { + j -= 2; + a = (a << 1) + ((X[j+1] >> i) & 1); + b = (b << 1) + ((X[j] >> i) & 1); + } + Y[0] = a; + Y[32] = b; + } +} + +void ecp_8Folds(U8* Y, const U32* X) +{ + int i, j; + U8 a = 0; + for (i = 32; i-- > 0;) + { + for (j = 8; j-- > 0;) a = (a << 1) + ((X[j] >> i) & 1); + *Y++ = a; + } +} diff --git a/source/extern/curve25519/source/custom_blind.c b/source/extern/curve25519/source/custom_blind.c new file mode 100644 index 0000000..207109a --- /dev/null +++ b/source/extern/curve25519/source/custom_blind.c @@ -0,0 +1,27 @@ +/* The MIT License (MIT) + * + * Copyright (c) 2015 mehdi sotoodeh + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "curve25519_mehdi.h" +#include "custom_blind.h" + diff --git a/source/extern/curve25519/source/custom_blind.h b/source/extern/curve25519/source/custom_blind.h new file mode 100644 index 0000000..7d06f86 --- /dev/null +++ b/source/extern/curve25519/source/custom_blind.h @@ -0,0 +1,11 @@ +EDP_BLINDING_CTX edp_custom_blinding = +{ + W256(0xDB763B3D,0x2F33DD36,0xF8DC6F74,0x98C50273,0x8216E0D5,0x1AA522E6,0x6BFD2924,0x04BEFBE2), + W256(0x73FF0C54,0x459E8BA8,0x84F9550B,0xDBF5F46A,0x6DB0E537,0x65F44FFD,0x2AED4E78,0x2CF92B82), + { + W256(0xAA6B12E6,0x7E6A2126,0x2E016899,0x80453AE0,0x6F300787,0x7A7E739E,0x264D5BA3,0x4C34AA79), + W256(0x52EA83C8,0xE4156040,0x0792FA4C,0x97CA4A43,0x3C9611E1,0x2198FE81,0x41AEAAE6,0x11919AD1), + W256(0x54A55516,0xCD8ADA8A,0x820A3D65,0xF85C2DB2,0x06667F3B,0xFF1E45F7,0x26BD6148,0x7F4E9D95), + W256(0xA4986374,0x2C1BF6DE,0x57ABC779,0x7C913D04,0xFA1127FA,0x1D7E8692,0xDE0E0680,0xC636320A) + } +}; diff --git a/source/extern/curve25519/source/ed25519_sign.c b/source/extern/curve25519/source/ed25519_sign.c new file mode 100644 index 0000000..8d3aa1c --- /dev/null +++ b/source/extern/curve25519/source/ed25519_sign.c @@ -0,0 +1,419 @@ +/* The MIT License (MIT) + * + * Copyright (c) 2015 mehdi sotoodeh + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "../include/external_calls.h" +#include "curve25519_mehdi.h" +#include "../include/ed25519_signature.h" +#include "sha512.h" + +/* + * Arithmetic on twisted Edwards curve y^2 - x^2 = 1 + dx^2y^2 + * with d = -(121665/121666) mod p + * d = 0x52036CEE2B6FFE738CC740797779E89800700A4D4141D8AB75EB4DCA135978A3 + * p = 2**255 - 19 + * p = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED + * Base point: y=4/5 mod p + * x = 0x216936D3CD6E53FEC0A4E231FDD6DC5C692CC7609525A7B2C9562D608F25D51A + * y = 0x6666666666666666666666666666666666666666666666666666666666666658 + * Base point order: + * l = 2**252 + 27742317777372353535851937790883648493 + * l = 0x1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED + */ + +extern const U_WORD _w_maxP[K_WORDS]; +extern const U_WORD _w_NxBPO[16][K_WORDS]; + +#define _w_BPO _w_NxBPO[1] + +/* +// -- custom blind --------------------------------------------------------- +// +// edp_custom_blinding is defined in source/custom_blind.c +// source/custom_blind is created randomly on every new build +// +// ------------------------------------------------------------------------- +*/ +extern EDP_BLINDING_CTX edp_custom_blinding; + +const U_WORD _w_2d[K_WORDS] = /* 2*d */ + W256(0x26B2F159,0xEBD69B94,0x8283B156,0x00E0149A,0xEEF3D130,0x198E80F2,0x56DFFCE7,0x2406D9DC); +const U_WORD _w_di[K_WORDS] = /* 1/d */ + W256(0xCDC9F843,0x25E0F276,0x4279542E,0x0B5DD698,0xCDB9CF66,0x2B162114,0x14D5CE43,0x40907ED2); + +#include "base_folding8.h" + +/* + Reference: http://eprint.iacr.org/2008/522 + Cost: 7M + 7add + Return: R = P + BasePoint +*/ +void edp_AddBasePoint(Ext_POINT *p) +{ + U_WORD a[K_WORDS], b[K_WORDS], c[K_WORDS], d[K_WORDS], e[K_WORDS]; + + ecp_SubReduce(a, p->y, p->x); /* A = (Y1-X1)*(Y2-X2) */ + ecp_MulReduce(a, a, _w_base_folding8[1].YmX); + ecp_AddReduce(b, p->y, p->x); /* B = (Y1+X1)*(Y2+X2) */ + ecp_MulReduce(b, b, _w_base_folding8[1].YpX); + ecp_MulReduce(c, p->t, _w_base_folding8[1].T2d); /* C = T1*2d*T2 */ + ecp_AddReduce(d, p->z, p->z); /* D = 2*Z1 */ + ecp_SubReduce(e, b, a); /* E = B-A */ + ecp_AddReduce(b, b, a); /* H = B+A */ + ecp_SubReduce(a, d, c); /* F = D-C */ + ecp_AddReduce(d, d, c); /* G = D+C */ + + ecp_MulReduce(p->x, e, a); /* E*F */ + ecp_MulReduce(p->y, b, d); /* H*G */ + ecp_MulReduce(p->t, e, b); /* E*H */ + ecp_MulReduce(p->z, d, a); /* G*F */ +} + +/* + Assumptions: pre-computed q, q->Z=1 + Cost: 7M + 7add + Return: P = P + Q +*/ +void edp_AddAffinePoint(Ext_POINT *p, const PA_POINT *q) +{ + U_WORD a[K_WORDS], b[K_WORDS], c[K_WORDS], d[K_WORDS], e[K_WORDS]; + ecp_SubReduce(a, p->y, p->x); /* A = (Y1-X1)*(Y2-X2) */ + ecp_MulReduce(a, a, q->YmX); + ecp_AddReduce(b, p->y, p->x); /* B = (Y1+X1)*(Y2+X2) */ + ecp_MulReduce(b, b, q->YpX); + ecp_MulReduce(c, p->t, q->T2d); /* C = T1*2d*T2 */ + ecp_AddReduce(d, p->z, p->z); /* D = Z1*2*Z2 (Z2=1)*/ + ecp_SubReduce(e, b, a); /* E = B-A */ + ecp_AddReduce(b, b, a); /* H = B+A */ + ecp_SubReduce(a, d, c); /* F = D-C */ + ecp_AddReduce(d, d, c); /* G = D+C */ + + ecp_MulReduce(p->x, e, a); /* E*F */ + ecp_MulReduce(p->y, b, d); /* H*G */ + ecp_MulReduce(p->t, e, b); /* E*H */ + ecp_MulReduce(p->z, d, a); /* G*F */ +} + +/* + Reference: http://eprint.iacr.org/2008/522 + Cost: 4M + 4S + 7add + Return: P = 2*P +*/ +void edp_DoublePoint(Ext_POINT *p) +{ + U_WORD a[K_WORDS], b[K_WORDS], c[K_WORDS], d[K_WORDS], e[K_WORDS]; + + ecp_SqrReduce(a, p->x); /* A = X1^2 */ + ecp_SqrReduce(b, p->y); /* B = Y1^2 */ + ecp_SqrReduce(c, p->z); /* C = 2*Z1^2 */ + ecp_AddReduce(c, c, c); + ecp_SubReduce(d, _w_maxP, a); /* D = -A */ + + ecp_SubReduce(a, d, b); /* H = D-B */ + ecp_AddReduce(d, d, b); /* G = D+B */ + ecp_SubReduce(b, d, c); /* F = G-C */ + ecp_AddReduce(e, p->x, p->y); /* E = (X1+Y1)^2-A-B = (X1+Y1)^2+H */ + ecp_SqrReduce(e, e); + ecp_AddReduce(e, e, a); + + ecp_MulReduce(p->x, e, b); /* E*F */ + ecp_MulReduce(p->y, a, d); /* H*G */ + ecp_MulReduce(p->z, d, b); /* G*F */ + ecp_MulReduce(p->t, e, a); /* E*H */ +} + +/* -- FOLDING --------------------------------------------------------------- +// +// The performance boost is achieved by a process that I call it FOLDING. +// Folding can be viewed as an extension of Shamir's trick but it is based +// on break down of the scalar multiplier of a*P into a polynomial of the +// form: +// +// a*P = SUM(a_i*2^(i*w))*P for i = 0,1,2,...n-1 +// +// a*P = SUM(a_i*P_i) +// +// where P_i = (2^(i*w))*P +// n = number of folds +// w = bit-length of a_i +// +// For folding of 8, 256-bit multiplier 'a' is chopped into 8 limbs of +// 32-bits each (a_0, a_1,...a_7). P_0 - P_7 can be pre-calculated and +// their 256-different permutations can be cached or hard-coded +// directly into the code. +// This arrangement combined with double-and-add approach reduces the +// number of EC point calculations by a factor of 8. We only need 31 +// double & add operations. +// +// +---+---+---+---+---+---+- .... -+---+---+---+---+---+---+ +// a = (|255|254|253|252|251|250| | 5 | 4 | 3 | 2 | 1 | 0 |) +// +---+---+---+---+---+---+- .... -+---+---+---+---+---+---+ +// +// a_i P_i +// +---+---+---+ .... -+---+---+---+ ---------- +// a7 = (|255|254|253| |226|225|224|) * (2**224)*P +// +---+---+---+ .... -+---+---+---+ +// a6 = (|225|224|223| |194|193|192|) * (2**192)*P +// +---+---+---+ .... -+---+---+---+ +// a5 = (|191|190|189| |162|161|160|) * (2**160)*P +// +---+---+---+ .... -+---+---+---+ +// a4 = (|159|158|157| |130|129|128|) * (2**128)*P +// +---+---+---+ .... -+---+---+---+ +// a3 = (|127|126|125| | 98| 97| 96|) * (2**96)*P +// +---+---+---+ .... -+---+---+---+ +// a2 = (| 95| 94| 93| | 66| 65| 64|) * (2**64)*P +// +---+---+---+ .... -+---+---+---+ +// a1 = (| 63| 62| 61| | 34| 33| 32|) * (2**32)*P +// +---+---+---+ .... -+---+---+---+ +// a0 = (| 31| 30| 29| | 2 | 1 | 0 |) * (2**0)*P +// +---+---+---+ .... -+---+---+---+ +// | | | | +// | +--+ | +--+ +// | | | | +// V V slices V V +// +---+ +---+ .... +---+ +---+ +// |255| |254| |225| |224| P7 +// +---+ +---+ .... +---+ +---+ +// |225| |224| |193| |192| P6 +// +---+ +---+ .... +---+ +---+ +// |191| |190| |161| |160| P5 +// +---+ +---+ .... +---+ +---+ +// |159| |158| |129| |128| P4 +// +---+ +---+ .... +---+ +---+ +// |127| |126| | 97| | 96| P3 +// +---+ +---+ .... +---+ +---+ +// | 95| | 94| | 65| | 64| P2 +// +---+ +---+ .... +---+ +---+ +// | 63| | 62| | 33| | 32| P1 +// +---+ +---+ .... +---+ +---+ +// | 31| | 30| | 1 | | 0 | P0 +// +---+ +---+ .... +---+ +---+ +// cut[]: 0 1 .... 30 31 +// -------------------------------------------------------------------------- +// Return S = a*P where P is ed25519 base point and R is random +*/ +void edp_BasePointMult( + OUT Ext_POINT *S, + IN const U_WORD *sk, + IN const U_WORD *R) +{ + int i = 1; + U8 cut[32]; + const PA_POINT *p0; + + ecp_8Folds(cut, sk); + + p0 = &_w_base_folding8[cut[0]]; + + ecp_SubReduce(S->x, p0->YpX, p0->YmX); /* 2x */ + ecp_AddReduce(S->y, p0->YpX, p0->YmX); /* 2y */ + ecp_MulReduce(S->t, p0->T2d, _w_di); /* 2xy */ + + /* Randomize starting point */ + + ecp_AddReduce(S->z, R, R); /* Z = 2R */ + ecp_MulReduce(S->x, S->x, R); /* X = 2xR */ + ecp_MulReduce(S->t, S->t, R); /* T = 2xyR */ + ecp_MulReduce(S->y, S->y, R); /* Y = 2yR */ + + do + { + edp_DoublePoint(S); + edp_AddAffinePoint(S, &_w_base_folding8[cut[i]]); + } while (i++ < 31); +} + +void edp_BasePointMultiply( + OUT Affine_POINT *R, + IN const U_WORD *sk, + IN const void *blinding) +{ + Ext_POINT S; + U_WORD t[K_WORDS]; + + if (blinding) + { + eco_AddReduce(t, sk, ((EDP_BLINDING_CTX*)blinding)->bl); + edp_BasePointMult(&S, t, ((EDP_BLINDING_CTX*)blinding)->zr); + edp_AddPoint(&S, &S, &((EDP_BLINDING_CTX*)blinding)->BP); + } + else + { + edp_BasePointMult(&S, sk, edp_custom_blinding.zr); + } + + ecp_Inverse(S.z, S.z); + ecp_MulMod(R->x, S.x, S.z); + ecp_MulMod(R->y, S.y, S.z); +} + +void edp_ExtPoint2PE(PE_POINT *r, const Ext_POINT *p) +{ + ecp_AddReduce(r->YpX, p->y, p->x); + ecp_SubReduce(r->YmX, p->y, p->x); + ecp_MulReduce(r->T2d, p->t, _w_2d); + ecp_AddReduce(r->Z2, p->z, p->z); +} + +/* -- Blinding ------------------------------------------------------------- +// +// Blinding is a measure to protect against side channel attacks. +// Blinding randomizes the scalar multiplier. +// +// Instead of calculating a*P, calculate (a+b mod BPO)*P + B +// +// Where b = random blinding and B = -b*P +// +// ------------------------------------------------------------------------- +*/ +void *ed25519_Blinding_Init( + void *context, /* IO: null or ptr blinding context */ + const unsigned char *seed, /* IN: [size bytes] random blinding seed */ + size_t size) /* IN: size of blinding seed */ +{ + struct { + Ext_POINT T; + U_WORD t[K_WORDS]; + SHA512_CTX H; + U8 digest[SHA512_DIGEST_LENGTH]; + } d; + + EDP_BLINDING_CTX *ctx = (EDP_BLINDING_CTX*)context; + + if (ctx == 0) + { + ctx = (EDP_BLINDING_CTX*)mem_alloc(sizeof(EDP_BLINDING_CTX)); + if (ctx == 0) return 0; + } + + /* Use edp_custom_blinding to protect generation of the new blinder */ + + SHA512_Init(&d.H); + SHA512_Update(&d.H, edp_custom_blinding.zr, 32); + SHA512_Update(&d.H, seed, size); + SHA512_Final(d.digest, &d.H); + + ecp_BytesToWords(ctx->zr, d.digest+32); + ecp_BytesToWords(d.t, d.digest); + eco_Mod(d.t); + ecp_Sub(ctx->bl, _w_BPO, d.t); + + eco_AddReduce(d.t, d.t, edp_custom_blinding.bl); + edp_BasePointMult(&d.T, d.t, edp_custom_blinding.zr); + edp_AddPoint(&d.T, &d.T, &edp_custom_blinding.BP); + + edp_ExtPoint2PE(&ctx->BP, &d.T); + + /* clear potentially sensitive data */ + mem_clear (&d, sizeof(d)); + + return ctx; +} + +void ed25519_Blinding_Finish( + void *context) /* IN: blinding context */ +{ + if (context) + { + mem_clear (context, sizeof(EDP_BLINDING_CTX)); + mem_free (context); + } +} + +/* Generate public and private key pair associated with the secret key */ +void ed25519_CreateKeyPair( + unsigned char *pubKey, /* OUT: public key */ + unsigned char *privKey, /* OUT: private key */ + const void *blinding, /* IN: [optional] null or blinding context */ + const unsigned char *sk) /* IN: secret key (32 bytes) */ +{ + U8 md[SHA512_DIGEST_LENGTH]; + U_WORD t[K_WORDS]; + SHA512_CTX H; + Affine_POINT Q; + + /* [a:b] = H(sk) */ + SHA512_Init(&H); + SHA512_Update(&H, sk, 32); + SHA512_Final(md, &H); + ecp_TrimSecretKey(md); + + ecp_BytesToWords(t, md); + edp_BasePointMultiply(&Q, t, blinding); + ed25519_PackPoint(pubKey, Q.y, Q.x[0]); + + memcpy(privKey, sk, 32); + memcpy(privKey+32, pubKey, 32); +} + +/* + * Generate message signature + */ +void ed25519_SignMessage( + unsigned char *signature, /* OUT: [64 bytes] signature (R,S) */ + const unsigned char *privKey, /* IN: [64 bytes] private key (sk,pk) */ + const void *blinding, /* IN: [optional] null or blinding context */ + const unsigned char *msg, /* IN: [msg_size bytes] message to sign */ + size_t msg_size) +{ + SHA512_CTX H; + Affine_POINT R; + U_WORD a[K_WORDS], t[K_WORDS], r[K_WORDS]; + U8 md[SHA512_DIGEST_LENGTH]; + + /* [a:b] = H(sk) */ + SHA512_Init(&H); + SHA512_Update(&H, privKey, 32); + SHA512_Final(md, &H); + ecp_TrimSecretKey(md); /* a = first 32 bytes */ + ecp_BytesToWords(a, md); + + /* r = H(b + m) mod BPO */ + SHA512_Init(&H); + SHA512_Update(&H, md+32, 32); + SHA512_Update(&H, msg, msg_size); + SHA512_Final(md, &H); + eco_DigestToWords(r, md); + eco_Mod(r); /* r mod BPO */ + + /* R = r*P */ + edp_BasePointMultiply(&R, r, blinding); + ed25519_PackPoint(signature, R.y, R.x[0]); /* R part of signature */ + + /* S = r + H(encoded(R) + pk + m) * a mod BPO */ + SHA512_Init(&H); + SHA512_Update(&H, signature, 32); /* encoded(R) */ + SHA512_Update(&H, privKey+32, 32); /* pk */ + SHA512_Update(&H, msg, msg_size); /* m */ + SHA512_Final(md, &H); + eco_DigestToWords(t, md); + + eco_MulReduce(t, t, a); /* h()*a */ + eco_AddReduce(t, t, r); + eco_Mod(t); + ecp_WordsToBytes(signature+32, t); /* S part of signature */ + + /* Clear sensitive data */ + ecp_SetValue(a, 0); + ecp_SetValue(r, 0); +} diff --git a/source/extern/curve25519/source/ed25519_verify.c b/source/extern/curve25519/source/ed25519_verify.c new file mode 100644 index 0000000..fdd8c55 --- /dev/null +++ b/source/extern/curve25519/source/ed25519_verify.c @@ -0,0 +1,313 @@ +/* The MIT License (MIT) + * + * Copyright (c) 2015 mehdi sotoodeh + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "../include/external_calls.h" +#include "curve25519_mehdi.h" +#include "../include/ed25519_signature.h" +#include "sha512.h" + +/* + * Arithmetic on twisted Edwards curve y^2 - x^2 = 1 + dx^2y^2 + * with d = -(121665/121666) mod p + * d = 0x52036CEE2B6FFE738CC740797779E89800700A4D4141D8AB75EB4DCA135978A3 + * p = 2**255 - 19 + * p = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED + * Base point: y=4/5 mod p + * x = 0x216936D3CD6E53FEC0A4E231FDD6DC5C692CC7609525A7B2C9562D608F25D51A + * y = 0x6666666666666666666666666666666666666666666666666666666666666658 + * Base point order: + * l = 2**252 + 27742317777372353535851937790883648493 + * l = 0x1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED + */ + +typedef struct { + unsigned char pk[32]; + PE_POINT q_table[16]; +} EDP_SIGV_CTX; + +extern const U_WORD _w_P[K_WORDS]; +extern const U_WORD _w_di[K_WORDS]; + +extern const PA_POINT _w_base_folding8[256]; +extern const U_WORD _w_NxBPO[16][K_WORDS]; + +#define _w_BPO _w_NxBPO[1] + +#define _w_Zero _w_base_folding8[0].T2d +#define _w_One _w_base_folding8[0].YpX + +const U_WORD _w_I[K_WORDS] = /* sqrt(-1) */ + W256(0x4A0EA0B0,0xC4EE1B27,0xAD2FE478,0x2F431806,0x3DFBD7A7,0x2B4D0099,0x4FC1DF0B,0x2B832480); + +static const U_WORD _w_d[K_WORDS] = + W256(0x135978A3,0x75EB4DCA,0x4141D8AB,0x00700A4D,0x7779E898,0x8CC74079,0x2B6FFE73,0x52036CEE); + +void ed25519_CalculateX(OUT U_WORD *X, IN const U_WORD *Y, U_WORD parity) +{ + U_WORD u[K_WORDS], v[K_WORDS], a[K_WORDS], b[K_WORDS]; + + /* Calculate sqrt((y^2 - 1)/(d*y^2 + 1)) */ + + ecp_SqrReduce(u, Y); /* u = y^2 */ + ecp_MulReduce(v, u, _w_d); /* v = dy^2 */ + ecp_SubReduce(u, u, _w_One); /* u = y^2-1 */ + ecp_AddReduce(v, v, _w_One); /* v = dy^2+1 */ + + /* Calculate: sqrt(u/v) = u*v^3 * (u*v^7)^((p-5)/8) */ + + ecp_SqrReduce(b, v); + ecp_MulReduce(a, u, b); + ecp_MulReduce(a, a, v); /* a = u*v^3 */ + ecp_SqrReduce(b, b); /* b = v^4 */ + ecp_MulReduce(b, a, b); /* b = u*v^7 */ + ecp_ModExp2523(b, b); + ecp_MulReduce(X, b, a); + + /* Check if we have correct sqrt, else, multiply by sqrt(-1) */ + + ecp_SqrReduce(b, X); + ecp_MulReduce(b, b, v); + ecp_SubReduce(b, b, u); + ecp_Mod(b); + if (ecp_CmpNE(b, _w_Zero)) ecp_MulReduce(X, X, _w_I); + + while (ecp_CmpLT(X, _w_P) == 0) ecp_Sub(X, X, _w_P); + + /* match parity */ + if (((X[0] ^ parity) & 1) != 0) + ecp_Sub(X, _w_P, X); +} + +void ed25519_UnpackPoint(Affine_POINT *r, const unsigned char *p) +{ + U8 parity = ecp_DecodeInt(r->y, p); + ed25519_CalculateX(r->x, r->y, parity); +} + +void ecp_SrqMulReduce(U_WORD *Z, const U_WORD *X, int n, const U_WORD *Y) +{ + U_WORD t[K_WORDS]; + ecp_SqrReduce(t, X); + while (n-- > 1) ecp_SqrReduce(t, t); + ecp_MulReduce(Z, t, Y); +} + +void ecp_ModExp2523(U_WORD *Y, const U_WORD *X) +{ + U_WORD x2[K_WORDS], x9[K_WORDS], x11[K_WORDS], x5[K_WORDS], x10[K_WORDS]; + U_WORD x20[K_WORDS], x50[K_WORDS], x100[K_WORDS], t[K_WORDS]; + + ecp_SqrReduce(x2, X); /* 2 */ + ecp_SrqMulReduce(x9, x2, 2, X); /* 9 */ + ecp_MulReduce(x11, x9, x2); /* 11 */ + ecp_SqrReduce(t, x11); /* 22 */ + ecp_MulReduce(x5, t, x9); /* 31 = 2^5 - 2^0 */ + ecp_SrqMulReduce(x10, x5, 5, x5); /* 2^10 - 2^0 */ + ecp_SrqMulReduce(x20, x10, 10, x10); /* 2^20 - 2^0 */ + ecp_SrqMulReduce(t, x20, 20, x20); /* 2^40 - 2^0 */ + ecp_SrqMulReduce(x50, t, 10, x10); /* 2^50 - 2^0 */ + ecp_SrqMulReduce(x100, x50, 50, x50); /* 2^100 - 2^0 */ + ecp_SrqMulReduce(t, x100, 100, x100); /* 2^200 - 2^0 */ + ecp_SrqMulReduce(t, t, 50, x50); /* 2^250 - 2^0 */ + ecp_SqrReduce(t, t); ecp_SqrReduce(t, t); /* 2^252 - 2^2 */ + ecp_MulReduce(Y, t, X); /* 2^252 - 3 */ +} + +/* + Assumptions: pre-computed q + Cost: 8M + 6add + Return: P = P + Q +*/ +void edp_AddPoint(Ext_POINT *r, const Ext_POINT *p, const PE_POINT *q) +{ + U_WORD a[K_WORDS], b[K_WORDS], c[K_WORDS], d[K_WORDS], e[K_WORDS]; + + ecp_SubReduce(a, p->y, p->x); /* A = (Y1-X1)*(Y2-X2) */ + ecp_MulReduce(a, a, q->YmX); + ecp_AddReduce(b, p->y, p->x); /* B = (Y1+X1)*(Y2+X2) */ + ecp_MulReduce(b, b, q->YpX); + ecp_MulReduce(c, p->t, q->T2d); /* C = T1*2d*T2 */ + ecp_MulReduce(d, p->z, q->Z2); /* D = Z1*2*Z2 */ + ecp_SubReduce(e, b, a); /* E = B-A */ + ecp_AddReduce(b, b, a); /* H = B+A */ + ecp_SubReduce(a, d, c); /* F = D-C */ + ecp_AddReduce(d, d, c); /* G = D+C */ + + ecp_MulReduce(r->x, e, a); /* E*F */ + ecp_MulReduce(r->y, b, d); /* H*G */ + ecp_MulReduce(r->t, e, b); /* E*H */ + ecp_MulReduce(r->z, d, a); /* G*F */ +} + +int ed25519_VerifySignature( + const unsigned char *signature, /* IN: signature (R,S) */ + const unsigned char *publicKey, /* IN: public key */ + const unsigned char *msg, size_t msg_size) /* IN: message to sign */ +{ + EDP_SIGV_CTX ctx; + + ed25519_Verify_Init(&ctx, publicKey); + + return ed25519_Verify_Check(&ctx, signature, msg, msg_size); +} + +#define QTABLE_SET(d,s) \ + edp_AddPoint(&T, &Q, &ctx->q_table[s]); \ + edp_ExtPoint2PE(&ctx->q_table[d], &T) + +void * ed25519_Verify_Init( + void *context, /* IO: null or context buffer to use */ + const unsigned char *publicKey) /* IN: [32 bytes] public key */ +{ + int i; + Ext_POINT Q, T; + EDP_SIGV_CTX *ctx = (EDP_SIGV_CTX*)context; + + if (ctx == 0) ctx = (EDP_SIGV_CTX*)mem_alloc(sizeof(EDP_SIGV_CTX)); + + if (ctx) + { + memcpy(ctx->pk, publicKey, 32); + i = ecp_DecodeInt(Q.y, publicKey); + ed25519_CalculateX(Q.x, Q.y, ~i); /* Invert parity for -Q */ + ecp_MulMod(Q.t, Q.x, Q.y); + ecp_SetValue(Q.z, 1); + + /* pre-compute q-table */ + + /* Calculate: Q0=Q, Q1=(2^64)*Q, Q2=(2^128)*Q, Q3=(2^192)*Q */ + + ecp_SetValue(ctx->q_table[0].YpX, 1); /* -- -- -- -- */ + ecp_SetValue(ctx->q_table[0].YmX, 1); + ecp_SetValue(ctx->q_table[0].T2d, 0); + ecp_SetValue(ctx->q_table[0].Z2, 2); + + edp_ExtPoint2PE(&ctx->q_table[1], &Q); /* -- -- -- q0 */ + + for (i = 0; i < 64; i++) edp_DoublePoint(&Q); + + edp_ExtPoint2PE(&ctx->q_table[2], &Q); /* -- -- q1 -- */ + QTABLE_SET(3,1); /* -- -- q1 q0 */ + + do edp_DoublePoint(&Q); while (++i < 128); + + edp_ExtPoint2PE(&ctx->q_table[4], &Q); /* -- q2 -- -- */ + QTABLE_SET(5, 1); /* -- q2 -- q0 */ + QTABLE_SET(6, 2); /* -- q2 q1 -- */ + QTABLE_SET(7, 3); /* -- q2 q1 q0 */ + + do edp_DoublePoint(&Q); while (++i < 192); + + edp_ExtPoint2PE(&ctx->q_table[8], &Q); /* q3 -- -- -- */ + QTABLE_SET(9, 1); /* q3 -- -- q0 */ + QTABLE_SET(10, 2); /* q3 -- q1 -- */ + QTABLE_SET(11, 3); /* q3 -- q1 q0 */ + QTABLE_SET(12, 4); /* q3 q2 -- -- */ + QTABLE_SET(13, 5); /* q3 q2 -- q0 */ + QTABLE_SET(14, 6); /* q3 q2 q1 -- */ + QTABLE_SET(15, 7); /* q3 q2 q1 q0 */ + } + return ctx; +} + +void ed25519_Verify_Finish(void *ctx) +{ + mem_free(ctx); +} + +/* + Assumptions: qtable = pre-computed Q + Calculate: point R = a*P + b*Q where P is base point +*/ +static void edp_PolyPointMultiply( + Affine_POINT *r, + const U_WORD *a, + const U_WORD *b, + const PE_POINT *qtable) +{ + int i = 1; + Ext_POINT S; + const PE_POINT *q0; + U8 u[32], v[64]; + + ecp_8Folds(u, a); + ecp_4Folds(v, b); + + /* Set initial value of S */ + q0 = &qtable[v[0]]; + ecp_SubReduce(S.x, q0->YpX, q0->YmX); /* 2x */ + ecp_AddReduce(S.y, q0->YpX, q0->YmX); /* 2y */ + ecp_MulReduce(S.t, q0->T2d, _w_di); /* 2xy */ + ecp_Copy(S.z, q0->Z2); /* 2z */ + + do + { /* 31D + 31A */ + edp_DoublePoint(&S); + edp_AddPoint(&S, &S, &qtable[v[i]]); + } while (++i < 32); + + do + { /* 32D + 64A */ + edp_DoublePoint(&S); + edp_AddAffinePoint(&S, &_w_base_folding8[u[i-32]]); + edp_AddPoint(&S, &S, &qtable[v[i]]); + } while (++i < 64); + + ecp_Inverse(S.z, S.z); + ecp_MulMod(r->x, S.x, S.z); + ecp_MulMod(r->y, S.y, S.z); +} + +/* + This function can be used for batch verification. + Assumptions: context = ed25519_Verify_Init(pk) + +*/ +int ed25519_Verify_Check( + const void *context, /* IN: precomputes */ + const unsigned char *signature, /* IN: signature (R,S) */ + const unsigned char *msg, size_t msg_size) /* IN: message to sign */ +{ + SHA512_CTX H; + Affine_POINT T; + U_WORD h[K_WORDS], s[K_WORDS]; + U8 md[SHA512_DIGEST_LENGTH]; + + /* h = H(enc(R) + pk + m) mod BPO */ + SHA512_Init(&H); + SHA512_Update(&H, signature, 32); /* enc(R) */ + SHA512_Update(&H, ((EDP_SIGV_CTX*)context)->pk, 32); + SHA512_Update(&H, msg, msg_size); + SHA512_Final(md, &H); + eco_DigestToWords(h, md); + eco_Mod(h); + + /* T = s*P + h*(-Q) = (s - h*a)*P = r*P = R */ + + ecp_BytesToWords(s, signature+32); + edp_PolyPointMultiply(&T, s, h, ((EDP_SIGV_CTX*)context)->q_table); + ed25519_PackPoint(md, T.y, T.x[0]); + + return (memcmp(md, signature, 32) == 0) ? 1 : 0; +} diff --git a/source/extern/curve25519/source/sha512.c b/source/extern/curve25519/source/sha512.c new file mode 100644 index 0000000..711cb32 --- /dev/null +++ b/source/extern/curve25519/source/sha512.c @@ -0,0 +1,294 @@ +/* crypto/sha/sha512.c */ +/* ==================================================================== + * Copyright (c) 2004 The OpenSSL Project. All rights reserved + * according to the OpenSSL license [found in ../../LICENSE]. + * ==================================================================== + */ +/* + * IMPLEMENTATION NOTES. + * + * As you might have noticed 32-bit hash algorithms: + * + * - permit SHA_LONG to be wider than 32-bit (case on CRAY); + * - optimized versions implement two transform functions: one operating + * on [aligned] data in host byte order and one - on data in input + * stream byte order; + * - share common byte-order neutral collector and padding function + * implementations, ../md32_common.h; + * + * Neither of the above applies to this SHA-512 implementations. Reasons + * [in reverse order] are: + * + * - it's the only 64-bit hash algorithm for the moment of this writing, + * there is no need for common collector/padding implementation [yet]; + * - by supporting only one transform function [which operates on + * *aligned* data in input stream byte order, big-endian in this case] + * we minimize burden of maintenance in two ways: a) collector/padding + * function is simpler; b) only one transform function to stare at; + * - SHA_LONG64 is required to be exactly 64-bit in order to be able to + * apply a number of optimizations to mitigate potential performance + * penalties caused by previous design decision; + * + * Caveat lector. + * + * Implementation relies on the fact that "long long" is 64-bit on + * both 32- and 64-bit platforms. If some compiler vendor comes up + * with 128-bit long long, adjustment to sha.h would be required. + * As this implementation relies on 64-bit integer type, it's totally + * inappropriate for platforms which don't support it, most notably + * 16-bit platforms. + * + */ +#include +#include "../include/external_calls.h" +#include "sha512.h" + +#define UINT64(X) X##ULL + +void SHA512_Transform (SHA512_CTX *ctx, const void *in); + +void SHA512_Init (SHA512_CTX *c) +{ + c->h[0]=UINT64(0x6a09e667f3bcc908); + c->h[1]=UINT64(0xbb67ae8584caa73b); + c->h[2]=UINT64(0x3c6ef372fe94f82b); + c->h[3]=UINT64(0xa54ff53a5f1d36f1); + c->h[4]=UINT64(0x510e527fade682d1); + c->h[5]=UINT64(0x9b05688c2b3e6c1f); + c->h[6]=UINT64(0x1f83d9abfb41bd6b); + c->h[7]=UINT64(0x5be0cd19137e2179); + + c->Nl=0; + c->Nh=0; + c->num=0; + c->md_len=SHA512_DIGEST_LENGTH; +} + +void SHA512_Final (unsigned char *md, SHA512_CTX *c) +{ + unsigned char *p=(unsigned char *)c->u.p; + size_t n=c->num; + + p[n]=0x80; /* There always is a room for one */ + n++; + if (n > (SHA512_CBLOCK-16)) + mem_fill (p+n,0,SHA512_CBLOCK-n), n=0, + SHA512_Transform (c,p); + + mem_fill (p+n,0,SHA512_CBLOCK-16-n); +#ifdef ECP_CONFIG_BIG_ENDIAN + c->u.d[SHA_LBLOCK-2] = c->Nh; + c->u.d[SHA_LBLOCK-1] = c->Nl; +#else + p[SHA512_CBLOCK-1] = (unsigned char)(c->Nl); + p[SHA512_CBLOCK-2] = (unsigned char)(c->Nl>>8); + p[SHA512_CBLOCK-3] = (unsigned char)(c->Nl>>16); + p[SHA512_CBLOCK-4] = (unsigned char)(c->Nl>>24); + p[SHA512_CBLOCK-5] = (unsigned char)(c->Nl>>32); + p[SHA512_CBLOCK-6] = (unsigned char)(c->Nl>>40); + p[SHA512_CBLOCK-7] = (unsigned char)(c->Nl>>48); + p[SHA512_CBLOCK-8] = (unsigned char)(c->Nl>>56); + p[SHA512_CBLOCK-9] = (unsigned char)(c->Nh); + p[SHA512_CBLOCK-10] = (unsigned char)(c->Nh>>8); + p[SHA512_CBLOCK-11] = (unsigned char)(c->Nh>>16); + p[SHA512_CBLOCK-12] = (unsigned char)(c->Nh>>24); + p[SHA512_CBLOCK-13] = (unsigned char)(c->Nh>>32); + p[SHA512_CBLOCK-14] = (unsigned char)(c->Nh>>40); + p[SHA512_CBLOCK-15] = (unsigned char)(c->Nh>>48); + p[SHA512_CBLOCK-16] = (unsigned char)(c->Nh>>56); +#endif + + SHA512_Transform (c,p); + + if (md) for (n=0; n < SHA512_DIGEST_LENGTH/8; n++) + { + M64 m; + m.u64 = c->h[n]; + *(md++) = m.u8.b7; + *(md++) = m.u8.b6; + *(md++) = m.u8.b5; + *(md++) = m.u8.b4; + *(md++) = m.u8.b3; + *(md++) = m.u8.b2; + *(md++) = m.u8.b1; + *(md++) = m.u8.b0; + } +} + +void SHA512_Update (SHA512_CTX *c, const void *_data, size_t len) +{ + SHA_LONG64 l; + unsigned char *p=c->u.p; + const unsigned char *data=(const unsigned char *)_data; + + if (len==0) return; + + l = (c->Nl+(((SHA_LONG64)len)<<3))&UINT64(0xffffffffffffffff); + if (l < c->Nl) c->Nh++; + if (sizeof(len)>=8) c->Nh+=(((SHA_LONG64)len)>>61); + c->Nl=l; + + if (c->num != 0) + { + size_t n = SHA512_CBLOCK - c->num; + + if (len < n) + { + memcpy (p+c->num,data,len), c->num += (unsigned int)len; + return; + } + else + { + memcpy (p+c->num,data,n), c->num = 0; + len-=n, data+=n; + SHA512_Transform (c,p); + } + } + + while (len >= SHA512_CBLOCK) + { + SHA512_Transform (c,data);//,len/SHA512_CBLOCK), + data += SHA512_CBLOCK; + len -= SHA512_CBLOCK; + } + + if (len != 0) + memcpy (p,data,len), c->num = (int)len; +} + +static const SHA_LONG64 K512[80] = +{ + UINT64(0x428a2f98d728ae22),UINT64(0x7137449123ef65cd), + UINT64(0xb5c0fbcfec4d3b2f),UINT64(0xe9b5dba58189dbbc), + UINT64(0x3956c25bf348b538),UINT64(0x59f111f1b605d019), + UINT64(0x923f82a4af194f9b),UINT64(0xab1c5ed5da6d8118), + UINT64(0xd807aa98a3030242),UINT64(0x12835b0145706fbe), + UINT64(0x243185be4ee4b28c),UINT64(0x550c7dc3d5ffb4e2), + UINT64(0x72be5d74f27b896f),UINT64(0x80deb1fe3b1696b1), + UINT64(0x9bdc06a725c71235),UINT64(0xc19bf174cf692694), + UINT64(0xe49b69c19ef14ad2),UINT64(0xefbe4786384f25e3), + UINT64(0x0fc19dc68b8cd5b5),UINT64(0x240ca1cc77ac9c65), + UINT64(0x2de92c6f592b0275),UINT64(0x4a7484aa6ea6e483), + UINT64(0x5cb0a9dcbd41fbd4),UINT64(0x76f988da831153b5), + UINT64(0x983e5152ee66dfab),UINT64(0xa831c66d2db43210), + UINT64(0xb00327c898fb213f),UINT64(0xbf597fc7beef0ee4), + UINT64(0xc6e00bf33da88fc2),UINT64(0xd5a79147930aa725), + UINT64(0x06ca6351e003826f),UINT64(0x142929670a0e6e70), + UINT64(0x27b70a8546d22ffc),UINT64(0x2e1b21385c26c926), + UINT64(0x4d2c6dfc5ac42aed),UINT64(0x53380d139d95b3df), + UINT64(0x650a73548baf63de),UINT64(0x766a0abb3c77b2a8), + UINT64(0x81c2c92e47edaee6),UINT64(0x92722c851482353b), + UINT64(0xa2bfe8a14cf10364),UINT64(0xa81a664bbc423001), + UINT64(0xc24b8b70d0f89791),UINT64(0xc76c51a30654be30), + UINT64(0xd192e819d6ef5218),UINT64(0xd69906245565a910), + UINT64(0xf40e35855771202a),UINT64(0x106aa07032bbd1b8), + UINT64(0x19a4c116b8d2d0c8),UINT64(0x1e376c085141ab53), + UINT64(0x2748774cdf8eeb99),UINT64(0x34b0bcb5e19b48a8), + UINT64(0x391c0cb3c5c95a63),UINT64(0x4ed8aa4ae3418acb), + UINT64(0x5b9cca4f7763e373),UINT64(0x682e6ff3d6b2b8a3), + UINT64(0x748f82ee5defb2fc),UINT64(0x78a5636f43172f60), + UINT64(0x84c87814a1f0ab72),UINT64(0x8cc702081a6439ec), + UINT64(0x90befffa23631e28),UINT64(0xa4506cebde82bde9), + UINT64(0xbef9a3f7b2c67915),UINT64(0xc67178f2e372532b), + UINT64(0xca273eceea26619c),UINT64(0xd186b8c721c0c207), + UINT64(0xeada7dd6cde0eb1e),UINT64(0xf57d4f7fee6ed178), + UINT64(0x06f067aa72176fba),UINT64(0x0a637dc5a2c898a6), + UINT64(0x113f9804bef90dae),UINT64(0x1b710b35131c471b), + UINT64(0x28db77f523047d84),UINT64(0x32caab7b40c72493), + UINT64(0x3c9ebe0a15c9bebc),UINT64(0x431d67c49c100d4c), + UINT64(0x4cc5d4becb3e42b6),UINT64(0x597f299cfc657e2a), + UINT64(0x5fcb6fab3ad6faec),UINT64(0x6c44198c4a475817) +}; + +#define B(x,j) (((SHA_LONG64)(*(((const unsigned char *)(&x))+j)))<<((7-j)*8)) +#define PULL64(x) (B(x,0)|B(x,1)|B(x,2)|B(x,3)|B(x,4)|B(x,5)|B(x,6)|B(x,7)) +#define ROTR(x,s) (((x)>>s) | (x)<<(64-s)) + +#define Sigma0(x) (ROTR((x),28) ^ ROTR((x),34) ^ ROTR((x),39)) +#define Sigma1(x) (ROTR((x),14) ^ ROTR((x),18) ^ ROTR((x),41)) +#define sigma0(x) (ROTR((x),1) ^ ROTR((x),8) ^ ((x)>>7)) +#define sigma1(x) (ROTR((x),19) ^ ROTR((x),61) ^ ((x)>>6)) + +#define Ch(x,y,z) (((x) & (y)) ^ ((~(x)) & (z))) +#define Maj(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) + +#define ROUND_00_15(i,a,b,c,d,e,f,g,h) do { \ + T1 += h + Sigma1(e) + Ch(e,f,g) + K512[i]; \ + h = Sigma0(a) + Maj(a,b,c); \ + d += T1; h += T1; } while (0) + +#define ROUND_16_80(i,j,a,b,c,d,e,f,g,h,X) do { \ + s0 = X[(j+1)&0x0f]; s0 = sigma0(s0); \ + s1 = X[(j+14)&0x0f]; s1 = sigma1(s1); \ + T1 = X[(j)&0x0f] += s0 + s1 + X[(j+9)&0x0f]; \ + ROUND_00_15(i+j,a,b,c,d,e,f,g,h); } while (0) + +void SHA512_Transform (SHA512_CTX *ctx, const void *in) +{ + const SHA_LONG64 *W = (SHA_LONG64*)in; + SHA_LONG64 a,b,c,d,e,f,g,h,s0,s1,T1; + SHA_LONG64 X[16]; + int i; + + a = ctx->h[0]; b = ctx->h[1]; c = ctx->h[2]; d = ctx->h[3]; + e = ctx->h[4]; f = ctx->h[5]; g = ctx->h[6]; h = ctx->h[7]; + +#ifdef ECP_CONFIG_BIG_ENDIAN + T1 = X[0] = W[0]; ROUND_00_15(0,a,b,c,d,e,f,g,h); + T1 = X[1] = W[1]; ROUND_00_15(1,h,a,b,c,d,e,f,g); + T1 = X[2] = W[2]; ROUND_00_15(2,g,h,a,b,c,d,e,f); + T1 = X[3] = W[3]; ROUND_00_15(3,f,g,h,a,b,c,d,e); + T1 = X[4] = W[4]; ROUND_00_15(4,e,f,g,h,a,b,c,d); + T1 = X[5] = W[5]; ROUND_00_15(5,d,e,f,g,h,a,b,c); + T1 = X[6] = W[6]; ROUND_00_15(6,c,d,e,f,g,h,a,b); + T1 = X[7] = W[7]; ROUND_00_15(7,b,c,d,e,f,g,h,a); + T1 = X[8] = W[8]; ROUND_00_15(8,a,b,c,d,e,f,g,h); + T1 = X[9] = W[9]; ROUND_00_15(9,h,a,b,c,d,e,f,g); + T1 = X[10] = W[10]; ROUND_00_15(10,g,h,a,b,c,d,e,f); + T1 = X[11] = W[11]; ROUND_00_15(11,f,g,h,a,b,c,d,e); + T1 = X[12] = W[12]; ROUND_00_15(12,e,f,g,h,a,b,c,d); + T1 = X[13] = W[13]; ROUND_00_15(13,d,e,f,g,h,a,b,c); + T1 = X[14] = W[14]; ROUND_00_15(14,c,d,e,f,g,h,a,b); + T1 = X[15] = W[15]; ROUND_00_15(15,b,c,d,e,f,g,h,a); +#else + T1 = X[0] = PULL64(W[0]); ROUND_00_15(0,a,b,c,d,e,f,g,h); + T1 = X[1] = PULL64(W[1]); ROUND_00_15(1,h,a,b,c,d,e,f,g); + T1 = X[2] = PULL64(W[2]); ROUND_00_15(2,g,h,a,b,c,d,e,f); + T1 = X[3] = PULL64(W[3]); ROUND_00_15(3,f,g,h,a,b,c,d,e); + T1 = X[4] = PULL64(W[4]); ROUND_00_15(4,e,f,g,h,a,b,c,d); + T1 = X[5] = PULL64(W[5]); ROUND_00_15(5,d,e,f,g,h,a,b,c); + T1 = X[6] = PULL64(W[6]); ROUND_00_15(6,c,d,e,f,g,h,a,b); + T1 = X[7] = PULL64(W[7]); ROUND_00_15(7,b,c,d,e,f,g,h,a); + T1 = X[8] = PULL64(W[8]); ROUND_00_15(8,a,b,c,d,e,f,g,h); + T1 = X[9] = PULL64(W[9]); ROUND_00_15(9,h,a,b,c,d,e,f,g); + T1 = X[10] = PULL64(W[10]); ROUND_00_15(10,g,h,a,b,c,d,e,f); + T1 = X[11] = PULL64(W[11]); ROUND_00_15(11,f,g,h,a,b,c,d,e); + T1 = X[12] = PULL64(W[12]); ROUND_00_15(12,e,f,g,h,a,b,c,d); + T1 = X[13] = PULL64(W[13]); ROUND_00_15(13,d,e,f,g,h,a,b,c); + T1 = X[14] = PULL64(W[14]); ROUND_00_15(14,c,d,e,f,g,h,a,b); + T1 = X[15] = PULL64(W[15]); ROUND_00_15(15,b,c,d,e,f,g,h,a); +#endif + + for (i=16;i<80;i+=16) + { + ROUND_16_80(i, 0,a,b,c,d,e,f,g,h,X); + ROUND_16_80(i, 1,h,a,b,c,d,e,f,g,X); + ROUND_16_80(i, 2,g,h,a,b,c,d,e,f,X); + ROUND_16_80(i, 3,f,g,h,a,b,c,d,e,X); + ROUND_16_80(i, 4,e,f,g,h,a,b,c,d,X); + ROUND_16_80(i, 5,d,e,f,g,h,a,b,c,X); + ROUND_16_80(i, 6,c,d,e,f,g,h,a,b,X); + ROUND_16_80(i, 7,b,c,d,e,f,g,h,a,X); + ROUND_16_80(i, 8,a,b,c,d,e,f,g,h,X); + ROUND_16_80(i, 9,h,a,b,c,d,e,f,g,X); + ROUND_16_80(i,10,g,h,a,b,c,d,e,f,X); + ROUND_16_80(i,11,f,g,h,a,b,c,d,e,X); + ROUND_16_80(i,12,e,f,g,h,a,b,c,d,X); + ROUND_16_80(i,13,d,e,f,g,h,a,b,c,X); + ROUND_16_80(i,14,c,d,e,f,g,h,a,b,X); + ROUND_16_80(i,15,b,c,d,e,f,g,h,a,X); + } + + ctx->h[0] += a; ctx->h[1] += b; ctx->h[2] += c; ctx->h[3] += d; + ctx->h[4] += e; ctx->h[5] += f; ctx->h[6] += g; ctx->h[7] += h; +} \ No newline at end of file diff --git a/source/extern/curve25519/source/sha512.h b/source/extern/curve25519/source/sha512.h new file mode 100644 index 0000000..675ee4c --- /dev/null +++ b/source/extern/curve25519/source/sha512.h @@ -0,0 +1,92 @@ +/* crypto/sha/sha512.h */ +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ + +#ifndef HEADER_SHA512_H +#define HEADER_SHA512_H + +#include "BaseTypes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define SHA512_DIGEST_LENGTH 64 +#define SHA512_CBLOCK 128 /* SHA-512 treats input data as a + * contiguous array of 64 bit + * wide big-endian values. */ +#define SHA_LONG64 U64 + +typedef struct SHA512state_st +{ + SHA_LONG64 h[8]; + SHA_LONG64 Nl,Nh; + union { + SHA_LONG64 d[8]; + unsigned char p[SHA512_CBLOCK]; + } u; + unsigned int num, md_len; +} SHA512_CTX; + +void SHA512_Init(SHA512_CTX *c); +void SHA512_Update(SHA512_CTX *c, const void *data, size_t len); +void SHA512_Final(unsigned char *md, SHA512_CTX *c); + +#ifdef __cplusplus +} +#endif +#endif \ No newline at end of file From 40223a5090bf8a502094927da39fc96a5cfd5eae Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Wed, 12 Jul 2023 22:16:12 +1000 Subject: [PATCH 03/34] Initial work --- source/base/StarMixer.cpp | 10 +++-- source/base/StarMixer.hpp | 3 +- source/client/StarClientApplication.cpp | 12 +++++- source/client/StarClientApplication.hpp | 5 ++- source/core/StarDataStream.cpp | 4 +- source/core/StarDataStreamDevices.cpp | 7 ++++ source/core/StarDataStreamDevices.hpp | 3 ++ source/frontend/StarMainMixer.cpp | 4 +- source/frontend/StarMainMixer.hpp | 2 +- source/game/CMakeLists.txt | 2 + source/game/StarNetworkedAnimator.cpp | 9 +++++ source/game/StarNetworkedAnimator.hpp | 2 + source/game/StarPlayer.cpp | 49 +++++++++++++++++++++++++ source/game/StarPlayer.hpp | 22 +++++++++++ source/game/StarVoice.cpp | 35 ++++++++++++++++++ source/game/StarVoice.hpp | 32 ++++++++++++++++ 16 files changed, 189 insertions(+), 12 deletions(-) create mode 100644 source/game/StarVoice.cpp create mode 100644 source/game/StarVoice.hpp diff --git a/source/base/StarMixer.cpp b/source/base/StarMixer.cpp index 96705b0..7b8e338 100644 --- a/source/base/StarMixer.cpp +++ b/source/base/StarMixer.cpp @@ -221,7 +221,7 @@ void Mixer::stopAll(float rampTime) { p.first->stop(vel); } -void Mixer::read(int16_t* outBuffer, size_t frameCount) { +void Mixer::read(int16_t* outBuffer, size_t frameCount, ExtraMixFunction extraMixFunction) { // Make this method as least locky as possible by copying all the needed // member data before the expensive audio / effect stuff. unsigned sampleRate; @@ -326,7 +326,7 @@ void Mixer::read(int16_t* outBuffer, size_t frameCount) { m_mixBuffer[s * channels + c] = 0; } else { for (size_t c = 0; c < channels; ++c) - m_mixBuffer[s * channels + c] = m_mixBuffer[s * channels + c] * volume; + m_mixBuffer[s * channels + c] *= volume; } } } @@ -338,7 +338,8 @@ void Mixer::read(int16_t* outBuffer, size_t frameCount) { float vol = lerp((float)s / frameCount, beginVolume * groupVolume * audioStopVolBegin, endVolume * groupEndVolume * audioStopVolEnd); for (size_t c = 0; c < channels; ++c) { float sample = m_mixBuffer[s * channels + c] * vol * audioState.positionalChannelVolumes[c] * audioInstance->m_volume.value; - outBuffer[s * channels + c] = clamp(sample + outBuffer[s * channels + c], -32767.0f, 32767.0f); + int16_t& outSample = outBuffer[s * channels + c]; + outSample = clamp(sample + outSample, -32767.0f, 32767.0f); } } @@ -347,6 +348,9 @@ void Mixer::read(int16_t* outBuffer, size_t frameCount) { } } + if (extraMixFunction) + extraMixFunction(outBuffer, bufferSize, channels); + { MutexLocker locker(m_effectsMutex); // Apply all active effects diff --git a/source/base/StarMixer.hpp b/source/base/StarMixer.hpp index 079549f..d369cfb 100644 --- a/source/base/StarMixer.hpp +++ b/source/base/StarMixer.hpp @@ -95,6 +95,7 @@ private: // Thread safe mixer class with basic effects support. class Mixer { public: + typedef function ExtraMixFunction; typedef function EffectFunction; typedef function PositionalAttenuationFunction; @@ -126,7 +127,7 @@ public: // Reads pending audio data. This is thread safe with the other Mixer // methods, but only one call to read may be active at a time. - void read(int16_t* samples, size_t frameCount); + void read(int16_t* samples, size_t frameCount, ExtraMixFunction extraMixFunction = {}); // Call within the main loop of the program using Mixer, calculates // positional attenuation of audio and does cleanup. diff --git a/source/client/StarClientApplication.cpp b/source/client/StarClientApplication.cpp index 356421b..e2a1515 100644 --- a/source/client/StarClientApplication.cpp +++ b/source/client/StarClientApplication.cpp @@ -14,6 +14,9 @@ #include "StarWorldTemplate.hpp" #include "StarWorldClient.hpp" #include "StarRootLoader.hpp" +#include "StarInput.hpp" +#include "StarVoice.hpp" + #include "StarInterfaceLuaBindings.hpp" #include "StarInputLuaBindings.hpp" @@ -171,6 +174,7 @@ void ClientApplication::applicationInit(ApplicationControllerPtr appController) m_guiContext = make_shared(m_mainMixer->mixer(), appController); m_input = make_shared(); + m_voice = make_shared(); auto configuration = m_root->configuration(); bool vsync = configuration->get("vsync").toBool(); @@ -417,8 +421,12 @@ void ClientApplication::render() { } void ClientApplication::getAudioData(int16_t* sampleData, size_t frameCount) { - if (m_mainMixer) - m_mainMixer->read(sampleData, frameCount); + if (m_mainMixer) { + m_mainMixer->read(sampleData, frameCount, [&](int16_t* buffer, size_t frames, unsigned channels) { + if (m_voice) + m_voice->mix(buffer, frames, channels); + }); + } } void ClientApplication::changeState(MainAppState newState) { diff --git a/source/client/StarClientApplication.hpp b/source/client/StarClientApplication.hpp index 186efaf..4ee837e 100644 --- a/source/client/StarClientApplication.hpp +++ b/source/client/StarClientApplication.hpp @@ -11,11 +11,13 @@ #include "StarErrorScreen.hpp" #include "StarCinematic.hpp" #include "StarKeyBindings.hpp" -#include "StarInput.hpp" #include "StarMainApplication.hpp" namespace Star { +STAR_CLASS(Input); +STAR_CLASS(Voice); + class ClientApplication : public Application { protected: virtual void startup(StringList const& cmdLineArgs) override; @@ -76,6 +78,7 @@ private: MainMixerPtr m_mainMixer; GuiContextPtr m_guiContext; InputPtr m_input; + VoicePtr m_voice; // Valid after renderInit is called the first time CinematicPtr m_cinematicOverlay; diff --git a/source/core/StarDataStream.cpp b/source/core/StarDataStream.cpp index c8d50a4..834f4de 100644 --- a/source/core/StarDataStream.cpp +++ b/source/core/StarDataStream.cpp @@ -205,7 +205,7 @@ size_t DataStream::readVlqU(uint64_t& i) { size_t bytesRead = Star::readVlqU(i, makeFunctionInputIterator([this]() { return this->read(); })); if (bytesRead == NPos) - throw DataStreamException("Error reading VLQ encoded intenger!"); + throw DataStreamException("Error reading VLQ encoded integer!"); return bytesRead; } @@ -214,7 +214,7 @@ size_t DataStream::readVlqI(int64_t& i) { size_t bytesRead = Star::readVlqI(i, makeFunctionInputIterator([this]() { return this->read(); })); if (bytesRead == NPos) - throw DataStreamException("Error reading VLQ encoded intenger!"); + throw DataStreamException("Error reading VLQ encoded integer!"); return bytesRead; } diff --git a/source/core/StarDataStreamDevices.cpp b/source/core/StarDataStreamDevices.cpp index 0c37d8d..85e6d3f 100644 --- a/source/core/StarDataStreamDevices.cpp +++ b/source/core/StarDataStreamDevices.cpp @@ -164,4 +164,11 @@ void DataStreamExternalBuffer::reset(char const* externalData, size_t len) { m_buffer.reset(externalData, len); } +void DataStreamExternalBuffer::readData(char* data, size_t len) { + m_buffer.readFull(data, len); +} +void DataStreamExternalBuffer::writeData(char const* data, size_t len) { + m_buffer.writeFull(data, len); +} + } diff --git a/source/core/StarDataStreamDevices.hpp b/source/core/StarDataStreamDevices.hpp index 88ee0e9..4a12d24 100644 --- a/source/core/StarDataStreamDevices.hpp +++ b/source/core/StarDataStreamDevices.hpp @@ -140,6 +140,9 @@ public: void reset(char const* externalData, size_t len); + void readData(char* data, size_t len) override; + void writeData(char const* data, size_t len) override; + private: ExternalBuffer m_buffer; }; diff --git a/source/frontend/StarMainMixer.cpp b/source/frontend/StarMainMixer.cpp index c49a0fc..59b6b52 100644 --- a/source/frontend/StarMainMixer.cpp +++ b/source/frontend/StarMainMixer.cpp @@ -127,8 +127,8 @@ void MainMixer::setVolume(float volume, float rampTime) { m_mixer->setVolume(volume, rampTime); } -void MainMixer::read(int16_t* sampleData, size_t frameCount) { - m_mixer->read(sampleData, frameCount); +void MainMixer::read(int16_t* sampleData, size_t frameCount, Mixer::ExtraMixFunction extraMixFunction) { + m_mixer->read(sampleData, frameCount, extraMixFunction); } } diff --git a/source/frontend/StarMainMixer.hpp b/source/frontend/StarMainMixer.hpp index ecf9443..2582a2f 100644 --- a/source/frontend/StarMainMixer.hpp +++ b/source/frontend/StarMainMixer.hpp @@ -22,7 +22,7 @@ public: MixerPtr mixer() const; void setVolume(float volume, float rampTime = 0.0f); - void read(int16_t* sampleData, size_t frameCount); + void read(int16_t* sampleData, size_t frameCount, Mixer::ExtraMixFunction = {}); private: UniverseClientPtr m_universeClient; diff --git a/source/game/CMakeLists.txt b/source/game/CMakeLists.txt index 120e9e8..fa55950 100644 --- a/source/game/CMakeLists.txt +++ b/source/game/CMakeLists.txt @@ -156,6 +156,7 @@ SET (star_game_HEADERS StarVehicle.hpp StarVehicleDatabase.hpp StarVersioningDatabase.hpp + StarVoice.hpp StarWarping.hpp StarWeather.hpp StarWeatherTypes.hpp @@ -414,6 +415,7 @@ SET (star_game_SOURCES StarVehicle.cpp StarVehicleDatabase.cpp StarVersioningDatabase.cpp + StarVoice.cpp StarWarping.cpp StarWeather.cpp StarWeatherTypes.cpp diff --git a/source/game/StarNetworkedAnimator.cpp b/source/game/StarNetworkedAnimator.cpp index f76882f..6f74f02 100644 --- a/source/game/StarNetworkedAnimator.cpp +++ b/source/game/StarNetworkedAnimator.cpp @@ -383,6 +383,15 @@ void NetworkedAnimator::setGlobalTag(String tagName, String tagValue) { m_globalTags.set(move(tagName), move(tagValue)); } +void NetworkedAnimator::removeGlobalTag(String const& tagName) { + m_globalTags.remove(tagName); +} + +String const* NetworkedAnimator::globalTagPtr(String const& tagName) const { + return m_globalTags.ptr(tagName); +} + + void NetworkedAnimator::setPartTag(String const& partType, String tagName, String tagValue) { m_partTags[partType].set(move(tagName), move(tagValue)); } diff --git a/source/game/StarNetworkedAnimator.hpp b/source/game/StarNetworkedAnimator.hpp index 563a21e..63f7774 100644 --- a/source/game/StarNetworkedAnimator.hpp +++ b/source/game/StarNetworkedAnimator.hpp @@ -115,6 +115,8 @@ public: // Drawables can also have a tag which will be set to whatever the // current state frame is (1 indexed, so the first frame is 1). void setGlobalTag(String tagName, String tagValue); + void removeGlobalTag(String const& tagName); + String const* globalTagPtr(String const& tagName) const; void setPartTag(String const& partType, String tagName, String tagValue); void setProcessingDirectives(Directives const& directives); diff --git a/source/game/StarPlayer.cpp b/source/game/StarPlayer.cpp index af8a2ed..c9b337f 100644 --- a/source/game/StarPlayer.cpp +++ b/source/game/StarPlayer.cpp @@ -2464,4 +2464,53 @@ Vec2F Player::cameraPosition() { return position(); } +NetworkedAnimatorPtr Player::effectsAnimator() { + return m_effectsAnimator; +} + +const String secretProprefix = strf("{:c}JsonProperty{:c}", 0, 0); + +Maybe Player::getSecretPropertyView(String const& name) const { + if (auto tag = m_effectsAnimator->globalTagPtr(secretProprefix + name)) { + auto& view = tag->utf8(); + DataStreamExternalBuffer buffer(view.data(), view.size()); + try { + uint8_t typeIndex = buffer.read() - 1; + if ((Json::Type)typeIndex == Json::Type::String) { + size_t len = buffer.readVlqU(); + size_t pos = buffer.pos(); + if (pos + len == buffer.size()) + return StringView(buffer.ptr() + pos, len); + } + } + catch (StarException const& e) {} + } + + return {}; +} + +Json Player::getSecretProperty(String const& name, Json defaultValue) const { + if (auto tag = m_effectsAnimator->globalTagPtr(secretProprefix + name)) { + DataStreamExternalBuffer buffer(tag->utf8Ptr(), tag->utf8Size()); + try + { return buffer.read(); } + catch (StarException const& e) + { Logger::error("Exception reading secret player property '{}': {}", name, e.what()); } + } + + return move(defaultValue); +} + +void Player::setSecretProperty(String const& name, Json const& value) { + if (value) { + DataStreamBuffer ds; + ds.write(value); + auto& data = ds.data(); + m_effectsAnimator->setGlobalTag(secretProprefix + name, String(data.ptr(), data.size())); + } + else + m_effectsAnimator->removeGlobalTag(secretProprefix + name); +} + + } diff --git a/source/game/StarPlayer.hpp b/source/game/StarPlayer.hpp index c63fe15..d534e03 100644 --- a/source/game/StarPlayer.hpp +++ b/source/game/StarPlayer.hpp @@ -447,6 +447,28 @@ public: using Entity::setTeam; + NetworkedAnimatorPtr effectsAnimator(); + + // We need to store ephemeral/large/always-changing networked properties that other clients can read. Candidates: + // genericProperties: + // Non-starter, is not networked. + // statusProperties: + // Nope! Changes to the status properties aren't networked efficiently - one change resends the whole map. + // We can't fix that because it would break compatibility with vanilla servers. + // effectsAnimator's globalTags: + // Cursed, but viable. + // Efficient networking due to using a NetElementMapWrapper. + // Unfortunately values are Strings, so to work with Json we need to serialize/deserialize. Whatever. + // Additionally, this is compatible with vanilla networking. + // I call this a 'secret property'. + + // If the secret property exists as a serialized Json string, returns a view to it without deserializing. + Maybe getSecretPropertyView(String const& name) const; + // Gets a secret Json property. It will be de-serialized. + Json getSecretProperty(String const& name, Json defaultValue = Json()) const; + // Sets a secret Json property. It will be serialized. + void setSecretProperty(String const& name, Json const& value); + private: enum class State { Idle, diff --git a/source/game/StarVoice.cpp b/source/game/StarVoice.cpp new file mode 100644 index 0000000..1c949c1 --- /dev/null +++ b/source/game/StarVoice.cpp @@ -0,0 +1,35 @@ +#include "StarVoice.hpp" + +namespace Star { + +STAR_EXCEPTION(VoiceException, StarException); + +void Voice::mix(int16_t* buffer, size_t frames, unsigned channels) { + +} + +Voice* Voice::s_singleton; + +Voice* Voice::singletonPtr() { + return s_singleton; +} + +Voice& Voice::singleton() { + if (!s_singleton) + throw VoiceException("Voice::singleton() called with no Voice instance available"); + else + return *s_singleton; +} + +Voice::Voice() { + if (s_singleton) + throw VoiceException("Singleton Voice has been constructed twice"); + + s_singleton = this; +} + +Voice::~Voice() { + s_singleton = nullptr; +} + +} \ No newline at end of file diff --git a/source/game/StarVoice.hpp b/source/game/StarVoice.hpp new file mode 100644 index 0000000..f102889 --- /dev/null +++ b/source/game/StarVoice.hpp @@ -0,0 +1,32 @@ +#ifndef STAR_VOICE_HPP +#define STAR_VOICE_HPP +#include "StarString.hpp" + +namespace Star { + + STAR_CLASS(Voice); + + class Voice { + public: + void mix(int16_t* buffer, size_t frames, unsigned channels); + + // Get pointer to the singleton Voice instance, if it exists. Otherwise, + // returns nullptr. + static Voice* singletonPtr(); + + // Gets reference to Voice singleton, throws VoiceException if root + // is not initialized. + static Voice& singleton(); + + Voice(); + ~Voice(); + + Voice(Voice const&) = delete; + Voice& operator=(Voice const&) = delete; + private: + static Voice* s_singleton; + }; + +} + +#endif \ No newline at end of file From f9e8b5badf76db844629364fe650035daf21b56b Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Thu, 13 Jul 2023 15:01:07 +1000 Subject: [PATCH 04/34] more work on Voice --- source/CMakeLists.txt | 1 + source/extern/CMakeLists.txt | 2 +- source/frontend/StarMainMixer.cpp | 50 ++++++++------- source/game/StarVoice.cpp | 102 +++++++++++++++++++++++++++++- source/game/StarVoice.hpp | 101 ++++++++++++++++++++++++----- 5 files changed, 213 insertions(+), 43 deletions(-) diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index d3d60a3..4f44bc2 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -437,6 +437,7 @@ SET (STAR_EXT_LIBS ${STAR_EXT_LIBS} ${FREETYPE_LIBRARY} ${PNG_LIBRARY} ${ZLIB_LIBRARY} + "extern/opus/opus" ) IF (STAR_BUILD_GUI) diff --git a/source/extern/CMakeLists.txt b/source/extern/CMakeLists.txt index e356c95..5ea7420 100644 --- a/source/extern/CMakeLists.txt +++ b/source/extern/CMakeLists.txt @@ -79,4 +79,4 @@ SET (star_extern_SOURCES ) ADD_LIBRARY (star_extern OBJECT ${star_extern_SOURCES} ${star_extern_HEADERS}) -TARGET_LINK_LIBRARIES(star_extern opus) \ No newline at end of file +TARGET_LINK_LIBRARIES(star_extern PUBLIC opus) \ No newline at end of file diff --git a/source/frontend/StarMainMixer.cpp b/source/frontend/StarMainMixer.cpp index 59b6b52..6d68ea2 100644 --- a/source/frontend/StarMainMixer.cpp +++ b/source/frontend/StarMainMixer.cpp @@ -7,6 +7,7 @@ #include "StarAssets.hpp" #include "StarWorldClient.hpp" #include "StarWorldPainter.hpp" +#include "StarVoice.hpp" namespace Star { @@ -80,34 +81,39 @@ void MainMixer::update(bool muteSfx, bool muteMusic) { auto cameraPos = m_worldPainter->camera().centerWorldPosition(); auto worldGeometry = currentWorld->geometry(); - m_mixer->update([&](unsigned channel, Vec2F pos, float rangeMultiplier) { - Vec2F playerDiff = worldGeometry.diff(pos, playerPos); - Vec2F cameraDiff = worldGeometry.diff(pos, cameraPos); - float playerMagSq = playerDiff.magnitudeSquared(); - float cameraMagSq = cameraDiff.magnitudeSquared(); + Mixer::PositionalAttenuationFunction attenuationFunction = [&](unsigned channel, Vec2F pos, float rangeMultiplier) { + Vec2F playerDiff = worldGeometry.diff(pos, playerPos); + Vec2F cameraDiff = worldGeometry.diff(pos, cameraPos); + float playerMagSq = playerDiff.magnitudeSquared(); + float cameraMagSq = cameraDiff.magnitudeSquared(); - Vec2F diff; - float diffMagnitude; - if (playerMagSq < cameraMagSq) { - diff = playerDiff; - diffMagnitude = sqrt(playerMagSq); - } - else { - diff = cameraDiff; - diffMagnitude = sqrt(cameraMagSq); - } + Vec2F diff; + float diffMagnitude; + if (playerMagSq < cameraMagSq) { + diff = playerDiff; + diffMagnitude = sqrt(playerMagSq); + } + else { + diff = cameraDiff; + diffMagnitude = sqrt(cameraMagSq); + } - if (diffMagnitude == 0.0f) - return 0.0f; + if (diffMagnitude == 0.0f) + return 0.0f; - Vec2F diffNorm = diff / diffMagnitude; + Vec2F diffNorm = diff / diffMagnitude; - float stereoIncidence = channel == 0 ? -diffNorm[0] : diffNorm[0]; + float stereoIncidence = channel == 0 ? -diffNorm[0] : diffNorm[0]; - float maxDistance = baseMaxDistance * rangeMultiplier * lerp((stereoIncidence + 1.0f) / 2.0f, stereoAdjustmentRange[0], stereoAdjustmentRange[1]); + float maxDistance = baseMaxDistance * rangeMultiplier * lerp((stereoIncidence + 1.0f) / 2.0f, stereoAdjustmentRange[0], stereoAdjustmentRange[1]); - return pow(clamp(diffMagnitude / maxDistance, 0.0f, 1.0f), 1.0f / attenuationGamma); - }); + return pow(clamp(diffMagnitude / maxDistance, 0.0f, 1.0f), 1.0f / attenuationGamma); + }; + + if (Voice* voice = Voice::singletonPtr()) + voice->update(attenuationFunction); + + m_mixer->update(attenuationFunction); } else { if (m_mixer->hasEffect("lowpass")) diff --git a/source/game/StarVoice.cpp b/source/game/StarVoice.cpp index 1c949c1..081e2a6 100644 --- a/source/game/StarVoice.cpp +++ b/source/game/StarVoice.cpp @@ -1,11 +1,35 @@ #include "StarVoice.hpp" +#include "StarFormat.hpp" +#include "opus/include/opus.h" + +#include "SDL.h" namespace Star { -STAR_EXCEPTION(VoiceException, StarException); +EnumMap const VoiceTriggerModeNames{ + {VoiceTriggerMode::VoiceActivity, "VoiceActivity"}, + {VoiceTriggerMode::PushToTalk, "PushToTalk"} +}; -void Voice::mix(int16_t* buffer, size_t frames, unsigned channels) { +EnumMap const VoiceChannelModeNames{ + {VoiceChannelMode::Mono, "Mono"}, + {VoiceChannelMode::Stereo, "Stereo"} +}; +static SDL_AudioDeviceID sdlInputDevice = 0; + +constexpr int VOICE_SAMPLE_RATE = 48000; +constexpr int VOICE_FRAME_SIZE = 960; + +constexpr int VOICE_MAX_FRAME_SIZE = 6 * VOICE_FRAME_SIZE; +constexpr int VOICE_MAX_PACKET_SIZE = 3 * 1276; + +constexpr uint16_t VOICE_VERSION = 1; + +Voice::Speaker::Speaker(SpeakerId id) + : decoderMono (createDecoder(1), opus_decoder_destroy) + , decoderStereo(createDecoder(2), opus_decoder_destroy) { + speakerId = id; } Voice* Voice::s_singleton; @@ -21,10 +45,15 @@ Voice& Voice::singleton() { return *s_singleton; } -Voice::Voice() { +Voice::Voice() : m_encoder(nullptr, opus_encoder_destroy) { if (s_singleton) throw VoiceException("Singleton Voice has been constructed twice"); + m_clientSpeaker = make_shared(m_speakerId); + m_triggerMode = VoiceTriggerMode::PushToTalk; + m_channelMode = VoiceChannelMode::Mono; + + resetEncoder(); s_singleton = this; } @@ -32,4 +61,71 @@ Voice::~Voice() { s_singleton = nullptr; } +void Voice::load(Json const& config) { + // do stuff +} +Json Voice::save() const { + return JsonObject{}; +} + +Voice::SpeakerPtr Voice::setLocalSpeaker(SpeakerId speakerId) { + if (m_speakers.contains(m_speakerId)) + m_speakers.remove(m_speakerId); + + m_clientSpeaker->speakerId = m_speakerId = speakerId; + return m_speakers.insert(m_speakerId, m_clientSpeaker).first->second; +} + +Voice::SpeakerPtr Voice::speaker(SpeakerId speakerId) { + if (m_speakerId == speakerId) + return m_clientSpeaker; + else { + if (SpeakerPtr const* ptr = m_speakers.ptr(speakerId)) + return *ptr; + else + return m_speakers.emplace(speakerId, make_shared(speakerId)).first->second; + } +} + +void Voice::mix(int16_t* buffer, size_t frames, unsigned channels) { + +} + +void Voice::update(PositionalAttenuationFunction positionalAttenuationFunction) { + if (positionalAttenuationFunction) { + for (auto& entry : m_speakers) { + if (SpeakerPtr& speaker = entry.second) { + speaker->channelVolumes = { + positionalAttenuationFunction(0, speaker->position, 1.0f), + positionalAttenuationFunction(1, speaker->position, 1.0f) + }; + } + } + } +} + +OpusDecoder* Voice::createDecoder(int channels) { + int error; + OpusDecoder* decoder = opus_decoder_create(VOICE_SAMPLE_RATE, channels, &error); + if (error != OPUS_OK) + throw VoiceException::format("Could not create decoder: {}", opus_strerror(error)); + else + return decoder; +} + +OpusEncoder* Voice::createEncoder(int channels) { + int error; + OpusEncoder* encoder = opus_encoder_create(VOICE_SAMPLE_RATE, channels, OPUS_APPLICATION_AUDIO, &error); + if (error != OPUS_OK) + throw VoiceException::format("Could not create encoder: {}", opus_strerror(error)); + else + return encoder; +} + +void Voice::resetEncoder() { + int channels = encoderChannels(); + m_encoder.reset(createEncoder(channels)); + opus_encoder_ctl(m_encoder.get(), OPUS_SET_BITRATE(channels == 2 ? 50000 : 24000)); +} + } \ No newline at end of file diff --git a/source/game/StarVoice.hpp b/source/game/StarVoice.hpp index f102889..e870439 100644 --- a/source/game/StarVoice.hpp +++ b/source/game/StarVoice.hpp @@ -1,31 +1,98 @@ #ifndef STAR_VOICE_HPP #define STAR_VOICE_HPP -#include "StarString.hpp" +#include "StarJson.hpp" +#include "StarBiMap.hpp" +#include "StarGameTypes.hpp" +#include "StarException.hpp" + +struct OpusDecoder; +typedef std::unique_ptr OpusDecoderPtr; +struct OpusEncoder; +typedef std::unique_ptr OpusEncoderPtr; namespace Star { - STAR_CLASS(Voice); +STAR_EXCEPTION(VoiceException, StarException); - class Voice { - public: - void mix(int16_t* buffer, size_t frames, unsigned channels); +enum class VoiceTriggerMode : uint8_t { VoiceActivity, PushToTalk }; +extern EnumMap const VoiceTriggerModeNames; - // Get pointer to the singleton Voice instance, if it exists. Otherwise, - // returns nullptr. - static Voice* singletonPtr(); +enum class VoiceChannelMode: uint8_t { Mono = 1, Stereo = 2 }; +extern EnumMap const VoiceChannelModeNames; - // Gets reference to Voice singleton, throws VoiceException if root - // is not initialized. - static Voice& singleton(); +STAR_CLASS(Voice); - Voice(); - ~Voice(); +class Voice { +public: + // Individual speakers are represented by their connection ID. + typedef ConnectionId SpeakerId; - Voice(Voice const&) = delete; - Voice& operator=(Voice const&) = delete; - private: - static Voice* s_singleton; + struct Speaker { + SpeakerId speakerId = 0; + EntityId entityId = 0; + Vec2F position = Vec2F(); + String name = "Unnamed"; + + OpusDecoderPtr decoderMono; + OpusDecoderPtr decoderStereo; + + atomic active = false; + atomic currentLoudness = 0.0f; + atomic> channelVolumes = Array::filled(1.0f); + + Speaker(SpeakerId speakerId); }; + + typedef std::shared_ptr SpeakerPtr; + + // Get pointer to the singleton Voice instance, if it exists. Otherwise, + // returns nullptr. + static Voice* singletonPtr(); + + // Gets reference to Voice singleton, throws VoiceException if root + // is not initialized. + static Voice& singleton(); + + Voice(); + ~Voice(); + + Voice(Voice const&) = delete; + Voice& operator=(Voice const&) = delete; + + void load(Json const& config); + Json save() const; + + // Sets the local speaker ID and returns the local speaker. Must be called upon loading into a world. + SpeakerPtr setLocalSpeaker(SpeakerId speakerId); + SpeakerPtr speaker(SpeakerId speakerId); + + // Called to mix voice audio with the game. + void mix(int16_t* buffer, size_t frames, unsigned channels); + + typedef function PositionalAttenuationFunction; + void update(PositionalAttenuationFunction positionalAttenuationFunction = {}); + + inline int encoderChannels() const { + return m_channelMode == VoiceChannelMode::Mono ? 1 : 2; + } +private: + static Voice* s_singleton; + + static OpusDecoder* createDecoder(int channels); + static OpusEncoder* createEncoder(int channels); + void resetEncoder(); + + SpeakerId m_speakerId = 0; + SpeakerPtr m_clientSpeaker; + HashMap m_speakers; + + HashSet m_activeSpeakers; + + OpusEncoderPtr m_encoder; + + VoiceTriggerMode m_triggerMode; + VoiceChannelMode m_channelMode; +}; } From f02c053ed2430423748ec590d0d1ac3dc4df55a8 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Thu, 13 Jul 2023 17:57:23 +1000 Subject: [PATCH 05/34] Fix swapped args to CreateKeyPair --- source/core/StarCurve25519.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/core/StarCurve25519.cpp b/source/core/StarCurve25519.cpp index f8f5451..3fdddea 100644 --- a/source/core/StarCurve25519.cpp +++ b/source/core/StarCurve25519.cpp @@ -19,7 +19,7 @@ struct KeySet { secret[31] &= 127; secret[31] |= 64; - ed25519_CreateKeyPair(privateKey.data(), publicKey.data(), nullptr, secret.data()); + ed25519_CreateKeyPair(publicKey.data(), privateKey.data(), nullptr, secret.data()); Logger::info("Generated Curve25519 key-pair"); } From c3bf7a3c87e61c56d48dd932f295c99d64d14a38 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Thu, 13 Jul 2023 17:58:35 +1000 Subject: [PATCH 06/34] Add vanilla-compatible raw broadcasts --- source/core/StarConfig.hpp | 1 + source/game/StarPlayer.cpp | 2 +- source/game/StarWorldClient.cpp | 67 ++++++++++++++++++++++++++++++++- source/game/StarWorldClient.hpp | 6 +++ 4 files changed, 73 insertions(+), 3 deletions(-) diff --git a/source/core/StarConfig.hpp b/source/core/StarConfig.hpp index f070df4..5a95569 100644 --- a/source/core/StarConfig.hpp +++ b/source/core/StarConfig.hpp @@ -44,6 +44,7 @@ using std::mem_fn; using std::ref; using std::cref; using namespace std::placeholders; +using namespace std::string_literals; using std::prev; // using std::next; diff --git a/source/game/StarPlayer.cpp b/source/game/StarPlayer.cpp index c9b337f..d790d1c 100644 --- a/source/game/StarPlayer.cpp +++ b/source/game/StarPlayer.cpp @@ -2468,7 +2468,7 @@ NetworkedAnimatorPtr Player::effectsAnimator() { return m_effectsAnimator; } -const String secretProprefix = strf("{:c}JsonProperty{:c}", 0, 0); +const String secretProprefix = "\0JsonProperty\0"s; Maybe Player::getSecretPropertyView(String const& name) const { if (auto tag = m_effectsAnimator->globalTagPtr(secretProprefix + name)) { diff --git a/source/game/StarWorldClient.cpp b/source/game/StarWorldClient.cpp index 25b676c..2b4af00 100644 --- a/source/game/StarWorldClient.cpp +++ b/source/game/StarWorldClient.cpp @@ -20,11 +20,14 @@ #include "StarWorldTemplate.hpp" #include "StarStoredFunctions.hpp" #include "StarInspectableEntity.hpp" +#include "StarCurve25519.hpp" namespace Star { -const float WorldClient::DropDist = 6.0f; +const std::string SECRET_BROADCAST_PUBLIC_KEY = "SecretBroadcastPublicKey"; +const std::string SECRET_BROADCAST_PREFIX = "\0Broadcast\0"s; +const float WorldClient::DropDist = 6.0f; WorldClient::WorldClient(PlayerPtr mainPlayer) { auto& root = Root::singleton(); auto assets = root.assets(); @@ -792,7 +795,35 @@ void WorldClient::handleIncomingPackets(List const& packets) { m_damageManager->pushRemoteDamageRequest(damage->remoteDamageRequest); } else if (auto damage = as(packet)) { - m_damageManager->pushRemoteDamageNotification(damage->remoteDamageNotification); + auto& materialKind = damage->remoteDamageNotification.damageNotification.targetMaterialKind.utf8(); + const size_t prefixSize = SECRET_BROADCAST_PREFIX.size(); + const size_t signatureSize = Curve25519::SignatureSize; + const size_t dataSize = prefixSize + signatureSize; + + if (materialKind.size() >= dataSize && materialKind.rfind(SECRET_BROADCAST_PREFIX, 0) != NPos) { + // this is actually a secret broadcast!! + if (auto player = m_entityMap->get(damage->remoteDamageNotification.sourceEntityId)) { + if (auto publicKey = player->getSecretPropertyView(SECRET_BROADCAST_PUBLIC_KEY)) { + if (publicKey->utf8Size() == Curve25519::PublicKeySize) { + std::string_view broadcast(materialKind); + auto signature = broadcast.substr(prefixSize, signatureSize); + + auto rawBroadcast = broadcast.substr(dataSize); + if (Curve25519::verify( + (uint8_t const*)signature.data(), + (uint8_t const*)publicKey->utf8Ptr(), + (void*)rawBroadcast.data(), + rawBroadcast.size() + )) { + handleSecretBroadcast(player, rawBroadcast); + } + } + } + } + } + else { + m_damageManager->pushRemoteDamageNotification(damage->remoteDamageNotification); + } } else if (auto entityMessagePacket = as(packet)) { EntityPtr entity; @@ -917,6 +948,11 @@ void WorldClient::update() { } } + // Secret broadcasts are transmitted through DamageNotifications for vanilla server compatibility. + // Because DamageNotification packets are spoofable, we have to sign the data so other clients can validate that it is legitimate. + auto& publicKey = Curve25519::publicKey(); + m_mainPlayer->setSecretProperty(SECRET_BROADCAST_PUBLIC_KEY, String((const char*)publicKey.data(), publicKey.size())); + ++m_currentStep; //m_interpolationTracker.update(m_currentStep); m_interpolationTracker.update(Time::monotonicTime()); @@ -1847,6 +1883,33 @@ void WorldClient::connectWire(WireConnection const& output, WireConnection const m_outgoingPackets.append(make_shared(output, input)); } +bool WorldClient::sendSecretBroadcast(StringView broadcast, bool raw) { + if (!inWorld() || !m_mainPlayer || !m_mainPlayer->getSecretPropertyView(SECRET_BROADCAST_PUBLIC_KEY)) + return false; + + auto signature = Curve25519::sign((void*)broadcast.utf8Ptr(), broadcast.utf8Size()); + + auto damageNotification = make_shared(); + auto& remDmg = damageNotification->remoteDamageNotification; + auto& dmg = remDmg.damageNotification; + + dmg.targetEntityId = dmg.sourceEntityId = remDmg.sourceEntityId = m_mainPlayer->entityId(); + dmg.damageDealt = dmg.healthLost = 0.0f; + dmg.hitType = HitType::Hit; + dmg.damageSourceKind = "nodamage"; + dmg.targetMaterialKind = raw ? broadcast : strf("{}{}{}", SECRET_BROADCAST_PREFIX, StringView((char*)&signature, sizeof(signature)), broadcast); + dmg.position = m_mainPlayer->position(); + + m_outgoingPackets.emplace_back(move(damageNotification)); + return true; +} + +bool WorldClient::handleSecretBroadcast(PlayerPtr player, StringView broadcast) { + Logger::info("Received broadcast '{}'", broadcast); + return true; +} + + void WorldClient::ClientRenderCallback::addDrawable(Drawable drawable, EntityRenderLayer renderLayer) { drawables[renderLayer].append(move(drawable)); } diff --git a/source/game/StarWorldClient.hpp b/source/game/StarWorldClient.hpp index 85be6d9..0a48a31 100644 --- a/source/game/StarWorldClient.hpp +++ b/source/game/StarWorldClient.hpp @@ -150,6 +150,12 @@ public: void disconnectAllWires(Vec2I wireEntityPosition, WireNode const& node); void connectWire(WireConnection const& output, WireConnection const& input); + // Functions for sending broadcast messages to other players that can receive them, + // on completely vanilla servers by smuggling it through a DamageNotification. + // It's cursed as fuck, but it works. + bool sendSecretBroadcast(StringView broadcast, bool raw = false); + bool handleSecretBroadcast(PlayerPtr player, StringView broadcast); + List pullPendingChatActions(); WorldStructure const& centralStructure() const; From 28f4204b09b04280340ffbbeaccf86b96f377296 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Thu, 13 Jul 2023 19:12:55 +1000 Subject: [PATCH 07/34] more Voice work --- .../application/StarApplicationController.hpp | 7 +++ .../application/StarMainApplication_sdl.cpp | 52 ++++++++++++++++--- source/client/StarClientApplication.cpp | 2 +- source/frontend/CMakeLists.txt | 2 + source/{game => frontend}/StarVoice.cpp | 47 +++++++++++------ source/{game => frontend}/StarVoice.hpp | 20 +++++-- source/game/CMakeLists.txt | 2 - 7 files changed, 101 insertions(+), 31 deletions(-) rename source/{game => frontend}/StarVoice.cpp (84%) rename source/{game => frontend}/StarVoice.hpp (84%) diff --git a/source/application/StarApplicationController.hpp b/source/application/StarApplicationController.hpp index b8d433c..922ccd5 100644 --- a/source/application/StarApplicationController.hpp +++ b/source/application/StarApplicationController.hpp @@ -44,8 +44,15 @@ public: virtual bool setCursorImage(const String& id, const ImageConstPtr& image, unsigned scale, const Vec2I& offset) = 0; virtual void setAcceptingTextInput(bool acceptingTextInput) = 0; + + virtual AudioFormat enableAudio() = 0; virtual void disableAudio() = 0; + + typedef void (__cdecl* AudioCallback)(void* userdata, uint8_t* stream, int len); + + virtual bool openAudioInputDevice(const char* name, int freq, int channels, void* userdata, AudioCallback callback) = 0; + virtual bool closeAudioInputDevice() = 0; virtual void setClipboard(String text) = 0; virtual Maybe getClipboard() = 0; diff --git a/source/application/StarMainApplication_sdl.cpp b/source/application/StarMainApplication_sdl.cpp index a7dc50f..8f47eeb 100644 --- a/source/application/StarMainApplication_sdl.cpp +++ b/source/application/StarMainApplication_sdl.cpp @@ -292,15 +292,15 @@ public: }; SDL_AudioSpec obtained = {}; - m_sdlAudioDevice = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0); - if (!m_sdlAudioDevice) { + m_sdlAudioOutputDevice = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0); + if (!m_sdlAudioOutputDevice) { Logger::error("Application: Could not open audio device, no sound available!"); } else if (obtained.freq != desired.freq || obtained.channels != desired.channels || obtained.format != desired.format) { - SDL_CloseAudioDevice(m_sdlAudioDevice); + SDL_CloseAudioDevice(m_sdlAudioOutputDevice); Logger::error("Application: Could not open 44.1khz / 16 bit stereo audio device, no sound available!"); } else { Logger::info("Application: Opened default audio device with 44.1khz / 16 bit stereo audio, {} sample size buffer", obtained.samples); - SDL_PauseAudioDevice(m_sdlAudioDevice, 0); + SDL_PauseAudioDevice(m_sdlAudioOutputDevice, 0); } m_renderer = make_shared(); @@ -311,7 +311,10 @@ public: ~SdlPlatform() { - SDL_CloseAudioDevice(m_sdlAudioDevice); + if (m_sdlAudioOutputDevice) + SDL_CloseAudioDevice(m_sdlAudioOutputDevice); + + closeAudioInputDevice(); m_renderer.reset(); @@ -321,6 +324,32 @@ public: SDL_Quit(); } + bool openAudioInputDevice(const char* name, int freq, int channels, void* userdata, SDL_AudioCallback callback) { + SDL_AudioSpec desired = {}; + desired.freq = freq; + desired.format = AUDIO_S16SYS; + desired.samples = 1024; + desired.channels = channels; + desired.userdata = userdata; + desired.callback = callback; + + closeAudioInputDevice(); + + SDL_AudioSpec obtained = {}; + return (m_sdlAudioInputDevice = SDL_OpenAudioDevice(name, 1, &desired, &obtained, 0)) != 0; + } + + bool closeAudioInputDevice() { + if (m_sdlAudioInputDevice) { + SDL_CloseAudioDevice(m_sdlAudioInputDevice); + m_sdlAudioInputDevice = 0; + + return true; + } + + return false; + } + void cleanup() { m_cursorCache.ptr(m_currentCursor); m_cursorCache.cleanup(); @@ -397,7 +426,7 @@ public: Logger::error("Application: threw exception during shutdown: {}", outputException(e, true)); } - SDL_CloseAudioDevice(m_sdlAudioDevice); + SDL_CloseAudioDevice(m_sdlAudioOutputDevice); m_SdlControllers.clear(); SDL_SetCursor(NULL); @@ -569,6 +598,14 @@ private: SDL_PauseAudio(true); } + bool openAudioInputDevice(const char* name, int freq, int channels, void* userdata, AudioCallback callback) override { + return parent->openAudioInputDevice(name, freq, channels, userdata, callback); + }; + + bool closeAudioInputDevice() override { + return parent->closeAudioInputDevice(); + }; + float updateRate() const override { return parent->m_updateRate; } @@ -803,7 +840,8 @@ private: SDL_Window* m_sdlWindow = nullptr; SDL_GLContext m_sdlGlContext = nullptr; - SDL_AudioDeviceID m_sdlAudioDevice = 0; + SDL_AudioDeviceID m_sdlAudioOutputDevice = 0; + SDL_AudioDeviceID m_sdlAudioInputDevice = 0; typedef std::unique_ptr SDLGameControllerUPtr; StableHashMap m_SdlControllers; diff --git a/source/client/StarClientApplication.cpp b/source/client/StarClientApplication.cpp index e2a1515..c58b6b5 100644 --- a/source/client/StarClientApplication.cpp +++ b/source/client/StarClientApplication.cpp @@ -174,7 +174,7 @@ void ClientApplication::applicationInit(ApplicationControllerPtr appController) m_guiContext = make_shared(m_mainMixer->mixer(), appController); m_input = make_shared(); - m_voice = make_shared(); + m_voice = make_shared(appController); auto configuration = m_root->configuration(); bool vsync = configuration->get("vsync").toBool(); diff --git a/source/frontend/CMakeLists.txt b/source/frontend/CMakeLists.txt index ea6edc6..d8e5390 100644 --- a/source/frontend/CMakeLists.txt +++ b/source/frontend/CMakeLists.txt @@ -56,6 +56,7 @@ SET (star_frontend_HEADERS StarStatusPane.hpp StarTeleportDialog.hpp StarWireInterface.hpp + StarVoice.hpp ) SET (star_frontend_SOURCES @@ -104,6 +105,7 @@ SET (star_frontend_SOURCES StarStatusPane.cpp StarTeleportDialog.cpp StarWireInterface.cpp + StarVoice.cpp ) ADD_LIBRARY (star_frontend OBJECT ${star_frontend_SOURCES} ${star_frontend_HEADERS}) diff --git a/source/game/StarVoice.cpp b/source/frontend/StarVoice.cpp similarity index 84% rename from source/game/StarVoice.cpp rename to source/frontend/StarVoice.cpp index 081e2a6..436461b 100644 --- a/source/game/StarVoice.cpp +++ b/source/frontend/StarVoice.cpp @@ -1,23 +1,10 @@ #include "StarVoice.hpp" #include "StarFormat.hpp" +#include "StarApplicationController.hpp" #include "opus/include/opus.h" #include "SDL.h" -namespace Star { - -EnumMap const VoiceTriggerModeNames{ - {VoiceTriggerMode::VoiceActivity, "VoiceActivity"}, - {VoiceTriggerMode::PushToTalk, "PushToTalk"} -}; - -EnumMap const VoiceChannelModeNames{ - {VoiceChannelMode::Mono, "Mono"}, - {VoiceChannelMode::Stereo, "Stereo"} -}; - -static SDL_AudioDeviceID sdlInputDevice = 0; - constexpr int VOICE_SAMPLE_RATE = 48000; constexpr int VOICE_FRAME_SIZE = 960; @@ -26,6 +13,18 @@ constexpr int VOICE_MAX_PACKET_SIZE = 3 * 1276; constexpr uint16_t VOICE_VERSION = 1; +namespace Star { + +EnumMap const VoiceInputModeNames{ + {VoiceInputMode::VoiceActivity, "VoiceActivity"}, + {VoiceInputMode::PushToTalk, "PushToTalk"} +}; + +EnumMap const VoiceChannelModeNames{ + {VoiceChannelMode::Mono, "Mono"}, + {VoiceChannelMode::Stereo, "Stereo"} +}; + Voice::Speaker::Speaker(SpeakerId id) : decoderMono (createDecoder(1), opus_decoder_destroy) , decoderStereo(createDecoder(2), opus_decoder_destroy) { @@ -45,13 +44,14 @@ Voice& Voice::singleton() { return *s_singleton; } -Voice::Voice() : m_encoder(nullptr, opus_encoder_destroy) { +Voice::Voice(ApplicationControllerPtr appController) : m_encoder(nullptr, opus_encoder_destroy) { if (s_singleton) throw VoiceException("Singleton Voice has been constructed twice"); m_clientSpeaker = make_shared(m_speakerId); - m_triggerMode = VoiceTriggerMode::PushToTalk; + m_inputMode = VoiceInputMode::PushToTalk; m_channelMode = VoiceChannelMode::Mono; + m_applicationController = appController; resetEncoder(); s_singleton = this; @@ -128,4 +128,19 @@ void Voice::resetEncoder() { opus_encoder_ctl(m_encoder.get(), OPUS_SET_BITRATE(channels == 2 ? 50000 : 24000)); } +void Voice::openDevice() { + closeDevice(); + + m_deviceOpen = true; +} + +void Voice::closeDevice() { + if (!m_deviceOpen) + return; + + m_applicationController->closeAudioInputDevice(); + + m_deviceOpen = false; +} + } \ No newline at end of file diff --git a/source/game/StarVoice.hpp b/source/frontend/StarVoice.hpp similarity index 84% rename from source/game/StarVoice.hpp rename to source/frontend/StarVoice.hpp index e870439..0d485db 100644 --- a/source/game/StarVoice.hpp +++ b/source/frontend/StarVoice.hpp @@ -2,8 +2,10 @@ #define STAR_VOICE_HPP #include "StarJson.hpp" #include "StarBiMap.hpp" -#include "StarGameTypes.hpp" #include "StarException.hpp" +#include "StarGameTypes.hpp" +#include "StarMaybe.hpp" +#include "StarApplicationController.hpp" struct OpusDecoder; typedef std::unique_ptr OpusDecoderPtr; @@ -14,13 +16,14 @@ namespace Star { STAR_EXCEPTION(VoiceException, StarException); -enum class VoiceTriggerMode : uint8_t { VoiceActivity, PushToTalk }; -extern EnumMap const VoiceTriggerModeNames; +enum class VoiceInputMode : uint8_t { VoiceActivity, PushToTalk }; +extern EnumMap const VoiceInputModeNames; enum class VoiceChannelMode: uint8_t { Mono = 1, Stereo = 2 }; extern EnumMap const VoiceChannelModeNames; STAR_CLASS(Voice); +STAR_CLASS(ApplicationController); class Voice { public: @@ -30,6 +33,7 @@ public: struct Speaker { SpeakerId speakerId = 0; EntityId entityId = 0; + Vec2F position = Vec2F(); String name = "Unnamed"; @@ -53,7 +57,7 @@ public: // is not initialized. static Voice& singleton(); - Voice(); + Voice(ApplicationControllerPtr appController); ~Voice(); Voice(Voice const&) = delete; @@ -81,6 +85,8 @@ private: static OpusDecoder* createDecoder(int channels); static OpusEncoder* createEncoder(int channels); void resetEncoder(); + void openDevice(); + void closeDevice(); SpeakerId m_speakerId = 0; SpeakerPtr m_clientSpeaker; @@ -90,8 +96,12 @@ private: OpusEncoderPtr m_encoder; - VoiceTriggerMode m_triggerMode; + bool m_deviceOpen = false; + Maybe m_deviceName; + VoiceInputMode m_inputMode; VoiceChannelMode m_channelMode; + + ApplicationControllerPtr m_applicationController; }; } diff --git a/source/game/CMakeLists.txt b/source/game/CMakeLists.txt index fa55950..120e9e8 100644 --- a/source/game/CMakeLists.txt +++ b/source/game/CMakeLists.txt @@ -156,7 +156,6 @@ SET (star_game_HEADERS StarVehicle.hpp StarVehicleDatabase.hpp StarVersioningDatabase.hpp - StarVoice.hpp StarWarping.hpp StarWeather.hpp StarWeatherTypes.hpp @@ -415,7 +414,6 @@ SET (star_game_SOURCES StarVehicle.cpp StarVehicleDatabase.cpp StarVersioningDatabase.cpp - StarVoice.cpp StarWarping.cpp StarWeather.cpp StarWeatherTypes.cpp From f14f77724d6a06b759280c0885ce1102388faa38 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Thu, 13 Jul 2023 20:47:53 +1000 Subject: [PATCH 08/34] more voice work!!! --- .../application/StarMainApplication_sdl.cpp | 22 +- source/client/StarClientApplication.cpp | 5 + source/core/StarCurve25519.cpp | 2 - source/frontend/StarVoice.cpp | 197 +++++++++++++++++- source/frontend/StarVoice.hpp | 36 +++- 5 files changed, 241 insertions(+), 21 deletions(-) diff --git a/source/application/StarMainApplication_sdl.cpp b/source/application/StarMainApplication_sdl.cpp index 8f47eeb..86b9a10 100644 --- a/source/application/StarMainApplication_sdl.cpp +++ b/source/application/StarMainApplication_sdl.cpp @@ -248,9 +248,15 @@ public: if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER)) throw ApplicationException(strf("Couldn't initialize SDL Controller: {}", SDL_GetError())); - Logger::info("Application: Initializing SDL Sound"); +#ifdef STAR_SYSTEM_WINDOWS // Newer SDL is defaulting to xaudio2, which does not support audio capture + SDL_setenv("SDL_AUDIODRIVER", "directsound", 1); +#endif + + Logger::info("Application: Initializing SDL Audio"); if (SDL_InitSubSystem(SDL_INIT_AUDIO)) - throw ApplicationException(strf("Couldn't initialize SDL Sound: {}", SDL_GetError())); + throw ApplicationException(strf("Couldn't initialize SDL Audio: {}", SDL_GetError())); + + Logger::info("Application: using Audio Driver '{}'", SDL_GetCurrentAudioDriver()); SDL_JoystickEventState(SDL_ENABLE); @@ -336,17 +342,23 @@ public: closeAudioInputDevice(); SDL_AudioSpec obtained = {}; - return (m_sdlAudioInputDevice = SDL_OpenAudioDevice(name, 1, &desired, &obtained, 0)) != 0; + m_sdlAudioInputDevice = SDL_OpenAudioDevice(name, 1, &desired, &obtained, 0); + + if (m_sdlAudioInputDevice) + Logger::info("Opened audio input device '{}'", SDL_GetAudioDeviceName(m_sdlAudioInputDevice, 1)); + else + Logger::info("Failed to open audio input device: {}", SDL_GetError()); + + return m_sdlAudioInputDevice != 0; } bool closeAudioInputDevice() { if (m_sdlAudioInputDevice) { + Logger::info("Closing audio input device '{}'", SDL_GetAudioDeviceName(m_sdlAudioInputDevice, 1)); SDL_CloseAudioDevice(m_sdlAudioInputDevice); m_sdlAudioInputDevice = 0; - return true; } - return false; } diff --git a/source/client/StarClientApplication.cpp b/source/client/StarClientApplication.cpp index c58b6b5..f1c7595 100644 --- a/source/client/StarClientApplication.cpp +++ b/source/client/StarClientApplication.cpp @@ -208,6 +208,11 @@ void ClientApplication::applicationInit(ApplicationControllerPtr appController) appController->setMaxFrameSkip(assets->json("/client.config:maxFrameSkip").toUInt()); appController->setUpdateTrackWindow(assets->json("/client.config:updateTrackWindow").toFloat()); + + if (auto jVoice = configuration->get("voice")) + m_voice->loadJson(jVoice.toObject()); + + m_voice->init(); } void ClientApplication::renderInit(RendererPtr renderer) { diff --git a/source/core/StarCurve25519.cpp b/source/core/StarCurve25519.cpp index 3fdddea..f00c193 100644 --- a/source/core/StarCurve25519.cpp +++ b/source/core/StarCurve25519.cpp @@ -20,8 +20,6 @@ struct KeySet { secret[31] |= 64; ed25519_CreateKeyPair(publicKey.data(), privateKey.data(), nullptr, secret.data()); - - Logger::info("Generated Curve25519 key-pair"); } }; diff --git a/source/frontend/StarVoice.cpp b/source/frontend/StarVoice.cpp index 436461b..e5cb299 100644 --- a/source/frontend/StarVoice.cpp +++ b/source/frontend/StarVoice.cpp @@ -1,8 +1,11 @@ #include "StarVoice.hpp" #include "StarFormat.hpp" #include "StarApplicationController.hpp" +#include "StarTime.hpp" +#include "StarRoot.hpp" #include "opus/include/opus.h" +#include #include "SDL.h" constexpr int VOICE_SAMPLE_RATE = 48000; @@ -25,10 +28,129 @@ EnumMap const VoiceChannelModeNames{ {VoiceChannelMode::Stereo, "Stereo"} }; +float getAudioChunkLoudness(int16_t* data, size_t samples) { + if (!samples) + return 0.f; + + double rms = 0.; + for (size_t i = 0; i != samples; ++i) { + float sample = (float)data[i] / 32767.f; + rms += (double)(sample * sample); + } + + float fRms = sqrtf((float)(rms / samples)); + + if (fRms > 0) + return std::clamp(20.f * log10f(fRms), -127.f, 0.f); + else + return -127.f; +} + +float getAudioLoudness(int16_t* data, size_t samples) { + constexpr size_t CHUNK_SIZE = 50; + + float highest = -127.f; + for (size_t i = 0; i < samples; i += CHUNK_SIZE) { + float level = getAudioChunkLoudness(data + i, std::min(i + CHUNK_SIZE, samples) - i); + if (level > highest) + highest = level; + } + + return highest; +} + +struct VoiceAudioChunk { + std::unique_ptr data; + size_t remaining; + size_t offset = 0; + + VoiceAudioChunk(int16_t* ptr, size_t size) { + data.reset(ptr); + remaining = size; + offset = 0; + } + + inline size_t takeSamples(std::vector& out, size_t count) { + size_t toRead = std::min(count, remaining); + int16_t* start = data.get() + offset; + out.insert(out.end(), start, start + toRead); + offset += toRead; + remaining -= toRead; + return toRead; + } + + //this one's unsafe + inline int16_t takeSample() { + --remaining; + return *(data.get() + offset++); + } + + inline bool exhausted() { + return remaining == 0; + } +}; + +struct VoiceAudioStream { + // TODO: This should really be a ring buffer instead. + std::queue chunks{}; + size_t samples = 0; + atomic muted = false; + atomic playing = false; + atomic decibelLevel = 0.0f; + atomic> channelVolumes = Array::filled(1.0f); + + Mutex mutex; + + inline int16_t getSample() { + int16_t sample = 0; + while (!chunks.empty()) { + auto& front = chunks.front(); + if (front.exhausted()) { + chunks.pop(); + continue; + } + --samples; + return front.takeSample(); + } + return 0; + } + + void nukeSamples(size_t count) { + while (!chunks.empty() && count > 0) { + auto& front = chunks.front(); + if (count >= front.remaining) { + count -= front.remaining; + samples -= front.remaining; + chunks.pop(); + } + else { + for (size_t i = 0; i != count; ++i) { + --samples; + front.takeSample(); + } + break; + } + } + } + + inline bool empty() { return chunks.empty(); } + + void take(int16_t* ptr, size_t size) { + MutexLocker lock(mutex); + while (samples > 22050 && !chunks.empty()) { + samples -= chunks.front().remaining; + chunks.pop(); + } + chunks.emplace(ptr, size); + samples += size; + } +}; + Voice::Speaker::Speaker(SpeakerId id) : decoderMono (createDecoder(1), opus_decoder_destroy) , decoderStereo(createDecoder(2), opus_decoder_destroy) { speakerId = id; + audioStream = make_shared(); } Voice* Voice::s_singleton; @@ -53,19 +175,58 @@ Voice::Voice(ApplicationControllerPtr appController) : m_encoder(nullptr, opus_e m_channelMode = VoiceChannelMode::Mono; m_applicationController = appController; - resetEncoder(); s_singleton = this; } Voice::~Voice() { + save(); + s_singleton = nullptr; } -void Voice::load(Json const& config) { - // do stuff +void Voice::init() { + resetEncoder(); + if (m_inputEnabled) + openDevice(); } -Json Voice::save() const { - return JsonObject{}; + +void Voice::loadJson(Json const& config) { + m_enabled = config.getBool("enabled", m_enabled); + m_inputEnabled = config.getBool("inputEnabled", m_inputEnabled); + m_deviceName = config.optQueryString("inputDevice"); + m_threshold = config.getFloat("threshold", m_threshold); + m_inputVolume = config.getFloat("inputVolume", m_inputVolume); + m_outputVolume = config.getFloat("outputVolume", m_outputVolume); + m_inputMode = VoiceInputModeNames.getLeft(config.getString("inputMode", "pushToTalk")); + m_channelMode = VoiceChannelModeNames.getLeft(config.getString("channelMode", "mono")); +} + + + +Json Voice::saveJson() const { + return JsonObject{ + {"enabled", m_enabled}, + {"inputEnabled", m_inputEnabled}, + {"inputDevice", m_deviceName ? *m_deviceName : Json()}, + {"threshold", m_threshold}, + {"inputVolume", m_inputVolume}, + {"outputVolume", m_outputVolume}, + {"inputMode", VoiceInputModeNames.getRight(m_inputMode)}, + {"channelMode", VoiceChannelModeNames.getRight(m_channelMode)}, + {"version", 1} + }; +} + +void Voice::save() const { + if (Root* root = Root::singletonPtr()) { + if (auto config = root->configuration()) + config->set("voice", saveJson()); + } +} + +void Voice::scheduleSave() { + if (nextSaveTime == 0.0) + nextSaveTime = Time::monotonicTime() + 2.0; } Voice::SpeakerPtr Voice::setLocalSpeaker(SpeakerId speakerId) { @@ -87,6 +248,10 @@ Voice::SpeakerPtr Voice::speaker(SpeakerId speakerId) { } } +void Voice::getAudioData(uint8_t* stream, int len) { + +} + void Voice::mix(int16_t* buffer, size_t frames, unsigned channels) { } @@ -95,13 +260,29 @@ void Voice::update(PositionalAttenuationFunction positionalAttenuationFunction) if (positionalAttenuationFunction) { for (auto& entry : m_speakers) { if (SpeakerPtr& speaker = entry.second) { - speaker->channelVolumes = { + speaker->audioStream->channelVolumes = { positionalAttenuationFunction(0, speaker->position, 1.0f), positionalAttenuationFunction(1, speaker->position, 1.0f) }; } } } + + auto now = Time::monotonicTime(); + if (now > nextSaveTime) { + nextSaveTime = 0.0; + save(); + } +} + + +void Voice::setDeviceName(Maybe deviceName) { + if (m_deviceName == deviceName) + return; + + m_deviceName = deviceName; + if (m_deviceOpen) + openDevice(); } OpusDecoder* Voice::createDecoder(int channels) { @@ -131,6 +312,10 @@ void Voice::resetEncoder() { void Voice::openDevice() { closeDevice(); + m_applicationController->openAudioInputDevice(m_deviceName ? m_deviceName->utf8Ptr() : nullptr, VOICE_SAMPLE_RATE, encoderChannels(), this, [](void* userdata, uint8_t* stream, int len) { + ((Voice*)(userdata))->getAudioData(stream, len); + }); + m_deviceOpen = true; } diff --git a/source/frontend/StarVoice.hpp b/source/frontend/StarVoice.hpp index 0d485db..269adb4 100644 --- a/source/frontend/StarVoice.hpp +++ b/source/frontend/StarVoice.hpp @@ -5,6 +5,7 @@ #include "StarException.hpp" #include "StarGameTypes.hpp" #include "StarMaybe.hpp" +#include "StarThread.hpp" #include "StarApplicationController.hpp" struct OpusDecoder; @@ -23,6 +24,7 @@ enum class VoiceChannelMode: uint8_t { Mono = 1, Stereo = 2 }; extern EnumMap const VoiceChannelModeNames; STAR_CLASS(Voice); +STAR_CLASS(VoiceAudioStream); STAR_CLASS(ApplicationController); class Voice { @@ -30,7 +32,8 @@ public: // Individual speakers are represented by their connection ID. typedef ConnectionId SpeakerId; - struct Speaker { + class Speaker { + public: SpeakerId speakerId = 0; EntityId entityId = 0; @@ -39,10 +42,8 @@ public: OpusDecoderPtr decoderMono; OpusDecoderPtr decoderStereo; - - atomic active = false; - atomic currentLoudness = 0.0f; - atomic> channelVolumes = Array::filled(1.0f); + VoiceAudioStreamPtr audioStream; + Mutex mutex; Speaker(SpeakerId speakerId); }; @@ -63,19 +64,29 @@ public: Voice(Voice const&) = delete; Voice& operator=(Voice const&) = delete; - void load(Json const& config); - Json save() const; - + void init(); + + void loadJson(Json const& config); + Json saveJson() const; + + void save() const; + void scheduleSave(); + // Sets the local speaker ID and returns the local speaker. Must be called upon loading into a world. SpeakerPtr setLocalSpeaker(SpeakerId speakerId); SpeakerPtr speaker(SpeakerId speakerId); + // Called when receiving input audio data from SDL, on its own thread. + void getAudioData(uint8_t* stream, int len); + // Called to mix voice audio with the game. void mix(int16_t* buffer, size_t frames, unsigned channels); typedef function PositionalAttenuationFunction; void update(PositionalAttenuationFunction positionalAttenuationFunction = {}); + void setDeviceName(Maybe device); + inline int encoderChannels() const { return m_channelMode == VoiceChannelMode::Mono ? 1 : 2; } @@ -96,12 +107,21 @@ private: OpusEncoderPtr m_encoder; + float m_outputVolume = 1.0f; + float m_inputVolume = 1.0f; + float m_threshold = -50.0f; + + bool m_enabled = true; + bool m_inputEnabled = true; + bool m_deviceOpen = false; Maybe m_deviceName; VoiceInputMode m_inputMode; VoiceChannelMode m_channelMode; ApplicationControllerPtr m_applicationController; + + double nextSaveTime = 0.0f; }; } From 3b38825b34ebb95b59989934dc849858cee42c97 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Fri, 14 Jul 2023 13:13:19 +1000 Subject: [PATCH 09/34] more voice stuff --- assets/opensb/binds/opensb.binds | 14 +- .../application/StarMainApplication_sdl.cpp | 4 +- source/client/StarClientApplication.cpp | 8 + source/frontend/StarVoice.cpp | 279 +++++++++++++++--- source/frontend/StarVoice.hpp | 76 ++++- 5 files changed, 322 insertions(+), 59 deletions(-) diff --git a/assets/opensb/binds/opensb.binds b/assets/opensb/binds/opensb.binds index b8a13c2..2f84e37 100644 --- a/assets/opensb/binds/opensb.binds +++ b/assets/opensb/binds/opensb.binds @@ -1,7 +1,8 @@ { "opensb": { "groups": { - "camera": { "name": "Camera" } + "camera": { "name": "Camera" }, + "voice": { "name": "Voice" } }, "name": "Open^#ebd74a;Starbound", "binds": { @@ -21,13 +22,10 @@ "group" : "camera", "name": "Zoom Out" }, - "test": { - "default": [{ - "type": "key", - "value": "C", - "mods": ["LShift"] - }], - "name": "Test Bind" + "pushToTalk": { + "default": [], + "group" : "voice", + "name": "Push To Talk" } } } diff --git a/source/application/StarMainApplication_sdl.cpp b/source/application/StarMainApplication_sdl.cpp index 86b9a10..1685cc0 100644 --- a/source/application/StarMainApplication_sdl.cpp +++ b/source/application/StarMainApplication_sdl.cpp @@ -344,8 +344,10 @@ public: SDL_AudioSpec obtained = {}; m_sdlAudioInputDevice = SDL_OpenAudioDevice(name, 1, &desired, &obtained, 0); - if (m_sdlAudioInputDevice) + if (m_sdlAudioInputDevice) { Logger::info("Opened audio input device '{}'", SDL_GetAudioDeviceName(m_sdlAudioInputDevice, 1)); + SDL_PauseAudioDevice(m_sdlAudioInputDevice, 0); + } else Logger::info("Failed to open audio input device: {}", SDL_GetError()); diff --git a/source/client/StarClientApplication.cpp b/source/client/StarClientApplication.cpp index f1c7595..8093582 100644 --- a/source/client/StarClientApplication.cpp +++ b/source/client/StarClientApplication.cpp @@ -375,6 +375,14 @@ void ClientApplication::update() { else if (m_state > MainAppState::Title) updateRunning(); + { // testing + m_voice->setLocalSpeaker(0); + m_voice->setInput(m_input->bindHeld("opensb", "pushToTalk")); + DataStreamBuffer data; + if (m_voice->send(data, 5000)) + m_voice->receive(m_voice->speaker(0), std::string_view(data.ptr(), data.size())); + } + m_guiContext->cleanup(); m_edgeKeyEvents.clear(); m_input->reset(); diff --git a/source/frontend/StarVoice.cpp b/source/frontend/StarVoice.cpp index e5cb299..fcfaa35 100644 --- a/source/frontend/StarVoice.cpp +++ b/source/frontend/StarVoice.cpp @@ -3,9 +3,9 @@ #include "StarApplicationController.hpp" #include "StarTime.hpp" #include "StarRoot.hpp" +#include "StarLogging.hpp" #include "opus/include/opus.h" -#include #include "SDL.h" constexpr int VOICE_SAMPLE_RATE = 48000; @@ -59,45 +59,10 @@ float getAudioLoudness(int16_t* data, size_t samples) { return highest; } -struct VoiceAudioChunk { - std::unique_ptr data; - size_t remaining; - size_t offset = 0; - - VoiceAudioChunk(int16_t* ptr, size_t size) { - data.reset(ptr); - remaining = size; - offset = 0; - } - - inline size_t takeSamples(std::vector& out, size_t count) { - size_t toRead = std::min(count, remaining); - int16_t* start = data.get() + offset; - out.insert(out.end(), start, start + toRead); - offset += toRead; - remaining -= toRead; - return toRead; - } - - //this one's unsafe - inline int16_t takeSample() { - --remaining; - return *(data.get() + offset++); - } - - inline bool exhausted() { - return remaining == 0; - } -}; - struct VoiceAudioStream { // TODO: This should really be a ring buffer instead. std::queue chunks{}; size_t samples = 0; - atomic muted = false; - atomic playing = false; - atomic decibelLevel = 0.0f; - atomic> channelVolumes = Array::filled(1.0f); Mutex mutex; @@ -225,8 +190,8 @@ void Voice::save() const { } void Voice::scheduleSave() { - if (nextSaveTime == 0.0) - nextSaveTime = Time::monotonicTime() + 2.0; + if (!m_nextSaveTime) + m_nextSaveTime = Time::monotonicMilliseconds() + 2000; } Voice::SpeakerPtr Voice::setLocalSpeaker(SpeakerId speakerId) { @@ -248,19 +213,130 @@ Voice::SpeakerPtr Voice::speaker(SpeakerId speakerId) { } } -void Voice::getAudioData(uint8_t* stream, int len) { +void Voice::readAudioData(uint8_t* stream, int len) { + auto now = Time::monotonicMilliseconds(); + if (!m_encoder || m_inputMode == VoiceInputMode::PushToTalk && now > m_lastInputTime) + return; + // Stop encoding if 2048 bytes have been encoded and not taken by the game thread yet + if (m_encodedChunksLength > 2048) + return; + + size_t samples = len / 2; + float decibels = getAudioLoudness((int16_t*)stream, samples); + m_clientSpeaker->decibelLevel = decibels; + + bool active = true; + + if (m_inputMode == VoiceInputMode::VoiceActivity) { + bool aboveThreshold = decibels > m_threshold; + if (aboveThreshold) + m_lastThresholdTime = now; + active = now - m_lastThresholdTime < 50; + } + + if (active) { + m_capturedChunksFrames += samples / m_deviceChannels; + auto data = (opus_int16*)malloc(len); + memcpy(data, stream, len); + m_capturedChunks.emplace(data, samples); + } + else { // Clear out any residual data so they don't manifest at the start of the next encode, whenever that is + while (!m_capturedChunks.empty()) + m_capturedChunks.pop(); + + m_capturedChunksFrames = 0; + } + + std::vector takenSamples; + while (m_capturedChunksFrames >= VOICE_FRAME_SIZE) { + takenSamples.clear(); + size_t samplesToTake = VOICE_FRAME_SIZE * (size_t)m_deviceChannels; + takenSamples.reserve(samplesToTake); + + while (!m_capturedChunks.empty()) { + auto& front = m_capturedChunks.front(); + if (front.exhausted()) + m_capturedChunks.pop(); + else if ((samplesToTake -= front.takeSamples(takenSamples, samplesToTake)) == 0) + break; + } + m_capturedChunksFrames -= VOICE_FRAME_SIZE; + + ByteArray encodedData(VOICE_MAX_PACKET_SIZE, 0); + float vol = m_inputVolume; + if (m_inputVolume != 1.0f) { + for (size_t i = 0; i != takenSamples.size(); ++i) + takenSamples[i] *= m_inputVolume; + } + + + if (opus_int32 size = opus_encode(m_encoder.get(), takenSamples.data(), VOICE_FRAME_SIZE, (unsigned char*)encodedData.ptr(), VOICE_MAX_PACKET_SIZE)) { + if (size == 1) + continue; + + encodedData.resize(size); + MutexLocker lock(m_captureMutex); + m_encodedChunks.emplace_back(move(encodedData)); // reset takes ownership of data buffer + m_encodedChunksLength += size; + Logger::info("Voice: encoded Opus chunk {} bytes big", size); + } + else if (size < 0) { + Logger::error("Voice: Opus encode error {}", opus_strerror(size)); + } + } } -void Voice::mix(int16_t* buffer, size_t frames, unsigned channels) { +void Voice::mix(int16_t* buffer, size_t samples, unsigned channels) { + static std::vector finalMixBuffer{}; + static std::vector voiceMixBuffer{}; + finalMixBuffer.resize(samples); + voiceMixBuffer.resize(samples); + int32_t* mixBuf = (int32_t*)memset(voiceMixBuffer.data(), 0, samples * sizeof(int32_t)); + //read into buffer now + bool mix = false; + { + MutexLocker lock(m_activeSpeakersMutex); + auto it = m_activeSpeakers.begin(); + while (it != m_activeSpeakers.end()) { + SpeakerPtr const& speaker = *it; + VoiceAudioStream* audio = speaker->audioStream.get(); + MutexLocker audioLock(audio->mutex); + if (!audio->empty()) { + if (!speaker->muted) { + mix = true; + auto channelVolumes = speaker->channelVolumes.load(); + for (size_t i = 0; i != samples; ++i) + mixBuf[i] += (int32_t)(audio->getSample()) * channelVolumes[i % 2]; + } + else { + for (size_t i = 0; i != samples; ++i) + audio->getSample(); + } + ++it; + } + else { + speaker->playing = false; + it = m_activeSpeakers.erase(it); + } + } + } + if (mix) { + int16_t* finBuf = finalMixBuffer.data(); + float vol = m_outputVolume; + for (size_t i = 0; i != samples; ++i) + finBuf[i] = (int16_t)std::clamp(mixBuf[i] * vol, INT16_MIN, INT16_MAX); + + SDL_MixAudioFormat((Uint8*)buffer, (Uint8*)finBuf, AUDIO_S16, samples * sizeof(int16_t), SDL_MIX_MAXVOLUME); + } } void Voice::update(PositionalAttenuationFunction positionalAttenuationFunction) { if (positionalAttenuationFunction) { for (auto& entry : m_speakers) { if (SpeakerPtr& speaker = entry.second) { - speaker->audioStream->channelVolumes = { + speaker->channelVolumes = { positionalAttenuationFunction(0, speaker->position, 1.0f), positionalAttenuationFunction(1, speaker->position, 1.0f) }; @@ -268,9 +344,8 @@ void Voice::update(PositionalAttenuationFunction positionalAttenuationFunction) } } - auto now = Time::monotonicTime(); - if (now > nextSaveTime) { - nextSaveTime = 0.0; + if (Time::monotonicMilliseconds() > m_nextSaveTime) { + m_nextSaveTime = 0; save(); } } @@ -285,6 +360,97 @@ void Voice::setDeviceName(Maybe deviceName) { openDevice(); } +int Voice::send(DataStreamBuffer& out, size_t budget) { + out.setByteOrder(ByteOrder::LittleEndian); + out.write(VOICE_VERSION); + MutexLocker captureLock(m_captureMutex); + + if (!m_encoder || m_capturedChunks.empty()) + return 0; + + std::vector encodedChunks = move(m_encodedChunks); + size_t encodedChunksLength = m_encodedChunksLength; + m_encodedChunksLength = 0; + captureLock.unlock(); + + for (auto& chunk : encodedChunks) { + out.write(chunk.size()); + out.writeBytes(chunk); + if ((budget -= min(budget, chunk.size())) == 0) + break; + } + + m_lastSentTime = Time::monotonicMilliseconds(); + return 1; +} + +bool Voice::receive(SpeakerPtr speaker, std::string_view view) { + if (!speaker || view.empty()) + return false; + + try { + DataStreamExternalBuffer reader(view.data(), view.size()); + reader.setByteOrder(ByteOrder::LittleEndian); + + if (reader.read() > VOICE_VERSION) + return false; + + uint32_t opusLength = 0; + while (!reader.atEnd()) { + reader >> opusLength; + auto opusData = (unsigned char*)reader.ptr() + reader.pos(); + reader.seek(opusLength, IOSeek::Relative); + + int channels = opus_packet_get_nb_channels(opusData); + if (channels == OPUS_INVALID_PACKET) + continue; + + bool mono = channels == 1; + OpusDecoder* decoder = mono ? speaker->decoderMono.get() : speaker->decoderStereo.get(); + int samples = opus_decoder_get_nb_samples(decoder, opusData, opusLength); + if (samples < 0) + throw VoiceException(strf("Decoder error: {}", opus_strerror(samples)), false); + + size_t decodeBufferSize = samples * sizeof(opus_int16) * (size_t)channels; + opus_int16* decodeBuffer = (opus_int16*)malloc(decodeBufferSize); + + int decodedSamples = opus_decode(decoder, opusData, opusLength, decodeBuffer, decodeBufferSize, 0); + if (decodedSamples < 0) { + free(decodeBuffer); + throw VoiceException(strf("Decoder error: {}", opus_strerror(samples)), false); + } + + static auto getCVT = [](int channels) -> SDL_AudioCVT { + SDL_AudioCVT cvt; + SDL_BuildAudioCVT(&cvt, AUDIO_S16SYS, channels, VOICE_SAMPLE_RATE, AUDIO_S16, 2, 44100); + return cvt; + }; + + //TODO: This isn't the best way to resample to 44100 hz because SDL_ConvertAudio is not for streamed audio. + static SDL_AudioCVT monoCVT = getCVT(1); + static SDL_AudioCVT stereoCVT = getCVT(2); + SDL_AudioCVT& cvt = mono ? monoCVT : stereoCVT; + cvt.len = decodedSamples * sizeof(opus_int16) * (size_t)channels; + cvt.buf = (Uint8*)realloc(decodeBuffer, (size_t)(cvt.len * cvt.len_mult)); + SDL_ConvertAudio(&cvt); + + size_t reSamples = (size_t)cvt.len_cvt / 2; + speaker->decibelLevel = getAudioLoudness((int16_t*)cvt.buf, reSamples); + speaker->audioStream->take((opus_int16*)realloc(cvt.buf, cvt.len_cvt), reSamples); + playSpeaker(speaker, channels); + } + return true; + } + catch (StarException const& e) { + Logger::error("Voice: Error receiving voice data for speaker #{} ('{}'): {}", speaker->speakerId, speaker->name, e.what()); + return false; + } +} + +void Voice::setInput(bool input) { + m_lastInputTime = input ? Time::monotonicMilliseconds() + 1000 : 0; +} + OpusDecoder* Voice::createDecoder(int channels) { int error; OpusDecoder* decoder = opus_decoder_create(VOICE_SAMPLE_RATE, channels, &error); @@ -312,9 +478,17 @@ void Voice::resetEncoder() { void Voice::openDevice() { closeDevice(); - m_applicationController->openAudioInputDevice(m_deviceName ? m_deviceName->utf8Ptr() : nullptr, VOICE_SAMPLE_RATE, encoderChannels(), this, [](void* userdata, uint8_t* stream, int len) { - ((Voice*)(userdata))->getAudioData(stream, len); - }); + + + m_applicationController->openAudioInputDevice( + m_deviceName ? m_deviceName->utf8Ptr() : nullptr, + VOICE_SAMPLE_RATE, + m_deviceChannels = encoderChannels(), + this, + [](void* userdata, uint8_t* stream, int len) { + ((Voice*)(userdata))->readAudioData(stream, len); + } + ); m_deviceOpen = true; } @@ -328,4 +502,15 @@ void Voice::closeDevice() { m_deviceOpen = false; } +bool Voice::playSpeaker(SpeakerPtr const& speaker, int channels) { + unsigned int minSamples = speaker->minimumPlaySamples * channels; + if (speaker->playing || speaker->audioStream->samples < minSamples) + return false; + + speaker->playing = true; + MutexLocker lock(m_activeSpeakersMutex); + m_activeSpeakers.insert(speaker); + return true; +} + } \ No newline at end of file diff --git a/source/frontend/StarVoice.hpp b/source/frontend/StarVoice.hpp index 269adb4..e7ecd80 100644 --- a/source/frontend/StarVoice.hpp +++ b/source/frontend/StarVoice.hpp @@ -6,8 +6,11 @@ #include "StarGameTypes.hpp" #include "StarMaybe.hpp" #include "StarThread.hpp" +#include "StarDataStreamDevices.hpp" #include "StarApplicationController.hpp" +#include + struct OpusDecoder; typedef std::unique_ptr OpusDecoderPtr; struct OpusEncoder; @@ -27,6 +30,36 @@ STAR_CLASS(Voice); STAR_CLASS(VoiceAudioStream); STAR_CLASS(ApplicationController); +struct VoiceAudioChunk { + std::unique_ptr data; + size_t remaining; + size_t offset = 0; + + VoiceAudioChunk(int16_t* ptr, size_t size) { + data.reset(ptr); + remaining = size; + offset = 0; + } + + inline size_t takeSamples(std::vector& out, size_t count) { + size_t toRead = std::min(count, remaining); + int16_t* start = data.get() + offset; + out.insert(out.end(), start, start + toRead); + offset += toRead; + remaining -= toRead; + return toRead; + } + + //this one's unsafe + inline int16_t takeSample() { + --remaining; + return *(data.get() + offset++); + } + + inline bool exhausted() { return remaining == 0; } +}; + + class Voice { public: // Individual speakers are represented by their connection ID. @@ -45,6 +78,13 @@ public: VoiceAudioStreamPtr audioStream; Mutex mutex; + atomic muted = false; + atomic playing = false; + atomic decibelLevel = 0.0f; + atomic> channelVolumes = Array::filled(1.0f); + + unsigned int minimumPlaySamples = 4096; + Speaker(SpeakerId speakerId); }; @@ -77,7 +117,7 @@ public: SpeakerPtr speaker(SpeakerId speakerId); // Called when receiving input audio data from SDL, on its own thread. - void getAudioData(uint8_t* stream, int len); + void readAudioData(uint8_t* stream, int len); // Called to mix voice audio with the game. void mix(int16_t* buffer, size_t frames, unsigned channels); @@ -87,6 +127,12 @@ public: void setDeviceName(Maybe device); + int send(DataStreamBuffer& out, size_t budget); + bool receive(SpeakerPtr speaker, std::string_view view); + + // Must be called every frame with input state, expires after 1s. + void setInput(bool input = true); + inline int encoderChannels() const { return m_channelMode == VoiceChannelMode::Mono ? 1 : 2; } @@ -99,10 +145,13 @@ private: void openDevice(); void closeDevice(); + bool playSpeaker(SpeakerPtr const& speaker, int channels); + SpeakerId m_speakerId = 0; SpeakerPtr m_clientSpeaker; HashMap m_speakers; + Mutex m_activeSpeakersMutex; HashSet m_activeSpeakers; OpusEncoderPtr m_encoder; @@ -110,10 +159,15 @@ private: float m_outputVolume = 1.0f; float m_inputVolume = 1.0f; float m_threshold = -50.0f; - + + int64_t m_lastSentTime = 0; + int64_t m_lastInputTime = 0; + int64_t m_lastThresholdTime = 0; + int64_t m_nextSaveTime = 0; bool m_enabled = true; bool m_inputEnabled = true; + int m_deviceChannels = 1; bool m_deviceOpen = false; Maybe m_deviceName; VoiceInputMode m_inputMode; @@ -121,7 +175,23 @@ private: ApplicationControllerPtr m_applicationController; - double nextSaveTime = 0.0f; + struct EncodedChunk { + std::unique_ptr data; + size_t size; + + EncodedChunk(unsigned char* _data, size_t len) { + data.reset(_data); + size = len; + } + }; + + std::vector m_encodedChunks; + size_t m_encodedChunksLength = 0; + + std::queue m_capturedChunks; + size_t m_capturedChunksFrames = 0; + + Mutex m_captureMutex; }; } From 77e14b5941d420b9f789877fb2c90f1d1a689ad1 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Fri, 14 Jul 2023 18:29:36 +1000 Subject: [PATCH 10/34] cleanup --- source/base/StarMixer.cpp | 2 +- source/extern/CMakeLists.txt | 1 + source/frontend/StarVoice.cpp | 54 ++++++++++++++++++----------------- source/frontend/StarVoice.hpp | 6 ++-- 4 files changed, 32 insertions(+), 31 deletions(-) diff --git a/source/base/StarMixer.cpp b/source/base/StarMixer.cpp index 7b8e338..c38d17e 100644 --- a/source/base/StarMixer.cpp +++ b/source/base/StarMixer.cpp @@ -349,7 +349,7 @@ void Mixer::read(int16_t* outBuffer, size_t frameCount, ExtraMixFunction extraMi } if (extraMixFunction) - extraMixFunction(outBuffer, bufferSize, channels); + extraMixFunction(outBuffer, frameCount, channels); { MutexLocker locker(m_effectsMutex); diff --git a/source/extern/CMakeLists.txt b/source/extern/CMakeLists.txt index 5ea7420..f2e670c 100644 --- a/source/extern/CMakeLists.txt +++ b/source/extern/CMakeLists.txt @@ -3,6 +3,7 @@ SET (OPUS_INSTALL_CMAKE_CONFIG_MODULE OFF) SET (OPUS_X86_MAY_HAVE_AVX OFF) SET (OPUS_X86_MAY_HAVE_SSE4_1 OFF) SET (OPUS_STACK_PROTECTOR OFF) +SET (OPUS_ENABLE_FLOAT_API ON) ADD_SUBDIRECTORY (opus) INCLUDE_DIRECTORIES ( diff --git a/source/frontend/StarVoice.cpp b/source/frontend/StarVoice.cpp index fcfaa35..9acbc47 100644 --- a/source/frontend/StarVoice.cpp +++ b/source/frontend/StarVoice.cpp @@ -145,6 +145,7 @@ Voice::Voice(ApplicationControllerPtr appController) : m_encoder(nullptr, opus_e Voice::~Voice() { save(); + closeDevice(); s_singleton = nullptr; } @@ -162,8 +163,8 @@ void Voice::loadJson(Json const& config) { m_threshold = config.getFloat("threshold", m_threshold); m_inputVolume = config.getFloat("inputVolume", m_inputVolume); m_outputVolume = config.getFloat("outputVolume", m_outputVolume); - m_inputMode = VoiceInputModeNames.getLeft(config.getString("inputMode", "pushToTalk")); - m_channelMode = VoiceChannelModeNames.getLeft(config.getString("channelMode", "mono")); + m_inputMode = VoiceInputModeNames.getLeft(config.getString("inputMode", "PushToTalk")); + m_channelMode = VoiceChannelModeNames.getLeft(config.getString("channelMode", "Mono")); } @@ -239,7 +240,7 @@ void Voice::readAudioData(uint8_t* stream, int len) { m_capturedChunksFrames += samples / m_deviceChannels; auto data = (opus_int16*)malloc(len); memcpy(data, stream, len); - m_capturedChunks.emplace(data, samples); + m_capturedChunks.emplace(data, samples); // takes ownership } else { // Clear out any residual data so they don't manifest at the start of the next encode, whenever that is while (!m_capturedChunks.empty()) @@ -248,46 +249,47 @@ void Voice::readAudioData(uint8_t* stream, int len) { m_capturedChunksFrames = 0; } - std::vector takenSamples; while (m_capturedChunksFrames >= VOICE_FRAME_SIZE) { - takenSamples.clear(); size_t samplesToTake = VOICE_FRAME_SIZE * (size_t)m_deviceChannels; + std::vector takenSamples; takenSamples.reserve(samplesToTake); while (!m_capturedChunks.empty()) { auto& front = m_capturedChunks.front(); if (front.exhausted()) m_capturedChunks.pop(); - else if ((samplesToTake -= front.takeSamples(takenSamples, samplesToTake)) == 0) - break; + else { + samplesToTake -= front.takeSamples(takenSamples, samplesToTake); + if (samplesToTake == 0) + break; + } } m_capturedChunksFrames -= VOICE_FRAME_SIZE; - ByteArray encodedData(VOICE_MAX_PACKET_SIZE, 0); float vol = m_inputVolume; if (m_inputVolume != 1.0f) { for (size_t i = 0; i != takenSamples.size(); ++i) takenSamples[i] *= m_inputVolume; } - - - if (opus_int32 size = opus_encode(m_encoder.get(), takenSamples.data(), VOICE_FRAME_SIZE, (unsigned char*)encodedData.ptr(), VOICE_MAX_PACKET_SIZE)) { - if (size == 1) - continue; - - encodedData.resize(size); + ByteArray encodedData(VOICE_MAX_FRAME_SIZE, 0); + opus_int32 encodedSize = opus_encode(m_encoder.get(), takenSamples.data(), VOICE_FRAME_SIZE, (unsigned char*)encodedData.ptr(), encodedData.size()); + if (encodedSize == 1) + continue; + else if (encodedSize < 0) + Logger::error("Voice: Opus encode error {}", opus_strerror(encodedSize)); + else { + encodedData.resize(encodedSize); MutexLocker lock(m_captureMutex); m_encodedChunks.emplace_back(move(encodedData)); // reset takes ownership of data buffer - m_encodedChunksLength += size; - Logger::info("Voice: encoded Opus chunk {} bytes big", size); - } - else if (size < 0) { - Logger::error("Voice: Opus encode error {}", opus_strerror(size)); + m_encodedChunksLength += encodedSize; + Logger::info("Voice: encoded Opus chunk {} bytes big", encodedSize); } + } } -void Voice::mix(int16_t* buffer, size_t samples, unsigned channels) { +void Voice::mix(int16_t* buffer, size_t frameCount, unsigned channels) { + size_t samples = frameCount * channels; static std::vector finalMixBuffer{}; static std::vector voiceMixBuffer{}; finalMixBuffer.resize(samples); @@ -326,7 +328,7 @@ void Voice::mix(int16_t* buffer, size_t samples, unsigned channels) { float vol = m_outputVolume; for (size_t i = 0; i != samples; ++i) - finBuf[i] = (int16_t)std::clamp(mixBuf[i] * vol, INT16_MIN, INT16_MAX); + finBuf[i] = (int16_t)clamp(mixBuf[i] * vol, INT16_MIN, INT16_MAX); SDL_MixAudioFormat((Uint8*)buffer, (Uint8*)finBuf, AUDIO_S16, samples * sizeof(int16_t), SDL_MIX_MAXVOLUME); } @@ -344,7 +346,7 @@ void Voice::update(PositionalAttenuationFunction positionalAttenuationFunction) } } - if (Time::monotonicMilliseconds() > m_nextSaveTime) { + if (m_nextSaveTime && Time::monotonicMilliseconds() > m_nextSaveTime) { m_nextSaveTime = 0; save(); } @@ -365,7 +367,7 @@ int Voice::send(DataStreamBuffer& out, size_t budget) { out.write(VOICE_VERSION); MutexLocker captureLock(m_captureMutex); - if (!m_encoder || m_capturedChunks.empty()) + if (m_capturedChunks.empty()) return 0; std::vector encodedChunks = move(m_encodedChunks); @@ -420,6 +422,8 @@ bool Voice::receive(SpeakerPtr speaker, std::string_view view) { throw VoiceException(strf("Decoder error: {}", opus_strerror(samples)), false); } + Logger::info("Voice: decoded Opus chunk {} bytes big", opusLength); + static auto getCVT = [](int channels) -> SDL_AudioCVT { SDL_AudioCVT cvt; SDL_BuildAudioCVT(&cvt, AUDIO_S16SYS, channels, VOICE_SAMPLE_RATE, AUDIO_S16, 2, 44100); @@ -478,8 +482,6 @@ void Voice::resetEncoder() { void Voice::openDevice() { closeDevice(); - - m_applicationController->openAudioInputDevice( m_deviceName ? m_deviceName->utf8Ptr() : nullptr, VOICE_SAMPLE_RATE, diff --git a/source/frontend/StarVoice.hpp b/source/frontend/StarVoice.hpp index e7ecd80..8b003e2 100644 --- a/source/frontend/StarVoice.hpp +++ b/source/frontend/StarVoice.hpp @@ -42,7 +42,7 @@ struct VoiceAudioChunk { } inline size_t takeSamples(std::vector& out, size_t count) { - size_t toRead = std::min(count, remaining); + size_t toRead = min(count, remaining); int16_t* start = data.get() + offset; out.insert(out.end(), start, start + toRead); offset += toRead; @@ -133,9 +133,7 @@ public: // Must be called every frame with input state, expires after 1s. void setInput(bool input = true); - inline int encoderChannels() const { - return m_channelMode == VoiceChannelMode::Mono ? 1 : 2; - } + inline int encoderChannels() const { return (int)m_channelMode; } private: static Voice* s_singleton; From 8ff5d9f82bfa8461291ddf7e2669c6848ca1b24f Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Fri, 14 Jul 2023 19:54:56 +1000 Subject: [PATCH 11/34] Update StarVoice.cpp --- source/frontend/StarVoice.cpp | 67 ++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/source/frontend/StarVoice.cpp b/source/frontend/StarVoice.cpp index 9acbc47..4f9051b 100644 --- a/source/frontend/StarVoice.cpp +++ b/source/frontend/StarVoice.cpp @@ -223,24 +223,23 @@ void Voice::readAudioData(uint8_t* stream, int len) { if (m_encodedChunksLength > 2048) return; - size_t samples = len / 2; - float decibels = getAudioLoudness((int16_t*)stream, samples); + size_t sampleCount = len / 2; + float decibels = getAudioLoudness((int16_t*)stream, sampleCount); m_clientSpeaker->decibelLevel = decibels; bool active = true; if (m_inputMode == VoiceInputMode::VoiceActivity) { - bool aboveThreshold = decibels > m_threshold; - if (aboveThreshold) + if (decibels > m_threshold) m_lastThresholdTime = now; active = now - m_lastThresholdTime < 50; } if (active) { - m_capturedChunksFrames += samples / m_deviceChannels; + m_capturedChunksFrames += sampleCount / m_deviceChannels; auto data = (opus_int16*)malloc(len); memcpy(data, stream, len); - m_capturedChunks.emplace(data, samples); // takes ownership + m_capturedChunks.emplace(data, sampleCount); // takes ownership } else { // Clear out any residual data so they don't manifest at the start of the next encode, whenever that is while (!m_capturedChunks.empty()) @@ -249,41 +248,44 @@ void Voice::readAudioData(uint8_t* stream, int len) { m_capturedChunksFrames = 0; } + ByteArray encoded(VOICE_MAX_PACKET_SIZE, 0); + size_t frameSamples = VOICE_FRAME_SIZE * (size_t)m_deviceChannels; + std::vector samples; + samples.reserve(frameSamples); while (m_capturedChunksFrames >= VOICE_FRAME_SIZE) { - size_t samplesToTake = VOICE_FRAME_SIZE * (size_t)m_deviceChannels; - std::vector takenSamples; - takenSamples.reserve(samplesToTake); - - while (!m_capturedChunks.empty()) { + size_t samplesLeft = frameSamples; + while (samplesLeft && !m_capturedChunks.empty()) { auto& front = m_capturedChunks.front(); if (front.exhausted()) m_capturedChunks.pop(); - else { - samplesToTake -= front.takeSamples(takenSamples, samplesToTake); - if (samplesToTake == 0) - break; - } + else + samplesLeft -= front.takeSamples(samples, samplesLeft); } - m_capturedChunksFrames -= VOICE_FRAME_SIZE; + m_capturedChunksFrames -= VOICE_FRAME_SIZE; - float vol = m_inputVolume; if (m_inputVolume != 1.0f) { - for (size_t i = 0; i != takenSamples.size(); ++i) - takenSamples[i] *= m_inputVolume; + for (size_t i = 0; i != samples.size(); ++i) + samples[i] *= m_inputVolume; } - ByteArray encodedData(VOICE_MAX_FRAME_SIZE, 0); - opus_int32 encodedSize = opus_encode(m_encoder.get(), takenSamples.data(), VOICE_FRAME_SIZE, (unsigned char*)encodedData.ptr(), encodedData.size()); - if (encodedSize == 1) - continue; - else if (encodedSize < 0) - Logger::error("Voice: Opus encode error {}", opus_strerror(encodedSize)); - else { - encodedData.resize(encodedSize); - MutexLocker lock(m_captureMutex); - m_encodedChunks.emplace_back(move(encodedData)); // reset takes ownership of data buffer - m_encodedChunksLength += encodedSize; + + if (int encodedSize = opus_encode(m_encoder.get(), samples.data(), VOICE_FRAME_SIZE, (unsigned char*)encoded.ptr(), encoded.size())) { + if (encodedSize == 1) + continue; + + encoded.resize(encodedSize); + + { + MutexLocker lock(m_captureMutex); + m_encodedChunks.emplace_back(move(encoded)); // reset takes ownership of data buffer + m_encodedChunksLength += encodedSize; + + encoded = ByteArray(VOICE_MAX_PACKET_SIZE, 0); + } + Logger::info("Voice: encoded Opus chunk {} bytes big", encodedSize); } + else if (encodedSize < 0) + Logger::error("Voice: Opus encode error {}", opus_strerror(encodedSize)); } } @@ -294,7 +296,8 @@ void Voice::mix(int16_t* buffer, size_t frameCount, unsigned channels) { static std::vector voiceMixBuffer{}; finalMixBuffer.resize(samples); voiceMixBuffer.resize(samples); - int32_t* mixBuf = (int32_t*)memset(voiceMixBuffer.data(), 0, samples * sizeof(int32_t)); + int32_t* mixBuf = voiceMixBuffer.data(); + memset(mixBuf, 0, samples * sizeof(int32_t)); //read into buffer now bool mix = false; { From 52ba6fa7f78269fb69bb3addb297b6a8d0a779fd Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Fri, 14 Jul 2023 21:44:13 +1000 Subject: [PATCH 12/34] Ensure NONTHREADSAFE_PSUEDOSTACK is never used --- source/CMakeLists.txt | 2 +- source/extern/CMakeLists.txt | 9 ++++++++- source/frontend/StarVoice.cpp | 4 ++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 4f44bc2..e71ba21 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -437,7 +437,7 @@ SET (STAR_EXT_LIBS ${STAR_EXT_LIBS} ${FREETYPE_LIBRARY} ${PNG_LIBRARY} ${ZLIB_LIBRARY} - "extern/opus/opus" + opus ) IF (STAR_BUILD_GUI) diff --git a/source/extern/CMakeLists.txt b/source/extern/CMakeLists.txt index f2e670c..2e4ea24 100644 --- a/source/extern/CMakeLists.txt +++ b/source/extern/CMakeLists.txt @@ -2,10 +2,17 @@ SET (OPUS_INSTALL_PKG_CONFIG_MODULE OFF) SET (OPUS_INSTALL_CMAKE_CONFIG_MODULE OFF) SET (OPUS_X86_MAY_HAVE_AVX OFF) SET (OPUS_X86_MAY_HAVE_SSE4_1 OFF) -SET (OPUS_STACK_PROTECTOR OFF) SET (OPUS_ENABLE_FLOAT_API ON) +SET (OPUS_STACK_PROTECTOR OFF) +SET (OPUS_NONTHREADSAFE_PSEUDOSTACK OFF) +SET (OPUS_USE_ALLOCA ON) + ADD_SUBDIRECTORY (opus) +IF (OPUS_NONTHREADSAFE_PSEUDOSTACK) + MESSAGE (FATAL_ERROR "Opus should not be using NONTHREADSAFE_PSEUDOSTACK") +ENDIF () + INCLUDE_DIRECTORIES ( ${STAR_EXTERN_INCLUDES} opus/include diff --git a/source/frontend/StarVoice.cpp b/source/frontend/StarVoice.cpp index 4f9051b..dc97070 100644 --- a/source/frontend/StarVoice.cpp +++ b/source/frontend/StarVoice.cpp @@ -250,9 +250,9 @@ void Voice::readAudioData(uint8_t* stream, int len) { ByteArray encoded(VOICE_MAX_PACKET_SIZE, 0); size_t frameSamples = VOICE_FRAME_SIZE * (size_t)m_deviceChannels; - std::vector samples; - samples.reserve(frameSamples); while (m_capturedChunksFrames >= VOICE_FRAME_SIZE) { + std::vector samples; + samples.reserve(frameSamples); size_t samplesLeft = frameSamples; while (samplesLeft && !m_capturedChunks.empty()) { auto& front = m_capturedChunks.front(); From 73c5a17746312415335098cb70e83e933b597565 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Fri, 14 Jul 2023 22:47:49 +1000 Subject: [PATCH 13/34] Move Opus encoding off-thread because of SDL SDL gives its audio threads a very small stack size and it was making Opus fuck up --- source/frontend/StarVoice.cpp | 127 ++++++++++++++++++++++------------ source/frontend/StarVoice.hpp | 17 +++-- 2 files changed, 96 insertions(+), 48 deletions(-) diff --git a/source/frontend/StarVoice.cpp b/source/frontend/StarVoice.cpp index dc97070..29fcb9e 100644 --- a/source/frontend/StarVoice.cpp +++ b/source/frontend/StarVoice.cpp @@ -140,10 +140,22 @@ Voice::Voice(ApplicationControllerPtr appController) : m_encoder(nullptr, opus_e m_channelMode = VoiceChannelMode::Mono; m_applicationController = appController; + m_stopThread = false; + m_thread = Thread::invoke("Voice::thread", mem_fn(&Voice::thread), this); + s_singleton = this; } Voice::~Voice() { + m_stopThread = true; + + { + MutexLocker locker(m_threadMutex); + m_threadCond.broadcast(); + } + + m_thread.finish(); + save(); closeDevice(); @@ -232,14 +244,18 @@ void Voice::readAudioData(uint8_t* stream, int len) { if (m_inputMode == VoiceInputMode::VoiceActivity) { if (decibels > m_threshold) m_lastThresholdTime = now; - active = now - m_lastThresholdTime < 50; + active = now - m_lastThresholdTime < 50; } + bool added = false; + + MutexLocker captureLock(m_captureMutex); if (active) { m_capturedChunksFrames += sampleCount / m_deviceChannels; auto data = (opus_int16*)malloc(len); memcpy(data, stream, len); m_capturedChunks.emplace(data, sampleCount); // takes ownership + added = true; } else { // Clear out any residual data so they don't manifest at the start of the next encode, whenever that is while (!m_capturedChunks.empty()) @@ -248,46 +264,8 @@ void Voice::readAudioData(uint8_t* stream, int len) { m_capturedChunksFrames = 0; } - ByteArray encoded(VOICE_MAX_PACKET_SIZE, 0); - size_t frameSamples = VOICE_FRAME_SIZE * (size_t)m_deviceChannels; - while (m_capturedChunksFrames >= VOICE_FRAME_SIZE) { - std::vector samples; - samples.reserve(frameSamples); - size_t samplesLeft = frameSamples; - while (samplesLeft && !m_capturedChunks.empty()) { - auto& front = m_capturedChunks.front(); - if (front.exhausted()) - m_capturedChunks.pop(); - else - samplesLeft -= front.takeSamples(samples, samplesLeft); - } - m_capturedChunksFrames -= VOICE_FRAME_SIZE; - - if (m_inputVolume != 1.0f) { - for (size_t i = 0; i != samples.size(); ++i) - samples[i] *= m_inputVolume; - } - - if (int encodedSize = opus_encode(m_encoder.get(), samples.data(), VOICE_FRAME_SIZE, (unsigned char*)encoded.ptr(), encoded.size())) { - if (encodedSize == 1) - continue; - - encoded.resize(encodedSize); - - { - MutexLocker lock(m_captureMutex); - m_encodedChunks.emplace_back(move(encoded)); // reset takes ownership of data buffer - m_encodedChunksLength += encodedSize; - - encoded = ByteArray(VOICE_MAX_PACKET_SIZE, 0); - } - - Logger::info("Voice: encoded Opus chunk {} bytes big", encodedSize); - } - else if (encodedSize < 0) - Logger::error("Voice: Opus encode error {}", opus_strerror(encodedSize)); - - } + if (added) + m_threadCond.signal(); } void Voice::mix(int16_t* buffer, size_t frameCount, unsigned channels) { @@ -368,15 +346,17 @@ void Voice::setDeviceName(Maybe deviceName) { int Voice::send(DataStreamBuffer& out, size_t budget) { out.setByteOrder(ByteOrder::LittleEndian); out.write(VOICE_VERSION); - MutexLocker captureLock(m_captureMutex); - if (m_capturedChunks.empty()) + MutexLocker encodeLock(m_encodeMutex); + + if (m_encodedChunks.empty()) return 0; std::vector encodedChunks = move(m_encodedChunks); size_t encodedChunksLength = m_encodedChunksLength; m_encodedChunksLength = 0; - captureLock.unlock(); + + encodeLock.unlock(); for (auto& chunk : encodedChunks) { out.write(chunk.size()); @@ -518,4 +498,63 @@ bool Voice::playSpeaker(SpeakerPtr const& speaker, int channels) { return true; } +void Voice::thread() { + while (true) { + MutexLocker locker(m_threadMutex); + + m_threadCond.wait(m_threadMutex); + if (m_stopThread) + return; + + { + MutexLocker locker(m_captureMutex); + ByteArray encoded(VOICE_MAX_PACKET_SIZE, 0); + size_t frameSamples = VOICE_FRAME_SIZE * (size_t)m_deviceChannels; + while (m_capturedChunksFrames >= VOICE_FRAME_SIZE) { + std::vector samples; + samples.reserve(frameSamples); + size_t samplesLeft = frameSamples; + while (samplesLeft && !m_capturedChunks.empty()) { + auto& front = m_capturedChunks.front(); + if (front.exhausted()) + m_capturedChunks.pop(); + else + samplesLeft -= front.takeSamples(samples, samplesLeft); + } + m_capturedChunksFrames -= VOICE_FRAME_SIZE; + + if (m_inputVolume != 1.0f) { + for (size_t i = 0; i != samples.size(); ++i) + samples[i] *= m_inputVolume; + } + + if (int encodedSize = opus_encode(m_encoder.get(), samples.data(), VOICE_FRAME_SIZE, (unsigned char*)encoded.ptr(), encoded.size())) { + if (encodedSize == 1) + continue; + + encoded.resize(encodedSize); + + { + MutexLocker lock(m_encodeMutex); + m_encodedChunks.emplace_back(move(encoded)); // reset takes ownership of data buffer + m_encodedChunksLength += encodedSize; + + encoded = ByteArray(VOICE_MAX_PACKET_SIZE, 0); + } + + Logger::info("Voice: encoded Opus chunk {} bytes big", encodedSize); + } + else if (encodedSize < 0) + Logger::error("Voice: Opus encode error {}", opus_strerror(encodedSize)); + } + } + + continue; + + locker.unlock(); + Thread::yield(); + } + return; +} + } \ No newline at end of file diff --git a/source/frontend/StarVoice.hpp b/source/frontend/StarVoice.hpp index 8b003e2..8f39246 100644 --- a/source/frontend/StarVoice.hpp +++ b/source/frontend/StarVoice.hpp @@ -134,17 +134,19 @@ public: void setInput(bool input = true); inline int encoderChannels() const { return (int)m_channelMode; } -private: - static Voice* s_singleton; static OpusDecoder* createDecoder(int channels); static OpusEncoder* createEncoder(int channels); +private: + static Voice* s_singleton; void resetEncoder(); void openDevice(); void closeDevice(); bool playSpeaker(SpeakerPtr const& speaker, int channels); + void thread(); + SpeakerId m_speakerId = 0; SpeakerPtr m_clientSpeaker; HashMap m_speakers; @@ -152,6 +154,8 @@ private: Mutex m_activeSpeakersMutex; HashSet m_activeSpeakers; + + OpusEncoderPtr m_encoder; float m_outputVolume = 1.0f; @@ -171,6 +175,11 @@ private: VoiceInputMode m_inputMode; VoiceChannelMode m_channelMode; + ThreadFunction m_thread; + Mutex m_threadMutex; + ConditionVariable m_threadCond; + atomic m_stopThread; + ApplicationControllerPtr m_applicationController; struct EncodedChunk { @@ -183,13 +192,13 @@ private: } }; + Mutex m_encodeMutex; std::vector m_encodedChunks; size_t m_encodedChunksLength = 0; + Mutex m_captureMutex; std::queue m_capturedChunks; size_t m_capturedChunksFrames = 0; - - Mutex m_captureMutex; }; } From b4a53e07067d1ca96fa2e09e5ab07a99574d8e21 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Sat, 15 Jul 2023 00:35:23 +1000 Subject: [PATCH 14/34] Only resample during mix, store samples a simpler way Still need a better resampler, I think --- source/frontend/StarVoice.cpp | 130 ++++++++++++---------------------- 1 file changed, 44 insertions(+), 86 deletions(-) diff --git a/source/frontend/StarVoice.cpp b/source/frontend/StarVoice.cpp index 29fcb9e..210a509 100644 --- a/source/frontend/StarVoice.cpp +++ b/source/frontend/StarVoice.cpp @@ -61,54 +61,9 @@ float getAudioLoudness(int16_t* data, size_t samples) { struct VoiceAudioStream { // TODO: This should really be a ring buffer instead. - std::queue chunks{}; - size_t samples = 0; + std::vector samples; Mutex mutex; - - inline int16_t getSample() { - int16_t sample = 0; - while (!chunks.empty()) { - auto& front = chunks.front(); - if (front.exhausted()) { - chunks.pop(); - continue; - } - --samples; - return front.takeSample(); - } - return 0; - } - - void nukeSamples(size_t count) { - while (!chunks.empty() && count > 0) { - auto& front = chunks.front(); - if (count >= front.remaining) { - count -= front.remaining; - samples -= front.remaining; - chunks.pop(); - } - else { - for (size_t i = 0; i != count; ++i) { - --samples; - front.takeSample(); - } - break; - } - } - } - - inline bool empty() { return chunks.empty(); } - - void take(int16_t* ptr, size_t size) { - MutexLocker lock(mutex); - while (samples > 22050 && !chunks.empty()) { - samples -= chunks.front().remaining; - chunks.pop(); - } - chunks.emplace(ptr, size); - samples += size; - } }; Voice::Speaker::Speaker(SpeakerId id) @@ -269,14 +224,12 @@ void Voice::readAudioData(uint8_t* stream, int len) { } void Voice::mix(int16_t* buffer, size_t frameCount, unsigned channels) { + static std::vector finalBuffer; + static std::vector voiceBuffer; + static std::vector resampled; size_t samples = frameCount * channels; - static std::vector finalMixBuffer{}; - static std::vector voiceMixBuffer{}; - finalMixBuffer.resize(samples); - voiceMixBuffer.resize(samples); - int32_t* mixBuf = voiceMixBuffer.data(); - memset(mixBuf, 0, samples * sizeof(int32_t)); - //read into buffer now + resampled.resize(samples, 0); + bool mix = false; { MutexLocker lock(m_activeSpeakersMutex); @@ -285,16 +238,17 @@ void Voice::mix(int16_t* buffer, size_t frameCount, unsigned channels) { SpeakerPtr const& speaker = *it; VoiceAudioStream* audio = speaker->audioStream.get(); MutexLocker audioLock(audio->mutex); - if (!audio->empty()) { + if (!audio->samples.empty()) { + std::vector samples = move(audio->samples); + audioLock.unlock(); if (!speaker->muted) { mix = true; + if (voiceBuffer.size() < samples.size()) + voiceBuffer.resize(samples.size(), 0); + auto channelVolumes = speaker->channelVolumes.load(); - for (size_t i = 0; i != samples; ++i) - mixBuf[i] += (int32_t)(audio->getSample()) * channelVolumes[i % 2]; - } - else { - for (size_t i = 0; i != samples; ++i) - audio->getSample(); + for (size_t i = 0; i != samples.size(); ++i) + voiceBuffer[i] += (int32_t)(samples[i]) * channelVolumes[i % 2]; } ++it; } @@ -304,15 +258,27 @@ void Voice::mix(int16_t* buffer, size_t frameCount, unsigned channels) { } } } + + static std::unique_ptr audioStream + (SDL_NewAudioStream(AUDIO_S16, 2, 48000, AUDIO_S16SYS, 2, 44100), SDL_FreeAudioStream); + if (mix) { - int16_t* finBuf = finalMixBuffer.data(); + finalBuffer.resize(voiceBuffer.size(), 0); float vol = m_outputVolume; - for (size_t i = 0; i != samples; ++i) - finBuf[i] = (int16_t)clamp(mixBuf[i] * vol, INT16_MIN, INT16_MAX); + for (size_t i = 0; i != voiceBuffer.size(); ++i) + finalBuffer[i] = (int16_t)clamp(voiceBuffer[i] * vol, INT16_MIN, INT16_MAX); - SDL_MixAudioFormat((Uint8*)buffer, (Uint8*)finBuf, AUDIO_S16, samples * sizeof(int16_t), SDL_MIX_MAXVOLUME); + SDL_AudioStreamPut(audioStream.get(), finalBuffer.data(), finalBuffer.size() * sizeof(int16_t)); } + + if (size_t available = min(samples * sizeof(int16_t), SDL_AudioStreamAvailable(audioStream.get()))) { + SDL_AudioStreamGet(audioStream.get(), resampled.data(), available); + SDL_MixAudioFormat((Uint8*)buffer, (Uint8*)resampled.data(), AUDIO_S16, samples * sizeof(int16_t), SDL_MIX_MAXVOLUME); + } + + resampled.clear(); + voiceBuffer.clear(); } void Voice::update(PositionalAttenuationFunction positionalAttenuationFunction) { @@ -400,30 +366,22 @@ bool Voice::receive(SpeakerPtr speaker, std::string_view view) { opus_int16* decodeBuffer = (opus_int16*)malloc(decodeBufferSize); int decodedSamples = opus_decode(decoder, opusData, opusLength, decodeBuffer, decodeBufferSize, 0); - if (decodedSamples < 0) { + if (decodedSamples <= 0) { free(decodeBuffer); - throw VoiceException(strf("Decoder error: {}", opus_strerror(samples)), false); + if (decodedSamples < 0) + throw VoiceException(strf("Decoder error: {}", opus_strerror(samples)), false); + return true; } - Logger::info("Voice: decoded Opus chunk {} bytes big", opusLength); + decodedSamples *= channels; + //Logger::info("Voice: decoded Opus chunk {} bytes -> {} samples", opusLength, decodedSamples); - static auto getCVT = [](int channels) -> SDL_AudioCVT { - SDL_AudioCVT cvt; - SDL_BuildAudioCVT(&cvt, AUDIO_S16SYS, channels, VOICE_SAMPLE_RATE, AUDIO_S16, 2, 44100); - return cvt; - }; - - //TODO: This isn't the best way to resample to 44100 hz because SDL_ConvertAudio is not for streamed audio. - static SDL_AudioCVT monoCVT = getCVT(1); - static SDL_AudioCVT stereoCVT = getCVT(2); - SDL_AudioCVT& cvt = mono ? monoCVT : stereoCVT; - cvt.len = decodedSamples * sizeof(opus_int16) * (size_t)channels; - cvt.buf = (Uint8*)realloc(decodeBuffer, (size_t)(cvt.len * cvt.len_mult)); - SDL_ConvertAudio(&cvt); - - size_t reSamples = (size_t)cvt.len_cvt / 2; - speaker->decibelLevel = getAudioLoudness((int16_t*)cvt.buf, reSamples); - speaker->audioStream->take((opus_int16*)realloc(cvt.buf, cvt.len_cvt), reSamples); + speaker->decibelLevel = getAudioLoudness(decodeBuffer, decodedSamples); + { + MutexLocker lock(speaker->audioStream->mutex); + auto& samples = speaker->audioStream->samples; + samples.insert(samples.end(), decodeBuffer, decodeBuffer + decodedSamples); + } playSpeaker(speaker, channels); } return true; @@ -489,7 +447,7 @@ void Voice::closeDevice() { bool Voice::playSpeaker(SpeakerPtr const& speaker, int channels) { unsigned int minSamples = speaker->minimumPlaySamples * channels; - if (speaker->playing || speaker->audioStream->samples < minSamples) + if (speaker->playing || speaker->audioStream->samples.size() < minSamples) return false; speaker->playing = true; @@ -542,7 +500,7 @@ void Voice::thread() { encoded = ByteArray(VOICE_MAX_PACKET_SIZE, 0); } - Logger::info("Voice: encoded Opus chunk {} bytes big", encodedSize); + //Logger::info("Voice: encoded Opus chunk {} samples -> {} bytes", frameSamples, encodedSize); } else if (encodedSize < 0) Logger::error("Voice: Opus encode error {}", opus_strerror(encodedSize)); From 91cd6182d8647aa4ddee3abf8bd0bd8f741f1511 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Sat, 15 Jul 2023 00:39:10 +1000 Subject: [PATCH 15/34] Fix mono --- source/frontend/StarVoice.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/source/frontend/StarVoice.cpp b/source/frontend/StarVoice.cpp index 210a509..f2bcbe2 100644 --- a/source/frontend/StarVoice.cpp +++ b/source/frontend/StarVoice.cpp @@ -380,7 +380,15 @@ bool Voice::receive(SpeakerPtr speaker, std::string_view view) { { MutexLocker lock(speaker->audioStream->mutex); auto& samples = speaker->audioStream->samples; - samples.insert(samples.end(), decodeBuffer, decodeBuffer + decodedSamples); + if (mono) { + size_t prevSize = samples.size(); + samples.resize(prevSize + (size_t)decodedSamples * 2); + int16_t* data = samples.data() + prevSize; + for (int i = 0; i != decodedSamples; ++i) + *data++ = *data++ = decodeBuffer[i]; + } + else + samples.insert(samples.end(), decodeBuffer, decodeBuffer + decodedSamples); } playSpeaker(speaker, channels); } From 4e44a4ed7566fbbc7248796423b822430205ad98 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Sat, 15 Jul 2023 14:01:44 +1000 Subject: [PATCH 16/34] Get transmission working --- source/client/StarClientApplication.cpp | 38 +++++++++++++++++++------ source/frontend/StarVoice.cpp | 6 ++-- source/frontend/StarVoice.hpp | 2 ++ source/game/StarWorldClient.cpp | 10 +++++-- source/game/StarWorldClient.hpp | 4 +++ 5 files changed, 46 insertions(+), 14 deletions(-) diff --git a/source/client/StarClientApplication.cpp b/source/client/StarClientApplication.cpp index 8093582..8dfd110 100644 --- a/source/client/StarClientApplication.cpp +++ b/source/client/StarClientApplication.cpp @@ -375,14 +375,6 @@ void ClientApplication::update() { else if (m_state > MainAppState::Title) updateRunning(); - { // testing - m_voice->setLocalSpeaker(0); - m_voice->setInput(m_input->bindHeld("opensb", "pushToTalk")); - DataStreamBuffer data; - if (m_voice->send(data, 5000)) - m_voice->receive(m_voice->speaker(0), std::string_view(data.ptr(), data.size())); - } - m_guiContext->cleanup(); m_edgeKeyEvents.clear(); m_input->reset(); @@ -860,13 +852,41 @@ void ClientApplication::updateRunning() { if (checkDisconnection()) return; + m_voice->setInput(m_input->bindHeld("opensb", "pushToTalk")); + DataStreamBuffer voiceData; + voiceData.setByteOrder(ByteOrder::LittleEndian); + voiceData.writeBytes(VoiceBroadcastPrefix.utf8Bytes()); + bool needstoSendVoice = m_voice->send(voiceData, 5000); + m_universeClient->update(); if (checkDisconnection()) return; - if (auto worldClient = m_universeClient->worldClient()) + if (auto worldClient = m_universeClient->worldClient()) { + auto& broadcastCallback = worldClient->broadcastCallback(); + if (!broadcastCallback) { + broadcastCallback = [&](PlayerPtr player, StringView broadcast) -> bool { + auto& view = broadcast.utf8(); + if (view.rfind(VoiceBroadcastPrefix.utf8(), 0) != NPos) { + auto entityId = player->entityId(); + auto speaker = m_voice->speaker(connectionForEntity(entityId)); + speaker->entityId = entityId; + speaker->name = player->name(); + speaker->position = player->mouthPosition(); + m_voice->receive(speaker, view.substr(VoiceBroadcastPrefix.utf8Size())); + } + return true; + }; + } + + if (worldClient->inWorld()) { + if (needstoSendVoice) + worldClient->sendSecretBroadcast(StringView(voiceData.ptr(), voiceData.size())); + m_voice->setLocalSpeaker(worldClient->connection()); + } worldClient->setInteractiveHighlightMode(isActionTaken(InterfaceAction::ShowLabels)); + } updateCamera(); diff --git a/source/frontend/StarVoice.cpp b/source/frontend/StarVoice.cpp index f2bcbe2..c424d30 100644 --- a/source/frontend/StarVoice.cpp +++ b/source/frontend/StarVoice.cpp @@ -241,6 +241,7 @@ void Voice::mix(int16_t* buffer, size_t frameCount, unsigned channels) { if (!audio->samples.empty()) { std::vector samples = move(audio->samples); audioLock.unlock(); + speaker->decibelLevel = getAudioLoudness(samples.data(), samples.size()); if (!speaker->muted) { mix = true; if (voiceBuffer.size() < samples.size()) @@ -286,8 +287,8 @@ void Voice::update(PositionalAttenuationFunction positionalAttenuationFunction) for (auto& entry : m_speakers) { if (SpeakerPtr& speaker = entry.second) { speaker->channelVolumes = { - positionalAttenuationFunction(0, speaker->position, 1.0f), - positionalAttenuationFunction(1, speaker->position, 1.0f) + 1.0f - positionalAttenuationFunction(0, speaker->position, 1.0f), + 1.0f - positionalAttenuationFunction(1, speaker->position, 1.0f) }; } } @@ -376,7 +377,6 @@ bool Voice::receive(SpeakerPtr speaker, std::string_view view) { decodedSamples *= channels; //Logger::info("Voice: decoded Opus chunk {} bytes -> {} samples", opusLength, decodedSamples); - speaker->decibelLevel = getAudioLoudness(decodeBuffer, decodedSamples); { MutexLocker lock(speaker->audioStream->mutex); auto& samples = speaker->audioStream->samples; diff --git a/source/frontend/StarVoice.hpp b/source/frontend/StarVoice.hpp index 8f39246..94ef8ac 100644 --- a/source/frontend/StarVoice.hpp +++ b/source/frontend/StarVoice.hpp @@ -18,6 +18,8 @@ typedef std::unique_ptr OpusEncoderPtr; namespace Star { +String const VoiceBroadcastPrefix = "Voice\0"s; + STAR_EXCEPTION(VoiceException, StarException); enum class VoiceInputMode : uint8_t { VoiceActivity, PushToTalk }; diff --git a/source/game/StarWorldClient.cpp b/source/game/StarWorldClient.cpp index 2b4af00..272b5df 100644 --- a/source/game/StarWorldClient.cpp +++ b/source/game/StarWorldClient.cpp @@ -1212,6 +1212,10 @@ void WorldClient::waitForLighting() { MutexLocker lock(m_lightingMutex); } +WorldClient::BroadcastCallback& WorldClient::broadcastCallback() { + return m_broadcastCallback; +} + bool WorldClient::isTileProtected(Vec2I const& pos) const { if (!inWorld()) return true; @@ -1905,8 +1909,10 @@ bool WorldClient::sendSecretBroadcast(StringView broadcast, bool raw) { } bool WorldClient::handleSecretBroadcast(PlayerPtr player, StringView broadcast) { - Logger::info("Received broadcast '{}'", broadcast); - return true; + if (m_broadcastCallback) + return m_broadcastCallback(player, broadcast); + else + return false; } diff --git a/source/game/StarWorldClient.hpp b/source/game/StarWorldClient.hpp index 0a48a31..436c2df 100644 --- a/source/game/StarWorldClient.hpp +++ b/source/game/StarWorldClient.hpp @@ -166,6 +166,8 @@ public: void waitForLighting(); + typedef std::function BroadcastCallback; + BroadcastCallback& broadcastCallback(); private: static const float DropDist; @@ -345,6 +347,8 @@ private: HashMap> m_entityInteractionResponses; List m_forceRegions; + + BroadcastCallback m_broadcastCallback; }; } From da098c7b4812408d1316b14b3b3f46d2ec7dce04 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Sun, 16 Jul 2023 20:44:15 +1000 Subject: [PATCH 17/34] Support receiving SE voice data, resample per-speaker again because of positional delay --- source/frontend/StarVoice.cpp | 106 +++++++++++++++++++++----------- source/frontend/StarVoice.hpp | 2 + source/game/StarWorldClient.cpp | 31 +++++++--- 3 files changed, 95 insertions(+), 44 deletions(-) diff --git a/source/frontend/StarVoice.cpp b/source/frontend/StarVoice.cpp index c424d30..4c0f4be 100644 --- a/source/frontend/StarVoice.cpp +++ b/source/frontend/StarVoice.cpp @@ -61,9 +61,31 @@ float getAudioLoudness(int16_t* data, size_t samples) { struct VoiceAudioStream { // TODO: This should really be a ring buffer instead. - std::vector samples; - + std::queue samples; + SDL_AudioStream* sdlAudioStream; Mutex mutex; + + VoiceAudioStream() : sdlAudioStream(SDL_NewAudioStream(AUDIO_S16, 2, 48000, AUDIO_S16SYS, 2, 44100)) {}; + ~VoiceAudioStream() { SDL_FreeAudioStream(sdlAudioStream); } + + inline int16_t take() { + int16_t sample = 0; + if (!samples.empty()) { + sample = samples.front(); + samples.pop(); + } + return sample; + } + + size_t resample(int16_t* in, size_t inSamples, std::vector& out) { + SDL_AudioStreamPut(sdlAudioStream, in, inSamples * sizeof(int16_t)); + if (int available = SDL_AudioStreamAvailable(sdlAudioStream)) { + out.resize(available / 2); + SDL_AudioStreamGet(sdlAudioStream, out.data(), available); + return available; + } + return 0; + } }; Voice::Speaker::Speaker(SpeakerId id) @@ -224,11 +246,11 @@ void Voice::readAudioData(uint8_t* stream, int len) { } void Voice::mix(int16_t* buffer, size_t frameCount, unsigned channels) { - static std::vector finalBuffer; - static std::vector voiceBuffer; - static std::vector resampled; size_t samples = frameCount * channels; - resampled.resize(samples, 0); + static std::vector finalBuffer, speakerBuffer; + static std::vector sharedBuffer; //int32 to reduce clipping + speakerBuffer.resize(samples); + sharedBuffer.resize(samples); bool mix = false; { @@ -239,17 +261,21 @@ void Voice::mix(int16_t* buffer, size_t frameCount, unsigned channels) { VoiceAudioStream* audio = speaker->audioStream.get(); MutexLocker audioLock(audio->mutex); if (!audio->samples.empty()) { - std::vector samples = move(audio->samples); - audioLock.unlock(); - speaker->decibelLevel = getAudioLoudness(samples.data(), samples.size()); + SDL_AudioStream* sdlStream = audio->sdlAudioStream; if (!speaker->muted) { mix = true; - if (voiceBuffer.size() < samples.size()) - voiceBuffer.resize(samples.size(), 0); + for (size_t i = 0; i != samples; ++i) + speakerBuffer[i] = audio->take(); + speaker->decibelLevel = getAudioLoudness(speakerBuffer.data(), samples); auto channelVolumes = speaker->channelVolumes.load(); - for (size_t i = 0; i != samples.size(); ++i) - voiceBuffer[i] += (int32_t)(samples[i]) * channelVolumes[i % 2]; + + for (size_t i = 0; i != samples; ++i) + sharedBuffer[i] += (int32_t)(speakerBuffer[i]) * channelVolumes[i % 2]; + } + else { + for (size_t i = 0; i != samples; ++i) + audio->take(); } ++it; } @@ -260,26 +286,16 @@ void Voice::mix(int16_t* buffer, size_t frameCount, unsigned channels) { } } - static std::unique_ptr audioStream - (SDL_NewAudioStream(AUDIO_S16, 2, 48000, AUDIO_S16SYS, 2, 44100), SDL_FreeAudioStream); - if (mix) { - finalBuffer.resize(voiceBuffer.size(), 0); + finalBuffer.resize(sharedBuffer.size(), 0); float vol = m_outputVolume; - for (size_t i = 0; i != voiceBuffer.size(); ++i) - finalBuffer[i] = (int16_t)clamp(voiceBuffer[i] * vol, INT16_MIN, INT16_MAX); + for (size_t i = 0; i != sharedBuffer.size(); ++i) + finalBuffer[i] = (int16_t)clamp(sharedBuffer[i] * vol, INT16_MIN, INT16_MAX); - SDL_AudioStreamPut(audioStream.get(), finalBuffer.data(), finalBuffer.size() * sizeof(int16_t)); + SDL_MixAudioFormat((Uint8*)buffer, (Uint8*)finalBuffer.data(), AUDIO_S16, finalBuffer.size() * sizeof(int16_t), SDL_MIX_MAXVOLUME); + memset(sharedBuffer.data(), 0, sharedBuffer.size() * sizeof(int32_t)); } - - if (size_t available = min(samples * sizeof(int16_t), SDL_AudioStreamAvailable(audioStream.get()))) { - SDL_AudioStreamGet(audioStream.get(), resampled.data(), available); - SDL_MixAudioFormat((Uint8*)buffer, (Uint8*)resampled.data(), AUDIO_S16, samples * sizeof(int16_t), SDL_MIX_MAXVOLUME); - } - - resampled.clear(); - voiceBuffer.clear(); } void Voice::update(PositionalAttenuationFunction positionalAttenuationFunction) { @@ -378,17 +394,35 @@ bool Voice::receive(SpeakerPtr speaker, std::string_view view) { //Logger::info("Voice: decoded Opus chunk {} bytes -> {} samples", opusLength, decodedSamples); { - MutexLocker lock(speaker->audioStream->mutex); + std::vector resamBuffer(decodedSamples, 0); + speaker->audioStream->resample(decodeBuffer, decodedSamples, resamBuffer); + + MutexLocker lock(speaker->audioStream->mutex); auto& samples = speaker->audioStream->samples; - if (mono) { - size_t prevSize = samples.size(); - samples.resize(prevSize + (size_t)decodedSamples * 2); - int16_t* data = samples.data() + prevSize; - for (int i = 0; i != decodedSamples; ++i) - *data++ = *data++ = decodeBuffer[i]; + + auto now = Time::monotonicMilliseconds(); + if (now - speaker->lastReceiveTime < 1000) { + auto limit = ((size_t)speaker->minimumPlaySamples + 22050) * (size_t)channels; + if (samples.size() > limit) { // skip ahead if we're getting too far + for (size_t i = samples.size(); i >= limit; --i) + samples.pop(); + } } else - samples.insert(samples.end(), decodeBuffer, decodeBuffer + decodedSamples); + samples = std::queue(); + + speaker->lastReceiveTime = now; + + if (mono) { + for (int16_t sample : resamBuffer) { + samples.push(sample); + samples.push(sample); + } + } + else { + for (int16_t sample : resamBuffer) + samples.push(sample); + } } playSpeaker(speaker, channels); } diff --git a/source/frontend/StarVoice.hpp b/source/frontend/StarVoice.hpp index 94ef8ac..3b95235 100644 --- a/source/frontend/StarVoice.hpp +++ b/source/frontend/StarVoice.hpp @@ -80,6 +80,8 @@ public: VoiceAudioStreamPtr audioStream; Mutex mutex; + int64_t lastReceiveTime = 0; + atomic muted = false; atomic playing = false; atomic decibelLevel = 0.0f; diff --git a/source/game/StarWorldClient.cpp b/source/game/StarWorldClient.cpp index 272b5df..54d4651 100644 --- a/source/game/StarWorldClient.cpp +++ b/source/game/StarWorldClient.cpp @@ -795,20 +795,18 @@ void WorldClient::handleIncomingPackets(List const& packets) { m_damageManager->pushRemoteDamageRequest(damage->remoteDamageRequest); } else if (auto damage = as(packet)) { - auto& materialKind = damage->remoteDamageNotification.damageNotification.targetMaterialKind.utf8(); - const size_t prefixSize = SECRET_BROADCAST_PREFIX.size(); - const size_t signatureSize = Curve25519::SignatureSize; - const size_t dataSize = prefixSize + signatureSize; + std::string_view view(damage->remoteDamageNotification.damageNotification.targetMaterialKind.utf8()); + static const size_t FULL_SIZE = SECRET_BROADCAST_PREFIX.size() + Curve25519::SignatureSize; + static const std::string LEGACY_VOICE_PREFIX = "data\0voice\0"s; - if (materialKind.size() >= dataSize && materialKind.rfind(SECRET_BROADCAST_PREFIX, 0) != NPos) { + if (view.size() >= FULL_SIZE && view.rfind(SECRET_BROADCAST_PREFIX, 0) != NPos) { // this is actually a secret broadcast!! if (auto player = m_entityMap->get(damage->remoteDamageNotification.sourceEntityId)) { if (auto publicKey = player->getSecretPropertyView(SECRET_BROADCAST_PUBLIC_KEY)) { if (publicKey->utf8Size() == Curve25519::PublicKeySize) { - std::string_view broadcast(materialKind); - auto signature = broadcast.substr(prefixSize, signatureSize); + auto signature = view.substr(SECRET_BROADCAST_PREFIX.size(), Curve25519::SignatureSize); - auto rawBroadcast = broadcast.substr(dataSize); + auto rawBroadcast = view.substr(FULL_SIZE); if (Curve25519::verify( (uint8_t const*)signature.data(), (uint8_t const*)publicKey->utf8Ptr(), @@ -821,6 +819,23 @@ void WorldClient::handleIncomingPackets(List const& packets) { } } } + else if (view.size() > 75 && view.rfind(LEGACY_VOICE_PREFIX, 0) != NPos) { + // this is a StarExtensions voice packet + // (remove this and stop transmitting like this once most SE features are ported over) + if (auto player = m_entityMap->get(damage->remoteDamageNotification.sourceEntityId)) { + if (auto publicKey = player->effectsAnimator()->globalTagPtr("\0SE_VOICE_SIGNING_KEY"s)) { + auto rawData = view.substr(75); + if (m_broadcastCallback && Curve25519::verify( + (uint8_t const*)view.data() + LEGACY_VOICE_PREFIX.size(), + (uint8_t const*)publicKey->utf8Ptr(), + (void*)rawData.data(), + rawData.size() + )) { + m_broadcastCallback(player, "Voice\0"s + rawData); + } + } + } + } else { m_damageManager->pushRemoteDamageNotification(damage->remoteDamageNotification); } From 848b11399f2e34d7f1e0523e214287bfdcc5816c Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Sun, 16 Jul 2023 23:04:09 +1000 Subject: [PATCH 18/34] Get SE-compatible voice transmission working --- source/application/StarMainApplication_sdl.cpp | 7 +++++-- source/client/StarClientApplication.cpp | 14 +++++++++----- source/frontend/StarVoice.cpp | 5 +++-- source/frontend/StarVoice.hpp | 2 +- source/game/StarWorldClient.cpp | 5 ++++- 5 files changed, 22 insertions(+), 11 deletions(-) diff --git a/source/application/StarMainApplication_sdl.cpp b/source/application/StarMainApplication_sdl.cpp index 1685cc0..8b8c702 100644 --- a/source/application/StarMainApplication_sdl.cpp +++ b/source/application/StarMainApplication_sdl.cpp @@ -345,7 +345,10 @@ public: m_sdlAudioInputDevice = SDL_OpenAudioDevice(name, 1, &desired, &obtained, 0); if (m_sdlAudioInputDevice) { - Logger::info("Opened audio input device '{}'", SDL_GetAudioDeviceName(m_sdlAudioInputDevice, 1)); + if (name) + Logger::info("Opened audio input device '{}'", name); + else + Logger::info("Opened default audio input device"); SDL_PauseAudioDevice(m_sdlAudioInputDevice, 0); } else @@ -356,7 +359,7 @@ public: bool closeAudioInputDevice() { if (m_sdlAudioInputDevice) { - Logger::info("Closing audio input device '{}'", SDL_GetAudioDeviceName(m_sdlAudioInputDevice, 1)); + Logger::info("Closing audio input device"); SDL_CloseAudioDevice(m_sdlAudioInputDevice); m_sdlAudioInputDevice = 0; return true; diff --git a/source/client/StarClientApplication.cpp b/source/client/StarClientApplication.cpp index 8dfd110..f7ab21b 100644 --- a/source/client/StarClientApplication.cpp +++ b/source/client/StarClientApplication.cpp @@ -16,7 +16,7 @@ #include "StarRootLoader.hpp" #include "StarInput.hpp" #include "StarVoice.hpp" - +#include "StarCurve25519.hpp" #include "StarInterfaceLuaBindings.hpp" #include "StarInputLuaBindings.hpp" @@ -855,9 +855,8 @@ void ClientApplication::updateRunning() { m_voice->setInput(m_input->bindHeld("opensb", "pushToTalk")); DataStreamBuffer voiceData; voiceData.setByteOrder(ByteOrder::LittleEndian); - voiceData.writeBytes(VoiceBroadcastPrefix.utf8Bytes()); + //voiceData.writeBytes(VoiceBroadcastPrefix.utf8Bytes()); transmitting with SE compat for now bool needstoSendVoice = m_voice->send(voiceData, 5000); - m_universeClient->update(); if (checkDisconnection()) @@ -881,8 +880,13 @@ void ClientApplication::updateRunning() { } if (worldClient->inWorld()) { - if (needstoSendVoice) - worldClient->sendSecretBroadcast(StringView(voiceData.ptr(), voiceData.size())); + if (needstoSendVoice) { + auto signature = Curve25519::sign(voiceData.ptr(), voiceData.size()); + std::string_view signatureView((char*)signature.data(), signature.size()); + std::string_view audioDataView(voiceData.ptr(), voiceData.size()); + auto broadcast = strf("data\0voice\0{}{}"s, signatureView, audioDataView); + worldClient->sendSecretBroadcast(broadcast, true); + } m_voice->setLocalSpeaker(worldClient->connection()); } worldClient->setInteractiveHighlightMode(isActionTaken(InterfaceAction::ShowLabels)); diff --git a/source/frontend/StarVoice.cpp b/source/frontend/StarVoice.cpp index 4c0f4be..c6ec1d0 100644 --- a/source/frontend/StarVoice.cpp +++ b/source/frontend/StarVoice.cpp @@ -269,7 +269,6 @@ void Voice::mix(int16_t* buffer, size_t frameCount, unsigned channels) { speaker->decibelLevel = getAudioLoudness(speakerBuffer.data(), samples); auto channelVolumes = speaker->channelVolumes.load(); - for (size_t i = 0; i != samples; ++i) sharedBuffer[i] += (int32_t)(speakerBuffer[i]) * channelVolumes[i % 2]; } @@ -366,6 +365,8 @@ bool Voice::receive(SpeakerPtr speaker, std::string_view view) { uint32_t opusLength = 0; while (!reader.atEnd()) { reader >> opusLength; + if (reader.pos() + opusLength > reader.size()) + throw VoiceException("Opus packet length goes past end of buffer"s, false); auto opusData = (unsigned char*)reader.ptr() + reader.pos(); reader.seek(opusLength, IOSeek::Relative); @@ -536,7 +537,7 @@ void Voice::thread() { { MutexLocker lock(m_encodeMutex); - m_encodedChunks.emplace_back(move(encoded)); // reset takes ownership of data buffer + m_encodedChunks.emplace_back(move(encoded)); m_encodedChunksLength += encodedSize; encoded = ByteArray(VOICE_MAX_PACKET_SIZE, 0); diff --git a/source/frontend/StarVoice.hpp b/source/frontend/StarVoice.hpp index 3b95235..38964b0 100644 --- a/source/frontend/StarVoice.hpp +++ b/source/frontend/StarVoice.hpp @@ -171,7 +171,7 @@ private: int64_t m_lastThresholdTime = 0; int64_t m_nextSaveTime = 0; bool m_enabled = true; - bool m_inputEnabled = true; + bool m_inputEnabled = false; int m_deviceChannels = 1; bool m_deviceOpen = false; diff --git a/source/game/StarWorldClient.cpp b/source/game/StarWorldClient.cpp index 54d4651..091d66e 100644 --- a/source/game/StarWorldClient.cpp +++ b/source/game/StarWorldClient.cpp @@ -966,7 +966,10 @@ void WorldClient::update() { // Secret broadcasts are transmitted through DamageNotifications for vanilla server compatibility. // Because DamageNotification packets are spoofable, we have to sign the data so other clients can validate that it is legitimate. auto& publicKey = Curve25519::publicKey(); - m_mainPlayer->setSecretProperty(SECRET_BROADCAST_PUBLIC_KEY, String((const char*)publicKey.data(), publicKey.size())); + String publicKeyString((const char*)publicKey.data(), publicKey.size()); + m_mainPlayer->setSecretProperty(SECRET_BROADCAST_PUBLIC_KEY, publicKeyString); + // Temporary: Backwards compatibility with StarExtensions + m_mainPlayer->effectsAnimator()->setGlobalTag("\0SE_VOICE_SIGNING_KEY"s, publicKeyString); ++m_currentStep; //m_interpolationTracker.update(m_currentStep); From 34bb0b54222c1c0f3450c56e76f89f192d77374b Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Mon, 17 Jul 2023 22:20:39 +1000 Subject: [PATCH 19/34] Initial voice HUD indicator setup --- assets/opensb/client.config.patch | 2 + .../interface/voicechat/indicator/back.png | Bin 0 -> 611 bytes .../interface/voicechat/indicator/front.png | Bin 0 -> 1526 bytes .../voicechat/indicator/front_muted.png | Bin 0 -> 1620 bytes .../opensb/scripts/universeClient/opensb.lua | 29 +++ .../universeClient/opensb/voice_manager.lua | 216 ++++++++++++++++++ source/client/StarClientApplication.cpp | 1 + source/frontend/StarInterfaceLuaBindings.cpp | 5 + source/frontend/StarMainInterface.cpp | 26 ++- source/frontend/StarMainInterface.hpp | 4 +- source/game/StarUniverseClient.cpp | 32 ++- source/game/StarUniverseClient.hpp | 11 +- source/windowing/StarCanvasWidget.cpp | 11 +- source/windowing/StarGuiContext.cpp | 10 +- source/windowing/StarGuiContext.hpp | 1 + source/windowing/StarWidgetLuaBindings.cpp | 9 +- 16 files changed, 333 insertions(+), 24 deletions(-) create mode 100644 assets/opensb/interface/voicechat/indicator/back.png create mode 100644 assets/opensb/interface/voicechat/indicator/front.png create mode 100644 assets/opensb/interface/voicechat/indicator/front_muted.png create mode 100644 assets/opensb/scripts/universeClient/opensb.lua create mode 100644 assets/opensb/scripts/universeClient/opensb/voice_manager.lua diff --git a/assets/opensb/client.config.patch b/assets/opensb/client.config.patch index 88bf328..e0d5687 100644 --- a/assets/opensb/client.config.patch +++ b/assets/opensb/client.config.patch @@ -1,4 +1,6 @@ { + "universeScriptContexts" : { "opensb" : ["/scripts/universeClient/opensb.lua"] }, + // Disables scissoring and letterboxing on vanilla and modded warp cinematics "warpCinematicBase" : { "scissor" : false, diff --git a/assets/opensb/interface/voicechat/indicator/back.png b/assets/opensb/interface/voicechat/indicator/back.png new file mode 100644 index 0000000000000000000000000000000000000000..869ed1cc651720c14816070a52eca22e0e80dce1 GIT binary patch literal 611 zcmeAS@N?(olHy`uVBq!ia0y~yVAKJ!4LI0<pR<7|q<9nv`OzIg|o&$<|j@TTxIcjt4&F_8ny?+<_H;ZoE)mtiGY--0~ zu}?8g`eN0Lyy^E?wVkHU``h)?(vIQB|1!gJ!FBsDzkT9;Pjw@cpRTFK){MlD+}iuu z-YvIon0$N6`u5@tpV+_I8}5r_-0E?E%f9`!9s2Q{m-ja&v0hOz)!dpH!lo@7%dq0? zon=N5FXxClHk-_#A? z3=fxZ2DDyTa%I^?nKk>~$0z3keWsODJlie)vmEc?57m3)>bp^t{an^LB{Ts5 Diyi=c literal 0 HcmV?d00001 diff --git a/assets/opensb/interface/voicechat/indicator/front.png b/assets/opensb/interface/voicechat/indicator/front.png new file mode 100644 index 0000000000000000000000000000000000000000..ff32e382a349889e1f1a9b556f5447492f0a54db GIT binary patch literal 1526 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 0 then + canvas:drawDrawable(getLinePadding(0, math.min(12, width)), pos) + if width > 12 then + lineDrawable.line[2][1] = math.min(width, LINE_WIDTH_PADDED) + canvas:drawDrawable(lineDrawable, pos) + if width > LINE_WIDTH_PADDED then + canvas:drawDrawable(getLinePadding(LINE_WIDTH_PADDED, width), pos) + end + end + end +end + +local drawable = { + image = BACK_INDICATOR_IMAGE, + centered = false +} + +local textPositioning = { + position = {0, 0}, + horizontalAnchor = "left", + verticalAnchor = "mid" +} + +local hoveredSpeaker = nil +local hoveredSpeakerIndex = 1 +local hoveredSpeakerPosition = {0, 0} +local function mouseOverSpeaker(mouse, pos, expand) + expand = tonumber(expand) or 0 + return (mouse[1] > pos[1] - expand and mouse[1] < pos[1] + 300 + expand) + and (mouse[2] > pos[2] - expand and mouse[2] < pos[2] + 48 + expand) +end + +local function drawSpeakerBar(mouse, pos, speaker, i) + drawable.image = BACK_INDICATOR_IMAGE + canvas:drawDrawable(drawable, pos) + drawTheLine(pos, 1 - math.sqrt(math.min(1, math.max(0, speaker.loudness / -50)))) + local hovering = not speaker.isLocal and mouseOverSpeaker(mouse, pos) + + textPositioning.position = {pos[1] + 49, pos[2] + 24} + textPositioning.horizontalAnchor = "left" + local text = NAME_PREFIX .. + (hovering and (speaker.muted and "^#31d2f7;Unmute^reset; " or "^#f43030;Mute^reset; ") or "") + .. speaker.name + canvas:drawText(text, textPositioning, 16, nil, nil, nil, FONT_DIRECTIVES) + drawable.image = speaker.muted and FRONT_MUTED_INDICATOR_IMAGE or FRONT_INDICATOR_IMAGE + canvas:drawDrawable(drawable, pos) + + if hovering then + hoveredSpeaker = speaker + hoveredSpeakerIndex = i + hoveredSpeakerPosition = pos + --if input.key("LShift") then + -- textPositioning.position = {pos[1] + 288, pos[2] + 24} + -- textPositioning.horizontalAnchor = "right" + -- canvas:drawText("^#fff7;" .. tostring(speaker.speakerId), textPositioning, 16, nil, nil, nil, FONT_DIRECTIVES) + --end +-- + --if input.mouseDown("MouseLeft") then + -- local muted = not voice.muted(speaker.speakerId) + -- interface.queueMessage((muted and "^#f43030;Muted^reset; " or "^#31d2f7;Unmuted^reset; ") .. speaker.name, 4, 0.5) + -- voice.setMuted(speaker.speakerId, muted) + --end + end +end +local speakersTime = {} + +local function simulateSpeakers() + local speakers = {} + for i = 2, 5 + math.floor((math.sin(os.clock()) * 4) + .5) do + speakers[i] = { + speakerId = i, + entityId = -65536 * i, + name = "Player " .. i, + loudness = -48 + 48 * math.sin(os.clock() + (i * 0.5)), + muted = false + } + end + return speakers +end + +local function drawIndicators() + canvas:clear() + local screenSize = canvas:size() + local mousePosition = canvas:mousePosition() + sb.setLogMap("mousePosition", sb.printJson(mousePosition)) + local basePos = {screenSize[1] - 350, 50} + + -- sort it ourselves for now + local speakersRemaining, speakersSorted = {}, {} + local hoveredSpeakerId = nil + if hoveredSpeaker then + if not mouseOverSpeaker(mousePosition, hoveredSpeakerPosition, 16) then + hoveredSpeaker = nil + else + hoveredSpeakerId = hoveredSpeaker.speakerId + end + end + + --local speakers = voice.speakers() + local speakers = { -- just testing before implementing voice lua functions + { + speakerId = 1, + entityId = -65536, + loudness = -96 + math.random() * 96, + muted = false, + name = "theres a pipe bomb up my ass" + } + } + + local sortI = 0 + local now = os.clock() + for i, speaker in pairs(speakers) do + local speakerId = speaker.speakerId + speakersRemaining[speakerId] = true + local t = speakersTime[speakerId] + if not t then + t = now + speakersTime[speakerId] = t + end + speaker.startTime = t + if speakerId == hoveredSpeakerId then + hoveredSpeaker = speaker + else + sortI = sortI + 1 + speakersSorted[sortI] = speaker + end + end + + for i, v in pairs(speakersTime) do + if not speakersRemaining[i] then + speakersTime[i] = nil + end + end + + table.sort(speakersSorted, function(a, b) + if a.startTime == b.startTime then + return a.speakerId < b.speakerId + else + return a.startTime < b.startTime + end + end) + + if hoveredSpeaker then + local len = #speakersSorted + if hoveredSpeakerIndex > len then + for i = len + 1, hoveredSpeakerIndex - 1 do + speakersSorted[i] = false + end + speakersSorted[hoveredSpeakerIndex] = hoveredSpeaker + else + table.insert(speakersSorted, hoveredSpeakerIndex, hoveredSpeaker) + end + end + + for i, v in pairs(speakersSorted) do + if v then + local entityId = v.entityId + local loudness = v.loudness + local pos = {basePos[1], basePos[2] + (i - 1) * 52} + drawSpeakerBar(mousePosition, pos, v, i) + end + end +end + +function module.init() + canvas = interface.bindCanvas("voice", true) +end + +function module.update() + drawIndicators() +end \ No newline at end of file diff --git a/source/client/StarClientApplication.cpp b/source/client/StarClientApplication.cpp index f7ab21b..9de1642 100644 --- a/source/client/StarClientApplication.cpp +++ b/source/client/StarClientApplication.cpp @@ -614,6 +614,7 @@ void ClientApplication::changeState(MainAppState newState) { m_worldPainter = make_shared(); m_mainInterface = make_shared(m_universeClient, m_worldPainter, m_cinematicOverlay); m_universeClient->setLuaCallbacks("interface", LuaBindings::makeInterfaceCallbacks(m_mainInterface.get())); + m_universeClient->startLua(); m_mainMixer->setWorldPainter(m_worldPainter); diff --git a/source/frontend/StarInterfaceLuaBindings.cpp b/source/frontend/StarInterfaceLuaBindings.cpp index 14b2e7c..d5e0960 100644 --- a/source/frontend/StarInterfaceLuaBindings.cpp +++ b/source/frontend/StarInterfaceLuaBindings.cpp @@ -27,6 +27,11 @@ LuaCallbacks LuaBindings::makeInterfaceCallbacks(MainInterface* mainInterface) { return GuiContext::singleton().interfaceScale(); }); + callbacks.registerCallback("queueMessage", [mainInterface](String const& message, Maybe cooldown, Maybe springState) { + mainInterface->queueMessage(message, cooldown, springState.value(0)); + }); + + return callbacks; } diff --git a/source/frontend/StarMainInterface.cpp b/source/frontend/StarMainInterface.cpp index 9f335b6..0dc3a01 100644 --- a/source/frontend/StarMainInterface.cpp +++ b/source/frontend/StarMainInterface.cpp @@ -62,8 +62,8 @@ namespace Star { GuiMessage::GuiMessage() : message(), cooldown(), springState() {} -GuiMessage::GuiMessage(String const& message, float cooldown) - : message(message), cooldown(cooldown), springState(0) {} +GuiMessage::GuiMessage(String const& message, float cooldown, float spring) + : message(message), cooldown(cooldown), springState(spring) {} MainInterface::MainInterface(UniverseClientPtr client, WorldPainterPtr painter, CinematicPtr cinematicOverlay) { m_state = Running; @@ -369,6 +369,9 @@ bool MainInterface::handleInputEvent(InputEvent const& event) { player->endTrigger(); } + for (auto& pair : m_canvases) + pair.second->sendEvent(event); + return true; } @@ -863,11 +866,15 @@ void MainInterface::doChat(String const& chat, bool addToHistory) { m_chat->addHistory(chat); } -void MainInterface::queueMessage(String const& message) { - auto guiMessage = make_shared(message, m_config->messageTime); +void MainInterface::queueMessage(String const& message, Maybe cooldown, float spring) { + auto guiMessage = make_shared(message, cooldown.value(m_config->messageTime), spring); m_messages.append(guiMessage); } +void MainInterface::queueMessage(String const& message) { + queueMessage(message, m_config->messageTime, 0.0f); +} + void MainInterface::queueJoinRequest(pair> request) { m_queuedJoinRequests.push_back(request); @@ -927,18 +934,21 @@ void MainInterface::warpTo(WarpAction const& warpAction) { } CanvasWidgetPtr MainInterface::fetchCanvas(String const& canvasName, bool ignoreInterfaceScale) { + CanvasWidgetPtr canvas; + if (auto canvasPtr = m_canvases.ptr(canvasName)) - return *canvasPtr; + canvas = *canvasPtr; else { - CanvasWidgetPtr canvas = m_canvases.emplace(canvasName, make_shared()).first->second; + m_canvases.emplace(canvasName, canvas = make_shared()); canvas->setPosition(Vec2I()); if (ignoreInterfaceScale) canvas->setSize(Vec2I(m_guiContext->windowSize())); else canvas->setSize(Vec2I(m_guiContext->windowInterfaceSize())); - canvas->setIgnoreInterfaceScale(ignoreInterfaceScale); - return canvas; } + + canvas->setIgnoreInterfaceScale(ignoreInterfaceScale); + return canvas; } PanePtr MainInterface::createEscapeDialog() { diff --git a/source/frontend/StarMainInterface.hpp b/source/frontend/StarMainInterface.hpp index d96779d..9cf2342 100644 --- a/source/frontend/StarMainInterface.hpp +++ b/source/frontend/StarMainInterface.hpp @@ -49,7 +49,7 @@ STAR_CLASS(MainInterface); struct GuiMessage { GuiMessage(); - GuiMessage(String const& message, float cooldown); + GuiMessage(String const& message, float cooldown, float spring = 0); String message; float cooldown; @@ -105,7 +105,9 @@ public: void doChat(String const& chat, bool addToHistory); + void queueMessage(String const& message, Maybe cooldown, float spring); void queueMessage(String const& message); + void queueItemPickupText(ItemPtr const& item); void queueJoinRequest(pair> request); diff --git a/source/game/StarUniverseClient.cpp b/source/game/StarUniverseClient.cpp index 1622518..bc1b0b9 100644 --- a/source/game/StarUniverseClient.cpp +++ b/source/game/StarUniverseClient.cpp @@ -30,6 +30,7 @@ UniverseClient::UniverseClient(PlayerStoragePtr playerStorage, StatisticsPtr sta m_playerStorage = move(playerStorage); m_statistics = move(statistics); m_pause = false; + m_luaRoot = make_shared(); reset(); } @@ -86,7 +87,7 @@ Maybe UniverseClient::connect(UniverseConnection connection, bool allowA return String(strf("Join failed! Server does not support connections with protocol version {}", StarProtocolVersion)); connection.pushSingle(make_shared(Root::singleton().assets()->digest(), allowAssetsMismatch, m_mainPlayer->uuid(), m_mainPlayer->name(), - m_mainPlayer->species(), m_playerStorage->loadShipData(m_mainPlayer->uuid()), ShipUpgrades(m_mainPlayer->shipUpgrades()), + m_mainPlayer->species(), m_playerStorage->loadShipData(m_mainPlayer->uuid()), m_mainPlayer->shipUpgrades(), m_mainPlayer->log()->introComplete(), account)); connection.sendAll(timeout); @@ -219,8 +220,11 @@ void UniverseClient::update() { m_statistics->update(); - if (!m_pause) + if (!m_pause) { m_worldClient->update(); + for (auto& p : m_scriptContexts) + p.second->update(); + } m_connection->push(m_worldClient->getOutgoingPackets()); if (!m_pause) @@ -444,6 +448,28 @@ void UniverseClient::setLuaCallbacks(String const& groupName, LuaCallbacks const m_worldClient->setLuaCallbacks(groupName, callbacks); } +void UniverseClient::startLua() { + auto assets = Root::singleton().assets(); + for (auto& p : assets->json("/client.config:universeScriptContexts").toObject()) { + auto scriptComponent = make_shared(); + scriptComponent->setLuaRoot(m_luaRoot); + scriptComponent->setScripts(jsonToStringList(p.second.toArray())); + + for (auto& pair : m_luaCallbacks) + scriptComponent->addCallbacks(pair.first, pair.second); + + m_scriptContexts.set(p.first, scriptComponent); + scriptComponent->init(); + } +} + +void UniverseClient::stopLua() { + for (auto& p : m_scriptContexts) + p.second->uninit(); + + m_scriptContexts.clear(); +} + ClockConstPtr UniverseClient::universeClock() const { return m_universeClock; } @@ -539,6 +565,8 @@ void UniverseClient::handlePackets(List const& packets) { } void UniverseClient::reset() { + stopLua(); + m_universeClock.reset(); m_worldClient.reset(); m_celestialDatabase.reset(); diff --git a/source/game/StarUniverseClient.hpp b/source/game/StarUniverseClient.hpp index b26f2df..daad481 100644 --- a/source/game/StarUniverseClient.hpp +++ b/source/game/StarUniverseClient.hpp @@ -10,6 +10,7 @@ #include "StarAiTypes.hpp" #include "StarSky.hpp" #include "StarUniverseConnection.hpp" +#include "StarLuaComponents.hpp" namespace Star { @@ -29,8 +30,8 @@ STAR_CLASS(CelestialDatabase); STAR_CLASS(JsonRpcInterface); STAR_CLASS(TeamClient); STAR_CLASS(QuestManager); - STAR_CLASS(UniverseClient); +STAR_CLASS(LuaRoot); class UniverseClient { public: @@ -86,6 +87,8 @@ public: uint16_t maxPlayers(); void setLuaCallbacks(String const& groupName, LuaCallbacks const& callbacks); + void startLua(); + void stopLua(); ClockConstPtr universeClock() const; CelestialLogConstPtr celestialLog() const; @@ -141,6 +144,12 @@ private: List m_pendingMessages; Maybe m_disconnectReason; + + LuaRootPtr m_luaRoot; + + typedef LuaUpdatableComponent ScriptComponent; + typedef shared_ptr ScriptComponentPtr; + StringMap m_scriptContexts; }; } diff --git a/source/windowing/StarCanvasWidget.cpp b/source/windowing/StarCanvasWidget.cpp index ed2e30f..a0eeb7d 100644 --- a/source/windowing/StarCanvasWidget.cpp +++ b/source/windowing/StarCanvasWidget.cpp @@ -88,20 +88,21 @@ bool CanvasWidget::sendEvent(InputEvent const& event) { return false; auto& context = GuiContext::singleton(); + int interfaceScale = m_ignoreInterfaceScale ? 1 : context.interfaceScale(); if (auto mouseButtonDown = event.ptr()) { - if (inMember(*context.mousePosition(event)) && m_captureMouse) { - m_clickEvents.append({*context.mousePosition(event) - screenPosition(), mouseButtonDown->mouseButton, true}); + if (inMember(*context.mousePosition(event, interfaceScale)) && m_captureMouse) { + m_clickEvents.append({*context.mousePosition(event, interfaceScale) - screenPosition(), mouseButtonDown->mouseButton, true}); m_clickEvents.limitSizeBack(MaximumEventBuffer); return true; } } else if (auto mouseButtonUp = event.ptr()) { if (m_captureMouse) { - m_clickEvents.append({*context.mousePosition(event) - screenPosition(), mouseButtonUp->mouseButton, false}); + m_clickEvents.append({*context.mousePosition(event, interfaceScale) - screenPosition(), mouseButtonUp->mouseButton, false}); m_clickEvents.limitSizeBack(MaximumEventBuffer); return true; } } else if (event.is()) { - m_mousePosition = *context.mousePosition(event) - screenPosition(); + m_mousePosition = *context.mousePosition(event, interfaceScale) - screenPosition(); return false; } else if (auto keyDown = event.ptr()) { if (m_captureKeyboard) { @@ -258,7 +259,7 @@ void CanvasWidget::renderTriangles(Vec2F const& renderingOffset, List GuiContext::mousePosition(InputEvent const& event) const { - auto getInterfacePosition = [this](Vec2I pos) { - return Vec2I(pos) / interfaceScale(); +Maybe GuiContext::mousePosition(InputEvent const& event, int pixelRatio) const { + auto getInterfacePosition = [pixelRatio](Vec2I pos) { + return Vec2I(pos) / pixelRatio; }; if (auto mouseMoveEvent = event.ptr()) @@ -115,6 +115,10 @@ Maybe GuiContext::mousePosition(InputEvent const& event) const { return {}; } +Maybe GuiContext::mousePosition(InputEvent const& event) const { + return mousePosition(event, interfaceScale()); +} + Set GuiContext::actions(InputEvent const& event) const { return m_keyBindings.actions(event); } diff --git a/source/windowing/StarGuiContext.hpp b/source/windowing/StarGuiContext.hpp index 1d6ed46..8e689ee 100644 --- a/source/windowing/StarGuiContext.hpp +++ b/source/windowing/StarGuiContext.hpp @@ -50,6 +50,7 @@ public: int interfaceScale() const; void setInterfaceScale(int interfaceScale); + Maybe mousePosition(InputEvent const& event, int pixelRatio) const; Maybe mousePosition(InputEvent const& event) const; Set actions(InputEvent const& event) const; diff --git a/source/windowing/StarWidgetLuaBindings.cpp b/source/windowing/StarWidgetLuaBindings.cpp index 46f1ac8..f69bd53 100644 --- a/source/windowing/StarWidgetLuaBindings.cpp +++ b/source/windowing/StarWidgetLuaBindings.cpp @@ -25,13 +25,14 @@ LuaMethods LuaUserDataMethods::make() { methods.registerMethodWithSignature("clear", mem_fn(&CanvasWidget::clear)); - methods.registerMethod("drawDrawable", [](CanvasWidgetPtr canvasWidget, Drawable drawable) { - canvasWidget->drawDrawable(move(drawable), Vec2F()); + methods.registerMethod("drawDrawable", [](CanvasWidgetPtr canvasWidget, Drawable drawable, Maybe screenPos) { + canvasWidget->drawDrawable(move(drawable), screenPos.value(Vec2F())); }); - methods.registerMethod("drawDrawables", [](CanvasWidgetPtr canvasWidget, List drawables) { + methods.registerMethod("drawDrawables", [](CanvasWidgetPtr canvasWidget, List drawables, Maybe screenPos) { + Vec2F pos = screenPos.value(Vec2F()); for (auto& drawable : drawables) - canvasWidget->drawDrawable(move(drawable), Vec2F()); + canvasWidget->drawDrawable(move(drawable), pos); }); methods.registerMethod("drawImage", From 6e1d29fe861ef5a81c5458cd8ae68d09f36e28c3 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Tue, 18 Jul 2023 17:36:51 +1000 Subject: [PATCH 20/34] Provide speaker info to HUD indicators --- .../universeClient/opensb/voice_manager.lua | 53 +++--------- source/client/StarClientApplication.cpp | 7 ++ source/frontend/CMakeLists.txt | 2 + source/frontend/StarVoice.cpp | 83 ++++++++++++------- source/frontend/StarVoice.hpp | 5 +- source/frontend/StarVoiceLuaBindings.cpp | 29 +++++++ source/frontend/StarVoiceLuaBindings.hpp | 16 ++++ 7 files changed, 122 insertions(+), 73 deletions(-) create mode 100644 source/frontend/StarVoiceLuaBindings.cpp create mode 100644 source/frontend/StarVoiceLuaBindings.hpp diff --git a/assets/opensb/scripts/universeClient/opensb/voice_manager.lua b/assets/opensb/scripts/universeClient/opensb/voice_manager.lua index c07a8c2..d1c540f 100644 --- a/assets/opensb/scripts/universeClient/opensb/voice_manager.lua +++ b/assets/opensb/scripts/universeClient/opensb/voice_manager.lua @@ -107,7 +107,6 @@ local function drawSpeakerBar(mouse, pos, speaker, i) --end end end -local speakersTime = {} local function simulateSpeakers() local speakers = {} @@ -127,11 +126,10 @@ local function drawIndicators() canvas:clear() local screenSize = canvas:size() local mousePosition = canvas:mousePosition() - sb.setLogMap("mousePosition", sb.printJson(mousePosition)) local basePos = {screenSize[1] - 350, 50} -- sort it ourselves for now - local speakersRemaining, speakersSorted = {}, {} + local speakersRemaining, speakers = {}, {} local hoveredSpeakerId = nil if hoveredSpeaker then if not mouseOverSpeaker(mousePosition, hoveredSpeakerPosition, 16) then @@ -141,63 +139,32 @@ local function drawIndicators() end end - --local speakers = voice.speakers() - local speakers = { -- just testing before implementing voice lua functions - { - speakerId = 1, - entityId = -65536, - loudness = -96 + math.random() * 96, - muted = false, - name = "theres a pipe bomb up my ass" - } - } - - local sortI = 0 + local speakerCount = 0 local now = os.clock() - for i, speaker in pairs(speakers) do + for i, speaker in pairs(voice.speakers()) do local speakerId = speaker.speakerId speakersRemaining[speakerId] = true - local t = speakersTime[speakerId] - if not t then - t = now - speakersTime[speakerId] = t - end - speaker.startTime = t if speakerId == hoveredSpeakerId then hoveredSpeaker = speaker else - sortI = sortI + 1 - speakersSorted[sortI] = speaker + speakerCount = speakerCount + 1 + speakers[speakerCount] = speaker end end - for i, v in pairs(speakersTime) do - if not speakersRemaining[i] then - speakersTime[i] = nil - end - end - - table.sort(speakersSorted, function(a, b) - if a.startTime == b.startTime then - return a.speakerId < b.speakerId - else - return a.startTime < b.startTime - end - end) - if hoveredSpeaker then - local len = #speakersSorted + local len = #speakers if hoveredSpeakerIndex > len then for i = len + 1, hoveredSpeakerIndex - 1 do - speakersSorted[i] = false + speakers[i] = false end - speakersSorted[hoveredSpeakerIndex] = hoveredSpeaker + speakers[hoveredSpeakerIndex] = hoveredSpeaker else - table.insert(speakersSorted, hoveredSpeakerIndex, hoveredSpeaker) + table.insert(speakers, hoveredSpeakerIndex, hoveredSpeaker) end end - for i, v in pairs(speakersSorted) do + for i, v in pairs(speakers) do if v then local entityId = v.entityId local loudness = v.loudness diff --git a/source/client/StarClientApplication.cpp b/source/client/StarClientApplication.cpp index 9de1642..cee7dec 100644 --- a/source/client/StarClientApplication.cpp +++ b/source/client/StarClientApplication.cpp @@ -20,6 +20,7 @@ #include "StarInterfaceLuaBindings.hpp" #include "StarInputLuaBindings.hpp" +#include "StarVoiceLuaBindings.hpp" namespace Star { @@ -496,6 +497,7 @@ void ClientApplication::changeState(MainAppState newState) { m_statistics = make_shared(m_root->toStoragePath("player"), appController()->statisticsService()); m_universeClient = make_shared(m_playerStorage, m_statistics); m_universeClient->setLuaCallbacks("input", LuaBindings::makeInputCallbacks()); + m_universeClient->setLuaCallbacks("voice", LuaBindings::makeVoiceCallbacks(m_voice.get())); m_mainMixer->setUniverseClient(m_universeClient); m_titleScreen = make_shared(m_playerStorage, m_mainMixer->mixer()); @@ -888,6 +890,11 @@ void ClientApplication::updateRunning() { auto broadcast = strf("data\0voice\0{}{}"s, signatureView, audioDataView); worldClient->sendSecretBroadcast(broadcast, true); } + if (auto mainPlayer = m_universeClient->mainPlayer()) { + auto localSpeaker = m_voice->localSpeaker(); + localSpeaker->entityId = mainPlayer->entityId(); + localSpeaker->name = mainPlayer->name(); + } m_voice->setLocalSpeaker(worldClient->connection()); } worldClient->setInteractiveHighlightMode(isActionTaken(InterfaceAction::ShowLabels)); diff --git a/source/frontend/CMakeLists.txt b/source/frontend/CMakeLists.txt index d8e5390..4c6b9c8 100644 --- a/source/frontend/CMakeLists.txt +++ b/source/frontend/CMakeLists.txt @@ -57,6 +57,7 @@ SET (star_frontend_HEADERS StarTeleportDialog.hpp StarWireInterface.hpp StarVoice.hpp + StarVoiceLuaBindings.hpp ) SET (star_frontend_SOURCES @@ -106,6 +107,7 @@ SET (star_frontend_SOURCES StarTeleportDialog.cpp StarWireInterface.cpp StarVoice.cpp + StarVoiceLuaBindings.cpp ) ADD_LIBRARY (star_frontend OBJECT ${star_frontend_SOURCES} ${star_frontend_HEADERS}) diff --git a/source/frontend/StarVoice.cpp b/source/frontend/StarVoice.cpp index c6ec1d0..9d84033 100644 --- a/source/frontend/StarVoice.cpp +++ b/source/frontend/StarVoice.cpp @@ -28,13 +28,13 @@ EnumMap const VoiceChannelModeNames{ {VoiceChannelMode::Stereo, "Stereo"} }; -float getAudioChunkLoudness(int16_t* data, size_t samples) { +inline float getAudioChunkLoudness(int16_t* data, size_t samples, float volume) { if (!samples) return 0.f; double rms = 0.; for (size_t i = 0; i != samples; ++i) { - float sample = (float)data[i] / 32767.f; + float sample = ((float)data[i] / 32767.f) * volume; rms += (double)(sample * sample); } @@ -46,12 +46,12 @@ float getAudioChunkLoudness(int16_t* data, size_t samples) { return -127.f; } -float getAudioLoudness(int16_t* data, size_t samples) { +float getAudioLoudness(int16_t* data, size_t samples, float volume = 1.0f) { constexpr size_t CHUNK_SIZE = 50; float highest = -127.f; for (size_t i = 0; i < samples; i += CHUNK_SIZE) { - float level = getAudioChunkLoudness(data + i, std::min(i + CHUNK_SIZE, samples) - i); + float level = getAudioChunkLoudness(data + i, std::min(i + CHUNK_SIZE, samples) - i, volume); if (level > highest) highest = level; } @@ -192,6 +192,10 @@ Voice::SpeakerPtr Voice::setLocalSpeaker(SpeakerId speakerId) { return m_speakers.insert(m_speakerId, m_clientSpeaker).first->second; } +Voice::SpeakerPtr Voice::localSpeaker() { + return m_clientSpeaker; +} + Voice::SpeakerPtr Voice::speaker(SpeakerId speakerId) { if (m_speakerId == speakerId) return m_clientSpeaker; @@ -203,28 +207,47 @@ Voice::SpeakerPtr Voice::speaker(SpeakerId speakerId) { } } -void Voice::readAudioData(uint8_t* stream, int len) { - auto now = Time::monotonicMilliseconds(); - if (!m_encoder || m_inputMode == VoiceInputMode::PushToTalk && now > m_lastInputTime) - return; +List Voice::speakers(bool onlyPlaying) { + List result; - // Stop encoding if 2048 bytes have been encoded and not taken by the game thread yet - if (m_encodedChunksLength > 2048) - return; + auto sorter = [](SpeakerPtr const& a, SpeakerPtr const& b) -> bool { + if (a->lastPlayTime != b->lastPlayTime) + return a->lastPlayTime < b->lastPlayTime; + else + return a->speakerId < b->speakerId; + }; - size_t sampleCount = len / 2; - float decibels = getAudioLoudness((int16_t*)stream, sampleCount); - m_clientSpeaker->decibelLevel = decibels; - - bool active = true; - - if (m_inputMode == VoiceInputMode::VoiceActivity) { - if (decibels > m_threshold) - m_lastThresholdTime = now; - active = now - m_lastThresholdTime < 50; + for (auto& p : m_speakers) { + if (!onlyPlaying || p.second->playing) + result.insertSorted(p.second, sorter); } - bool added = false; + return result; +} + +void Voice::readAudioData(uint8_t* stream, int len) { + auto now = Time::monotonicMilliseconds(); + bool active = m_encoder && m_encodedChunksLength < 2048 + && (m_inputMode == VoiceInputMode::VoiceActivity || now < m_lastInputTime); + + size_t sampleCount = len / 2; + + if (active) { + float volume = m_inputVolume; + float decibels = getAudioLoudness((int16_t*)stream, sampleCount); + + if (m_inputMode == VoiceInputMode::VoiceActivity) { + if (decibels > m_threshold) + m_lastThresholdTime = now; + active = now - m_lastThresholdTime < 50; + } + } + + if (active && !m_clientSpeaker->playing) + m_clientSpeaker->lastPlayTime = now; + + if (!(m_clientSpeaker->playing = active)) + return; MutexLocker captureLock(m_captureMutex); if (active) { @@ -232,7 +255,7 @@ void Voice::readAudioData(uint8_t* stream, int len) { auto data = (opus_int16*)malloc(len); memcpy(data, stream, len); m_capturedChunks.emplace(data, sampleCount); // takes ownership - added = true; + m_threadCond.signal(); } else { // Clear out any residual data so they don't manifest at the start of the next encode, whenever that is while (!m_capturedChunks.empty()) @@ -240,9 +263,6 @@ void Voice::readAudioData(uint8_t* stream, int len) { m_capturedChunksFrames = 0; } - - if (added) - m_threadCond.signal(); } void Voice::mix(int16_t* buffer, size_t frameCount, unsigned channels) { @@ -493,9 +513,12 @@ bool Voice::playSpeaker(SpeakerPtr const& speaker, int channels) { if (speaker->playing || speaker->audioStream->samples.size() < minSamples) return false; - speaker->playing = true; - MutexLocker lock(m_activeSpeakersMutex); - m_activeSpeakers.insert(speaker); + if (!speaker->playing) { + speaker->lastPlayTime = Time::monotonicMilliseconds(); + speaker->playing = true; + MutexLocker lock(m_activeSpeakersMutex); + m_activeSpeakers.insert(speaker); + } return true; } @@ -529,6 +552,8 @@ void Voice::thread() { samples[i] *= m_inputVolume; } + m_clientSpeaker->decibelLevel = getAudioLoudness(samples.data(), samples.size()); + if (int encodedSize = opus_encode(m_encoder.get(), samples.data(), VOICE_FRAME_SIZE, (unsigned char*)encoded.ptr(), encoded.size())) { if (encodedSize == 1) continue; diff --git a/source/frontend/StarVoice.hpp b/source/frontend/StarVoice.hpp index 38964b0..d6bc467 100644 --- a/source/frontend/StarVoice.hpp +++ b/source/frontend/StarVoice.hpp @@ -81,9 +81,10 @@ public: Mutex mutex; int64_t lastReceiveTime = 0; + int64_t lastPlayTime = 0; atomic muted = false; - atomic playing = false; + atomic playing = 0; atomic decibelLevel = 0.0f; atomic> channelVolumes = Array::filled(1.0f); @@ -118,7 +119,9 @@ public: // Sets the local speaker ID and returns the local speaker. Must be called upon loading into a world. SpeakerPtr setLocalSpeaker(SpeakerId speakerId); + SpeakerPtr localSpeaker(); SpeakerPtr speaker(SpeakerId speakerId); + List speakers(bool onlyPlaying); // Called when receiving input audio data from SDL, on its own thread. void readAudioData(uint8_t* stream, int len); diff --git a/source/frontend/StarVoiceLuaBindings.cpp b/source/frontend/StarVoiceLuaBindings.cpp new file mode 100644 index 0000000..e3271fd --- /dev/null +++ b/source/frontend/StarVoiceLuaBindings.cpp @@ -0,0 +1,29 @@ +#include "StarVoiceLuaBindings.hpp" +#include "StarVoice.hpp" + +namespace Star { + +LuaCallbacks LuaBindings::makeVoiceCallbacks(Voice* voice) { + LuaCallbacks callbacks; + + callbacks.registerCallback("speakers", [voice](Maybe onlyPlaying) -> List { + List list; + + for (auto& speaker : voice->speakers(onlyPlaying.value(true))) { + list.append(JsonObject{ + {"speakerId", speaker->speakerId }, + {"entityId", speaker->entityId }, + {"name", speaker->name }, + {"playing", (bool)speaker->playing }, + {"muted", (bool)speaker->muted }, + {"loudness", (float)speaker->decibelLevel }, + }); + } + + return list; + }); + + return callbacks; +} + +} diff --git a/source/frontend/StarVoiceLuaBindings.hpp b/source/frontend/StarVoiceLuaBindings.hpp new file mode 100644 index 0000000..8c83e54 --- /dev/null +++ b/source/frontend/StarVoiceLuaBindings.hpp @@ -0,0 +1,16 @@ +#ifndef STAR_VOICE_LUA_BINDINGS_HPP +#define STAR_VOICE_LUA_BINDINGS_HPP + +#include "StarLua.hpp" + +namespace Star { + +STAR_CLASS(Voice); + +namespace LuaBindings { + LuaCallbacks makeVoiceCallbacks(Voice* voice); +} + +} + +#endif From 770314fd7e86c0be355f19bd4273ebd12d5bcdc6 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Tue, 18 Jul 2023 18:18:02 +1000 Subject: [PATCH 21/34] Unused alternate blending for stereo --- source/frontend/StarVoice.cpp | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/source/frontend/StarVoice.cpp b/source/frontend/StarVoice.cpp index 9d84033..f27c000 100644 --- a/source/frontend/StarVoice.cpp +++ b/source/frontend/StarVoice.cpp @@ -288,9 +288,31 @@ void Voice::mix(int16_t* buffer, size_t frameCount, unsigned channels) { speakerBuffer[i] = audio->take(); speaker->decibelLevel = getAudioLoudness(speakerBuffer.data(), samples); - auto channelVolumes = speaker->channelVolumes.load(); + + auto levels = speaker->channelVolumes.load(); for (size_t i = 0; i != samples; ++i) - sharedBuffer[i] += (int32_t)(speakerBuffer[i]) * channelVolumes[i % 2]; + sharedBuffer[i] += (int32_t)(speakerBuffer[i]) * levels[i % 2]; + //Blends the weaker channel into the stronger one, + /* unused, is a bit too strong on stereo music. + float maxLevel = max(levels[0], levels[1]); + float leftToRight = maxLevel != 0.0f ? 1.0f - (levels[0] / maxLevel) : 0.0f; + float rightToLeft = maxLevel != 0.0f ? 1.0f - (levels[1] / maxLevel) : 0.0f; + + int16_t* speakerData = speakerBuffer.data(); + int32_t* sharedData = sharedBuffer.data(); + for (size_t i = 0; i != frameCount; ++i) { + auto leftSample = (float)*speakerData++; + auto rightSample = (float)*speakerData++; + + if (rightToLeft != 0.0f) + leftSample = ( leftSample + rightSample * rightToLeft) / (1.0f + rightToLeft); + if (leftToRight != 0.0f) + rightSample = (rightSample + leftSample * leftToRight) / (1.0f + leftToRight); + + *sharedData++ += (int32_t)leftSample * levels[0]; + *sharedData++ += (int32_t)rightSample * levels[1]; + } + //*/ } else { for (size_t i = 0; i != samples; ++i) From e1645f37fc72e7733b64c51ffbee0370e13cbe29 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Wed, 19 Jul 2023 01:16:22 +1000 Subject: [PATCH 22/34] Support for player entity message commands --- assets/opensb/client.config.patch | 2 +- assets/opensb/player.config.patch | 1 + .../opensb/scripts/opensb/player/commands.lua | 30 ++++++++++ .../opensb/scripts/opensb/player/player.lua | 2 + .../opensb/universeclient/universeclient.lua | 2 + .../universeclient/voicemanager.lua} | 51 ++++++----------- .../opensb.lua => opensb/util/modules.lua} | 16 +++--- source/core/StarLua.cpp | 11 ++++ source/core/StarLua.hpp | 3 + .../frontend/StarClientCommandProcessor.cpp | 55 +++++++++++++------ .../frontend/StarClientCommandProcessor.hpp | 32 +++++------ source/game/scripting/StarLuaRoot.cpp | 5 ++ 12 files changed, 133 insertions(+), 77 deletions(-) create mode 100644 assets/opensb/scripts/opensb/player/commands.lua create mode 100644 assets/opensb/scripts/opensb/player/player.lua create mode 100644 assets/opensb/scripts/opensb/universeclient/universeclient.lua rename assets/opensb/scripts/{universeClient/opensb/voice_manager.lua => opensb/universeclient/voicemanager.lua} (81%) rename assets/opensb/scripts/{universeClient/opensb.lua => opensb/util/modules.lua} (50%) diff --git a/assets/opensb/client.config.patch b/assets/opensb/client.config.patch index e0d5687..a0de983 100644 --- a/assets/opensb/client.config.patch +++ b/assets/opensb/client.config.patch @@ -1,5 +1,5 @@ { - "universeScriptContexts" : { "opensb" : ["/scripts/universeClient/opensb.lua"] }, + "universeScriptContexts" : { "OpenStarbound" : ["/scripts/opensb/universeclient/universeclient.lua"] }, // Disables scissoring and letterboxing on vanilla and modded warp cinematics "warpCinematicBase" : { diff --git a/assets/opensb/player.config.patch b/assets/opensb/player.config.patch index 88bc330..3daa696 100644 --- a/assets/opensb/player.config.patch +++ b/assets/opensb/player.config.patch @@ -1,4 +1,5 @@ { + "genericScriptContexts" : { "OpenStarbound" : "/scripts/opensb/player/player.lua" }, "wireConfig" : { "innerBrightnessScale" : 20, "firstStripeThickness" : 0.6, diff --git a/assets/opensb/scripts/opensb/player/commands.lua b/assets/opensb/scripts/opensb/player/commands.lua new file mode 100644 index 0000000..f62e2b8 --- /dev/null +++ b/assets/opensb/scripts/opensb/player/commands.lua @@ -0,0 +1,30 @@ +local module = {} +modules.commands = module + +local commands = {} +local function command(name, func) + commands[name] = func +end + +function module.init() + for name, func in pairs(commands) do + message.setHandler("/" .. name, function(isLocal, _, ...) + if not isLocal then + return + else + return func(...) + end + end) + end +end + + +command("run", function(src) + local success, result = pcall(loadstring, src, "/run") + if not success then + return "^#f00;compile error: " .. result + else + local success, result = pcall(result) + return not success and "^#f00;error: " .. result or sb.printJson(result) + end +end) \ No newline at end of file diff --git a/assets/opensb/scripts/opensb/player/player.lua b/assets/opensb/scripts/opensb/player/player.lua new file mode 100644 index 0000000..05cbfb9 --- /dev/null +++ b/assets/opensb/scripts/opensb/player/player.lua @@ -0,0 +1,2 @@ +require "/scripts/opensb/util/modules.lua" +modules("/scripts/opensb/player/", {"commands"}) \ No newline at end of file diff --git a/assets/opensb/scripts/opensb/universeclient/universeclient.lua b/assets/opensb/scripts/opensb/universeclient/universeclient.lua new file mode 100644 index 0000000..cad342b --- /dev/null +++ b/assets/opensb/scripts/opensb/universeclient/universeclient.lua @@ -0,0 +1,2 @@ +require "/scripts/opensb/util/modules.lua" +modules("/scripts/opensb/universeclient/", {"voicemanager"}) \ No newline at end of file diff --git a/assets/opensb/scripts/universeClient/opensb/voice_manager.lua b/assets/opensb/scripts/opensb/universeclient/voicemanager.lua similarity index 81% rename from assets/opensb/scripts/universeClient/opensb/voice_manager.lua rename to assets/opensb/scripts/opensb/universeclient/voicemanager.lua index d1c540f..c0aa309 100644 --- a/assets/opensb/scripts/universeClient/opensb/voice_manager.lua +++ b/assets/opensb/scripts/opensb/universeclient/voicemanager.lua @@ -4,7 +4,7 @@ local fmt = string.format local sqrt = math.sqrt local module = {} -submodules.voice_manager = module +modules.voice_manager = module --constants local INDICATOR_PATH = "/interface/voicechat/indicator/" @@ -19,52 +19,35 @@ local LINE_COLOR = {50, 210, 255, 255} local FONT_DIRECTIVES = "?border=1;333;3337?border=1;333;3330" local NAME_PREFIX = "^noshadow,white,set;" -local canvas - -local linePaddingDrawable = { - image = BACK_INDICATOR_IMAGE, - position = {0, 0}, - color = LINE_COLOR, - centered = false -} - -local function getLinePadding(a, b) - linePaddingDrawable.image = BACK_INDICATOR_IMAGE .. fmt("?crop=%i;%i;%i;%i?fade=fff;1", a, 0, b, INDICATOR_SIZE[2]) - linePaddingDrawable.position[1] = a - return linePaddingDrawable; +local linePaddingDrawable +do + local drawable = { image = BACK_INDICATOR_IMAGE, position = {0, 0}, color = LINE_COLOR, centered = false } + function linePaddingDrawable(a, b) + drawable.image = BACK_INDICATOR_IMAGE .. fmt("?crop=%i;%i;%i;%i?fade=fff;1", a, 0, b, INDICATOR_SIZE[2]) + drawable.position[1] = a + return drawable; + end end -local lineDrawable = { - line = {{LINE_PADDING, 24}, {10, 24}}, - width = 48, - color = LINE_COLOR -} - -local function drawTheLine(pos, value) +local function line(pos, value) local width = math.floor((LINE_WIDTH * value) + 0.5) LINE_COLOR[4] = 255 * math.min(1, sqrt(width / 350)) if width > 0 then - canvas:drawDrawable(getLinePadding(0, math.min(12, width)), pos) + canvas:drawDrawable(linePaddingDrawable(0, math.min(12, width)), pos) if width > 12 then lineDrawable.line[2][1] = math.min(width, LINE_WIDTH_PADDED) canvas:drawDrawable(lineDrawable, pos) if width > LINE_WIDTH_PADDED then - canvas:drawDrawable(getLinePadding(LINE_WIDTH_PADDED, width), pos) + canvas:drawDrawable(linePaddingDrawable(LINE_WIDTH_PADDED, width), pos) end end end end -local drawable = { - image = BACK_INDICATOR_IMAGE, - centered = false -} +local canvas -local textPositioning = { - position = {0, 0}, - horizontalAnchor = "left", - verticalAnchor = "mid" -} +local drawable = { image = BACK_INDICATOR_IMAGE, centered = false } +local textPositioning = { position = {0, 0}, horizontalAnchor = "left", verticalAnchor = "mid" } local hoveredSpeaker = nil local hoveredSpeakerIndex = 1 @@ -78,7 +61,7 @@ end local function drawSpeakerBar(mouse, pos, speaker, i) drawable.image = BACK_INDICATOR_IMAGE canvas:drawDrawable(drawable, pos) - drawTheLine(pos, 1 - math.sqrt(math.min(1, math.max(0, speaker.loudness / -50)))) + line(pos, 1 - sqrt(math.min(1, math.max(0, speaker.loudness / -50)))) local hovering = not speaker.isLocal and mouseOverSpeaker(mouse, pos) textPositioning.position = {pos[1] + 49, pos[2] + 24} @@ -128,7 +111,6 @@ local function drawIndicators() local mousePosition = canvas:mousePosition() local basePos = {screenSize[1] - 350, 50} - -- sort it ourselves for now local speakersRemaining, speakers = {}, {} local hoveredSpeakerId = nil if hoveredSpeaker then @@ -140,7 +122,6 @@ local function drawIndicators() end local speakerCount = 0 - local now = os.clock() for i, speaker in pairs(voice.speakers()) do local speakerId = speaker.speakerId speakersRemaining[speakerId] = true diff --git a/assets/opensb/scripts/universeClient/opensb.lua b/assets/opensb/scripts/opensb/util/modules.lua similarity index 50% rename from assets/opensb/scripts/universeClient/opensb.lua rename to assets/opensb/scripts/opensb/util/modules.lua index c1b4dea..2ce7086 100644 --- a/assets/opensb/scripts/universeClient/opensb.lua +++ b/assets/opensb/scripts/opensb/util/modules.lua @@ -1,8 +1,10 @@ -submodules = {} +modules = setmetatable({}, {__call = function(this, path, names) + for i, name in pairs(names) do + require(path .. name .. ".lua") + end +end}) -require "/scripts/universeClient/opensb/voice_manager.lua" - -local submodules, type = submodules, type +local modules, type = modules, type local function call(func, ...) if type(func) == "function" then return func(...) @@ -11,19 +13,19 @@ end function init(...) script.setUpdateDelta(1) - for i, module in pairs(submodules) do + for i, module in pairs(modules) do call(module.init, ...) end end function update(...) - for i, module in pairs(submodules) do + for i, module in pairs(modules) do call(module.update, ...) end end function uninit(...) - for i, module in pairs(submodules) do + for i, module in pairs(modules) do call(module.uninit, ...) end end \ No newline at end of file diff --git a/source/core/StarLua.cpp b/source/core/StarLua.cpp index 4e89097..287bf5c 100644 --- a/source/core/StarLua.cpp +++ b/source/core/StarLua.cpp @@ -1095,6 +1095,17 @@ LuaFunction LuaEngine::createRawFunction(lua_CFunction function) { return LuaFunction(LuaDetail::LuaHandle(RefPtr(this), popHandle(m_state))); } +LuaFunction LuaEngine::createFunctionFromSource(int handleIndex, char const* contents, size_t size, char const* name) { + lua_checkstack(m_state, 2); + + handleError(m_state, luaL_loadbuffer(m_state, contents, size, name)); + + pushHandle(m_state, handleIndex); + lua_setupvalue(m_state, -2, 1); + + return LuaFunction(LuaDetail::LuaHandle(RefPtr(this), popHandle(m_state))); +} + void LuaEngine::pushLuaValue(lua_State* state, LuaValue const& luaValue) { lua_checkstack(state, 1); diff --git a/source/core/StarLua.hpp b/source/core/StarLua.hpp index 500923d..dda0802 100644 --- a/source/core/StarLua.hpp +++ b/source/core/StarLua.hpp @@ -310,6 +310,7 @@ public: using LuaTable::contains; using LuaTable::remove; using LuaTable::engine; + using LuaTable::handleIndex; // Splits the path by '.' character, so can get / set values in tables inside // other tables. If any table in the path is not a table but is accessed as @@ -530,6 +531,8 @@ public: LuaFunction createRawFunction(lua_CFunction func); + LuaFunction createFunctionFromSource(int handleIndex, char const* contents, size_t size, char const* name); + LuaThread createThread(); template diff --git a/source/frontend/StarClientCommandProcessor.cpp b/source/frontend/StarClientCommandProcessor.cpp index 7b6e234..ec5b27c 100644 --- a/source/frontend/StarClientCommandProcessor.cpp +++ b/source/frontend/StarClientCommandProcessor.cpp @@ -10,6 +10,7 @@ #include "StarAiInterface.hpp" #include "StarQuestInterface.hpp" #include "StarStatistics.hpp" +#include "StarInterfaceLuaBindings.hpp" namespace Star { @@ -76,11 +77,10 @@ StringList ClientCommandProcessor::handleCommand(String const& commandLine) { String allArguments = commandLine.substr(1); String command = allArguments.extract(); - auto arguments = m_parser.tokenizeToStringList(allArguments); StringList result; if (auto builtinCommand = m_builtinCommands.maybe(command)) { - result.append((*builtinCommand)(arguments)); + result.append((*builtinCommand)(allArguments)); } else if (auto macroCommand = m_macroCommands.maybe(command)) { for (auto const& c : *macroCommand) { if (c.beginsWith("/")) @@ -89,7 +89,11 @@ StringList ClientCommandProcessor::handleCommand(String const& commandLine) { result.append(c); } } else { - m_universeClient->sendChat(commandLine, ChatSendMode::Broadcast); + auto player = m_universeClient->mainPlayer(); + if (auto messageResult = player->receiveMessage(connectionForEntity(player->entityId()), strf("/{}", command), { allArguments })) + result.append(messageResult->isType(Json::Type::String) ? *messageResult->stringPtr() : messageResult->repr(1, true)); + else + m_universeClient->sendChat(commandLine, ChatSendMode::Broadcast); } return result; } catch (ShellParsingException const& e) { @@ -130,7 +134,8 @@ String ClientCommandProcessor::gravity() { return toString(m_universeClient->worldClient()->gravity(m_universeClient->mainPlayer()->position())); } -String ClientCommandProcessor::debug(StringList const& arguments) { +String ClientCommandProcessor::debug(String const& argumentsString) { + auto arguments = m_parser.tokenizeToStringList(argumentsString); if (!adminCommandAllowed()) return "You must be an admin to use this command."; @@ -168,7 +173,8 @@ String ClientCommandProcessor::asyncLighting() { ? "enabled" : "disabled"); } -String ClientCommandProcessor::setGravity(StringList const& arguments) { +String ClientCommandProcessor::setGravity(String const& argumentsString) { + auto arguments = m_parser.tokenizeToStringList(argumentsString); if (!adminCommandAllowed()) return "You must be an admin to use this command."; @@ -198,7 +204,8 @@ String ClientCommandProcessor::monochromeLighting() { return strf("Monochrome lighting {}", monochrome ? "enabled" : "disabled"); } -String ClientCommandProcessor::radioMessage(StringList const& arguments) { +String ClientCommandProcessor::radioMessage(String const& argumentsString) { + auto arguments = m_parser.tokenizeToStringList(argumentsString); if (!adminCommandAllowed()) return "You must be an admin to use this command."; @@ -225,7 +232,8 @@ String ClientCommandProcessor::clearCinematics() { return "Player cinematic records cleared!"; } -String ClientCommandProcessor::startQuest(StringList const& arguments) { +String ClientCommandProcessor::startQuest(String const& argumentsString) { + auto arguments = m_parser.tokenizeToStringList(argumentsString); if (!adminCommandAllowed()) return "You must be an admin to use this command."; @@ -234,7 +242,8 @@ String ClientCommandProcessor::startQuest(StringList const& arguments) { return "Quest started"; } -String ClientCommandProcessor::completeQuest(StringList const& arguments) { +String ClientCommandProcessor::completeQuest(String const& argumentsString) { + auto arguments = m_parser.tokenizeToStringList(argumentsString); if (!adminCommandAllowed()) return "You must be an admin to use this command."; @@ -242,7 +251,8 @@ String ClientCommandProcessor::completeQuest(StringList const& arguments) { return strf("Quest {} complete", arguments.at(0)); } -String ClientCommandProcessor::failQuest(StringList const& arguments) { +String ClientCommandProcessor::failQuest(String const& argumentsString) { + auto arguments = m_parser.tokenizeToStringList(argumentsString); if (!adminCommandAllowed()) return "You must be an admin to use this command."; @@ -250,7 +260,8 @@ String ClientCommandProcessor::failQuest(StringList const& arguments) { return strf("Quest {} failed", arguments.at(0)); } -String ClientCommandProcessor::previewNewQuest(StringList const& arguments) { +String ClientCommandProcessor::previewNewQuest(String const& argumentsString) { + auto arguments = m_parser.tokenizeToStringList(argumentsString); if (!adminCommandAllowed()) return "You must be an admin to use this command."; @@ -259,7 +270,8 @@ String ClientCommandProcessor::previewNewQuest(StringList const& arguments) { }); } -String ClientCommandProcessor::previewQuestComplete(StringList const& arguments) { +String ClientCommandProcessor::previewQuestComplete(String const& argumentsString) { + auto arguments = m_parser.tokenizeToStringList(argumentsString); if (!adminCommandAllowed()) return "You must be an admin to use this command."; @@ -268,7 +280,8 @@ String ClientCommandProcessor::previewQuestComplete(StringList const& arguments) }); } -String ClientCommandProcessor::previewQuestFailed(StringList const& arguments) { +String ClientCommandProcessor::previewQuestFailed(String const& argumentsString) { + auto arguments = m_parser.tokenizeToStringList(argumentsString); if (!adminCommandAllowed()) return "You must be an admin to use this command."; @@ -294,7 +307,8 @@ String ClientCommandProcessor::deathCount() { return strf("Total deaths: {}{}", deaths, deaths == 0 ? ". Well done!" : ""); } -String ClientCommandProcessor::cinema(StringList const& arguments) { +String ClientCommandProcessor::cinema(String const& argumentsString) { + auto arguments = m_parser.tokenizeToStringList(argumentsString); if (!adminCommandAllowed()) return "You must be an admin to use this command."; @@ -326,7 +340,8 @@ String ClientCommandProcessor::resetAchievements() { return "Unable to reset achievements"; } -String ClientCommandProcessor::statistic(StringList const& arguments) { +String ClientCommandProcessor::statistic(String const& argumentsString) { + auto arguments = m_parser.tokenizeToStringList(argumentsString); if (!adminCommandAllowed()) return "You must be an admin to use this command."; @@ -337,7 +352,8 @@ String ClientCommandProcessor::statistic(StringList const& arguments) { return values.join("\n"); } -String ClientCommandProcessor::giveEssentialItem(StringList const& arguments) { +String ClientCommandProcessor::giveEssentialItem(String const& argumentsString) { + auto arguments = m_parser.tokenizeToStringList(argumentsString); if (!adminCommandAllowed()) return "You must be an admin to use this command."; @@ -354,7 +370,8 @@ String ClientCommandProcessor::giveEssentialItem(StringList const& arguments) { } } -String ClientCommandProcessor::makeTechAvailable(StringList const& arguments) { +String ClientCommandProcessor::makeTechAvailable(String const& argumentsString) { + auto arguments = m_parser.tokenizeToStringList(argumentsString); if (!adminCommandAllowed()) return "You must be an admin to use this command."; @@ -365,7 +382,8 @@ String ClientCommandProcessor::makeTechAvailable(StringList const& arguments) { return strf("Added {} to player's visible techs", arguments.at(0)); } -String ClientCommandProcessor::enableTech(StringList const& arguments) { +String ClientCommandProcessor::enableTech(String const& argumentsString) { + auto arguments = m_parser.tokenizeToStringList(argumentsString); if (!adminCommandAllowed()) return "You must be an admin to use this command."; @@ -377,7 +395,8 @@ String ClientCommandProcessor::enableTech(StringList const& arguments) { return strf("Player tech {} enabled", arguments.at(0)); } -String ClientCommandProcessor::upgradeShip(StringList const& arguments) { +String ClientCommandProcessor::upgradeShip(String const& argumentsString) { + auto arguments = m_parser.tokenizeToStringList(argumentsString); if (!adminCommandAllowed()) return "You must be an admin to use this command."; diff --git a/source/frontend/StarClientCommandProcessor.hpp b/source/frontend/StarClientCommandProcessor.hpp index 41809ed..e9e8962 100644 --- a/source/frontend/StarClientCommandProcessor.hpp +++ b/source/frontend/StarClientCommandProcessor.hpp @@ -29,40 +29,40 @@ private: String reload(); String whoami(); String gravity(); - String debug(StringList const& arguments); + String debug(String const& argumentsString); String boxes(); String fullbright(); String asyncLighting(); - String setGravity(StringList const& arguments); + String setGravity(String const& argumentsString); String resetGravity(); String fixedCamera(); String monochromeLighting(); - String radioMessage(StringList const& arguments); + String radioMessage(String const& argumentsString); String clearRadioMessages(); String clearCinematics(); - String startQuest(StringList const& arguments); - String completeQuest(StringList const& arguments); - String failQuest(StringList const& arguments); - String previewNewQuest(StringList const& arguments); - String previewQuestComplete(StringList const& arguments); - String previewQuestFailed(StringList const& arguments); + String startQuest(String const& argumentsString); + String completeQuest(String const& argumentsString); + String failQuest(String const& argumentsString); + String previewNewQuest(String const& argumentsString); + String previewQuestComplete(String const& argumentsString); + String previewQuestFailed(String const& argumentsString); String clearScannedObjects(); String playTime(); String deathCount(); - String cinema(StringList const& arguments); + String cinema(String const& argumentsString); String suicide(); String naked(); String resetAchievements(); - String statistic(StringList const& arguments); - String giveEssentialItem(StringList const& arguments); - String makeTechAvailable(StringList const& arguments); - String enableTech(StringList const& arguments); - String upgradeShip(StringList const& arguments); + String statistic(String const& argumentsString); + String giveEssentialItem(String const& argumentsString); + String makeTechAvailable(String const& argumentsString); + String enableTech(String const& argumentsString); + String upgradeShip(String const& argumentsString); UniverseClientPtr m_universeClient; CinematicPtr m_cinematicOverlay; MainInterfacePaneManager* m_paneManager; - CaseInsensitiveStringMap> m_builtinCommands; + CaseInsensitiveStringMap> m_builtinCommands; StringMap m_macroCommands; ShellParser m_parser; LuaBaseComponent m_scriptComponent; diff --git a/source/game/scripting/StarLuaRoot.cpp b/source/game/scripting/StarLuaRoot.cpp index d41e8fc..5aa0284 100644 --- a/source/game/scripting/StarLuaRoot.cpp +++ b/source/game/scripting/StarLuaRoot.cpp @@ -106,6 +106,11 @@ LuaContext LuaRoot::createContext(StringList const& scriptPaths) { } }); + newContext.set("loadstring", m_luaEngine->createFunction([newContext](String const& source, Maybe const& name, Maybe const& env) -> LuaFunction { + String functionName = name ? strf("loadstring: {}", name) : "loadstring"; + return newContext.engine().createFunctionFromSource(newContext.handleIndex(), source.utf8Ptr(), source.utf8Size(), functionName.utf8Ptr()); + })); + for (auto const& scriptPath : scriptPaths) cache->loadContextScript(newContext, scriptPath); From a9dac1b2dfedcf8a4c4b0322789d30633af80f8b Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Wed, 19 Jul 2023 01:16:47 +1000 Subject: [PATCH 23/34] Detect setting changes loading Voice JSON --- source/frontend/StarVoice.cpp | 44 ++++++++++++++++++++++++++++------- source/frontend/StarVoice.hpp | 2 ++ 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/source/frontend/StarVoice.cpp b/source/frontend/StarVoice.cpp index f27c000..202444e 100644 --- a/source/frontend/StarVoice.cpp +++ b/source/frontend/StarVoice.cpp @@ -140,20 +140,39 @@ Voice::~Voice() { } void Voice::init() { - resetEncoder(); - if (m_inputEnabled) - openDevice(); + resetEncoder(); + resetDevice(); +} + + +template +inline bool change(T& value, T newValue) { + bool changed = value != newValue; + value = move(newValue); + return changed; } void Voice::loadJson(Json const& config) { - m_enabled = config.getBool("enabled", m_enabled); - m_inputEnabled = config.getBool("inputEnabled", m_inputEnabled); - m_deviceName = config.optQueryString("inputDevice"); + { + bool enabled = shouldEnableInput(); + m_enabled = config.getBool("enabled", m_enabled); + m_inputEnabled = config.getBool("inputEnabled", m_inputEnabled); + if (shouldEnableInput() != enabled) + resetDevice(); + } + + if (change(m_deviceName, config.optQueryString("inputDevice"))) + resetDevice(); + m_threshold = config.getFloat("threshold", m_threshold); m_inputVolume = config.getFloat("inputVolume", m_inputVolume); m_outputVolume = config.getFloat("outputVolume", m_outputVolume); - m_inputMode = VoiceInputModeNames.getLeft(config.getString("inputMode", "PushToTalk")); - m_channelMode = VoiceChannelModeNames.getLeft(config.getString("channelMode", "Mono")); + + if (change(m_inputMode, VoiceInputModeNames.getLeft(config.getString("inputMode", "PushToTalk")))) + m_lastInputTime = 0; + + if (change(m_channelMode, VoiceChannelModeNames.getLeft(config.getString("channelMode", "Mono")))) + resetEncoder(); } @@ -478,7 +497,7 @@ bool Voice::receive(SpeakerPtr speaker, std::string_view view) { } void Voice::setInput(bool input) { - m_lastInputTime = input ? Time::monotonicMilliseconds() + 1000 : 0; + m_lastInputTime = (m_deviceOpen && input) ? Time::monotonicMilliseconds() + 1000 : 0; } OpusDecoder* Voice::createDecoder(int channels) { @@ -505,6 +524,13 @@ void Voice::resetEncoder() { opus_encoder_ctl(m_encoder.get(), OPUS_SET_BITRATE(channels == 2 ? 50000 : 24000)); } +void Voice::resetDevice() { + if (shouldEnableInput()) + openDevice(); + else + closeDevice(); +} + void Voice::openDevice() { closeDevice(); diff --git a/source/frontend/StarVoice.hpp b/source/frontend/StarVoice.hpp index d6bc467..18e677a 100644 --- a/source/frontend/StarVoice.hpp +++ b/source/frontend/StarVoice.hpp @@ -147,8 +147,10 @@ public: private: static Voice* s_singleton; void resetEncoder(); + void resetDevice(); void openDevice(); void closeDevice(); + inline bool shouldEnableInput() const { return m_enabled && m_inputEnabled; } bool playSpeaker(SpeakerPtr const& speaker, int channels); From 3cdbf8bf014f72827906ef1ba1715845a95e9919 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Wed, 19 Jul 2023 18:15:49 +1000 Subject: [PATCH 24/34] Lua functions for updating Voice settings, improve loudness visualization --- .../opensb/universeclient/voicemanager.lua | 17 ++++---- source/frontend/StarVoice.cpp | 42 ++++++++++++++++--- source/frontend/StarVoice.hpp | 7 +++- source/frontend/StarVoiceLuaBindings.cpp | 16 ++++--- source/game/scripting/StarLuaRoot.cpp | 2 +- 5 files changed, 63 insertions(+), 21 deletions(-) diff --git a/assets/opensb/scripts/opensb/universeclient/voicemanager.lua b/assets/opensb/scripts/opensb/universeclient/voicemanager.lua index c0aa309..be57182 100644 --- a/assets/opensb/scripts/opensb/universeclient/voicemanager.lua +++ b/assets/opensb/scripts/opensb/universeclient/voicemanager.lua @@ -1,7 +1,7 @@ -- Manages the voice HUD indicators and click-to-mute/unmute. local fmt = string.format -local sqrt = math.sqrt +local sqrt, min, max = math.sqrt, math.min, math.max local module = {} modules.voice_manager = module @@ -19,6 +19,10 @@ local LINE_COLOR = {50, 210, 255, 255} local FONT_DIRECTIVES = "?border=1;333;3337?border=1;333;3330" local NAME_PREFIX = "^noshadow,white,set;" +local function dbToLoudness(db) return 2 ^ (db / 6) end + +local canvas + local linePaddingDrawable do local drawable = { image = BACK_INDICATOR_IMAGE, position = {0, 0}, color = LINE_COLOR, centered = false } @@ -29,6 +33,7 @@ do end end +local lineDrawable = { line = {{LINE_PADDING, 24}, {10, 24}}, width = 48, color = LINE_COLOR } local function line(pos, value) local width = math.floor((LINE_WIDTH * value) + 0.5) LINE_COLOR[4] = 255 * math.min(1, sqrt(width / 350)) @@ -44,8 +49,6 @@ local function line(pos, value) end end -local canvas - local drawable = { image = BACK_INDICATOR_IMAGE, centered = false } local textPositioning = { position = {0, 0}, horizontalAnchor = "left", verticalAnchor = "mid" } @@ -61,7 +64,7 @@ end local function drawSpeakerBar(mouse, pos, speaker, i) drawable.image = BACK_INDICATOR_IMAGE canvas:drawDrawable(drawable, pos) - line(pos, 1 - sqrt(math.min(1, math.max(0, speaker.loudness / -50)))) + line(pos, dbToLoudness(speaker.smoothDecibels)) local hovering = not speaker.isLocal and mouseOverSpeaker(mouse, pos) textPositioning.position = {pos[1] + 49, pos[2] + 24} @@ -94,11 +97,13 @@ end local function simulateSpeakers() local speakers = {} for i = 2, 5 + math.floor((math.sin(os.clock()) * 4) + .5) do + local dB = -48 + 48 * math.sin(os.clock() + (i * 0.5)) speakers[i] = { speakerId = i, entityId = -65536 * i, name = "Player " .. i, - loudness = -48 + 48 * math.sin(os.clock() + (i * 0.5)), + decibels = dB, + smoothDecibels = dB, muted = false } end @@ -147,8 +152,6 @@ local function drawIndicators() for i, v in pairs(speakers) do if v then - local entityId = v.entityId - local loudness = v.loudness local pos = {basePos[1], basePos[2] + (i - 1) * 52} drawSpeakerBar(mousePosition, pos, v, i) end diff --git a/source/frontend/StarVoice.cpp b/source/frontend/StarVoice.cpp index 202444e..9207555 100644 --- a/source/frontend/StarVoice.cpp +++ b/source/frontend/StarVoice.cpp @@ -4,6 +4,7 @@ #include "StarTime.hpp" #include "StarRoot.hpp" #include "StarLogging.hpp" +#include "StarInterpolation.hpp" #include "opus/include/opus.h" #include "SDL.h" @@ -153,6 +154,8 @@ inline bool change(T& value, T newValue) { } void Voice::loadJson(Json const& config) { + // Not all keys are required + { bool enabled = shouldEnableInput(); m_enabled = config.getBool("enabled", m_enabled); @@ -161,18 +164,26 @@ void Voice::loadJson(Json const& config) { resetDevice(); } - if (change(m_deviceName, config.optQueryString("inputDevice"))) + if (config.contains("deviceName") // Make sure null-type key exists + && change(m_deviceName, config.optString("deviceName"))) resetDevice(); m_threshold = config.getFloat("threshold", m_threshold); m_inputVolume = config.getFloat("inputVolume", m_inputVolume); m_outputVolume = config.getFloat("outputVolume", m_outputVolume); - if (change(m_inputMode, VoiceInputModeNames.getLeft(config.getString("inputMode", "PushToTalk")))) - m_lastInputTime = 0; + if (auto inputMode = config.optString("inputMode")) { + if (change(m_inputMode, VoiceInputModeNames.getLeft(*inputMode))) + m_lastInputTime = 0; + } - if (change(m_channelMode, VoiceChannelModeNames.getLeft(config.getString("channelMode", "Mono")))) - resetEncoder(); + if (auto channelMode = config.optString("channelMode")) { + if (change(m_channelMode, VoiceChannelModeNames.getLeft(*channelMode))) { + closeDevice(); + resetEncoder(); + resetDevice(); + } + } } @@ -366,6 +377,15 @@ void Voice::update(PositionalAttenuationFunction positionalAttenuationFunction) 1.0f - positionalAttenuationFunction(0, speaker->position, 1.0f), 1.0f - positionalAttenuationFunction(1, speaker->position, 1.0f) }; + + auto& dbHistory = speaker->dbHistory; + memcpy(&dbHistory[1], &dbHistory[0], (dbHistory.size() - 1) * sizeof(float)); + dbHistory[0] = speaker->decibelLevel; + float smoothDb = 0.0f; + for (float dB : dbHistory) + smoothDb += dB; + + speaker->smoothDb = smoothDb / dbHistory.size(); } } } @@ -386,6 +406,17 @@ void Voice::setDeviceName(Maybe deviceName) { openDevice(); } +StringList Voice::availableDevices() { + int devices = SDL_GetNumAudioDevices(1); + StringList deviceList; + if (devices > 0) { + deviceList.reserve(devices); + for (size_t i = 0; i != devices; ++i) + deviceList.emplace_back(SDL_GetAudioDeviceName(i, 1)); + } + return deviceList; +} + int Voice::send(DataStreamBuffer& out, size_t budget) { out.setByteOrder(ByteOrder::LittleEndian); out.write(VOICE_VERSION); @@ -520,6 +551,7 @@ OpusEncoder* Voice::createEncoder(int channels) { void Voice::resetEncoder() { int channels = encoderChannels(); + MutexLocker locker(m_threadMutex); m_encoder.reset(createEncoder(channels)); opus_encoder_ctl(m_encoder.get(), OPUS_SET_BITRATE(channels == 2 ? 50000 : 24000)); } diff --git a/source/frontend/StarVoice.hpp b/source/frontend/StarVoice.hpp index 18e677a..236dfb0 100644 --- a/source/frontend/StarVoice.hpp +++ b/source/frontend/StarVoice.hpp @@ -82,11 +82,13 @@ public: int64_t lastReceiveTime = 0; int64_t lastPlayTime = 0; + float smoothDb = -96.0f; + Array dbHistory = Array::filled(0); atomic muted = false; atomic playing = 0; - atomic decibelLevel = 0.0f; - atomic> channelVolumes = Array::filled(1.0f); + atomic decibelLevel = -96.0f; + atomic> channelVolumes = Array::filled(1); unsigned int minimumPlaySamples = 4096; @@ -133,6 +135,7 @@ public: void update(PositionalAttenuationFunction positionalAttenuationFunction = {}); void setDeviceName(Maybe device); + StringList availableDevices(); int send(DataStreamBuffer& out, size_t budget); bool receive(SpeakerPtr speaker, std::string_view view); diff --git a/source/frontend/StarVoiceLuaBindings.cpp b/source/frontend/StarVoiceLuaBindings.cpp index e3271fd..a91937e 100644 --- a/source/frontend/StarVoiceLuaBindings.cpp +++ b/source/frontend/StarVoiceLuaBindings.cpp @@ -6,17 +6,21 @@ namespace Star { LuaCallbacks LuaBindings::makeVoiceCallbacks(Voice* voice) { LuaCallbacks callbacks; + callbacks.registerCallback("getSettings", [voice]() -> Json { return voice->saveJson(); }); + callbacks.registerCallback("mergeSettings", [voice](Json const& settings) { voice->loadJson(settings); }); + callbacks.registerCallback("speakers", [voice](Maybe onlyPlaying) -> List { List list; for (auto& speaker : voice->speakers(onlyPlaying.value(true))) { list.append(JsonObject{ - {"speakerId", speaker->speakerId }, - {"entityId", speaker->entityId }, - {"name", speaker->name }, - {"playing", (bool)speaker->playing }, - {"muted", (bool)speaker->muted }, - {"loudness", (float)speaker->decibelLevel }, + {"speakerId", speaker->speakerId }, + {"entityId", speaker->entityId }, + {"name", speaker->name }, + {"playing", (bool)speaker->playing }, + {"muted", (bool)speaker->muted }, + {"decibels", (float)speaker->decibelLevel }, + {"smoothDecibels", (float)speaker->smoothDb }, }); } diff --git a/source/game/scripting/StarLuaRoot.cpp b/source/game/scripting/StarLuaRoot.cpp index 5aa0284..152bbec 100644 --- a/source/game/scripting/StarLuaRoot.cpp +++ b/source/game/scripting/StarLuaRoot.cpp @@ -107,7 +107,7 @@ LuaContext LuaRoot::createContext(StringList const& scriptPaths) { }); newContext.set("loadstring", m_luaEngine->createFunction([newContext](String const& source, Maybe const& name, Maybe const& env) -> LuaFunction { - String functionName = name ? strf("loadstring: {}", name) : "loadstring"; + String functionName = name ? strf("loadstring: {}", *name) : "loadstring"; return newContext.engine().createFunctionFromSource(newContext.handleIndex(), source.utf8Ptr(), source.utf8Size(), functionName.utf8Ptr()); })); From 35b1c36b171d67dae4c47bad9b4aa31da6ba0070 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Wed, 19 Jul 2023 18:59:35 +1000 Subject: [PATCH 25/34] Add loopback, fix mono and leak --- .../opensb/universeclient/voicemanager.lua | 2 +- source/client/StarClientApplication.cpp | 1 + source/frontend/StarVoice.cpp | 63 +++++++++++-------- source/frontend/StarVoice.hpp | 4 ++ 4 files changed, 43 insertions(+), 27 deletions(-) diff --git a/assets/opensb/scripts/opensb/universeclient/voicemanager.lua b/assets/opensb/scripts/opensb/universeclient/voicemanager.lua index be57182..b7468ce 100644 --- a/assets/opensb/scripts/opensb/universeclient/voicemanager.lua +++ b/assets/opensb/scripts/opensb/universeclient/voicemanager.lua @@ -19,7 +19,7 @@ local LINE_COLOR = {50, 210, 255, 255} local FONT_DIRECTIVES = "?border=1;333;3337?border=1;333;3330" local NAME_PREFIX = "^noshadow,white,set;" -local function dbToLoudness(db) return 2 ^ (db / 6) end +local function dbToLoudness(db) return 2 ^ (db / 8) end local canvas diff --git a/source/client/StarClientApplication.cpp b/source/client/StarClientApplication.cpp index cee7dec..9894ed5 100644 --- a/source/client/StarClientApplication.cpp +++ b/source/client/StarClientApplication.cpp @@ -892,6 +892,7 @@ void ClientApplication::updateRunning() { } if (auto mainPlayer = m_universeClient->mainPlayer()) { auto localSpeaker = m_voice->localSpeaker(); + localSpeaker->position = mainPlayer->position(); localSpeaker->entityId = mainPlayer->entityId(); localSpeaker->name = mainPlayer->name(); } diff --git a/source/frontend/StarVoice.cpp b/source/frontend/StarVoice.cpp index 9207555..71a1a3d 100644 --- a/source/frontend/StarVoice.cpp +++ b/source/frontend/StarVoice.cpp @@ -63,11 +63,17 @@ float getAudioLoudness(int16_t* data, size_t samples, float volume = 1.0f) { struct VoiceAudioStream { // TODO: This should really be a ring buffer instead. std::queue samples; - SDL_AudioStream* sdlAudioStream; + SDL_AudioStream* sdlAudioStreamMono; + SDL_AudioStream* sdlAudioStreamStereo; Mutex mutex; - VoiceAudioStream() : sdlAudioStream(SDL_NewAudioStream(AUDIO_S16, 2, 48000, AUDIO_S16SYS, 2, 44100)) {}; - ~VoiceAudioStream() { SDL_FreeAudioStream(sdlAudioStream); } + VoiceAudioStream() + : sdlAudioStreamMono (SDL_NewAudioStream(AUDIO_S16, 1, 48000, AUDIO_S16SYS, 1, 44100)) + , sdlAudioStreamStereo(SDL_NewAudioStream(AUDIO_S16, 2, 48000, AUDIO_S16SYS, 2, 44100)) {}; + ~VoiceAudioStream() { + SDL_FreeAudioStream(sdlAudioStreamMono); + SDL_FreeAudioStream(sdlAudioStreamStereo); + } inline int16_t take() { int16_t sample = 0; @@ -78,11 +84,12 @@ struct VoiceAudioStream { return sample; } - size_t resample(int16_t* in, size_t inSamples, std::vector& out) { - SDL_AudioStreamPut(sdlAudioStream, in, inSamples * sizeof(int16_t)); - if (int available = SDL_AudioStreamAvailable(sdlAudioStream)) { + size_t resample(int16_t* in, size_t inSamples, std::vector& out, bool mono) { + SDL_AudioStream* stream = mono ? sdlAudioStreamMono : sdlAudioStreamStereo; + SDL_AudioStreamPut(stream, in, inSamples * sizeof(int16_t)); + if (int available = SDL_AudioStreamAvailable(stream)) { out.resize(available / 2); - SDL_AudioStreamGet(sdlAudioStream, out.data(), available); + SDL_AudioStreamGet(stream, out.data(), available); return available; } return 0; @@ -171,6 +178,9 @@ void Voice::loadJson(Json const& config) { m_threshold = config.getFloat("threshold", m_threshold); m_inputVolume = config.getFloat("inputVolume", m_inputVolume); m_outputVolume = config.getFloat("outputVolume", m_outputVolume); + + if (change(m_loopBack, config.getBool("loopBack", m_loopBack))) + m_clientSpeaker->playing = false; if (auto inputMode = config.optString("inputMode")) { if (change(m_inputMode, VoiceInputModeNames.getLeft(*inputMode))) @@ -273,10 +283,14 @@ void Voice::readAudioData(uint8_t* stream, int len) { } } - if (active && !m_clientSpeaker->playing) - m_clientSpeaker->lastPlayTime = now; + if (!m_loopBack) { + if (active && !m_clientSpeaker->playing) + m_clientSpeaker->lastPlayTime = now; - if (!(m_clientSpeaker->playing = active)) + m_clientSpeaker->playing = active; + } + + if (!active) return; MutexLocker captureLock(m_captureMutex); @@ -311,7 +325,6 @@ void Voice::mix(int16_t* buffer, size_t frameCount, unsigned channels) { VoiceAudioStream* audio = speaker->audioStream.get(); MutexLocker audioLock(audio->mutex); if (!audio->samples.empty()) { - SDL_AudioStream* sdlStream = audio->sdlAudioStream; if (!speaker->muted) { mix = true; for (size_t i = 0; i != samples; ++i) @@ -440,6 +453,8 @@ int Voice::send(DataStreamBuffer& out, size_t budget) { } m_lastSentTime = Time::monotonicMilliseconds(); + if (m_loopBack) + receive(m_clientSpeaker, { out.ptr(), out.size() }); return 1; } @@ -472,30 +487,26 @@ bool Voice::receive(SpeakerPtr speaker, std::string_view view) { if (samples < 0) throw VoiceException(strf("Decoder error: {}", opus_strerror(samples)), false); - size_t decodeBufferSize = samples * sizeof(opus_int16) * (size_t)channels; - opus_int16* decodeBuffer = (opus_int16*)malloc(decodeBufferSize); + m_decodeBuffer.resize(samples * (size_t)channels); - int decodedSamples = opus_decode(decoder, opusData, opusLength, decodeBuffer, decodeBufferSize, 0); + int decodedSamples = opus_decode(decoder, opusData, opusLength, m_decodeBuffer.data(), m_decodeBuffer.size() * sizeof(int16_t), 0); if (decodedSamples <= 0) { - free(decodeBuffer); if (decodedSamples < 0) throw VoiceException(strf("Decoder error: {}", opus_strerror(samples)), false); return true; } - decodedSamples *= channels; - //Logger::info("Voice: decoded Opus chunk {} bytes -> {} samples", opusLength, decodedSamples); + //Logger::info("Voice: decoded Opus chunk {} bytes -> {} samples", opusLength, decodedSamples * channels); + + speaker->audioStream->resample(m_decodeBuffer.data(), (size_t)decodedSamples * channels, m_resampleBuffer, mono); { - std::vector resamBuffer(decodedSamples, 0); - speaker->audioStream->resample(decodeBuffer, decodedSamples, resamBuffer); - MutexLocker lock(speaker->audioStream->mutex); auto& samples = speaker->audioStream->samples; auto now = Time::monotonicMilliseconds(); if (now - speaker->lastReceiveTime < 1000) { - auto limit = ((size_t)speaker->minimumPlaySamples + 22050) * (size_t)channels; + auto limit = (size_t)speaker->minimumPlaySamples + 22050; if (samples.size() > limit) { // skip ahead if we're getting too far for (size_t i = samples.size(); i >= limit; --i) samples.pop(); @@ -507,13 +518,13 @@ bool Voice::receive(SpeakerPtr speaker, std::string_view view) { speaker->lastReceiveTime = now; if (mono) { - for (int16_t sample : resamBuffer) { + for (int16_t sample : m_resampleBuffer) { samples.push(sample); samples.push(sample); } } else { - for (int16_t sample : resamBuffer) + for (int16_t sample : m_resampleBuffer) samples.push(sample); } } @@ -589,8 +600,7 @@ void Voice::closeDevice() { } bool Voice::playSpeaker(SpeakerPtr const& speaker, int channels) { - unsigned int minSamples = speaker->minimumPlaySamples * channels; - if (speaker->playing || speaker->audioStream->samples.size() < minSamples) + if (speaker->playing || speaker->audioStream->samples.size() < speaker->minimumPlaySamples) return false; if (!speaker->playing) { @@ -632,7 +642,8 @@ void Voice::thread() { samples[i] *= m_inputVolume; } - m_clientSpeaker->decibelLevel = getAudioLoudness(samples.data(), samples.size()); + if (!m_loopBack) + m_clientSpeaker->decibelLevel = getAudioLoudness(samples.data(), samples.size()); if (int encodedSize = opus_encode(m_encoder.get(), samples.data(), VOICE_FRAME_SIZE, (unsigned char*)encoded.ptr(), encoded.size())) { if (encodedSize == 1) diff --git a/source/frontend/StarVoice.hpp b/source/frontend/StarVoice.hpp index 236dfb0..15d9cd6 100644 --- a/source/frontend/StarVoice.hpp +++ b/source/frontend/StarVoice.hpp @@ -180,6 +180,7 @@ private: int64_t m_nextSaveTime = 0; bool m_enabled = true; bool m_inputEnabled = false; + bool m_loopBack = false; int m_deviceChannels = 1; bool m_deviceOpen = false; @@ -192,6 +193,9 @@ private: ConditionVariable m_threadCond; atomic m_stopThread; + std::vector m_decodeBuffer; + std::vector m_resampleBuffer; + ApplicationControllerPtr m_applicationController; struct EncodedChunk { From 620c23e70a455bc0d7b0af3a279ca8bd3ec1488d Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Wed, 19 Jul 2023 19:04:31 +1000 Subject: [PATCH 26/34] Schedule voice config save when updating settings from Lua --- source/client/StarClientApplication.cpp | 2 +- source/frontend/StarVoice.cpp | 26 +++++++++++++++++-------- source/frontend/StarVoice.hpp | 2 +- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/source/client/StarClientApplication.cpp b/source/client/StarClientApplication.cpp index 9894ed5..ba2bbd1 100644 --- a/source/client/StarClientApplication.cpp +++ b/source/client/StarClientApplication.cpp @@ -211,7 +211,7 @@ void ClientApplication::applicationInit(ApplicationControllerPtr appController) appController->setUpdateTrackWindow(assets->json("/client.config:updateTrackWindow").toFloat()); if (auto jVoice = configuration->get("voice")) - m_voice->loadJson(jVoice.toObject()); + m_voice->loadJson(jVoice.toObject(), true); m_voice->init(); } diff --git a/source/frontend/StarVoice.cpp b/source/frontend/StarVoice.cpp index 71a1a3d..d553fa1 100644 --- a/source/frontend/StarVoice.cpp +++ b/source/frontend/StarVoice.cpp @@ -141,7 +141,9 @@ Voice::~Voice() { m_thread.finish(); - save(); + if (m_nextSaveTime) + save(); + closeDevice(); s_singleton = nullptr; @@ -154,46 +156,54 @@ void Voice::init() { template -inline bool change(T& value, T newValue) { +inline bool change(T& value, T newValue, bool& out) { bool changed = value != newValue; + out |= changed; value = move(newValue); return changed; } -void Voice::loadJson(Json const& config) { +void Voice::loadJson(Json const& config, bool skipSave) { // Not all keys are required + bool changed = false; + { bool enabled = shouldEnableInput(); m_enabled = config.getBool("enabled", m_enabled); m_inputEnabled = config.getBool("inputEnabled", m_inputEnabled); - if (shouldEnableInput() != enabled) + if (shouldEnableInput() != enabled) { + changed = true; resetDevice(); + } } if (config.contains("deviceName") // Make sure null-type key exists - && change(m_deviceName, config.optString("deviceName"))) + && change(m_deviceName, config.optString("deviceName"), changed)) resetDevice(); m_threshold = config.getFloat("threshold", m_threshold); m_inputVolume = config.getFloat("inputVolume", m_inputVolume); m_outputVolume = config.getFloat("outputVolume", m_outputVolume); - if (change(m_loopBack, config.getBool("loopBack", m_loopBack))) + if (change(m_loopBack, config.getBool("loopBack", m_loopBack), changed)) m_clientSpeaker->playing = false; if (auto inputMode = config.optString("inputMode")) { - if (change(m_inputMode, VoiceInputModeNames.getLeft(*inputMode))) + if (change(m_inputMode, VoiceInputModeNames.getLeft(*inputMode), changed)) m_lastInputTime = 0; } if (auto channelMode = config.optString("channelMode")) { - if (change(m_channelMode, VoiceChannelModeNames.getLeft(*channelMode))) { + if (change(m_channelMode, VoiceChannelModeNames.getLeft(*channelMode), changed)) { closeDevice(); resetEncoder(); resetDevice(); } } + + if (changed && !skipSave) + scheduleSave(); } diff --git a/source/frontend/StarVoice.hpp b/source/frontend/StarVoice.hpp index 15d9cd6..0cd8ca8 100644 --- a/source/frontend/StarVoice.hpp +++ b/source/frontend/StarVoice.hpp @@ -113,7 +113,7 @@ public: void init(); - void loadJson(Json const& config); + void loadJson(Json const& config, bool skipSave = false); Json saveJson() const; void save() const; From 0c1c3611b1b1c1b17efac547ad08a6821f3b8f01 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Wed, 19 Jul 2023 19:06:53 +1000 Subject: [PATCH 27/34] Save loopback setting --- source/frontend/StarVoice.cpp | 9 +++++---- source/frontend/StarVoice.hpp | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/source/frontend/StarVoice.cpp b/source/frontend/StarVoice.cpp index d553fa1..a4641ab 100644 --- a/source/frontend/StarVoice.cpp +++ b/source/frontend/StarVoice.cpp @@ -186,7 +186,7 @@ void Voice::loadJson(Json const& config, bool skipSave) { m_inputVolume = config.getFloat("inputVolume", m_inputVolume); m_outputVolume = config.getFloat("outputVolume", m_outputVolume); - if (change(m_loopBack, config.getBool("loopBack", m_loopBack), changed)) + if (change(m_loopback, config.getBool("loopback", m_loopback), changed)) m_clientSpeaker->playing = false; if (auto inputMode = config.optString("inputMode")) { @@ -218,6 +218,7 @@ Json Voice::saveJson() const { {"outputVolume", m_outputVolume}, {"inputMode", VoiceInputModeNames.getRight(m_inputMode)}, {"channelMode", VoiceChannelModeNames.getRight(m_channelMode)}, + {"loopback", m_loopback}, {"version", 1} }; } @@ -293,7 +294,7 @@ void Voice::readAudioData(uint8_t* stream, int len) { } } - if (!m_loopBack) { + if (!m_loopback) { if (active && !m_clientSpeaker->playing) m_clientSpeaker->lastPlayTime = now; @@ -463,7 +464,7 @@ int Voice::send(DataStreamBuffer& out, size_t budget) { } m_lastSentTime = Time::monotonicMilliseconds(); - if (m_loopBack) + if (m_loopback) receive(m_clientSpeaker, { out.ptr(), out.size() }); return 1; } @@ -652,7 +653,7 @@ void Voice::thread() { samples[i] *= m_inputVolume; } - if (!m_loopBack) + if (!m_loopback) m_clientSpeaker->decibelLevel = getAudioLoudness(samples.data(), samples.size()); if (int encodedSize = opus_encode(m_encoder.get(), samples.data(), VOICE_FRAME_SIZE, (unsigned char*)encoded.ptr(), encoded.size())) { diff --git a/source/frontend/StarVoice.hpp b/source/frontend/StarVoice.hpp index 0cd8ca8..99228e8 100644 --- a/source/frontend/StarVoice.hpp +++ b/source/frontend/StarVoice.hpp @@ -180,7 +180,7 @@ private: int64_t m_nextSaveTime = 0; bool m_enabled = true; bool m_inputEnabled = false; - bool m_loopBack = false; + bool m_loopback = false; int m_deviceChannels = 1; bool m_deviceOpen = false; From d682b164aa87435183a5ad3196b25b5ff8a5ad18 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Wed, 19 Jul 2023 21:12:14 +1000 Subject: [PATCH 28/34] more Lua voice callbacks --- source/client/StarClientApplication.cpp | 2 ++ source/frontend/StarVoice.cpp | 39 +++++++++++++++++++++--- source/frontend/StarVoice.hpp | 6 +++- source/frontend/StarVoiceLuaBindings.cpp | 28 +++++++++-------- 4 files changed, 58 insertions(+), 17 deletions(-) diff --git a/source/client/StarClientApplication.cpp b/source/client/StarClientApplication.cpp index ba2bbd1..d8d79fd 100644 --- a/source/client/StarClientApplication.cpp +++ b/source/client/StarClientApplication.cpp @@ -464,6 +464,8 @@ void ClientApplication::changeState(MainAppState newState) { } m_cinematicOverlay->stop(); + m_voice->clearSpeakers(); + if (auto p2pNetworkingService = appController()->p2pNetworkingService()) { p2pNetworkingService->setJoinUnavailable(); p2pNetworkingService->setAcceptingP2PConnections(false); diff --git a/source/frontend/StarVoice.cpp b/source/frontend/StarVoice.cpp index a4641ab..b7f8a6c 100644 --- a/source/frontend/StarVoice.cpp +++ b/source/frontend/StarVoice.cpp @@ -1,5 +1,6 @@ #include "StarVoice.hpp" #include "StarFormat.hpp" +#include "StarJsonExtra.hpp" #include "StarApplicationController.hpp" #include "StarTime.hpp" #include "StarRoot.hpp" @@ -103,6 +104,19 @@ Voice::Speaker::Speaker(SpeakerId id) audioStream = make_shared(); } +Json Voice::Speaker::toJson() const { + return JsonObject{ + {"speakerId", speakerId}, + {"entityId", entityId }, + {"name", name }, + {"playing", (bool)playing}, + {"muted", (bool)muted }, + {"decibels", (float)decibelLevel}, + {"smoothDecibels", (float)smoothDb }, + {"position", jsonFromVec2F(position)} + }; +} + Voice* Voice::s_singleton; Voice* Voice::singletonPtr() { @@ -258,7 +272,11 @@ Voice::SpeakerPtr Voice::speaker(SpeakerId speakerId) { } } -List Voice::speakers(bool onlyPlaying) { +HashMap& Voice::speakers() { + return m_speakers; +} + +List Voice::sortedSpeakers(bool onlyPlaying) { List result; auto sorter = [](SpeakerPtr const& a, SpeakerPtr const& b) -> bool { @@ -276,6 +294,16 @@ List Voice::speakers(bool onlyPlaying) { return result; } +void Voice::clearSpeakers() { + auto it = m_speakers.begin(); + while (it != m_speakers.end()) { + if (it->second == m_clientSpeaker) + it = ++it; + else + it = m_speakers.erase(it); + } +} + void Voice::readAudioData(uint8_t* stream, int len) { auto now = Time::monotonicMilliseconds(); bool active = m_encoder && m_encodedChunksLength < 2048 @@ -335,7 +363,7 @@ void Voice::mix(int16_t* buffer, size_t frameCount, unsigned channels) { SpeakerPtr const& speaker = *it; VoiceAudioStream* audio = speaker->audioStream.get(); MutexLocker audioLock(audio->mutex); - if (!audio->samples.empty()) { + if (speaker->playing && !audio->samples.empty()) { if (!speaker->muted) { mix = true; for (size_t i = 0; i != samples; ++i) @@ -343,9 +371,10 @@ void Voice::mix(int16_t* buffer, size_t frameCount, unsigned channels) { speaker->decibelLevel = getAudioLoudness(speakerBuffer.data(), samples); - auto levels = speaker->channelVolumes.load(); + float volume = speaker->volume; + Array2F levels = speaker->channelVolumes; for (size_t i = 0; i != samples; ++i) - sharedBuffer[i] += (int32_t)(speakerBuffer[i]) * levels[i % 2]; + sharedBuffer[i] += (int32_t)(speakerBuffer[i]) * levels[i % 2] * volume; //Blends the weaker channel into the stronger one, /* unused, is a bit too strong on stereo music. float maxLevel = max(levels[0], levels[1]); @@ -606,6 +635,8 @@ void Voice::closeDevice() { return; m_applicationController->closeAudioInputDevice(); + if (!m_loopback) + m_clientSpeaker->playing = false; m_deviceOpen = false; } diff --git a/source/frontend/StarVoice.hpp b/source/frontend/StarVoice.hpp index 99228e8..4500aa6 100644 --- a/source/frontend/StarVoice.hpp +++ b/source/frontend/StarVoice.hpp @@ -88,11 +88,13 @@ public: atomic muted = false; atomic playing = 0; atomic decibelLevel = -96.0f; + atomic volume = 1.0f; atomic> channelVolumes = Array::filled(1); unsigned int minimumPlaySamples = 4096; Speaker(SpeakerId speakerId); + Json toJson() const; }; typedef std::shared_ptr SpeakerPtr; @@ -123,7 +125,9 @@ public: SpeakerPtr setLocalSpeaker(SpeakerId speakerId); SpeakerPtr localSpeaker(); SpeakerPtr speaker(SpeakerId speakerId); - List speakers(bool onlyPlaying); + HashMap& speakers(); + List sortedSpeakers(bool onlyPlaying); + void clearSpeakers(); // Called when receiving input audio data from SDL, on its own thread. void readAudioData(uint8_t* stream, int len); diff --git a/source/frontend/StarVoiceLuaBindings.cpp b/source/frontend/StarVoiceLuaBindings.cpp index a91937e..1b62aef 100644 --- a/source/frontend/StarVoiceLuaBindings.cpp +++ b/source/frontend/StarVoiceLuaBindings.cpp @@ -1,28 +1,32 @@ +#include "StarLuaConverters.hpp" #include "StarVoiceLuaBindings.hpp" #include "StarVoice.hpp" + namespace Star { +typedef Voice::SpeakerId SpeakerId; LuaCallbacks LuaBindings::makeVoiceCallbacks(Voice* voice) { LuaCallbacks callbacks; - callbacks.registerCallback("getSettings", [voice]() -> Json { return voice->saveJson(); }); + callbacks.registerCallbackWithSignature("devices", bind(&Voice::availableDevices, voice)); + callbacks.registerCallback( "getSettings", [voice]() -> Json { return voice->saveJson(); }); callbacks.registerCallback("mergeSettings", [voice](Json const& settings) { voice->loadJson(settings); }); + // i have an alignment addiction i'm so sorry + callbacks.registerCallback("setSpeakerMuted", [voice](SpeakerId speakerId, bool muted) { voice->speaker(speakerId)->muted = muted; }); + callbacks.registerCallback( "speakerMuted", [voice](SpeakerId speakerId) { return (bool)voice->speaker(speakerId)->muted; }); + // it just looks so neat to me!! + callbacks.registerCallback("setSpeakerVolume", [voice](SpeakerId speakerId, float volume) { voice->speaker(speakerId)->volume = volume; }); + callbacks.registerCallback( "speakerVolume", [voice](SpeakerId speakerId) { return (float)voice->speaker(speakerId)->volume; }); + callbacks.registerCallback("speakerPosition", [voice](SpeakerId speakerId) { return voice->speaker(speakerId)->position; }); + + callbacks.registerCallback("speaker", [voice](SpeakerId speakerId) { return voice->speaker(speakerId)->toJson(); }); callbacks.registerCallback("speakers", [voice](Maybe onlyPlaying) -> List { List list; - for (auto& speaker : voice->speakers(onlyPlaying.value(true))) { - list.append(JsonObject{ - {"speakerId", speaker->speakerId }, - {"entityId", speaker->entityId }, - {"name", speaker->name }, - {"playing", (bool)speaker->playing }, - {"muted", (bool)speaker->muted }, - {"decibels", (float)speaker->decibelLevel }, - {"smoothDecibels", (float)speaker->smoothDb }, - }); - } + for (auto& speaker : voice->sortedSpeakers(onlyPlaying.value(true))) + list.append(speaker->toJson()); return list; }); From 1f038540a59ff96aed3cda901449a298b6f1c11c Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Wed, 19 Jul 2023 23:16:59 +1000 Subject: [PATCH 29/34] Port in the voice settings menu --- .../interface/opensb/voicechat/activity.png | Bin 0 -> 201 bytes .../opensb/voicechat/activityback.png | Bin 0 -> 326 bytes .../interface/opensb/voicechat/bigbutton.png | Bin 0 -> 223 bytes .../opensb/voicechat/bigbuttonback.png | Bin 0 -> 496 bytes .../interface/opensb/voicechat/body.png | Bin 0 -> 1049 bytes .../interface/opensb/voicechat/device.png | Bin 0 -> 195 bytes .../interface/opensb/voicechat/deviceback.png | Bin 0 -> 447 bytes .../interface/opensb/voicechat/footer.png | Bin 0 -> 250 bytes .../interface/opensb/voicechat/header.png | Bin 0 -> 569 bytes .../{ => opensb}/voicechat/indicator/back.png | Bin .../voicechat/indicator/front.png | Bin .../voicechat/indicator/front_muted.png | Bin .../interface/opensb/voicechat/pushtotalk.png | Bin 0 -> 207 bytes .../opensb/voicechat/pushtotalkback.png | Bin 0 -> 378 bytes .../opensb/voicechat/voicechat.config | 174 ++++++++++++++ .../interface/opensb/voicechat/voicechat.lua | 227 ++++++++++++++++++ .../interface/optionsmenu/body_blank.png | Bin 0 -> 1017 bytes .../optionsmenu/duocontrolsbutton.png | Bin 0 -> 159 bytes .../optionsmenu/duocontrolsbuttonhover.png | Bin 0 -> 154 bytes .../optionsmenu/optionsmenu.config.patch | 40 ++- ...ntrolsbutton.png => tricontrolsbutton.png} | Bin ...onhover.png => tricontrolsbuttonhover.png} | Bin .../opensb/universeclient/voicemanager.lua | 2 +- source/client/StarClientApplication.cpp | 9 +- source/frontend/CMakeLists.txt | 2 + source/frontend/StarMainMixer.cpp | 3 + source/frontend/StarOptionsMenu.cpp | 14 +- source/frontend/StarOptionsMenu.hpp | 3 + source/frontend/StarVoice.cpp | 50 ++-- source/frontend/StarVoice.hpp | 2 +- source/frontend/StarVoiceLuaBindings.cpp | 12 +- source/frontend/StarVoiceLuaBindings.hpp | 2 +- source/frontend/StarVoiceSettingsMenu.cpp | 23 ++ source/frontend/StarVoiceSettingsMenu.hpp | 24 ++ source/windowing/StarPane.cpp | 4 + 35 files changed, 552 insertions(+), 39 deletions(-) create mode 100644 assets/opensb/interface/opensb/voicechat/activity.png create mode 100644 assets/opensb/interface/opensb/voicechat/activityback.png create mode 100644 assets/opensb/interface/opensb/voicechat/bigbutton.png create mode 100644 assets/opensb/interface/opensb/voicechat/bigbuttonback.png create mode 100644 assets/opensb/interface/opensb/voicechat/body.png create mode 100644 assets/opensb/interface/opensb/voicechat/device.png create mode 100644 assets/opensb/interface/opensb/voicechat/deviceback.png create mode 100644 assets/opensb/interface/opensb/voicechat/footer.png create mode 100644 assets/opensb/interface/opensb/voicechat/header.png rename assets/opensb/interface/{ => opensb}/voicechat/indicator/back.png (100%) rename assets/opensb/interface/{ => opensb}/voicechat/indicator/front.png (100%) rename assets/opensb/interface/{ => opensb}/voicechat/indicator/front_muted.png (100%) create mode 100644 assets/opensb/interface/opensb/voicechat/pushtotalk.png create mode 100644 assets/opensb/interface/opensb/voicechat/pushtotalkback.png create mode 100644 assets/opensb/interface/opensb/voicechat/voicechat.config create mode 100644 assets/opensb/interface/opensb/voicechat/voicechat.lua create mode 100644 assets/opensb/interface/optionsmenu/body_blank.png create mode 100644 assets/opensb/interface/optionsmenu/duocontrolsbutton.png create mode 100644 assets/opensb/interface/optionsmenu/duocontrolsbuttonhover.png rename assets/opensb/interface/optionsmenu/{controlsbutton.png => tricontrolsbutton.png} (100%) rename assets/opensb/interface/optionsmenu/{controlsbuttonhover.png => tricontrolsbuttonhover.png} (100%) create mode 100644 source/frontend/StarVoiceSettingsMenu.cpp create mode 100644 source/frontend/StarVoiceSettingsMenu.hpp diff --git a/assets/opensb/interface/opensb/voicechat/activity.png b/assets/opensb/interface/opensb/voicechat/activity.png new file mode 100644 index 0000000000000000000000000000000000000000..20286e7d4f44bdd52c1a1f25c8ea46a57c985fd6 GIT binary patch literal 201 zcmeAS@N?(olHy`uVBq!ia0vp^0YEIp!3HE1F5O-Zq!^2X+?^QKos)S9a~60+7BevL9RguSQ4OyKpkSP*i(^QJ^V^#{1rHc-FgQN^SN+XA>%hJe z^QJFtJ(l_1_wO|rZw}vAk@xbP0l+XkK D5vDZ& literal 0 HcmV?d00001 diff --git a/assets/opensb/interface/opensb/voicechat/activityback.png b/assets/opensb/interface/opensb/voicechat/activityback.png new file mode 100644 index 0000000000000000000000000000000000000000..8308c3decccb666508fdf277e0ea116458e5d357 GIT binary patch literal 326 zcmeAS@N?(olHy`uVBq!ia0vp^0YEIp!3HE1F5O-Zq!^2X+?^QKos)S9a~60+7BevL9RguSQ4OyKpx|v!7srqc=eIK*`C1eNSR8AY%YQy*Eo$a_ zDJ>;(vTywENhckQ-GXJ#9xvRI8Yz?&dp+*lFxs(FpoYSxu*DB|I8m}qWfx> zT${D-^)Bw;hO?ip-C8p5d9B#>IWyNFVdQ&MBb@0Muc5Jpcdz literal 0 HcmV?d00001 diff --git a/assets/opensb/interface/opensb/voicechat/bigbutton.png b/assets/opensb/interface/opensb/voicechat/bigbutton.png new file mode 100644 index 0000000000000000000000000000000000000000..2908feb97dee1d4b967cc7b67d0113ed3ccc50e5 GIT binary patch literal 223 zcmeAS@N?(olHy`uVBq!ia0vp^-+)+(gAGWwOj&3Mq!^2X+?^QKos)S9a~60+7BevL9RguSQ4OyKpkRroi(^Pd+}pb+c@G$HFgQ;BSA8yVXXl!< zxPSv)>c#uTCTu!9Z{}vE`tNsNy*}g6u|d(}hJZ>AhmaWyr*u;buj8RMg+%8G#0tx? dIh>2)pLUF2?ckJ6w}JLEc)I$ztaD0e0s#COMhO4_ literal 0 HcmV?d00001 diff --git a/assets/opensb/interface/opensb/voicechat/bigbuttonback.png b/assets/opensb/interface/opensb/voicechat/bigbuttonback.png new file mode 100644 index 0000000000000000000000000000000000000000..fe107707200e5c34a4d5b4e8a73a5f1f7ea9fd86 GIT binary patch literal 496 zcmeAS@N?(olHy`uVBq!ia0vp^-+)+(gAGWwOj&3Mq!^2X+?^QKos)S9a~60+7BevL9ROj*tZSW|Kn<;)E{-7;ac}P)%sb@3)AF!|{cQKsW6hIJ zc`Wwpot|@P_SgDJQ{Svfe0i|I`+DvD{?gd#fvmCe{l|~*o4)#6@l(6_-+Rw}^(i%- z{ZqO`es7fb*5^yl*cS(=-CJyT-sSf^?bX-joww?HytruH*VkWvO=dm6^<3nM*LQ@& z*Be;rKe4?VURri;YEH?z`{AzxcV@+lK40a2J#a7k@1HgMr1y6|dy}`U^s4l_J?+2! z#nR4kq}N`3y)X6ZyxEoi4Q{Ld20~u zb@fgRTMi~L^BUf8aON?)AkfTfa)b)pKZhGaUUhfHtzCNSbI`N-mKwtE1LvnNsy#n7 zH2Zw|(a*<=pT@kt$oywD`=@HLPgdWLT-EM5$6e|FJ#_BeVpZw6ufJcd3R!n6{OHbC f7q_lU{>gawXa0ddZtwYkvBco%>gTe~DWM4fTwvq~ literal 0 HcmV?d00001 diff --git a/assets/opensb/interface/opensb/voicechat/body.png b/assets/opensb/interface/opensb/voicechat/body.png new file mode 100644 index 0000000000000000000000000000000000000000..37f73a8f555dbe37ec57f453f7ce9209925cf058 GIT binary patch literal 1049 zcmeAS@N?(olHy`uVBq!ia0vp^zkv7z2OE$)824v3kYX$ja(7}_cTVOdki(Mh=X?n_UsGSt#|L= z*K^#pf2=^9g?9aX%~GE}zc{dS^M&x$SGPYmUKTfda{SvpbBfFU9hr0HZtdwgn?Fyp zoVok=#o~<-f2IOi*6HVNR-OmS{XJos{`~2jKRN09@7I6o<7+NXli@ixv(UgI{g~qf zqWoMVC(HKqj75h9_umqEiJtNji8;oPcVt|?KfP|*{^K(XW41j1eD06W{qr*m1ym>u zR(6Ymi8cowe&_wk!2aM)s{ zKD=O#B)eex!uju*n|~^Pd0SS!i#KN1%IlWlj~M@jJd(@#G={PHRE_~VaNn{UeOW6nugv#(t0 ST@5fhGkCiCxvXa~60+7BevL9RguSQ4OyKpkRcji(^QJ^V{1yc^M2j7#xfLey^Dy(-Ja~60+7BevL9RguSQ4OyKpoS1n7srqc=eM&R^I8IUoZ0zK9lcQ)kYPJj z+h0vyX<4`1r^z~k$AA9uJ<^pKdp&v9vf~~N=Re!*{}SSLt)Iz;f5rLdYy4B=M2yqF zw{4P1?45T_#CEUtiFa~)EsReEHrRe$C3F1a>e~DFZLYmo$$q83^h-xt#OebtVlUiW zvualJCZ4?&)7PATuB{mBrAA4UYZE-m~JE$${=H zA7+vme`ep_j@=Ir^rf|ar*@bA`OCFJ{`CvNvm3%1FYaEjIWp9gJyo*mqh93v^Wj08 h>`bce@9sM;KKp+18ZWL*n}LDO;OXk;vd$@?2>=*U$EW}R literal 0 HcmV?d00001 diff --git a/assets/opensb/interface/opensb/voicechat/footer.png b/assets/opensb/interface/opensb/voicechat/footer.png new file mode 100644 index 0000000000000000000000000000000000000000..39069c9325f930e6a50d1cb13781169b8b00096c GIT binary patch literal 250 zcmeAS@N?(olHy`uVBq!ia0vp^zkryZgAGWY-F#^}kYX$ja(7}_cTVOdki(Mh=_9MIcC=XQ@X0-#$z#? tm~a+3w=G`P)X1^@s67(6o(00001b5ch_0Itp) z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!T0=7Noon4>IFIr5>yI>&Y#w~76K5(4bRIpAK6=;6a~KA(U5LjXH- zyWPJ1!Z{+v#y>0Sr^6?7MJ0uJ=U1zy5F1)Y!seUh+$`sV;Tc3?LL z`sQ|Zc649>1RWRvU)`0*%ib%@e>!r;fWthpxyeFt3A>zg0$k`C_N zYn9_hdT(kFbi8aOD5YW72)m7-8+5a5p>bS8>zf~5g?oK+^FwBU{+g~~?laKui=4BC z&unW5x)L^5s`>SNO>?zq%%!ziuX7Dc>M56$wwzbsO)9Tofu%iQ= zV@C%DLC}E#5OiPw1RWRvK?epf{px7G#>MFZ$7i<{qL&-Rrj(nR~XLi#+z?YOLw{_cK3B?^=BO zXyvm~x3$SV7jKouZa1_)|5^DtkL|CHos%uoXI$PD`?dGjui6Er=PupY&1&2CxNboL z!?76|Z&&)=|LQ1y{Z{bYxYrBLh5IY~GxwiRZoAg@>dEap&i#MIyf$-g&TX@=`wMgTe~DWM4f_bj8* literal 0 HcmV?d00001 diff --git a/assets/opensb/interface/opensb/voicechat/voicechat.config b/assets/opensb/interface/opensb/voicechat/voicechat.config new file mode 100644 index 0000000..20e88d9 --- /dev/null +++ b/assets/opensb/interface/opensb/voicechat/voicechat.config @@ -0,0 +1,174 @@ +{ + "scripts" : ["/interface/opensb/voicechat/voicechat.lua"], + "scriptDelta" : 1, + "scriptWidgetCallbacks" : [ + "selectDevice", + "voiceToggle", + "switchVoiceMode" + ], + + "canvasClickCallbacks" : { + "inputVolume" : "inputVolume", + "voiceVolume" : "voiceVolume", + "threshold" : "threshold" + }, + + "gui" : { + "panefeature" : { + "type" : "panefeature", + "positionLocked" : false + }, + "background" : { + "type" : "background", + "fileHeader" : "/interface/opensb/voicechat/header.png", + "fileBody" : "/interface/opensb/voicechat/body.png", + "fileFooter" : "/interface/opensb/voicechat/footer.png" + }, + + "voiceVolumeLabel" : { + "type" : "label", + "value" : "THEIR VOLUME", + "position" : [26, 178], + "wrapWidth" : 48, + "lineSpacing" : 0.75, + "hAnchor" : "mid", + "vAnchor" : "mid" + }, + "voiceVolume" : { + "type" : "canvas", + "rect" : [50, 171, 247, 186], + "captureMouseEvents" : true, + "captureKeyboardEvents" : false + }, + + "inputVolumeLabel" : { + "type" : "label", + "value" : "YOUR VOLUME", + "position" : [26, 158], + "wrapWidth" : 48, + "lineSpacing" : 0.75, + "hAnchor" : "mid", + "vAnchor" : "mid" + }, + "inputVolume" : { + "type" : "canvas", + "rect" : [50, 151, 247, 166], + "captureMouseEvents" : true, + "captureKeyboardEvents" : false + }, + + "enableVoiceToggleBack" : { + "type" : "image", + "file" : "/interface/opensb/voicechat/bigbuttonback.png?multiply=0f0", + "position" : [2, 189], + "zlevel" : -1 + }, + "enableVoiceToggle" : { + "type" : "button", + "pressedOffset" : [0, 0], + "position" : [2, 189], + "base" : "/interface/opensb/voicechat/bigbutton.png?replace;fff=fff0;000=0007", + "hover" : "/interface/opensb/voicechat/bigbutton.png?replace;fff=fff7;000=3337", + "press" : "/interface/opensb/voicechat/bigbutton.png?replace;fff=000;000=7777", + "callback" : "voiceToggle", + "fontSize" : 16, + "zlevel" : 1 + }, + + + "voiceModeLabel" : { + "type" : "label", + "value" : "VOICE MODE", + "position" : [26, 133], + "wrapWidth" : 32, + "lineSpacing" : 0.75, + "hAnchor" : "mid", + "vAnchor" : "mid" + }, + "pushToTalkBack" : { + "type" : "image", + "file" : "/interface/opensb/voicechat/pushtotalkback.png?multiply=0f0", + "position" : [50, 121], + "zlevel" : -1 + }, + "pushToTalk" : { + "type" : "button", + "pressedOffset" : [0, 0], + "position" : [50, 121], + "base" : "/interface/opensb/voicechat/pushtotalk.png?replace;fff=fff0;000=0007", + "hover" : "/interface/opensb/voicechat/pushtotalk.png?replace;fff=fff7;000=3337", + "press" : "/interface/opensb/voicechat/pushtotalk.png?replace;fff=000;000=7777", + "callback" : "switchVoiceMode", + "fontSize" : 16, + "zlevel" : 1 + }, + + "voiceActivityBack" : { + "type" : "image", + "file" : "/interface/opensb/voicechat/activityback.png?multiply=0f0", + "position" : [167, 121], + "zlevel" : -1 + }, + "voiceActivity" : { + "type" : "button", + "pressedOffset" : [0, 0], + "position" : [167, 121], + "base" : "/interface/opensb/voicechat/activity.png?replace;fff=fff0;000=0007", + "hover" : "/interface/opensb/voicechat/activity.png?replace;fff=fff7;000=3337", + "press" : "/interface/opensb/voicechat/activity.png?replace;fff=000;000=7777", + "callback" : "switchVoiceMode", + "fontSize" : 16, + "zlevel" : 1 + }, + + "thresholdLevel" : { + "type" : "label", + "value" : "THRESHOLD", + "position" : [26, 109], + "wrapWidth" : 48, + "lineSpacing" : 0.75, + "hAnchor" : "mid", + "vAnchor" : "mid" + }, + "threshold" : { + "type" : "canvas", + "rect" : [50, 102, 247, 117], + "captureMouseEvents" : true, + "captureKeyboardEvents" : false + }, + + "devices" : { + "type" : "scrollArea", + "rect" : [3, 16, 248, 98], + "children" : { + "list" : { + "type" : "list", + "schema" : { + "selectedBG" : "/interface/opensb/voicechat/deviceback.png?multiply=0f0", + "unselectedBG" : "/interface/opensb/voicechat/deviceback.png?multiply=222", + "spacing" : [0, 1], + "memberSize" : [234, 16], + "listTemplate" : { + "background" : { + "type" : "image", + "file" : "/interface/opensb/voicechat/deviceback.png?multiply=222", + "position" : [0, 0], + "zlevel" : -1 + }, + "button" : { + "type" : "button", + "callback" : "selectDevice", + "caption" : "Unnamed", + "base" : "/interface/opensb/voicechat/device.png?replace;fff=fff0;000=0007", + "hover" : "/interface/opensb/voicechat/device.png?replace;fff=fff7;000=3337", + "press" : "/interface/opensb/voicechat/device.png?replace;fff=000;000=7777", + "pressedOffset" : [0, 0], + "position" : [0, 0] + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/assets/opensb/interface/opensb/voicechat/voicechat.lua b/assets/opensb/interface/opensb/voicechat/voicechat.lua new file mode 100644 index 0000000..4e9f7e7 --- /dev/null +++ b/assets/opensb/interface/opensb/voicechat/voicechat.lua @@ -0,0 +1,227 @@ +--constants +local PATH = "/interface/opensb/voicechat/" +local DEVICE_LIST_WIDGET = "devices.list" +local DEFAULT_DEVICE_NAME = "Use System Default" +local NULL_DEVICE_NAME = "No Audio Device" +local COLD_COLOR = {25, 255, 255, 225} +local HOT_COLOR = {255, 96, 96, 225} +local MINIMUM_DB = -80 +local VOICE_MAX, INPUT_MAX = 1.75, 1.0 +local MID = 7.5 + +local fmt = string.format + +local debugging = false +local function log(...) + if not debugging then return end + sb.logInfo(...) +end + +local function mapToRange(x, min, max) + return math.min(1, math.max(0, (x - min)) / max) +end + +local function linear(a, b, c) + return a + (b - a) * c +end + +local settings = {} + +local function set(k, v) + settings[k] = v + local newSettings = jobject() + newSettings[k] = v + voice.mergeSettings(newSettings) + return v +end + +local devicesToWidgets = {} +local widgetsToDevices = {} +local nullWidget +local function addDeviceToList(deviceName) + local name = widget.addListItem(DEVICE_LIST_WIDGET) + widget.setText(fmt("%s.%s.button", DEVICE_LIST_WIDGET, name), deviceName) + widgetsToDevices[name] = deviceName + devicesToWidgets[deviceName] = name + log("Added audio device '%s' to list", deviceName) + return name +end + +function selectDevice() + local selected = widget.getListSelected(DEVICE_LIST_WIDGET) + if selected == nullWidget then + set("inputEnabled", false) + widget.setListSelected(DEVICE_LIST_WIDGET, nullWidget) + end + local deviceName = widgetsToDevices[selected] + if deviceName == DEFAULT_DEVICE_NAME then deviceName = nil end + + if settings.deviceName == deviceName then + local inputEnabled = set("inputEnabled", not settings.inputEnabled) + widget.setListSelected(DEVICE_LIST_WIDGET, inputEnabled and selected or nullWidget) + else + set("deviceName", deviceName) + set("inputEnabled", true) + end +end + +local function initCallbacks() + widget.registerMemberCallback(DEVICE_LIST_WIDGET, "selectDevice", selectDevice) +end + +local function updateVoiceButton() + local enabled = settings.enabled + widget.setText("enableVoiceToggle", enabled and "^#0f0;disable voice chat" or "^#f00;enable voice chat") + widget.setImage("enableVoiceToggleBack", PATH .. "bigbuttonback.png?multiply=" .. (enabled and "0f0" or "f00")) +end + +local function updateVoiceModeButtons() + local pushToTalk = settings.inputMode:lower() == "pushtotalk" + widget.setImage("pushToTalkBack", PATH .. "pushtotalkback.png?multiply=" .. (pushToTalk and "0f0" or "f00")) + widget.setImage("voiceActivityBack", PATH .. "activityback.png?multiply=" .. (pushToTalk and "f00" or "0f0")) + widget.setText("pushToTalk", pushToTalk and "^#0f0;PUSH TO TALK" or "^#f00;PUSH TO TALK") + widget.setText("voiceActivity", pushToTalk and "^#f00;ACTIVITY" or "^#0f0;ACTIVITY") +end + +local voiceCanvas, inputCanvas = nil, nil +local function updateVolumeCanvas(canvas, volume, multiplier) + canvas:clear() + local lineEnd = 1 + volume * 195 + local lineColor = {95, 110, 255, 225} + local multiplied = volume * multiplier + if multiplied > 1 then + local level = (multiplied - 1) / (multiplier - 1) + for i = 1, 4 do + lineColor[i] = linear(lineColor[i], HOT_COLOR[i], level) + end + else + for i = 1, 4 do + lineColor[i] = linear(lineColor[i], COLD_COLOR[i], 1 - multiplied) + end + end + + canvas:drawLine({1, MID}, {lineEnd, MID}, lineColor, 60) + canvas:drawLine({lineEnd - 0.5, MID}, {lineEnd + 0.5, MID}, {255, 255, 255, 200}, 60) + local str = volume == 0 and "^#f00,shadow;MUTED" or fmt("^shadow;%s%%", math.floor(volume * multiplier * 1000) * 0.1) + canvas:drawText(str, {position = {92.5, MID}, horizontalAnchor = "mid", verticalAnchor = "mid"}, 16, {255, 255, 255, 255}) +end + +local thresholdCanvas = nil +local function updateThresholdCanvas(canvas, dB) + canvas:clear() + local scale = pane.scale() + local lineEnd = 1 + (1 - (dB / MINIMUM_DB)) * 195 + local lineColor = {255, 255, 0, 127} + canvas:drawLine({1, 2}, {lineEnd, 2}, lineColor, scale) + canvas:drawLine({1, 13}, {lineEnd, 13}, lineColor, scale) + lineColor[4] = 64 + canvas:drawLine({lineEnd, 2}, {196, 2}, lineColor, scale) + canvas:drawLine({lineEnd, 13}, {196, 13}, lineColor, scale) + canvas:drawLine({lineEnd - 0.5, MID}, {lineEnd + 0.5, MID}, {255, 255, 255, 200}, 60) + + local loudness = 1 - (voice.speaker().smoothDecibels / MINIMUM_DB) + local loudnessEnd = math.min(1 + loudness * 195, 196) + if loudnessEnd > 0 then + lineColor[4] = 200 + canvas:drawLine({1, MID}, {loudnessEnd, MID}, lineColor, 4 * scale) + end + + local str = fmt("^shadow;%sdB", math.floor(dB * 10) * 0.1) + canvas:drawText(str, {position = {92.5, MID}, horizontalAnchor = "mid", verticalAnchor = "mid"}, 16, {255, 255, 255, 255}) +end + +function init() + settings = voice.getSettings() + voiceCanvas = widget.bindCanvas("voiceVolume") + inputCanvas = widget.bindCanvas("inputVolume") + thresholdCanvas = widget.bindCanvas("threshold") +end + +function displayed() + devicesToWidgets = {} + widgetsToDevices = {} + widget.clearListItems(DEVICE_LIST_WIDGET) + initCallbacks() + + addDeviceToList(DEFAULT_DEVICE_NAME) + + for i, v in pairs(voice.devices()) do + addDeviceToList(v) + end + + nullWidget = widget.addListItem(DEVICE_LIST_WIDGET) + local nullWidgetPath = fmt("%s.%s", DEVICE_LIST_WIDGET, nullWidget) + widget.setPosition(nullWidgetPath, {0, 10000}) + widget.setVisible(nullWidgetPath, false) + + local preferredDeviceWidget = devicesToWidgets[settings.deviceName or DEFAULT_DEVICE_NAME] + if preferredDeviceWidget and settings.inputEnabled then + widget.setListSelected(DEVICE_LIST_WIDGET, preferredDeviceWidget) + end + + updateVoiceButton() + updateVoiceModeButtons() + updateVolumeCanvas(voiceCanvas, settings.outputVolume / VOICE_MAX, VOICE_MAX) + updateVolumeCanvas(inputCanvas, settings.inputVolume / INPUT_MAX, INPUT_MAX) + updateThresholdCanvas(thresholdCanvas, settings.threshold) +end + +function update() + updateThresholdCanvas(thresholdCanvas, settings.threshold) +end + +local function sliderToValue(x) + return mapToRange(x, 5, 187) +end + +local function mouseInSlider(mouse) + return mouse[1] > 0 and mouse[1] < 197 + and mouse[2] > 0 and mouse[2] < 16 +end + +local function handleVolume(canvas, mouse, multiplier, setter) + if not mouseInSlider(mouse) then return end + + local volumePreMul = sliderToValue(mouse[1]) + local volume = volumePreMul * multiplier; + if math.abs(volume - 1) < 0.01 then + volumePreMul = 1 / multiplier + volume = 1 + end + + updateVolumeCanvas(canvas, volumePreMul, multiplier) + setter(volume) +end + +function voiceVolume(mouse, button) + if button ~= 0 then return end + handleVolume(voiceCanvas, mouse, VOICE_MAX, function(v) set("outputVolume", v) end) +end + +function inputVolume(mouse, button) + if button ~= 0 then return end + handleVolume(inputCanvas, mouse, INPUT_MAX, function(v) set("inputVolume", v) end) +end + +function threshold(mouse, button) + if button ~= 0 then return end + if not mouseInSlider(mouse) then return end + local dB = (1 - sliderToValue(mouse[1])) * MINIMUM_DB + set("threshold", dB) + + updateThresholdCanvas(thresholdCanvas, dB) +end + +function switchVoiceMode(mode) + log("switching voice mode to %s", tostring(mode)) + local success, err = pcall(function() + set("inputMode", mode) + updateVoiceModeButtons() + end) + if not success then log("%s", err) end +end + +function voiceToggle() + set("enabled", not settings.enabled) + updateVoiceButton() +end \ No newline at end of file diff --git a/assets/opensb/interface/optionsmenu/body_blank.png b/assets/opensb/interface/optionsmenu/body_blank.png new file mode 100644 index 0000000000000000000000000000000000000000..7d5b0f1b36716d0682417c0ee9c487dc8286a2ed GIT binary patch literal 1017 zcmeAS@N?(olHy`uVBq!ia0vp^Z-96e2OE%FcWH4dkYX$ja(7}_cTVOdki(Mh=PLc+$DlcYiT)y$g%I}l-c;DpgcPI}Jyw7GEp*Owx z^y$;TXV0F!`IAt9&+^Odq0;uJF6ZtKzrFs`5C68Of*cAF97-7I((41AH*>y4#4TF% zv!-reolO7na((@Pt0_j7rUgt=Di|6tjX)E*blU&O8sFu?*{=^+uUX67E2P6Bhy%SW z%~*8x^#k42*BuwLa61CgEW7r*YeM|i^@q)@udDkPS@!>QfyJM=GV7P0WwzZ+I9g@% zPwcJLjiX70q4Ua&qPMwjlPN+P^PG{>3{^}=3s$$Nxv+_+!Q0zcL9+MBw;;#nEyglHd zo@H=prSj**hqZ22FlSfUw#U}`+iUwbCS5(&TpW=x0ZB8$po9NyZ=Yevs^yx$|FCxR z|Am1~tqP=~Yu6fWH*e!J@!BBZf`pFb&ShA9@x`t;`!+`CtUIL_|77#=!?|%%+KH{VRI^m6_D^H0rR z3%UOHad8V)olGfurDbSxqBi2jX1n)?xy%l@FV5JP6(zpjuG)rYPR50AdrqGXySJz* z>bNtK^ISG4ZhIb^w+-Twyw?Zj$-b$La2DooUf9`w^nweD-G~5#Imjdbpq;JlRNI93 znnNfNj2zz+?$w!oeDvtizi;2Jou6}v50R>X39E|x+mk0t-Z1`tBrMj%fgL6Bu+=j> YJ#*MM_LAl%V0LBjboFyt=akR{0B3HORR910 literal 0 HcmV?d00001 diff --git a/assets/opensb/interface/optionsmenu/duocontrolsbutton.png b/assets/opensb/interface/optionsmenu/duocontrolsbutton.png new file mode 100644 index 0000000000000000000000000000000000000000..1d40af3d116d84529b86e0aa81b275a3c62c8c34 GIT binary patch literal 159 zcmeAS@N?(olHy`uVBq!ia0vp^?m*1P!3HF2pEK?TQfZzpjv*CsZ?7NZY%t()dpLbk zF3Z84OD>CC7c60PERcPD&^xed+k^kU0{2uq3s2j0N=^u#rNMc`;t|8=O`p{K9^Nqj zx9&B2p1_Jp4wpsT3&E)A;Z#}u^kc7&&o}v7acg_?P1dASf=SoCCZ+&wWbkzLb6Mw< G&;$U_mOVQF literal 0 HcmV?d00001 diff --git a/assets/opensb/interface/optionsmenu/duocontrolsbuttonhover.png b/assets/opensb/interface/optionsmenu/duocontrolsbuttonhover.png new file mode 100644 index 0000000000000000000000000000000000000000..bdeb055393e8395a94148ad05b2d822a45eb3765 GIT binary patch literal 154 zcmeAS@N?(olHy`uVBq!ia0vp^?m*1P!3HF2pEK?TQi+}}jv*CsZ?7A2H5l-?J)Ax% znp4bXN5H|@Duveak?)xl(mHPjqw3PR(;Rw)`6hfmIM<@rH22S4p>!>d zFHf`!Mcg5%>0z_1e)_@V$LH_)^>Ir&`)2N?Qv|2Vc!<6MTE^h%>gTe~DWM4fm`FDT literal 0 HcmV?d00001 diff --git a/assets/opensb/interface/optionsmenu/optionsmenu.config.patch b/assets/opensb/interface/optionsmenu/optionsmenu.config.patch index fe5def8..68b27f9 100644 --- a/assets/opensb/interface/optionsmenu/optionsmenu.config.patch +++ b/assets/opensb/interface/optionsmenu/optionsmenu.config.patch @@ -1,25 +1,47 @@ { "paneLayout" : { + "voiceLabel" : { + "type" : "label", + "position" : [119, 185], + "hAnchor" : "mid", + "value" : "VOICE" + }, + "showVoiceSettings" : { + "type" : "button", + "position" : [30, 169], + "caption" : "Settings", + "base" : "/interface/optionsmenu/duocontrolsbutton.png", + "hover" : "/interface/optionsmenu/duocontrolsbuttonhover.png" + }, + "showVoicePlayers" : { + "type" : "button", + "disabled" : true, + "position" : [133, 169], + "caption" : "^#a0a000,font=iosevka-semiboldoblique;TODO^#aa7;:^reset; Players", + "base" : "/interface/optionsmenu/duocontrolsbutton.png", + "hover" : "/interface/optionsmenu/duocontrolsbuttonhover.png" + }, "showKeybindings" : { "type" : "button", - "position" : [150, 95], + "position" : [153, 95], "caption" : "Game Binds", - "base" : "/interface/optionsmenu/controlsbutton.png", - "hover" : "/interface/optionsmenu/controlsbuttonhover.png" + "base" : "/interface/optionsmenu/tricontrolsbutton.png", + "hover" : "/interface/optionsmenu/tricontrolsbuttonhover.png" }, "showModBindings" : { "type" : "button", "position" : [87, 95], "caption" : "Mod Binds", - "base" : "/interface/optionsmenu/controlsbutton.png", - "hover" : "/interface/optionsmenu/controlsbuttonhover.png" + "base" : "/interface/optionsmenu/tricontrolsbutton.png", + "hover" : "/interface/optionsmenu/tricontrolsbuttonhover.png" }, "showGraphics" : { "type" : "button", - "position" : [24, 95], + "position" : [21, 95], "caption" : "Graphics", - "base" : "/interface/optionsmenu/controlsbutton.png", - "hover" : "/interface/optionsmenu/controlsbuttonhover.png" - } + "base" : "/interface/optionsmenu/tricontrolsbutton.png", + "hover" : "/interface/optionsmenu/tricontrolsbuttonhover.png" + }, + "sfxValueLabel" : { "position" : [192, 142] } // this is 2px too low in vanilla lol } } \ No newline at end of file diff --git a/assets/opensb/interface/optionsmenu/controlsbutton.png b/assets/opensb/interface/optionsmenu/tricontrolsbutton.png similarity index 100% rename from assets/opensb/interface/optionsmenu/controlsbutton.png rename to assets/opensb/interface/optionsmenu/tricontrolsbutton.png diff --git a/assets/opensb/interface/optionsmenu/controlsbuttonhover.png b/assets/opensb/interface/optionsmenu/tricontrolsbuttonhover.png similarity index 100% rename from assets/opensb/interface/optionsmenu/controlsbuttonhover.png rename to assets/opensb/interface/optionsmenu/tricontrolsbuttonhover.png diff --git a/assets/opensb/scripts/opensb/universeclient/voicemanager.lua b/assets/opensb/scripts/opensb/universeclient/voicemanager.lua index b7468ce..2f19f6a 100644 --- a/assets/opensb/scripts/opensb/universeclient/voicemanager.lua +++ b/assets/opensb/scripts/opensb/universeclient/voicemanager.lua @@ -7,7 +7,7 @@ local module = {} modules.voice_manager = module --constants -local INDICATOR_PATH = "/interface/voicechat/indicator/" +local INDICATOR_PATH = "/interface/opensb/voicechat/indicator/" local BACK_INDICATOR_IMAGE = INDICATOR_PATH .. "back.png" local FRONT_INDICATOR_IMAGE = INDICATOR_PATH .. "front.png" local FRONT_MUTED_INDICATOR_IMAGE = INDICATOR_PATH .. "front_muted.png" diff --git a/source/client/StarClientApplication.cpp b/source/client/StarClientApplication.cpp index d8d79fd..b264f15 100644 --- a/source/client/StarClientApplication.cpp +++ b/source/client/StarClientApplication.cpp @@ -376,6 +376,13 @@ void ClientApplication::update() { else if (m_state > MainAppState::Title) updateRunning(); + // swallow leftover encoded data incase we aren't in-game yet to allow mic read to continue. + // TODO: directly disable encoding at menu so we don't have to do this + { + DataStreamBuffer ext; + m_voice->send(ext); + } + m_guiContext->cleanup(); m_edgeKeyEvents.clear(); m_input->reset(); @@ -499,7 +506,7 @@ void ClientApplication::changeState(MainAppState newState) { m_statistics = make_shared(m_root->toStoragePath("player"), appController()->statisticsService()); m_universeClient = make_shared(m_playerStorage, m_statistics); m_universeClient->setLuaCallbacks("input", LuaBindings::makeInputCallbacks()); - m_universeClient->setLuaCallbacks("voice", LuaBindings::makeVoiceCallbacks(m_voice.get())); + m_universeClient->setLuaCallbacks("voice", LuaBindings::makeVoiceCallbacks()); m_mainMixer->setUniverseClient(m_universeClient); m_titleScreen = make_shared(m_playerStorage, m_mainMixer->mixer()); diff --git a/source/frontend/CMakeLists.txt b/source/frontend/CMakeLists.txt index 4c6b9c8..b002155 100644 --- a/source/frontend/CMakeLists.txt +++ b/source/frontend/CMakeLists.txt @@ -58,6 +58,7 @@ SET (star_frontend_HEADERS StarWireInterface.hpp StarVoice.hpp StarVoiceLuaBindings.hpp + StarVoiceSettingsMenu.hpp ) SET (star_frontend_SOURCES @@ -108,6 +109,7 @@ SET (star_frontend_SOURCES StarWireInterface.cpp StarVoice.cpp StarVoiceLuaBindings.cpp + StarVoiceSettingsMenu.cpp ) ADD_LIBRARY (star_frontend OBJECT ${star_frontend_SOURCES} ${star_frontend_HEADERS}) diff --git a/source/frontend/StarMainMixer.cpp b/source/frontend/StarMainMixer.cpp index 6d68ea2..d4a2e73 100644 --- a/source/frontend/StarMainMixer.cpp +++ b/source/frontend/StarMainMixer.cpp @@ -121,6 +121,9 @@ void MainMixer::update(bool muteSfx, bool muteMusic) { if (m_mixer->hasEffect("echo")) m_mixer->removeEffect("echo", 0); + if (Voice* voice = Voice::singletonPtr()) + voice->update(); + m_mixer->update(); } } diff --git a/source/frontend/StarOptionsMenu.cpp b/source/frontend/StarOptionsMenu.cpp index cef2e7d..caa1b21 100644 --- a/source/frontend/StarOptionsMenu.cpp +++ b/source/frontend/StarOptionsMenu.cpp @@ -7,6 +7,7 @@ #include "StarLabelWidget.hpp" #include "StarAssets.hpp" #include "StarKeybindingsMenu.hpp" +#include "StarVoiceSettingsMenu.hpp" #include "StarBindingsMenu.hpp" #include "StarGraphicsMenu.hpp" @@ -49,8 +50,14 @@ OptionsMenu::OptionsMenu(PaneManager* manager) reader.registerCallback("showKeybindings", [=](Widget*) { displayControls(); }); + reader.registerCallback("showVoiceSettings", [=](Widget*) { + displayVoiceSettings(); + }); + reader.registerCallback("showVoicePlayers", [=](Widget*) { + + }); reader.registerCallback("showModBindings", [=](Widget*) { - displayModBindings(); + displayModBindings(); }); reader.registerCallback("showGraphics", [=](Widget*) { displayGraphics(); @@ -74,6 +81,7 @@ OptionsMenu::OptionsMenu(PaneManager* manager) m_sfxSlider->setRange(m_sfxRange, assets->json("/interface/optionsmenu/optionsmenu.config:sfxDelta").toInt()); m_musicSlider->setRange(m_musicRange, assets->json("/interface/optionsmenu/optionsmenu.config:musicDelta").toInt()); + m_voiceSettingsMenu = make_shared(assets->json(config.getString("voiceSettingsPanePath", "/interface/opensb/voicechat/voicechat.config"))); m_modBindingsMenu = make_shared(assets->json(config.getString("bindingsPanePath", "/interface/opensb/bindings/bindings.config"))); m_keybindingsMenu = make_shared(); m_graphicsMenu = make_shared(); @@ -169,6 +177,10 @@ void OptionsMenu::displayControls() { m_paneManager->displayPane(PaneLayer::ModalWindow, m_keybindingsMenu); } +void OptionsMenu::displayVoiceSettings() { + m_paneManager->displayPane(PaneLayer::ModalWindow, m_voiceSettingsMenu); +} + void OptionsMenu::displayModBindings() { m_paneManager->displayPane(PaneLayer::ModalWindow, m_modBindingsMenu); } diff --git a/source/frontend/StarOptionsMenu.hpp b/source/frontend/StarOptionsMenu.hpp index bd8c4ba..b984f02 100644 --- a/source/frontend/StarOptionsMenu.hpp +++ b/source/frontend/StarOptionsMenu.hpp @@ -10,6 +10,7 @@ namespace Star { STAR_CLASS(SliderBarWidget); STAR_CLASS(ButtonWidget); STAR_CLASS(LabelWidget); +STAR_CLASS(VoiceSettingsMenu); STAR_CLASS(KeybindingsMenu); STAR_CLASS(GraphicsMenu); STAR_CLASS(BindingsMenu); @@ -38,6 +39,7 @@ private: void syncGuiToConf(); void displayControls(); + void displayVoiceSettings(); void displayModBindings(); void displayGraphics(); @@ -59,6 +61,7 @@ private: JsonObject m_origConfig; JsonObject m_localChanges; + VoiceSettingsMenuPtr m_voiceSettingsMenu; BindingsMenuPtr m_modBindingsMenu; KeybindingsMenuPtr m_keybindingsMenu; GraphicsMenuPtr m_graphicsMenu; diff --git a/source/frontend/StarVoice.cpp b/source/frontend/StarVoice.cpp index b7f8a6c..843203f 100644 --- a/source/frontend/StarVoice.cpp +++ b/source/frontend/StarVoice.cpp @@ -312,15 +312,19 @@ void Voice::readAudioData(uint8_t* stream, int len) { size_t sampleCount = len / 2; if (active) { - float volume = m_inputVolume; float decibels = getAudioLoudness((int16_t*)stream, sampleCount); + if (!m_loopback) + m_clientSpeaker->decibelLevel = getAudioLoudness((int16_t*)stream, sampleCount, m_inputVolume); + if (m_inputMode == VoiceInputMode::VoiceActivity) { if (decibels > m_threshold) m_lastThresholdTime = now; active = now - m_lastThresholdTime < 50; } } + else if (!m_loopback) + m_clientSpeaker->decibelLevel = -96.0f; if (!m_loopback) { if (active && !m_clientSpeaker->playing) @@ -405,6 +409,7 @@ void Voice::mix(int16_t* buffer, size_t frameCount, unsigned channels) { } else { speaker->playing = false; + speaker->decibelLevel = -96.0f; it = m_activeSpeakers.erase(it); } } @@ -423,23 +428,25 @@ void Voice::mix(int16_t* buffer, size_t frameCount, unsigned channels) { } void Voice::update(PositionalAttenuationFunction positionalAttenuationFunction) { - if (positionalAttenuationFunction) { - for (auto& entry : m_speakers) { - if (SpeakerPtr& speaker = entry.second) { - speaker->channelVolumes = { - 1.0f - positionalAttenuationFunction(0, speaker->position, 1.0f), + for (auto& entry : m_speakers) { + if (SpeakerPtr& speaker = entry.second) { + if (positionalAttenuationFunction) { + speaker->channelVolumes = { + 1.0f - positionalAttenuationFunction(0, speaker->position, 1.0f), 1.0f - positionalAttenuationFunction(1, speaker->position, 1.0f) - }; + }; + } + else + speaker->channelVolumes = Vec2F::filled(1.0f); - auto& dbHistory = speaker->dbHistory; - memcpy(&dbHistory[1], &dbHistory[0], (dbHistory.size() - 1) * sizeof(float)); - dbHistory[0] = speaker->decibelLevel; - float smoothDb = 0.0f; - for (float dB : dbHistory) - smoothDb += dB; + auto& dbHistory = speaker->dbHistory; + memcpy(&dbHistory[1], &dbHistory[0], (dbHistory.size() - 1) * sizeof(float)); + dbHistory[0] = speaker->decibelLevel; + float smoothDb = 0.0f; + for (float dB : dbHistory) + smoothDb += dB; - speaker->smoothDb = smoothDb / dbHistory.size(); - } + speaker->smoothDb = smoothDb / dbHistory.size(); } } @@ -467,6 +474,7 @@ StringList Voice::availableDevices() { for (size_t i = 0; i != devices; ++i) deviceList.emplace_back(SDL_GetAudioDeviceName(i, 1)); } + deviceList.sort(); return deviceList; } @@ -488,7 +496,7 @@ int Voice::send(DataStreamBuffer& out, size_t budget) { for (auto& chunk : encodedChunks) { out.write(chunk.size()); out.writeBytes(chunk); - if ((budget -= min(budget, chunk.size())) == 0) + if (budget && (budget -= min(budget, chunk.size())) == 0) break; } @@ -499,7 +507,7 @@ int Voice::send(DataStreamBuffer& out, size_t budget) { } bool Voice::receive(SpeakerPtr speaker, std::string_view view) { - if (!speaker || view.empty()) + if (!m_enabled || !speaker || view.empty()) return false; try { @@ -635,9 +643,8 @@ void Voice::closeDevice() { return; m_applicationController->closeAudioInputDevice(); - if (!m_loopback) - m_clientSpeaker->playing = false; - + m_clientSpeaker->playing = false; + m_clientSpeaker->decibelLevel = -96.0f; m_deviceOpen = false; } @@ -684,9 +691,6 @@ void Voice::thread() { samples[i] *= m_inputVolume; } - if (!m_loopback) - m_clientSpeaker->decibelLevel = getAudioLoudness(samples.data(), samples.size()); - if (int encodedSize = opus_encode(m_encoder.get(), samples.data(), VOICE_FRAME_SIZE, (unsigned char*)encoded.ptr(), encoded.size())) { if (encodedSize == 1) continue; diff --git a/source/frontend/StarVoice.hpp b/source/frontend/StarVoice.hpp index 4500aa6..95046af 100644 --- a/source/frontend/StarVoice.hpp +++ b/source/frontend/StarVoice.hpp @@ -141,7 +141,7 @@ public: void setDeviceName(Maybe device); StringList availableDevices(); - int send(DataStreamBuffer& out, size_t budget); + int send(DataStreamBuffer& out, size_t budget = 0); bool receive(SpeakerPtr speaker, std::string_view view); // Must be called every frame with input state, expires after 1s. diff --git a/source/frontend/StarVoiceLuaBindings.cpp b/source/frontend/StarVoiceLuaBindings.cpp index 1b62aef..934b294 100644 --- a/source/frontend/StarVoiceLuaBindings.cpp +++ b/source/frontend/StarVoiceLuaBindings.cpp @@ -6,9 +6,11 @@ namespace Star { typedef Voice::SpeakerId SpeakerId; -LuaCallbacks LuaBindings::makeVoiceCallbacks(Voice* voice) { +LuaCallbacks LuaBindings::makeVoiceCallbacks() { LuaCallbacks callbacks; + auto voice = Voice::singletonPtr(); + callbacks.registerCallbackWithSignature("devices", bind(&Voice::availableDevices, voice)); callbacks.registerCallback( "getSettings", [voice]() -> Json { return voice->saveJson(); }); callbacks.registerCallback("mergeSettings", [voice](Json const& settings) { voice->loadJson(settings); }); @@ -21,7 +23,13 @@ LuaCallbacks LuaBindings::makeVoiceCallbacks(Voice* voice) { callbacks.registerCallback("speakerPosition", [voice](SpeakerId speakerId) { return voice->speaker(speakerId)->position; }); - callbacks.registerCallback("speaker", [voice](SpeakerId speakerId) { return voice->speaker(speakerId)->toJson(); }); + callbacks.registerCallback("speaker", [voice](Maybe speakerId) { + if (speakerId) + return voice->speaker(*speakerId)->toJson(); + else + return voice->localSpeaker()->toJson(); + }); + callbacks.registerCallback("speakers", [voice](Maybe onlyPlaying) -> List { List list; diff --git a/source/frontend/StarVoiceLuaBindings.hpp b/source/frontend/StarVoiceLuaBindings.hpp index 8c83e54..5d670f3 100644 --- a/source/frontend/StarVoiceLuaBindings.hpp +++ b/source/frontend/StarVoiceLuaBindings.hpp @@ -8,7 +8,7 @@ namespace Star { STAR_CLASS(Voice); namespace LuaBindings { - LuaCallbacks makeVoiceCallbacks(Voice* voice); + LuaCallbacks makeVoiceCallbacks(); } } diff --git a/source/frontend/StarVoiceSettingsMenu.cpp b/source/frontend/StarVoiceSettingsMenu.cpp new file mode 100644 index 0000000..c815dee --- /dev/null +++ b/source/frontend/StarVoiceSettingsMenu.cpp @@ -0,0 +1,23 @@ +#include "StarVoiceSettingsMenu.hpp" +#include "StarVoiceLuaBindings.hpp" + +namespace Star { + +VoiceSettingsMenu::VoiceSettingsMenu(Json const& config) : BaseScriptPane(config) { + m_script.setLuaRoot(make_shared()); + m_script.addCallbacks("voice", LuaBindings::makeVoiceCallbacks()); +} + +void VoiceSettingsMenu::show() { + BaseScriptPane::show(); +} + +void VoiceSettingsMenu::displayed() { + BaseScriptPane::displayed(); +} + +void VoiceSettingsMenu::dismissed() { + BaseScriptPane::dismissed(); +} + +} \ No newline at end of file diff --git a/source/frontend/StarVoiceSettingsMenu.hpp b/source/frontend/StarVoiceSettingsMenu.hpp new file mode 100644 index 0000000..1f9e58d --- /dev/null +++ b/source/frontend/StarVoiceSettingsMenu.hpp @@ -0,0 +1,24 @@ +#ifndef STAR_VOICE_SETTINGS_MENU_HPP +#define STAR_VOICE_SETTINGS_MENU_HPP + +#include "StarBaseScriptPane.hpp" + +namespace Star { + +STAR_CLASS(VoiceSettingsMenu); + +class VoiceSettingsMenu : public BaseScriptPane { +public: + VoiceSettingsMenu(Json const& config); + + virtual void show() override; + void displayed() override; + void dismissed() override; + +private: + +}; + +} + +#endif diff --git a/source/windowing/StarPane.cpp b/source/windowing/StarPane.cpp index a2d32ad..04739cb 100644 --- a/source/windowing/StarPane.cpp +++ b/source/windowing/StarPane.cpp @@ -404,6 +404,10 @@ LuaCallbacks Pane::makePaneCallbacks() { return this->removeChild(widgetName); }); + callbacks.registerCallback("scale", []() -> int { + return GuiContext::singleton().interfaceScale(); + }); + return callbacks; } From f2bc9adc365c5488e0fdd0996d5bdc3027f34806 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Wed, 19 Jul 2023 23:25:16 +1000 Subject: [PATCH 30/34] Minor fixes --- source/client/StarClientApplication.cpp | 10 +++++----- source/frontend/StarVoice.cpp | 6 +----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/source/client/StarClientApplication.cpp b/source/client/StarClientApplication.cpp index b264f15..33327fa 100644 --- a/source/client/StarClientApplication.cpp +++ b/source/client/StarClientApplication.cpp @@ -214,6 +214,7 @@ void ClientApplication::applicationInit(ApplicationControllerPtr appController) m_voice->loadJson(jVoice.toObject(), true); m_voice->init(); + m_voice->setLocalSpeaker(0); } void ClientApplication::renderInit(RendererPtr renderer) { @@ -375,13 +376,12 @@ void ClientApplication::update() { updateTitle(); else if (m_state > MainAppState::Title) updateRunning(); - - // swallow leftover encoded data incase we aren't in-game yet to allow mic read to continue. - // TODO: directly disable encoding at menu so we don't have to do this - { + + // Swallow leftover encoded voice data if we aren't in-game to allow mic read to continue for settings. + if (m_state <= MainAppState::Title) { DataStreamBuffer ext; m_voice->send(ext); - } + } // TODO: directly disable encoding at menu so we don't have to do this m_guiContext->cleanup(); m_edgeKeyEvents.clear(); diff --git a/source/frontend/StarVoice.cpp b/source/frontend/StarVoice.cpp index 843203f..4a0e344 100644 --- a/source/frontend/StarVoice.cpp +++ b/source/frontend/StarVoice.cpp @@ -314,22 +314,18 @@ void Voice::readAudioData(uint8_t* stream, int len) { if (active) { float decibels = getAudioLoudness((int16_t*)stream, sampleCount); - if (!m_loopback) - m_clientSpeaker->decibelLevel = getAudioLoudness((int16_t*)stream, sampleCount, m_inputVolume); - if (m_inputMode == VoiceInputMode::VoiceActivity) { if (decibels > m_threshold) m_lastThresholdTime = now; active = now - m_lastThresholdTime < 50; } } - else if (!m_loopback) - m_clientSpeaker->decibelLevel = -96.0f; if (!m_loopback) { if (active && !m_clientSpeaker->playing) m_clientSpeaker->lastPlayTime = now; + m_clientSpeaker->decibelLevel = getAudioLoudness((int16_t*)stream, sampleCount, m_inputVolume); m_clientSpeaker->playing = active; } From 7ad1671e0daf1c15d593343e5e2cd03593db744e Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Wed, 19 Jul 2023 23:28:13 +1000 Subject: [PATCH 31/34] Fix residual voice data not clearing --- source/frontend/StarVoice.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/source/frontend/StarVoice.cpp b/source/frontend/StarVoice.cpp index 4a0e344..44e62cb 100644 --- a/source/frontend/StarVoice.cpp +++ b/source/frontend/StarVoice.cpp @@ -329,9 +329,6 @@ void Voice::readAudioData(uint8_t* stream, int len) { m_clientSpeaker->playing = active; } - if (!active) - return; - MutexLocker captureLock(m_captureMutex); if (active) { m_capturedChunksFrames += sampleCount / m_deviceChannels; From db3d004d3008f10f133c8a1eb8f0200f0b05eea1 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Wed, 19 Jul 2023 23:30:04 +1000 Subject: [PATCH 32/34] Fix decibel level being 0 when under threshold --- source/frontend/StarVoice.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/source/frontend/StarVoice.cpp b/source/frontend/StarVoice.cpp index 44e62cb..fdc10ea 100644 --- a/source/frontend/StarVoice.cpp +++ b/source/frontend/StarVoice.cpp @@ -321,11 +321,12 @@ void Voice::readAudioData(uint8_t* stream, int len) { } } + m_clientSpeaker->decibelLevel = getAudioLoudness((int16_t*)stream, sampleCount, m_inputVolume); + if (!m_loopback) { if (active && !m_clientSpeaker->playing) m_clientSpeaker->lastPlayTime = now; - m_clientSpeaker->decibelLevel = getAudioLoudness((int16_t*)stream, sampleCount, m_inputVolume); m_clientSpeaker->playing = active; } @@ -366,7 +367,8 @@ void Voice::mix(int16_t* buffer, size_t frameCount, unsigned channels) { for (size_t i = 0; i != samples; ++i) speakerBuffer[i] = audio->take(); - speaker->decibelLevel = getAudioLoudness(speakerBuffer.data(), samples); + if (speaker != m_clientSpeaker) + speaker->decibelLevel = getAudioLoudness(speakerBuffer.data(), samples); float volume = speaker->volume; Array2F levels = speaker->channelVolumes; @@ -402,7 +404,8 @@ void Voice::mix(int16_t* buffer, size_t frameCount, unsigned channels) { } else { speaker->playing = false; - speaker->decibelLevel = -96.0f; + if (speaker != m_clientSpeaker) + speaker->decibelLevel = -96.0f; it = m_activeSpeakers.erase(it); } } From 3aa45ab7995c000432bba8ee1335ff7db923ac41 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Thu, 20 Jul 2023 12:52:08 +1000 Subject: [PATCH 33/34] Add voice muting, add input key and mouse functions --- .../opensb/universeclient/voicemanager.lua | 23 ++-- source/game/StarInput.cpp | 101 ++++++++++++------ source/game/StarInput.hpp | 24 ++++- .../game/scripting/StarInputLuaBindings.cpp | 17 +++ 4 files changed, 120 insertions(+), 45 deletions(-) diff --git a/assets/opensb/scripts/opensb/universeclient/voicemanager.lua b/assets/opensb/scripts/opensb/universeclient/voicemanager.lua index 2f19f6a..0a93c3f 100644 --- a/assets/opensb/scripts/opensb/universeclient/voicemanager.lua +++ b/assets/opensb/scripts/opensb/universeclient/voicemanager.lua @@ -80,17 +80,18 @@ local function drawSpeakerBar(mouse, pos, speaker, i) hoveredSpeaker = speaker hoveredSpeakerIndex = i hoveredSpeakerPosition = pos - --if input.key("LShift") then - -- textPositioning.position = {pos[1] + 288, pos[2] + 24} - -- textPositioning.horizontalAnchor = "right" - -- canvas:drawText("^#fff7;" .. tostring(speaker.speakerId), textPositioning, 16, nil, nil, nil, FONT_DIRECTIVES) - --end --- - --if input.mouseDown("MouseLeft") then - -- local muted = not voice.muted(speaker.speakerId) - -- interface.queueMessage((muted and "^#f43030;Muted^reset; " or "^#31d2f7;Unmuted^reset; ") .. speaker.name, 4, 0.5) - -- voice.setMuted(speaker.speakerId, muted) - --end + if input.keyHeld("LShift") then + textPositioning.position = {pos[1] + 288, pos[2] + 24} + textPositioning.horizontalAnchor = "right" + canvas:drawText("^#fff7,font=iosevka-semibold;" .. tostring(speaker.speakerId), textPositioning, 16, nil, nil, nil, FONT_DIRECTIVES) + end + + if input.mouseDown("MouseLeft") then + local muted = not voice.speakerMuted(speaker.speakerId) + interface.queueMessage((muted and "^#f43030;Muted^reset; " or "^#31d2f7;Unmuted^reset; ") .. speaker.name, 4, 0.5) + voice.setSpeakerMuted(speaker.speakerId, muted) + speaker.muted = muted + end end end diff --git a/source/game/StarInput.cpp b/source/game/StarInput.cpp index 7c50d4b..7f0c09c 100644 --- a/source/game/StarInput.cpp +++ b/source/game/StarInput.cpp @@ -316,10 +316,6 @@ Input::InputState* Input::bindStatePtr(String const& categoryId, String const& b return nullptr; } -Input::InputState* Input::inputStatePtr(InputVariant key) { - return m_inputStates.ptr(key); -} - Input* Input::s_singleton; Input* Input::singletonPtr() { @@ -361,28 +357,17 @@ List> const& Input::inputEventsThisFrame() const { void Input::reset() { - m_inputEvents.resize(0); // keeps reserved memory - { - auto it = m_inputStates.begin(); - while (it != m_inputStates.end()) { - if (it->second.held) { - it->second.reset(); - ++it; - } - else it = m_inputStates.erase(it); - } - } + m_inputEvents.clear(); + auto eraseCond = [](auto& p) { + if (p.second.held) + p.second.reset(); + return !p.second.held; + }; - { - auto it = m_bindStates.begin(); - while (it != m_bindStates.end()) { - if (it->second.held) { - it->second.reset(); - ++it; - } - else it = m_bindStates.erase(it); - } - } + eraseWhere(m_keyStates, eraseCond); + eraseWhere(m_mouseStates, eraseCond); + eraseWhere(m_controllerStates, eraseCond); + eraseWhere(m_bindStates, eraseCond); } void Input::update() { @@ -397,7 +382,10 @@ bool Input::handleInput(InputEvent const& input, bool gameProcessed) { m_pressedMods |= *keyToMod; if (!gameProcessed && !m_textInputActive) { - m_inputStates[keyDown->key].press(); + auto& state = m_keyStates[keyDown->key]; + if (keyToMod) + state.mods |= *keyToMod; + state.press(); if (auto binds = m_bindMappings.ptr(keyDown->key)) { for (auto bind : filterBindEntries(*binds, keyDown->mods)) @@ -410,8 +398,11 @@ bool Input::handleInput(InputEvent const& input, bool gameProcessed) { m_pressedMods &= ~*keyToMod; // We need to be able to release input even when gameProcessed is true, but only if it's already down. - if (auto state = m_inputStates.ptr(keyUp->key)) + if (auto state = m_keyStates.ptr(keyUp->key)) { + if (keyToMod) + state->mods &= ~*keyToMod; state->release(); + } if (auto binds = m_bindMappings.ptr(keyUp->key)) { for (auto& bind : *binds) { @@ -421,7 +412,9 @@ bool Input::handleInput(InputEvent const& input, bool gameProcessed) { } } else if (auto mouseDown = input.ptr()) { if (!gameProcessed) { - m_inputStates[mouseDown->mouseButton].press(); + auto& state = m_mouseStates[mouseDown->mouseButton]; + state.pressPositions.append(mouseDown->mousePosition); + state.press(); if (auto binds = m_bindMappings.ptr(mouseDown->mouseButton)) { for (auto bind : filterBindEntries(*binds, m_pressedMods)) @@ -429,8 +422,10 @@ bool Input::handleInput(InputEvent const& input, bool gameProcessed) { } } } else if (auto mouseUp = input.ptr()) { - if (auto state = m_inputStates.ptr(mouseUp->mouseButton)) + if (auto state = m_mouseStates.ptr(mouseUp->mouseButton)) { + state->releasePositions.append(mouseUp->mousePosition); state->release(); + } if (auto binds = m_bindMappings.ptr(mouseUp->mouseButton)) { for (auto& bind : *binds) { @@ -497,10 +492,10 @@ void Input::setTextInputActive(bool active) { } Maybe Input::bindDown(String const& categoryId, String const& bindId) { - if (auto state = bindStatePtr(categoryId, bindId)) + if (auto state = bindStatePtr(categoryId, bindId)) { if (state->presses) return state->presses; - + } return {}; } @@ -512,10 +507,52 @@ bool Input::bindHeld(String const& categoryId, String const& bindId) { } Maybe Input::bindUp(String const& categoryId, String const& bindId) { - if (auto state = bindStatePtr(categoryId, bindId)) + if (auto state = bindStatePtr(categoryId, bindId)) { if (state->releases) return state->releases; + } + return {}; +} +Maybe Input::keyDown(Key key, Maybe keyMod) { + if (auto state = m_keyStates.ptr(key)) { + if (state->presses && (!keyMod || compareKeyMod(*keyMod, state->mods))) + return state->presses; + } + return {}; +} + +bool Input::keyHeld(Key key) { + auto state = m_keyStates.ptr(key); + return state && state->held; +} + +Maybe Input::keyUp(Key key) { + if (auto state = m_keyStates.ptr(key)) { + if (state->releases) + return state->releases; + } + return {}; +} + +Maybe> Input::mouseDown(MouseButton button) { + if (auto state = m_mouseStates.ptr(button)) { + if (state->presses) + return state->pressPositions; + } + return {}; +} + +bool Input::mouseHeld(MouseButton button) { + auto state = m_mouseStates.ptr(button); + return state && state->held; +} + +Maybe> Input::mouseUp(MouseButton button) { + if (auto state = m_mouseStates.ptr(button)) { + if (state->releases) + return state->releasePositions; + } return {}; } diff --git a/source/game/StarInput.hpp b/source/game/StarInput.hpp index 96d4cc6..e4355ce 100644 --- a/source/game/StarInput.hpp +++ b/source/game/StarInput.hpp @@ -117,6 +117,17 @@ public: inline void release() { released = ++releases; held = false; } }; + struct KeyInputState : InputState { + KeyMod mods = KeyMod::NoMod; + }; + + struct MouseInputState : InputState { + List pressPositions; + List releasePositions; + }; + + typedef InputState ControllerInputState; + // Get pointer to the singleton Input instance, if it exists. Otherwise, // returns nullptr. static Input* singletonPtr(); @@ -152,6 +163,14 @@ public: bool bindHeld(String const& categoryId, String const& bindId); Maybe bindUp (String const& categoryId, String const& bindId); + Maybe keyDown(Key key, Maybe keyMod); + bool keyHeld(Key key); + Maybe keyUp (Key key); + + Maybe> mouseDown(MouseButton button); + bool mouseHeld(MouseButton button); + Maybe> mouseUp (MouseButton button); + void resetBinds(String const& categoryId, String const& bindId); void setBinds(String const& categoryId, String const& bindId, Json const& binds); Json getDefaultBinds(String const& categoryId, String const& bindId); @@ -163,7 +182,6 @@ private: BindEntry& bindEntry(String const& categoryId, String const& bindId); InputState* bindStatePtr(String const& categoryId, String const& bindId); - InputState* inputStatePtr(InputVariant key); static Input* s_singleton; @@ -179,7 +197,9 @@ private: // Per-frame input state maps. //Input states - HashMap m_inputStates; + HashMap m_keyStates; + HashMap m_mouseStates; + HashMap m_controllerStates; //Bind states HashMap m_bindStates; diff --git a/source/game/scripting/StarInputLuaBindings.cpp b/source/game/scripting/StarInputLuaBindings.cpp index 5195e31..9f58a40 100644 --- a/source/game/scripting/StarInputLuaBindings.cpp +++ b/source/game/scripting/StarInputLuaBindings.cpp @@ -13,6 +13,23 @@ LuaCallbacks LuaBindings::makeInputCallbacks() { callbacks.registerCallbackWithSignature("bindHeld", bind(mem_fn(&Input::bindHeld), input, _1, _2)); callbacks.registerCallbackWithSignature, String, String>("bindUp", bind(mem_fn(&Input::bindUp), input, _1, _2)); + callbacks.registerCallback("keyDown", [input](String const& keyName, Maybe& const modNames) -> Maybe { + Key key = KeyNames.getLeft(keyName); + Maybe mod; + if (modNames) { + mod = KeyMod::NoMod; + for (auto& modName : *modNames) + *mod |= KeyModNames.getLeft(modName); + } + return input->keyDown(key, mod); + }); + callbacks.registerCallback("keyHeld", [input](String const& keyName) -> bool { return input->keyHeld(KeyNames.getLeft(keyName)); }); + callbacks.registerCallback("keyUp", [input](String const& keyName) -> Maybe { return input->keyUp( KeyNames.getLeft(keyName)); }); + + callbacks.registerCallback("mouseDown", [input](String const& buttonName) -> Maybe> { return input->mouseDown(MouseButtonNames.getLeft(buttonName)); }); + callbacks.registerCallback("mouseHeld", [input](String const& buttonName) -> bool { return input->mouseHeld(MouseButtonNames.getLeft(buttonName)); }); + callbacks.registerCallback("mouseUp", [input](String const& buttonName) -> Maybe> { return input->mouseUp( MouseButtonNames.getLeft(buttonName)); }); + callbacks.registerCallbackWithSignature("resetBinds", bind(mem_fn(&Input::resetBinds), input, _1, _2)); callbacks.registerCallbackWithSignature("setBinds", bind(mem_fn(&Input::setBinds), input, _1, _2, _3)); callbacks.registerCallbackWithSignature("getDefaultBinds", bind(mem_fn(&Input::getDefaultBinds), input, _1, _2)); From 043db1841ee46ace0f6919bfdf6ac20a539faaca Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Thu, 20 Jul 2023 14:55:38 +1000 Subject: [PATCH 34/34] Voice: Minor options menu fixes, fix decibel level freezing on muted speakers --- .../interface/opensb/voicechat/voicechat.lua | 1 + .../optionsmenu/optionsmenu.config.patch | 3 ++- assets/opensb/interface/optionsmenu/shine.png | Bin 0 -> 4562 bytes assets/opensb/sfx/interface/voice_off.ogg | Bin 0 -> 12926 bytes assets/opensb/sfx/interface/voice_on.ogg | Bin 0 -> 13933 bytes source/frontend/StarVoice.cpp | 15 ++++++--------- 6 files changed, 9 insertions(+), 10 deletions(-) create mode 100644 assets/opensb/interface/optionsmenu/shine.png create mode 100644 assets/opensb/sfx/interface/voice_off.ogg create mode 100644 assets/opensb/sfx/interface/voice_on.ogg diff --git a/assets/opensb/interface/opensb/voicechat/voicechat.lua b/assets/opensb/interface/opensb/voicechat/voicechat.lua index 4e9f7e7..974b5b1 100644 --- a/assets/opensb/interface/opensb/voicechat/voicechat.lua +++ b/assets/opensb/interface/opensb/voicechat/voicechat.lua @@ -223,5 +223,6 @@ end function voiceToggle() set("enabled", not settings.enabled) + widget.playSound(fmt("/sfx/interface/voice_%s.ogg", settings.enabled and "on" or "off"), 0) updateVoiceButton() end \ No newline at end of file diff --git a/assets/opensb/interface/optionsmenu/optionsmenu.config.patch b/assets/opensb/interface/optionsmenu/optionsmenu.config.patch index 68b27f9..8866793 100644 --- a/assets/opensb/interface/optionsmenu/optionsmenu.config.patch +++ b/assets/opensb/interface/optionsmenu/optionsmenu.config.patch @@ -42,6 +42,7 @@ "base" : "/interface/optionsmenu/tricontrolsbutton.png", "hover" : "/interface/optionsmenu/tricontrolsbuttonhover.png" }, - "sfxValueLabel" : { "position" : [192, 142] } // this is 2px too low in vanilla lol + "sfxValueLabel" : { "position" : [192, 142] }, // this is 2px too low in vanilla lol + "musicSlider" : { "position" : [62, 126] } } } \ No newline at end of file diff --git a/assets/opensb/interface/optionsmenu/shine.png b/assets/opensb/interface/optionsmenu/shine.png new file mode 100644 index 0000000000000000000000000000000000000000..0117c5fc62d78181e872f20582d2027c3b69e35c GIT binary patch literal 4562 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 Qf2>r_UEn(Kz^cxF0vrS9x&QzG literal 0 HcmV?d00001 diff --git a/assets/opensb/sfx/interface/voice_off.ogg b/assets/opensb/sfx/interface/voice_off.ogg new file mode 100644 index 0000000000000000000000000000000000000000..7a9d2fb617bb0e199c1af7494d74f2c6739badd9 GIT binary patch literal 12926 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 literal 0 HcmV?d00001 diff --git a/assets/opensb/sfx/interface/voice_on.ogg b/assets/opensb/sfx/interface/voice_on.ogg new file mode 100644 index 0000000000000000000000000000000000000000..c00633cc4e9e21f414650a4360913dd24b44f91e GIT binary patch literal 13933 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 literal 0 HcmV?d00001 diff --git a/source/frontend/StarVoice.cpp b/source/frontend/StarVoice.cpp index fdc10ea..82205bd 100644 --- a/source/frontend/StarVoice.cpp +++ b/source/frontend/StarVoice.cpp @@ -362,13 +362,14 @@ void Voice::mix(int16_t* buffer, size_t frameCount, unsigned channels) { VoiceAudioStream* audio = speaker->audioStream.get(); MutexLocker audioLock(audio->mutex); if (speaker->playing && !audio->samples.empty()) { + for (size_t i = 0; i != samples; ++i) + speakerBuffer[i] = audio->take(); + + if (speaker != m_clientSpeaker) + speaker->decibelLevel = getAudioLoudness(speakerBuffer.data(), samples); + if (!speaker->muted) { mix = true; - for (size_t i = 0; i != samples; ++i) - speakerBuffer[i] = audio->take(); - - if (speaker != m_clientSpeaker) - speaker->decibelLevel = getAudioLoudness(speakerBuffer.data(), samples); float volume = speaker->volume; Array2F levels = speaker->channelVolumes; @@ -396,10 +397,6 @@ void Voice::mix(int16_t* buffer, size_t frameCount, unsigned channels) { } //*/ } - else { - for (size_t i = 0; i != samples; ++i) - audio->take(); - } ++it; } else {