project_files/frontlib/net/netconn.c
changeset 7271 5608ac657362
parent 7269 5b0aeef8ba2a
child 7273 8eed495fd8da
--- a/project_files/frontlib/net/netconn.c	Thu Jun 21 21:32:12 2012 +0200
+++ b/project_files/frontlib/net/netconn.c	Mon Jun 25 00:42:07 2012 +0200
@@ -18,93 +18,21 @@
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
  */
 
-#include "netconn.h"
-#include "netbase.h"
+// TODO: Check the state transitions. Document with a diagram or something
+
+#include "netconn_internal.h"
 #include "netprotocol.h"
 #include "../util/logging.h"
 #include "../util/util.h"
 #include "../model/roomlist.h"
 #include "../md5/md5.h"
+#include "../base64/base64.h"
 
-#include <stdbool.h>
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
 #include <ctype.h>
 
-struct _flib_netconn {
-	flib_netbase *netBase;
-	char *playerName;
-	flib_cfg_meta *metaCfg;
-	flib_roomlist *roomList;
-
-	int netconnState;	// One of the NETCONN_STATE constants
-
-	bool isAdmin;			// Player is server administrator
-	bool isChief;			// Player can modify the current room
-
-
-	void (*onMessageCb)(void *context, int msgtype, const char *msg);
-	void *onMessageCtx;
-
-	void (*onConnectedCb)(void *context);
-	void *onConnectedCtx;
-
-	void (*onDisconnectedCb)(void *context, int reason, const char *message);
-	void *onDisconnectedCtx;
-
-	void (*onRoomAddCb)(void *context, const flib_roomlist_room *room);
-	void *onRoomAddCtx;
-
-	void (*onRoomDeleteCb)(void *context, const char *name);
-	void *onRoomDeleteCtx;
-
-	void (*onRoomUpdateCb)(void *context, const char *oldName, const flib_roomlist_room *room);
-	void *onRoomUpdateCtx;
-
-	void (*onChatCb)(void *context, const char *nick, const char *msg);
-	void *onChatCtx;
-
-	void (*onLobbyJoinCb)(void *context, const char *nick);
-	void *onLobbyJoinCtx;
-
-	void (*onLobbyLeaveCb)(void *context, const char *nick, const char *partMessage);
-	void *onLobbyLeaveCtx;
-
-	void (*onRoomJoinCb)(void *context, const char *nick);
-	void *onRoomJoinCtx;
-
-	void (*onRoomLeaveCb)(void *context, const char *nick, const char *partMessage);
-	void *onRoomLeaveCtx;
-
-	void (*onNickTakenCb)(void *context, const char *nick);
-	void *onNickTakenCtx;
-
-	void (*onNickAcceptCb)(void *context, const char *nick);
-	void *onNickAcceptCtx;
-
-	void (*onPasswordRequestCb)(void *context, const char *nick);
-	void *onPasswordRequestCtx;
-
-	void (*onRoomChiefStatusCb)(void *context, bool isChief);
-	void *onRoomChiefStatusCtx;
-
-	void (*onReadyStateCb)(void *context, const char *nick, bool ready);
-	void *onReadyStateCtx;
-
-	void (*onEnterRoomCb)(void *context, bool chief);
-	void *onEnterRoomCtx;
-
-	void (*onLeaveRoomCb)(void *context, int reason, const char *message);
-	void *onLeaveRoomCtx;
-
-	void (*onTeamAddCb)(void *context, flib_team *team);
-	void *onTeamAddCtx;
-
-	bool running;
-	bool destroyRequested;
-};
-
 static void defaultCallback_onMessage(void *context, int msgtype, const char *msg) {
 	flib_log_i("Net: [%i] %s", msgtype, msg);
 }
@@ -115,6 +43,7 @@
 static void defaultCallback_int_str(void *context, int i, const char *str) {}
 static void defaultCallback_str_str(void *context, const char *str1, const char *str2) {}
 static void defaultCallback_str_bool(void *context, const char *str, bool b) {}
