public | ||
source | ||
.editorconfig | ||
.gitattributes | ||
.gitignore | ||
beans.service | ||
docker-compose.yml | ||
Dockerfile | ||
dub.json | ||
dub.selections.json | ||
LICENSE | ||
README.md |
april-fools-2025
stinky server thats probably really easy to break that shows peoples cursors in places this will surely not get annoying
Running
for development just running dub
should do the trick with a dlang build environment, although script.js
is hardcoded for beans.flashii.net
the following commands are how things are set up in prod, all run by root:
mkdir -p /opt/beans
curl -fsSL https://patchii.net/flashii/april-fools-2025/raw/branch/trunk/docker-compose.yml -o /opt/beans/docker-compose.yml
curl -fsSL https://patchii.net/flashii/april-fools-2025/raw/branch/trunk/beans.service -o /etc/systemd/system/beans.service
systemctl enable beans --now
and that ought to do it, dubiousness and all
Protocol
the protocol is also slightly documented in app.d
, but here's a more thorough overview.
also worth noting is that that all of this is going through an rfc6455 websocket transport using binary data frames.
it is an extremely rudimentary stateful protocol with a primary focus on being compact, seeing as a packet is sent on every mouse movement event.
in this protocol, the server takes the initiative to acknowledge that a client has connected. a client should not sent any packet until it has been assigned an connection id by the server.
Server to Client
S2C packets will always consist out of at least five bytes:
- the first bit of the first byte is used to inform the client of the connection id it has been assigned by the server, this cannot be changed.
- the rest of the first byte and the subsequent three bytes contain an unsigned 31-bit integer.
- the fifth byte contains a flagset.
- if
0x01
is set, the subsequent four bytes will contain the displayed user id for the pointer as an unsigned 32-bit integer. this differs from the connection id and can be set at will. - if
0x02
is set, the subsequent two bytes will contain the X position of the pointer as an unsigned 16-bit integer, starting from the left of the viewport. - if
0x04
is set, the subsequent two bytes will contain the Y position of the pointer as an unsigned 16-bit integer, starting from the top of the viewport. - if
0x08
is set, the subsequent three bytes will contain an RGB colour, with each byte representing each colour in that order. - if
0x10
is set, the pointer should be visible. - if
0x20
is set, the pointer is in a clicking state. 0x40
and0x80
are unused but the server will send broadcast their values if set.
- if
a little visualisation table thingy inspired by the websocket RFC in case that made no sense:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-------------------------------------------------------------+
|S| |
|E| Unique Connection ID |
|L| (4) |
|F| |
+-+-+-+-+-+-+-+-+-----------------------------------------------+
|?|?|C|V|C|Y|X|U| |
|?|?|L|I|O| | |S| User ID, if USRID flag is set |
|?|?|I|S|L|P|P|R| (4) |
|?|?|C|I|O|O|O|I| |
|?|?|K|B|R|S|S|D| |
+-+-+-+-+-+-+-+-+-------------------------------+---------------+
| User ID | Pointer X Position, (2) | Y Position,(2)|
| continued ... | if X POS flag is set | if Y POS set |
+---------------+---------------+---------------+---------------+
| Y Position | Red (1) | Green (1) | Blue (1) |
| continued ... | if COLOR flag is set |
+---------------+---------------+---------------+---------------+
hopefully the combination of the two will make some sense!
Client to Server
C2S packets have an entirely variable size, technically an empty binary data frame is still considered a valid packet.
empty packets will be treated the same as if a single 0x00
byte was sent.
- the first byte contains a flagset that determines the layout of the rest of the packet.
- if any of the upper four bits (
0xF0
) are set, they will be used as a mask for the subsequent byte. if this area is non-zero, the byte MUST be present. - if
0x01
is set, the subsequent four bytes will contain the desired display user id as an unsigned 32-bit integer. - if
0x02
is set, the subsequent two bytes will contain the X position of the pointer as an unsigned 16-bit integer, starting from the left of the viewport. - if
0x04
is set, the subsequent two bytes will contain the Y position of the pointer as an unsigned 16-bit integer, starting from the top of the viewport. - if
0x08
is set, the subsequent three bytes will contain an RGB colour, with each byte representing each colour in that order.
- if any of the upper four bits (
- the second byte, present if any of the upper four bits (
0xF0
) are set of the previous byte are set, contains new values for the connection state such as visibility and click state.- the lower four bits (
0x0F
) cannot be overwritten by the client. - if
0x10
is set, the pointer should be visible. - if
0x20
is set, the pointer is in a clicking state. 0x40
and0x80
are unused but the server will send broadcast their values if set.
- the lower four bits (
another little visualisation table thingy:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-------------------------------+
|U|X|Y|C|?|?|C|V|?|?|C|V|0|0|0|0| |
|S| | |O|?|?|L|S|?|?|O|I|0|0|0|0| User ID, if USRID flag is set |
|R|P|P|L|M|M|M|M|?|?|L|S|0|0|0|0| (4) |
|I|O|O|O|S|S|S|S|?|?|O|I|0|0|0|0| |
|D|S|S|R|K|K|K|K|?|?|R|B|0|0|0|0| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-------------------------------+
| User ID | Pointer X Position, (2) |
| continued ... | if X POS flag is set |
+-------------------------------+---------------+---------------+
| Pointer Y Position, (2) | Red (1) | Green (1) |
| if Y POS flag is set | if COLOR flag is set |
+---------------+---------------+---------------+---------------+
| Blue (1) |
| if COLOR set |
+---------------+
unfortunately that one doesn't have a number of bytes that's a multiple of four. sadness.