aboutsummaryrefslogtreecommitdiff
path: root/src/fontengine.cpp
blob: f881701e0f2306a2d6786772cd15ebbadf40d7fe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
/*
Minetest
Copyright (C) 2010-2014 sapier <sapier at gmx dot net>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "fontengine.h"
#include "log.h"
#include "config.h"
#include "porting.h"
#include "constants.h"
#include "filesys.h"

#if USE_FREETYPE
#include "gettext.h"
#include "xCGUITTFont.h"
#endif

/** maximum size distance for getting a "similar" font size */
#define MAX_FONT_SIZE_OFFSET 10

/** reference to access font engine, has to be initialized by main */
FontEngine* g_fontengine = NULL;

/** callback to be used on change of font size setting */
static void font_setting_changed(const std::string &name, void *userdata)
{
	g_fontengine->readSettings();
}

/******************************************************************************/
FontEngine::FontEngine(Settings* main_settings, gui::IGUIEnvironment* env) :
	m_settings(main_settings),
	m_env(env),
	m_font_cache(),
	m_currentMode(FM_Standard),
	m_lastMode(),
	m_lastSize(0),
	m_lastFont(NULL)
{

	for (unsigned int i = 0; i < FM_MaxMode; i++) {
		m_default_size[i] = (FontMode) FONT_SIZE_UNSPECIFIED;
	}

	assert(m_settings != NULL); // pre-condition
	assert(m_env != NULL); // pre-condition
	assert(m_env->getSkin() != NULL); // pre-condition

	m_currentMode = FM_Simple;

#if USE_FREETYPE
	if (g_settings->getBool("freetype")) {
		m_default_size[FM_Standard] = m_settings->getU16("font_size");
		m_default_size[FM_Fallback] = m_settings->getU16("fallback_font_size");
		m_default_size[FM_Mono]     = m_settings->getU16("mono_font_size");

		if (is_yes(gettext("needs_fallback_font"))) {
			m_currentMode = FM_Fallback;
		}
		else {
			m_currentMode = FM_Standard;
		}
	}

	// having freetype but not using it is quite a strange case so we need to do
	// special handling for it
	if (m_currentMode == FM_Simple) {
		std::stringstream fontsize;
		fontsize << DEFAULT_FONT_SIZE;
		m_settings->setDefault("font_size", fontsize.str());
		m_settings->setDefault("mono_font_size", fontsize.str());
	}
#endif

	m_default_size[FM_Simple]       = m_settings->getU16("font_size");
	m_default_size[FM_SimpleMono]   = m_settings->getU16("mono_font_size");

	updateSkin();

	if (m_currentMode == FM_Standard) {
		m_settings->registerChangedCallback("font_size", font_setting_changed, NULL);
		m_settings->registerChangedCallback("font_path", font_setting_changed, NULL);
		m_settings->registerChangedCallback("font_shadow", font_setting_changed, NULL);
		m_settings->registerChangedCallback("font_shadow_alpha", font_setting_changed, NULL);
	}
	else if (m_currentMode == FM_Fallback) {
		m_settings->registerChangedCallback("fallback_font_size", font_setting_changed, NULL);
		m_settings->registerChangedCallback("fallback_font_path", font_setting_changed, NULL);
		m_settings->registerChangedCallback("fallback_font_shadow", font_setting_changed, NULL);
		m_settings->registerChangedCallback("fallback_font_shadow_alpha", font_setting_changed, NULL);
	}

	m_settings->registerChangedCallback("mono_font_path", font_setting_changed, NULL);
	m_settings->registerChangedCallback("mono_font_size", font_setting_changed, NULL);
	m_settings->registerChangedCallback("screen_dpi", font_setting_changed, NULL);
	m_settings->registerChangedCallback("gui_scaling", font_setting_changed, NULL);
}

/******************************************************************************/
FontEngine::~FontEngine()
{
	cleanCache();
}

/******************************************************************************/
void FontEngine::cleanCache()
{
	for ( unsigned int i = 0; i < FM_MaxMode; i++) {

		for (std::map<unsigned int, irr::gui::IGUIFont*>::iterator iter
				= m_font_cache[i].begin();
				iter != m_font_cache[i].end(); iter++) {
			iter->second->drop();
			iter->second = NULL;
		}
		m_font_cache[i].clear();
	}
}

/******************************************************************************/
irr::gui::IGUIFont* FontEngine::getFont(unsigned int font_size, FontMode mode)
{
	if (mode == FM_Unspecified) {
		mode = m_currentMode;
	}
	else if ((mode == FM_Mono) && (m_currentMode == FM_Simple)) {
		mode = FM_SimpleMono;
	}

	if (font_size == FONT_SIZE_UNSPECIFIED) {
		font_size = m_default_size[mode];
	}

	if ((font_size == m_lastSize) && (mode == m_lastMode)) {
		return m_lastFont;
	}

	if (m_font_cache[mode].find(font_size) == m_font_cache[mode].end()) {
		initFont(font_size, mode);
	}

	if (m_font_cache[mode].find(font_size) == m_font_cache[mode].end()) {
		return NULL;
	}

	m_lastSize = font_size;
	m_lastMode = mode;
	m_lastFont = m_font_cache[mode][font_size];

	return m_font_cache[mode][font_size];
}

/******************************************************************************/
unsigned int FontEngine::getTextHeight(unsigned int font_size, FontMode mode)
{
	irr::gui::IGUIFont* font = getFont(font_size, mode);

	// use current skin font as fallback
	if (font == NULL) {
		font = m_env->getSkin()->getFont();
	}
	FATAL_ERROR_IF(font == NULL, "Could not get skin font");

	return font->getDimension(L"Some unimportant example String").Height;
}

/******************************************************************************/
unsigned int FontEngine::getTextWidth(const std::wstring& text,
		unsigned int font_size, FontMode mode)
{
	irr::gui::IGUIFont* font = getFont(font_size, mode);

	// use current skin font as fallback
	if (font == NULL) {
		font = m_env->getSkin()->getFont();
	}
	FATAL_ERROR_IF(font == NULL, "Could not get font");

	return font->getDimension(text.c_str()).Width;
}


/** get line height for a specific font (including empty room between lines) */
unsigned int FontEngine::getLineHeight(unsigned int font_size, FontMode mode)
{
	irr::gui::IGUIFont* font = getFont(font_size, mode);

	// use current skin font as fallback
	if (font == NULL) {
		font = m_env->getSkin()->getFont();
	}
	FATAL_ERROR_IF(font == NULL, "Could not get font");

	return font->getDimension(L"Some unimportant example String").Height
			+ font->getKerningHeight();
}