+static void defaultCallback_str_int(void *context, const char *str, int i) {}
 
 static void defaultCallback_onRoomAdd(void *context, const flib_roomlist_room *room) {}
 static void defaultCallback_onRoomUpdate(void *context, const char *oldName, const flib_roomlist_room *room) {}
@@ -149,6 +78,10 @@
 }
 
 static void defaultCallback_onTeamAdd(void *context, flib_team *team) {}
+static void defaultCallback_onTeamColorChanged(void *context, const char *teamName, uint32_t color) {}
+static void defaultCallback_onCfgScheme(void *context, flib_cfg *scheme) {}
+static void defaultCallback_onMapChanged(void *context, const flib_map *map, int changetype) {}
+static void defaultCallback_onWeaponsetChanged(void *context, flib_weaponset *weaponset) {}
 
 static void clearCallbacks(flib_netconn *conn) {
 	flib_netconn_onMessage(conn, NULL, NULL);
@@ -163,12 +96,24 @@
 	flib_netconn_onRoomJoin(conn, NULL, NULL);
 	flib_netconn_onRoomLeave(conn, NULL, NULL);
 	flib_netconn_onNickTaken(conn, NULL, NULL);
-	flib_netconn_onNickAccept(conn, NULL, NULL);
 	flib_netconn_onPasswordRequest(conn, NULL, NULL);
 	flib_netconn_onRoomChiefStatus(conn, NULL, NULL);
-	flib_netconn_onReadyStateCb(conn, NULL, NULL);
-	flib_netconn_onEnterRoomCb(conn, NULL, NULL);
-	flib_netconn_onTeamAddCb(conn, NULL, NULL);
+	flib_netconn_onReadyState(conn, NULL, NULL);
+	flib_netconn_onEnterRoom(conn, NULL, NULL);
+	flib_netconn_onLeaveRoom(conn, NULL, NULL);
+	flib_netconn_onTeamAdd(conn, NULL, NULL);
+	flib_netconn_onTeamDelete(conn, NULL, NULL);
+	flib_netconn_onRunGame(conn, NULL, NULL);
+	flib_netconn_onTeamAccepted(conn, NULL, NULL);
+	flib_netconn_onHogCountChanged(conn, NULL, NULL);
+	flib_netconn_onTeamColorChanged(conn, NULL, NULL);
+	flib_netconn_onEngineMessage(conn, NULL, NULL);
+	flib_netconn_onCfgScheme(conn, NULL, NULL);
+	flib_netconn_onMapChanged(conn, NULL, NULL);
+	flib_netconn_onScriptChanged(conn, NULL, NULL);
+	flib_netconn_onWeaponsetChanged(conn, NULL, NULL);
+	flib_netconn_onAdminAccess(conn, NULL, NULL);
+	flib_netconn_onServerVar(conn, NULL, NULL);
 }
 
 flib_netconn *flib_netconn_create(const char *playerName, flib_cfg_meta *metacfg, const char *host, uint16_t port) {
@@ -183,6 +128,7 @@
 			newConn->isChief = false;
 			newConn->metaCfg = flib_cfg_meta_retain(metacfg);
 			newConn->roomList = flib_roomlist_create();
+			newConn->map = flib_map_create_named("", "NoSuchMap");
 			newConn->running = false;
 			newConn->destroyRequested = false;
 			clearCallbacks(newConn);
@@ -212,6 +158,7 @@
 			flib_netbase_destroy(conn->netBase);
 			flib_cfg_meta_release(conn->metaCfg);
 			flib_roomlist_destroy(conn->roomList);
+			flib_map_release(conn->map);
 			free(conn->playerName);
 			free(conn);
 		}
@@ -238,65 +185,6 @@
 	return result;
 }
 
