SDL_net: api: Add NET_GetAddressBytes().

From ed0b9a46a6d6ddacc64c9f2c757a37809884d60a Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Fri, 22 May 2026 12:38:59 -0400
Subject: [PATCH] api: Add NET_GetAddressBytes().

As requested via tweet.  :)
---
 include/SDL3_net/SDL_net.h | 43 ++++++++++++++++++++++++++++++++++++++
 src/SDL_net.c              | 28 +++++++++++++++++++++++++
 src/SDL_net.exports        |  1 +
 src/SDL_net.sym            |  1 +
 src/SDL_net_stub_only.c    |  1 +
 5 files changed, 74 insertions(+)

diff --git a/include/SDL3_net/SDL_net.h b/include/SDL3_net/SDL_net.h
index 5927befe..068a34ce 100644
--- a/include/SDL3_net/SDL_net.h
+++ b/include/SDL3_net/SDL_net.h
@@ -415,6 +415,49 @@ extern SDL_DECLSPEC NET_Status SDLCALL NET_GetAddressStatus(NET_Address *address
  */
 extern SDL_DECLSPEC const char * SDLCALL NET_GetAddressString(NET_Address *address);
 
+/**
+ * Get the protocol-level bytes of a network address from a resolved address.
+ *
+ * This data is not human-readable, is protocol-specific, and might not even
+ * be in a specific byte order.
+ *
+ * This is only useful for possibly hashing, to map a address to a specific
+ * player in a game, or possibly for handing to a system-level networking API
+ * (which is _not_ recommended; an app does this at their own risk).
+ *
+ * Do not store these bytes for future runs of the program; there is no
+ * promise the format won't change.
+ *
+ * On return `*num_bytes` will hold the number of bytes provided with the
+ * address. Since the data is not NULL-terminated, this is the only way to
+ * determine its size; as such, this parameter must not be NULL.
+ *
+ * Do not free or modify the returned data; it belongs to the NET_Address
+ * that was queried, and is valid as long as the object lives. Either make
+ * sure the address has a reference as long as you need this or make a copy of
+ * the bytes.
+ *
+ * This will return NULL if resolution is still in progress, or if resolution
+ * failed. You can use NET_GetAddressStatus() or NET_WaitUntilResolved() to
+ * make sure resolution has successfully completed before calling this.
+ *
+ * A human-readable version is available in NET_GetAddressString() and isn't
+ * any less efficient to query than the raw bytes.
+ *
+ * \param address The NET_Address to query.
+ * \param num_bytes on return, will be set to the number of bytes returned.
+ * \returns a pointer to bytes, or NULL on error; call SDL_GetError() for details.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL_net 3.0.0.
+ *
+ * \sa NET_GetAddressString
+ * \sa NET_GetAddressStatus
+ * \sa NET_WaitUntilResolved
+ */
+extern SDL_DECLSPEC const void * SDLCALL NET_GetAddressBytes(NET_Address *address, int *num_bytes);
+
 /**
  * Add a reference to an NET_Address.
  *
diff --git a/src/SDL_net.c b/src/SDL_net.c
index 6bc98628..1b55ff16 100644
--- a/src/SDL_net.c
+++ b/src/SDL_net.c
@@ -1253,6 +1253,34 @@ const char *NET_GetAddressString(NET_Address *addr)
     return retval;
 }
 
+const void * NET_GetAddressBytes(NET_Address *addr, int *num_bytes)
+{
+    if (!num_bytes) {
+        SDL_InvalidParamError("num_bytes");
+        return NULL;
+    }
+
+    *num_bytes = 0;
+
+    if (!addr) {
+        SDL_InvalidParamError("address");
+        return NULL;
+    }
+
+    const void *retval = NULL;
+    const NET_Status rc = NET_GetAddressStatus(addr);
+    if (rc == NET_SUCCESS) {
+        const struct addrinfo *ainfo = addr->ainfo;
+        SDL_assert(ainfo != NULL);
+        retval = (const void *) ainfo->ai_addr;
+        *num_bytes = (int) ainfo->ai_addrlen;
+    } else if (rc != NET_FAILURE) {  // if NET_FAILURE, it'll set the error message.
+        SDL_SetError("Address not yet resolved");
+    }
+
+    return retval;
+}
+
 int NET_CompareAddresses(const NET_Address *sdlneta, const NET_Address *sdlnetb)
 {
     if (sdlneta == sdlnetb) {  // same pointer?
diff --git a/src/SDL_net.exports b/src/SDL_net.exports
index ef3bf338..d1c52d9b 100644
--- a/src/SDL_net.exports
+++ b/src/SDL_net.exports
@@ -32,4 +32,5 @@ _NET_WaitUntilInputAvailable
 _NET_WaitUntilResolved
 _NET_WaitUntilStreamSocketDrained
 _NET_WriteToStreamSocket
+_NET_GetAddressBytes
 # extra symbols go here (don't modify this line)
diff --git a/src/SDL_net.sym b/src/SDL_net.sym
index 53c9c9c1..07108897 100644
--- a/src/SDL_net.sym
+++ b/src/SDL_net.sym
@@ -33,6 +33,7 @@ SDL3_net_0.0.0 {
     NET_WaitUntilResolved;
     NET_WaitUntilStreamSocketDrained;
     NET_WriteToStreamSocket;
+    NET_GetAddressBytes;
     # extra symbols go here (don't modify this line)
   local: *;
 };
diff --git a/src/SDL_net_stub_only.c b/src/SDL_net_stub_only.c
index c2350221..c2313264 100644
--- a/src/SDL_net_stub_only.c
+++ b/src/SDL_net_stub_only.c
@@ -29,6 +29,7 @@ NET_Address * NET_ResolveHostname(const char *host) { SDL_Unsupported(); return
 NET_Status NET_WaitUntilResolved(NET_Address *address, Sint32 timeout) { SDL_Unsupported(); return NET_FAILURE; }
 NET_Status NET_GetAddressStatus(NET_Address *address) { SDL_Unsupported(); return NET_FAILURE; }
 const char * NET_GetAddressString(NET_Address *address) { SDL_Unsupported(); return NULL; }
+const void * NET_GetAddressBytes(NET_Address *address, int *num_bytes) { if (num_bytes) { *num_bytes = 0; } SDL_Unsupported(); return NULL; }
 NET_Address *NET_RefAddress(NET_Address *address) { SDL_Unsupported(); return NULL; }
 void NET_UnrefAddress(NET_Address *address) {}
 void NET_SimulateAddressResolutionLoss(int percent_loss) {}