Changed Game Link to use libretros Netplay/Netpacket

Netplay is now enabled on all platforms
Required libretro is now 1.17.0
Updated libretro.h to latest version
This commit is contained in:
Maximilian Micko 2024-04-21 10:50:57 +02:00
parent cbda5ea9fd
commit 466e8b1749
11 changed files with 43766 additions and 52540 deletions

View File

@ -49,7 +49,7 @@ SOURCES_CXX := \
ifeq ($(HAVE_NETWORK),1) ifeq ($(HAVE_NETWORK),1)
SOURCES_CXX += \ SOURCES_CXX += \
$(CORE_DIR)/../libretro/net_serial.cpp $(CORE_DIR)/../libretro/netplay_serial.cpp
endif endif
ifneq ($(STATIC_LINKING), 1) ifneq ($(STATIC_LINKING), 1)

View File

@ -1,5 +1,5 @@
DEBUG = 0 DEBUG = 0
HAVE_NETWORK = 0 HAVE_NETWORK = 1
VIDEO_RGB565 = 1 VIDEO_RGB565 = 1
SPACE := SPACE :=

View File

@ -24,135 +24,160 @@
#include "serial_io.h" #include "serial_io.h"
#endif #endif
#include "gbint.h" #include "gbint.h"
#include <string>
#include <cstddef> #include <cstddef>
#include <string>
namespace gambatte { namespace gambatte
{
#if defined(VIDEO_RGB565) || defined(VIDEO_ABGR1555) #if defined(VIDEO_RGB565) || defined(VIDEO_ABGR1555)
typedef uint16_t video_pixel_t; typedef uint16_t video_pixel_t;
#else #else
typedef uint_least32_t video_pixel_t; typedef uint_least32_t video_pixel_t;
#endif #endif
enum { BG_PALETTE = 0, SP1_PALETTE = 1, SP2_PALETTE = 2 }; enum
{
BG_PALETTE = 0,
SP1_PALETTE = 1,
SP2_PALETTE = 2
};
class GB { class GB
public: {
GB(); public:
~GB(); GB();
~GB();
enum LoadFlag {
FORCE_DMG = 1, /**< Treat the ROM as not having CGB support regardless of what its header advertises. */
GBA_CGB = 2, /**< Use GBA intial CPU register values when in CGB mode. */
MULTICART_COMPAT = 4, /**< Use heuristics to detect and support some multicart MBCs disguised as MBC1. */
FORCE_CGB = 8
};
int load(const void *romdata, unsigned size, unsigned flags = 0);
/** Emulates until at least 'samples' stereo sound samples are produced in the supplied buffer,
* or until a video frame has been drawn.
*
* There are 35112 stereo sound samples in a video frame.
* May run for uptil 2064 stereo samples too long.
* EDIT: Due to internal emulator bugs, may in fact run for
* an arbitrary number of samples...
* A stereo sample consists of two native endian 2s complement 16-bit PCM samples,
* with the left sample preceding the right one. Usually casting soundBuf to/from
* short* is OK and recommended. The reason for not using a short* in the interface
* is to avoid implementation-defined behaviour without compromising performance.
*
* Returns early when a new video frame has finished drawing in the video buffer,
* such that the caller may update the video output before the frame is overwritten.
* The return value indicates whether a new video frame has been drawn, and the
* exact time (in number of samples) at which it was drawn.
*
* @param videoBuf 160x144 RGB32 (native endian) video frame buffer or 0
* @param pitch distance in number of pixels (not bytes) from the start of one line to the next in videoBuf.
* @param soundBuf buffer with space >= samples + 2064
* @param soundBufSize actual size of soundBuf buffer
* @param samples in: number of stereo samples to produce, out: actual number of samples produced
* @return sample number at which the video frame was produced. -1 means no frame was produced.
*/
long runFor(gambatte::video_pixel_t *videoBuf, int pitch,
gambatte::uint_least32_t *soundBuf, std::size_t soundBufSize, unsigned &samples);
/** Reset to initial state.
* Equivalent to reloading a ROM image, or turning a Game Boy Color off and on again.
*/
void reset();
/** @param palNum 0 <= palNum < 3. One of BG_PALETTE, SP1_PALETTE and SP2_PALETTE.
* @param colorNum 0 <= colorNum < 4
*/
void setDmgPaletteColor(unsigned palNum, unsigned colorNum, unsigned rgb32);
/** Sets the callback used for getting input state. */ enum LoadFlag
void setInputGetter(InputGetter *getInput); {
FORCE_DMG = 1, /**< Treat the ROM as not having CGB support regardless of
/** Sets the callback used for getting the bootloader data. */ what its header advertises. */
void setBootloaderGetter(bool (*getter)(void *userdata, bool isgbc, uint8_t *data, uint32_t buf_size)); GBA_CGB = 2, /**< Use GBA intial CPU register values when in CGB mode. */
MULTICART_COMPAT = 4, /**< Use heuristics to detect and support some
multicart MBCs disguised as MBC1. */
FORCE_CGB = 8
};
int load(const void *romdata, unsigned size, unsigned flags = 0);
/** Emulates until at least 'samples' stereo sound samples are produced in
* the supplied buffer, or until a video frame has been drawn.
*
* There are 35112 stereo sound samples in a video frame.
* May run for uptil 2064 stereo samples too long.
* EDIT: Due to internal emulator bugs, may in fact run for
* an arbitrary number of samples...
* A stereo sample consists of two native endian 2s complement 16-bit PCM
* samples, with the left sample preceding the right one. Usually casting
* soundBuf to/from short* is OK and recommended. The reason for not using a
* short* in the interface is to avoid implementation-defined behaviour
* without compromising performance.
*
* Returns early when a new video frame has finished drawing in the video
* buffer, such that the caller may update the video output before the frame
* is overwritten. The return value indicates whether a new video frame has
* been drawn, and the exact time (in number of samples) at which it was
* drawn.
*
* @param videoBuf 160x144 RGB32 (native endian) video frame buffer or 0
* @param pitch distance in number of pixels (not bytes) from the start of
* one line to the next in videoBuf.
* @param soundBuf buffer with space >= samples + 2064
* @param soundBufSize actual size of soundBuf buffer
* @param samples in: number of stereo samples to produce, out: actual number
* of samples produced
* @return sample number at which the video frame was produced. -1 means no
* frame was produced.
*/
long runFor(gambatte::video_pixel_t *videoBuf, int pitch,
gambatte::uint_least32_t *soundBuf, std::size_t soundBufSize,
unsigned &samples);
/** Reset to initial state.
* Equivalent to reloading a ROM image, or turning a Game Boy Color off and
* on again.
*/
void reset();
/** @param palNum 0 <= palNum < 3. One of BG_PALETTE, SP1_PALETTE and
* SP2_PALETTE.
* @param colorNum 0 <= colorNum < 4
*/
void setDmgPaletteColor(unsigned palNum, unsigned colorNum, unsigned rgb32);
/** Sets the callback used for getting input state. */
void setInputGetter(InputGetter *getInput);
/** Sets the callback used for getting the bootloader data. */
void setBootloaderGetter(bool (*getter)(void *userdata, bool isgbc,
uint8_t *data, uint32_t buf_size));
#ifdef HAVE_NETWORK #ifdef HAVE_NETWORK
/** Sets the callback used for transferring serial data. */ /** Sets the callback used for transferring serial data. */
void setSerialIO(SerialIO *serial_io); void setSerialIO(SerialIO *serial_io);
#endif #endif
/** Sets the directory used for storing save data. The default is the same directory as the ROM Image file. */
void setSaveDir(const std::string &sdir);
void *savedata_ptr(); /** Sets the directory used for storing save data. The default is the same
unsigned savedata_size(); * directory as the ROM Image file. */
void *rtcdata_ptr(); void setSaveDir(const std::string &sdir);
unsigned rtcdata_size();
/** Returns true if the currently loaded ROM image is treated as having CGB support. */
bool isCgb() const;
/** Returns true if a ROM image is loaded. */
bool isLoaded() const;
void saveState(void *data);
void loadState(const void *data);
size_t stateSize() const;
void setColorCorrection(bool enable); void *savedata_ptr();
void setColorCorrectionMode(unsigned colorCorrectionMode); unsigned savedata_size();
void setColorCorrectionBrightness(float colorCorrectionBrightness); void *rtcdata_ptr();
void setDarkFilterLevel(unsigned darkFilterLevel); unsigned rtcdata_size();
video_pixel_t gbcToRgb32(const unsigned bgr15);
/** Set Game Genie codes to apply to currently loaded ROM image. Cleared on ROM load. /** Returns true if the currently loaded ROM image is treated as having CGB
* @param codes Game Genie codes in format HHH-HHH-HHH;HHH-HHH-HHH;... where H is [0-9]|[A-F] * support. */
*/ bool isCgb() const;
void setGameGenie(const std::string &codes);
/** Set Game Shark codes to apply to currently loaded ROM image. Cleared on ROM load. /** Returns true if a ROM image is loaded. */
* @param codes Game Shark codes in format 01HHHHHH;01HHHHHH;... where H is [0-9]|[A-F] bool isLoaded() const;
*/
void setGameShark(const std::string &codes); void saveState(void *data);
void loadState(const void *data);
size_t stateSize() const;
void setColorCorrection(bool enable);
void setColorCorrectionMode(unsigned colorCorrectionMode);
void setColorCorrectionBrightness(float colorCorrectionBrightness);
void setDarkFilterLevel(unsigned darkFilterLevel);
video_pixel_t gbcToRgb32(const unsigned bgr15);
/** Set Game Genie codes to apply to currently loaded ROM image. Cleared on
* ROM load.
* @param codes Game Genie codes in format HHH-HHH-HHH;HHH-HHH-HHH;... where
* H is [0-9]|[A-F]
*/
void setGameGenie(const std::string &codes);
/** Set Game Shark codes to apply to currently loaded ROM image. Cleared on
* ROM load.
* @param codes Game Shark codes in format 01HHHHHH;01HHHHHH;... where H is
* [0-9]|[A-F]
*/
void setGameShark(const std::string &codes);
void clearCheats();
void clearCheats();
#ifdef __LIBRETRO__ #ifdef __LIBRETRO__
void *vram_ptr() const; void *vram_ptr() const;
void *rambank0_ptr() const; void *rambank0_ptr() const;
void *rambank1_ptr() const; void *rambank1_ptr() const;
void *rambank2_ptr() const; void *rambank2_ptr() const;
void *bankedram_ptr() const; void *bankedram_ptr() const;
void *rombank0_ptr() const; void *rombank0_ptr() const;
void *rombank1_ptr() const; void *rombank1_ptr() const;
void *zeropage_ptr() const; void *zeropage_ptr() const;
void *oamram_ptr() const; void *oamram_ptr() const;
#endif #endif
private: private:
struct Priv; struct Priv;
Priv *const p_; Priv *const p_;
void loadState(const std::string &filepath, bool osdMessage); void loadState(const std::string &filepath, bool osdMessage);
GB(const GB &); GB(const GB &);
GB & operator=(const GB &); GB &operator=(const GB &);
}; };
} } // namespace gambatte
#endif #endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,296 +0,0 @@
#include "net_serial.h"
#include "libretro.h"
#include "gambatte_log.h"
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <netdb.h>
#endif
NetSerial::NetSerial()
: is_stopped_(true)
, is_server_(false)
, port_(12345)
, hostname_()
, server_fd_(-1)
, sockfd_(-1)
, lastConnectAttempt_(0)
{
}
NetSerial::~NetSerial()
{
stop();
}
bool NetSerial::start(bool is_server, int port, const std::string& hostname)
{
stop();
gambatte_log(RETRO_LOG_INFO, "Starting GameLink network %s on %s:%d\n",
is_server ? "server" : "client", hostname.c_str(), port);
is_server_ = is_server;
port_ = port;
hostname_ = hostname;
is_stopped_ = false;
return checkAndRestoreConnection(false);
}
void NetSerial::stop()
{
if (!is_stopped_) {
gambatte_log(RETRO_LOG_INFO, "Stopping GameLink network\n");
is_stopped_ = true;
if (sockfd_ >= 0) {
close(sockfd_);
sockfd_ = -1;
}
if (server_fd_ >= 0) {
close(server_fd_);
server_fd_ = -1;
}
}
}
bool NetSerial::checkAndRestoreConnection(bool throttle)
{
if (is_stopped_) {
return false;
}
if (sockfd_ < 0 && throttle) {
clock_t now = clock();
// Only attempt to establish the connection every 5 seconds
if (((now - lastConnectAttempt_) / CLOCKS_PER_SEC) < 5) {
return false;
}
}
lastConnectAttempt_ = clock();
if (is_server_) {
if (!startServerSocket()) {
return false;
}
if (!acceptClient()) {
return false;
}
} else {
if (!startClientSocket()) {
return false;
}
}
return true;
}
bool NetSerial::startServerSocket()
{
int fd;
struct sockaddr_in server_addr;
if (server_fd_ < 0) {
memset((char *)&server_addr, '\0', sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port_);
server_addr.sin_addr.s_addr = INADDR_ANY;
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
gambatte_log(RETRO_LOG_ERROR, "Error opening socket: %s\n", strerror(errno));
return false;
}
if (bind(fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
gambatte_log(RETRO_LOG_ERROR, "Error on binding: %s\n", strerror(errno));
close(fd);
return false;
}
if (listen(fd, 1) < 0) {
gambatte_log(RETRO_LOG_ERROR, "Error listening: %s\n", strerror(errno));
close(fd);
return false;
}
server_fd_ = fd;
gambatte_log(RETRO_LOG_INFO, "GameLink network server started!\n");
}
return true;
}
bool NetSerial::acceptClient()
{
struct sockaddr_in client_addr;
struct timeval tv;
fd_set rfds;
if (server_fd_ < 0) {
return false;
}
if (sockfd_ < 0) {
int retval;
FD_ZERO(&rfds);
FD_SET(server_fd_, &rfds);
tv.tv_sec = 0;
tv.tv_usec = 0;
if (select(server_fd_ + 1, &rfds, NULL, NULL, &tv) <= 0) {
return false;
}
socklen_t client_len = sizeof(client_addr);
sockfd_ = accept(server_fd_, (struct sockaddr*)&client_addr, &client_len);
if (sockfd_ < 0) {
gambatte_log(RETRO_LOG_ERROR, "Error on accept: %s\n", strerror(errno));
return false;
}
gambatte_log(RETRO_LOG_INFO, "GameLink network server connected to client!\n");
}
return true;
}
bool NetSerial::startClientSocket()
{
int fd;
struct sockaddr_in server_addr;
if (sockfd_ < 0) {
memset((char *)&server_addr, '\0', sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port_);
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
gambatte_log(RETRO_LOG_ERROR, "Error opening socket: %s\n", strerror(errno));
return false;
}
struct hostent* server_hostname = gethostbyname(hostname_.c_str());
if (server_hostname == NULL) {
gambatte_log(RETRO_LOG_ERROR, "Error, no such host: %s\n", hostname_.c_str());
close(fd);
return false;
}
memmove((char*)&server_addr.sin_addr.s_addr, (char*)server_hostname->h_addr, server_hostname->h_length);
if (connect(fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
gambatte_log(RETRO_LOG_ERROR, "Error connecting to server: %s\n", strerror(errno));
close(fd);
return false;
}
sockfd_ = fd;
gambatte_log(RETRO_LOG_INFO, "GameLink network client connected to server!\n");
}
return true;
}
unsigned char NetSerial::send(unsigned char data, bool fastCgb)
{
unsigned char buffer[2];
if (is_stopped_) {
return 0xFF;
}
if (sockfd_ < 0) {
if (!checkAndRestoreConnection(true)) {
return 0xFF;
}
}
buffer[0] = data;
buffer[1] = fastCgb;
#ifdef _WIN32
if (::send(sockfd_, (char*) buffer, 2, 0) <= 0)
#else
if (write(sockfd_, buffer, 2) <= 0)
#endif
{
gambatte_log(RETRO_LOG_ERROR, "Error writing to socket: %s\n", strerror(errno));
close(sockfd_);
sockfd_ = -1;
return 0xFF;
}
#ifdef _WIN32
if (recv(sockfd_, (char*) buffer, 2, 0) <= 0)
#else
if (read(sockfd_, buffer, 2) <= 0)
#endif
{
gambatte_log(RETRO_LOG_ERROR, "Error reading from socket: %s\n", strerror(errno));
close(sockfd_);
sockfd_ = -1;
return 0xFF;
}
return buffer[0];
}
bool NetSerial::check(unsigned char out, unsigned char& in, bool& fastCgb)
{
unsigned char buffer[2];
#ifdef _WIN32
u_long bytes_avail = 0;
#else
int bytes_avail = 0;
#endif
if (is_stopped_) {
return false;
}
if (sockfd_ < 0) {
if (!checkAndRestoreConnection(true)) {
return false;
}
}
#ifdef _WIN32
if (ioctlsocket(sockfd_, FIONREAD, &bytes_avail) < 0)
#else
if (ioctl(sockfd_, FIONREAD, &bytes_avail) < 0)
#endif
{
gambatte_log(RETRO_LOG_ERROR, "IOCTL Failed: %s\n", strerror(errno));
return false;
}
// No data available yet
if (bytes_avail < 2) {
return false;
}
#ifdef _WIN32
if (recv(sockfd_, (char*) buffer, 2, 0) <= 0)
#else
if (read(sockfd_, buffer, 2) <= 0)
#endif
{
gambatte_log(RETRO_LOG_ERROR, "Error reading from socket: %s\n", strerror(errno));
close(sockfd_);
sockfd_ = -1;
return false;
}
// slave_txn_cnt++;
in = buffer[0];
fastCgb = buffer[1];
buffer[0] = out;
buffer[1] = 128;
#ifdef _WIN32
if (::send(sockfd_, (char*) buffer, 2, 0) <= 0)
#else
if (write(sockfd_, buffer, 2) <= 0)
#endif
{
gambatte_log(RETRO_LOG_ERROR, "Error writing to socket: %s\n", strerror(errno));
close(sockfd_);
sockfd_ = -1;
return false;
}
return true;
}

