commit 8867659

uint  ·  2026-08-05 01:04:54 +0000 UTC
parent f4c5c63
reorder functions, add comments above function impl
14 files changed,  +603, -489
M TODO
M TODO
+2, -1
 1@@ -1,8 +1,9 @@
 2 uint:
 3 [ ] Clean up test_scene.h
 4 [ ] Consolidate variable initaliasation methods
 5-[ ] mc_proto, mg_random type cleanup (int->i8 on bool)
 6+[ ] mc_*, mg_random type cleanup (int->i8 on bool)
 7 [ ] Review `block`
 8+[ ] Small3DLib inline?? check ANSI
 9 
10 [-] Face Hiding
11 [-] Chunk meshing
+4, -0
 1@@ -9,6 +9,8 @@
 2 static Chunk* world_chunk_find(ClientWorld* world, i32 x, i32 z);
 3 static Chunk* world_chunk_get(ClientWorld* world, i32 x, i32 z);
 4 
 5+/* find chunk in world by coordinates
 6+   returns NULL if not found */
 7 static Chunk* world_chunk_find(ClientWorld* world, i32 x, i32 z)
 8 {
 9 	i32 i;
10@@ -24,6 +26,8 @@ static Chunk* world_chunk_find(ClientWorld* world, i32 x, i32 z)
11 	return NULL;
12 }
13 
14+/* get chunk from world by coordinates
15+   returns NULL if not found and no free chunk available */
16 static Chunk* world_chunk_get(ClientWorld* world, i32 x, i32 z)
17 {
18 	Chunk* chunk;
+9, -1
 1@@ -37,6 +37,7 @@ static void handle_input(void);
 2 static i8   handle_server_packets(void);
 3 static void render_world(void);
 4 
 5+/* create local fallback world */
 6 static void create_local_world(void)
 7 {
 8 	Chunk* chunk;
 9@@ -62,6 +63,7 @@ static void create_local_world(void)
10 	}
11 }
12 
13+/* destroy game resources */
14 static void game_dies(void)
15 {
16 	if (game.client)
17@@ -73,6 +75,7 @@ static void game_dies(void)
18 	platform_shutdown(&game.ctx);
19 }
20 
21+/* initialise game */
22 static void game_init(int argc, char** argv)
23 {
24 	MCAccount account;
25@@ -121,6 +124,7 @@ static void game_init(int argc, char** argv)
26 		create_local_world();
27 }
28 
29+/* run main loop */
30 static void game_run(void)
31 {
32 	while (platform_poll(&game.ctx)) {
33@@ -141,6 +145,7 @@ static void game_run(void)
34 	}
35 }
36 
37+/* handle decoded chunk packet */
38 static void handle_chunk_packet(const MCPacket* packet)
39 {
40 	MCChunkPacket chunk;
41@@ -175,6 +180,7 @@ static void handle_chunk_packet(const MCPacket* packet)
42 	mc_chunk_destroy(&chunk);
43 }
44 
45+/* handle local input */
46 static void handle_input(void)
47 {
48 	static u32 last;
49@@ -223,6 +229,8 @@ static void handle_input(void)
50 	camera_rotate(&game.cam, mx * sensitivity, -my * sensitivity);
51 }
52 
53+/* handle pending server packets
54+   returns 1 to continue, 0 to stop */
55 static i8 handle_server_packets(void)
56 {
57 	MCPacket packet;
58@@ -238,7 +246,6 @@ static i8 handle_server_packets(void)
59 		if (poll_result <= 0)
60 			break;
61 
62-		printf("packet 0x%02x size %lu\n", packet.id, packet.size);
63 		switch (packet.id) {
64 		case 0x00:
65 			if (!mc_client_write(game.client, 0x00, packet.data, packet.size))
66@@ -268,6 +275,7 @@ static i8 handle_server_packets(void)
67 	return 1;
68 }
69 
70+/* render loaded world */
71 static void render_world(void)
72 {
73 	MGMat4 proj;
+13, -0
 1@@ -12,6 +12,13 @@
 2 #define MC_JOIN_PORT "443"
 3 #define MC_JOIN_PATH "/session/minecraft/join"
 4 
 5+static long http_status(const MCBuffer* response);
 6+static int  json_string(MCBuffer* buffer, const char* value);
 7+static int  tls_read_response(struct tls* tls, MCBuffer* response);
 8+static int  tls_write_all(struct tls* tls, const void* data, size_t size);
 9+
10+/* write escaped JSON string
11+   returns 1 on success, 0 on failure */
12 static int json_string(MCBuffer* buffer, const char* value)
13 {
14 	const unsigned char* p = (const unsigned char*)value;
15@@ -72,6 +79,8 @@ static int json_string(MCBuffer* buffer, const char* value)
16 	return bufwrite_u8(buffer, '"');
17 }
18 
19+/* write all bytes to TLS connection
20+   returns 1 on success, 0 on failure */
21 static int tls_write_all(struct tls* tls, const void* data, size_t size)
22 {
23 	const u8* bytes = (const u8*)data;
24@@ -88,6 +97,8 @@ static int tls_write_all(struct tls* tls, const void* data, size_t size)
25 	return 1;
26 }
27 
28+/* read TLS response
29+   returns 1 on success, 0 on failure */
30 static int tls_read_response(struct tls* tls, MCBuffer* response)
31 {
32 	u8 data[2048];
33@@ -107,6 +118,8 @@ static int tls_read_response(struct tls* tls, MCBuffer* response)
34 	}
35 }
36 
37+/* parse HTTP status from response
38+   returns status code, or 0 on failure */
39 static long http_status(const MCBuffer* response)
40 {
41 	long status;
+42, -39
 1@@ -10,47 +10,12 @@
 2 #define MC_CHUNK_SECTION_LIGHT_BYTES 2048
 3 #define MC_CHUNK_BIOME_BYTES 256
 4 
 5-static i8  mc_chunk_decode_single(MCChunkPacket* chunk, PacketReader* reader);
 6 static i8  mc_chunk_decode_bulk(MCChunkPacket* chunk, PacketReader* reader);
 7+static i8  mc_chunk_decode_single(MCChunkPacket* chunk, PacketReader* reader);
 8 static i32 mc_chunk_section_count(u16 mask);
 9 
10-/* decode single chunk data from packet */
11-static i8 mc_chunk_decode_single(MCChunkPacket* chunk, PacketReader* reader)
12-{
13-	MCChunkUpdate* update;
14-	i32            declared_size;
15-	u8             ground_up;
16-
17-	update = &chunk->single_update;
18-
19-	/* read chunk coordinates, mask, size */
20-	if (!packet_read_i32(reader, &update->x) || !packet_read_i32(reader, &update->z) ||
21-	    !packet_read_u8(reader, &ground_up) || !packet_read_u16(reader, &update->mask) ||
22-	    !packet_read_varint(reader, &declared_size)) {
23-		mg_log(LOG_ERR, "Malformed Chunk Data packet");
24-		return 0;
25-	}
26-
27-	if (declared_size < 0 || (size_t)declared_size > reader->size - reader->cursor)
28-		return 0;
29-
30-	/* if ground_up set and mask is 0, set chunk for unload */
31-	if (ground_up && update->mask == 0) {
32-		chunk->unload = 1;
33-		chunk->unload_x = update->x;
34-		chunk->unload_z = update->z;
35-		return 1;
36-	}
37-
38-	update->replace = ground_up;
39-	chunk->updates = update;
40-	chunk->update_count = 1;
41-	chunk->data = reader->data + reader->cursor;
42-	chunk->data_size = declared_size;
43-	return 1;
44-}
45-
46-/* decode bulk chunk data from packet */
47+/* decode bulk chunk data from packet
48+   returns 1 on success, 0 on failure */
49 static i8 mc_chunk_decode_bulk(MCChunkPacket* chunk, PacketReader* reader)
50 {
51 	i32 update;
52@@ -86,7 +51,45 @@ static i8 mc_chunk_decode_bulk(MCChunkPacket* chunk, PacketReader* reader)
53 	return 1;
54 }
55 
56-/* count number of chunk sections mask */
57+/* decode single chunk data from packet
58+   returns 1 on success, 0 on failure */
59+static i8 mc_chunk_decode_single(MCChunkPacket* chunk, PacketReader* reader)
60+{
61+	MCChunkUpdate* update;
62+	i32            declared_size;
63+	u8             ground_up;
64+
65+	update = &chunk->single_update;
66+
67+	/* read chunk coordinates, mask, size */
68+	if (!packet_read_i32(reader, &update->x) || !packet_read_i32(reader, &update->z) ||
69+	    !packet_read_u8(reader, &ground_up) || !packet_read_u16(reader, &update->mask) ||
70+	    !packet_read_varint(reader, &declared_size)) {
71+		mg_log(LOG_ERR, "Malformed Chunk Data packet");
72+		return 0;
73+	}
74+
75+	if (declared_size < 0 || (size_t)declared_size > reader->size - reader->cursor)
76+		return 0;
77+
78+	/* if ground_up set and mask is 0, set chunk for unload */
79+	if (ground_up && update->mask == 0) {
80+		chunk->unload = 1;
81+		chunk->unload_x = update->x;
82+		chunk->unload_z = update->z;
83+		return 1;
84+	}
85+
86+	update->replace = ground_up;
87+	chunk->updates = update;
88+	chunk->update_count = 1;
89+	chunk->data = reader->data + reader->cursor;
90+	chunk->data_size = declared_size;
91+	return 1;
92+}
93+
94+/* count number of chunk sections mask
95+   returns section count */
96 static i32 mc_chunk_section_count(u16 mask)
97 {
98 	i32 section;
+247, -220
  1@@ -18,7 +18,251 @@
  2 	#define MSG_NOSIGNAL 0
  3 #endif
  4 
  5-/* parse into host and port  + validate */
  6+static int handle_encryption(MCClient* client, const MCAccount* account, MCPacket* packet, char* error,
  7+                             size_t error_size);
  8+static int login_loop(MCClient* client, const MCAccount* account, char* error, size_t error_size);
  9+static int send_handshake(MCClient* client);
 10+static int send_login_start(MCClient* client);
 11+static int socket_read(MCClient* client, u8* data, size_t size);
 12+static int socket_read_varint(MCClient* client, i32* value);
 13+static int socket_write(MCClient* client, const u8* data, size_t size);
 14+static int split_address(const char* address, char* host, size_t host_size, u16* port, char* error, size_t error_size);
 15+static int tcp_connect(MCClient* client, char* error, size_t error_size);
 16+
 17+/* handle server encryption request
 18+   returns 1 on success, 0 on failure */
 19+static int handle_encryption(MCClient* client, const MCAccount* account, MCPacket* packet, char* error,
 20+                             size_t error_size)
 21+{
 22+	MCBuffer  input = { packet->data, packet->size, packet->size, 0 };
 23+	MCBuffer  response;
 24+	char      server_id[256];
 25+	const u8* public_key;
 26+	const u8* verify_token;
 27+	size_t    public_key_size;
 28+	size_t    verify_token_size;
 29+	u8        shared_secret[16];
 30+	u8*       encrypted_secret = NULL;
 31+	u8*       encrypted_token = NULL;
 32+	size_t    encrypted_secret_size = 0;
 33+	size_t    encrypted_token_size = 0;
 34+	int       ok = 0;
 35+
 36+	/* parse the server encryption request */
 37+	if (!bufread_string(&input, server_id, sizeof(server_id)) ||
 38+	    !bufread_bytes(&input, &public_key, &public_key_size) ||
 39+	    !bufread_bytes(&input, &verify_token, &verify_token_size)) {
 40+		mc_set_error(MC_EC_MALFORMED_PACKET, error, error_size, "malformed encryption request");
 41+		return 0;
 42+	}
 43+	/* create the AES shared secret for the connection */
 44+	if (!mg_random_bytes(shared_secret, sizeof(shared_secret))) {
 45+		mc_set_error(MC_EC_RANDOM, error, error_size, "could not obtain secure random bytes");
 46+		return 0;
 47+	}
 48+	/* join the mojang session, encrypt the secret, verify token */
 49+	if (!mc_join_server(account, server_id, shared_secret, public_key, public_key_size, error, error_size) ||
 50+	    !mc_rsa_encrypt(public_key, public_key_size, shared_secret, sizeof(shared_secret), &encrypted_secret,
 51+	                    &encrypted_secret_size, error, error_size) ||
 52+	    !mc_rsa_encrypt(public_key, public_key_size, verify_token, verify_token_size, &encrypted_token,
 53+	                    &encrypted_token_size, error, error_size))
 54+		goto cleanup;
 55+
 56+	bufinit(&response);
 57+	if (bufwrite_varint(&response, (i32)encrypted_secret_size) &&
 58+	    bufwrite(&response, encrypted_secret, encrypted_secret_size) &&
 59+	    bufwrite_varint(&response, (i32)encrypted_token_size) &&
 60+	    bufwrite(&response, encrypted_token, encrypted_token_size) &&
 61+	    mc_client_write(client, 0x01, response.data, response.size)) {
 62+		/* enable encryption after sending the unencrypted response */
 63+		mc_cipher_init(&client->read_cipher, shared_secret);
 64+		mc_cipher_init(&client->write_cipher, shared_secret);
 65+		client->encrypted = 1;
 66+		ok = 1;
 67+	}
 68+	else {
 69+		mc_set_error(MC_EC_SOCKET_WRITE, error, error_size, "could not send encryption response");
 70+	}
 71+	buffree(&response);
 72+
 73+cleanup:
 74+	memset(shared_secret, 0, sizeof(shared_secret));
 75+	free(encrypted_secret);
 76+	free(encrypted_token);
 77+	return ok;
 78+}
 79+
 80+/* process login packets until play state
 81+   returns 1 on success, 0 on failure */
 82+static int login_loop(MCClient* client, const MCAccount* account, char* error, size_t error_size)
 83+{
 84+	/* login state packet ids for protocol 47:
 85+	   0x00 disconnect
 86+	   0x01 encryption request
 87+	   0x02 login success
 88+	   0x03 set compression
 89+
 90+	   https://prismarinejs.github.io/minecraft-data/protocol/pc/1.8/#login.toClient.types */
 91+	for (;;) {
 92+		MCPacket packet;
 93+		MCBuffer input;
 94+		if (!mc_client_read(client, &packet, error, error_size))
 95+			return 0;
 96+		input.data = packet.data;
 97+		input.size = packet.size;
 98+		input.capacity = packet.size;
 99+		input.cursor = 0;
100+		if (packet.id == 0x00) {
101+			char reason[1024];
102+			if (!bufread_string(&input, reason, sizeof(reason)))
103+				strcpy(reason, "malformed disconnect reason");
104+			mc_set_error(MC_EC_LOGIN_DISCONNECT, error, error_size, "server disconnected during login: %s",
105+			             reason);
106+			mc_packet_free(&packet);
107+			return 0;
108+		}
109+		if (packet.id == 0x01) {
110+			int ok = handle_encryption(client, account, &packet, error, error_size);
111+			mc_packet_free(&packet);
112+			if (!ok)
113+				return 0;
114+			continue;
115+		}
116+		if (packet.id == 0x02) {
117+			char username[64];
118+			if (!bufread_string(&input, client->uuid, sizeof(client->uuid)) ||
119+			    !bufread_string(&input, username, sizeof(username))) {
120+				mc_set_error(MC_EC_MALFORMED_PACKET, error, error_size,
121+				             "malformed login success packet");
122+				mc_packet_free(&packet);
123+				return 0;
124+			}
125+			printf("login success: uuid=%s username=%s\n", client->uuid, username);
126+			mc_packet_free(&packet);
127+			return 1;
128+		}
129+		if (packet.id == 0x03) {
130+			i32 threshold;
131+			if (!bufread_varint(&input, &threshold) || threshold < 0) {
132+				mc_set_error(MC_EC_INVALID_COMPRESSED_PACKET, error, error_size,
133+				             "invalid compression threshold");
134+				mc_packet_free(&packet);
135+				return 0;
136+			}
137+			client->compression_threshold = threshold;
138+			printf("compression enabled: threshold=%d\n", threshold);
139+			mc_packet_free(&packet);
140+			continue;
141+		}
142+		mc_set_error(MC_EC_UNEXPECTED_LOGIN_PACKET, error, error_size, "unexpected login packet 0x%02x",
143+		             packet.id);
144+		mc_packet_free(&packet);
145+		return 0;
146+	}
147+}
148+
149+/* send handshake packet
150+   returns 1 on success, 0 on failure */
151+static int send_handshake(MCClient* client)
152+{
153+	MCBuffer payload;
154+	int      ok;
155+	bufinit(&payload);
156+	/* handshake to next state 2 switches the connection into login state */
157+	ok = bufwrite_varint(&payload, MC_PROTOCOL_1_8_9) && bufwrite_string(&payload, client->host) &&
158+	     bufwrite_u16(&payload, client->port) && bufwrite_varint(&payload, 2) &&
159+	     mc_client_write(client, 0x00, payload.data, payload.size);
160+	buffree(&payload);
161+	return ok;
162+}
163+
164+/* send login start packet
165+   returns 1 on success, 0 on failure */
166+static int send_login_start(MCClient* client)
167+{
168+	MCBuffer payload;
169+	int      ok;
170+	bufinit(&payload);
171+	ok = bufwrite_string(&payload, client->username) && mc_client_write(client, 0x00, payload.data, payload.size);
172+	buffree(&payload);
173+	return ok;
174+}
175+
176+/* read bytes from socket
177+   returns 1 on success, 0 on failure */
178+static int socket_read(MCClient* client, u8* data, size_t size)
179+{
180+	size_t offset = 0;
181+	while (offset < size) {
182+		ssize_t count = recv(client->socket_fd, data + offset, size - offset, 0);
183+		if (count > 0) {
184+			offset += (size_t)count;
185+		}
186+		else if (count < 0 && errno == EINTR) {
187+			continue;
188+		}
189+		else {
190+			return 0;
191+		}
192+	}
193+	if (client->encrypted)
194+		mc_cipher_decrypt(&client->read_cipher, data, size);
195+	return 1;
196+}
197+
198+/* read VarInt from socket
199+   returns 1 on success, 0 on failure */
200+static int socket_read_varint(MCClient* client, i32* value)
201+{
202+	u32 result = 0;
203+	int shift;
204+	for (shift = 0; shift < 35; shift += 7) {
205+		u8 byte;
206+		if (!socket_read(client, &byte, 1))
207+			return 0;
208+		result |= (u32)(byte & 0x7f) << shift;
209+		if (!(byte & 0x80)) {
210+			*value = (i32)result;
211+			return 1;
212+		}
213+	}
214+	return 0;
215+}
216+
217+/* write bytes to socket
218+   returns 1 on success, 0 on failure */
219+static int socket_write(MCClient* client, const u8* data, size_t size)
220+{
221+	u8     encrypted[4096];
222+	size_t offset = 0;
223+
224+	while (offset < size) {
225+		size_t chunk = size - offset;
226+		size_t sent = 0;
227+		if (chunk > sizeof(encrypted))
228+			chunk = sizeof(encrypted);
229+		memcpy(encrypted, data + offset, chunk);
230+		if (client->encrypted)
231+			mc_cipher_encrypt(&client->write_cipher, encrypted, chunk);
232+		while (sent < chunk) {
233+			ssize_t count = send(client->socket_fd, encrypted + sent, chunk - sent, MSG_NOSIGNAL);
234+			if (count > 0) {
235+				sent += (size_t)count;
236+			}
237+			else if (count < 0 && errno == EINTR) {
238+				continue;
239+			}
240+			else {
241+				return 0;
242+			}
243+		}
244+		offset += chunk;
245+	}
246+	return 1;
247+}
248+
249+/* parse address into host and port
250+   returns 1 on success, 0 on failure */
251 static int split_address(const char* address, char* host, size_t host_size, u16* port, char* error, size_t error_size)
252 {
253 	const char* colon = strrchr(address, ':');
254@@ -60,7 +304,8 @@ static int split_address(const char* address, char* host, size_t host_size, u16*
255 	return 1;
256 }
257 
258-/* connect to the configured server address */
259+/* connect to the configured server address
260+   returns 1 on success, 0 on failure */
261 static int tcp_connect(MCClient* client, char* error, size_t error_size)
262 {
263 	struct addrinfo  hints;
264@@ -100,73 +345,6 @@ static int tcp_connect(MCClient* client, char* error, size_t error_size)
265 	return 1;
266 }
267 
268-static int socket_write(MCClient* client, const u8* data, size_t size)
269-{
270-	u8     encrypted[4096];
271-	size_t offset = 0;
272-
273-	while (offset < size) {
274-		size_t chunk = size - offset;
275-		size_t sent = 0;
276-		if (chunk > sizeof(encrypted))
277-			chunk = sizeof(encrypted);
278-		memcpy(encrypted, data + offset, chunk);
279-		if (client->encrypted)
280-			mc_cipher_encrypt(&client->write_cipher, encrypted, chunk);
281-		while (sent < chunk) {
282-			ssize_t count = send(client->socket_fd, encrypted + sent, chunk - sent, MSG_NOSIGNAL);
283-			if (count > 0) {
284-				sent += (size_t)count;
285-			}
286-			else if (count < 0 && errno == EINTR) {
287-				continue;
288-			}
289-			else {
290-				return 0;
291-			}
292-		}
293-		offset += chunk;
294-	}
295-	return 1;
296-}
297-
298-static int socket_read(MCClient* client, u8* data, size_t size)
299-{
300-	size_t offset = 0;
301-	while (offset < size) {
302-		ssize_t count = recv(client->socket_fd, data + offset, size - offset, 0);
303-		if (count > 0) {
304-			offset += (size_t)count;
305-		}
306-		else if (count < 0 && errno == EINTR) {
307-			continue;
308-		}
309-		else {
310-			return 0;
311-		}
312-	}
313-	if (client->encrypted)
314-		mc_cipher_decrypt(&client->read_cipher, data, size);
315-	return 1;
316-}
317-
318-static int socket_read_varint(MCClient* client, i32* value)
319-{
320-	u32 result = 0;
321-	int shift;
322-	for (shift = 0; shift < 35; shift += 7) {
323-		u8 byte;
324-		if (!socket_read(client, &byte, 1))
325-			return 0;
326-		result |= (u32)(byte & 0x7f) << shift;
327-		if (!(byte & 0x80)) {
328-			*value = (i32)result;
329-			return 1;
330-		}
331-	}
332-	return 0;
333-}
334-
335 int mc_client_write(MCClient* client, i32 packet_id, const void* data, size_t size)
336 {
337 	MCBuffer body;
338@@ -316,157 +494,6 @@ void mc_packet_free(MCPacket* packet)
339 	memset(packet, 0, sizeof(*packet));
340 }
341 
342-static int send_handshake(MCClient* client)
343-{
344-	MCBuffer payload;
345-	int      ok;
346-	bufinit(&payload);
347-	/* handshake to next state 2 switches the connection into login state */
348-	ok = bufwrite_varint(&payload, MC_PROTOCOL_1_8_9) && bufwrite_string(&payload, client->host) &&
349-	     bufwrite_u16(&payload, client->port) && bufwrite_varint(&payload, 2) &&
350-	     mc_client_write(client, 0x00, payload.data, payload.size);
351-	buffree(&payload);
352-	return ok;
353-}
354-
355-static int send_login_start(MCClient* client)
356-{
357-	MCBuffer payload;
358-	int      ok;
359-	bufinit(&payload);
360-	ok = bufwrite_string(&payload, client->username) && mc_client_write(client, 0x00, payload.data, payload.size);
361-	buffree(&payload);
362-	return ok;
363-}
364-
365-static int handle_encryption(MCClient* client, const MCAccount* account, MCPacket* packet, char* error,
366-                             size_t error_size)
367-{
368-	MCBuffer  input = { packet->data, packet->size, packet->size, 0 };
369-	MCBuffer  response;
370-	char      server_id[256];
371-	const u8* public_key;
372-	const u8* verify_token;
373-	size_t    public_key_size;
374-	size_t    verify_token_size;
375-	u8        shared_secret[16];
376-	u8*       encrypted_secret = NULL;
377-	u8*       encrypted_token = NULL;
378-	size_t    encrypted_secret_size = 0;
379-	size_t    encrypted_token_size = 0;
380-	int       ok = 0;
381-
382-	/* parse the server encryption request */
383-	if (!bufread_string(&input, server_id, sizeof(server_id)) ||
384-	    !bufread_bytes(&input, &public_key, &public_key_size) ||
385-	    !bufread_bytes(&input, &verify_token, &verify_token_size)) {
386-		mc_set_error(MC_EC_MALFORMED_PACKET, error, error_size, "malformed encryption request");
387-		return 0;
388-	}
389-	/* create the AES shared secret for the connection */
390-	if (!mg_random_bytes(shared_secret, sizeof(shared_secret))) {
391-		mc_set_error(MC_EC_RANDOM, error, error_size, "could not obtain secure random bytes");
392-		return 0;
393-	}
394-	/* join the mojang session, encrypt the secret, verify token */
395-	if (!mc_join_server(account, server_id, shared_secret, public_key, public_key_size, error, error_size) ||
396-	    !mc_rsa_encrypt(public_key, public_key_size, shared_secret, sizeof(shared_secret), &encrypted_secret,
397-	                    &encrypted_secret_size, error, error_size) ||
398-	    !mc_rsa_encrypt(public_key, public_key_size, verify_token, verify_token_size, &encrypted_token,
399-	                    &encrypted_token_size, error, error_size))
400-		goto cleanup;
401-
402-	bufinit(&response);
403-	if (bufwrite_varint(&response, (i32)encrypted_secret_size) &&
404-	    bufwrite(&response, encrypted_secret, encrypted_secret_size) &&
405-	    bufwrite_varint(&response, (i32)encrypted_token_size) &&
406-	    bufwrite(&response, encrypted_token, encrypted_token_size) &&
407-	    mc_client_write(client, 0x01, response.data, response.size)) {
408-		/* enable encryption after sending the unencrypted response */
409-		mc_cipher_init(&client->read_cipher, shared_secret);
410-		mc_cipher_init(&client->write_cipher, shared_secret);
411-		client->encrypted = 1;
412-		ok = 1;
413-	}
414-	else {
415-		mc_set_error(MC_EC_SOCKET_WRITE, error, error_size, "could not send encryption response");
416-	}
417-	buffree(&response);
418-
419-cleanup:
420-	memset(shared_secret, 0, sizeof(shared_secret));
421-	free(encrypted_secret);
422-	free(encrypted_token);
423-	return ok;
424-}
425-
426-/* process the login packets until we enter play state */
427-static int login_loop(MCClient* client, const MCAccount* account, char* error, size_t error_size)
428-{
429-	/* login state packet ids for protocol 47:
430-	   0x00 disconnect
431-	   0x01 encryption request
432-	   0x02 login success
433-	   0x03 set compression
434-
435-	   https://prismarinejs.github.io/minecraft-data/protocol/pc/1.8/#login.toClient.types */
436-	for (;;) {
437-		MCPacket packet;
438-		MCBuffer input;
439-		if (!mc_client_read(client, &packet, error, error_size))
440-			return 0;
441-		input.data = packet.data;
442-		input.size = packet.size;
443-		input.capacity = packet.size;
444-		input.cursor = 0;
445-		if (packet.id == 0x00) {
446-			char reason[1024];
447-			if (!bufread_string(&input, reason, sizeof(reason)))
448-				strcpy(reason, "malformed disconnect reason");
449-			mc_set_error(MC_EC_LOGIN_DISCONNECT, error, error_size, "server disconnected during login: %s",
450-			             reason);
451-			mc_packet_free(&packet);
452-			return 0;
453-		}
454-		if (packet.id == 0x01) {
455-			int ok = handle_encryption(client, account, &packet, error, error_size);
456-			mc_packet_free(&packet);
457-			if (!ok)
458-				return 0;
459-			continue;
460-		}
461-		if (packet.id == 0x02) {
462-			char username[64];
463-			if (!bufread_string(&input, client->uuid, sizeof(client->uuid)) ||
464-			    !bufread_string(&input, username, sizeof(username))) {
465-				mc_set_error(MC_EC_MALFORMED_PACKET, error, error_size,
466-				             "malformed login success packet");
467-				mc_packet_free(&packet);
468-				return 0;
469-			}
470-			printf("login success: uuid=%s username=%s\n", client->uuid, username);
471-			mc_packet_free(&packet);
472-			return 1;
473-		}
474-		if (packet.id == 0x03) {
475-			i32 threshold;
476-			if (!bufread_varint(&input, &threshold) || threshold < 0) {
477-				mc_set_error(MC_EC_INVALID_COMPRESSED_PACKET, error, error_size,
478-				             "invalid compression threshold");
479-				mc_packet_free(&packet);
480-				return 0;
481-			}
482-			client->compression_threshold = threshold;
483-			printf("compression enabled: threshold=%d\n", threshold);
484-			mc_packet_free(&packet);
485-			continue;
486-		}
487-		mc_set_error(MC_EC_UNEXPECTED_LOGIN_PACKET, error, error_size, "unexpected login packet 0x%02x",
488-		             packet.id);
489-		mc_packet_free(&packet);
490-		return 0;
491-	}
492-}
493 /*create a client */
494 int mc_client_login(MCClient** out, const char* address, const MCAccount* account, char* error, size_t error_size)
495 {
+37, -23
 1@@ -11,6 +11,25 @@ typedef struct {
 2 	size_t    cursor;
 3 } DERReader;
 4 
 5+static u8   cipher_byte(MCCipher* cipher);
 6+static int  der_length(DERReader* reader, size_t* length);
 7+static int  der_value(DERReader* reader, u8 tag, DERReader* value);
 8+static int  rsa_public_key(const u8* der, size_t der_size, br_rsa_public_key* key, char* error, size_t error_size);
 9+static void twos_complement(u8* data, size_t size);
10+
11+/* generate next cipher stream byte
12+   returns cipher byte */
13+static u8 cipher_byte(MCCipher* cipher)
14+{
15+	u8 zero_iv[16] = { 0 };
16+	u8 block[16];
17+	memcpy(block, cipher->state, 16);
18+	br_aes_ct_cbcenc_run(&cipher->key, zero_iv, block, sizeof(block));
19+	return block[0];
20+}
21+
22+/* read DER length
23+   returns 1 on success, 0 on failure */
24 static int der_length(DERReader* reader, size_t* length)
25 {
26 	u8       first;
27@@ -36,6 +55,8 @@ static int der_length(DERReader* reader, size_t* length)
28 	return 1;
29 }
30 
31+/* read DER value by tag
32+   returns 1 on success, 0 on failure */
33 static int der_value(DERReader* reader, u8 tag, DERReader* value)
34 {
35 	size_t length;
36@@ -48,7 +69,8 @@ static int der_value(DERReader* reader, u8 tag, DERReader* value)
37 	return 1;
38 }
39 
40-/* parse the server's X.509 RSA pubkey for BearSSL */
41+/* parse the server's X.509 RSA pubkey for BearSSL
42+   returns 1 on success, 0 on failure */
43 static int rsa_public_key(const u8* der, size_t der_size, br_rsa_public_key* key, char* error, size_t error_size)
44 {
45 	DERReader root = { der, der_size, 0 };
46@@ -84,19 +106,24 @@ static int rsa_public_key(const u8* der, size_t der_size, br_rsa_public_key* key
47 	return key->nlen >= 64 && key->elen > 0;
48 }
49 
50-void mc_cipher_init(MCCipher* cipher, const u8 key[16])
51+/* convert bytes to twos complement in place */
52+static void twos_complement(u8* data, size_t size)
53 {
54-	br_aes_ct_cbcenc_init(&cipher->key, key, 16);
55-	memcpy(cipher->state, key, 16);
56+	size_t   i = size;
57+	unsigned carry = 1;
58+	while (i > 0) {
59+		unsigned value;
60+		--i;
61+		value = (unsigned)(data[i] ^ 0xff) + carry;
62+		data[i] = (u8)value;
63+		carry = value >> 8;
64+	}
65 }
66 
67-static u8 cipher_byte(MCCipher* cipher)
68+void mc_cipher_init(MCCipher* cipher, const u8 key[16])
69 {
70-	u8 zero_iv[16] = { 0 };
71-	u8 block[16];
72-	memcpy(block, cipher->state, 16);
73-	br_aes_ct_cbcenc_run(&cipher->key, zero_iv, block, sizeof(block));
74-	return block[0];
75+	br_aes_ct_cbcenc_init(&cipher->key, key, 16);
76+	memcpy(cipher->state, key, 16);
77 }
78 
79 void mc_cipher_encrypt(MCCipher* cipher, u8* data, size_t size)
80@@ -174,19 +201,6 @@ int mc_rsa_encrypt(const u8* public_key, size_t public_key_size, const u8* messa
81 	return 1;
82 }
83 
84-static void twos_complement(u8* data, size_t size)
85-{
86-	size_t   i = size;
87-	unsigned carry = 1;
88-	while (i > 0) {
89-		unsigned value;
90-		--i;
91-		value = (unsigned)(data[i] ^ 0xff) + carry;
92-		data[i] = (u8)value;
93-		carry = value >> 8;
94-	}
95-}
96-
97 void mc_server_hash(const char* server_id, const u8 shared_secret[16], const u8* public_key, size_t public_key_size,
98                     char output[42])
99 {
+2, -2
 1@@ -2,10 +2,10 @@
 2 
 3 #include "net/packet.h"
 4 
 5-/* read a number of bytes from packet, copy them to output.
 6-   returns 1 on success, 0 on failure */
 7 static i8 packet_read(PacketReader* reader, void* output, size_t size);
 8 
 9+/* read a number of bytes from packet, copy them to output.
10+   returns 1 on success, 0 on failure */
11 static i8 packet_read(PacketReader* reader, void* output, size_t size)
12 {
13 	if (reader->cursor > reader->size)
+1, -1
 1@@ -3,9 +3,9 @@
 2 #include "util.h"
 3 #include "platform/platform.h"
 4 
 5-/* set the state of a logical key */
 6 static void platform_key_set(MGContext* ctx, MGKey key, i32 down);
 7 
 8+/* set the state of a logical key */
 9 static void platform_key_set(MGContext* ctx, MGKey key, i32 down)
10 {
11 	int i;
+29, -9
  1@@ -85,15 +85,17 @@ static void create_screen_texture(i32 width, i32 height);
  2 /* destroy the SDL texture and software framebuffer */
  3 static void destroy_screen_texture(void);
  4 
  5-static ClipVertex transform_vertex(const MGMat4* mvp, const MGVertex* v);
  6-static i8         triangle_outside(const ClipVertex* a, const ClipVertex* b, const ClipVertex* c);
  7-static i8         triangle_front_facing(const ClipVertex* a, const ClipVertex* b, const ClipVertex* c);
  8-static S3L_Vec4   clip_to_screen(const ClipVertex* v);
  9-static u32        sample_texture(const RendererMesh* mesh, const RendererTexture* texture, const S3L_PixelInfo* pixel);
 10-static u16        rgba_to_rgb565(u32 rgba);
 11-static u32        rgb565_to_argb(u16 rgb);
 12-static u32        pack_texture_pixel(u32 rgba);
 13-
 14+static ClipVertex  transform_vertex(const MGMat4* mvp, const MGVertex* v);
 15+static i8          triangle_outside(const ClipVertex* a, const ClipVertex* b, const ClipVertex* c);
 16+static i8          triangle_front_facing(const ClipVertex* a, const ClipVertex* b, const ClipVertex* c);
 17+static S3L_Vec4    clip_to_screen(const ClipVertex* v);
 18+static u32         sample_texture(const RendererMesh* mesh, const RendererTexture* texture, const S3L_PixelInfo* pixel);
 19+static u16         rgba_to_rgb565(u32 rgba);
 20+static u32         rgb565_to_argb(u16 rgb);
 21+static u32         pack_texture_pixel(u32 rgba);
 22+static inline void small3dlib_pixel(S3L_PixelInfo* pixel);
 23+
 24+/* shade small3dlib pixel */
 25 static inline void small3dlib_pixel(S3L_PixelInfo* pixel)
 26 {
 27 	u32 index;
 28@@ -120,6 +122,7 @@ static inline void small3dlib_pixel(S3L_PixelInfo* pixel)
 29 	ren.framebuffer[index] = (u16)sampled;
 30 }
 31 
 32+/* create or resize screen texture */
 33 static void create_screen_texture(i32 width, i32 height)
 34 {
 35 	u64  pixel_count;
 36@@ -165,6 +168,7 @@ static void create_screen_texture(i32 width, i32 height)
 37 	S3L_resolutionY = (uint16_t)height;
 38 }
 39 
 40+/* destroy screen texture */
 41 static void destroy_screen_texture(void)
 42 {
 43 #ifdef MG_PLATFORM_SDL
 44@@ -180,6 +184,8 @@ static void destroy_screen_texture(void)
 45 	ren.height = 0;
 46 }
 47 
 48+/* transform vertex to clip space
 49+   returns clip space vertex */
 50 static ClipVertex transform_vertex(const MGMat4* mvp, const MGVertex* v)
 51 {
 52 	ClipVertex r;
 53@@ -196,6 +202,8 @@ static ClipVertex transform_vertex(const MGMat4* mvp, const MGVertex* v)
 54 	return r;
 55 }
 56 
 57+/* test whether triangle is outside clip volume
 58+   returns 1 if outside, 0 otherwise */
 59 static i8 triangle_outside(const ClipVertex* a, const ClipVertex* b, const ClipVertex* c)
 60 {
 61 	if (a->w <= 0.0001f || b->w <= 0.0001f || c->w <= 0.0001f)
 62@@ -217,6 +225,8 @@ static i8 triangle_outside(const ClipVertex* a, const ClipVertex* b, const ClipV
 63 	return 0;
 64 }
 65 
 66+/* test whether triangle faces camera
 67+   returns 1 if front facing, 0 otherwise */
 68 static i8 triangle_front_facing(const ClipVertex* a, const ClipVertex* b, const ClipVertex* c)
 69 {
 70 	float ax = a->x / a->w;
 71@@ -230,6 +240,8 @@ static i8 triangle_front_facing(const ClipVertex* a, const ClipVertex* b, const
 72 	return area > 0.0f;
 73 }
 74 
 75+/* convert clip vertex to screen coordinates
 76+   returns small3dlib screen vertex */
 77 static S3L_Vec4 clip_to_screen(const ClipVertex* v)
 78 {
 79 	S3L_Vec4 r;
 80@@ -249,6 +261,8 @@ static S3L_Vec4 clip_to_screen(const ClipVertex* v)
 81 	return r;
 82 }
 83 
 84+/* sample texture for rasterized pixel
 85+   returns packed sampled colour */
 86 static u32 sample_texture(const RendererMesh* mesh, const RendererTexture* texture, const S3L_PixelInfo* pixel)
 87 {
 88 	u32             sampled;
 89@@ -307,6 +321,8 @@ static u32 sample_texture(const RendererMesh* mesh, const RendererTexture* textu
 90 	return (sampled & 0xff000000) | r | g | b;
 91 }
 92 
 93+/* convert RGBA8 colour to RGB565
 94+   returns RGB565 colour */
 95 static u16 rgba_to_rgb565(u32 rgba)
 96 {
 97 	u32 r = (rgba >> 0) & 0xff;
 98@@ -316,6 +332,8 @@ static u16 rgba_to_rgb565(u32 rgba)
 99 	return (u16)(((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3));
100 }
101 
102+/* convert RGB565 colour to ARGB8
103+   returns ARGB8 colour */
104 static u32 rgb565_to_argb(u16 rgb)
105 {
106 	u32 r = (rgb >> 11) & 31;
107@@ -329,6 +347,8 @@ static u32 rgb565_to_argb(u16 rgb)
108 	return 0xff000000 | (r << 16) | (g << 8) | b;
109 }
110 
111+/* pack texture pixel for renderer
112+   returns packed texture pixel */
113 static u32 pack_texture_pixel(u32 rgba)
114 {
115 	return ((rgba >> 24) << 24) | rgba_to_rgb565(rgba);
+3, -5
 1@@ -37,13 +37,8 @@ typedef struct {
 2 	RendererTexture textures[MAX_TEXTURES];
 3 } RendererState;
 4 
 5-/* create the shader and render pipeline */
 6 static void create_pipelines(void);
 7-
 8-/* create texture sampler */
 9 static void create_sampler(void);
10-
11-/* logging function for passing to sokol  */
12 static void sokol_log(const char* tag, u32 level, u32 item, const char* msg, u32 ln, const char* file, void* user);
13 
14 static RendererState ren;
15@@ -77,6 +72,7 @@ static const char*   fsdr = "#version 330\n"
16                             "    if (frag_colour.a < 0.5) discard;\n"
17                             "}\n";
18 
19+/* create shader and render pipeline */
20 static void create_pipelines(void)
21 {
22 	sg_shader_desc   shader_desc = { 0 };
23@@ -126,6 +122,7 @@ static void create_pipelines(void)
24 		mg_die(MG_EC_PIPELINE_CREATE, "Failed to create pipeline");
25 }
26 
27+/* create texture sampler */
28 static void create_sampler(void)
29 {
30 	sg_sampler_desc desc = { 0 };
31@@ -141,6 +138,7 @@ static void create_sampler(void)
32 		mg_die(MG_EC_SAMPLER_CREATE, "Failed to create sampler");
33 }
34 
35+/* logging function for passing to sokol  */
36 static void sokol_log(const char* tag, u32 level, u32 item, const char* msg, u32 ln, const char* file, void* user)
37 {
38 	LogType type;
+1, -0
1@@ -6,6 +6,7 @@
2 
3 static void vlog(FILE* fd, const char* fmt, va_list ap);
4 
5+/* write formatted log message to file */
6 static void vlog(FILE* fd, const char* fmt, va_list ap)
7 {
8 	fprintf(fd, "Magnolia: ");
+212, -187
  1@@ -66,6 +66,112 @@ static char        pack_path[PACK_SIZE];
  2 static i32         texture_count;
  3 static cJSON*      block_registry;
  4 
  5+static void        block_load(BlockType block);
  6+static i8          face_covers_cube(const ModelFace* face);
  7+static float       json_number(cJSON* array, i32 index);
  8+static cJSON*      json_load(const char* path);
  9+static void        model_cube(BlockModel* model, const char* texture);
 10+static void        model_face(BlockModel* model, cJSON* from, cJSON* to, cJSON* faces, i32 face);
 11+static i8          model_load(const char* name, BlockModel* model, i32 depth);
 12+static const char* model_resolve(const BlockModel* model, const char* name);
 13+static void        model_rotate(BlockModel* model, i32 first, cJSON* rotation);
 14+static void        model_texture(BlockModel* model, const char* name, const char* value);
 15+static i32         texture_load(const char* name);
 16+
 17+/* check whether face covers cube side
 18+   returns 1 if face covers side, 0 otherwise */
 19+static i8 face_covers_cube(const ModelFace* face)
 20+{
 21+	/* only faces spanning their entire boundary plane can occlude neighbours */
 22+	static const i32 face_axes[BLOCK_FACE_COUNT] = { 2, 2, 0, 0, 1, 1 };
 23+	float            low[3] = { 1, 1, 1 };
 24+	float            high[3] = { 0, 0, 0 };
 25+	float            value[3];
 26+	i32              vertex;
 27+	i32              axis;
 28+	i32              plane;
 29+
 30+	for (vertex = 0; vertex < 4; vertex++) {
 31+		value[0] = face->vertices[vertex].x;
 32+		value[1] = face->vertices[vertex].y;
 33+		value[2] = face->vertices[vertex].z;
 34+		for (axis = 0; axis < 3; axis++) {
 35+			if (value[axis] < low[axis])
 36+				low[axis] = value[axis];
 37+			if (value[axis] > high[axis])
 38+				high[axis] = value[axis];
 39+		}
 40+	}
 41+	plane = face_axes[face->cullface];
 42+	for (axis = 0; axis < 3; axis++) {
 43+		if (axis != plane && (low[axis] != 0 || high[axis] != 1))
 44+			return 0;
 45+	}
 46+	return face->cullface & 1 ? high[plane] == 1 : low[plane] == 0;
 47+}
 48+
 49+/* load block model and textures */
 50+static void block_load(BlockType block)
 51+{
 52+	LoadedBlock* loaded;
 53+	BlockModel   model;
 54+	cJSON*       entry;
 55+	cJSON*       item;
 56+	const char*  name;
 57+	const char*  fallback;
 58+	i32          face;
 59+	i32          mask;
 60+	char         path[PATH_SIZE];
 61+
 62+	loaded = &blocks[block];
 63+	loaded->loaded = 1;
 64+	entry = cJSON_GetArrayItem(block_registry, block >> 4);
 65+	item = cJSON_GetObjectItemCaseSensitive(entry, "blockState");
 66+	name = cJSON_IsString(item) ? item->valuestring : NULL;
 67+	item = cJSON_GetObjectItemCaseSensitive(entry, "texture");
 68+	fallback = cJSON_IsString(item) ? item->valuestring : NULL;
 69+	memset(&model, 0, sizeof(model));
 70+	if (name) {
 71+		snprintf(path, sizeof(path), "%s/blockstates/%s.json", pack_path, name);
 72+		item = json_load(path);
 73+		if (item) {
 74+			entry = cJSON_GetObjectItemCaseSensitive(item, "variants");
 75+			if (cJSON_GetArraySize(entry) > (block & 15))
 76+				entry = cJSON_GetArrayItem(entry, block & 15);
 77+			else
 78+				entry = entry ? entry->child : NULL;
 79+			if (cJSON_IsArray(entry))
 80+				entry = cJSON_GetArrayItem(entry, 0);
 81+			entry = cJSON_GetObjectItemCaseSensitive(entry, "model");
 82+			if (cJSON_IsString(entry))
 83+				model_load(entry->valuestring, &model, 0);
 84+			cJSON_Delete(item);
 85+		}
 86+	}
 87+	if (!model.face_count && fallback)
 88+		model_cube(&model, fallback);
 89+	if (!model.face_count)
 90+		return;
 91+	loaded->faces = malloc(model.face_count * sizeof(*loaded->faces));
 92+	if (!loaded->faces)
 93+		return;
 94+	loaded->face_count = model.face_count;
 95+	mask = 0;
 96+	for (face = 0; face < model.face_count; face++) {
 97+		name = model_resolve(&model, model.faces[face].texture);
 98+		if (!name)
 99+			name = fallback;
100+		loaded->faces[face].texture = name ? texture_load(name) : -1;
101+		loaded->faces[face].cullface = model.faces[face].cullface;
102+		memcpy(loaded->faces[face].vertices, model.faces[face].vertices, sizeof(model.faces[face].vertices));
103+		if (model.faces[face].cullface >= 0 && face_covers_cube(&model.faces[face]))
104+			mask |= 1 << model.faces[face].cullface;
105+	}
106+	loaded->occludes = mask == (1 << BLOCK_FACE_COUNT) - 1;
107+}
108+
109+/* load JSON file
110+   returns parsed JSON, or NULL on failure */
111 static cJSON* json_load(const char* path)
112 {
113 	FILE*  file;
114@@ -92,30 +198,83 @@ static cJSON* json_load(const char* path)
115 	return json;
116 }
117 
118-/* add or override model texture */
119-static void model_texture(BlockModel* model, const char* name, const char* value)
120+/* read normalized JSON number
121+   returns number, or 0 on failure */
122+static float json_number(cJSON* array, i32 index)
123 {
124-	i32 i;
125+	cJSON* item = cJSON_GetArrayItem(array, index);
126+	return cJSON_IsNumber(item) ? (float)item->valuedouble / 16.0f : 0.0f;
127+}
128 
129-	for (i = 0; i < model->variable_count; i++) {
130-		if (strcmp(model->variables[i].name, name) == 0)
131-			break;
132+/* create cube model from texture */
133+static void model_cube(BlockModel* model, const char* texture)
134+{
135+	i32 face;
136+
137+	for (face = 0; face < BLOCK_FACE_COUNT; face++) {
138+		model->faces[face].cullface = face;
139+		memcpy(model->faces[face].vertices, face_vertices[face], sizeof(face_vertices[face]));
140+		snprintf(model->faces[face].texture, NAME_SIZE, "%s", texture);
141 	}
142-	if (i == VARIABLE_COUNT)
143-		return;
144-	if (i == model->variable_count)
145-		model->variable_count++;
146-	snprintf(model->variables[i].name, NAME_SIZE, "%s", name);
147-	snprintf(model->variables[i].value, NAME_SIZE, "%s", value);
148+	model->face_count = BLOCK_FACE_COUNT;
149 }
150 
151-static float json_number(cJSON* array, i32 index)
152+/* load inherited model data and apply overrides
153+   returns 1 on success, 0 on failure */
154+static i8 model_load(const char* name, BlockModel* model, i32 depth)
155 {
156-	cJSON* item = cJSON_GetArrayItem(array, index);
157-	return cJSON_IsNumber(item) ? (float)item->valuedouble / 16.0f : 0.0f;
158+	char   path[PATH_SIZE];
159+	cJSON* json;
160+	cJSON* item;
161+	cJSON* element;
162+	cJSON* from;
163+	cJSON* to;
164+	cJSON* faces;
165+	i32    face;
166+	i32    first;
167+
168+	if (depth == 16)
169+		return 0;
170+	if (strchr(name, ':'))
171+		name = strchr(name, ':') + 1;
172+	snprintf(path, sizeof(path), "%s/models/%s%s.json", pack_path, strchr(name, '/') ? "" : "block/", name);
173+	json = json_load(path);
174+	if (!json)
175+		return 0;
176+	/* load the parent first... */
177+	item = cJSON_GetObjectItemCaseSensitive(json, "parent");
178+	if (cJSON_IsString(item) && !model_load(item->valuestring, model, depth + 1)) {
179+		cJSON_Delete(json);
180+		return 0;
181+	}
182+	item = cJSON_GetObjectItemCaseSensitive(json, "textures");
183+	cJSON_ArrayForEach(faces, item)
184+	{
185+		if (cJSON_IsString(faces))
186+			model_texture(model, faces->string, faces->valuestring);
187+	}
188+	item = cJSON_GetObjectItemCaseSensitive(json, "elements");
189+	if (cJSON_IsArray(item)) {
190+		/* ... then let the child override*/
191+		model->face_count = 0;
192+		/* get the from and to values and then turn them into verticies*/
193+		/*TODO we dont rotate stairs*/
194+		cJSON_ArrayForEach(element, item)
195+		{
196+			first = model->face_count;
197+			from = cJSON_GetObjectItemCaseSensitive(element, "from");
198+			to = cJSON_GetObjectItemCaseSensitive(element, "to");
199+			faces = cJSON_GetObjectItemCaseSensitive(element, "faces");
200+			for (face = 0; face < BLOCK_FACE_COUNT; face++)
201+				model_face(model, from, to, faces, face);
202+			model_rotate(model, first, cJSON_GetObjectItemCaseSensitive(element, "rotation"));
203+		}
204+	}
205+	cJSON_Delete(json);
206+	return 1;
207 }
208 
209-/*convert json face element into verticies*/
210+/* convert JSON face element into vertices */
211 static void model_face(BlockModel* model, cJSON* from, cJSON* to, cJSON* faces, i32 face)
212 {
213 	ModelFace* out;
214@@ -167,7 +326,41 @@ static void model_face(BlockModel* model, cJSON* from, cJSON* to, cJSON* faces,
215 	}
216 }
217 
218-/*rotate stuff that whats to be rotated the amount it wants to be rotated by*/
219+/* add or override model texture */
220+static void model_texture(BlockModel* model, const char* name, const char* value)
221+{
222+	i32 i;
223+
224+	for (i = 0; i < model->variable_count; i++) {
225+		if (strcmp(model->variables[i].name, name) == 0)
226+			break;
227+	}
228+	if (i == VARIABLE_COUNT)
229+		return;
230+	if (i == model->variable_count)
231+		model->variable_count++;
232+	snprintf(model->variables[i].name, NAME_SIZE, "%s", name);
233+	snprintf(model->variables[i].value, NAME_SIZE, "%s", value);
234+}
235+
236+/* resolve model texture variable
237+   returns resolved texture name, or NULL on failure */
238+static const char* model_resolve(const BlockModel* model, const char* name)
239+{
240+	i32 depth;
241+	i32 i;
242+
243+	for (depth = 0; name && name[0] == '#' && depth < VARIABLE_COUNT; depth++) {
244+		for (i = 0; i < model->variable_count; i++) {
245+			if (strcmp(model->variables[i].name, name + 1) == 0)
246+				break;
247+		}
248+		name = i == model->variable_count ? NULL : model->variables[i].value;
249+	}
250+	return name;
251+}
252+
253+/* rotate model faces from JSON rotation */
254 static void model_rotate(BlockModel* model, i32 first, cJSON* rotation)
255 {
256 	cJSON*      origin;
257@@ -227,75 +420,8 @@ static void model_rotate(BlockModel* model, i32 first, cJSON* rotation)
258 	}
259 }
260 
261-/* load inhereted model data from the parent feild and then apply per model overrides*/
262-static i8 model_load(const char* name, BlockModel* model, i32 depth)
263-{
264-	char   path[PATH_SIZE];
265-	cJSON* json;
266-	cJSON* item;
267-	cJSON* element;
268-	cJSON* from;
269-	cJSON* to;
270-	cJSON* faces;
271-	i32    face;
272-	i32    first;
273-
274-	if (depth == 16)
275-		return 0;
276-	if (strchr(name, ':'))
277-		name = strchr(name, ':') + 1;
278-	snprintf(path, sizeof(path), "%s/models/%s%s.json", pack_path, strchr(name, '/') ? "" : "block/", name);
279-	json = json_load(path);
280-	if (!json)
281-		return 0;
282-	/* load the parent first... */
283-	item = cJSON_GetObjectItemCaseSensitive(json, "parent");
284-	if (cJSON_IsString(item) && !model_load(item->valuestring, model, depth + 1)) {
285-		cJSON_Delete(json);
286-		return 0;
287-	}
288-	item = cJSON_GetObjectItemCaseSensitive(json, "textures");
289-	cJSON_ArrayForEach(faces, item)
290-	{
291-		if (cJSON_IsString(faces))
292-			model_texture(model, faces->string, faces->valuestring);
293-	}
294-	item = cJSON_GetObjectItemCaseSensitive(json, "elements");
295-	if (cJSON_IsArray(item)) {
296-		/* ... then let the child override*/
297-		model->face_count = 0;
298-		/* get the from and to values and then turn them into verticies*/
299-		/*TODO we dont rotate stairs*/
300-		cJSON_ArrayForEach(element, item)
301-		{
302-			first = model->face_count;
303-			from = cJSON_GetObjectItemCaseSensitive(element, "from");
304-			to = cJSON_GetObjectItemCaseSensitive(element, "to");
305-			faces = cJSON_GetObjectItemCaseSensitive(element, "faces");
306-			for (face = 0; face < BLOCK_FACE_COUNT; face++)
307-				model_face(model, from, to, faces, face);
308-			model_rotate(model, first, cJSON_GetObjectItemCaseSensitive(element, "rotation"));
309-		}
310-	}
311-	cJSON_Delete(json);
312-	return 1;
313-}
314-
315-static const char* model_resolve(const BlockModel* model, const char* name)
316-{
317-	i32 depth;
318-	i32 i;
319-
320-	for (depth = 0; name && name[0] == '#' && depth < VARIABLE_COUNT; depth++) {
321-		for (i = 0; i < model->variable_count; i++) {
322-			if (strcmp(model->variables[i].name, name + 1) == 0)
323-				break;
324-		}
325-		name = i == model->variable_count ? NULL : model->variables[i].value;
326-	}
327-	return name;
328-}
329-
330+/* load texture by name
331+   returns texture index, or -1 on failure */
332 static i32 texture_load(const char* name)
333 {
334 	char        path[PATH_SIZE];
335@@ -319,107 +445,6 @@ static i32 texture_load(const char* name)
336 	return texture_count++;
337 }
338 
339-static void model_cube(BlockModel* model, const char* texture)
340-{
341-	i32 face;
342-
343-	for (face = 0; face < BLOCK_FACE_COUNT; face++) {
344-		model->faces[face].cullface = face;
345-		memcpy(model->faces[face].vertices, face_vertices[face], sizeof(face_vertices[face]));
346-		snprintf(model->faces[face].texture, NAME_SIZE, "%s", texture);
347-	}
348-	model->face_count = BLOCK_FACE_COUNT;
349-}
350-
351-static i8 face_covers_cube(const ModelFace* face)
352-{
353-	/* only faces spanning their entire boundary plane can occlude neighbours */
354-	static const i32 face_axes[BLOCK_FACE_COUNT] = { 2, 2, 0, 0, 1, 1 };
355-	float            low[3] = { 1, 1, 1 };
356-	float            high[3] = { 0, 0, 0 };
357-	float            value[3];
358-	i32              vertex;
359-	i32              axis;
360-	i32              plane;
361-
362-	for (vertex = 0; vertex < 4; vertex++) {
363-		value[0] = face->vertices[vertex].x;
364-		value[1] = face->vertices[vertex].y;
365-		value[2] = face->vertices[vertex].z;
366-		for (axis = 0; axis < 3; axis++) {
367-			if (value[axis] < low[axis])
368-				low[axis] = value[axis];
369-			if (value[axis] > high[axis])
370-				high[axis] = value[axis];
371-		}
372-	}
373-	plane = face_axes[face->cullface];
374-	for (axis = 0; axis < 3; axis++) {
375-		if (axis != plane && (low[axis] != 0 || high[axis] != 1))
376-			return 0;
377-	}
378-	return face->cullface & 1 ? high[plane] == 1 : low[plane] == 0;
379-}
380-
381-static void block_load(BlockType block)
382-{
383-	LoadedBlock* loaded;
384-	BlockModel   model;
385-	cJSON*       entry;
386-	cJSON*       item;
387-	const char*  name;
388-	const char*  fallback;
389-	i32          face;
390-	i32          mask;
391-	char         path[PATH_SIZE];
392-
393-	loaded = &blocks[block];
394-	loaded->loaded = 1;
395-	entry = cJSON_GetArrayItem(block_registry, block >> 4);
396-	item = cJSON_GetObjectItemCaseSensitive(entry, "blockState");
397-	name = cJSON_IsString(item) ? item->valuestring : NULL;
398-	item = cJSON_GetObjectItemCaseSensitive(entry, "texture");
399-	fallback = cJSON_IsString(item) ? item->valuestring : NULL;
400-	memset(&model, 0, sizeof(model));
401-	if (name) {
402-		snprintf(path, sizeof(path), "%s/blockstates/%s.json", pack_path, name);
403-		item = json_load(path);
404-		if (item) {
405-			entry = cJSON_GetObjectItemCaseSensitive(item, "variants");
406-			if (cJSON_GetArraySize(entry) > (block & 15))
407-				entry = cJSON_GetArrayItem(entry, block & 15);
408-			else
409-				entry = entry ? entry->child : NULL;
410-			if (cJSON_IsArray(entry))
411-				entry = cJSON_GetArrayItem(entry, 0);
412-			entry = cJSON_GetObjectItemCaseSensitive(entry, "model");
413-			if (cJSON_IsString(entry))
414-				model_load(entry->valuestring, &model, 0);
415-			cJSON_Delete(item);
416-		}
417-	}
418-	if (!model.face_count && fallback)
419-		model_cube(&model, fallback);
420-	if (!model.face_count)
421-		return;
422-	loaded->faces = malloc(model.face_count * sizeof(*loaded->faces));
423-	if (!loaded->faces)
424-		return;
425-	loaded->face_count = model.face_count;
426-	mask = 0;
427-	for (face = 0; face < model.face_count; face++) {
428-		name = model_resolve(&model, model.faces[face].texture);
429-		if (!name)
430-			name = fallback;
431-		loaded->faces[face].texture = name ? texture_load(name) : -1;
432-		loaded->faces[face].cullface = model.faces[face].cullface;
433-		memcpy(loaded->faces[face].vertices, model.faces[face].vertices, sizeof(model.faces[face].vertices));
434-		if (model.faces[face].cullface >= 0 && face_covers_cube(&model.faces[face]))
435-			mask |= 1 << model.faces[face].cullface;
436-	}
437-	loaded->occludes = mask == (1 << BLOCK_FACE_COUNT) - 1;
438-}
439-
440 i8 block_textures_create(const char* path)
441 {
442 	block_textures_destroy();
+1, -1
1@@ -14,7 +14,7 @@ static const i32 face_offsets[6][3] = {
2 	{ 0, 0, -1 }, { 0, 0, 1 }, { -1, 0, 0 }, { 1, 0, 0 }, { 0, -1, 0 }, { 0, 1, 0 }
3 };
4 
5-/* add a face to the mesh */
6+/* add face vertices and indices */
7 static void add_face(MGVertex* vertices, u16* indices, u32* vertex_count, u32* index_count, const BlockFace* face,
8                      i32 x, i32 y, i32 z)
9 {