Quick answer: Use buffer_write(buf, buffer_string, "...") on the sender and buffer_read(buf, buffer_string) on the receiver. Both sides must agree on type; mixing buffer_string with buffer_text breaks UTF-8.
Two players chat in a GameMaker multiplayer test. ASCII text works. The moment one player types café with an accent, the receiver sees caf? — or worse, a garbled string with off-by-one boundary issues that affect subsequent packet fields.
Buffer Types for Strings
GameMaker provides two string buffer types:
buffer_string— null-terminated UTF-8. Write writes bytes + 0x00; read reads up to 0x00.buffer_text— raw bytes without terminator. Reader must know the length somehow.
For network protocols, buffer_string is almost always what you want. It handles UTF-8 natively (multi-byte characters serialize as their byte sequence) and self-delimits.
The Fix
/// Sender
var buf = buffer_create(256, buffer_grow, 1);
buffer_write(buf, buffer_u8, MSG_CHAT);
buffer_write(buf, buffer_string, "café");
network_send_packet(socket, buf, buffer_tell(buf));
buffer_delete(buf);
/// Receiver
buffer_seek(packet_buffer, buffer_seek_start, 0);
var msg_type = buffer_read(packet_buffer, buffer_u8);
if (msg_type == MSG_CHAT) {
var text = buffer_read(packet_buffer, buffer_string);
show_debug_message(text);
}
Both sides use buffer_string. UTF-8 bytes serialize and deserialize correctly. The accented character round-trips.
Why Mixing Types Breaks
If the sender uses buffer_text (writing raw bytes without terminator) but the receiver uses buffer_string (reading until null), the reader keeps reading past the intended end — into the next field’s bytes — until it happens to find a 0x00. Result: corrupted string, misaligned subsequent reads.
The inverse — sender uses buffer_string, receiver uses buffer_text with hand-coded length — produces an off-by-one because the receiver doesn’t skip the terminator byte.
Length-Prefixed for Fixed-Length Strings
For binary protocols where you don’t want a null terminator:
/// Sender
var s = "café";
var byte_len = string_byte_length(s);
buffer_write(buf, buffer_u16, byte_len);
buffer_write(buf, buffer_text, s); // raw bytes, no terminator
/// Receiver
var byte_len = buffer_read(packet_buffer, buffer_u16);
var raw = buffer_read_text(packet_buffer, byte_len);
string_byte_length returns the UTF-8 encoded length, not the character count. buffer_read_text(buf, n) reads exactly n bytes — safer than buffer_text without a known length.
Validation
Validate received string lengths before allocating:
var byte_len = buffer_read(packet_buffer, buffer_u16);
if (byte_len > 1024) {
show_debug_message("Suspicious string length, dropping packet");
exit;
}
Untrusted clients can send arbitrarily large length fields trying to OOM your server. Cap reads.
Verifying
Test with multi-byte UTF-8: café, こんにちは, 你好, 😀. All should round-trip exactly. Print string_length and string_byte_length on both ends — they should match across the network.
“Both sides agree on buffer_string, or both sides agree on length-prefixed buffer_text. Mixing breaks UTF-8 silently.”
Wrap network protocols in a typed packet builder — centralizes encoding choices so a future ASCII assumption doesn’t leak in.