commit 2e29696
shrub
·
2026-07-30 23:00:12 +0000 UTC
parent 42d3dc4
implement protocol basics offline mode
6 files changed,
+875,
-2
+40,
-0
1@@ -0,0 +1,40 @@
2+#ifndef MCPROTO_H
3+#define MCPROTO_H
4+
5+#include <stddef.h>
6+
7+#include "types.h"
8+
9+#define MC_PROTOCOL_1_8_9 47
10+
11+typedef struct MCClient MCClient;
12+
13+typedef struct {
14+ const char* username;
15+ const char* profile_uuid;
16+ const char* access_token;
17+} MCAccount;
18+
19+typedef struct {
20+ i32 id;
21+ u8* data;
22+ size_t size;
23+} MCPacket;
24+
25+int mc_client_login(
26+ MCClient** out,
27+ const char* address,
28+ const MCAccount* account,
29+ char* error,
30+ size_t error_size);
31+void mc_client_interrupt(MCClient* client);
32+void mc_client_destroy(MCClient* client);
33+
34+int mc_client_read(MCClient* client, MCPacket* packet, char* error, size_t error_size);
35+int mc_client_write(MCClient* client, i32 packet_id, const void* data, size_t size);
36+void mc_packet_free(MCPacket* packet);
37+
38+const char* mc_client_username(const MCClient* client);
39+const char* mc_client_uuid(const MCClient* client);
40+
41+#endif /* MCPROTO_H */
+4,
-0
1@@ -10,6 +10,7 @@ project(
2 cc = meson.get_compiler('c')
3 math_dep = cc.find_library('m', required: false)
4 sdl_dep = dependency('sdl2')
5+zlib_dep = dependency('zlib')
6 renderer_backend = get_option('renderer')
7
8 sources = [
9@@ -17,6 +18,8 @@ sources = [
10 'source/util.c',
11 'source/world/chunk.c',
12 'source/client/camera.c',
13+ 'source/mcproto/buffer.c',
14+ 'source/mcproto/client.c',
15 'source/platform/platform.c',
16 'vendor/stb_image_c89.c',
17 ]
18@@ -95,6 +98,7 @@ renderer_dep = declare_dependency(
19
20 deps = [
21 sdl_dep,
22+ zlib_dep,
23 mgmath_dep,
24 renderer_dep,
25 ]
+52,
-2
1@@ -1,7 +1,9 @@
2 #include <SDL2/SDL.h>
3+#include <stdio.h>
4
5 #include "SDL_keyboard.h"
6 #include "SDL_timer.h"
7+#include "mcproto/mcproto.h"
8 #include "util.h"
9 #include "platform/platform.h"
10 #include "renderer/renderer.h"
11@@ -50,14 +52,62 @@ static void input(void)
12 if (keys[SDL_SCANCODE_LCTRL])
13 move.y -= 1.0f;
14
15- move = MG_MulV3F(move, 4.0f * dt); /* arbritrary movement speed */
16+ move = MG_MulV3F(move, 8.0f * dt); /* arbritrary movement speed */
17 camera_move(&cam, move);
18 SDL_GetRelativeMouseState(&mx, &my);
19 camera_rotate(&cam, mx * sensitivity, -my * sensitivity);
20 }
21
22-int main(void)
23+static int protocol_poc(const char* address, const char* username)
24 {
25+ MCAccount account;
26+ MCClient* client;
27+ char error[1024];
28+
29+ account.username = username;
30+ account.profile_uuid = NULL;
31+ account.access_token = NULL;
32+ if (!mc_client_login(&client, address, &account, error, sizeof(error))) {
33+ fprintf(stderr, "%s\n", error);
34+ return MG_ERR_EXIT_FAILURE;
35+ }
36+ /*
37+ * offline mode protocol test:
38+ * packet keep alive is play/clientbound 0x00 in protocol 47
39+ *https://prismarinejs.github.io/minecraft-data/protocol/pc/1.8/#play.toClient.types.packet_keep_alive
40+ */
41+ puts("entered play state: printing packet ids and echoing keepalives");
42+ for (;;) {
43+ MCPacket packet;
44+ if (!mc_client_read(client, &packet, error, sizeof(error))) {
45+ fprintf(stderr, "%s\n", error);
46+ mc_client_destroy(client);
47+ return MG_ERR_EXIT_FAILURE;
48+ }
49+ printf("play packet 0x%02x size %lu\n",
50+ packet.id, (unsigned long)packet.size);
51+ if (packet.id == 0x00) {
52+ if (!mc_client_write(client, 0x00, packet.data, packet.size)) {
53+ fprintf(stderr, "failed to echo keepalive\n");
54+ mc_packet_free(&packet);
55+ mc_client_destroy(client);
56+ return MG_ERR_EXIT_FAILURE;
57+ }
58+ puts("echoed keepalive");
59+ }
60+ mc_packet_free(&packet);
61+ }
62+}
63+
64+int main(int argc, char** argv)
65+{
66+ if (argc != 1 && argc != 3) {
67+ fprintf(stderr, "usage: %s [server[:port] username]\n", argv[0]);
68+ return MG_ERR_EXIT_FAILURE;
69+ }
70+ if (argc == 3)
71+ return protocol_poc(argv[1], argv[2]);
72+
73 platform_init(&ctx);
74 platform_window_create(&ctx);
75 renderer_create(&ctx);
+259,
-0
1@@ -0,0 +1,259 @@
2+#include <stdarg.h>
3+#include <stdio.h>
4+#include <stdlib.h>
5+#include <string.h>
6+
7+#include "internal.h"
8+
9+void mc_set_error(char* error, size_t error_size, const char* fmt, ...)
10+{
11+ va_list args;
12+
13+ if (!error || error_size == 0)
14+ return;
15+ va_start(args, fmt);
16+ vsnprintf(error, error_size, fmt, args);
17+ va_end(args);
18+}
19+
20+void bufinit(MCBuffer* buffer)
21+{
22+ memset(buffer, 0, sizeof(*buffer));
23+}
24+
25+void buffree(MCBuffer* buffer)
26+{
27+ free(buffer->data);
28+ memset(buffer, 0, sizeof(*buffer));
29+}
30+
31+int bufreserve(MCBuffer* buffer, size_t extra)
32+{
33+ size_t needed;
34+ size_t capacity;
35+ u8* data;
36+
37+ if (extra > MC_MAX_PACKET_SIZE || buffer->size > MC_MAX_PACKET_SIZE - extra)
38+ return 0;
39+ needed = buffer->size + extra;
40+ if (needed <= buffer->capacity)
41+ return 1;
42+ capacity = buffer->capacity ? buffer->capacity : 128;
43+ while (capacity < needed) {
44+ if (capacity > MC_MAX_PACKET_SIZE / 2) {
45+ capacity = MC_MAX_PACKET_SIZE;
46+ break;
47+ }
48+ capacity *= 2;
49+ }
50+ data = (u8*)realloc(buffer->data, capacity);
51+ if (!data)
52+ return 0;
53+ buffer->data = data;
54+ buffer->capacity = capacity;
55+ return 1;
56+}
57+
58+int bufwrite(MCBuffer* buffer, const void* data, size_t size)
59+{
60+ if (!bufreserve(buffer, size))
61+ return 0;
62+ if (size)
63+ memcpy(buffer->data + buffer->size, data, size);
64+ buffer->size += size;
65+ return 1;
66+}
67+
68+int bufwrite_u8(MCBuffer* buffer, u8 value)
69+{
70+ return bufwrite(buffer, &value, 1);
71+}
72+
73+int bufwrite_u16(MCBuffer* buffer, u16 value)
74+{
75+ u8 data[2];
76+ /*
77+ * minecraft protocol integers are big-endian except VarInt
78+ * https://wikivg.booky.dev/Protocol#Data_types
79+ */
80+ data[0] = (u8)(value >> 8);
81+ data[1] = (u8)value;
82+ return bufwrite(buffer, data, sizeof(data));
83+}
84+
85+int bufwrite_i32(MCBuffer* buffer, i32 value)
86+{
87+ u32 v = (u32)value;
88+ u8 data[4];
89+ data[0] = (u8)(v >> 24);
90+ data[1] = (u8)(v >> 16);
91+ data[2] = (u8)(v >> 8);
92+ data[3] = (u8)v;
93+ return bufwrite(buffer, data, sizeof(data));
94+}
95+
96+int bufwrite_i64(MCBuffer* buffer, i64 value)
97+{
98+ u64 v = (u64)value;
99+ u8 data[8];
100+ int i;
101+ for (i = 0; i < 8; ++i)
102+ data[i] = (u8)(v >> (56 - i * 8));
103+ return bufwrite(buffer, data, sizeof(data));
104+}
105+
106+int bufwrite_f32(MCBuffer* buffer, float value)
107+{
108+ u32 bits;
109+ memcpy(&bits, &value, sizeof(bits));
110+ return bufwrite_i32(buffer, (i32)bits);
111+}
112+
113+int bufwrite_f64(MCBuffer* buffer, double value)
114+{
115+ u64 bits;
116+ memcpy(&bits, &value, sizeof(bits));
117+ return bufwrite_i64(buffer, (i64)bits);
118+}
119+
120+int bufwrite_varint(MCBuffer* buffer, i32 value)
121+{
122+ u32 v = (u32)value;
123+ /*
124+ * minecraft VarInt is 7 data bits per byte (5 bytes max on i32) and high bit means more bytes follow
125+ * https://wikivg.booky.dev/Protocol#VarInt_and_VarLong
126+ */
127+ do {
128+ u8 byte = (u8)(v & 0x7f);
129+ v >>= 7;
130+ if (v)
131+ byte |= 0x80;
132+ if (!bufwrite_u8(buffer, byte))
133+ return 0;
134+ } while (v);
135+ return 1;
136+}
137+
138+int bufwrite_string(MCBuffer* buffer, const char* value)
139+{
140+ size_t size = strlen(value);
141+ if (size > MC_MAX_STRING_SIZE)
142+ return 0;
143+ return bufwrite_varint(buffer, (i32)size) &&
144+ bufwrite(buffer, value, size);
145+}
146+
147+int bufread(MCBuffer* buffer, void* out, size_t size)
148+{
149+ if (size > buffer->size - buffer->cursor)
150+ return 0;
151+ if (size)
152+ memcpy(out, buffer->data + buffer->cursor, size);
153+ buffer->cursor += size;
154+ return 1;
155+}
156+
157+int bufread_u8(MCBuffer* buffer, u8* value)
158+{
159+ return bufread(buffer, value, 1);
160+}
161+
162+int bufread_u16(MCBuffer* buffer, u16* value)
163+{
164+ u8 data[2];
165+ if (!bufread(buffer, data, sizeof(data)))
166+ return 0;
167+ *value = (u16)(((u16)data[0] << 8) | data[1]);
168+ return 1;
169+}
170+
171+int bufread_i32(MCBuffer* buffer, i32* value)
172+{
173+ u8 data[4];
174+ u32 result;
175+ if (!bufread(buffer, data, sizeof(data)))
176+ return 0;
177+ result = ((u32)data[0] << 24) | ((u32)data[1] << 16) |
178+ ((u32)data[2] << 8) | data[3];
179+ *value = (i32)result;
180+ return 1;
181+}
182+
183+int bufread_i64(MCBuffer* buffer, i64* value)
184+{
185+ u8 data[8];
186+ u64 result = 0;
187+ int i;
188+ if (!bufread(buffer, data, sizeof(data)))
189+ return 0;
190+ for (i = 0; i < 8; ++i)
191+ result = (result << 8) | data[i];
192+ *value = (i64)result;
193+ return 1;
194+}
195+
196+int bufread_f32(MCBuffer* buffer, float* value)
197+{
198+ i32 bits;
199+ if (!bufread_i32(buffer, &bits))
200+ return 0;
201+ memcpy(value, &bits, sizeof(*value));
202+ return 1;
203+}
204+
205+int bufread_f64(MCBuffer* buffer, double* value)
206+{
207+ i64 bits;
208+ if (!bufread_i64(buffer, &bits))
209+ return 0;
210+ memcpy(value, &bits, sizeof(*value));
211+ return 1;
212+}
213+
214+int bufread_varint(MCBuffer* buffer, i32* value)
215+{
216+ u32 result = 0;
217+ int shift;
218+
219+ /*
220+ * VarInt is at best 5 bytes for a 32-bit value
221+ * https://wikivg.booky.dev/Protocol#VarInt_and_VarLong
222+ *
223+ * 35 = 7*5
224+ */
225+ for (shift = 0; shift < 35; shift += 7) {
226+ u8 byte;
227+ if (!bufread_u8(buffer, &byte))
228+ return 0;
229+ result |= (u32)(byte & 0x7f) << shift;
230+ if (!(byte & 0x80)) {
231+ *value = (i32)result;
232+ return 1;
233+ }
234+ }
235+ return 0;
236+}
237+
238+int bufread_string(MCBuffer* buffer, char* value, size_t value_size)
239+{
240+ i32 length;
241+ if (!bufread_varint(buffer, &length) || length < 0 ||
242+ (size_t)length >= value_size || (size_t)length > MC_MAX_STRING_SIZE)
243+ return 0;
244+ if (!bufread(buffer, value, (size_t)length))
245+ return 0;
246+ value[length] = '\0';
247+ return 1;
248+}
249+
250+int bufread_bytes(MCBuffer* buffer, const u8** value, size_t* size)
251+{
252+ i32 length;
253+ if (!bufread_varint(buffer, &length) || length < 0 ||
254+ (size_t)length > buffer->size - buffer->cursor)
255+ return 0;
256+ *value = buffer->data + buffer->cursor;
257+ *size = (size_t)length;
258+ buffer->cursor += (size_t)length;
259+ return 1;
260+}
+466,
-0
1@@ -0,0 +1,466 @@
2+#define _POSIX_C_SOURCE 200112L
3+
4+#include <errno.h>
5+#include <netdb.h>
6+#include <stdio.h>
7+#include <stdlib.h>
8+#include <string.h>
9+#include <sys/socket.h>
10+#include <unistd.h>
11+
12+#include <zlib.h>
13+
14+#include "internal.h"
15+
16+#ifndef MSG_NOSIGNAL
17+#define MSG_NOSIGNAL 0
18+#endif
19+
20+static int split_address(
21+ const char* address,
22+ char* host,
23+ size_t host_size,
24+ u16* port,
25+ char* error,
26+ size_t error_size)
27+{
28+ const char* colon = strrchr(address, ':');
29+ const char* end = address + strlen(address);
30+ char* port_end = NULL;
31+ size_t length;
32+ long parsed_port = 25565;
33+
34+ if (address[0] == '[') {
35+ const char* close = strchr(address, ']');
36+ if (!close) {
37+ mc_set_error(error, error_size, "invalid bracketed IPv6 address");
38+ return 0;
39+ }
40+ length = (size_t)(close - address - 1);
41+ address += 1;
42+ if (close[1] == ':')
43+ parsed_port = strtol(close + 2, &port_end, 10);
44+ else if (close[1] != '\0') {
45+ mc_set_error(error, error_size, "invalid address suffix");
46+ return 0;
47+ }
48+ } else if (colon && strchr(address, ':') == colon) {
49+ length = (size_t)(colon - address);
50+ parsed_port = strtol(colon + 1, &port_end, 10);
51+ } else {
52+ length = (size_t)(end - address);
53+ }
54+ if ((port_end && *port_end) || length == 0 || length >= host_size ||
55+ parsed_port < 1 || parsed_port > 65535) {
56+ mc_set_error(error, error_size, "invalid server address");
57+ return 0;
58+ }
59+ memcpy(host, address, length);
60+ host[length] = '\0';
61+ *port = (u16)parsed_port;
62+ return 1;
63+}
64+
65+static int tcp_connect(MCClient* client, char* error, size_t error_size)
66+{
67+ struct addrinfo hints;
68+ struct addrinfo* addresses = NULL;
69+ struct addrinfo* address;
70+ char service[6];
71+ int result;
72+
73+ memset(&hints, 0, sizeof(hints));
74+ hints.ai_family = AF_UNSPEC;
75+ hints.ai_socktype = SOCK_STREAM;
76+ snprintf(service, sizeof(service), "%u", client->port);
77+ result = getaddrinfo(client->host, service, &hints, &addresses);
78+ if (result != 0) {
79+ mc_set_error(error, error_size, "could not resolve %s: %s",
80+ client->host, gai_strerror(result));
81+ return 0;
82+ }
83+ for (address = addresses; address; address = address->ai_next) {
84+ client->socket_fd = socket(
85+ address->ai_family, address->ai_socktype, address->ai_protocol);
86+ if (client->socket_fd < 0)
87+ continue;
88+ if (connect(client->socket_fd, address->ai_addr, address->ai_addrlen) == 0)
89+ break;
90+ close(client->socket_fd);
91+ client->socket_fd = -1;
92+ }
93+ freeaddrinfo(addresses);
94+ if (client->socket_fd < 0) {
95+ mc_set_error(error, error_size, "could not connect to %s:%u: %s",
96+ client->host, client->port, strerror(errno));
97+ return 0;
98+ }
99+ return 1;
100+}
101+
102+static int socket_write(MCClient* client, const u8* data, size_t size)
103+{
104+ size_t sent = 0;
105+ while (sent < size) {
106+ ssize_t count = send(client->socket_fd, data + sent,
107+ size - sent, MSG_NOSIGNAL);
108+ if (count > 0)
109+ sent += (size_t)count;
110+ else if (count < 0 && errno == EINTR)
111+ continue;
112+ else
113+ return 0;
114+ }
115+ return 1;
116+}
117+
118+static int socket_read(MCClient* client, u8* data, size_t size)
119+{
120+ size_t offset = 0;
121+ while (offset < size) {
122+ ssize_t count = recv(client->socket_fd, data + offset, size - offset, 0);
123+ if (count > 0)
124+ offset += (size_t)count;
125+ else if (count < 0 && errno == EINTR)
126+ continue;
127+ else
128+ return 0;
129+ }
130+ return 1;
131+}
132+
133+static int socket_read_varint(MCClient* client, i32* value)
134+{
135+ u32 result = 0;
136+ int shift;
137+ for (shift = 0; shift < 35; shift += 7) {
138+ u8 byte;
139+ if (!socket_read(client, &byte, 1))
140+ return 0;
141+ result |= (u32)(byte & 0x7f) << shift;
142+ if (!(byte & 0x80)) {
143+ *value = (i32)result;
144+ return 1;
145+ }
146+ }
147+ return 0;
148+}
149+
150+int mc_client_write(MCClient* client, i32 packet_id, const void* data, size_t size)
151+{
152+ MCBuffer body;
153+ MCBuffer framed;
154+ MCBuffer length;
155+ u8* compressed = NULL;
156+ uLongf compressed_size;
157+ int ok = 0;
158+
159+ if (!client || client->socket_fd < 0 || size > MC_MAX_PACKET_SIZE)
160+ return 0;
161+ bufinit(&body);
162+ bufinit(&framed);
163+ bufinit(&length);
164+ if (!bufwrite_varint(&body, packet_id) ||
165+ !bufwrite(&body, data, size))
166+ goto cleanup;
167+ /*
168+ * packet layout:
169+ * length VarInt
170+ * [data length VarInt if compression is enabled]
171+ * packet id VarInt
172+ * payload
173+ *
174+ * https://wikivg.booky.dev/Protocol#Packet_format
175+ */
176+ if (client->compression_threshold >= 0) {
177+ /*
178+ * if in compressed mode, data length 0 means this packet body is
179+ * not actually compressed. otherwise it is the size after zlib decompression
180+ * https://wikivg.booky.dev/Protocol#With_compression
181+ */
182+ if (body.size >= (size_t)client->compression_threshold) {
183+ compressed_size = compressBound((uLong)body.size);
184+ compressed = (u8*)malloc(compressed_size);
185+ if (!compressed ||
186+ compress2(compressed, &compressed_size, body.data,
187+ (uLong)body.size, Z_DEFAULT_COMPRESSION) != Z_OK)
188+ goto cleanup;
189+ if (!bufwrite_varint(&framed, (i32)body.size) ||
190+ !bufwrite(&framed, compressed, compressed_size))
191+ goto cleanup;
192+ } else if (!bufwrite_varint(&framed, 0) ||
193+ !bufwrite(&framed, body.data, body.size)) {
194+ goto cleanup;
195+ }
196+ } else if (!bufwrite(&framed, body.data, body.size)) {
197+ goto cleanup;
198+ }
199+ if (bufwrite_varint(&length, (i32)framed.size) &&
200+ socket_write(client, length.data, length.size) &&
201+ socket_write(client, framed.data, framed.size))
202+ ok = 1;
203+
204+cleanup:
205+ free(compressed);
206+ buffree(&length);
207+ buffree(&framed);
208+ buffree(&body);
209+ return ok;
210+}
211+
212+int mc_client_read(MCClient* client, MCPacket* packet, char* error, size_t error_size)
213+{
214+ i32 frame_length;
215+ MCBuffer frame;
216+ MCBuffer decoded;
217+ i32 data_length;
218+ i32 packet_id;
219+ int decoded_allocated = 0;
220+ int ok = 0;
221+
222+ memset(packet, 0, sizeof(*packet));
223+ bufinit(&frame);
224+ bufinit(&decoded);
225+ if (!socket_read_varint(client, &frame_length)) {
226+ mc_set_error(error, error_size, "connection closed while reading packet length");
227+ goto cleanup;
228+ }
229+ if (frame_length <= 0 || (u32)frame_length > MC_MAX_PACKET_SIZE ||
230+ !bufreserve(&frame, (size_t)frame_length)) {
231+ mc_set_error(error, error_size, "invalid packet length: %d", frame_length);
232+ goto cleanup;
233+ }
234+ frame.size = (size_t)frame_length;
235+ if (!socket_read(client, frame.data, frame.size)) {
236+ mc_set_error(error, error_size, "connection closed while reading packet");
237+ goto cleanup;
238+ }
239+ if (client->compression_threshold >= 0) {
240+ /*
241+ * same as before data length 0 means no compression
242+ */
243+ if (!bufread_varint(&frame, &data_length) || data_length < 0 ||
244+ (u32)data_length > MC_MAX_PACKET_SIZE) {
245+ mc_set_error(error, error_size, "invalid compressed packet header");
246+ goto cleanup;
247+ }
248+ if (data_length == 0) {
249+ decoded.data = frame.data + frame.cursor;
250+ decoded.size = frame.size - frame.cursor;
251+ } else {
252+ uLongf output_size = (uLongf)data_length;
253+ if (!bufreserve(&decoded, (size_t)data_length) ||
254+ uncompress(decoded.data, &output_size,
255+ frame.data + frame.cursor,
256+ (uLong)(frame.size - frame.cursor)) != Z_OK ||
257+ output_size != (uLongf)data_length) {
258+ mc_set_error(error, error_size, "invalid zlib packet");
259+ goto cleanup;
260+ }
261+ decoded_allocated = 1;
262+ decoded.size = (size_t)output_size;
263+ }
264+ } else {
265+ decoded.data = frame.data;
266+ decoded.size = frame.size;
267+ }
268+ if (!bufread_varint(&decoded, &packet_id)) {
269+ mc_set_error(error, error_size, "packet has no valid id");
270+ goto cleanup;
271+ }
272+ packet->size = decoded.size - decoded.cursor;
273+ packet->data = (u8*)malloc(packet->size ? packet->size : 1);
274+ if (!packet->data) {
275+ mc_set_error(error, error_size, "out of memory reading packet");
276+ goto cleanup;
277+ }
278+ memcpy(packet->data, decoded.data + decoded.cursor, packet->size);
279+ packet->id = packet_id;
280+ ok = 1;
281+
282+cleanup:
283+ if (decoded_allocated)
284+ buffree(&decoded);
285+ else
286+ decoded.data = NULL;
287+ buffree(&frame);
288+ if (!ok)
289+ mc_packet_free(packet);
290+ return ok;
291+}
292+
293+void mc_packet_free(MCPacket* packet)
294+{
295+ if (!packet)
296+ return;
297+ free(packet->data);
298+ memset(packet, 0, sizeof(*packet));
299+}
300+
301+static int send_handshake(MCClient* client)
302+{
303+ MCBuffer payload;
304+ int ok;
305+ bufinit(&payload);
306+ /*
307+ * handshake to next state 2 switches the connection into login state
308+ */
309+ ok = bufwrite_varint(&payload, MC_PROTOCOL_1_8_9) &&
310+ bufwrite_string(&payload, client->host) &&
311+ bufwrite_u16(&payload, client->port) &&
312+ bufwrite_varint(&payload, 2) &&
313+ mc_client_write(client, 0x00, payload.data, payload.size);
314+ buffree(&payload);
315+ return ok;
316+}
317+
318+static int send_login_start(MCClient* client)
319+{
320+ MCBuffer payload;
321+ int ok;
322+ bufinit(&payload);
323+ ok = bufwrite_string(&payload, client->username) &&
324+ mc_client_write(client, 0x00, payload.data, payload.size);
325+ buffree(&payload);
326+ return ok;
327+}
328+
329+static int login_loop(MCClient* client, char* error, size_t error_size)
330+{
331+ /*
332+ * login state packet ids for protocol 47:
333+ * 0x00 disconnect
334+ * 0x01 encryption request
335+ * 0x02 login success
336+ * 0x03 set compression
337+ *
338+ * https://prismarinejs.github.io/minecraft-data/protocol/pc/1.8/#login.toClient.types
339+ */
340+ for (;;) {
341+ MCPacket packet;
342+ MCBuffer input;
343+ if (!mc_client_read(client, &packet, error, error_size))
344+ return 0;
345+ input.data = packet.data;
346+ input.size = packet.size;
347+ input.capacity = packet.size;
348+ input.cursor = 0;
349+ if (packet.id == 0x00) {
350+ char reason[1024];
351+ if (!bufread_string(&input, reason, sizeof(reason)))
352+ strcpy(reason, "malformed disconnect reason");
353+ mc_set_error(error, error_size, "server disconnected during login: %s", reason);
354+ mc_packet_free(&packet);
355+ return 0;
356+ }
357+ if (packet.id == 0x01) {
358+ mc_set_error(error, error_size,
359+ "server requested online-mode encryption, idfk know what to do");
360+ mc_packet_free(&packet);
361+ return 0;
362+ }
363+ if (packet.id == 0x02) {
364+ char username[64];
365+ if (!bufread_string(&input, client->uuid, sizeof(client->uuid)) ||
366+ !bufread_string(&input, username, sizeof(username))) {
367+ mc_set_error(error, error_size, "malformed login success packet");
368+ mc_packet_free(&packet);
369+ return 0;
370+ }
371+ printf("login success: uuid=%s username=%s\n", client->uuid, username);
372+ mc_packet_free(&packet);
373+ return 1;
374+ }
375+ if (packet.id == 0x03) {
376+ i32 threshold;
377+ if (!bufread_varint(&input, &threshold) || threshold < 0) {
378+ mc_set_error(error, error_size, "invalid compression threshold");
379+ mc_packet_free(&packet);
380+ return 0;
381+ }
382+ client->compression_threshold = threshold;
383+ printf("compression enabled: threshold=%d\n", threshold);
384+ mc_packet_free(&packet);
385+ continue;
386+ }
387+ mc_set_error(error, error_size, "unexpected login packet 0x%02x", packet.id);
388+ mc_packet_free(&packet);
389+ return 0;
390+ }
391+}
392+
393+int mc_client_login(
394+ MCClient** out,
395+ const char* address,
396+ const MCAccount* account,
397+ char* error,
398+ size_t error_size)
399+{
400+ MCClient* client;
401+ size_t username_size;
402+
403+ *out = NULL;
404+ if (error && error_size)
405+ error[0] = '\0';
406+ if (!address || !account || !account->username) {
407+ mc_set_error(error, error_size, "missing server address or account");
408+ return 0;
409+ }
410+ username_size = strlen(account->username);
411+ if (username_size == 0 || username_size > 16) {
412+ mc_set_error(error, error_size, "Minecraft username must be 1 to 16 characters");
413+ return 0;
414+ }
415+ client = (MCClient*)calloc(1, sizeof(*client));
416+ if (!client) {
417+ mc_set_error(error, error_size, "out of memory creating client");
418+ return 0;
419+ }
420+ client->socket_fd = -1;
421+ client->compression_threshold = -1;
422+ memcpy(client->username, account->username, username_size + 1);
423+ printf("connecting to %s as %s\n", address, client->username);
424+ if (!split_address(address, client->host, sizeof(client->host),
425+ &client->port, error, error_size) ||
426+ !tcp_connect(client, error, error_size) ||
427+ !send_handshake(client) ||
428+ !send_login_start(client)) {
429+ if (!error || !error_size || !error[0])
430+ mc_set_error(error, error_size, "could not start Minecraft login");
431+ mc_client_destroy(client);
432+ return 0;
433+ }
434+ printf("sent handshake and login start\n");
435+ if (!login_loop(client, error, error_size)) {
436+ mc_client_destroy(client);
437+ return 0;
438+ }
439+ *out = client;
440+ return 1;
441+}
442+
443+void mc_client_interrupt(MCClient* client)
444+{
445+ if (client && client->socket_fd >= 0)
446+ shutdown(client->socket_fd, SHUT_RDWR);
447+}
448+
449+void mc_client_destroy(MCClient* client)
450+{
451+ if (!client)
452+ return;
453+ if (client->socket_fd >= 0)
454+ close(client->socket_fd);
455+ memset(client, 0, sizeof(*client));
456+ free(client);
457+}
458+
459+const char* mc_client_username(const MCClient* client)
460+{
461+ return client ? client->username : "";
462+}
463+
464+const char* mc_client_uuid(const MCClient* client)
465+{
466+ return client ? client->uuid : "";
467+}
+54,
-0
1@@ -0,0 +1,54 @@
2+#ifndef MCPROTO_INTERNAL_H
3+#define MCPROTO_INTERNAL_H
4+
5+#include <stddef.h>
6+
7+#include "mcproto/mcproto.h"
8+
9+#define MC_MAX_PACKET_SIZE (16u * 1024u * 1024u)
10+#define MC_MAX_STRING_SIZE 32767u
11+
12+typedef struct {
13+ u8* data;
14+ size_t size;
15+ size_t capacity;
16+ /* read offset used by bufread funcs, writes always append at size */
17+ size_t cursor;
18+} MCBuffer;
19+
20+struct MCClient {
21+ int socket_fd;
22+ char host[256];
23+ u16 port;
24+ char username[17];
25+ char uuid[37];
26+ i32 compression_threshold;
27+};
28+
29+void mc_set_error(char* error, size_t error_size, const char* fmt, ...);
30+
31+void bufinit(MCBuffer* buffer);
32+void buffree(MCBuffer* buffer);
33+int bufreserve(MCBuffer* buffer, size_t extra);
34+int bufwrite(MCBuffer* buffer, const void* data, size_t size);
35+int bufwrite_u8(MCBuffer* buffer, u8 value);
36+int bufwrite_u16(MCBuffer* buffer, u16 value);
37+int bufwrite_i32(MCBuffer* buffer, i32 value);
38+int bufwrite_i64(MCBuffer* buffer, i64 value);
39+int bufwrite_f32(MCBuffer* buffer, float value);
40+int bufwrite_f64(MCBuffer* buffer, double value);
41+int bufwrite_varint(MCBuffer* buffer, i32 value);
42+int bufwrite_string(MCBuffer* buffer, const char* value);
43+
44+int bufread(MCBuffer* buffer, void* out, size_t size);
45+int bufread_u8(MCBuffer* buffer, u8* value);
46+int bufread_u16(MCBuffer* buffer, u16* value);
47+int bufread_i32(MCBuffer* buffer, i32* value);
48+int bufread_i64(MCBuffer* buffer, i64* value);
49+int bufread_f32(MCBuffer* buffer, float* value);
50+int bufread_f64(MCBuffer* buffer, double* value);
51+int bufread_varint(MCBuffer* buffer, i32* value);
52+int bufread_string(MCBuffer* buffer, char* value, size_t value_size);
53+int bufread_bytes(MCBuffer* buffer, const u8** value, size_t* size);
54+
55+#endif /* MCPROTO_INTERNAL_H */