diff --git a/AMBuilder b/AMBuilder index 2cd6157..73b85bb 100644 --- a/AMBuilder +++ b/AMBuilder @@ -36,10 +36,12 @@ for sdk_name in SM.sdks: binary.compiler.cxxincludes += [ os.path.join(SM.sm_root, 'public', 'extensions'), - os.path.join(builder.sourcePath, 'opus') + os.path.join(builder.sourcePath, 'opus'), + os.path.join(builder.sourcePath, 'celt') ] binary.compiler.linkflags += [ - os.path.join(builder.sourcePath, 'opus', 'libopus.a') + os.path.join(builder.sourcePath, 'opus', 'libopus.a'), + os.path.join(builder.sourcePath, 'celt', 'libcelt0_patched.a') ] SM.extensions += builder.Add(project) diff --git a/README.md b/README.md new file mode 100644 index 0000000..c9bedb5 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +This new branch is used for allowing both nosteamers and linux users to communicate at the same time. + +Nosteamers are stuck with CELT codec as they dont support steam/opus. + +Linux users are stuck with steam as they dont support celt without manually fixing the .so file. + +This branch discards BroadCastVoiceData for instead creating our own SendNetMsg using SVC_VoiceData + +that enables us to take opus packets, decode to raw pcm and encode with celt. + +that enables us to take celt packets, decode to raw pcm and encode with opus. + +currently does the support for nosteamers to hear steam voice chat still have to be improved, but steam users can hear nosteam users very fine. + +the sourcemod plugin used for stubbing nosteamers with SVC_VoiceInit: https://git.unloze.com/UNLOZE/sm-plugins/src/branch/master/CELT_VOICE diff --git a/celt/celt.h b/celt/celt.h new file mode 100644 index 0000000..2bbf506 --- /dev/null +++ b/celt/celt.h @@ -0,0 +1,325 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Copyright (c) 2008 Gregory Maxwell + Written by Jean-Marc Valin and Gregory Maxwell */ +/** + @file celt.h + @brief Contains all the functions for encoding and decoding audio + */ + +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION 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. +*/ + +#ifndef CELT_H +#define CELT_H + +#include "celt_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(__GNUC__) && defined(CELT_BUILD) +#define EXPORT __attribute__ ((visibility ("default"))) +#elif defined(WIN32) +#define EXPORT __declspec(dllexport) +#else +#define EXPORT +#endif + +#define _celt_check_int(x) (((void)((x) == (celt_int32)0)), (celt_int32)(x)) +#define _celt_check_mode_ptr_ptr(ptr) ((ptr) + ((ptr) - (const CELTMode**)(ptr))) +#define _celt_check_int_ptr(ptr) ((ptr) + ((ptr) - (int*)(ptr))) + +/* Error codes */ +/** No error */ +#define CELT_OK 0 +/** An (or more) invalid argument (e.g. out of range) */ +#define CELT_BAD_ARG -1 +/** The mode struct passed is invalid */ +#define CELT_BUFFER_TOO_SMALL -2 +/** An internal error was detected */ +#define CELT_INTERNAL_ERROR -3 +/** The data passed (e.g. compressed data to decoder) is corrupted */ +#define CELT_CORRUPTED_DATA -4 +/** Invalid/unsupported request number */ +#define CELT_UNIMPLEMENTED -5 +/** An encoder or decoder structure is invalid or already freed */ +#define CELT_INVALID_STATE -6 +/** Memory allocation has failed */ +#define CELT_ALLOC_FAIL -7 + + +/* Encoder/decoder Requests */ + +#define CELT_SET_COMPLEXITY_REQUEST 2 +/** Controls the complexity from 0-10 (int) */ +#define CELT_SET_COMPLEXITY(x) CELT_SET_COMPLEXITY_REQUEST, _celt_check_int(x) + +#define CELT_SET_PREDICTION_REQUEST 4 +/** Controls the use of interframe prediction. + 0=Independent frames + 1=Short term interframe prediction allowed + 2=Long term prediction allowed + */ +#define CELT_SET_PREDICTION(x) CELT_SET_PREDICTION_REQUEST, _celt_check_int(x) + +#define CELT_SET_BITRATE_REQUEST 6 +/** Set the target VBR rate in bits per second(int); 0=CBR (default) */ +#define CELT_SET_BITRATE(x) CELT_SET_BITRATE_REQUEST, _celt_check_int(x) + +#define CELT_RESET_STATE_REQUEST 8 +/** Reset the encoder/decoder memories to zero*/ +#define CELT_RESET_STATE CELT_RESET_STATE_REQUEST + +#define CELT_SET_VBR_CONSTRAINT_REQUEST 10 +#define CELT_SET_VBR_CONSTRAINT(x) CELT_SET_VBR_CONSTRAINT_REQUEST, _celt_check_int(x) + +#define CELT_SET_VBR_REQUEST 12 +#define CELT_SET_VBR(x) CELT_SET_VBR_REQUEST, _celt_check_int(x) + +#define CELT_SET_INPUT_CLIPPING_REQUEST 14 +#define CELT_SET_INPUT_CLIPPING(x) CELT_SET_INPUT_CLIPPING_REQUEST, _celt_check_int(x) + +#define CELT_GET_AND_CLEAR_ERROR_REQUEST 15 +#define CELT_GET_AND_CLEAR_ERROR(x) CELT_GET_AND_CLEAR_ERROR_REQUEST, _celt_check_int_ptr(x) + +#define CELT_GET_LOOKAHEAD_REQUEST 17 +#define CELT_GET_LOOKAHEAD(x) CELT_GET_LOOKAHEAD_REQUEST, _celt_check_int_ptr(x) + +#define CELT_SET_CHANNELS_REQUEST 18 +#define CELT_SET_CHANNELS(x) CELT_SET_CHANNELS_REQUEST, _celt_check_int(x) + +#define CELT_SET_LOSS_PERC_REQUEST 20 +#define CELT_SET_LOSS_PERC(x) CELT_SET_LOSS_PERC_REQUEST, _celt_check_int(x) + +/* Internal */ +#define CELT_SET_START_BAND_REQUEST 10000 +#define CELT_SET_START_BAND(x) CELT_SET_START_BAND_REQUEST, _celt_check_int(x) + +#define CELT_SET_END_BAND_REQUEST 10001 +#define CELT_SET_END_BAND(x) CELT_SET_END_BAND_REQUEST, _celt_check_int(x) + + + +/** Contains the state of an encoder. One encoder state is needed + for each stream. It is initialised once at the beginning of the + stream. Do *not* re-initialise the state for every frame. + @brief Encoder state + */ +typedef struct CELTEncoder CELTEncoder; + +/** State of the decoder. One decoder state is needed for each stream. + It is initialised once at the beginning of the stream. Do *not* + re-initialise the state for every frame */ +typedef struct CELTDecoder CELTDecoder; + +/** The mode contains all the information necessary to create an + encoder. Both the encoder and decoder need to be initialised + with exactly the same mode, otherwise the quality will be very + bad */ +typedef struct CELTMode CELTMode; + + +/** \defgroup codec Encoding and decoding */ +/* @{ */ + +/* Mode calls */ + +/** Creates a new mode struct. This will be passed to an encoder or + decoder. The mode MUST NOT BE DESTROYED until the encoders and + decoders that use it are destroyed as well. + @param Fs Sampling rate (32000 to 96000 Hz) + @param frame_size Number of samples (per channel) to encode in each + packet (even values; 64 - 512) + @param error Returned error code (if NULL, no error will be returned) + @return A newly created mode +*/ +EXPORT CELTMode *celt_mode_create(celt_int32 Fs, int frame_size, int *error); + +/** Destroys a mode struct. Only call this after all encoders and + decoders using this mode are destroyed as well. + @param mode Mode to be destroyed +*/ +EXPORT void celt_mode_destroy(CELTMode *mode); + +/* Encoder stuff */ + +EXPORT int celt_encoder_get_size(int channels); + +EXPORT int celt_encoder_get_size_custom(const CELTMode *mode, int channels); + +/** Creates a new encoder state. Each stream needs its own encoder + state (can't be shared across simultaneous streams). + @param channels Number of channels + @param error Returns an error code + @return Newly created encoder state. +*/ +EXPORT CELTEncoder *celt_encoder_create(int sampling_rate, int channels, int *error); + +/** Creates a new encoder state. Each stream needs its own encoder + state (can't be shared across simultaneous streams). + @param mode Contains all the information about the characteristics of + * the stream (must be the same characteristics as used for the + * decoder) + @param channels Number of channels + @param error Returns an error code + @return Newly created encoder state. +*/ +EXPORT CELTEncoder *celt_encoder_create_custom(const CELTMode *mode, int channels, int *error); + +EXPORT CELTEncoder *celt_encoder_init(CELTEncoder *st, int sampling_rate, int channels, int *error); + +EXPORT CELTEncoder *celt_encoder_init_custom(CELTEncoder *st, const CELTMode *mode, int channels, int *error); + +/** Destroys a an encoder state. + @param st Encoder state to be destroyed + */ +EXPORT void celt_encoder_destroy(CELTEncoder *st); + +/** Encodes a frame of audio. + @param st Encoder state + @param pcm PCM audio in float format, with a normal range of ±1.0. + * Samples with a range beyond ±1.0 are supported but will + * be clipped by decoders using the integer API and should + * only be used if it is known that the far end supports + * extended dynmaic range. There must be exactly + * frame_size samples per channel. + @param compressed The compressed data is written here. This may not alias pcm or + * optional_synthesis. + @param nbCompressedBytes Maximum number of bytes to use for compressing the frame + * (can change from one frame to another) + @return Number of bytes written to "compressed". Will be the same as + * "nbCompressedBytes" unless the stream is VBR and will never be larger. + * If negative, an error has occurred (see error codes). It is IMPORTANT that + * the length returned be somehow transmitted to the decoder. Otherwise, no + * decoding is possible. +*/ +EXPORT int celt_encode_float(CELTEncoder *st, const float *pcm, int frame_size, unsigned char *compressed, int maxCompressedBytes); + +/** Encodes a frame of audio. + @param st Encoder state + @param pcm PCM audio in signed 16-bit format (native endian). There must be + * exactly frame_size samples per channel. + @param compressed The compressed data is written here. This may not alias pcm or + * optional_synthesis. + @param nbCompressedBytes Maximum number of bytes to use for compressing the frame + * (can change from one frame to another) + @return Number of bytes written to "compressed". Will be the same as + * "nbCompressedBytes" unless the stream is VBR and will never be larger. + * If negative, an error has occurred (see error codes). It is IMPORTANT that + * the length returned be somehow transmitted to the decoder. Otherwise, no + * decoding is possible. + */ +EXPORT int celt_encode(CELTEncoder *st, const celt_int16 *pcm, int frame_size, unsigned char *compressed, int maxCompressedBytes); + +/** Query and set encoder parameters + @param st Encoder state + @param request Parameter to change or query + @param value Pointer to a 32-bit int value + @return Error code +*/ +EXPORT int celt_encoder_ctl(CELTEncoder * st, int request, ...); + +/* Decoder stuff */ + +EXPORT int celt_decoder_get_size(int channels); + +EXPORT int celt_decoder_get_size_custom(const CELTMode *mode, int channels); + +/** Creates a new decoder state. Each stream needs its own decoder state (can't + be shared across simultaneous streams). + @param mode Contains all the information about the characteristics of the + stream (must be the same characteristics as used for the encoder) + @param channels Number of channels + @param error Returns an error code + @return Newly created decoder state. + */ +EXPORT CELTDecoder *celt_decoder_create(int sampling_rate, int channels, int *error); + +/** Creates a new decoder state. Each stream needs its own decoder state (can't + be shared across simultaneous streams). + @param mode Contains all the information about the characteristics of the + stream (must be the same characteristics as used for the encoder) + @param channels Number of channels + @param error Returns an error code + @return Newly created decoder state. + */ +EXPORT CELTDecoder *celt_decoder_create_custom(const CELTMode *mode, int channels, int *error); + +EXPORT CELTDecoder *celt_decoder_init(CELTDecoder *st, int sampling_rate, int channels, int *error); + +EXPORT CELTDecoder *celt_decoder_init_custom(CELTDecoder *st, const CELTMode *mode, int channels, int *error); + +/** Destroys a a decoder state. + @param st Decoder state to be destroyed + */ +EXPORT void celt_decoder_destroy(CELTDecoder *st); + +/** Decodes a frame of audio. + @param st Decoder state + @param data Compressed data produced by an encoder + @param len Number of bytes to read from "data". This MUST be exactly the number + of bytes returned by the encoder. Using a larger value WILL NOT WORK. + @param pcm One frame (frame_size samples per channel) of decoded PCM will be + returned here in float format. + @return Error code. + */ +EXPORT int celt_decode_float(CELTDecoder *st, const unsigned char *data, int len, float *pcm, int frame_size); + +/** Decodes a frame of audio. + @param st Decoder state + @param data Compressed data produced by an encoder + @param len Number of bytes to read from "data". This MUST be exactly the number + of bytes returned by the encoder. Using a larger value WILL NOT WORK. + @param pcm One frame (frame_size samples per channel) of decoded PCM will be + returned here in 16-bit PCM format (native endian). + @return Error code. + */ +EXPORT int celt_decode(CELTDecoder *st, const unsigned char *data, int len, celt_int16 *pcm, int frame_size); + +/** Query and set decoder parameters + @param st Decoder state + @param request Parameter to change or query + @param value Pointer to a 32-bit int value + @return Error code + */ +EXPORT int celt_decoder_ctl(CELTDecoder * st, int request, ...); + + +/** Returns the English string that corresponds to an error code + * @param error Error code (negative for an error, 0 for success + * @return Constant string (must NOT be freed) + */ +EXPORT const char *celt_strerror(int error); + +/* @} */ + + +#ifdef __cplusplus +} +#endif + +#endif /*CELT_H */ diff --git a/celt/celt_header.h b/celt/celt_header.h new file mode 100644 index 0000000..3777484 --- /dev/null +++ b/celt/celt_header.h @@ -0,0 +1,66 @@ +/* Copyright (c) 2007 CSIRO + Copyright (c) 2007-2008 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION 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. +*/ + +#ifndef CELT_HEADER_H +#define CELT_HEADER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "celt.h" +#include "celt_types.h" + +/** Header data to be used for Ogg files (or possibly other encapsulation) + @brief Header data + */ +typedef struct { + char codec_id[8]; /**< MUST be "CELT " (four spaces) */ + char codec_version[20]; /**< Version used (as string) */ + celt_int32 version_id; /**< Version id (negative for until stream is frozen) */ + celt_int32 header_size; /**< Size of this header */ + celt_int32 sample_rate; /**< Sampling rate of the original audio */ + celt_int32 nb_channels; /**< Number of channels */ + celt_int32 frame_size; /**< Samples per frame (per channel) */ + celt_int32 overlap; /**< Overlapping samples (per channel) */ + celt_int32 bytes_per_packet; /**< Number of bytes per compressed packet (0 if unknown) */ + celt_int32 extra_headers; /**< Number of additional headers that follow this header */ +} CELTHeader; + +/** Creates a basic header struct */ +EXPORT int celt_header_init(CELTHeader *header, const CELTMode *m, int frame_size, int channels); + +EXPORT int celt_header_to_packet(const CELTHeader *header, unsigned char *packet, celt_uint32 size); + +EXPORT int celt_header_from_packet(const unsigned char *packet, celt_uint32 size, CELTHeader *header); + +#ifdef __cplusplus +} +#endif + +#endif /* CELT_HEADER_H */ diff --git a/celt/celt_types.h b/celt/celt_types.h new file mode 100644 index 0000000..bfd498a --- /dev/null +++ b/celt/celt_types.h @@ -0,0 +1,151 @@ +/* (C) COPYRIGHT 1994-2002 Xiph.Org Foundation */ +/* Modified by Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION 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. +*/ +/* celt_types.h taken from libogg */ + +/** + @file celt_types.h + @brief CELT types +*/ +#ifndef _CELT_TYPES_H +#define _CELT_TYPES_H + +/* Use the real stdint.h if it's there (taken from Paul Hsieh's pstdint.h) */ +#if (defined(__STDC__) && __STDC__ && __STDC_VERSION__ >= 199901L) || (defined(__GNUC__) && (defined(_STDINT_H) || defined(_STDINT_H_)) || defined (HAVE_STDINT_H)) +#include + + typedef int16_t celt_int16; + typedef uint16_t celt_uint16; + typedef int32_t celt_int32; + typedef uint32_t celt_uint32; +#elif defined(_WIN32) + +# if defined(__CYGWIN__) +# include <_G_config.h> + typedef _G_int32_t celt_int32; + typedef _G_uint32_t celt_uint32; + typedef _G_int16 celt_int16; + typedef _G_uint16 celt_uint16; +# elif defined(__MINGW32__) + typedef short celt_int16; + typedef unsigned short celt_uint16; + typedef int celt_int32; + typedef unsigned int celt_uint32; +# elif defined(__MWERKS__) + typedef int celt_int32; + typedef unsigned int celt_uint32; + typedef short celt_int16; + typedef unsigned short celt_uint16; +# else + /* MSVC/Borland */ + typedef __int32 celt_int32; + typedef unsigned __int32 celt_uint32; + typedef __int16 celt_int16; + typedef unsigned __int16 celt_uint16; +# endif + +#elif defined(__MACOS__) + +# include + typedef SInt16 celt_int16; + typedef UInt16 celt_uint16; + typedef SInt32 celt_int32; + typedef UInt32 celt_uint32; + +#elif (defined(__APPLE__) && defined(__MACH__)) /* MacOS X Framework build */ + +# include + typedef int16_t celt_int16; + typedef u_int16_t celt_uint16; + typedef int32_t celt_int32; + typedef u_int32_t celt_uint32; + +#elif defined(__BEOS__) + + /* Be */ +# include + typedef int16 celt_int16; + typedef u_int16 celt_uint16; + typedef int32_t celt_int32; + typedef u_int32_t celt_uint32; + +#elif defined (__EMX__) + + /* OS/2 GCC */ + typedef short celt_int16; + typedef unsigned short celt_uint16; + typedef int celt_int32; + typedef unsigned int celt_uint32; + +#elif defined (DJGPP) + + /* DJGPP */ + typedef short celt_int16; + typedef unsigned short celt_uint16; + typedef int celt_int32; + typedef unsigned int celt_uint32; + +#elif defined(R5900) + + /* PS2 EE */ + typedef int celt_int32; + typedef unsigned celt_uint32; + typedef short celt_int16; + typedef unsigned short celt_uint16; + +#elif defined(__SYMBIAN32__) + + /* Symbian GCC */ + typedef signed short celt_int16; + typedef unsigned short celt_uint16; + typedef signed int celt_int32; + typedef unsigned int celt_uint32; + +#elif defined(CONFIG_TI_C54X) || defined (CONFIG_TI_C55X) + + typedef short celt_int16; + typedef unsigned short celt_uint16; + typedef long celt_int32; + typedef unsigned long celt_uint32; + +#elif defined(CONFIG_TI_C6X) + + typedef short celt_int16; + typedef unsigned short celt_uint16; + typedef int celt_int32; + typedef unsigned int celt_uint32; + +#else + + /* Give up, take a reasonable guess */ + typedef short celt_int16; + typedef unsigned short celt_uint16; + typedef int celt_int32; + typedef unsigned int celt_uint32; + +#endif + +#endif /* _CELT_TYPES_H */ diff --git a/celt/celt_work/bands.o b/celt/celt_work/bands.o new file mode 100644 index 0000000..efcf256 Binary files /dev/null and b/celt/celt_work/bands.o differ diff --git a/celt/celt_work/celt.o b/celt/celt_work/celt.o new file mode 100644 index 0000000..0b257cf Binary files /dev/null and b/celt/celt_work/celt.o differ diff --git a/celt/celt_work/cwrs.o b/celt/celt_work/cwrs.o new file mode 100644 index 0000000..b3c5d5f Binary files /dev/null and b/celt/celt_work/cwrs.o differ diff --git a/celt/celt_work/entcode.o b/celt/celt_work/entcode.o new file mode 100644 index 0000000..80489f3 Binary files /dev/null and b/celt/celt_work/entcode.o differ diff --git a/celt/celt_work/entdec.o b/celt/celt_work/entdec.o new file mode 100644 index 0000000..b177b02 Binary files /dev/null and b/celt/celt_work/entdec.o differ diff --git a/celt/celt_work/entenc.o b/celt/celt_work/entenc.o new file mode 100644 index 0000000..a2255cc Binary files /dev/null and b/celt/celt_work/entenc.o differ diff --git a/celt/celt_work/header.o b/celt/celt_work/header.o new file mode 100644 index 0000000..65d39f9 Binary files /dev/null and b/celt/celt_work/header.o differ diff --git a/celt/celt_work/kiss_fft.o b/celt/celt_work/kiss_fft.o new file mode 100644 index 0000000..eef1622 Binary files /dev/null and b/celt/celt_work/kiss_fft.o differ diff --git a/celt/celt_work/laplace.o b/celt/celt_work/laplace.o new file mode 100644 index 0000000..59ef2a0 Binary files /dev/null and b/celt/celt_work/laplace.o differ diff --git a/celt/celt_work/mathops.o b/celt/celt_work/mathops.o new file mode 100644 index 0000000..89303de Binary files /dev/null and b/celt/celt_work/mathops.o differ diff --git a/celt/celt_work/mdct.o b/celt/celt_work/mdct.o new file mode 100644 index 0000000..96e12b0 Binary files /dev/null and b/celt/celt_work/mdct.o differ diff --git a/celt/celt_work/modes.o b/celt/celt_work/modes.o new file mode 100644 index 0000000..1aa6d72 Binary files /dev/null and b/celt/celt_work/modes.o differ diff --git a/celt/celt_work/pitch.o b/celt/celt_work/pitch.o new file mode 100644 index 0000000..b382df0 Binary files /dev/null and b/celt/celt_work/pitch.o differ diff --git a/celt/celt_work/plc.o b/celt/celt_work/plc.o new file mode 100644 index 0000000..8436146 Binary files /dev/null and b/celt/celt_work/plc.o differ diff --git a/celt/celt_work/quant_bands.o b/celt/celt_work/quant_bands.o new file mode 100644 index 0000000..fde0145 Binary files /dev/null and b/celt/celt_work/quant_bands.o differ diff --git a/celt/celt_work/rate.o b/celt/celt_work/rate.o new file mode 100644 index 0000000..491d6c5 Binary files /dev/null and b/celt/celt_work/rate.o differ diff --git a/celt/celt_work/vq.o b/celt/celt_work/vq.o new file mode 100644 index 0000000..33968a3 Binary files /dev/null and b/celt/celt_work/vq.o differ diff --git a/celt/libcelt0.a b/celt/libcelt0.a new file mode 100644 index 0000000..0c73b68 Binary files /dev/null and b/celt/libcelt0.a differ diff --git a/celt/libcelt0_patched.a b/celt/libcelt0_patched.a new file mode 100644 index 0000000..379d519 Binary files /dev/null and b/celt/libcelt0_patched.a differ diff --git a/extension.cpp b/extension.cpp index 7b64cab..28fe0fb 100644 --- a/extension.cpp +++ b/extension.cpp @@ -44,6 +44,8 @@ #include #include +#include "inetmessage.h" + #include "CDetour/detours.h" #include "extension.h" @@ -136,11 +138,133 @@ IServer *iserver = NULL; double g_fLastVoiceData[SM_MAXPLAYERS + 1]; int g_aFrameVoiceBytes[SM_MAXPLAYERS + 1]; +bool g_bIsNoSteam[SM_MAXPLAYERS + 1]; +bool g_bClientMuted[SM_MAXPLAYERS + 1][SM_MAXPLAYERS + 1]; // [muter][mutee] + +static void SendVoiceDataMsg(int fromClientSlot, IClient *pToClient, unsigned char *data, int nBytes, int64 xuid) +{ + SVC_VoiceData msg; + msg.m_bProximity = false; + msg.m_nLength = nBytes * 8; // length in bits + msg.m_xuid = xuid; + msg.m_nFromClient = fromClientSlot; + msg.m_DataOut = data; + pToClient->SendNetMsg(msg); +} + +#define Bits2Bytes(b) ((b+7)>>3) + +/* +objdump -d /home/gameservers/css_dev/bin/engine_srv.so | grep -A 10 "_ZNK11CBaseClient13GetPlayerSlotEv" +00081a90 <_ZNK11CBaseClient13GetPlayerSlotEv>: + 81a90: 55 push %ebp + 81a91: 89 e5 mov %esp,%ebp + 81a93: 8b 45 08 mov 0x8(%ebp),%eax + 81a96: 5d pop %ebp + 81a97: 8b 40 0c mov 0xc(%eax),%eax + 81a9a: c3 ret + 81a9b: 8d 74 26 00 lea 0x0(%esi,%eiz,1),%esi + 81a9f: 90 nop + +00081aa0 <_ZThn4_NK11CBaseClient13GetPlayerSlotEv>: + +mov 0xc(%eax),%eax -> offset is 12 bytes (0x0c) +*/ + +DETOUR_DECL_MEMBER1(ProcessVoiceData, bool, void *, msg) +{ + //in member class we use "this" to obtain the client. + int playerSlot = *(int*)((char*)this + 12); //m_nClientSlot member CBaseClient which CGameClient inherits from. + int clientIndex = playerSlot + 1; + + CLC_VoiceData *voiceMsg = (CLC_VoiceData *)msg; + + char voiceDataBuffer[4096]; + int bitsRead = voiceMsg->m_DataIn.ReadBitsClamped(voiceDataBuffer, voiceMsg->m_nLength); + int nBytes = Bits2Bytes(bitsRead); + + //still need this super important check for preventing empty voice packets spam. + if (nBytes < 1) + return true; + + if (clientIndex < 1 || clientIndex > SM_MAXPLAYERS) + return true; + + // Reject voice packet if we'd send more than NET_MAX_VOICE_BYTES_FRAME voice bytes from this client in the current frame. + // 5 = SVC_VoiceData header/overhead + g_aFrameVoiceBytes[clientIndex] += 5 + nBytes; + if (g_aFrameVoiceBytes[clientIndex] > NET_MAX_VOICE_BYTES_FRAME) + return true; + + g_fLastVoiceData[clientIndex] = gpGlobals->curtime; + + // Get IClient for sending + IClient *pClient = iserver->GetClient(playerSlot); + if (!pClient) + return true; + + // Detect NoSteam + if (g_bIsNoSteam[clientIndex]) + { + //convert nosteam CELT packet to OPUS for steam clients + g_Interface.TranscodeNoSteamToSteam(playerSlot, nBytes, voiceDataBuffer); + //send nosteam CELT packet to no steam clients + int maxClients = iserver->GetClientCount(); + for (int i = 0; i < maxClients; i++) + { + IClient *pToClient = iserver->GetClient(i); + if (!pToClient || !pToClient->IsConnected() || !pToClient->IsActive() || pToClient->IsFakeClient()) + continue; + + bool bSelf = (i == playerSlot); + + if (bSelf) + continue; + + if (!g_bIsNoSteam[i + 1]) + continue; + if (g_bClientMuted[i + 1][clientIndex]) + continue; + //smutils->LogMessage(myself, "in ProcessVoiceData before SendVoiceDataMsg"); + SendVoiceDataMsg(playerSlot, pToClient, (unsigned char *)voiceDataBuffer, nBytes, voiceMsg->m_xuid); + } + } + else + { + //convert steam OPUS packet to CELT for no steam clients + g_Interface.PushPlayerVoiceData(playerSlot, nBytes, voiceDataBuffer); + // Send steam Opus packet to Steam clients + int maxClients = iserver->GetClientCount(); + for (int i = 0; i < maxClients; i++) + { + IClient *pToClient = iserver->GetClient(i); + if (!pToClient || !pToClient->IsConnected() || !pToClient->IsActive() || pToClient->IsFakeClient()) + continue; + + bool bSelf = (i == playerSlot); + + if (bSelf) + continue; + if (g_bIsNoSteam[i + 1]) + continue; + if (g_bClientMuted[i + 1][clientIndex]) + continue; + //smutils->LogMessage(myself, "in ProcessVoiceData before SendVoiceDataMsg"); + SendVoiceDataMsg(playerSlot, pToClient, (unsigned char *)voiceDataBuffer, nBytes, voiceMsg->m_xuid); + } + } + return true; +} DETOUR_DECL_STATIC4(SV_BroadcastVoiceData, void, IClient *, pClient, int, nBytes, char *, data, int64, xuid) { - if(g_Interface.OnBroadcastVoiceData(pClient, nBytes, data)) + //if(g_Interface.OnBroadcastVoiceData(pClient, nBytes, data)) + /* + if (1 == 2) //we dont do dis no more + { DETOUR_STATIC_CALL(SV_BroadcastVoiceData)(pClient, nBytes, data, xuid); + } + */ } #ifdef _WIN32 @@ -191,7 +315,40 @@ CVoice::CVoice() m_AvailableTime = 0.0; m_VoiceDetour = NULL; + m_ProcessVoiceDataDetour = NULL; m_SV_BroadcastVoiceData = NULL; + + m_TorchlightOpusDecoder = NULL; + for (int i = 0; i <= SM_MAXPLAYERS; i++) + { + m_PlayerOpusDecoder[i] = NULL; + } + + for (int i = 0; i <= SM_MAXPLAYERS; i++) + { + m_pCeltDecoder[i] = NULL; + } + + m_pCeltMode = NULL; + m_pCeltCodec = NULL; + m_pCeltModePlayer = NULL; + m_torchMonoAccumLen = 0; + m_torchResampleAccum = 0; + memset(m_playerResampleAccum, 0, sizeof(m_playerResampleAccum)); + memset(m_torchMonoAccum, 0, sizeof(m_torchMonoAccum)); + memset(m_nosteamSeqNum, 0, sizeof(m_nosteamSeqNum)); + + memset(g_bIsNoSteam, 0, sizeof(g_bIsNoSteam)); + + memset(m_playerAvailableTime, 0, sizeof(m_playerAvailableTime)); + for (int i = 0; i <= SM_MAXPLAYERS; i++) + m_pCeltCodecPlayer[i] = NULL; + + memset(g_bClientMuted, 0, sizeof(g_bClientMuted)); + memset(m_nosteamResampleAccum, 0, sizeof(m_nosteamResampleAccum)); + memset(m_nosteamAvailableTime, 0, sizeof(m_nosteamAvailableTime)); + for (int i = 0; i <= SM_MAXPLAYERS; i++) + m_nosteamOpusEncoder[i] = NULL; } bool CVoice::SDK_OnLoad(char *error, size_t maxlength, bool late) @@ -266,6 +423,8 @@ bool CVoice::SDK_OnLoad(char *error, size_t maxlength, bool late) dlclose(pEngineSo); return false; } + //2026 signature of CGameClient::ProcessVoiceData + void *adrProcessVoiceData = memutils->ResolveSymbol(pEngineSo, "_ZN11CGameClient16ProcessVoiceDataEP13CLC_VoiceData"); dlclose(pEngineSo); m_SV_BroadcastVoiceData = (t_SV_BroadcastVoiceData)adrVoiceData; @@ -299,6 +458,15 @@ bool CVoice::SDK_OnLoad(char *error, size_t maxlength, bool late) m_VoiceDetour->EnableDetour(); + //2026 detouring CGameClient::ProcessVoiceData + m_ProcessVoiceDataDetour = DETOUR_CREATE_MEMBER(ProcessVoiceData, adrProcessVoiceData); + if (!m_ProcessVoiceDataDetour) + { + g_SMAPI->Format(error, maxlength, "ProcessVoiceData detour failed."); + return false; + } + m_ProcessVoiceDataDetour->EnableDetour(); + //opus edit int err; //m_OpusEncoder = opus_encoder_create(24000, 2, OPUS_APPLICATION_AUDIO, &err); @@ -322,6 +490,56 @@ bool CVoice::SDK_OnLoad(char *error, size_t maxlength, bool late) return false; } + // Opus decoder for torchlight (48000 Hz stereo) + int err2; + m_TorchlightOpusDecoder = opus_decoder_create(48000, 2, &err2); + if (err2 < 0) + { + smutils->LogError(myself, "failed to create torchlight opus decoder: %s", opus_strerror(err2)); + return false; + } + + // CELT encoder (22050 Hz mono, 512 samples/frame, 64 byte packets) + m_CeltEncoderSettings.SampleRate_Hz = 22050; + m_CeltEncoderSettings.TargetBitRate_Kbps = 64; + m_CeltEncoderSettings.FrameSize = 512; + m_CeltEncoderSettings.PacketSize = 64; + m_CeltEncoderSettings.Complexity = 10; + m_CeltEncoderSettings.FrameTime = (double)m_CeltEncoderSettings.FrameSize + / (double)m_CeltEncoderSettings.SampleRate_Hz; + + int theError; + //torchlight celt + m_pCeltMode = celt_mode_create(m_CeltEncoderSettings.SampleRate_Hz, + m_CeltEncoderSettings.FrameSize, &theError); + if (!m_pCeltMode) + { + g_SMAPI->Format(error, maxlength, "celt_mode_create error: %d", theError); + SDK_OnUnload(); + return false; + } + + m_pCeltCodec = celt_encoder_create_custom(m_pCeltMode, 1, &theError); + if (!m_pCeltCodec) + { + g_SMAPI->Format(error, maxlength, "celt_encoder_create_custom error: %d", theError); + SDK_OnUnload(); + return false; + } + + celt_encoder_ctl(m_pCeltCodec, CELT_RESET_STATE_REQUEST, NULL); + celt_encoder_ctl(m_pCeltCodec, CELT_SET_BITRATE(m_CeltEncoderSettings.TargetBitRate_Kbps * 1000)); + celt_encoder_ctl(m_pCeltCodec, CELT_SET_COMPLEXITY(m_CeltEncoderSettings.Complexity)); + + //Player celt + m_pCeltModePlayer = celt_mode_create(m_CeltEncoderSettings.SampleRate_Hz, + m_CeltEncoderSettings.FrameSize, &theError); + if (!m_pCeltModePlayer) + { + g_SMAPI->Format(error, maxlength, "celt_mode_create 2 error: %d", theError); + SDK_OnUnload(); + return false; + } return true; } @@ -360,9 +578,54 @@ cell_t IsClientTalking(IPluginContext *pContext, const cell_t *params) return true; } +cell_t Native_SetClientNoSteam(IPluginContext *pContext, const cell_t *params) +{ + int client = params[1]; + bool IsSteam = params[2]; + + if (client < 1 || client > SM_MAXPLAYERS) + return pContext->ThrowNativeError("Invalid client index %d", client); + + g_bIsNoSteam[client] = IsSteam; + return 1; +} + +cell_t Native_SendCeltVoiceInit(IPluginContext *pContext, const cell_t *params) +{ + int client = params[1]; + if (client < 1 || client > SM_MAXPLAYERS) + return pContext->ThrowNativeError("Invalid client index %d", client); + + IClient *pClient = iserver->GetClient(client - 1); + if (!pClient || !pClient->IsConnected()) + return 0; + + SVC_VoiceInit msg("vaudio_celt", 22050); + pClient->SendNetMsg(msg); + smutils->LogMessage(myself, "Sent SVC_VoiceInit vaudio_celt to client %d", client); + return 1; +} + +cell_t Native_ClientMutedOtherClient(IPluginContext *pContext, const cell_t *params) +{ + int clientmuter = params[1]; + int clientmutee = params[2]; + if (clientmuter < 1 || clientmuter > SM_MAXPLAYERS) + return pContext->ThrowNativeError("Invalid clientmuter index %d", clientmuter); + + if (clientmutee < 0 || clientmutee > SM_MAXPLAYERS) //we allow muting the SourceTV + return pContext->ThrowNativeError("Invalid clientmutee index %d", clientmutee); + + g_bClientMuted[clientmuter][clientmutee] = params[3]; //true or false + //smutils->LogMessage(myself, "native g_bClientMuted: %i. clientmuter: %i. clientmutee: %i", g_bClientMuted[clientmuter][clientmutee], clientmuter, clientmutee); + return 1; +} const sp_nativeinfo_t MyNatives[] = { { "IsClientTalking", IsClientTalking }, + { "SetClientNoSteam", Native_SetClientNoSteam }, + { "SendCeltVoiceInit", Native_SendCeltVoiceInit }, + { "ClientMutedOtherClient", Native_ClientMutedOtherClient }, { NULL, NULL } }; @@ -462,6 +725,12 @@ void CVoice::SDK_OnUnload() m_VoiceDetour = NULL; } + if (m_ProcessVoiceDataDetour) + { + m_ProcessVoiceDataDetour->Destroy(); + m_ProcessVoiceDataDetour = NULL; + } + if(m_ListenSocket != -1) { close(m_ListenSocket); @@ -478,12 +747,74 @@ void CVoice::SDK_OnUnload() } opus_encoder_destroy(m_OpusEncoder); + + if (m_TorchlightOpusDecoder) + { + opus_decoder_destroy(m_TorchlightOpusDecoder); + m_TorchlightOpusDecoder = NULL; + } + + for (int i = 0; i <= SM_MAXPLAYERS; i++) + { + if (m_PlayerOpusDecoder[i]) + { + opus_decoder_destroy(m_PlayerOpusDecoder[i]); + m_PlayerOpusDecoder[i] = NULL; + } + } + + if (m_pCeltCodec) + { + celt_encoder_destroy(m_pCeltCodec); + m_pCeltCodec = NULL; + } + + if (m_pCeltMode) + { + celt_mode_destroy(m_pCeltMode); + m_pCeltMode = NULL; + } + + for (int i = 0; i <= SM_MAXPLAYERS; i++) + { + if (m_pCeltCodecPlayer[i]) + { + celt_encoder_destroy(m_pCeltCodecPlayer[i]); + m_pCeltCodecPlayer[i] = NULL; + } + } + + if (m_pCeltModePlayer) + { + celt_mode_destroy(m_pCeltModePlayer); + m_pCeltModePlayer = NULL; + } + + for (int i = 0; i <= SM_MAXPLAYERS; i++) + { + if (m_pCeltDecoder[i]) + { + celt_decoder_destroy(m_pCeltDecoder[i]); + m_pCeltDecoder[i] = NULL; + } + } + + for (int i = 0; i <= SM_MAXPLAYERS; i++) + { + if (m_nosteamOpusEncoder[i]) + { + opus_encoder_destroy(m_nosteamOpusEncoder[i]); + m_nosteamOpusEncoder[i] = NULL; + } + } } void CVoice::OnGameFrame(bool simulating) { HandleNetwork(); HandleVoiceData(); + HandlePlayerVoiceData(); //send celt packets to nosteamers. + HandleNoSteamVoiceData(); //send opus packets to steamers. // Reset per-client voice byte counter to 0 every frame. memset(g_aFrameVoiceBytes, 0, sizeof(g_aFrameVoiceBytes)); @@ -497,6 +828,11 @@ bool CVoice::OnBroadcastVoiceData(IClient *pClient, int nBytes, char *data) int client = pClient->GetPlayerSlot() + 1; + if (client < 1 || client > SM_MAXPLAYERS) + { + return false; + } + // Reject voice packet if we'd send more than NET_MAX_VOICE_BYTES_FRAME voice bytes from this client in the current frame. // 5 = SVC_VoiceData header/overhead g_aFrameVoiceBytes[client] += 5 + nBytes; @@ -690,9 +1026,9 @@ void CVoice::OnDataReceived(CClient *pClient, int16_t *pData, size_t Samples) pClient->m_LastValidData = getTime(); } + void CVoice::HandleVoiceData() { - //uint32_t sampleRate = 24000; uint32_t sampleRate = 48000; const int SamplesPerChannel = 480; const int Channels = 2; @@ -784,6 +1120,7 @@ void CVoice::HandleVoiceData() smutils->LogError(myself, "Opus encode failed: %s", opus_strerror(nbBytes)); return; } + BroadcastVoiceDataCelt(pClient, aBuffer, SamplesPerChannel); // Write frame size *pFrameSize = (uint16_t)nbBytes; @@ -813,7 +1150,20 @@ void CVoice::HandleVoiceData() memcpy(&aFinal[FinalSize], &crc32_value, sizeof(uint32_t)); FinalSize += sizeof(uint32_t); - BroadcastVoiceData(pClient, FinalSize, aFinal); + int maxClients = iserver->GetClientCount(); + for (int i = 0; i < maxClients; i++) + { + IClient *pToClient = iserver->GetClient(i); + if (!pToClient || !pToClient->IsConnected() || !pToClient->IsActive() || pToClient->IsFakeClient()) + continue; + if (g_bClientMuted[i + 1][pClient->GetPlayerSlot()]) + continue; + if (!g_bIsNoSteam[i + 1]) + { + //sending opus packets to steam users. + SendVoiceDataMsg(pClient->GetPlayerSlot(), pToClient, aFinal, FinalSize, 0); + } + } if (m_AvailableTime < getTime()) m_AvailableTime = getTime(); @@ -825,14 +1175,483 @@ void CVoice::HandleVoiceData() } } -void CVoice::BroadcastVoiceData(IClient *pClient, int nBytes, unsigned char *pData) +void CVoice::BroadcastVoiceDataCelt(IClient *pClient, int16_t *pPCM, int nSamples) { - #ifdef _WIN32 - __asm mov ecx, pClient; - __asm mov edx, nBytes; + // pPCM is stereo interleaved at 48000 Hz (SamplesPerChannel * 2 total samples) + // Downsample to mono 22050 Hz and accumulate into m_torchMonoAccum + const int STEP = m_CeltEncoderSettings.SampleRate_Hz; // 22050 + const int DIV = 48000; + const int CELT_FRAME_SIZE = m_CeltEncoderSettings.FrameSize; // 512 + const int CELT_PACKET_SIZE = m_CeltEncoderSettings.PacketSize; // 64 - DETOUR_STATIC_CALL(SV_BroadcastVoiceData_LTCG)((char *)pData, 0); - #else - DETOUR_STATIC_CALL(SV_BroadcastVoiceData)(pClient, nBytes, (char *)pData, 0); - #endif + int fromSlot = pClient->GetPlayerSlot(); + int maxClients = iserver->GetClientCount(); + + for (int i = 0; i < nSamples; i++) + { + if (m_torchMonoAccumLen >= 4096) + break; + + int32_t mono = ((int32_t)pPCM[i * 2] + (int32_t)pPCM[i * 2 + 1]) / 2; + if (mono > 32767) mono = 32767; + if (mono < -32768) mono = -32768; + + m_torchResampleAccum += STEP; + while (m_torchResampleAccum >= DIV) + { + m_torchResampleAccum -= DIV; + if (m_torchMonoAccumLen < 4096) + m_torchMonoAccum[m_torchMonoAccumLen++] = (int16_t)mono; + } + } + + while (m_torchMonoAccumLen >= CELT_FRAME_SIZE) + { + unsigned char celtPacket[CELT_PACKET_SIZE]; + int celtBytes = celt_encode(m_pCeltCodec, m_torchMonoAccum, CELT_FRAME_SIZE, + celtPacket, CELT_PACKET_SIZE); + if (celtBytes > 0) + { + for (int i = 0; i < maxClients; i++) + { + IClient *pToClient = iserver->GetClient(i); + if (!pToClient || !pToClient->IsConnected() || !pToClient->IsActive() || pToClient->IsFakeClient()) + continue; + if (!g_bIsNoSteam[i + 1]) //i dont actually understand why the index is one off here. + continue; + if (g_bClientMuted[i + 1][fromSlot]) + continue; + //smutils->LogMessage(myself, "right before SendVoiceDataMsg"); + SendVoiceDataMsg(fromSlot, pToClient, celtPacket, celtBytes, 0); + } + } + else + { + smutils->LogError(myself, "BroadcastVoiceDataCelt: celt_encode failed: %d", celtBytes); + } + + m_torchMonoAccumLen -= CELT_FRAME_SIZE; + if (m_torchMonoAccumLen > 0) + memmove(m_torchMonoAccum, m_torchMonoAccum + CELT_FRAME_SIZE, + m_torchMonoAccumLen * sizeof(int16_t)); + } +} + +void CVoice::PushPlayerVoiceData(int playerSlot, int nBytes, char *data) +{ + if (playerSlot < 0 || playerSlot >= SM_MAXPLAYERS) + return; + + if (!m_pCeltCodecPlayer[playerSlot]) + { + // First time this player speaks - create encoder + int theError; + m_pCeltCodecPlayer[playerSlot] = celt_encoder_create_custom( + m_pCeltModePlayer, 1, &theError); + if (!m_pCeltCodecPlayer[playerSlot]) + { + smutils->LogError(myself, "PushPlayerVoiceData: celt_encoder_create_custom failed: %d", theError); + return; + } + celt_encoder_ctl(m_pCeltCodecPlayer[playerSlot], CELT_RESET_STATE_REQUEST, NULL); + celt_encoder_ctl(m_pCeltCodecPlayer[playerSlot], CELT_SET_BITRATE( + m_CeltEncoderSettings.TargetBitRate_Kbps * 1000)); + celt_encoder_ctl(m_pCeltCodecPlayer[playerSlot], CELT_SET_COMPLEXITY( + m_CeltEncoderSettings.Complexity)); + } + + if (!m_PlayerOpusDecoder[playerSlot]) + { + int err; + m_PlayerOpusDecoder[playerSlot] = opus_decoder_create(24000, 1, &err); + if (err < 0) + { + smutils->LogError(myself, "PushPlayerVoiceData: opus_decoder_create failed: %s", + opus_strerror(err)); + return; + } + } + + const int OPUS_SAMPLE_RATE = 24000; + const unsigned char *p = (const unsigned char *)data; + + /* + // debugging real steam clients voice packets. + int dumpLen = nBytes < 32 ? nBytes : 32; + char hexBuf[32 * 3 + 1]; + int pos = 0; + for (int i = 0; i < dumpLen; i++) + { + static const char hex[] = "0123456789ABCDEF"; + hexBuf[pos++] = hex[(p[i] >> 4) & 0xF]; + hexBuf[pos++] = hex[p[i] & 0xF]; + hexBuf[pos++] = ' '; + } + hexBuf[pos] = '\0'; + smutils->LogMessage(myself, "Steam packet in PushPlayerVoiceData: nBytes=%d first %d bytes: %s", nBytes, dumpLen, hexBuf); + */ + + //if its not opus packets we dont process it + if (nBytes < 18 || p[8] != 0x0B || (p[11] != 0x05 && p[11] != 0x06)) + return; + + uint16_t totalDataLength; + memcpy(&totalDataLength, p + 12, sizeof(uint16_t)); + if ((int)(14 + totalDataLength + 4) != nBytes) + return; + + uint32_t expectedCRC; + memcpy(&expectedCRC, p + nBytes - 4, sizeof(uint32_t)); + if (UTIL_CRC32(p, nBytes - 4) != expectedCRC) + return; + + int offset = 14; + int end = 14 + (int)totalDataLength; + + if (offset + 4 <= end) + { + uint16_t frameLen; + memcpy(&frameLen, p + offset, sizeof(uint16_t)); + offset += 2; + offset += 2; + + if (frameLen <= 2 || offset + (int)(frameLen - 2) > end) + return; + + const unsigned char *opusFrame = p + offset; + + int16_t pcmBuf[5760]; + int decoded = opus_decode(m_PlayerOpusDecoder[playerSlot], + opusFrame, frameLen - 2, + pcmBuf, 5760, 0); + if (decoded <= 0) + { + smutils->LogError(myself, "PushPlayerVoiceData: opus_decode failed: %s", + opus_strerror(decoded)); + return; + } + + const int STEP = m_CeltEncoderSettings.SampleRate_Hz; + const int DIV = OPUS_SAMPLE_RATE; + + for (int i = 0; i < decoded; i++) + { + m_playerResampleAccum[playerSlot] += STEP; + while (m_playerResampleAccum[playerSlot] >= DIV) + { + m_playerResampleAccum[playerSlot] -= DIV; + int16_t sample = pcmBuf[i]; + if (m_playerVoiceBuffer[playerSlot].CurrentFree() > 0) + m_playerVoiceBuffer[playerSlot].Push(&sample, 1); + } + } + + // If buffer still doesn't have enough for one CELT frame, use Opus PLC to fill + if (m_playerVoiceBuffer[playerSlot].TotalLength() < (size_t)m_CeltEncoderSettings.FrameSize) + { + int16_t plcBuf[5760]; + int plcDecoded = opus_decode(m_PlayerOpusDecoder[playerSlot], + NULL, 0, // NULL = PLC mode + plcBuf, 480, 0); + if (plcDecoded > 0) + { + for (int i = 0; i < plcDecoded; i++) + { + m_playerResampleAccum[playerSlot] += m_CeltEncoderSettings.SampleRate_Hz; + while (m_playerResampleAccum[playerSlot] >= 24000) + { + m_playerResampleAccum[playerSlot] -= 24000; + int16_t sample = plcBuf[i]; + if (m_playerVoiceBuffer[playerSlot].CurrentFree() > 0) + m_playerVoiceBuffer[playerSlot].Push(&sample, 1); + } + } + } + } + } + //smutils->LogMessage(myself, "PushPlayerVoiceData: pushed, bufLen=%d", (int)m_playerVoiceBuffer[playerSlot].TotalLength()); +} + +void CVoice::HandlePlayerVoiceData() +{ + const int CELT_FRAME_SIZE = m_CeltEncoderSettings.FrameSize; + const int CELT_PACKET_SIZE = m_CeltEncoderSettings.PacketSize; + int maxClients = iserver->GetClientCount(); + + for (int playerSlot = 0; playerSlot < maxClients; playerSlot++) + { + if (!m_pCeltCodecPlayer[playerSlot]) + continue; + + if (m_playerVoiceBuffer[playerSlot].TotalLength() < (size_t)CELT_FRAME_SIZE) + continue; + + float timeAvailable = (float)m_playerVoiceBuffer[playerSlot].TotalLength() + / (float)m_CeltEncoderSettings.SampleRate_Hz; + + + //warmup encoder with 5 frames + if (m_playerAvailableTime[playerSlot] == 0.0 && + m_playerVoiceBuffer[playerSlot].TotalLength() >= (size_t)CELT_FRAME_SIZE * 11) + { + for (int w = 0; w < 10; w++) + { + int16_t warmupInput[CELT_FRAME_SIZE]; + if (!m_playerVoiceBuffer[playerSlot].Pop(warmupInput, CELT_FRAME_SIZE)) + break; + unsigned char discard[64]; + celt_encode(m_pCeltCodecPlayer[playerSlot], warmupInput, CELT_FRAME_SIZE, discard, 64); + } + m_playerAvailableTime[playerSlot] = getTime(); + } + + //if (m_playerAvailableTime[playerSlot] < getTime() && timeAvailable < 0.2) + if (m_playerAvailableTime[playerSlot] < getTime() && timeAvailable < 0.05) + continue; + + if (m_playerAvailableTime[playerSlot] > getTime() + 1.0) + continue; + + int framesEmitted = 0; + while (m_playerVoiceBuffer[playerSlot].TotalLength() >= (size_t)CELT_FRAME_SIZE) + { + //smutils->LogMessage(myself, "framesEmitted: %d", framesEmitted); + if (framesEmitted >= 1) + break; + + int16_t celtInput[CELT_FRAME_SIZE]; + if (!m_playerVoiceBuffer[playerSlot].Pop(celtInput, CELT_FRAME_SIZE)) + break; + + unsigned char celtPacket[CELT_PACKET_SIZE]; + int celtBytes = celt_encode(m_pCeltCodecPlayer[playerSlot], celtInput, + CELT_FRAME_SIZE, celtPacket, CELT_PACKET_SIZE); + if (celtBytes > 0) + { + for (int i = 0; i < maxClients; i++) + { + IClient *pToClient = iserver->GetClient(i); + if (!pToClient || !pToClient->IsConnected() || !pToClient->IsActive()) + continue; + if (!g_bIsNoSteam[i + 1]) + continue; + if (g_bClientMuted[i + 1][playerSlot + 1]) + continue; + SendVoiceDataMsg(playerSlot, pToClient, celtPacket, celtBytes, 0); + } + framesEmitted++; + } + else + { + smutils->LogError(myself, "HandlePlayerVoiceData: celt_encode failed: %d", celtBytes); + break; + } + } + + if (framesEmitted > 0) + { + if (m_playerAvailableTime[playerSlot] < getTime()) + m_playerAvailableTime[playerSlot] = getTime(); + m_playerAvailableTime[playerSlot] += (double)framesEmitted * m_CeltEncoderSettings.FrameTime; + } + } +} + +void CVoice::TranscodeNoSteamToSteam(int playerSlot, int nBytes, char *data) +{ + if (playerSlot < 0 || playerSlot >= SM_MAXPLAYERS) + return; + if (!m_pCeltDecoder[playerSlot]) + { + int err; + m_pCeltDecoder[playerSlot] = celt_decoder_create_custom(m_pCeltModePlayer, 1, &err); + if (!m_pCeltDecoder[playerSlot]) + { + smutils->LogError(myself, "TranscodeNoSteamToSteam: celt_decoder_create_custom failed: %d", err); + return; + } + } + + if (!m_nosteamOpusEncoder[playerSlot]) + { + int err; + m_nosteamOpusEncoder[playerSlot] = opus_encoder_create(24000, 1, OPUS_APPLICATION_AUDIO, &err); + if (err < 0) + { + smutils->LogError(myself, "TranscodeNoSteamToSteam: opus_encoder_create failed: %s", opus_strerror(err)); + return; + } + /* + 0x68 TOC -> 01101000 -> c bits (6-7): 00 -> s bit (5): 0 (Mono) -> config bits (0-4): 01101 (13) + Mode: Hybrid (SILK + CELT) + Bandwidth: SWB + Frame Size: 20 ms + BITRATE 640000 changes TOC to 0xD8 + */ + opus_encoder_ctl(m_nosteamOpusEncoder[playerSlot], OPUS_SET_BITRATE(32000)); + opus_encoder_ctl(m_nosteamOpusEncoder[playerSlot], OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE)); + } + + int16_t pcmBuf[512]; + int decoded = celt_decode(m_pCeltDecoder[playerSlot], + (const unsigned char *)data, nBytes, + pcmBuf, m_CeltEncoderSettings.FrameSize); + if (decoded < 0) + { + smutils->LogError(myself, "TranscodeNoSteamToSteam: celt_decode failed: %d", decoded); + return; + } + + // Resample 22050 -> 24000 and push into ring buffer + const int STEP = 24000; + const int DIV = 22050; + + for (int i = 0; i < decoded; i++) + { + m_nosteamResampleAccum[playerSlot] += STEP; + while (m_nosteamResampleAccum[playerSlot] >= DIV) + { + m_nosteamResampleAccum[playerSlot] -= DIV; + int16_t sample = pcmBuf[i]; + if (m_nosteamOpusPCMBuffer[playerSlot].CurrentFree() > 0) + m_nosteamOpusPCMBuffer[playerSlot].Push(&sample, 1); + } + } +} + +void CVoice::HandleNoSteamVoiceData() +{ + int maxClients = iserver->GetClientCount(); + + for (int playerSlot = 0; playerSlot < maxClients; playerSlot++) + { + if (!m_nosteamOpusEncoder[playerSlot]) + continue; + + if (m_nosteamOpusPCMBuffer[playerSlot].TotalLength() < 480) + continue; + + float timeAvailable = (float)m_nosteamOpusPCMBuffer[playerSlot].TotalLength() / 24000.0f; + + if (m_nosteamAvailableTime[playerSlot] < getTime() && timeAvailable < 0.1) + continue; + + if (m_nosteamAvailableTime[playerSlot] > getTime() + 1.0) + continue; + + int framesEmitted = 0; + while (m_nosteamOpusPCMBuffer[playerSlot].TotalLength() >= 480) + { + if (framesEmitted >= 1) + break; + + int16_t opusInput[480]; + if (!m_nosteamOpusPCMBuffer[playerSlot].Pop(opusInput, 480)) + break; + + unsigned char opusBuf[512]; + int opusBytes = opus_encode(m_nosteamOpusEncoder[playerSlot], opusInput, 480, opusBuf, sizeof(opusBuf)); + if (opusBytes < 0) + { + smutils->LogError(myself, "HandleNoSteamVoiceData: opus_encode failed: %s", + opus_strerror(opusBytes)); + break; + } + + IClient *pNoSteamClient = iserver->GetClient(playerSlot); + if (!pNoSteamClient) + continue; + + const char *networkID = pNoSteamClient->GetNetworkIDString(); + uint32_t accountID = 0; + // Parse [U:1:XXXXXXXX] format + const char *numStart = strrchr(networkID, ':'); + if (numStart) + accountID = (uint32_t)atoi(numStart + 1); + + // Build modern Steam voice packet with working routing blocks + unsigned char aFinal[8192]; + size_t FinalSize = 0; + + // Build Steam voice packet header + uint32_t steamID = accountID; + uint32_t steamCommunity = 0x01100001; + memcpy(&aFinal[FinalSize], &steamID, sizeof(uint32_t)); + FinalSize += sizeof(uint32_t); + memcpy(&aFinal[FinalSize], &steamCommunity, sizeof(uint32_t)); + FinalSize += sizeof(uint32_t); + + // 2. Payload Type (1 byte) + aFinal[FinalSize++] = 0x0B; + + // 3. Sample Rate (2 bytes little-endian) + uint16_t sampleRate = 24000; + memcpy(&aFinal[FinalSize], &sampleRate, sizeof(uint16_t)); + FinalSize += sizeof(uint16_t); + + // 4. Codec Type (1 byte) - 0x06 for Opus PLC + aFinal[FinalSize++] = 0x06; + + // 5. Track where Size 1 lives + size_t totalDataLengthOffset = FinalSize; + FinalSize += sizeof(uint16_t); // Reserve 2 bytes + + // 6. Sub-Frame Header size (2 bytes) - MUST be exactly raw opusBytes count + uint16_t frameSize = (uint16_t)opusBytes; + memcpy(&aFinal[FinalSize], &frameSize, sizeof(uint16_t)); + FinalSize += sizeof(uint16_t); + + // 7. Sequence Number (2 bytes) + uint16_t seq = m_nosteamSeqNum[playerSlot]++; + memcpy(&aFinal[FinalSize], &seq, sizeof(uint16_t)); + FinalSize += sizeof(uint16_t); + + // 8. Raw Opus Audio Payload + memcpy(&aFinal[FinalSize], opusBuf, opusBytes); + FinalSize += opusBytes; + + uint16_t totalRemainingBytes = (uint16_t)(sizeof(uint16_t) + sizeof(uint16_t) + opusBytes); + memcpy(&aFinal[totalDataLengthOffset], &totalRemainingBytes, sizeof(uint16_t)); + + // 10. Compute and append CRC32 checksum over the complete array built so far + uint32_t crc = UTIL_CRC32(aFinal, FinalSize); + memcpy(&aFinal[FinalSize], &crc, sizeof(uint32_t)); + FinalSize += sizeof(uint32_t); + + for (int i = 0; i < maxClients; i++) + { + IClient *pToClient = iserver->GetClient(i); + if (!pToClient || !pToClient->IsConnected() || !pToClient->IsActive() || pToClient->IsFakeClient()) + continue; + if (g_bIsNoSteam[i + 1]) + continue; + if (g_bClientMuted[i + 1][playerSlot + 1]) + continue; + // debugging voice packets + /* + int dumpLen = (int)FinalSize < 32 ? (int)FinalSize : 32; + char hexBuf[32 * 3 + 1]; + int pos = 0; + for (int i = 0; i < dumpLen; i++) + { + static const char hex[] = "0123456789ABCDEF"; + hexBuf[pos++] = hex[(aFinal[i] >> 4) & 0xF]; + hexBuf[pos++] = hex[aFinal[i] & 0xF]; + hexBuf[pos++] = ' '; + } + hexBuf[pos] = '\0'; + smutils->LogMessage(myself, "CVoice::HandleNoSteamVoiceData packet: FinalSize=%d first %d bytes: %s", (int)FinalSize, dumpLen, hexBuf); + */ + SendVoiceDataMsg(playerSlot, pToClient, aFinal, (int)FinalSize, 0); + } + framesEmitted++; + } + + if (framesEmitted > 0) + { + if (m_nosteamAvailableTime[playerSlot] < getTime()) + m_nosteamAvailableTime[playerSlot] = getTime(); + m_nosteamAvailableTime[playerSlot] += (double)framesEmitted * (480.0 / 24000.0); + } + } } diff --git a/extension.h b/extension.h index 32f7b9a..d5c46d6 100644 --- a/extension.h +++ b/extension.h @@ -34,6 +34,7 @@ #include #include "smsdk_ext.h" +#include "celt_header.h" #include "ringbuffer.h" /** @@ -139,6 +140,9 @@ public: bool OnBroadcastVoiceData(IClient *pClient, int nBytes, char *data); void ListenSocket(); + void PushPlayerVoiceData(int playerSlot, int nBytes, char *data); + void BroadcastVoiceDataCelt(IClient *pClient, int16_t *pPCM, int nSamples); + void TranscodeNoSteamToSteam(int playerSlot, int nBytes, char *data); private: int m_ListenSocket; @@ -161,16 +165,58 @@ private: OpusEncoder *m_OpusEncoder; - double m_AvailableTime; + // Torchlight transcode state (48000 Hz stereo -> 22050 Hz mono -> CELT) + OpusDecoder *m_TorchlightOpusDecoder; + int16_t m_torchMonoAccum[4096]; + int m_torchMonoAccumLen; + int m_torchResampleAccum; + // Player transcode state (24000 Hz mono -> 22050 Hz mono -> CELT) + OpusDecoder *m_PlayerOpusDecoder[SM_MAXPLAYERS + 1]; + int m_playerResampleAccum[SM_MAXPLAYERS + 1]; + + double m_AvailableTime; + double m_playerAvailableTime[SM_MAXPLAYERS + 1]; + + // NoSteam->Steam transcode state per player + CRingBuffer m_nosteamOpusPCMBuffer[SM_MAXPLAYERS + 1]; + int m_nosteamResampleAccum[SM_MAXPLAYERS + 1]; + OpusEncoder *m_nosteamOpusEncoder[SM_MAXPLAYERS + 1]; + + // CELT encoder (shared by both paths, same output format) + struct CEncoderSettings + { + int32_t SampleRate_Hz; + int32_t TargetBitRate_Kbps; + int32_t FrameSize; + int32_t PacketSize; + int32_t Complexity; + double FrameTime; + } m_CeltEncoderSettings; + + CELTMode *m_pCeltMode; + CELTEncoder *m_pCeltCodec; + + CELTMode *m_pCeltModePlayer; + CELTEncoder *m_pCeltCodecPlayer[SM_MAXPLAYERS + 1]; + + CELTDecoder *m_pCeltDecoder[SM_MAXPLAYERS + 1]; + + CRingBuffer m_playerVoiceBuffer[SM_MAXPLAYERS + 1]; t_SV_BroadcastVoiceData m_SV_BroadcastVoiceData; CDetour *m_VoiceDetour; + CDetour *m_ProcessVoiceDataDetour; + + uint16_t m_nosteamSeqNum[SM_MAXPLAYERS + 1]; + + double m_nosteamAvailableTime[SM_MAXPLAYERS + 1]; void HandleNetwork(); void OnDataReceived(CClient *pClient, int16_t *pData, size_t Samples); void HandleVoiceData(); - void BroadcastVoiceData(IClient *pClient, int nBytes, unsigned char *pData); + void HandlePlayerVoiceData(); + void HandleNoSteamVoiceData(); }; #endif // _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_ diff --git a/inetmessage.h b/inetmessage.h new file mode 100644 index 0000000..aaae7a0 --- /dev/null +++ b/inetmessage.h @@ -0,0 +1,195 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: INetMessage interface +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef INETMESSAGE_FRAEVEN_H +#define INETMESSAGE_FRAEVEN_H + +#include "smsdk_ext.h" +#include "bitbuf.h" +#include "inetchannelinfo.h" + +class INetMsgHandler; +class INetMessage; +class INetChannel; + +// typedef bool (INetMsgHandler::*PROCESSFUNCPTR)(INetMessage*); +// #define CASTPROCPTR( fn ) static_cast (fn) + +class INetMessage +{ +public: + virtual ~INetMessage() {}; + + // Use these to setup who can hear whose voice. + // Pass in client indices (which are their ent indices - 1). + + virtual void SetNetChannel(INetChannel * netchan) = 0; // netchannel this message is from/for + virtual void SetReliable( bool state ) = 0; // set to true if it's a reliable message + + virtual bool Process( void ) = 0; // calles the recently set handler to process this message + + virtual bool ReadFromBuffer( bf_read &buffer ) = 0; // returns true if parsing was OK + virtual bool WriteToBuffer( bf_write &buffer ) = 0; // returns true if writing was OK + + virtual bool IsReliable( void ) const = 0; // true, if message needs reliable handling + + virtual int GetType( void ) const = 0; // returns module specific header tag eg svc_serverinfo + virtual int GetGroup( void ) const = 0; // returns net message group of this message + virtual const char *GetName( void ) const = 0; // returns network message name, eg "svc_serverinfo" + virtual INetChannel *GetNetChannel( void ) const = 0; + virtual const char *ToString( void ) const = 0; // returns a human readable string about message content +}; + +class CNetMessage : public INetMessage +{ +public: + CNetMessage() { m_bReliable = true; + m_NetChannel = NULL; } + + virtual ~CNetMessage() {}; + + virtual int GetGroup() const { return INetChannelInfo::GENERIC; } + INetChannel *GetNetChannel() const { return m_NetChannel; } + + virtual void SetReliable( bool state) {m_bReliable = state;}; + virtual bool IsReliable() const { return m_bReliable; }; + virtual void SetNetChannel(INetChannel * netchan) { m_NetChannel = netchan; } + virtual bool Process() { return false; }; // no handler set + +protected: + bool m_bReliable; // true if message should be send reliable + INetChannel *m_NetChannel; // netchannel this message is from/for +}; + +#define svc_VoiceData 15 + +#define DECLARE_BASE_MESSAGE( msgtype ) \ + public: \ + bool ReadFromBuffer( bf_read &buffer ); \ + bool WriteToBuffer( bf_write &buffer ); \ + const char *ToString() const; \ + int GetType() const { return msgtype; } \ + const char *GetName() const { return #msgtype;}\ + +#define DECLARE_SVC_MESSAGE( name ) \ + DECLARE_BASE_MESSAGE( svc_##name ); \ + IServerMessageHandler *m_pMessageHandler;\ + bool Process() { return m_pMessageHandler->Process##name( this ); }\ + +class SVC_VoiceData : public CNetMessage +{ + DECLARE_SVC_MESSAGE( VoiceData ); + + int GetGroup() const { return INetChannelInfo::VOICE; } + + SVC_VoiceData() { m_bReliable = false; } + + public: + int m_nFromClient; // client who has spoken + bool m_bProximity; + int m_nLength; // data length in bits + uint64 m_xuid; // X360 player ID + + bf_read m_DataIn; + void *m_DataOut; +}; + +#define NETMSG_TYPE_BITS 6 + +bool SVC_VoiceData::WriteToBuffer( bf_write &buffer ) +{ + buffer.WriteUBitLong( GetType(), NETMSG_TYPE_BITS ); + buffer.WriteByte( m_nFromClient ); + buffer.WriteByte( m_bProximity ); + buffer.WriteWord( m_nLength ); + + return buffer.WriteBits( m_DataOut, m_nLength ); +} + +bool SVC_VoiceData::ReadFromBuffer( bf_read &buffer ) +{ + // VPROF( "SVC_VoiceData::ReadFromBuffer" ); + + m_nFromClient = buffer.ReadByte(); + m_bProximity = !!buffer.ReadByte(); + m_nLength = buffer.ReadWord(); + + // if ( IsX360() ) + // { + // m_xuid = buffer.ReadLongLong(); + // } + + m_DataIn = buffer; + return buffer.SeekRelative( m_nLength ); +} + +const char *SVC_VoiceData::ToString(void) const +{ + // Q_snprintf(s_text, sizeof(s_text), "%s: client %i, bytes %i", GetName(), m_nFromClient, Bits2Bytes(m_nLength) ); + // return s_text; + return "idc"; +} + +#define svc_VoiceInit 14 + +class SVC_VoiceInit : public CNetMessage +{ + DECLARE_SVC_MESSAGE( VoiceInit ); + + int GetGroup() const { return INetChannelInfo::SIGNON; } + + SVC_VoiceInit() : m_nSampleRate(0) + { + m_bReliable = true; + memset(m_szVoiceCodec, 0, sizeof(m_szVoiceCodec)); + } + + SVC_VoiceInit(const char *pCodec, int nSampleRate) : m_nSampleRate(nSampleRate) + { + m_bReliable = true; + Q_strncpy(m_szVoiceCodec, pCodec ? pCodec : "", sizeof(m_szVoiceCodec)); + } + +public: + char m_szVoiceCodec[MAX_PATH]; + int m_nSampleRate; +}; + +bool SVC_VoiceInit::WriteToBuffer(bf_write &buffer) +{ + buffer.WriteUBitLong(GetType(), NETMSG_TYPE_BITS); + buffer.WriteString(m_szVoiceCodec); + buffer.WriteShort(m_nSampleRate); + return !buffer.IsOverflowed(); +} + +bool SVC_VoiceInit::ReadFromBuffer(bf_read &buffer) +{ + buffer.ReadString(m_szVoiceCodec, sizeof(m_szVoiceCodec)); + m_nSampleRate = buffer.ReadShort(); + return !buffer.IsOverflowed(); +} + +const char *SVC_VoiceInit::ToString() const +{ + return "SVC_VoiceInit"; +} + +#define clc_VoiceData 10 + +//objdump -d /home/gameservers/css_dev/bin/engine_srv.so | grep -A 15 "_ZN11CGameClient16ProcessVoiceDataEP13CLC_VoiceData" +class CLC_VoiceData +{ +public: + uint8_t _pad[20]; // vtable + CNetMessage base fields + padding up to m_nLength (20 offset) + int m_nLength; + bf_read m_DataIn; + bf_write m_DataOut; + uint64 m_xuid; +}; + +#endif