View File

@ -1,41 +0,0 @@
#ifndef _NET_SERIAL_H
#define _NET_SERIAL_H
#if defined(__HAIKU__)
#include <sys/socket.h>
#include <sys/select.h>
#endif
#include <gambatte.h>
#include <time.h>
class NetSerial : public gambatte::SerialIO
{
public:
NetSerial();
~NetSerial();
bool start(bool is_server, int port, const std::string& hostname);
void stop();
virtual bool check(unsigned char out, unsigned char& in, bool& fastCgb);
virtual unsigned char send(unsigned char data, bool fastCgb);
private:
bool startServerSocket();
bool startClientSocket();
bool acceptClient();
bool checkAndRestoreConnection(bool throttle);
bool is_stopped_;
bool is_server_;
int port_;
std::string hostname_;
int server_fd_;
int sockfd_;
clock_t lastConnectAttempt_;
};
#endif

View File

@ -0,0 +1,128 @@
#include "netplay_serial.h"
#include "gambatte_log.h"
#include <string.h>
#include <math.h>
NetplaySerial::NetplaySerial() : dataAvailable_(0), hasPeer_(false)
{
}
NetplaySerial::~NetplaySerial()
{
}
void NetplaySerial::netplayStart(uint16_t clientId, retro_netpacket_send_t sendCallback, retro_netpacket_poll_receive_t pollCallback)
{
gambatte_log(RETRO_LOG_DEBUG, "Starting netplay!\n");
send_cb_ = sendCallback;
poll_cb_ = pollCallback;
}
void NetplaySerial::netplayStop()
{
gambatte_log(RETRO_LOG_DEBUG, "Stopping netplay!\n");
send_cb_ = NULL;
poll_cb_ = NULL;
hasPeer_ = false;
dataAvailable_ = 0;
}
bool NetplaySerial::netplayConnect(uint16_t clientId)
{
if (hasPeer_)
{
gambatte_log(RETRO_LOG_DEBUG, "Client tried to connect but we already have a peer\n");
return false;
}
else
{
gambatte_log(RETRO_LOG_DEBUG, "Client connected\n");
hasPeer_ = true;
return true;
}
}
void NetplaySerial::netplayDisconnect(uint16_t clientId)
{
gambatte_log(RETRO_LOG_DEBUG, "Client disconnected\n");
hasPeer_ = false;
}
void NetplaySerial::netplayReceive(const void *buf, size_t len, uint16_t client_id)
{
if (dataAvailable_ + len > NETPLAY_SERIAL_BUFFER_SIZE)
{
gambatte_log(RETRO_LOG_WARN, "[receive] buffer full, dropping packets...\n");
return;
}
memcpy(&buffer_[dataAvailable_], buf, len);
dataAvailable_ += len;
}
bool NetplaySerial::check(unsigned char out, unsigned char &in, bool &fastCgb)
{
if (!isAvailable_() || dataAvailable_ < 2)
{
return false;
}
int read;
unsigned char buffer[2];
if ((read = getData_(&buffer, 2)) <= 0)
{
gambatte_log(RETRO_LOG_ERROR, "[check] Error during receive (read=%d)!\n", read);
return false;
}
in = buffer[0];
fastCgb = buffer[1];
buffer[0] = out;
buffer[1] = 128;
send_cb_(NETPLAY_SERIAL_PACKET_FLAGS, buffer, 2, RETRO_NETPACKET_BROADCAST);
return true;
}
unsigned char NetplaySerial::send(unsigned char data, bool fastCgb)
{
if (!isAvailable_())
{
return 0xFF;
}
unsigned char buffer[2];
buffer[0] = data;
buffer[1] = fastCgb;
send_cb_(NETPLAY_SERIAL_PACKET_FLAGS, buffer, 2, RETRO_NETPACKET_BROADCAST);
// Poll data to wait for response
while (!hasData_() && isAvailable_()) // TODO timeout
{
poll_cb_();
}
int read;
if ((read = getData_(&buffer, 2)) < 0)
{
gambatte_log(RETRO_LOG_ERROR, "[send] Received invalid data length (%d)\n", read);
return 0xFF;
}
return buffer[0];
}
bool NetplaySerial::isAvailable_()
{
return send_cb_ != NULL && poll_cb_ != NULL;
}
bool NetplaySerial::hasData_()
{
return dataAvailable_ > 0;
}
int NetplaySerial::getData_(void *buf, size_t len)
{
if (len > dataAvailable_ || len <= 0)
{
return -1;
}
int avail = len > dataAvailable_ ? dataAvailable_ : len;
memcpy(buf, &buffer_, avail);
memmove(&buffer_[0], &buffer_[avail], dataAvailable_ - avail);
dataAvailable_ -= avail;
return avail;
}