/******************************************************************************/
unsigned int FontEngine::getDefaultFontSize()
{
	return m_default_size[m_currentMode];
}

/******************************************************************************/
void FontEngine::readSettings()
{
#if USE_FREETYPE
	if (g_settings->getBool("freetype")) {
		m_default_size[FM_Standard] = m_settings->getU16("font_size");
		m_default_size[FM_Fallback] = m_settings->getU16("fallback_font_size");
		m_default_size[FM_Mono]     = m_settings->getU16("mono_font_size");

		if (is_yes(gettext("needs_fallback_font"))) {
			m_currentMode = FM_Fallback;
		}
		else {
			m_currentMode = FM_Standard;
		}
	}
#endif
	m_default_size[FM_Simple]       = m_settings->getU16("font_size");
	m_default_size[FM_SimpleMono]   = m_settings->getU16("mono_font_size");

	cleanCache();
	updateFontCache();
	updateSkin();
}

/******************************************************************************/
void FontEngine::updateSkin()
{
	gui::IGUIFont *font = getFont();

	if (font)
		m_env->getSkin()->setFont(font);
	else
		errorstream << "FontEngine: Default font file: " <<
				"\n\t\"" << m_settings->get("font_path") << "\"" <<
				"\n\trequired for current screen configuration was not found" <<
				" or was invalid file format." <<
				"\n\tUsing irrlicht default font." << std::endl;

	// If we did fail to create a font our own make irrlicht find a default one
	font = m_env->getSkin()->getFont();
	FATAL_ERROR_IF(font == NULL, "Could not create/get font");

	u32 text_height = font->getDimension(L"Hello, world!").Height;
	infostream << "text_height=" << text_height << std::endl;
}

/******************************************************************************/
void FontEngine::updateFontCache()
{
	/* the only font to be initialized is default one,
	 * all others are re-initialized on demand */
	initFont(m_default_size[m_currentMode], m_currentMode);

	/* reset font quick access */
	m_lastMode = FM_Unspecified;
	m_lastSize = 0;
	m_lastFont = NULL;
}

/******************************************************************************/
void FontEngine::initFont(unsigned int basesize, FontMode mode)
{

	std::string font_config_prefix;

	if (mode == FM_Unspecified) {
		mode = m_currentMode;
	}

	switch (mode) {

		case FM_Standard:
			font_config_prefix = "";
			break;

		case FM_Fallback:
			font_config_prefix = "fallback_";
			break;

		case FM_Mono:
			font_config_prefix = "mono_";
			if (m_currentMode == FM_Simple)
				mode = FM_SimpleMono;
			break;

		case FM_Simple: /* Fallthrough */
		case FM_SimpleMono: /* Fallthrough */
		default:
			font_config_prefix = "";

	}

	if (m_font_cache[mode].find(basesize) != m_font_cache[mode].end())
		return;

	if ((mode == FM_Simple) || (mode == FM_SimpleMono)) {
		initSimpleFont(basesize, mode);
		return;
	}
#if USE_FREETYPE
	else {
		if (! is_yes(m_settings->get("freetype"))) {
			return;
		}
		unsigned int size = floor(
				porting::getDisplayDensity() *
				m_settings->getFloat("gui_scaling") *
				basesize);
		u32 font_shadow       = 0;
		u32 font_shadow_alpha = 0;

		try {
			font_shadow =
					g_settings->getU16(font_config_prefix + "font_shadow");
		} catch (SettingNotFoundException&) {}
		try {
			font_shadow_alpha =
					g_settings->getU16(font_config_prefix + "font_shadow_alpha");
		} catch (SettingNotFoundException&) {}

		std::string font_path = g_settings->get(font_config_prefix + "font_path");

		irr::gui::IGUIFont* font = gui::CGUITTFont::createTTFont(m_env,
				font_path.c_str(), size, true, true, font_shadow,
				font_shadow_alpha);

		if (font != NULL) {
			m_font_cache[mode][basesize] = font;
		}
		else {
			errorstream << "FontEngine: failed to load freetype font: "
					<< font_path << std::endl;
		}
	}
#endif
}

