Custom UDP packet composer/HID data/ Custom binary data parser
Posted: Sun Dec 07, 2025 11:55 pm
I do not know where to put this, but I think it's game related. This is a packet composer module for custom binary UDP packets for low payload, Can set/read individual bits, so I use the same for Hid data bitflags (buttons/hatswitches)
-theres no builtin boundary check, thats up to the user to keep in mind.
-allocates memory with noclear
I'm not sure if anyone will have a good use of it, but here's the module:
And heres an example composer (copy this after the module to make it work):
If you find a bug, please inform me too 
-theres no builtin boundary check, thats up to the user to keep in mind.
-allocates memory with noclear
I'm not sure if anyone will have a good use of it, but here's the module:
Code: Select all
DeclareModule NET
EnableExplicit
#PB_MAX_UDP = 2048
;-HEADER BYTE FLAGS
#_KEEPALIVE = %00000001
#_REQUEST = %00000010
#_ACK = %00000100
#_NACK = %00001000
#_PING = %00010000
#_PONG = %00100000
#_GM = %01000000 ;game manager or game master flag
#_ENCRYPTED = %10000000
;-Packet Structure
Structure pstruct
*address
size.i ;size for received packets, ofset when composing a new packet
sourceconnection.i ;filled if received,0 if composed
sourceip.i ;filled if received,0 if composed
sourceport.i ;filled if received,0 if composed
timestamp.q ;filled when received or sent
timeout.q ;resend time if no response
resent.i ;counter, how many times it has been resent, or processed without ack/nack
error.i ;0 if no error
EndStructure
;-Global variables
Global.i mtu = 1024
Global.i serverport = 5000 ;must be open, portforwarded in your NAT for internet
Global.u appid = $5000 ;optionally, this magic number can be used as an endianness check
Global.i keepalive = 15000 ;Client sends a keepalive packet if sending is absent for keepalive millisecs. Client Nat slot will be alive then.
;RFC 4787 says, that an UDP mapping should not expire in less than 2 mins, but thats not garanteed.
;-Procedure Public declarations, Call these ONLY before starting a server or "joining" with client
Declare.i SetMtu(newMTU.i)
Declare.i SetServerPort(newport.i)
Declare.i SetAppId(newappid.u)
Declare.i SetClientKeepAlive(newtime.i)
Declare.i newpacket(*packet.pstruct)
Declare.i AddSByte(*packet.pstruct,byte.b)
Declare.i AddUByte(*packet.pstruct,byte.a)
Declare.i AddWord(*packet.pstruct,word.w)
Declare.i AddUint16(*packet.pstruct,uint.u)
Declare.i AddLong(*packet.pstruct,long.l)
Declare.i AddQuad(*packet.pstruct,quad.q)
Declare.i AddFloat(*packet.pstruct,float.f)
Declare.i AddDouble(*packet.pstruct,double.d)
Declare.i AddStringAscii(*packet.pstruct,string.s)
Declare.i AddStringUTF8(*packet.pstruct,string.s)
Declare.i AddStringUnicode(*packet.pstruct,string.s)
Declare.i StartFlags(*packet.pstruct)
Declare.i AddFlag(*packet.pstruct,bitposition)
Declare.i ClearFlag(*packet.pstruct,bitposition)
Declare.i SwitchFlag(*packet.pstruct,bitposition)
Declare.i NextPosition(*packet.pstruct)
Declare.i SetPosition(*packet.pstruct,position.i)
Declare.i GetFlag(*packet.pstruct,bitposition.a)
Declare.i FinishFlags(*packet.pstruct)
Declare.b GetSByte(*packet.pstruct)
Declare.a GetUByte(*packet.pstruct)
Declare.w GetWord(*packet.pstruct)
Declare.u GetUint16(*packet.pstruct)
Declare.l GetLong(*packet.pstruct)
Declare.q GetQuad(*packet.pstruct)
Declare.f GetFloat(*packet.pstruct)
Declare.d GetDouble(*packet.pstruct)
Declare.s GetStringAscii(*packet.pstruct)
Declare.s GetStringUTF8(*packet.pstruct)
Declare.s GetStringUnicode(*packet.pstruct)
EndDeclareModule
Module NET
Procedure.i SetMtu(newMTU.i)
net::mtu = newMTU
ProcedureReturn #True
EndProcedure
;sets the server port, call before opening a socket. If not called, default is 5000
Procedure.i SetServerPort(newport.i)
net::serverport = newport
ProcedureReturn #True
EndProcedure
Procedure.i SetAppId(newappid.u)
net::appid = newappid
ProcedureReturn #True
EndProcedure
Procedure.i SetClientKeepAlive(newtime.i)
net::keepalive = newtime
EndProcedure
;PACKET COMPOSITION
Procedure ResetPacket(*packet.pstruct)
If *packet\address : FreeMemory(*packet\address) : EndIf
*packet\address = 0
*packet\size = 0
*packet\sourceconnection = 0
*packet\sourceip = 0
*packet\sourceport = 0
*packet\timestamp = 0
*packet\timeout = 0
*packet\resent = 0
*packet\error = 0
EndProcedure
Procedure NewPacket(*packet.pstruct)
ResetPacket(*packet)
*packet\address = AllocateMemory(net::mtu,#PB_Memory_NoClear)
EndProcedure
;*****************************************************************************************************
;Clears the current byte. It's needed before setting bitflags, as the memory is not cleared when allocated
;*****************************************************************************************************
Procedure StartFlags(*packet.pstruct)
PokeA((*packet\address+*packet\size),0)
EndProcedure
Procedure FinishFlags(*packet.pstruct)
NextPosition(*packet)
EndProcedure
;*****************************************************************************************************
;sets a bit to 1 at current memory position. Bitposition can be 0-7, where 0 is the least significant bit
;*****************************************************************************************************
Procedure AddFlag(*packet.pstruct,bitposition)
Protected original.a , flag.a
If bitposition<0 Or bitposition>7 : ProcedureReturn #False : EndIf
original.a = PeekA(*packet\address+*packet\size)
flag.a = %1<<bitposition
PokeA((*packet\address+*packet\size),original|flag)
EndProcedure
Procedure.i GetFlag(*packet.pstruct,bitposition.a)
Protected original.a , mask.a
If bitposition<0 Or bitposition>7 : ProcedureReturn #False : EndIf
original.a = PeekA(*packet\address+*packet\size)
mask.a = %1<<bitposition
If mask & original : ProcedureReturn 1 : EndIf : ProcedureReturn 0
EndProcedure
;*****************************************************************************************************
;sets a bit to 0 at current memory position. Bitposition can be 0-7, where 0 is the least significant bit
;*****************************************************************************************************
Procedure ClearFlag(*packet.pstruct,bitposition)
Protected original.a , flag.a
If bitposition<0 Or bitposition>7 : ProcedureReturn #False : EndIf
original.a = PeekA(*packet\address+*packet\size)
flag.a = %1<<bitposition
PokeA((*packet\address+*packet\size),original & (~flag))
EndProcedure
;*****************************************************************************************************
;Inverts a bit at current memory position. Bitposition can be 0-7, where 0 is the least significant bit
;*****************************************************************************************************
Procedure SwitchFlag(*packet.pstruct,bitposition)
Protected original.a , flag.a
If bitposition<0 Or bitposition>7 : ProcedureReturn #False : EndIf
original.a = PeekA(*packet\address+*packet\size)
flag.a = %1<<bitposition
PokeA(*packet\address+*packet\size,original!flag)
EndProcedure
;*****************************************************************************************************
;Steps forward one byteposition. This commands exist to step forward a position after flags had been set.
;*****************************************************************************************************
Procedure NextPosition(*packet.pstruct)
*packet\size + 1
EndProcedure
;*****************************************************************************************************
;Sets the byteposition, position can be 0 - (packetsize-1 or MTU-1) Use this only if you know what you
;are doing With it. The final value of size will be the packet size when sending.
;This commands exist, because the parser can be used to parse any kind of binary data besides of packets,
;like HID descriptor or read HID data
;*****************************************************************************************************
Procedure SetPosition(*packet.pstruct,position.i)
*packet\size = position
EndProcedure
;*****************************************************************************************************
;Signed 1 byte integer
;*****************************************************************************************************
Procedure AddSByte(*packet.pstruct,byte.b)
PokeB(*packet\address+*packet\size,byte.b)
*packet\size + 1
EndProcedure
;*****************************************************************************************************
;Signed 1 byte integer
;*****************************************************************************************************
Procedure.b GetSByte(*packet.pstruct)
Protected retval.b
retval.b = PeekB(*packet\address+*packet\size)
*packet\size + 1
ProcedureReturn retval.b
EndProcedure
;*****************************************************************************************************
;Usigned 1 byte integer
;*****************************************************************************************************
Procedure AddUByte(*packet.pstruct,byte.a)
PokeA(*packet\address+*packet\size,byte.a)
*packet\size + 1
EndProcedure
;*****************************************************************************************************
;Unsigned 1 byte integer
;*****************************************************************************************************
Procedure.a GetUByte(*packet.pstruct)
Protected retval.a
retval.a = PeekB(*packet\address+*packet\size)
*packet\size + 1
ProcedureReturn retval.a
EndProcedure
;*****************************************************************************************************
;signed 2 byte integer
;*****************************************************************************************************
Procedure AddWord(*packet.pstruct,word.w)
PokeW(*packet\address+*packet\size,word.w)
*packet\size + 2
EndProcedure
;************************************************************
;Reads a word from the current position
;************************************************************
Procedure.w GetWord(*packet.pstruct)
Protected retval.w
retval.w = PeekW(*packet\address+*packet\size)
*packet\size + 2
ProcedureReturn retval.w
EndProcedure
;*****************************************************************************************************
;unsigned 2 byte integer
;*****************************************************************************************************
Procedure AddUint16(*packet.pstruct,uint.u)
PokeU(*packet\address+*packet\size,uint.u)
*packet\size + 2
EndProcedure
;************************************************************
;Reads an uint16 from the current position
;************************************************************
Procedure.u GetUint16(*packet.pstruct)
Protected retval.u
retval.u = PeekU(*packet\address+*packet\size)
*packet\size + 2
ProcedureReturn retval.u
EndProcedure
;*****************************************************************************************************
;signed 4 byte integer
;*****************************************************************************************************
Procedure AddLong(*packet.pstruct,long.l)
PokeL(*packet\address+*packet\size,long.l)
*packet\size + 2
EndProcedure
;************************************************************
;Reads a long from the current position
;************************************************************
Procedure.l GetLong(*packet.pstruct)
Protected retval.l
retval.l = PeekB(*packet\address+*packet\size)
*packet\size + 2
ProcedureReturn retval.l
EndProcedure
;*****************************************************************************************************
;signed 8 byte integer
;*****************************************************************************************************
Procedure AddQuad(*packet.pstruct,quad.q)
PokeQ(*packet\address+*packet\size,quad.q)
*packet\size + 8
EndProcedure
;************************************************************
;Reads a quad from the current position
;************************************************************
Procedure.q GetQuad(*packet.pstruct)
Protected retval.q
retval.q = PeekQ(*packet\address+*packet\size)
*packet\size + 8
ProcedureReturn retval.q
EndProcedure
;*****************************************************************************************************
;signed 4 byte float
;*****************************************************************************************************
Procedure AddFloat(*packet.pstruct,float.f)
PokeF(*packet\address+*packet\size,float.f)
*packet\size + 4
EndProcedure
;************************************************************
;Reads a float from the current position
;************************************************************
Procedure.f GetFloat(*packet.pstruct)
Protected retval.f
retval.f = PeekF(*packet\address+*packet\size)
*packet\size + 4
ProcedureReturn retval.f
EndProcedure
;*****************************************************************************************************
;signed 8 byte float (double)
;*****************************************************************************************************
Procedure AddDouble(*packet.pstruct,double.d)
PokeD(*packet\address+*packet\size,double.d)
*packet\size + 8
EndProcedure
;************************************************************
;Reads a double from the current position
;************************************************************
Procedure.d GetDouble(*packet.pstruct)
Protected retval.d
retval.d = PeekD(*packet\address+*packet\size)
*packet\size + 8
ProcedureReturn retval.d
EndProcedure
;*****************************************************************************************************
;adds an ascii zerostring to the packet
;*****************************************************************************************************
Procedure AddStringAscii(*packet.pstruct,string.s)
Protected i.i,r.s
For i = 1 To Len(string.s)
r.s=Mid(string,i,1)
PokeA(*packet\address+*packet\size,Asc(r))
*packet\size + 1
Next i
AddSByte(*packet,0)
EndProcedure
;************************************************************
;Reads an Ascii string from the current position
;************************************************************
Procedure.s GetStringAscii(*packet.pstruct)
Protected i.i,r.s,_offset.u
Repeat
i = PeekA(*packet\address+*packet\size+_offset)
r.s=r.s+Chr(i)
_offset+1
Until i = 0 Or _offset => net::mtu-1
*packet\size +_offset
ProcedureReturn r.s
EndProcedure
;*****************************************************************************************************
;adds a UTF8 string to the packet in the format 2byte.u stringbytesize+ 2byte.u charactercount + string data
;*****************************************************************************************************
Procedure AddStringUTF8(*packet.pstruct,string.s)
Protected bytesize.u,length.u
bytesize = StringByteLength(string.s,#PB_UTF8)
length = Len(string)
PokeU(*packet\address+*packet\size,bytesize)
PokeU(*packet\address+*packet\size+2,length)
PokeS(*packet\address+*packet\size+4,string,bytesize,#PB_UTF8)
*packet\size=*packet\size+bytesize+4
EndProcedure
;************************************************************
;Reads an UTF8 string from the current position
;************************************************************
Procedure.s GetStringUTF8(*packet.pstruct)
Protected bytesize.u,length.u,r.s
bytesize.u = PeekU(*packet\address+*packet\size)
length.u = PeekU(*packet\address+*packet\size+2)
r.s = PeekS(*packet\address+*packet\size+4,length,#PB_UTF8)
*packet\size=*packet\size+4+bytesize
ProcedureReturn r.s
EndProcedure
;************************************************************
;adds a Unicode string to the packet in the format 2byte .u stringbytesize+ 2byte charactercount + string data
;************************************************************
Procedure AddStringUnicode(*packet.pstruct,string.s)
Protected bytesize.u,length.u
bytesize = StringByteLength(string.s,#PB_Unicode)
length = Len(string.s)
PokeU(*packet\address+*packet\size,bytesize)
PokeU(*packet\address+*packet\size+2,length)
PokeS(*packet\address+*packet\size+4,string,length,#PB_Unicode)
*packet\size=*packet\size+bytesize+4
EndProcedure
;************************************************************
;Reads a Unicode string from the current position
;************************************************************
Procedure.s GetStringUnicode(*packet.pstruct)
Protected length.u,r.s,bytesize.u
bytesize.u = PeekU(*packet\address+*packet\size)
length.u = PeekU(*packet\address+*packet\size+2)
r.s = PeekS(*packet\address+*packet\size+4,length,#PB_Unicode)
*packet\size=*packet\size+4+bytesize
ProcedureReturn r.s
EndProcedure
;************************************************************
;same as resetpacket with a different name. Don't ask, it will
;be more to this later.
;************************************************************
Procedure DropPacket(*packet.pstruct)
ResetPacket(*packet.pstruct)
EndProcedure
EndModule
Code: Select all
;-=====Example======
DeclareModule SERVER
EnableExplicit
Global alive.a
Global id.i
Global sentpackets.i, receivedpackets.i
Global composer.net::pstruct
Global NewList sentpackets.net::pstruct()
Global NewList receivedpackets.net::pstruct()
Declare Startpublic()
Declare StartLocal()
Declare.i newpacket()
Declare.i AddSByte(byte.b)
Declare.i AddUByte(byte.a)
Declare.i AddWord(word.w)
Declare.i AddUint16(uint.u)
Declare.i AddLong(long.l)
Declare.i AddQuad(quad.q)
Declare.i AddFloat(float.f)
Declare.i AddDouble(double.d)
Declare.i AddStringAscii(string.s)
Declare.i AddStringUTF8(string.s)
Declare.i AddStringUnicode(string.s)
Declare.i AddFlag(bitposition)
Declare.i ClearFlag(bitposition)
Declare.i SwitchFlag(bitposition)
Declare.i NextPosition()
Declare.i SetPosition(position.i)
Declare.i StartFlags()
Declare.i FinishFlags()
Declare.i GetFlag(bitposition.i)
Declare.b GetSByte()
Declare.a GetUByte()
Declare.w GetWord()
Declare.u GetUint16()
Declare.l GetLong()
Declare.q GetQuad()
Declare.f GetFloat()
Declare.d GetDouble()
Declare.s GetStringAscii()
Declare.s GetStringUTF8()
Declare.s GetStringUnicode()
EndDeclareModule
Module SERVER
Procedure.i StartPublic()
If server::alive = #True : ProcedureReturn #False : EndIf
id = CreateNetworkServer(#PB_Any,net::serverport,#PB_Network_UDP|#PB_Network_IPv4)
server::alive=#True
ProcedureReturn #True
EndProcedure
Procedure.i StartLocal()
If server::alive = #True : ProcedureReturn #False : EndIf
id = CreateNetworkServer(#PB_Any,net::serverport,#PB_Network_UDP|#PB_Network_IPv4,"127.0.0.1")
server::alive=#True
ProcedureReturn #True
EndProcedure
;PACKET COMPOSITION
Procedure NewPacket()
net::newpacket(@composer)
EndProcedure
;Signed 1 byte integer
Procedure AddSByte(byte.b)
net::AddSByte(@composer,byte.b)
EndProcedure
;unsigned 1 byte integer
Procedure AddUByte(byte.a)
net::AddUByte(@composer,byte.a)
EndProcedure
;signed 2 byte integer
Procedure AddWord(word.w)
net::addWORD(@composer,word.w)
EndProcedure
;unsigned 2 byte integer
Procedure AddUint16(uint.u)
net::AddUint16(@composer,uint.u)
EndProcedure
;signed 4 byte integer
Procedure AddLong(long.l)
net::AddLong(@composer,long.l)
EndProcedure
;signed 8 byte integer
Procedure AddQuad(quad.q)
net::AddQuad(@composer,quad.q)
EndProcedure
;signed 4 byte float
Procedure AddFloat(float.f)
net::AddFloat(@composer,float.f)
EndProcedure
;signed 8 byte float (double)
Procedure AddDouble(double.d)
net::AddDouble(@composer,double.d)
EndProcedure
;adds an ascii zerostring to the packet
Procedure AddStringAscii(string.s)
net::AddStringAscii(@composer,string.s)
EndProcedure
;adds an utf8 string to the packet in the format 2byte .u stringbytesize + string data
Procedure AddStringUTF8(string.s)
net::AddStringUTF8(@composer,string.s)
EndProcedure
;adds a Unicode string to the packet in the format 2byte .u stringbytesize + string data
Procedure AddStringUnicode(string.s)
net::AddStringUnicode(@composer,string.s)
EndProcedure
Procedure StartFlags()
net::startflags(@composer)
EndProcedure
Procedure FinishFlags()
net::finishflags(@composer)
EndProcedure
Procedure AddFlag(bitposition)
net::AddFlag(@composer,bitposition)
EndProcedure
Procedure ClearFlag(bitposition)
net::ClearFlag(@composer,bitposition)
EndProcedure
Procedure SwitchFlag(bitposition)
net::SwitchFlag(@composer,bitposition)
EndProcedure
Procedure Nextposition()
net::NextPosition(@composer)
EndProcedure
Procedure Setposition(bitposition)
net::Setposition(@composer,bitposition)
EndProcedure
Procedure.i GetFlag(bitposition.i)
ProcedureReturn net::GetFlag(@composer,bitposition)
EndProcedure
Procedure.b GetSbyte()
ProcedureReturn net::Getsbyte(@composer)
EndProcedure
Procedure.a GetUByte()
ProcedureReturn net::GetuByte(@composer)
EndProcedure
Procedure.s GetStringUnicode()
ProcedureReturn net::GetStringUnicode(@composer)
EndProcedure
Procedure.d GetDouble()
ProcedureReturn net::GetDouble(@composer)
EndProcedure
Procedure.l GetLong()
ProcedureReturn net::Getlong(@composer)
EndProcedure
Procedure.s GetStringUTF8()
ProcedureReturn net::GetStringUTF8(@composer)
EndProcedure
Procedure.f GetFloat()
ProcedureReturn net::Getfloat(@composer)
EndProcedure
Procedure.s GetStringAscii()
ProcedureReturn net::GetStringAscii(@composer)
EndProcedure
Procedure.s GetWord()
ProcedureReturn net::GetStringUnicode(@composer)
EndProcedure
Procedure.q GetQuad()
ProcedureReturn net::GetQuad(@composer)
EndProcedure
Procedure.u GetUint16()
ProcedureReturn net::GetUint16(@composer)
EndProcedure
EndModule
;-=============Example, composing a packet
server::newpacket() ;frees previously allocated memory automatically
server::AddUByte(244)
server::AddSByte(-100)
server::AddUint16(214)
server::startflags() ; clears all bits of the current byte, as the memory had been allocated with noclear.
server::AddFlag(0)
server::AddFlag(1)
server::AddFlag(7)
server::Finishflags() ; increase the offset position with 1, as flags do not increase it automatically
server::AddStringAscii("Hello World! (AscII)")
server::AddStringUTF8("Hello World! (UTF8)")
server::AddStringUnicode("Hello World! (Unicode)")
server::adddouble(3.14)
server::addfloat(3.14)
Debug "The composed packets Address is "+Str(server::composer\address)
Debug "and its size is : " + Str(server::composer\size) + " bytes long, ready to send."
;-=============Reading data back in order
Debug " "
Debug "Proofreading some data back:"
server::SetPosition(0)
Debug server::getUByte()
Debug server::getSByte()
Debug server::GetUint16()
Debug server::getflag(0)
Debug server::getflag(2)
server::NextPosition()
Debug server::GetStringascii()
Debug server::GetStringUTF8()
Debug server::GetStringUnicode()
Debug server::GetDouble()
Debug server::getfloat()
Debug "Read Packet size : " + Str(server::composer\size)