View File

@ -0,0 +1,37 @@
#ifndef _NETPLAY_SERIAL_H
#define _NETPLAY_SERIAL_H
#include <gambatte.h>
#include <libretro.h>
#include <time.h>
#define NETPLAY_SERIAL_PACKET_FLAGS \
RETRO_NETPACKET_FLUSH_HINT | RETRO_NETPACKET_RELIABLE
#define NETPLAY_SERIAL_BUFFER_SIZE 128
class NetplaySerial : public gambatte::SerialIO {
public:
NetplaySerial();
~NetplaySerial();
void netplayStart(uint16_t clientId, retro_netpacket_send_t sendCallback,
retro_netpacket_poll_receive_t pollCallback);
void netplayStop();
bool netplayConnect(uint16_t clientId);
void netplayDisconnect(uint16_t clientId);
void netplayReceive(const void *buf, size_t len, uint16_t client_id);
virtual bool check(unsigned char out, unsigned char &in, bool &fastCgb);
virtual unsigned char send(unsigned char data, bool fastCgb);
private:
bool isAvailable_();
bool hasData_();
int getData_(void *buf, size_t len);
retro_netpacket_send_t send_cb_;
retro_netpacket_poll_receive_t poll_cb_;
bool hasPeer_;
char buffer_[NETPLAY_SERIAL_BUFFER_SIZE]; // Buffer to store received bytes
uint16_t dataAvailable_;
};
#endif