/** initialize a font without freetype */
void FontEngine::initSimpleFont(unsigned int basesize, FontMode mode)
{
	assert(mode == FM_Simple || mode == FM_SimpleMono); // pre-condition

	std::string font_path = "";
	if (mode == FM_Simple) {
		font_path = m_settings->get("font_path");
	} else {
		font_path = m_settings->get("mono_font_path");
	}
	std::string basename = font_path;
	std::string ending = font_path.substr(font_path.length() -4);

	if (ending == ".ttf") {
		errorstream << "FontEngine: Not trying to open \"" << font_path
				<< "\" which seems to be a truetype font." << std::endl;
		return;
	}

	if ((ending == ".xml") || (ending == ".png")) {
		basename = font_path.substr(0,font_path.length()-4);
	}

	if (basesize == FONT_SIZE_UNSPECIFIED)
		basesize = DEFAULT_FONT_SIZE;

	unsigned int size = floor(
			porting::getDisplayDensity() *
			m_settings->getFloat("gui_scaling") *
			basesize);

	irr::gui::IGUIFont* font = NULL;

	for(unsigned int offset = 0; offset < MAX_FONT_SIZE_OFFSET; offset++) {

		// try opening positive offset
		std::stringstream fontsize_plus_png;
		fontsize_plus_png << basename << "_" << (size + offset) << ".png";

		if (fs::PathExists(fontsize_plus_png.str())) {
			font = m_env->getFont(fontsize_plus_png.str().c_str());

			if (font) {
				verbosestream << "FontEngine: found font: " << fontsize_plus_png.str() << std::endl;
				break;
			}
		}

		std::stringstream fontsize_plus_xml;
		fontsize_plus_xml << basename << "_" << (size + offset) << ".xml";

		if (fs::PathExists(fontsize_plus_xml.str())) {
			font = m_env->getFont(fontsize_plus_xml.str().c_str());

			if (font) {
				verbosestream << "FontEngine: found font: " << fontsize_plus_xml.str() << std::endl;
				break;
			}
		}

		// try negative offset
		std::stringstream fontsize_minus_png;
		fontsize_minus_png << basename << "_" << (size - offset) << ".png";

		if (fs::PathExists(fontsize_minus_png.str())) {
			font = m_env->getFont(fontsize_minus_png.str().c_str());

			if (font) {
				verbosestream << "FontEngine: found font: " << fontsize_minus_png.str() << std::endl;
				break;
			}
		}

		std::stringstream fontsize_minus_xml;
		fontsize_minus_xml << basename << "_" << (size - offset) << ".xml";

		if (fs::PathExists(fontsize_minus_xml.str())) {
			font = m_env->getFont(fontsize_minus_xml.str().c_str());

			if (font) {
				verbosestream << "FontEngine: found font: " << fontsize_minus_xml.str() << std::endl;
				break;
			}
		}
	}

	// try name direct
	if (font == NULL) {
		if (fs::PathExists(font_path)) {
			font = m_env->getFont(font_path.c_str());
			if (font)
				verbosestream << "FontEngine: found font: " << font_path << std::endl;
		}
	}

	if (font != NULL) {
		font->grab();
		m_font_cache[mode][basesize] = font;
	}
}
opt">.DEC) minetest_client_commands[0x23] = { "PLAYERPOS", 34, { f_x, f_y, f_z, f_speed_x, f_speed_y, f_speed_z, f_pitch, f_yaw, f_key_pressed, f_fov, f_wanted_range }, function(buffer, pinfo, tree, t) t:add(f_x, buffer(2,4)) t:add(f_y, buffer(6,4)) t:add(f_z, buffer(10,4)) t:add(f_speed_x, buffer(14,4)) t:add(f_speed_y, buffer(18,4)) t:add(f_speed_z, buffer(22,4)) t:add(f_pitch, buffer(26,4)) t:add(f_yaw, buffer(30,4)) t:add(f_key_pressed, buffer(34,4)) t:add(f_fov, buffer(38,1)) t:add(f_wanted_range, buffer(39,1)) end } end -- TOSERVER_GOTBLOCKS do local f_count = ProtoField.uint8("minetest.client.gotblocks_count", "Count", base.DEC) local f_block = ProtoField.bytes("minetest.client.gotblocks_block", "Block", base.NONE) local f_x = ProtoField.int16("minetest.client.gotblocks_x", "Block position X", base.DEC) local f_y = ProtoField.int16("minetest.client.gotblocks_y", "Block position Y", base.DEC) local f_z = ProtoField.int16("minetest.client.gotblocks_z", "Block position Z", base.DEC) minetest_client_commands[0x24] = { "GOTBLOCKS", 3, { f_count, f_block, f_x, f_y, f_z }, function(buffer, pinfo, tree, t) t:add(f_count, buffer(2,1)) local count = buffer(2,1):uint() if minetest_check_length(buffer, 3 + 6*count, t) then pinfo.cols.info:append(" * " .. count) local index for index = 0, count - 1 do local pos = 3 + 6*index local t2 = t:add(f_block, buffer(pos, 6)) t2:set_text("Block, X: " .. buffer(pos, 2):int() .. ", Y: " .. buffer(pos + 2, 2):int() .. ", Z: " .. buffer(pos + 4, 2):int()) t2:add(f_x, buffer(pos, 2)) t2:add(f_y, buffer(pos + 2, 2)) t2:add(f_z, buffer(pos + 4, 2)) end end end } end -- TOSERVER_DELETEDBLOCKS do local f_count = ProtoField.uint8("minetest.client.deletedblocks_count", "Count", base.DEC) local f_block = ProtoField.bytes("minetest.client.deletedblocks_block", "Block", base.NONE) local f_x = ProtoField.int16("minetest.client.deletedblocks_x", "Block position X", base.DEC) local f_y = ProtoField.int16("minetest.client.deletedblocks_y", "Block position Y", base.DEC) local f_z = ProtoField.int16("minetest.client.deletedblocks_z", "Block position Z", base.DEC) minetest_client_commands[0x25] = { "DELETEDBLOCKS", 3, { f_count, f_block, f_x, f_y, f_z }, function(buffer, pinfo, tree, t) t:add(f_count, buffer(2,1)) local count = buffer(2,1):uint() if minetest_check_length(buffer, 3 + 6*count, t) then pinfo.cols.info:append(" * " .. count) local index for index = 0, count - 1 do local pos = 3 + 6*index local t2 = t:add(f_block, buffer(pos, 6)) t2:set_text("Block, X: " .. buffer(pos, 2):int() .. ", Y: " .. buffer(pos + 2, 2):int() .. ", Z: " .. buffer(pos + 4, 2):int()) t2:add(f_x, buffer(pos, 2)) t2:add(f_y, buffer(pos + 2, 2)) t2:add(f_z, buffer(pos + 4, 2)) end end end } end -- TOSERVER_ADDNODE_FROM_INVENTORY (obsolete) minetest_client_commands[0x26] = { "ADDNODE_FROM_INVENTORY", 2 } minetest_client_obsolete[0x26] = true -- TOSERVER_CLICK_OBJECT (obsolete) minetest_client_commands[0x27] = { "CLICK_OBJECT", 2 } minetest_client_obsolete[0x27] = true -- TOSERVER_GROUND_ACTION (obsolete) minetest_client_commands[0x28] = { "GROUND_ACTION", 2 } minetest_client_obsolete[0x28] = true -- TOSERVER_RELEASE (obsolete) minetest_client_commands[0x29] = { "RELEASE", 2 } minetest_client_obsolete[0x29] = true -- TOSERVER_SIGNTEXT (obsolete) minetest_client_commands[0x30] = { "SIGNTEXT", 2 } minetest_client_obsolete[0x30] = true -- TOSERVER_INVENTORY_ACTION do local f_action = ProtoField.string("minetest.client.inventory_action", "Action") minetest_client_commands[0x31] = { "INVENTORY_ACTION", 2, { f_action }, function(buffer, pinfo, tree, t) t:add(f_action, buffer(2, buffer:len() - 2)) end } end -- TOSERVER_CHAT_MESSAGE do local f_length = ProtoField.uint16("minetest.client.chat_message_length", "Length", base.DEC) local f_message = ProtoField.string("minetest.client.chat_message", "Message") minetest_client_commands[0x32] = { "CHAT_MESSAGE", 4, { f_length, f_message }, function(buffer, pinfo, tree, t) t:add(f_length, buffer(2,2)) local textlen = buffer(2,2):uint() if minetest_check_length(buffer, 4 + textlen*2, t) then t:add(f_message, buffer(4, textlen*2), buffer(4, textlen*2):ustring()) end end } end -- TOSERVER_SIGNNODETEXT (obsolete) minetest_client_commands[0x33] = { "SIGNNODETEXT", 2 } minetest_client_obsolete[0x33] = true -- TOSERVER_CLICK_ACTIVEOBJECT (obsolete) minetest_client_commands[0x34] = { "CLICK_ACTIVEOBJECT", 2 } minetest_client_obsolete[0x34] = true -- TOSERVER_DAMAGE do local f_amount = ProtoField.uint8("minetest.client.damage_amount", "Amount", base.DEC) minetest_client_commands[0x35] = { "DAMAGE", 3, { f_amount }, function(buffer, pinfo, tree, t) t:add(f_amount, buffer(2,1)) end } end -- TOSERVER_PASSWORD (obsolete) minetest_client_commands[0x36] = { "CLICK_ACTIVEOBJECT", 2 } minetest_client_obsolete[0x36] = true -- TOSERVER_PLAYERITEM do local f_item = ProtoField.uint16("minetest.client.playeritem_item", "Wielded item") minetest_client_commands[0x37] = { "PLAYERITEM", 4, { f_item }, function(buffer, pinfo, tree, t) t:add(f_item, buffer(2,2)) end } end -- TOSERVER_RESPAWN minetest_client_commands[0x38] = { "RESPAWN", 2 } -- TOSERVER_INTERACT do local abbr = "minetest.client.interact_" local vs_action = { [0] = "Start digging", [1] = "Stop digging", [2] = "Digging completed", [3] = "Place block or item", [4] = "Use item", [5] = "Activate held item", } local vs_pointed_type = { [0] = "Nothing", [1] = "Node", [2] = "Object", } local f_action = ProtoField.uint8(abbr.."action", "Action", base.DEC, vs_action) local f_item = ProtoField.uint16(abbr.."item", "Item Index", base.DEC) local f_plen = ProtoField.uint32(abbr.."plen", "Length of pointed thing", base.DEC) local f_pointed_version = ProtoField.uint8(abbr.."pointed_version", "Pointed Thing Version", base.DEC) local f_pointed_type = ProtoField.uint8(abbr.."pointed_version", "Pointed Thing Type", base.DEC, vs_pointed_type) local f_pointed_under_x = ProtoField.int16(abbr.."pointed_under_x", "Node position (under surface) X") local f_pointed_under_y = ProtoField.int16(abbr.."pointed_under_y", "Node position (under surface) Y") local f_pointed_under_z = ProtoField.int16(abbr.."pointed_under_z", "Node position (under surface) Z") local f_pointed_above_x = ProtoField.int16(abbr.."pointed_above_x", "Node position (above surface) X") local f_pointed_above_y = ProtoField.int16(abbr.."pointed_above_y", "Node position (above surface) Y") local f_pointed_above_z = ProtoField.int16(abbr.."pointed_above_z", "Node position (above surface) Z") local f_pointed_object_id = ProtoField.int16(abbr.."pointed_object_id", "Object ID") -- mising: additional playerpos data just like in TOSERVER_PLAYERPOS minetest_client_commands[0x39] = { "INTERACT", 11, { f_action, f_item, f_plen, f_pointed_version, f_pointed_type, f_pointed_under_x, f_pointed_under_y, f_pointed_under_z, f_pointed_above_x, f_pointed_above_y, f_pointed_above_z, f_pointed_object_id }, function(buffer, pinfo, tree, t) t:add(f_action, buffer(2,1)) t:add(f_item, buffer(3,2)) t:add(f_plen, buffer(5,4)) local plen = buffer(5,4):uint() if minetest_check_length(buffer, 9 + plen, t) then t:add(f_pointed_version, buffer(9,1)) t:add(f_pointed_type, buffer(10,1)) local ptype = buffer(10,1):uint() if ptype == 1 then -- Node t:add(f_pointed_under_x, buffer(11,2)) t:add(f_pointed_under_y, buffer(13,2)) t:add(f_pointed_under_z, buffer(15,2)) t:add(f_pointed_above_x, buffer(17,2)) t:add(f_pointed_above_y, buffer(19,2)) t:add(f_pointed_above_z, buffer(21,2)) elseif ptype == 2 then -- Object t:add(f_pointed_object_id, buffer(11,2)) end end end } end -- ... minetest_client_commands[0x3a] = { "REMOVED_SOUNDS", 2 } minetest_client_commands[0x3b] = { "NODEMETA_FIELDS", 2 } minetest_client_commands[0x3c] = { "INVENTORY_FIELDS", 2 } minetest_client_commands[0x40] = { "REQUEST_MEDIA", 2 } minetest_client_commands[0x41] = { "RECEIVED_MEDIA", 2 } -- TOSERVER_BREATH (obsolete) minetest_client_commands[0x42] = { "BREATH", 2 } minetest_client_obsolete[0x42] = true -- TOSERVER_CLIENT_READY do local abbr = "minetest.client.client_ready_" local f_major = ProtoField.uint8(abbr.."major","Version Major") local f_minor = ProtoField.uint8(abbr.."minor","Version Minor") local f_patch = ProtoField.uint8(abbr.."patch","Version Patch") local f_reserved = ProtoField.uint8(abbr.."reserved","Reserved") local f_versionlen, f_version = minetest_field_helper("uint16", abbr.."version", "Full Version String") local f_formspec_ver = ProtoField.uint16(abbr.."formspec_version", "Formspec API version") minetest_client_commands[0x43] = { "CLIENT_READY", 8, { f_major, f_minor, f_patch, f_reserved, f_versionlen, f_version, f_formspec_ver }, function(buffer, pinfo, tree, t) t:add(f_major, buffer(2,1)) t:add(f_minor, buffer(3,1)) t:add(f_patch, buffer(4,1)) t:add(f_reserved, buffer(5,1)) local off = minetest_decode_helper_ascii(buffer, t, "uint16", 6, f_versionlen, f_version) if off and minetest_check_length(buffer, off + 2, t) then t:add(f_formspec_ver, buffer(off,2)) end end } end -- ... minetest_client_commands[0x50] = { "FIRST_SRP", 2 } minetest_client_commands[0x51] = { "SRP_BYTES_A", 2 } minetest_client_commands[0x52] = { "SRP_BYTES_M", 2 } -------------------------------------------- -- Part 3 -- -- Server command dissectors (TOCLIENT_*) -- -------------------------------------------- minetest_server_commands = {} minetest_server_obsolete = {} -- TOCLIENT_HELLO do local abbr = "minetest.server.hello_" local f_ser_fmt = ProtoField.uint8(abbr.."ser_version", "Deployed serialization format version", base.DEC) local f_comp_mode = ProtoField.uint16(abbr.."compression", "Deployed compression mode", base.DEC, { [0] = "No compression" }) local f_proto = ProtoField.uint16(abbr.."proto", "Deployed protocol version", base.DEC) local f_auth_methods = ProtoField.bytes(abbr.."auth_modes", "Supported authentication modes") local f_legacy_namelen, f_legacy_name = minetest_field_helper("uint16", abbr.."legacy_name", "Legacy player name for hashing") minetest_server_commands[0x02] = { "HELLO", 13, { f_ser_fmt, f_comp_mode, f_proto, f_auth_methods, f_legacy_namelen, f_legacy_name }, function(buffer, pinfo, tree, t) t:add(f_ser_fmt, buffer(2,1)) t:add(f_comp_mode, buffer(3,2)) t:add(f_proto, buffer(5,2)) t:add(f_auth_methods, buffer(7,4)) minetest_decode_helper_ascii(buffer, t, "uint16", 11, f_legacy_namelen, f_legacy_name) end } end -- TOCLIENT_AUTH_ACCEPT do local abbr = "minetest.server.auth_accept_" local f_player_x = ProtoField.float(abbr.."player_x", "Player position X") local f_player_y = ProtoField.float(abbr.."player_y", "Player position Y") local f_player_z = ProtoField.float(abbr.."player_z", "Player position Z") local f_map_seed = ProtoField.uint64(abbr.."map_seed", "Map seed") local f_send_interval = ProtoField.float(abbr.."send_interval", "Recommended send interval") local f_sudo_auth_methods = ProtoField.bytes(abbr.."sudo_auth_methods", "Supported auth methods for sudo mode") minetest_server_commands[0x03] = { "AUTH_ACCEPT", 30, { f_player_x, f_player_y, f_player_z, f_map_seed, f_send_interval, f_sudo_auth_methods }, function(buffer, pinfo, tree, t) t:add(f_player_x, buffer(2,4)) t:add(f_player_y, buffer(6,4)) t:add(f_player_z, buffer(10,4)) t:add(f_map_seed, buffer(14,8)) t:add(f_send_interval, buffer(22,4)) t:add(f_sudo_auth_methods, buffer(26,4)) end } end -- ... minetest_server_commands[0x04] = {"ACCEPT_SUDO_MODE", 2} minetest_server_commands[0x05] = {"DENY_SUDO_MODE", 2} minetest_server_commands[0x0A] = {"ACCESS_DENIED", 2} -- TOCLIENT_INIT (obsolete) minetest_server_commands[0x10] = { "INIT", 2 } minetest_server_obsolete[0x10] = true -- TOCLIENT_BLOCKDATA do local f_x = ProtoField.int16("minetest.server.blockdata_x", "Block position X", base.DEC) local f_y = ProtoField.int16("minetest.server.blockdata_y", "Block position Y", base.DEC) local f_z = ProtoField.int16("minetest.server.blockdata_z", "Block position Z", base.DEC) local f_data = ProtoField.bytes("minetest.server.blockdata_block", "Serialized MapBlock") minetest_server_commands[0x20] = { "BLOCKDATA", 8, { f_x, f_y, f_z, f_data }, function(buffer, pinfo, tree, t) t:add(f_x, buffer(2,2)) t:add(f_y, buffer(4,2)) t:add(f_z, buffer(6,2)) t:add(f_data, buffer(8, buffer:len() - 8)) end } end -- TOCLIENT_ADDNODE do local f_x = ProtoField.int16("minetest.server.addnode_x", "Position X", base.DEC) local f_y = ProtoField.int16("minetest.server.addnode_y", "Position Y", base.DEC) local f_z = ProtoField.int16("minetest.server.addnode_z", "Position Z", base.DEC) local f_data = ProtoField.bytes("minetest.server.addnode_node", "Serialized MapNode") minetest_server_commands[0x21] = { "ADDNODE", 8, { f_x, f_y, f_z, f_data }, function(buffer, pinfo, tree, t) t:add(f_x, buffer(2,2)) t:add(f_y, buffer(4,2)) t:add(f_z, buffer(6,2)) t:add(f_data, buffer(8, buffer:len() - 8)) end } end -- TOCLIENT_REMOVENODE do local f_x = ProtoField.int16("minetest.server.removenode_x", "Position X", base.DEC) local f_y = ProtoField.int16("minetest.server.removenode_y", "Position Y", base.DEC) local f_z = ProtoField.int16("minetest.server.removenode_z", "Position Z", base.DEC) minetest_server_commands[0x22] = { "REMOVENODE", 8, { f_x, f_y, f_z }, function(buffer, pinfo, tree, t) t:add(f_x, buffer(2,2)) t:add(f_y, buffer(4,2)) t:add(f_z, buffer(6,2)) end } end -- TOCLIENT_PLAYERPOS (obsolete) minetest_server_commands[0x23] = { "PLAYERPOS", 2 } minetest_server_obsolete[0x23] = true -- TOCLIENT_PLAYERINFO (obsolete) minetest_server_commands[0x24] = { "PLAYERINFO", 2 } minetest_server_obsolete[0x24] = true -- TOCLIENT_OPT_BLOCK_NOT_FOUND (obsolete) minetest_server_commands[0x25] = { "OPT_BLOCK_NOT_FOUND", 2 } minetest_server_obsolete[0x25] = true -- TOCLIENT_SECTORMETA (obsolete) minetest_server_commands[0x26] = { "SECTORMETA", 2 } minetest_server_obsolete[0x26] = true -- TOCLIENT_INVENTORY do local f_inventory = ProtoField.string("minetest.server.inventory", "Inventory") minetest_server_commands[0x27] = { "INVENTORY", 2, { f_inventory }, function(buffer, pinfo, tree, t) t:add(f_inventory, buffer(2, buffer:len() - 2)) end } end -- TOCLIENT_OBJECTDATA (obsolete) minetest_server_commands[0x28] = { "OBJECTDATA", 2 } minetest_server_obsolete[0x28] = true -- TOCLIENT_TIME_OF_DAY do local f_time = ProtoField.uint16("minetest.server.time_of_day", "Time", base.DEC) local f_time_speed = ProtoField.float("minetest.server.time_speed", "Time Speed", base.DEC) minetest_server_commands[0x29] = { "TIME_OF_DAY", 4, { f_time, f_time_speed }, function(buffer, pinfo, tree, t) t:add(f_time, buffer(2,2)) t:add(f_time_speed, buffer(4,4)) end } end -- TOCLIENT_CSM_RESTRICTION_FLAGS minetest_server_commands[0x2a] = { "CSM_RESTRICTION_FLAGS", 2 } -- TOCLIENT_PLAYER_SPEED minetest_server_commands[0x2b] = { "PLAYER_SPEED", 2 } -- TOCLIENT_CHAT_MESSAGE do local abbr = "minetest.server.chat_message_" local vs_type = { [0] = "Raw", [1] = "Normal", [2] = "Announce", [3] = "System", } local f_version = ProtoField.uint8(abbr.."version", "Version") local f_type = ProtoField.uint8(abbr.."type", "Message Type", base.DEC, vs_type) local f_senderlen, f_sender = minetest_field_helper("uint16", abbr.."sender", "Message sender") local f_messagelen, f_message = minetest_field_helper("uint16", abbr:sub(1,-2), "Message") minetest_server_commands[0x2f] = { "CHAT_MESSAGE", 8, { f_version, f_type, f_senderlen, f_sender, f_messagelen, f_message }, function(buffer, pinfo, tree, t) t:add(f_version, buffer(2,1)) t:add(f_type, buffer(3,1)) local off = 4 off = minetest_decode_helper_utf16(buffer, t, "uint16", off, f_senderlen, f_sender) if off then off = minetest_decode_helper_utf16(buffer, t, "uint16", off, f_messagelen, f_message) end end } end -- TOCLIENT_CHAT_MESSAGE_OLD (obsolete) minetest_server_commands[0x30] = { "CHAT_MESSAGE_OLD", 2 } minetest_server_obsolete[0x30] = true -- TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD do local f_removed_count = ProtoField.uint16( "minetest.server.active_object_remove_add_removed_count", "Count of removed objects", base.DEC) local f_removed = ProtoField.bytes( "minetest.server.active_object_remove_add_removed", "Removed object") local f_removed_id = ProtoField.uint16( "minetest.server.active_object_remove_add_removed_id", "ID", base.DEC) local f_added_count = ProtoField.uint16( "minetest.server.active_object_remove_add_added_count", "Count of added objects", base.DEC) local f_added = ProtoField.bytes( "minetest.server.active_object_remove_add_added", "Added object") local f_added_id = ProtoField.uint16( "minetest.server.active_object_remove_add_added_id", "ID", base.DEC) local f_added_type = ProtoField.uint8( "minetest.server.active_object_remove_add_added_type", "Type", base.DEC) local f_added_init_length = ProtoField.uint32( "minetest.server.active_object_remove_add_added_init_length", "Initialization data length", base.DEC) local f_added_init_data = ProtoField.bytes( "minetest.server.active_object_remove_add_added_init_data", "Initialization data") minetest_server_commands[0x31] = { "ACTIVE_OBJECT_REMOVE_ADD", 6, { f_removed_count, f_removed, f_removed_id, f_added_count, f_added, f_added_id, f_added_type, f_added_init_length, f_added_init_data }, function(buffer, pinfo, tree, t) local t2, index, pos local removed_count_pos = 2 local removed_count = buffer(removed_count_pos, 2):uint() t:add(f_removed_count, buffer(removed_count_pos, 2)) local added_count_pos = removed_count_pos + 2 + 2 * removed_count if not minetest_check_length(buffer, added_count_pos + 2, t) then return end -- Loop through removed active objects for index = 0, removed_count - 1 do pos = removed_count_pos + 2 + 2 * index t2 = t:add(f_removed, buffer(pos, 2)) t2:set_text("Removed object, ID = " .. buffer(pos, 2):uint()) t2:add(f_removed_id, buffer(pos, 2)) end local added_count = buffer(added_count_pos, 2):uint() t:add(f_added_count, buffer(added_count_pos, 2)) -- Loop through added active objects pos = added_count_pos + 2 for index = 0, added_count - 1 do if not minetest_check_length(buffer, pos + 7, t) then return end local init_length = buffer(pos + 3, 4):uint() if not minetest_check_length(buffer, pos + 7 + init_length, t) then return end t2 = t:add(f_added, buffer(pos, 7 + init_length)) t2:set_text("Added object, ID = " .. buffer(pos, 2):uint()) t2:add(f_added_id, buffer(pos, 2)) t2:add(f_added_type, buffer(pos + 2, 1)) t2:add(f_added_init_length, buffer(pos + 3, 4)) t2:add(f_added_init_data, buffer(pos + 7, init_length)) pos = pos + 7 + init_length end pinfo.cols.info:append(" * " .. (removed_count + added_count)) end } end -- TOCLIENT_ACTIVE_OBJECT_MESSAGES do local f_object_count = ProtoField.uint16( "minetest.server.active_object_messages_object_count", "Count of objects", base.DEC) local f_object = ProtoField.bytes( "minetest.server.active_object_messages_object", "Object") local f_object_id = ProtoField.uint16( "minetest.server.active_object_messages_id", "ID", base.DEC) local f_message_length = ProtoField.uint16( "minetest.server.active_object_messages_message_length", "Message length", base.DEC) local f_message = ProtoField.bytes( "minetest.server.active_object_messages_message", "Message") minetest_server_commands[0x32] = { "ACTIVE_OBJECT_MESSAGES", 2, { f_object_count, f_object, f_object_id, f_message_length, f_message }, function(buffer, pinfo, tree, t) local t2, count, pos, message_length count = 0 pos = 2 while pos < buffer:len() do if not minetest_check_length(buffer, pos + 4, t) then return end message_length = buffer(pos + 2, 2):uint() if not minetest_check_length(buffer, pos + 4 + message_length, t) then return end count = count + 1 pos = pos + 4 + message_length end pinfo.cols.info:append(" * " .. count) t:add(f_object_count, count):set_generated() pos = 2 while pos < buffer:len() do message_length = buffer(pos + 2, 2):uint() t2 = t:add(f_object, buffer(pos, 4 + message_length)) t2:set_text("Object, ID = " .. buffer(pos, 2):uint()) t2:add(f_object_id, buffer(pos, 2)) t2:add(f_message_length, buffer(pos + 2, 2)) t2:add(f_message, buffer(pos + 4, message_length)) pos = pos + 4 + message_length end end } end -- TOCLIENT_HP do local f_hp = ProtoField.uint16("minetest.server.hp", "Health points", base.DEC) minetest_server_commands[0x33] = { "HP", 4, { f_hp }, function(buffer, pinfo, tree, t) t:add(f_hp, buffer(2,2)) end } end -- TOCLIENT_MOVE_PLAYER do local abbr = "minetest.server.move_player_" local f_x = ProtoField.float(abbr.."x", "Position X") local f_y = ProtoField.float(abbr.."y", "Position Y") local f_z = ProtoField.float(abbr.."z", "Position Z") local f_pitch = ProtoField.float(abbr.."_pitch", "Pitch") local f_yaw = ProtoField.float(abbr.."yaw", "Yaw") minetest_server_commands[0x34] = { "MOVE_PLAYER", 22, { f_x, f_y, f_z, f_pitch, f_yaw, f_garbage }, function(buffer, pinfo, tree, t) t:add(f_x, buffer(2, 4)) t:add(f_y, buffer(6, 4)) t:add(f_z, buffer(10, 4)) t:add(f_pitch, buffer(14, 4)) t:add(f_yaw, buffer(18, 4)) end } end -- TOCLIENT_ACCESS_DENIED_LEGACY do local f_reason_length = ProtoField.uint16("minetest.server.access_denied_reason_length", "Reason length", base.DEC) local f_reason = ProtoField.string("minetest.server.access_denied_reason", "Reason") minetest_server_commands[0x35] = { "ACCESS_DENIED_LEGACY", 4, { f_reason_length, f_reason }, function(buffer, pinfo, tree, t) t:add(f_reason_length, buffer(2,2)) local reason_length = buffer(2,2):uint() if minetest_check_length(buffer, 4 + reason_length * 2, t) then t:add(f_reason, minetest_convert_utf16(buffer(4, reason_length * 2), "Converted reason message")) end end } end -- TOCLIENT_FOV minetest_server_commands[0x36] = { "FOV", 2 } -- TOCLIENT_DEATHSCREEN do local f_set_camera_point_target = ProtoField.bool( "minetest.server.deathscreen_set_camera_point_target", "Set camera point target") local f_camera_point_target_x = ProtoField.int32( "minetest.server.deathscreen_camera_point_target_x", "Camera point target X", base.DEC) local f_camera_point_target_y = ProtoField.int32( "minetest.server.deathscreen_camera_point_target_y", "Camera point target Y", base.DEC) local f_camera_point_target_z = ProtoField.int32( "minetest.server.deathscreen_camera_point_target_z", "Camera point target Z", base.DEC) minetest_server_commands[0x37] = { "DEATHSCREEN", 15, { f_set_camera_point_target, f_camera_point_target_x, f_camera_point_target_y, f_camera_point_target_z}, function(buffer, pinfo, tree, t) t:add(f_set_camera_point_target, buffer(2,1)) t:add(f_camera_point_target_x, buffer(3,4)) t:add(f_camera_point_target_y, buffer(7,4)) t:add(f_camera_point_target_z, buffer(11,4)) end } end -- TOCLIENT_MEDIA minetest_server_commands[0x38] = {"MEDIA", 2} -- TOCLIENT_TOOLDEF (obsolete) minetest_server_commands[0x39] = {"TOOLDEF", 2} minetest_server_obsolete[0x39] = true -- TOCLIENT_NODEDEF minetest_server_commands[0x3a] = {"NODEDEF", 2} -- TOCLIENT_CRAFTITEMDEF (obsolete) minetest_server_commands[0x3b] = {"CRAFTITEMDEF", 2} minetest_server_obsolete[0x3b] = true -- ... minetest_server_commands[0x3c] = {"ANNOUNCE_MEDIA", 2} minetest_server_commands[0x3d] = {"ITEMDEF", 2} minetest_server_commands[0x3f] = {"PLAY_SOUND", 2} minetest_server_commands[0x40] = {"STOP_SOUND", 2} minetest_server_commands[0x41] = {"PRIVILEGES", 2} minetest_server_commands[0x42] = {"INVENTORY_FORMSPEC", 2} minetest_server_commands[0x43] = {"DETACHED_INVENTORY", 2} minetest_server_commands[0x44] = {"SHOW_FORMSPEC", 2} minetest_server_commands[0x45] = {"MOVEMENT", 2} minetest_server_commands[0x46] = {"SPAWN_PARTICLE", 2} minetest_server_commands[0x47] = {"ADD_PARTICLE_SPAWNER", 2} -- TOCLIENT_DELETE_PARTICLESPAWNER_LEGACY (obsolete) minetest_server_commands[0x48] = {"DELETE_PARTICLESPAWNER_LEGACY", 2} minetest_server_obsolete[0x48] = true -- ... minetest_server_commands[0x49] = {"HUDADD", 2} minetest_server_commands[0x4a] = {"HUDRM", 2} minetest_server_commands[0x4b] = {"HUDCHANGE", 2} minetest_server_commands[0x4c] = {"HUD_SET_FLAGS", 2} minetest_server_commands[0x4d] = {"HUD_SET_PARAM", 2} minetest_server_commands[0x4e] = {"BREATH", 2} minetest_server_commands[0x4f] = {"SET_SKY", 2} minetest_server_commands[0x50] = {"OVERRIDE_DAY_NIGHT_RATIO", 2} minetest_server_commands[0x51] = {"LOCAL_PLAYER_ANIMATIONS", 2} minetest_server_commands[0x52] = {"EYE_OFFSET", 2} minetest_server_commands[0x53] = {"DELETE_PARTICLESPAWNER", 2} minetest_server_commands[0x54] = {"CLOUD_PARAMS", 2} minetest_server_commands[0x55] = {"FADE_SOUND", 2} -- TOCLIENT_UPDATE_PLAYER_LIST do local abbr = "minetest.server.update_player_list_" local vs_type = { [0] = "Init", [1] = "Add", [2] = "Remove", } local f_type = ProtoField.uint8(abbr.."type", "Type", base.DEC, vs_type) local f_count = ProtoField.uint16(abbr.."count", "Number of players", base.DEC) local f_name = ProtoField.string(abbr.."name", "Name") minetest_server_commands[0x56] = { "UPDATE_PLAYER_LIST", 5, { f_type, f_count, f_name }, function(buffer, pinfo, tree, t) t:add(f_type, buffer(2,1)) t:add(f_count, buffer(3,2)) local count = buffer(3,2):uint() local off = 5 for i = 1, count do if not minetest_check_length(buffer, off + 2, t) then return end off = minetest_decode_helper_ascii(buffer, t, "uint16", off, nil, f_name) if not off then return end end end } end -- ... minetest_server_commands[0x57] = {"MODCHANNEL_MSG", 2} minetest_server_commands[0x58] = {"MODCHANNEL_SIGNAL", 2} minetest_server_commands[0x59] = {"NODEMETA_CHANGED", 2} minetest_server_commands[0x5a] = {"SET_SUN", 2} minetest_server_commands[0x5b] = {"SET_MOON", 2} minetest_server_commands[0x5c] = {"SET_STARS", 2} minetest_server_commands[0x60] = {"SRP_BYTES_S_B", 2} minetest_server_commands[0x61] = {"FORMSPEC_PREPEND", 2} ------------------------------------ -- Part 4 -- -- Wrapper protocol subdissectors -- ------------------------------------ -- minetest.control dissector do local p_control = Proto("minetest.control", "Minetest Control") local vs_control_type = { [0] = "Ack", [1] = "Set Peer ID", [2] = "Ping", [3] = "Disco" } local f_control_type = ProtoField.uint8("minetest.control.type", "Control Type", base.DEC, vs_control_type) local f_control_ack = ProtoField.uint16("minetest.control.ack", "ACK sequence number", base.DEC) local f_control_peerid = ProtoField.uint8("minetest.control.peerid", "New peer ID", base.DEC) p_control.fields = { f_control_type, f_control_ack, f_control_peerid } local data_dissector = Dissector.get("data") function p_control.dissector(buffer, pinfo, tree) local t = tree:add(p_control, buffer(0,1)) t:add(f_control_type, buffer(0,1)) pinfo.cols.info = "Control message" local pos = 1 if buffer(0,1):uint() == 0 then pos = 3 t:set_len(3) t:add(f_control_ack, buffer(1,2)) pinfo.cols.info = "Ack " .. buffer(1,2):uint() elseif buffer(0,1):uint() == 1 then pos = 3 t:set_len(3) t:add(f_control_peerid, buffer(1,2)) pinfo.cols.info = "Set peer ID " .. buffer(1,2):uint() elseif buffer(0,1):uint() == 2 then pinfo.cols.info = "Ping" elseif buffer(0,1):uint() == 3 then pinfo.cols.info = "Disco" end data_dissector:call(buffer(pos):tvb(), pinfo, tree) end end -- minetest.client dissector -- minetest.server dissector -- Defines the minetest.client or minetest.server Proto. These two protocols -- are created by the same function because they are so similar. -- Parameter: proto: the Proto object -- Parameter: this_peer: "Client" or "Server" -- Parameter: other_peer: "Server" or "Client" -- Parameter: commands: table of command information, built above -- Parameter: obsolete: table of obsolete commands, built above function minetest_define_client_or_server_proto(is_client) -- Differences between minetest.client and minetest.server local proto_name, this_peer, other_peer, empty_message_info local commands, obsolete if is_client then proto_name = "minetest.client" this_peer = "Client" other_peer = "Server" empty_message_info = "Empty message / Connect" commands = minetest_client_commands -- defined in Part 2 obsolete = minetest_client_obsolete -- defined in Part 2 else proto_name = "minetest.server" this_peer = "Server" other_peer = "Client" empty_message_info = "Empty message" commands = minetest_server_commands -- defined in Part 3 obsolete = minetest_server_obsolete -- defined in Part 3 end -- Create the protocol object. local proto = Proto(proto_name, "Minetest " .. this_peer .. " to " .. other_peer) -- Create a table vs_command that maps command codes to command names. local vs_command = {} local code, command_info for code, command_info in pairs(commands) do local command_name = command_info[1] vs_command[code] = "TO" .. other_peer:upper() .. "_" .. command_name end -- Field definitions local f_command = ProtoField.uint16(proto_name .. ".command", "Command", base.HEX, vs_command) local f_empty = ProtoField.bool(proto_name .. ".empty", "Is empty", BASE_NONE) proto.fields = { f_command, f_empty } -- Add command-specific fields to the protocol for code, command_info in pairs(commands) do local command_fields = command_info[3] if command_fields ~= nil then for index, field in ipairs(command_fields) do assert(field ~= nil) table.insert(proto.fields, field) end end end -- minetest.client or minetest.server dissector function function proto.dissector(buffer, pinfo, tree) local t = tree:add(proto, buffer) pinfo.cols.info = this_peer if buffer:len() == 0 then -- Empty message. t:add(f_empty, 1):set_generated() pinfo.cols.info:append(": " .. empty_message_info) elseif minetest_check_length(buffer, 2, t) then -- Get the command code. t:add(f_command, buffer(0,2)) local code = buffer(0,2):uint() local command_info = commands[code] if command_info == nil then -- Error: Unknown command. pinfo.cols.info:append(": Unknown command") t:add_expert_info(PI_UNDECODED, PI_WARN, "Unknown " .. this_peer .. " to " .. other_peer .. " command") else -- Process a known command local command_name = command_info[1] local command_min_length = command_info[2] local command_fields = command_info[3] local command_dissector = command_info[4] if minetest_check_length(buffer, command_min_length, t) then pinfo.cols.info:append(": " .. command_name) if command_dissector ~= nil then command_dissector(buffer, pinfo, tree, t) end end if obsolete[code] then t:add_expert_info(PI_REQUEST_CODE, PI_WARN, "Obsolete command.") end end end end end minetest_define_client_or_server_proto(true) -- minetest.client minetest_define_client_or_server_proto(false) -- minetest.server -- minetest.split dissector do local p_split = Proto("minetest.split", "Minetest Split Message") local f_split_seq = ProtoField.uint16("minetest.split.seq", "Sequence number", base.DEC) local f_split_chunkcount = ProtoField.uint16("minetest.split.chunkcount", "Chunk count", base.DEC) local f_split_chunknum = ProtoField.uint16("minetest.split.chunknum", "Chunk number", base.DEC) local f_split_data = ProtoField.bytes("minetest.split.data", "Split message data") p_split.fields = { f_split_seq, f_split_chunkcount, f_split_chunknum, f_split_data } function p_split.dissector(buffer, pinfo, tree) local t = tree:add(p_split, buffer(0,6)) t:add(f_split_seq, buffer(0,2)) t:add(f_split_chunkcount, buffer(2,2)) t:add(f_split_chunknum, buffer(4,2)) t:add(f_split_data, buffer(6)) pinfo.cols.info:append(" " .. buffer(0,2):uint() .. " chunk " .. buffer(4,2):uint() .. "/" .. buffer(2,2):uint()) end end ------------------------------------- -- Part 5 -- -- Wrapper protocol main dissector --