-int flib_netconn_send_quit(flib_netconn *conn, const char *quitmsg) {
-	int result = -1;
-	if(!conn) {
-		flib_log_e("null parameter in flib_netconn_send_quit");
-	} else {
-		result = flib_netbase_sendf(conn->netBase, "%s\n%s\n\n", "QUIT", quitmsg ? quitmsg : "User quit");
-	}
-	return result;
-}
-
-int flib_netconn_send_chat(flib_netconn *conn, const char *chat) {
-	int result = -1;
-	if(!conn || !chat) {
-		flib_log_e("null parameter in flib_netconn_send_chat");
-	} else {
-		result = flib_netbase_sendf(conn->netBase, "%s\n%s\n\n", "CHAT", chat);
-	}
-	return result;
-}
-
-int flib_netconn_send_nick(flib_netconn *conn, const char *nick) {
-	int result = -1;
-	if(!conn || !nick) {
-		flib_log_e("null parameter in flib_netconn_send_nick");
-	} else {
-		char *tmpName = flib_strdupnull(nick);
-		if(tmpName) {
-			if(!flib_netbase_sendf(conn->netBase, "%s\n%s\n\n", "NICK", nick)) {
-				free(conn->playerName);
-				conn->playerName = tmpName;
-				tmpName = NULL;
-				result = 0;
-			}
-		}
-		free(tmpName);
-	}
-	return result;
-}
-
-int flib_netconn_send_password(flib_netconn *conn, const char *latin1Passwd) {
-	int result = -1;
-	if(!conn || !latin1Passwd) {
-		flib_log_e("null parameter in flib_netconn_send_password");
-	} else {
-		md5_state_t md5state;
-		uint8_t md5bytes[16];
-		char md5hex[33];
-		md5_init(&md5state);
-		md5_append(&md5state, (unsigned char*)latin1Passwd, strlen(latin1Passwd));
-		md5_finish(&md5state, md5bytes);
-		for(int i=0;i<sizeof(md5bytes); i++) {
-			// Needs to be lowercase - server checks case sensitive
-			snprintf(md5hex+i*2, 3, "%02x", (unsigned)md5bytes[i]);
-		}
-		result = flib_netbase_sendf(conn->netBase, "%s\n%s\n\n", "PASSWORD", md5hex);
-	}
-	return result;
-}
-
 /*
  * Callback registration functions
  */
@@ -412,15 +300,6 @@
 	}
 }
 
-void flib_netconn_onNickAccept(flib_netconn *conn, void (*callback)(void *context, const char *nick), void* context) {
-	if(!conn) {
-		flib_log_e("null parameter in flib_netconn_onNickAccept");
-	} else {
-		conn->onNickAcceptCb = callback ? callback : &defaultCallback_str;
-		conn->onNickAcceptCtx = context;
-	}
-}
-
 void flib_netconn_onPasswordRequest(flib_netconn *conn, void (*callback)(void *context, const char *nick), void* context) {
 	if(!conn) {
 		flib_log_e("null parameter in flib_netconn_onPasswordRequest");
@@ -442,42 +321,162 @@
 	}
 }
 
-void flib_netconn_onReadyStateCb(flib_netconn *conn, void (*callback)(void *context, const char *nick, bool ready), void* context) {
+void flib_netconn_onReadyState(flib_netconn *conn, void (*callback)(void *context, const char *nick, bool ready), void* context) {
 	if(!conn) {
-		flib_log_e("null parameter in flib_netconn_onReadyStateCb");
+		flib_log_e("null parameter in flib_netconn_onReadyState");
 	} else {
 		conn->onReadyStateCb = callback ? callback : &defaultCallback_str_bool;
 		conn->onReadyStateCtx = context;
 	}
 }
 
-void flib_netconn_onEnterRoomCb(flib_netconn *conn, void (*callback)(void *context, bool chief), void *context) {
+void flib_netconn_onEnterRoom(flib_netconn *conn, void (*callback)(void *context, bool chief), void *context) {
 	if(!conn) {
-		flib_log_e("null parameter in flib_netconn_onEnterRoomCb");
+		flib_log_e("null parameter in flib_netconn_onEnterRoom");
 	} else {
 		conn->onEnterRoomCb = callback ? callback : &defaultCallback_bool;
 		conn->onEnterRoomCtx = context;
 	}
 }
 
-void flib_netconn_onLeaveRoomCb(flib_netconn *conn, void (*callback)(void *context, int reason, const char *message), void *context) {
+void flib_netconn_onLeaveRoom(flib_netconn *conn, void (*callback)(void *context, int reason, const char *message), void *context) {
 	if(!conn) {
-		flib_log_e("null parameter in flib_netconn_onLeaveRoomCb");
+		flib_log_e("null parameter in flib_netconn_onLeaveRoom");
 	} else {
 		conn->onLeaveRoomCb = callback ? callback : &defaultCallback_int_str;
 		conn->onLeaveRoomCtx = context;
 	}
 }
 
-void flib_netconn_onTeamAddCb(flib_netconn *conn, void (*callback)(void *context, flib_team *team), void *context) {
+void flib_netconn_onTeamAdd(flib_netconn *conn, void (*callback)(void *context, flib_team *team), void *context) {
 	if(!conn) {
-		flib_log_e("null parameter in flib_netconn_onTeamAddCb");
+		flib_log_e("null parameter in flib_netconn_onTeamAdd");
 	} else {
 		conn->onTeamAddCb = callback ? callback : &defaultCallback_onTeamAdd;
 		conn->onTeamAddCtx = context;
 	}
 }
 
+void flib_netconn_onTeamDelete(flib_netconn *conn, void (*callback)(void *context, const char *teamname), void *context) {
+	if(!conn) {
+		flib_log_e("null parameter in flib_netconn_onTeamDelete");
+	} else {
+		conn->onTeamDeleteCb = callback ? callback : &defaultCallback_str;
+		conn->onTeamDeleteCtx = context;
+	}
+}
+
+void flib_netconn_onRunGame(flib_netconn *conn, void (*callback)(void *context), void *context) {
+	if(!conn) {
+		flib_log_e("null parameter in flib_netconn_onRunGame");
+	} else {
+		conn->onRunGameCb = callback ? callback : &defaultCallback_void;
+		conn->onRunGameCtx = context;
+	}
+}
+
+void flib_netconn_onTeamAccepted(flib_netconn *conn, void (*callback)(void *context, const char *teamName), void *context) {
+	if(!conn) {
+		flib_log_e("null parameter in flib_netconn_onTeamAccepted");
+	} else {
+		conn->onTeamAcceptedCb = callback ? callback : &defaultCallback_str;
+		conn->onTeamAcceptedCtx = context;
+	}
+}
+
+void flib_netconn_onHogCountChanged(flib_netconn *conn, void (*callback)(void *context, const char *teamName, int hogs), void *context) {
+	if(!conn) {
+		flib_log_e("null parameter in flib_netconn_onHogCountChanged");
+	} else {
+		conn->onHogCountChangedCb = callback ? callback : &defaultCallback_str_int;
+		conn->onHogCountChangedCtx = context;
+	}
+}
+
+void flib_netconn_onTeamColorChanged(flib_netconn *conn, void (*callback)(void *context, const char *teamName, uint32_t colorARGB), void *context) {
+	if(!conn) {
+		flib_log_e("null parameter in flib_netconn_onTeamColorChanged");
+	} else {
+		conn->onTeamColorChangedCb = callback ? callback : &defaultCallback_onTeamColorChanged;
+		conn->onTeamColorChangedCtx = context;
+	}
+}
+
+void flib_netconn_onEngineMessage(flib_netconn *conn, void (*callback)(void *context, const char *message, int size), void *context) {
+	if(!conn) {
+		flib_log_e("null parameter in flib_netconn_onEngineMessage");
+	} else {
+		conn->onEngineMessageCb = callback ? callback : &defaultCallback_str_int;
+		conn->onEngineMessageCtx = context;
+	}
+}
+
+void flib_netconn_onCfgScheme(flib_netconn *conn, void (*callback)(void *context, flib_cfg *scheme), void *context) {
+	if(!conn) {
+		flib_log_e("null parameter in flib_netconn_onCfgScheme");
+	} else {
+		conn->onCfgSchemeCb = callback ? callback : &defaultCallback_onCfgScheme;
+		conn->onCfgSchemeCtx = context;
+	}
+}
+
+void flib_netconn_onMapChanged(flib_netconn *conn, void (*callback)(void *context, const flib_map *map, int changetype), void *context) {
+	if(!conn) {
+		flib_log_e("null parameter in flib_netconn_onMapChanged");
+	} else {
+		conn->onMapChangedCb = callback ? callback : &defaultCallback_onMapChanged;
+		conn->onMapChangedCtx = context;
+	}
+}
+
+void flib_netconn_onScriptChanged(flib_netconn *conn, void (*callback)(void *context, const char *script), void *context) {
+	if(!conn) {
+		flib_log_e("null parameter in flib_netconn_onScriptChanged");
+	} else {
+		conn->onScriptChangedCb = callback ? callback : &defaultCallback_str;
+		conn->onScriptChangedCtx = context;
+	}
+}
+
+void flib_netconn_onWeaponsetChanged(flib_netconn *conn, void (*callback)(void *context, flib_weaponset *weaponset), void *context) {
+	if(!conn) {
+		flib_log_e("null parameter in flib_netconn_onWeaponsetChanged");
+	} else {
+		conn->onWeaponsetChangedCb = callback ? callback : &defaultCallback_onWeaponsetChanged;
+		conn->onWeaponsetChangedCtx = context;
+	}
+}
+
+void flib_netconn_onAdminAccess(flib_netconn *conn, void (*callback)(void *context), void *context) {
+	if(!conn) {
+		flib_log_e("null parameter in flib_netconn_onAdminAccess");
+	} else {
+		conn->onAdminAccessCb = callback ? callback : &defaultCallback_void;
+		conn->onAdminAccessCtx = context;
+	}
+}
+
+void flib_netconn_onServerVar(flib_netconn *conn, void (*callback)(void *context, const char *name, const char *value), void *context) {
+	if(!conn) {
+		flib_log_e("null parameter in flib_netconn_onServerVar");
+	} else {
+		conn->onServerVarCb = callback ? callback : &defaultCallback_str_str;
+		conn->onServerVarCtx = context;
+	}
+}
+
+void leaveRoom(flib_netconn *conn) {
+	conn->netconnState = NETCONN_STATE_LOBBY;
+	conn->isChief = false;
+	flib_map *map = flib_map_create_named("", "NoSuchMap");
+	if(map) {
+		flib_map_release(conn->map);
+		conn->map = map;
+	} else {
+		flib_log_e("Error resetting netconn.map");
+	}
+}
+
 static void flib_netconn_wrappedtick(flib_netconn *conn) {
 	flib_netmsg *netmsg;
 	flib_netbase *net = conn->netBase;
@@ -503,14 +502,14 @@
 	    	if(netmsg->partCount<2) {
 	    		flib_log_w("Net: Malformed NICK message");
 	    	} else {
-				free(conn->playerName);
-				conn->playerName = flib_strdupnull(netmsg->parts[1]);
-				if(!conn->playerName) {
+	    		char *nick = flib_strdupnull(netmsg->parts[1]);
+	    		if(nick) {
+					free(conn->playerName);
+					conn->playerName = nick;
+	    		} else {
 					conn->netconnState = NETCONN_STATE_DISCONNECTED;
 					conn->onDisconnectedCb(conn->onDisconnectedCtx, NETCONN_DISCONNECT_INTERNAL_ERROR, "Out of memory");
 					exit = true;
-				} else {
-					conn->onNickAcceptCb(conn->onNickAcceptCtx, conn->playerName);
 				}
 	    	}
 	    } else if (!strcmp(cmd, "PROTO")) {
@@ -583,18 +582,9 @@
 	        	free(joined);
 	        }
 	    } else if(!strcmp(cmd, "SERVER_VARS")) {
-	    	// TODO
-//	        QStringList tmp = lst;
-//	        tmp.removeFirst();
-//	        while (tmp.size() >= 2)
-//	        {
-//	            if(tmp[0] == "MOTD_NEW") emit serverMessageNew(tmp[1]);
-//	            else if(tmp[0] == "MOTD_OLD") emit serverMessageOld(tmp[1]);
-//	            else if(tmp[0] == "LATEST_PROTO") emit latestProtocolVar(tmp[1].toInt());
-//
-//	            tmp.removeFirst();
-//	            tmp.removeFirst();
-//	        }
+	    	for(int offset=1; offset+2<netmsg->partCount; offset+=2) {
+	    		conn->onServerVarCb(conn->onServerVarCtx, netmsg->parts[offset], netmsg->parts[offset+1]);
+	    	}
 	    } else if (!strcmp(cmd, "CLIENT_FLAGS")) {
 	        if(netmsg->partCount < 3 || strlen(netmsg->parts[1]) < 2) {
 	        	flib_log_w("Net: Malformed CLIENT_FLAGS message");
@@ -606,12 +596,6 @@
 					switch(flags[i]) {
 					case 'r':
 						for(int j = 2; j < netmsg->partCount; ++j) {
-							if (!strcmp(conn->playerName, netmsg->parts[i])) {
-								// TODO what is the reason behind this (copied from QtFrontend)?
-								if (conn->isChief && !setFlag) {
-									flib_netbase_sendf(conn->netBase, "%s\n\n", "TOGGLE_READY");
-								}
-							}
 							conn->onReadyStateCb(conn->onReadyStateCtx, netmsg->parts[i], setFlag);
 						}
 						break;
@@ -631,21 +615,22 @@
 					conn->onDisconnectedCb(conn->onDisconnectedCtx, NETCONN_DISCONNECT_INTERNAL_ERROR, "Internal error");
 					exit = true;
 	        	} else {
+	        		team->remoteDriven = true;
 	        		conn->onTeamAddCb(conn->onTeamAddCtx, team);
 	        	}
+	        	flib_team_release(team);
 	        }
 	    } else if (!strcmp(cmd, "REMOVE_TEAM")) {
 	        if(netmsg->partCount != 2) {
 	            flib_log_w("Net: Bad REMOVETEAM message");
 	        } else {
-	        	// TODO
-	        	// emit RemoveNetTeam(HWTeam(lst[1]));
+	        	conn->onTeamDeleteCb(conn->onTeamDeleteCtx, netmsg->parts[1]);
 	        }
 	    } else if(!strcmp(cmd, "ROOMABANDONED")) {
-	        conn->netconnState = NETCONN_STATE_LOBBY;
+	    	leaveRoom(conn);
 	        conn->onLeaveRoomCb(conn->onLeaveRoomCtx, NETCONN_ROOMLEAVE_ABANDONED, "Room destroyed");
 	    } else if(!strcmp(cmd, "KICKED")) {
-	    	conn->netconnState = NETCONN_STATE_LOBBY;
+	    	leaveRoom(conn);
 	    	conn->onLeaveRoomCb(conn->onLeaveRoomCtx, NETCONN_ROOMLEAVE_KICKED, "You got kicked");
 	    } else if(!strcmp(cmd, "JOINED")) {
 	        if(netmsg->partCount < 2) {
@@ -718,8 +703,7 @@
 	        }
 	    } else if (!strcmp(cmd, "RUN_GAME")) {
 	        conn->netconnState = NETCONN_STATE_INGAME;
-	        // TODO
-	        // emit AskForRunGame();
+	        conn->onRunGameCb(conn->onRunGameCtx);
 	    } else if (!strcmp(cmd, "ASKPASSWORD")) {
 	    	conn->onPasswordRequestCb(conn->onPasswordRequestCtx, conn->playerName);
 	    } else if (!strcmp(cmd, "NOTICE")) {
@@ -740,49 +724,128 @@
 	        if (netmsg->partCount != 2) {
 	            flib_log_w("Net: Bad TEAM_ACCEPTED message");
 	        } else {
-	        	// TODO
-	        	// emit TeamAccepted(lst[1]);
+	        	conn->onTeamAcceptedCb(conn->onTeamAcceptedCtx, netmsg->parts[1]);
 	        }
 	    } else if (!strcmp(cmd, "CFG")) {
 	        if(netmsg->partCount < 3) {
 	            flib_log_w("Net: Bad CFG message");
 	        } else {
-	        	// TODO
-//				QStringList tmp = lst;
-//				tmp.removeFirst();
-//				tmp.removeFirst();
-//				if (lst[1] == "SCHEME")
-//					emit netSchemeConfig(tmp);
-//				else
-//					emit paramChanged(lst[1], tmp);
+	        	const char *subcmd = netmsg->parts[1];
+				if(!strcmp(subcmd, "SCHEME") && netmsg->partCount == conn->metaCfg->modCount + conn->metaCfg->settingCount + 3) {
+					flib_cfg *cfg = flib_netmsg_to_cfg(conn->metaCfg, netmsg->parts+2);
+					if(cfg) {
+						conn->onCfgSchemeCb(conn->onCfgSchemeCtx, cfg);
+					} else {
+						flib_log_e("Error processing CFG SCHEME message");
+					}
+					flib_cfg_release(cfg);
+				} else if(!strcmp(subcmd, "FULLMAPCONFIG") && netmsg->partCount == 7) {
+					flib_map *map = flib_netmsg_to_map(netmsg->parts+2);
+					if(map) {
+						flib_map_release(conn->map);
+						conn->map = map;
+						conn->onMapChangedCb(conn->onMapChangedCtx, conn->map, NETCONN_MAPCHANGE_FULL);
+					} else {
+						flib_log_e("Error processing CFG FULLMAPCONFIG message");
+					}
+				} else if(!strcmp(subcmd, "MAP") && netmsg->partCount == 3) {
+					char *mapname = flib_strdupnull(netmsg->parts[2]);
+					if(mapname) {
+						free(conn->map->name);
+						conn->map->name = mapname;
+						conn->onMapChangedCb(conn->onMapChangedCtx, conn->map, NETCONN_MAPCHANGE_MAP);
+					} else {
+						flib_log_e("Error processing CFG MAP message");
+					}
+				} else if(!strcmp(subcmd, "THEME") && netmsg->partCount == 3) {
+					char *themename = flib_strdupnull(netmsg->parts[2]);
+					if(themename) {
+						free(conn->map->theme);
+						conn->map->theme = themename;
+						conn->onMapChangedCb(conn->onMapChangedCtx, conn->map, NETCONN_MAPCHANGE_THEME);
+					} else {
+						flib_log_e("Error processing CFG THEME message");
+					}
+				} else if(!strcmp(subcmd, "SEED") && netmsg->partCount == 3) {
+					char *seed = flib_strdupnull(netmsg->parts[2]);
+					if(seed) {
+						free(conn->map->seed);
+						conn->map->seed = seed;
+						conn->onMapChangedCb(conn->onMapChangedCtx, conn->map, NETCONN_MAPCHANGE_SEED);
+					} else {
+						flib_log_e("Error processing CFG SEED message");
+					}
+				} else if(!strcmp(subcmd, "TEMPLATE") && netmsg->partCount == 3) {
+					conn->map->templateFilter = atoi(netmsg->parts[2]);
+					conn->onMapChangedCb(conn->onMapChangedCtx, conn->map, NETCONN_MAPCHANGE_TEMPLATE);
+				} else if(!strcmp(subcmd, "MAPGEN") && netmsg->partCount == 3) {
+					conn->map->mapgen = atoi(netmsg->parts[2]);
+					conn->onMapChangedCb(conn->onMapChangedCtx, conn->map, NETCONN_MAPCHANGE_MAPGEN);
+				} else if(!strcmp(subcmd, "MAZE_SIZE") && netmsg->partCount == 3) {
+					conn->map->mazeSize = atoi(netmsg->parts[2]);
+					conn->onMapChangedCb(conn->onMapChangedCtx, conn->map, NETCONN_MAPCHANGE_MAZE_SIZE);
+				} else if(!strcmp(subcmd, "DRAWNMAP") && netmsg->partCount == 3) {
+					size_t drawnMapSize = 0;
+					uint8_t *drawnMapData = flib_netmsg_to_drawnmapdata(&drawnMapSize, netmsg->parts[2]);
+					if(drawnMapData) {
+						free(conn->map->drawData);
+						conn->map->drawData = drawnMapData;
+						conn->map->drawDataSize = drawnMapSize;
+						conn->onMapChangedCb(conn->onMapChangedCtx, conn->map, NETCONN_MAPCHANGE_DRAWNMAP);
+					} else {
+						flib_log_e("Error processing CFG DRAWNMAP message");
+					}
+				} else if(!strcmp(subcmd, "SCRIPT") && netmsg->partCount == 3) {
+					conn->onScriptChangedCb(conn->onScriptChangedCtx, netmsg->parts[2]);
+				} else if(!strcmp(subcmd, "AMMO") && netmsg->partCount == 4) {
+					flib_weaponset *weapons = flib_weaponset_from_ammostring(netmsg->parts[2], netmsg->parts[3]);
+					if(weapons) {
+						conn->onWeaponsetChangedCb(conn->onWeaponsetChangedCtx, weapons);
+					} else {
+						flib_log_e("Error processing CFG AMMO message");
+					}
+					flib_weaponset_release(weapons);
+				} else {
+					flib_log_w("Net: Unknown or malformed CFG subcommand: %s", subcmd);
+				}
 	        }
 	    } else if (!strcmp(cmd, "HH_NUM")) {
 	        if (netmsg->partCount != 3) {
-	            flib_log_w("Net: Bad TEAM_ACCEPTED message");
+	            flib_log_w("Net: Bad HH_NUM message");
 	        } else {
-	        	// TODO
-//				HWTeam tmptm(lst[1]);
-//				tmptm.setNumHedgehogs(lst[2].toUInt());
-//				emit hhnumChanged(tmptm);
+	        	int hogs = atoi(netmsg->parts[2]);
+	        	if(hogs<=0 || hogs>HEDGEHOGS_PER_TEAM) {
+	        		flib_log_w("Net: Bad HH_NUM message: %s hogs", netmsg->parts[2]);
+	        	} else {
+	        		conn->onHogCountChangedCb(conn->onHogCountChangedCtx, netmsg->parts[1], hogs);
+	        	}
 	        }
 	    } else if (!strcmp(cmd, "TEAM_COLOR")) {
 	        if (netmsg->partCount != 3) {
 	            flib_log_w("Net: Bad TEAM_COLOR message");
 	        } else {
-	        	// TODO
-//				HWTeam tmptm(lst[1]);
-//				tmptm.setColor(lst[2].toInt());
-//				emit teamColorChanged(tmptm);
+	        	long color;
+	        	if(sscanf(netmsg->parts[2], "#%lx", &color)) {
+	        		conn->onTeamColorChangedCb(conn->onTeamColorChangedCtx, netmsg->parts[1], (uint32_t)color);
+	        	} else {
+	        		flib_log_w("Net: Bad TEAM_COLOR message: Color %s", netmsg->parts[2]);
+	        	}
 	        }
 	    } else if (!strcmp(cmd, "EM")) {
 	        if(netmsg->partCount < 2) {
 	            flib_log_w("Net: Bad EM message");
 	        } else {
-	        	// TODO
-//				for(int i = 1; i < netmsg->partCount; ++i) {
-//					QByteArray em = QByteArray::fromBase64(lst[i].toAscii());
-//					emit FromNet(em);
-//				}
+	        	for(int i = 1; i < netmsg->partCount; ++i) {
+					char *out = NULL;
+					size_t outlen;
+					bool ok = base64_decode_alloc(netmsg->parts[i], strlen(netmsg->parts[i]), &out, &outlen);
+					if(ok && outlen) {
+						conn->onEngineMessageCb(conn->onEngineMessageCtx, out, outlen);
+					} else {
+						flib_log_e("Net: Malformed engine message: %s", netmsg->parts[i]);
+					}
+					free(out);
+	        	}
 	        }
 	    } else if (!strcmp(cmd, "BYE")) {
 	        if (netmsg->partCount < 2) {
@@ -797,7 +860,7 @@
 				exit = true;
 	        }
 	    } else if (!strcmp(cmd, "ADMIN_ACCESS")) {
-	    	// TODO callback?
+	    	conn->onAdminAccessCb(conn->onAdminAccessCtx);
 	    	conn->isAdmin = true;
 	    } else if (!strcmp(cmd, "ROOM_CONTROL_ACCESS")) {
 	        if (netmsg->partCount < 2) {