Scandum 6 gadi atpakaļ
vecāks
revīzija
effbbdbc0f
65 mainītis faili ar 61561 papildinājumiem un 243 dzēšanām
  1. 101 0
      Makefile.in
  2. 18 6
      NEWS
  3. 19 0
      SCRIPTS
  4. 49 101
      TODO
  5. 597 0
      advertise.c
  6. 1153 0
      buffer.c
  7. 2590 0
      chat.c
  8. 381 0
      class.c
  9. 1061 0
      config.c
  10. 261 0
      config.h
  11. 1746 0
      cursor.c
  12. 672 0
      daemon.c
  13. 1159 0
      data.c
  14. 116 0
      debug.c
  15. 186 0
      dict.c
  16. 25 0
      dict.h
  17. 249 130
      docs/help.html
  18. 5 4
      docs/tintin19.txt
  19. 1790 0
      draw.c
  20. 453 0
      event.c
  21. 535 0
      files.c
  22. 180 0
      forkpty.c
  23. 65 0
      function.c
  24. 3546 0
      help.c
  25. 238 0
      history.c
  26. 821 0
      input.c
  27. 559 0
      line.c
  28. 617 0
      list.c
  29. 543 0
      log.c
  30. 800 0
      main.c
  31. 8175 0
      mapper.c
  32. 1393 0
      math.c
  33. 42 0
      mccp.c
  34. 391 0
      memory.c
  35. 286 0
      misc.c
  36. 171 2
      mods/igr.mods
  37. 976 0
      msdp.c
  38. 1197 0
      nest.c
  39. 527 0
      net.c
  40. 1078 0
      parse.c
  41. 980 0
      path.c
  42. 1222 0
      port.c
  43. 1200 0
      regex.c
  44. 447 0
      scan.c
  45. 1520 0
      screen.c
  46. 733 0
      session.c
  47. 547 0
      show.c
  48. 342 0
      split.c
  49. 408 0
      ssl.c
  50. 1947 0
      substitute.c
  51. 344 0
      system.c
  52. 2117 0
      tables.c
  53. 149 0
      telnet.h
  54. 2335 0
      telopt_client.c
  55. 1163 0
      telopt_server.c
  56. 239 0
      terminal.c
  57. 563 0
      text.c
  58. 2873 0
      tintin.h
  59. 1229 0
      tokenize.c
  60. 989 0
      trigger.c
  61. 1172 0
      update.c
  62. 1174 0
      utf8.c
  63. 498 0
      utils.c
  64. 1404 0
      variable.c
  65. 1195 0
      vt102.c

+ 101 - 0
Makefile.in

@@ -0,0 +1,101 @@
+###############################################################################
+#   This file is part of TinTin++                                             #
+#                                                                             #
+#   Copyright 2004-2019 Igor van den Hoven                                    #
+#                                                                             #
+#   TinTin++ is free software; you can redistribute it and/or modify          #
+#   it under the terms of the GNU General Public License as published by      #
+#   the Free Software Foundation; either version 3 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 General Public License for more details.                              #
+#                                                                             #
+#                                                                             #
+#   You should have received a copy of the GNU General Public License         #
+#   along with TinTin++.  If not, see https://www.gnu.org/licenses.           #
+###############################################################################
+
+DEFINES = -D_GNU_SOURCE @DEFS@
+
+CC = @CC@ -g
+MAKE = @MAKE@
+
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+bindir = ${DESTDIR}@bindir@
+docdir = @prefix@/doc
+
+#this is the standard CFLAGS options, this is what most people should use
+
+CFLAGS += $(DEFINES) @BIG5@
+
+LDFLAGS = @LDFLAGS@
+
+INCS = @MYINCLUDE@
+
+LIBS = @LIBS@
+
+# If you prefer to be able to enter ISO latin-1 characters instead
+# of being able to use the meta key for special emacs-like commands
+# uncomment the following line.
+#F1 = -DUSE_ISO_LATIN_ONE
+
+# If you need to make connections through a SOCKS server, uncomment
+# these 2 lines.  You also need to modify LIBSOCKS to point to the location
+# of your SOCKS library.
+#F6 = -DSOCKS
+#LIBSOCKS = -lsocks5
+
+
+# DEFAULT_FILE_DIR is the path to tintin files. 
+# DEFAULT_FILE_DIR = @HOME@
+DEFAULT_FILE_DIR = $(HOME)
+
+FFLAGS= $(F1) $(F6)
+
+
+OFILES = \
+files.o help.o trigger.o input.o main.o misc.o net.o parse.o debug.o \
+update.o history.o vt102.o terminal.o text.o memory.o math.o split.o \
+system.o mapper.o tables.o buffer.o event.o tokenize.o chat.o utf8.o \
+advertise.o list.o forkpty.o utils.o line.o data.o variable.o msdp.o \
+port.o scan.o telopt_client.o screen.o cursor.o nest.o show.o mccp.o \
+telopt_server.o draw.o log.o path.o session.o class.o config.o ssl.o \
+regex.o substitute.o daemon.o dict.o
+
+default: all
+
+all:	tt++
+
+
+tt++:	Makefile $(OFILES) tintin.h
+	@echo "---- Linking..."
+	$(CC) $(INCS) $(CFLAGS) $(FFLAGS) $(LDFLAGS) -o $@ $(OFILES) $(LIBS) ${LIBSOCKS}
+
+
+Makefile: Makefile.in
+	@echo "You need to run ./configure first; then try make."
+	@exit 1
+
+
+install: tt++
+	-mkdir -p $(bindir)
+	cp -f tt++ $(bindir)
+#	-mkdir -p $(docdir)
+#	cp ../docs/* ../[A-Z][A-Z]* $(docdir)
+
+
+# Autocompile all .c files into .o files using this rule:
+.c.o:
+	$(CC) $(PIPE) $(CFLAGS) $(FFLAGS) $(INCS) -c $<
+
+
+clean:
+	rm -f *.o *~ ../*~ ../mods/*~ tt++*
+
+
+distclean:
+	rm -f *.o *~ ../*~ ../mods/*~ tt++* Makefile config.h config.status config.log

+ 18 - 6
NEWS

@@ -1,6 +1,23 @@
 Due to continuous improvements old tintin scripts aren't always compatible
 with new versions. This document tries to list most compatibility conflicts.
 
+TinTin++ 2.01.93
+----------------
+
+01) #format <var> %C needs to be changed to #screen get cols <var>
+    #format <var> %R needs to be changed to #screen get rows <var>
+    #format <var> %S needs to be changed to #info session save
+
+02) If you were using ${variable}[1] this needs to be changed to
+    ${variable[1]} to have the same behavior.
+
+03) $variable() *variable() and &variable() are reserved until further notice,
+    use ${variable}( for proper behavior.
+
+04) %+ should no longer be used in regular expressions.
+
+    You can replace '%+' with '%+1..a', see #help regex for more information.
+
 TinTin++ 2.01.92
 ----------------
 
@@ -17,12 +34,7 @@ TinTin++ 2.01.92
 
 04) #forall has been removed.
 
-05) %. and %+ should no longer be used in regular expressions. This will
-    allow expansion of tintin's make-shift regex syntax in the future.
-
-    Use {.} and {.+} and {.?+} instead.
-
-06) Added a notice for people still using $variable[] instead of
+05) Added a notice for people still using $variable[] instead of
     *variable[]. I'll change $variable[] eventually to be identical to
     $variable[%*].
 

+ 19 - 0
SCRIPTS

@@ -288,3 +288,22 @@
 		#delay {($sleepcurr - $sleeptime) / 1000000.000} %2
 	}
 }
+
+#nop -------------------------------------------------------------------------
+#nop This function and substitution will highlight spelling errors as red.
+#nop -------------------------------------------------------------------------
+
+#function spellcheck
+{
+	#format result %S %1;
+	#if {$result == 0}
+	{
+		#var result %1
+	};
+	#else
+	{
+		#var result <118>%1<278>
+	}
+}
+
+#substitute {{\b[a-zA-Z]+\b}} {@spellcheck{%1}}

+ 49 - 101
TODO

@@ -1,74 +1,66 @@
+* BUGS
+
+
 * STUFF THAT IS PROBABLY GONNA GET DONE
 
-    well if i map run somewhere with a door, i won't open it
-    it just plots the directions, and doesn't use the exit commands unless you have nofollow flipped the other way
+  - #map exit <exit> dir is not updating the room grid.
+
+  - html log improvements
+  
+  - ascii draw mode for #screen fill DEFAULT
+
+  - #info unicode <character>
+
+  - pancake mode to display rooms at all levels and annotations
 
-  - add color stripped MSDP option.
+  - screen move, negative arg handling.
 
-  - resize update on attach and detach?
+  - case insensitive tabbing (partial start with rewrite)
 
-  - dummy sessions
+  - finish landmarks
+  - map sandbox mode support (flags to disable saving?)
+  - add ghosting to fix #map flag nofollow exit cmd issues?
+  - #map legend support for unicode graphics.
+  - add step count for #map list and rename distance to weight
+  - multi-line room symbols
+
+  - #math idea: 4 === 2+2 === 8-4
+  - #math true/false handling?
 
-  - highlight files being read with verbose
+  - highlight file being read with verbose
 
   - #draw table {1;2;3;4} {a;b;c;d} 
     #draw graph
+    #draw rain
     #draw button
+    #draw titanic
 
-  - %b %B mode. (compress + mode)
+  - %b %B mode. (compress + base mode)
 
   - proper vt100 skip detection for improper color codes.
 
   - I'll look and see if I can make { match both a { and \x7B as it's an annoying issue to debug.
 
-  - Add vtcode detection to skip semicolons? May be a security risk.
-
   - Add VT100 filter to make outside sources respect terminal size constraints, also needed to run bash in vsplit mode.
 
-  - list changes don't appear to fire variable update events:
-
-  - Add #draw error handling.
-
-  - Avoid/Hide flag exits coloring.
-
-  - #map merge / sync support.
-
-  - #map Landmark support.
-
-  - map sandbox mode support.
-
-  - map jump n e s w support.
-
-  - -⸜   diagonal void room handling
-      \
-
-  - custom exit colors.
-
-  - Would be awesome if #map move/at could take the speedwalking format
-
-  - Option to just save config.
-
   - Remote script loading
 
   - Work on VT2020 protocol (mouse click)
 
-  - #MAP exit commands do not work if nofollow flag is on. 
-
   - make #path load bi-directional.
 
   - class bla assign {stuff} feature?
+  - add class specific debug
+  - allow #class save/load to memory.
+  - better class event and class size handling ?
 
   - more potent long-click handling including held down ticks.
 
   - look into discord api / arachnos
 
-  - make *draw_room more comprehensible
-
-  - #detach command.
-
   - add maze flag for maze handling.
 
-  - add class specific debug
+
 
   - make #split static and fix wrapping of split line on horizontal shrink.
 
@@ -79,38 +71,25 @@
   - split up list data flag.
 
   - Add #log delete option.
+  - Fix #log helpfile and look into #log {remove} option for wintin++
 
   - See about filling COMMAND_LIST table.
 
   - See about adding SESSIONS to the list table.
 
   - disable packet patch in echo off mode / PACKET PATCH AUTO mode.
-
   - set packet patch on EOR and GA detection. #config packet patch auto ?
 
   - Add debugger to syntax highlighter, substitution highlighing, 256 color support, and variable expansion.
 
-  - allow #class save/load to memory.
-
-  - document #map legend
-
-  - better background in #map.
-
-  - only #map autolink rooms in same area.
-
   - look at scripts/bug.tin
 
-  - check term_table code
-
-  - Fix #log helpfile and look into #log {remove} option for wintin++
-
-  - Add #map center {x} {y} {z} option to change the center room of #map map
-
-  - Look into adding gb18030 GBK translation table.
-
-  - Add vtmap positioning options.
-
   - Add FILE READ <name> event
+  - #buffer events
+  - add KILLED TYPE ARG event, for example KILLED CLASS bla
+  - CAPTURED INPUT event
+  - http://tintin.sourceforge.net/board/viewtopic.php?p=8735#8735 RECEIVED MESSAGE event?
+? - http://tintin.sourceforge.net/board/viewtopic.php?p=8655#8655 FILE OPENED / CLOSED event?
 
   - fix readmud in net.c to not move the cursor on inactive sessions.
 
@@ -126,34 +105,18 @@
   - Fix arrow key up history recall overwriting the prompt (partial redesign)
     Auto prompt fixing on overwrite.
 
-  - https://tintin.sourceforge.io/forum/viewtopic.php?f=3&t=2614&p=10665 inherit local vars
-
-  - add step count for #map list and rename distance to weight
-
   - reportable_sounds
 
-  - more accurate map mouse reporting
-
-  - https://tintin.sourceforge.io/forum/viewtopic.php?f=5&t=2600 map viewing mode.
-
-  - #buffer events
-
   - #default smaller equal greater options
 
   - #buffer end after #split ?
 
   - https://tintin.sourceforge.io/forum/viewtopic.php?f=4&t=2597 #add #screen support
 
-  - better class event and class size handling ?
-
-  - #regex might increase the nesting level, causing issues with #local.
-
   - TELNET documentation.
 
   - Add JSON support to #scan
 
-  - add KILLED TYPE ARG event, for example KILLED CLASS bla
-
   - add form feed \x0C
 
   - look into \x7F not backspacing.
@@ -162,66 +125,51 @@
 
   - support strikethrough html logging.
 
-  - CAPTURED INPUT event
-
   - see if #break 2 is possible, maybe #continue 2 as well.
 
   - Add options to #cursor to implement custom behaviour.
 
   - multiline gag
 
-  - http://tintin.sourceforge.net/board/viewtopic.php?p=8735#8735 RECEIVED MESSAGE event?
-
   - toggle global flags with #message all on, #debug all on, etc.
 
-  - Add #map roomflag block option to block deathtrap rooms.
-
   - http://tintin.sourceforge.net/board/viewtopic.php?p=9625 (map undo issue) (not a big issue)
 
   - http://tintin.sourceforge.net/board/viewtopic.php?t=2339 (map area data)
 
-  - http://tintin.sourceforge.net/board/viewtopic.php?t=1707 (map locate patch)
-
   - IPv6 for chat
 
-  - http://tintin.sourceforge.net/board/viewtopic.php?p=9915 (crash if #map map x y is too big)
-
-  - http://tintin.sourceforge.net/board/viewtopic.php?p=9110 (map search stamp bug)
-
   - http://tintin.sourceforge.net/board/viewtopic.php?p=9109 (vt102 strip \e[2J\e[H )
 
   - http://tintin.sourceforge.net/board/viewtopic.php?p=8766#8766 (global verbose toggle?)
 
   - http://tintin.sourceforge.net/board/viewtopic.php?p=8745#8745 (verbose logging?)
 
-? - http://tintin.sourceforge.net/board/viewtopic.php?p=8655#8655 FILE OPENED / CLOSED event?
-
-  - http://tintinplusplus.github.io/
-
-  - Weirdness with joe
-
   - See about adding ~/ handling for file names.
 
-  - http://tintin.sourceforge.net/board/viewtopic.php?p=9934 Strange #map pathdir bug
-
-  - Add seconds till execution message to delays and tickers.
-
 --------------------------------------------------------------------------------
 
 * ROADMAP
 
-  - attach/detach support
+  - dictionary +
 
-  - #screen support
+  - editor
+
+  - clickable link support
 
   - tintin commander
 
-  - sixtel graphics
+  - sixel graphics
 
-  - #window support
+  - windowing
 
-  - clickable link support
+  - blind automapping
+
+  - better attach/detach
+
+  - GUI mode
 
+  - Braille handling
 
 --------------------------------------------------------------------------------
 

+ 597 - 0
advertise.c

@@ -0,0 +1,597 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2009                       *
+******************************************************************************/
+
+#include "tintin.h"
+
+struct advertisement_type
+{
+	time_t                  start;
+	time_t                  end;
+	int                     value;
+	char                  * desc;
+	char                  * wrap;
+};
+
+struct advertisement_type advertisement_table[] =
+{
+	{
+		1400000000, /* 2014 */
+		1800000000, /* 2027 */
+		100,
+
+		"\n"
+		"<138>                     Lost Souls  -  http://lostsouls.org\n"
+		"\n"
+		"<078>\"Lost Souls is not for the faint of heart.\" -- Net Games\n"
+		"\n"
+		"<078>\"The depth of Lost Souls can be amazing.\" -- Playing MUDs On The Internet\n"
+		"\n"
+		"<078>\"Have you ever come upon a place on the Net that's so incredible that you\n"
+		"<078>can't believe such entertainment is free? This MUD will blow your mind with\n"
+		"<078>its marvelous attention to detail and incredible role-playing atmosphere!\"\n"
+		"\n"
+		"<078>  -- Yahoo! Wild Web Rides\n"
+		"\n"
+                "<178>To connect to Lost Souls enter: #session ls lostsouls.org 23\n"
+                "\n",
+
+		"\n"
+		"<138>Lost Souls\n"
+		"<168>http://lostsouls.org\n"
+		"\n"
+		"<078>\"Lost Souls is not for the faint of heart.\" -- Net Games\n"
+		"\n"
+		"<078>\"The depth of Lost Souls can be amazing.\" -- Playing MUDs On The Internet\n"
+		"\n"
+		"<078>\"Have you ever come upon a place on the Net that's so incredible that you can't believe such entertainment is free? This MUD will blow your mind with its marvelous attention to detail and incredible role-playing atmosphere!\" -- Yahoo! Wild Web Rides\n"
+		"\n"
+		"<178>To connect to Lost Souls enter: #session ls lostsouls.org 23\n"
+		"\n"
+	},
+
+	{
+		1400000000, /* 2014 */
+		1800000000, /* 2027 */
+		100,
+		"\n"
+		"<138>               Legends of Kallisti  -  http://www.KallistiMUD.com\n"
+		"\n"
+		"<078>One of the longest running, most feature rich MUDs in the world with decades of\n"
+		"<078>development. Kallisti boasts a massive original world, great atmosphere of long\n"
+		"<078>time players, excellent combat system including group formations, ranged combat,\n"
+		"<078>optional PK and arena PvP, extensive character customization options, player\n"
+		"<078>lineages, clans, customizable player houses, item crafting, extensively\n"
+		"<078>customizable UI, Mud Sound Protocol (MSP), MSDP, and so much more.\n"
+		"\n"
+		"<078>This is an a amazing game that you could literally play for a decade and still\n"
+		"<078>discover more - you won't be disappointed!\n"
+		"\n"
+		"<178>To connect to Kallisti MUD enter: #session LoK kallistimud.com 4000\n"
+		"\n",
+
+		"\n"
+		"<138>Legends of Kallisti\n"
+		"<168>http://www.KallistiMUD.com\n"
+		"\n"
+		"<078>One of the longest running, most feature rich MUDs in the world with decades of development. Kallisti boasts a massive original world, great atmosphere of long time players, excellent combat system including group formations, ranged combat, optional PK and arena PvP, extensive character customization options, player lineages, clans, customizable player houses, item crafting, extensively customizable UI, Mud Sound Protocol (MSP), and so much more.\n"
+		"<078>This is an a amazing game that you could literally play for a decade and still discover more - you won't be disappointed!\n"
+		"\n"
+		"<178>To connect to Kallisti MUD enter: #session LoK kallistimud.com 4000\n"
+		"\n",
+	},
+/*
+	{
+		1400000000, // 2014
+ 		1800000000, // 2027
+		100,
+
+		"\n"
+                "<138>                    Lowlands  -  http://lolamud.net\n"
+		"\n"
+		"<078>Lowlands is based on the Storms of Time codebase which in turn is based on the\n"
+		"<078>Mortal Realms codebase which was established in 1993. Lowlands has many unique\n"
+		"<078>systems and features. Its main strength is the inclusion of over 100 detailed\n"
+		"<078>quests which have been custom coded using a powerful mob prog engine.\n"
+		"<078>\n"
+		"<078>Lowlands has a generic medieval fantasy theme with a mature playerbase that has\n"
+		"<078>little interest in roleplaying, but make up for that with their enthusiasm when\n"
+		"<078>it comes to grinding, raiding, questing and exploring.\n"
+		"\n"
+		"<178>To connect to Lowlands enter: #session lol lolamud.net 6969\n"
+		"\n",
+
+		"\n"
+		"<138>Lowlands\n"
+		"<168>http://lolamud.net\n"
+		"\n"
+		"<078>Lowlands is based on the Storms of Time codebase which in turn is based on the Mortal Realms codebase. It's a very polished MUD with many unique systems and features. Its main strength is the inclusion of over 100 detailed area quests which have been custom coded using a powerful mob prog engine.\n"
+		"<078>\n"
+		"<078>Lowlands has a generic medieval fantasy theme and the codebase is open source so anyone wanting to build or code can easily set up their own test mud.\n"
+		"\n"
+		"<178>To connect to Lowlands enter: #session lol lolamud.net 6969\n"
+		"\n"
+	},
+*/
+	{
+		1400000000,
+		1800000000,
+		100,
+		"\n"
+                "<138>                    3Kingdoms  -  http://3k.org\n"
+                "\n"
+                "<078>Based around the mighty town of Pinnacle, three main realms beckon the player\n"
+                "<078>to explore.  These kingdoms are: Fantasy, a vast medieval realm of orcs, elves,\n"
+                "<078>dragons, and a myriad of other creatures; Science, a post-apocalyptic, war-torn\n"
+                "<078>world set in the not-so-distant future; and Chaos, a transient realm where the\n"
+                "<078>realities of Fantasy and Science collide to produce unimaginable results.\n"
+                "\n"
+                "<078>3Kingdoms combines all these features, and so much more, to give the player an\n"
+                "<078>experience that will stay with them for the rest of their lives.  Come live the\n"
+                "<078>adventure and find out for yourself why 3K is the best there is!\n"
+                "\n"
+                "<178>To connect to 3Kingdoms enter:  #session 3K 3k.org 3000\n"
+                "\n",
+ 
+                "\n"
+                "<138>3Kingdoms\n"
+                "<168>http://3k.org\n"
+                "\n"
+                "<078>Based around the mighty town of Pinnacle, three main realms beckon the player to explore.  These kingdoms are: Fantasy, a vast medieval realm of orcs, elves, dragons, and a myriad of other creatures; Science, a post-apocalyptic, war-torn world set in the not-so-distant future; and Chaos, a transient realm where the realities of Fantasy and Science collide to produce unimaginable results.  3Kingdoms combines all these features, and so much more, to give the player an experience that will stay with them for the rest of their lives.  Come live the adventure and find out for yourself why 3K is the best there is!\n"
+                "\n"
+                "<178>To connect to 3Kingdoms enter:  #session 3K 3k.org 3000\n"
+                "\n",
+	},
+
+	{
+		1400000000,  /* 2014 */ 
+		1800000000,  /* 2027 */ 
+		100,
+
+		"\n"
+		"<138>                New Worlds Ateraan  -  http://www.ateraan.com\n"
+		"\n"
+		"<078>Ateraan is a world of Intensive Roleplaying offering many unique and powerful\n"
+		"<078>guilds, races, politics, religion, justice, economy, and a storyline that is\n"
+		"<078>dominantly player controlled and based on a novel. The game is based on a\n"
+		"<078>Kingdom with knights, merchants, mages, and thieves, and a fierce southern\n"
+		"<078>state that has warriors, shaman, slaves, and servants. Ships rule the seas and\n"
+		"<078>caravans travel the lands. With 100's of players and features like invasions,\n"
+		"<078>ship creation, house building, clans, theaters, leatherball fields, and massive\n"
+		"<078>events, the game is incredibly robust and diverse.\n"
+		"\n"
+                "<178>To connect to New Worlds Ateraan enter: #session nwa ateraan.com 4002\n"
+                "\n",
+
+                "\n"
+                "<138>New Worlds Ateraan\n"
+                "<168>http://www.ateraan.com\n"
+                "\n"
+                "<078>Ateraan is a world of Intensive Roleplaying offering many unique and powerful guilds, races, politics, religion, justice, economy, and a storyline that is dominantly player controlled and based on a novel. The game is based on a Kingdom with fighters, merchants, mages, and thieves, and a fierce southern state that has warriors, shaman, slaves, and servants. Ships rule the seas and caravans travel the lands. With 100's of players and features like invasions, ship creation, house building, clans, theaters, leatherball fields, and massive events, the game is incredibly robust and diverse.\n"
+                "\n"
+                "<178>To connect to New Worlds Ateraan enter: #session nwa ateraan.com 4002\n"
+                "\n"
+	},
+/*
+	{
+		1400000000,  // 2014
+		1800000000,  // 2027
+		100,
+
+		"\n"
+		"<138>                 Primal Darkness  -  http://www.primaldarkness.com\n"
+		"\n"
+		"<078>Primal Darkness boasts twenty three start races, three quest races, five start\n"
+		"<078>classes and twenty one subclasses to suit your adventuring needs, and a custom\n"
+		"<078>remort system allowing you to save all your hard work while trying out new\n"
+		"<078>races and/or classes. The world has zoned player killing (pk) supported by a\n"
+		"<078>justice system, player built and ran guilds, a fully sail-able ocean with\n"
+		"<078>customizable ships and ship battle system, a fully flyable sky with mapping\n"
+		"<078>system, mud-wide auction line, a colosseum in which to prove your battle\n"
+		"<078>prowess (harmless/free pk), and 30+ areas on multiple continents to explore.\n"
+		"\n"
+		"<178>To connect to Primal Darkness enter: #session pd mud.primaldarkness.com 5000\n"
+		"\n",
+		
+		"\n"
+		"<138>Primal Darkness\n"
+		"<168>http://www.primaldarkness.com\n"
+		"\n"
+		"<078>Primal Darkness boasts twenty three start races, three quest races, five start classes and twenty one subclasses to suit your adventuring needs, and a custom remort system allowing you to save all your hard work while trying out new races and/or classes. The world has zoned player killing (pk) supported by a justice system, player built and ran guilds, a fully sail-able ocean with customizable ships and ship battle system, a fully flyable sky with mapping system, mud-wide auction line, a colosseum in which to prove your battle prowess (harmless/free pk), and 30+ areas on multiple continents to explore.\n"
+		"\n"
+		"<178>To connect to Primal Darkness enter: #session pd mud.primaldarkness.com 5000\n"
+		"\n"
+	},
+*/
+/*	{
+		1400000000,
+ 		1800000000,
+		100,
+
+		"\n"
+		"<138>                The Last Outpost  -  https://www.last-outpost.com\n"
+		"\n"
+		"<078>The Last Outpost has been serving up adventure since 1992.  Along with\n"
+		"<078>exploring and advancing through the game world, the game offers players the\n"
+		"<078>ability to lay claim to the zones that make up the land.  Once claimed, a zone\n"
+		"<078>can be taxed, and the player making the claim gets to decide policy within the\n"
+		"<078>zone.  Whoever claims the whole world is declared the Leader of the Last\n"
+		"<078>Outpost!  Whether you enjoy hack 'n slash, following quests, PvP, NPK, playing\n"
+		"<078>in clans, or soloing, the Last Outpost has it.\n"
+		"\n"
+		"<178>To connect to The Last Outpost enter: #session lo last-outpost.com 4000\n"
+		"\n",
+
+		"\n"
+		"<138>The Last Outpost\n"
+		"<168>https://www.last-outpost.com\n"
+		"\n"
+		"<078>The Last Outpost has been serving up adventure since 1992.  Along with exploring and advancing through the game world, the game offers players the ability to lay claim to the zones that make up the land.  Once claimed, a zone can be taxed, and the player making the claim gets to decide policy within the zone.  Whoever claims the whole world is declared the Leader of the Last Outpost!  Whether you enjoy hack 'n slash, following quests, PvP, NPK, playing in clans, or soloing, the Last Outpost has it.\n"
+		"\n"
+		"<178>To connect to The Last Outpost enter: #session lo last-outpost.com 4000\n"
+		"\n"
+	},
+*/
+
+/*
+	{
+		1400000000,
+ 		1800000000,
+		100,
+
+		"\n"
+		"<138>              Realm of Utopian Dreams (RUD)  -  http://rudmud.com\n"
+		"\n"
+		"<078>RUD is a custom hack'n'slash MUD, with character choices for a range of combat\n"
+		"<078>and roleplay. Every race and class has many unique spells and skills, and many\n"
+		"<078>advanced remort options. We're a fantasy MUD started in 1996 seeking new\n"
+		"<078>adventurers to become the next hero, buy a home, start a shop, and eventually\n"
+		"<078>become Nobles of the Realm! Experience a classic game showcasing the content and\n"
+		"<078>features we've built up. The Immortals are ready to help build quests and new\n"
+		"<078>features for the curious. Come visit Lantarea and explore your dream world. We\n"
+		"<078>have an OLC, auction house, and noble system! Explore class-only areas and more!\n"
+		"\n"
+		"<178>To connect to Realm of Utopian Dreams enter: #session rud rudmud.com 1701\n"
+		"\n",
+
+		"\n"
+		"<138>Realms of Utopian Dreams (RUD)\n"
+		"<168>http://rudmud.com\n"
+		"\n"
+		"<078>RUD is a custom hack'n'slash MUD, with character choices for a range of combat and roleplay. Every race and class has many unique spells and skills, and many advanced remort options. We're a fantasy MUD started in 1996 seeking new adventurers to become the next hero, buy a home, start a shop, and eventually become Nobles of the Realm! Experience a classic game showcasing the content and features we've built up. The Immortals are ready to help build quests and new features for the curious. Come visit Lantarea and explore your dream world. We have an OLC, auction house, and noble system! Explore class-only areas and more!\n"
+		"\n"
+		"<178>To connect to Realm of Utopian Dreams enter: #session rud rudmud.com 1701\n"
+		"\n"
+	},
+*/
+/*
+	{
+		1400000000,
+		1800000000,
+		100,
+
+		"\n"
+		"<138>                Threshold RPG  -  http://www.thresholdrpg.com\n"
+		"\n"
+		"<078>Join us as Threshold RPG, one of the oldest RP enforced games on the\n"
+		"<078>internet. Add to thirteen years of player created history and make your own\n"
+		"<078>mark on the world today. Join a hundred other players who are vying for\n"
+		"<078>political and religious power in complex systems that reward skill, effort,\n"
+		"<078>and social interactions. Threshold RPG is a custom code-base written in\n"
+		"<078>LPC and features a completely unique and original world.\n"
+		"\n"
+		"<178>To connect to Threshold RPG enter: #session thresh thresholdrpg.com 23\n"
+		"\n",
+
+		"\n"
+		"<138>Threshold RPG\n"
+		"<168>http://www.thresholdrpg.com\n"
+		"\n"
+		"<078>Join us as Threshold RPG, one of the oldest RP enforced games on the internet. Add to thirteen years of player created history and make your own mark on the world today. Join a hundred other players who are vying for political and religious power in complex systems that reward skill, effort, and social interactions. Threshold RPG is a custom code-base written in LPC and features a completely unique and original world.\n"
+		"\n"
+		"<178>To connect to Threshold RPG enter: #session thresh thresholdrpg.com 23\n"
+		"\n"
+	},
+*/
+
+/*
+	{
+		1400000000, 
+		1700000000, 
+		100,
+		"\n"
+		"<138>               Carrion Fields  -  http://carrionfields.net\n"
+		"\n"
+		"<078>Adventure, politics and bloody war await you in this life of swords, sorcery,\n"
+		"<078>deception, and honor.  We have 17 customizable classes with which to explore a\n"
+		"<078>massively rich world of over 270 areas.  RP is mandatory, but help is always\n"
+		"<078>available on the newbie channel.  Intuitive game mechanics provide a fun and\n"
+		"<078>fulfilling PK environment.  Carrion Fields is 100% free to play and free of\n"
+		"<078>paid perks as well.  By what name do you wish to be mourned?\n"
+		"\n"
+                "<178>To connect to Carrion Fields enter: #session cf carrionfields.net 4449\n"
+                "\n"
+		                
+	},
+
+	{
+		1400000000, 
+		1700000000, 
+		100,
+		"\n"
+		"<138>                 Alter Aeon  -  http://www.alteraeon.com\n"
+		"\n"
+		"<078>Alter Aeon is a custom multiclass MUD, where each of the character\n"
+		"<078>classes can be combined to make very unique characters.  This huge\n"
+		"<078>fantasy themed game has hundreds of areas and quests, spanning\n"
+		"<078>several continents and outer planar regions.  There are custom spells,\n"
+		"<078>skills, minions, player run shops, boats, PvP, and many other features\n"
+		"<078>for nearly every kind of player.  The game is very friendly to new players\n"
+		"<078>and has extensive support for the blind and visually impaired.\n"
+		"\n"
+		"<178>To connect to Alter Aeon enter: #session aa alteraeon.com 3000\n"
+		"\n"
+	},
+
+	{
+		1388166000, 
+		1600000000,
+		100,
+		"\n"
+		"<138>                Threshold RPG  -  http://www.thresholdrpg.com\n"
+		"\n"
+		"<078>Join us as Threshold RPG, one of the oldest RP enforced games on the\n"
+		"<078>internet. Add to thirteen years of player created history and make your own\n"
+		"<078>mark on the world today. Join a hundred other players who are vying for\n"
+		"<078>political and religious power in complex systems that reward skill, effort,\n"
+		"<078>and social interactions. Threshold RPG is a custom code-base written in\n"
+		"<078>LPC and features a completely unique and original world.\n"
+		"\n"
+		"<178>To connect to Threshold RPG enter: #session thresh thresholdrpg.com 23\n"
+		"\n"
+	},
+*/
+	{
+		0,
+		0,
+		0,
+		"",
+		""
+	}
+};
+
+int valid_advertisement(int i)
+{
+	if (advertisement_table[i].start > gtd->time)
+	{
+		return 0;
+	}
+	if (advertisement_table[i].end < gtd->time)
+	{
+		return 0;
+	}
+	return advertisement_table[i].value;
+}
+
+int total_advertisements()
+{
+	int i, count = 0;
+
+	for (i = 0 ; *advertisement_table[i].desc ; i++)
+	{
+		count += valid_advertisement(i);
+	}
+	return count;
+}
+
+DO_COMMAND(do_advertise)
+{
+	int i, max, cnt;
+	char buf[BUFFER_SIZE];
+
+	max = total_advertisements();
+
+	for (i = 0 ; *advertisement_table[i].desc ; i++)
+	{
+		if (!valid_advertisement(i))
+		{
+			continue;
+		}
+
+		cnt = advertisement_table[i].value;
+
+		if (generate_rand(ses) % (unsigned long long) max < (unsigned long long) cnt)
+		{
+			char *pto, *ptf;
+
+			if (gtd->screen->cols >= 80)
+			{
+				substitute(ses, advertisement_table[i].desc, buf, SUB_COL);
+			}
+			else
+			{
+				substitute(ses, advertisement_table[i].wrap, buf, SUB_COL);
+			}
+
+			pto = buf;
+
+			while (*pto)
+			{
+				ptf = strchr(pto, '\n');
+
+				if (ptf == NULL)
+				{
+					break;
+				}
+				*ptf++ = 0;
+
+				tintin_puts(ses, pto);
+
+				pto = ptf;
+			}
+			break;
+		}
+		max -= cnt;
+	}
+
+//	tintin_printf2(ses, "#NO SESSION ACTIVE. USE: %csession {name} {host} {port} TO START ONE.", gtd->tintin_char);
+
+	return ses;
+}
+
+
+
+
+
+/*
+	{
+		1260469590, * 10 Dec 2009 *
+		1323541590, * 10 Dec 2011 *
+		"\n"
+		"<138>                                  Lowlands\n"
+		"\n"
+		"<078>Lowlands is an old school severely customized Merc derived Hack and Slash MUD\n"
+		"<078>with an emphasis on exploration and solving challenging and immersive area\n"
+		"<078>based quests.\n"
+		"\n"
+		"<078>A voluntary two faction player killing system is in place for the followers of\n"
+		"<078>the two gods, Chaos and Order, combined with easy corpse retrieval, no corpse\n"
+		"<078>looting, and a large 15,000 room world.\n"
+		"\n"
+		"<178>To connect to Lowlands enter: #session lo slackhalla.org 6969\n"
+		"\n"
+	},
+
+	{
+		1260469590, * 10 Dec 2009 *
+		1323541590, * 10 Dec 2011 *
+		"\n"
+		"<138>                Maiden Desmodus  -  http://maidendesmodus.com\n"
+		"\n"
+		"<078>Maiden Desmodus is an immersive world of high adventure where your actions, or\n"
+		"<078>inaction, will determine the fate of The Isle. Choose to be born unto one of\n"
+		"<078>two opposing factions, join one of the six powerful guilds, and carve your\n"
+		"<078>place in history through your cunning, your strategy, and your skill with magic\n"
+		"<078>or a blade. At every turn are players who may ally themselves to you, or work\n"
+		"<078>to destroy you. Shall you form your own cabal and command your peers, control\n"
+		"<078>the politics of your city, or lead an army against those who oppose you?\n"
+		"<078>Maiden Desmodus features a completely original world and a custom game engine.\n"
+		"\n"
+		"<178>To connect to Maiden Desmodus enter: #session md maidendesmodus.com 4000\n"
+		"\n"
+	},
+
+	{
+		1260469590, * 10 Dec 2009 *
+		1323541590, * 10 Dec 2011 *
+		"\n"
+		"<138>                     Lost Souls  -  http://lostsouls.org\n"
+		"\n"
+		"<078>\"Our world is fallen, boy.  Aedaris is a ruin.  My grandfather, he told me\n"
+		"<078>of days, not so long gone, when everything you see was part of a great empire.\n"
+		"<078>Peaceful, he said.  Full of wonders.  They called it eternal.  Funny, eh, boy?\n"
+		"<078>They thought it'd last forever, and it went crazy and tore itself apart.  But\n"
+		"<078>they left behind a few things for us, didn't they?  Ha!  Yes, lots for us.  Now\n"
+		"<078>give that wizard-stick here before you blow your fool horns off, and get to\n"
+		"<078>work.  Daylight's soon, and these faeries aren't going to skin themselves.\"\n"
+		"<078>Lost Souls: chaos in the wreckage of empire.  Be clever if you want to live.\n"
+		"\n"
+                "<178>To connect to Lost Souls enter: #session ls lostsouls.org 23\n"
+                "\n"
+		                
+	},
+
+	{
+		1291140000, * 30 Nov 2010 *
+		1354280000, * 30 Nov 2012 *
+		100,
+		"\n"
+		"<138>                   Alter Aeon  -  http://www.alteraeon.com\n"
+		"\n"
+		"<078>Alter Aeon is a custom MUD written entirely from scratch. The story setting\n"
+		"<078>is reminiscent of Dungeons and Dragons, but has elements of fantasy.\n"
+		"<078>There are unique spell, skill, and minion systems, player run shops, boats,\n"
+		"<078>and many other features for nearly every kind of player.  In development\n"
+		"<078>since 1995, the world of Alter Aeon has hundreds of areas to explore, quests\n"
+		"<078>to complete, and puzzles to solve.\n"
+		"\n"
+		"<178>To connect to Alter Aeon enter: #session aa alteraeon.com 3000\n"
+		"\n"
+	},
+
+	{
+		1291140000, * 30 Nov 2010 *
+		1354280000, * 30 Nov 2012 *
+		100,
+		"\n"
+		"<138>                Threshold RPG  -  http://www.thresholdrpg.com\n"
+		"\n"
+		"<078>Join us as Threshold RPG, one of the oldest RP enforced games on the\n"
+		"<078>internet. Add to thirteen years of player created history and make your own\n"
+		"<078>mark on the world today. Join a hundred other players who are vying for\n"
+		"<078>political and religious power in complex systems that reward skill, effort,\n"
+		"<078>and social interactions. Threshold RPG is a custom code-base written in\n"
+		"<078>LPC and features a completely unique and original world.\n"
+		"\n"
+		"<178>To connect to Threshold RPG enter: #session thresh thresholdrpg.com 23\n"
+		"\n"
+	},
+
+	{
+		1291140000,
+		1354280000,
+		100,
+		"\n"
+		"<138>                   Primordiax - http://www.primordiax.com\n"
+		"\n"
+		"<078>Primordiax is an in-character enforced MUD where roleplaying is heavily\n"
+		"<078>encouraged. The exclusive design of Primordiax makes it extremely\n"
+		"<078>accessible to new players without losing the intrigue and complexity that\n"
+		"<078>continues to attract veterans of the genre. Primordiax offers a classic\n"
+		"<078>gaming experience with a highly unique class system and an open skill tree.\n"
+		"\n"
+		"<178>To connect to Primordiax enter: #session prim primordiax.com 3000\n"
+		"\n"
+	},
+
+	{
+		1291140000, * 30 Nov 2010 *
+		1354280000, * 30 Nov 2012 *
+		100,
+		"\n"
+		"<138>                Northern Crossroads - http://www.ncmud.org\n"
+		"\n"
+		"<078>Northern Crossroads is a diverse world of blade wielders, assassins and magic\n"
+		"<078>users who use their powers together to seek out the darkest dungeons. Decide\n"
+		"<078>between five classes, as allowed by your choice of ten races, and prove your\n"
+		"<078>strength to ascend to an Advanced class. Venture to dangerous zones with other\n"
+		"<078>mortals, claim the rarest of items, join one of several clubs and build your\n"
+		"<078>character to challenge other mortals. NC has enthralled players with hundreds\n"
+		"<078>of detailed areas of various themes since 1993 and is one of the oldest MUDs\n"
+		"<078>in the world.\n"
+		"\n"
+		"<178>To connect to Northern Crossroads enter: #session NC ncmud.org 9000\n"
+		"\n"
+	},
+*/

+ 1153 - 0
buffer.c

@@ -0,0 +1,1153 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2004                       *
+******************************************************************************/
+
+#include "tintin.h"
+
+
+void init_buffer(struct session *ses, int size)
+{
+	int cnt;
+
+	push_call("init_buffer(%p,%p)",ses,size);
+
+	if (size && size == ses->scroll->size)
+	{
+		pop_call();
+		return;
+	}
+
+	if (ses->scroll->buffer)
+	{
+		for (cnt = 0 ; cnt < ses->scroll->used ; cnt++)
+		{
+			free(ses->scroll->buffer[cnt]->str);
+			free(ses->scroll->buffer[cnt]);
+		}
+		free(ses->scroll->buffer);
+	}
+
+	if (ses->scroll->input)
+	{
+		str_free(ses->scroll->input);
+	}
+
+	if (size)
+	{
+		ses->scroll->buffer = (struct buffer_data **) calloc(size, sizeof(struct buffer_data *));
+		ses->scroll->input  = str_dup("");
+
+		ses->scroll->size  = size;
+		ses->scroll->used  = 0;
+		ses->scroll->wrap  = get_scroll_cols(ses);
+
+		add_line_buffer(ses, "", 0);
+
+		ses->scroll->line = -1;
+	}
+	else
+	{
+		free(ses->scroll);
+	}
+
+	pop_call();
+	return;
+}
+
+void check_buffer(struct session *ses)
+{
+	struct buffer_data *buffer;
+	char temp[STRING_SIZE];
+	int index, wrap;
+
+	push_call("check_buffer(%p)",ses);
+
+	if (!HAS_BIT(ses->scroll->flags, SCROLL_FLAG_RESIZE))
+	{
+		pop_call();
+		return;
+	}
+
+	DEL_BIT(ses->scroll->flags, SCROLL_FLAG_RESIZE);
+
+	wrap = get_scroll_cols(ses);
+
+	for (index = ses->scroll->used - 1 ; index > 0 ; index--)
+	{
+		buffer = ses->scroll->buffer[index];
+
+		if (buffer->height == 1 && buffer->width < wrap - 1)
+		{
+			buffer->height = buffer->lines;
+		}
+		else
+		{
+			buffer->lines = word_wrap_split(ses, buffer->str, temp, wrap, 0, 0, FLAG_NONE, &buffer->height, &buffer->width);
+		}
+	}
+
+	ses->scroll->wrap = wrap;
+	ses->scroll->time = gtd->time;
+	ses->scroll->base = 0;
+	ses->scroll->line = -1;
+
+	pop_call();
+	return;
+}
+
+void add_line_buffer(struct session *ses, char *line, int prompt)
+{
+	char temp[STRING_SIZE];
+	char *pti, *pto;
+	int cnt, purge;
+	int cur_row, cur_col, top_row, bot_row;
+	struct buffer_data *buffer;
+
+	push_call("add_line_buffer(%p,%s,%d)",ses,line,prompt);
+
+	if (gtd->level->scroll)
+	{
+		pop_call();
+		return;
+	}
+
+	if (HAS_BIT(ses->flags, SES_FLAG_CONVERTMETA))
+	{
+		convert_meta(line, temp, TRUE);
+
+		line = temp;
+	}
+
+	cur_row = ses->cur_row;
+	cur_col = ses->cur_col;
+	top_row = ses->split->top_row;
+	bot_row = ses->split->bot_row;
+
+	if (str_len(ses->scroll->input) + strlen(line) + 100 >= BUFFER_SIZE)
+	{
+		str_cat_printf(&ses->scroll->input, "\n\e[1;31m#BUFFER: LINE LENGTH OF (%d) EXEEDS MAXIMUM SIZE OF (%d)%s\n", str_len(ses->scroll->input) + strlen(line), BUFFER_SIZE, COLOR_TEXT);
+	}
+	else
+	{
+		if (prompt == TRUE)
+		{
+			str_cat(&ses->scroll->input, line);
+
+			pop_call();
+			return;
+		}
+		else
+		{
+			str_cat(&ses->scroll->input, line);
+		}
+	}
+
+	pti = pto = ses->scroll->input;
+
+	while (*pti != 0)
+	{
+		while (skip_vt102_codes_non_graph(pti))
+		{
+			interpret_vt102_codes(ses, pti, FALSE);
+
+			pti += skip_vt102_codes_non_graph(pti);
+		}
+
+		if (*pti == 0)
+		{
+			break;
+		}
+
+		if (SCROLL(ses))
+		{
+			*pto++ = *pti++;
+		}
+		else
+		{
+			pti++;
+		}
+	}
+	*pto = 0;
+
+	if (HAS_BIT(ses->flags, SES_FLAG_SNOOP) && ses != gtd->ses)
+	{
+		tintin_printf2(gtd->ses, "%s[%s%s] %s", COLOR_TEXT, ses->name, ses->scroll->input, COLOR_TEXT);
+	}
+
+	ses->scroll->buffer[ses->scroll->used] = calloc(1, sizeof(struct buffer_data));
+
+	buffer = ses->scroll->buffer[ses->scroll->used];
+
+	buffer->lines = word_wrap_split(ses, ses->scroll->input, temp, ses->wrap, 0, 0, FLAG_NONE, &buffer->height, &buffer->width);
+	buffer->time  = gtd->time;
+	buffer->str   = strdup(ses->scroll->input);
+
+	if (gtd->level->grep || prompt == -1)
+	{
+		SET_BIT(buffer->flags, BUFFER_FLAG_GREP);
+	}
+
+	ses->scroll->used++;
+
+	str_cpy(&ses->scroll->input, "");
+
+	if (!HAS_BIT(ses->logmode, LOG_FLAG_LOW))
+	{
+		if (ses->logfile)
+		{
+			logit(ses, temp, ses->logfile, LOG_FLAG_LINEFEED);
+		}
+	}
+
+	if (gtd->chat)
+	{
+		chat_forward_session(ses, temp);
+	}
+
+	if (ses->scroll->used == ses->scroll->size)
+	{
+		if (ses->scroll->size < 100000)
+		{
+			purge = ses->scroll->size / 10;
+		}
+		else
+		{
+			purge = 10000;
+		}
+
+		for (cnt = 0 ; cnt < purge ; cnt++)
+		{
+			free(ses->scroll->buffer[cnt]->str);
+			free(ses->scroll->buffer[cnt]);
+		}
+		memmove(&ses->scroll->buffer[0], &ses->scroll->buffer[purge], (ses->scroll->size - purge) * sizeof(struct buffer_data *));
+
+		ses->scroll->used -= purge;
+		ses->scroll->line = URANGE(-1, ses->scroll->line - purge, ses->scroll->used - 1);
+	}
+
+	ses->cur_row = cur_row;
+	ses->cur_col = cur_col;
+	ses->split->top_row = top_row;
+	ses->split->bot_row = bot_row;
+
+	pop_call();
+	return;
+}
+
+
+
+void buffer_print(struct session *ses, int index, int start, int end)
+{
+	struct buffer_data *buffer;
+	char *pti, temp[STRING_SIZE];
+	int col, swap, raw_len, str_len, col_len, height, width;
+
+	push_call("buffer_print(%p,%d,%d,%d)",ses,index,start,end);
+
+	col_len = get_scroll_cols(ses);
+
+	if (start == 0 && end == 0)
+	{
+		if (HAS_BIT(ses->flags, SES_FLAG_PRINTBUFFER) || ses->scroll->line == ses->scroll->used - 1)
+		{
+			pti = ses->scroll->input;
+			raw_len = string_str_raw_len(ses, pti, 0, col_len - 1);
+		}
+		else
+		{
+			pti = temp;
+			raw_len = temp[0] = 0;
+		}
+
+		if (ses->cur_row != ses->split->bot_row)
+		{
+			print_stdout("%02d \e[1;32mmisaligned (%d)", ses->cur_row, ses->scroll->line);
+		}
+		else
+		{
+//			print_stdout("\e[1;36m%02d\e[0m", ses->cur_row);//
+			print_stdout("\e[%dX%.*s", col_len, raw_len, pti);
+		}
+	}
+	else
+	{
+		buffer = ses->scroll->buffer[index];
+
+		if (buffer->height == 1)
+		{
+//			print_stdout("\e[1;37m%02d", ses->cur_row);//
+
+			word_wrap_split(ses, buffer->str, temp, ses->wrap, start, end, 0, &height, &width);
+
+			print_stdout("%s", temp);
+
+			erase_cols(col_len - buffer->width);
+
+			goto_pos(ses, ++ses->cur_row, ses->split->top_col);
+		}
+		else
+		{
+			word_wrap_split(ses, buffer->str, temp, ses->wrap, start, end, WRAP_FLAG_SPLIT, &height, &width);
+
+			pti = temp;
+			col = 0;
+
+			while (TRUE)
+			{
+				if (pti[col] == '\n' || pti[col] == 0)
+				{
+					swap = pti[col];
+
+					pti[col] = 0;
+
+					str_len = strip_vt102_strlen(ses, pti);
+
+					// debug info
+/*					if (start == 0)
+					{
+						if (end == buffer->height)
+						{
+							print_stdout("\e[1;31m%02d\e[0m(%d)(%d)(%d)", ses->cur_row, buffer->lines, buffer->height, buffer->width);
+						}
+						else
+						{
+							print_stdout("\e[1;33m%02d\e[0m", ses->cur_row);
+						}
+					}
+					else
+					{
+						if (end == buffer->height)
+						{
+							print_stdout("\e[1;32m%02d\e[0m", ses->cur_row);
+						}
+						else
+						{
+							print_stdout("\e[1;34m%02d\e[0m", ses->cur_row);
+						}
+					}
+*/
+					print_stdout("%s", pti);
+
+					erase_cols(col_len - str_len);
+
+					pti += col + 1;
+					col = 0;
+
+					goto_pos(ses, ++ses->cur_row, ses->split->top_col);
+
+					if (swap == 0)
+					{
+						break;
+					}
+					continue;
+				}
+				col++;
+			}
+		}
+	}
+	pop_call();
+	return;
+}
+
+
+int show_buffer(struct session *ses)
+{
+	int scroll_size, scroll_cnt, scroll_tmp, scroll_add, scroll_cut, start, end;
+
+	if (ses != gtd->ses)
+	{
+		return TRUE;
+	}
+
+	push_call("show_buffer(%p)",ses);
+
+	check_buffer(ses);
+
+	scroll_size = get_scroll_rows(ses);
+	scroll_add  = 0;
+	scroll_cnt  = URANGE(0, ses->scroll->line, ses->scroll->used - 1);
+	scroll_cut  = 0;
+
+	if (ses->scroll->base)
+	{
+		scroll_add -= ses->scroll->base;
+	}
+
+	// scroll_cut is cut from top line
+
+	while (TRUE)
+	{
+		scroll_tmp = ses->scroll->buffer[scroll_cnt]->height;
+
+		if (scroll_add + scroll_tmp >= scroll_size)
+		{
+			if (scroll_add + scroll_tmp == scroll_size)
+			{
+				scroll_add += scroll_tmp;
+			}
+			else
+			{
+				scroll_cut = scroll_size - scroll_add;
+			}
+			break;
+		}
+
+		scroll_add += scroll_tmp;
+
+		if (scroll_cnt == 0) // home
+		{
+			erase_scroll_region(ses);
+
+			break;
+		}
+
+		scroll_cnt--;
+	}
+
+	save_pos(ses);
+
+	if (scroll_cnt == 0) // home
+	{
+		goto_pos(ses, ses->split->bot_row - scroll_add, ses->split->top_col);
+	}
+	else
+	{
+		goto_pos(ses, ses->split->top_row, ses->split->top_col);
+	}
+
+	if (IS_SPLIT(ses))
+	{
+		SET_BIT(ses->flags, SES_FLAG_READMUD);
+	}
+
+	// scroll_cut is taken from top line
+	// scroll_base is taken from the bot line
+
+	if (scroll_cut)
+	{
+		scroll_tmp = ses->scroll->buffer[scroll_cnt]->height;
+
+		// bottom
+
+		if (scroll_cut == scroll_size)
+		{
+			start = scroll_tmp - scroll_cut;
+			end   = scroll_tmp;
+
+			if (end - start != scroll_size)
+			{
+//				print_stdout("\e[1;32mcnt %d, base: %d, size: %d, add %d, tmp %d, scroll_cut %d start %d end %d\n", scroll_cnt, ses->scroll->base, scroll_size, scroll_add, scroll_tmp, scroll_cut, start, end);
+			}
+			else
+			{
+				buffer_print(ses, scroll_cnt, start, end);
+			}
+		}
+		// middle chunk
+
+		else if (scroll_cut > scroll_size)
+		{
+			start = scroll_tmp - scroll_cut;
+			end   = scroll_tmp - scroll_cut + scroll_size;
+
+//			if (end - start > scroll_size)
+			{
+//				print_stdout("\e[1;1H\e[1;33mcnt %d, base: %d, size: %d, add %d, tmp %d, scroll_cut %d start %d end %d\n", scroll_cnt, ses->scroll->base, scroll_size, scroll_add, scroll_tmp, scroll_cut, start, end);
+			}
+//			else
+			{
+				buffer_print(ses, scroll_cnt, start, end);
+			}
+
+			goto eof;
+		}
+
+		// top chunk
+		else if (scroll_add == 0)
+		{
+			start = ses->scroll->base;
+			end   = scroll_tmp - scroll_cut;
+
+//			if (end - start > scroll_size)
+			{
+//				print_stdout("\e[1;1H\e[1;34mcnt %d, base: %d, size: %d, add %d, tmp %d, scroll_cut %d start %d end %d\n", scroll_cnt, ses->scroll->base, scroll_size, scroll_add, scroll_tmp, scroll_cut, start, end);
+			}
+//			else
+			{
+				buffer_print(ses, scroll_cnt, start, end);
+			}
+
+			goto eof;
+		}
+		// bot chunk
+
+		else
+		{
+			start = scroll_tmp - scroll_cut;
+			end   = scroll_tmp;
+
+//			if (end - start > scroll_size)
+			{
+//				print_stdout("\e[1;1H\e[1;35mcnt %d, base: %d, size: %d, add %d, tmp %d, scroll_cut %d start %d end %d\n", scroll_cnt, ses->scroll->base, scroll_size, scroll_add, scroll_tmp, scroll_cut, start, end);
+			}
+//			else
+			{
+				buffer_print(ses, scroll_cnt, start, end);
+			}
+		}
+		scroll_cnt++;
+		scroll_cut = 0;
+	}
+
+	while (TRUE)
+	{
+		if (scroll_cnt == ses->scroll->used)
+		{
+			break;
+		}
+
+		scroll_tmp = ses->scroll->buffer[scroll_cnt]->height;
+
+		if (scroll_add - scroll_tmp < 0)
+		{
+			break;
+		}
+
+		scroll_add -= scroll_tmp;
+
+		start = 0;
+		end   = scroll_tmp;
+
+		buffer_print(ses, scroll_cnt, start, end);
+
+		scroll_cnt++;
+	}
+
+	if (scroll_cnt < ses->scroll->used && ses->scroll->base)
+	{
+		scroll_tmp = ses->scroll->buffer[scroll_cnt]->height;
+
+		start = 0;
+		end   = scroll_tmp - ses->scroll->base;
+
+		buffer_print(ses, scroll_cnt, start, end);
+	}
+
+	eof:
+
+	// prompt
+
+	buffer_print(ses, 0, 0, 0);
+
+	restore_pos(ses);
+
+	if (IS_SPLIT(ses))
+	{
+		DEL_BIT(ses->flags, SES_FLAG_READMUD);
+	}
+
+	pop_call();
+	return TRUE;
+}
+
+
+DO_COMMAND(do_buffer)
+{
+	char arg1[BUFFER_SIZE];
+	int cnt;
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+
+
+	check_buffer(ses);
+
+	if (*arg1 == 0)
+	{
+		tintin_header(ses, " BUFFER OPTIONS ");
+
+		for (cnt = 0 ; *buffer_table[cnt].name != 0 ; cnt++)
+		{
+			tintin_printf2(ses, "  [%-13s] %s", buffer_table[cnt].name, buffer_table[cnt].desc);
+		}
+		tintin_header(ses, "");
+
+		return ses;
+	}
+
+	for (cnt = 0 ; *buffer_table[cnt].name ; cnt++)
+	{
+		if (!is_abbrev(arg1, buffer_table[cnt].name))
+		{
+			continue;
+		}
+
+		buffer_table[cnt].fun(ses, arg);
+
+		return ses;
+	}
+
+	do_buffer(ses, "");
+
+	return ses;
+}
+
+DO_BUFFER(buffer_clear)
+{
+	int cnt;
+
+	for (cnt = 1 ; cnt < ses->scroll->used ; cnt++)
+	{
+		free(ses->scroll->buffer[cnt]->str);
+		free(ses->scroll->buffer[cnt]);
+	}
+
+	ses->scroll->used  = 1;
+	ses->scroll->line = - 1;
+}
+
+DO_BUFFER(buffer_up)
+{
+	char arg1[BUFFER_SIZE];
+	int scroll_size;
+
+	check_buffer(ses);
+
+	if (ses->scroll->line == -1)
+	{
+		ses->scroll->line = ses->scroll->used - 1;
+		ses->scroll->base = 0;
+	}
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if (is_math(ses, arg1))
+	{
+		scroll_size = URANGE(1, get_number(ses, arg1), get_scroll_rows(ses));
+	}
+	else
+	{
+		scroll_size = get_scroll_rows(ses);
+	}
+
+	while (scroll_size)
+	{
+		scroll_size--;
+		ses->scroll->base++;
+
+		if (ses->scroll->base == ses->scroll->buffer[ses->scroll->line]->height)
+		{
+			if (ses->scroll->line == 0)
+			{
+				ses->scroll->line = 0;
+				ses->scroll->base = 0;
+
+				return;
+			}
+			ses->scroll->line--;
+			ses->scroll->base = 0;
+		}
+	}
+
+	show_buffer(ses);
+
+	return;
+}
+
+DO_BUFFER(buffer_down)
+{
+	char arg1[BUFFER_SIZE];
+	int scroll_size;
+
+	if (ses->scroll->line == -1)
+	{
+		return;
+	}
+
+	check_buffer(ses);
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if (is_math(ses, arg1))
+	{
+		scroll_size = URANGE(1, get_number(ses, arg1), get_scroll_rows(ses));
+	}
+	else
+	{
+		scroll_size = get_scroll_rows(ses);
+	}
+
+	while (scroll_size)
+	{
+		scroll_size--;
+
+		if (ses->scroll->base == 0)
+		{
+			if (++ses->scroll->line == ses->scroll->used)
+			{
+				buffer_end(ses, "");
+				return;
+			}
+			ses->scroll->base = ses->scroll->buffer[ses->scroll->line]->height;
+		}
+		ses->scroll->base--;
+	}
+	show_buffer(ses);
+}
+
+DO_BUFFER(buffer_home)
+{
+	check_buffer(ses);
+
+	ses->scroll->line = 0;
+	ses->scroll->base = 0;
+
+	buffer_down(ses, "");
+}
+
+DO_BUFFER(buffer_end)
+{
+	check_buffer(ses);
+
+	ses->scroll->line = ses->scroll->used - 1;
+
+	ses->scroll->base = 0;
+
+	show_buffer(ses);
+
+	ses->scroll->line = -1;
+}
+
+DO_BUFFER(buffer_lock)
+{
+	char arg1[BUFFER_SIZE];
+
+	check_buffer(ses);
+
+	sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if (!strcasecmp(arg1, "ON"))
+	{
+		ses->scroll->line = ses->scroll->used + 1;
+	}
+	else if (!strcasecmp(arg1, "OFF"))
+	{
+		 buffer_end(ses, "");
+	}
+	else
+	{
+		if (ses->scroll->line == -1)
+		{
+			ses->scroll->line = ses->scroll->used + 1;
+		}
+		else
+		{
+			buffer_end(ses, "");
+		}
+	}
+}
+
+DO_BUFFER(buffer_find)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+	int scroll_cnt, grep_cnt, grep_max, page;
+
+	check_buffer(ses);
+
+	grep_cnt = grep_max = scroll_cnt = 0;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		show_error(gtd->ses, LIST_COMMAND, "#BUFFER, FIND WHAT?");
+
+		return;
+	}
+
+	if (is_math(ses, arg1))
+	{
+		page = get_number(ses, arg1);
+
+		arg = sub_arg_in_braces(ses, arg, arg2, GET_ALL, SUB_VAR|SUB_FUN);
+
+		if (*arg2 == 0)
+		{
+			show_error(gtd->ses, LIST_COMMAND, "#BUFFER, FIND OCCURANCE %d OF WHAT?", page);
+
+			return;
+		}
+	}
+	else
+	{
+		page = 1;
+
+		strcpy(arg2, arg1);
+	}
+
+	if (page > 0)
+	{
+		for (scroll_cnt = ses->scroll->used - 1; scroll_cnt ; scroll_cnt--)
+		{
+			if (HAS_BIT(ses->scroll->buffer[scroll_cnt]->flags, BUFFER_FLAG_GREP))
+			{
+				continue;
+			}
+
+			if (find(ses, ses->scroll->buffer[scroll_cnt]->str, arg2, SUB_NONE, REGEX_FLAG_NONE))
+			{
+				grep_cnt++;
+
+				if (grep_cnt == page)
+				{
+					break;
+				}
+			}
+		}
+	}
+	else
+	{
+		for (scroll_cnt = 0 ; scroll_cnt < ses->scroll->used ; scroll_cnt++)
+		{
+			if (HAS_BIT(ses->scroll->buffer[scroll_cnt]->flags, BUFFER_FLAG_GREP))
+			{
+				continue;
+			}
+
+			if (find(ses, ses->scroll->buffer[scroll_cnt]->str, arg2, SUB_NONE, REGEX_FLAG_NONE))
+			{
+				grep_cnt--;
+
+				if (grep_cnt == page)
+				{
+					break;
+				}
+
+			}
+		}
+	}
+
+	if (scroll_cnt < 0 || scroll_cnt >= ses->scroll->used)
+	{
+		show_error(gtd->ses, LIST_COMMAND, "#BUFFER FIND, NO MATCHES FOUND.");
+
+		return;
+	}
+
+	ses->scroll->line = scroll_cnt;
+
+	show_buffer(ses);
+
+	return;
+}
+
+DO_BUFFER(buffer_get)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], arg3[BUFFER_SIZE];
+	int cnt, min, max;
+
+	check_buffer(ses);
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_NST, SUB_NONE);
+
+	if (*arg1 == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #BUFFER GET <VARIABLE> [LOWER BOUND] [UPPER BOUND]");
+
+		return;
+	}
+
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg3, GET_ONE, SUB_VAR|SUB_FUN);
+
+	min = get_number(ses, arg2);
+
+	if (min < 0)
+	{
+		min = ses->scroll->used + min;
+	}
+	min = URANGE(1, min, ses->scroll->used - 1);
+
+	if (*arg3 == 0)
+	{
+		set_nest_node_ses(ses, arg1, "%s", ses->scroll->buffer[min]->str);
+
+		return;
+	}
+
+	max = get_number(ses, arg3);
+
+	if (max < 0)
+	{
+		max = ses->scroll->used + max;
+	}
+	max = URANGE(1, max, ses->scroll->used - 1);
+
+	if (min > max)
+	{
+		show_error(ses, LIST_COMMAND, "#ERROR: #BUFFER GET {%s} {%d} {%d} LOWER BOUND EXCEEDS UPPER BOUND.", arg1, min, max);
+
+		return;
+	}
+
+	cnt = 0;
+
+	set_nest_node_ses(ses, arg1, "");
+
+	while (min <= max)
+	{
+		sprintf(arg2, "%s[%d]", arg1, ++cnt);
+
+		set_nest_node_ses(ses, arg2, "%s", ses->scroll->buffer[min++]->str);
+	}
+
+	show_message(ses, LIST_COMMAND, "#BUFFER GET: %d LINES SAVED TO {%s}.", cnt, arg1);
+
+	return;
+}
+
+DO_BUFFER(buffer_write)
+{
+	FILE *fp;
+	char arg1[BUFFER_SIZE], out[STRING_SIZE];
+	int cnt;
+
+	check_buffer(ses);
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #BUFFER WRITE <FILENAME>]");
+	}
+	else
+	{
+		if ((fp = fopen(arg1, "w")))
+		{
+			show_message(ses, LIST_COMMAND, "#OK: WRITING BUFFER TO '%s'.", arg1);
+
+			loginit(ses, fp, ses->logmode + LOG_FLAG_OVERWRITE);
+
+			for (cnt = 0 ; cnt < ses->scroll->used ; cnt++)
+			{
+				if (HAS_BIT(ses->logmode, LOG_FLAG_PLAIN))
+				{
+					strip_vt102_codes(ses->scroll->buffer[cnt]->str, out);
+				}
+				else if (HAS_BIT(ses->logmode, LOG_FLAG_HTML))
+				{
+					vt102_to_html(ses, ses->scroll->buffer[cnt]->str, out);
+				}
+				else
+				{
+					strcpy(out, ses->scroll->buffer[cnt]->str);
+				}
+				fprintf(fp, "%s\n", out);
+			}
+
+			fclose(fp);
+		}
+		else
+		{
+			show_error(ses, LIST_COMMAND, "#ERROR: #BUFFER WRITE {%s} - FAILED TO OPEN FILE.", arg1);
+		}
+	}
+	return;
+}
+
+DO_BUFFER(buffer_info)
+{
+	int index, memory;
+
+	check_buffer(ses);
+
+	tintin_printf2(ses, "Scroll row:  %d", ses->scroll->used);
+	tintin_printf2(ses, "Scroll max:  %d", ses->scroll->size);
+	tintin_printf2(ses, "Scroll line: %d", ses->scroll->line);
+	tintin_printf2(ses, "Scroll base: %d", ses->scroll->base);
+	tintin_printf2(ses, "Scroll wrap: %d", ses->scroll->wrap);
+
+	tintin_printf2(ses, "");
+
+	memory = 0;
+
+	for (index = 0 ; index < ses->scroll->used ; index++)
+	{
+		memory += sizeof(struct buffer_data) + strlen(ses->scroll->buffer[index]->str);
+	}
+
+	tintin_printf2(ses, "Memory use:  %d", memory);
+
+}
+
+
+DO_COMMAND(do_grep)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+	int scroll_cnt, grep_cnt, grep_min, grep_max, grep_add, page;
+
+	check_buffer(ses);
+
+	grep_cnt = grep_add = scroll_cnt = grep_min = 0;
+	grep_max = ses->split->bot_row - ses->split->top_row - 2;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: GREP [#] <SEARCH TEXT>");
+
+		return ses;
+	}
+
+	if (is_math(ses, arg1))
+	{
+		page = get_number(ses, arg1);
+
+		arg = get_arg_in_braces(ses, arg, arg2, GET_ALL);
+
+		if (*arg2 == 0)
+		{
+			show_error(ses, LIST_COMMAND, "#SYNTAX: #grep {%s} <SEARCH TEXT>", arg1);
+
+			return ses;
+		}
+	}
+	else
+	{
+		page = 1;
+
+		strcpy(arg2, arg1);
+	}
+
+	if (page > 0)
+	{
+		grep_min = grep_max * page - grep_max;
+		grep_max = grep_max * page;
+	}
+	else
+	{
+		grep_min = grep_max * (page * -1) - grep_max;
+		grep_max = grep_max * (page * -1);
+	}
+
+	gtd->level->grep++;
+
+	tintin_header(ses, " GREPPING PAGE %d FOR %s ", page, arg2);
+
+	if (page > 0)
+	{
+		for (scroll_cnt = ses->scroll->used - 1 ; scroll_cnt > 0 ; scroll_cnt--)
+		{
+			if (HAS_BIT(ses->scroll->buffer[scroll_cnt]->flags, BUFFER_FLAG_GREP))
+			{
+				continue;
+			}
+
+			if (find(ses, ses->scroll->buffer[scroll_cnt]->str, arg2, SUB_NONE, REGEX_FLAG_NONE))
+			{
+				grep_add = ses->scroll->buffer[scroll_cnt]->height;
+
+				if (grep_cnt + grep_add > grep_max)
+				{
+					break;
+				}
+
+				grep_cnt += grep_add;
+			}
+		}
+
+		if (grep_cnt <= grep_min)
+		{
+			show_error(ses, LIST_COMMAND, "#NO MATCHES FOUND.");
+
+			gtd->level->grep--;
+
+			return ses;
+		}
+
+		while (++scroll_cnt < ses->scroll->used)
+		{
+			if (HAS_BIT(ses->scroll->buffer[scroll_cnt]->flags, BUFFER_FLAG_GREP))
+			{
+				continue;
+			}
+
+			if (find(ses, ses->scroll->buffer[scroll_cnt]->str, arg2, SUB_NONE, REGEX_FLAG_NONE))
+			{
+				grep_add = ses->scroll->buffer[scroll_cnt]->height;
+
+				if (grep_cnt - grep_add < grep_min)
+				{
+					break;
+				}
+
+				grep_cnt -= grep_add;
+
+				tintin_puts2(ses, ses->scroll->buffer[scroll_cnt]->str);
+			}
+		}
+	}
+	else
+	{
+		for (scroll_cnt = 0 ; scroll_cnt < ses->scroll->used ; scroll_cnt++)
+		{
+			if (HAS_BIT(ses->scroll->buffer[scroll_cnt]->flags, BUFFER_FLAG_GREP))
+			{
+				continue;
+			}
+
+			if (find(ses, ses->scroll->buffer[scroll_cnt]->str, arg2, SUB_NONE, REGEX_FLAG_NONE))
+			{
+				grep_add = ses->scroll->buffer[scroll_cnt]->height;
+
+				if (grep_cnt + grep_add >= grep_min)
+				{
+					grep_cnt += grep_add;
+					
+					tintin_puts2(ses, ses->scroll->buffer[scroll_cnt]->str);
+
+					if (grep_cnt + grep_add > grep_max)
+					{
+						break;
+					}
+				}
+			}
+		}
+
+		if (grep_cnt == 0)
+		{
+			show_error(ses, LIST_COMMAND, "#NO MATCHES FOUND.");
+
+			gtd->level->grep--;
+
+			return ses;
+		}
+	}
+	tintin_header(ses, "");
+
+	gtd->level->grep--;
+
+	return ses;
+}

+ 2590 - 0
chat.c

@@ -0,0 +1,2590 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License version 3 as         *
+*   published by the Free Software Foundation.                                *
+*                                                                             *
+*   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 General Public License for more details.                              *
+*                                                                             *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License along   *
+*   with TinTin++.  If not see https://www.gnu.org/licenses/gpl-3.0.txt       *
+******************************************************************************/
+
+/******************************************************************************
+*               (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                  *
+*                                                                             *
+*                         coded by Sean Butler 1998                           *
+*                    recoded by Igor van den Hoven 2005                       *
+******************************************************************************/
+
+#include "tintin.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+
+#ifdef HAVE_PTHREAD_H
+	#include <pthread.h>
+#endif
+
+#define CALL_TIMEOUT    5
+#define BLOCK_SIZE    500
+#define DEFAULT_PORT 4050
+
+extern  int chat_new(int s);
+extern void chat_printf(char *format, ...);
+extern  int process_chat_input(struct chat_data *buddy);
+extern void get_chat_commands(struct chat_data *buddy, char *buf, int len);
+extern void chat_name_change(struct chat_data *buddy, char *txt);
+extern void chat_receive_text_everybody(struct chat_data *buddy, char *txt);
+extern void chat_receive_text_personal(struct chat_data *buddy, char *txt);
+extern void chat_receive_text_group(struct chat_data *buddy, char *txt);
+extern void chat_receive_message(struct chat_data *buddy, char *txt);
+extern void chat_receive_snoop_data(struct chat_data *buddy, char *txt);
+extern void ping_response(struct chat_data *ch, char *time);
+extern void request_response(struct chat_data *requester);
+extern void parse_requested_connections(struct chat_data *buddy, char *txt);
+extern void peek_response(struct chat_data *peeker);
+extern void parse_peeked_connections(struct chat_data *buddy, char *txt);
+extern void chat_receive_file(char *arg, struct chat_data *ch);
+extern void send_block(struct chat_data *ch);
+extern  int receive_block(unsigned char *s, struct chat_data *ch, int size);
+extern void deny_file(struct chat_data *ch, char *arg);
+extern void file_denied(struct chat_data *ch, char *txt);
+extern void file_cleanup(struct chat_data *buddy);
+extern  int get_file_size(char *fpath);
+extern void chat_puts(char *arg);
+extern char *fix_file_name(char *name);
+extern struct chat_data *find_buddy(char *arg);
+extern struct chat_data *find_group(char *arg);
+
+DO_COMMAND(do_chat)
+{
+	char cmd[BUFFER_SIZE], arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+	int cnt;
+
+	arg = get_arg_in_braces(ses, arg, cmd, GET_ONE);
+
+	if (*cmd == 0)
+	{
+		tintin_header(ses, " CHAT OPTIONS ");
+
+		for (cnt = 0 ; *chat_table[cnt].name != 0 ; cnt++)
+		{
+			tintin_printf2(ses, "  [%-13s] %s", chat_table[cnt].name, chat_table[cnt].desc);
+		}
+		tintin_header(ses, "");
+
+		return ses;
+	}
+
+	for (cnt = 0 ; *chat_table[cnt].name != 0 ; cnt++)
+	{
+		if (!is_abbrev(cmd, chat_table[cnt].name))
+		{
+			continue;
+		}
+
+		if (chat_table[cnt].fun != chat_initialize && gtd->chat == NULL)
+		{
+			tintin_printf(NULL, "\e[1;31m<CHAT> You must initialize a chat port first.");
+
+			return ses;
+		}
+
+		arg = sub_arg_in_braces(ses, arg, arg1,  chat_table[cnt].lval, SUB_VAR|SUB_FUN);
+		arg = sub_arg_in_braces(ses, arg, arg2, chat_table[cnt].rval, SUB_VAR|SUB_FUN);
+
+		chat_table[cnt].fun(arg1, arg2);
+
+		return ses;
+	}
+
+	do_chat(ses, "");
+
+	return ses;
+}
+
+
+DO_CHAT(chat_initialize)
+{
+	struct sockaddr_in sa;
+	struct linger ld;
+	int sock, port, optval = 0;
+
+	if (gtd->chat)
+	{
+		chat_printf("Already initialised");
+
+		return;
+	}
+
+	port = atoi(arg1) ? atoi(arg1) : DEFAULT_PORT;
+
+	sa.sin_family      = AF_INET;
+	sa.sin_port        = htons(port);
+	sa.sin_addr.s_addr = INADDR_ANY;
+
+	sock = socket(AF_INET, SOCK_STREAM, 0);
+
+	if (sock < 0)
+	{
+		syserr_printf(gtd->ses, "chat_initialize: socket");
+
+		return;
+	}
+
+	if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(int)) == -1)
+	{
+		syserr_printf(gtd->ses, "chat_initialize: setsockopt");
+	}
+
+	ld.l_onoff  = 0; 
+	ld.l_linger = 100;
+
+	setsockopt(sock, SOL_SOCKET, SO_LINGER, (char *) &ld, sizeof(ld));
+
+	if (fcntl(sock, F_SETFL, O_NDELAY|O_NONBLOCK) == -1)
+	{
+		syserr_printf(gtd->ses, "chat_initialize: fcntl O_NDELAY|O_NONBLOCK");
+	}
+
+	if (bind(sock, (struct sockaddr *) &sa, sizeof(sa)) < 0)
+	{
+		tintin_printf(NULL, "Port %d is already in use, cannot initiate chat.", port);
+
+		close(sock);
+
+		return;
+	}
+
+	if (listen(sock, 32) == -1)
+	{
+		syserr_printf(gtd->ses, "chat_initialize: listen");
+
+		close(sock);
+
+		return;
+	}
+	gtd->chat = (struct chat_data *) calloc(1, sizeof(struct chat_data));
+
+	gtd->chat->fd       = sock;
+	gtd->chat->port     = port;
+
+	gtd->chat->color    = strdup("\e[0;1;31m");
+	gtd->chat->download = strdup("");
+	gtd->chat->ip       = strdup("<Unknown>");
+	gtd->chat->name     = strdup("TinTin");
+	gtd->chat->reply    = strdup("");
+	gtd->chat->prefix   = strdup("<CHAT> ");
+
+	chat_printf("Initialized chat on port %d.", gtd->chat->port);
+}
+
+
+DO_CHAT(chat_uninitialize)
+{
+	int port = gtd->chat->port;
+
+	while (gtd->chat->next)
+	{
+		close_chat(gtd->chat->next, TRUE);
+	}
+	close_chat(gtd->chat, FALSE);
+
+	gtd->chat = NULL;
+
+	tintin_printf(NULL, "#OK: Uninitialized chat on port %d.", port);
+}
+
+
+int chat_new(int s)
+{
+	struct chat_data *new_buddy;
+	struct sockaddr_in sock;
+	socklen_t i;
+	int fd;
+
+	i = sizeof(sock);
+
+	getsockname(s, (struct sockaddr *) &sock, &i);
+
+	if ((fd = accept(s, (struct sockaddr *) &sock, &i)) < 0)
+	{
+		syserr_printf(gtd->ses, "chat_new: accept");
+
+		return -1;
+	}
+
+	if (fcntl(fd, F_SETFL, O_NDELAY|O_NONBLOCK) == -1)
+	{
+		syserr_printf(gtd->ses, "chat_new: fcntl O_NDELAY|O_NONBLOCK");
+	}
+
+	if (HAS_BIT(gtd->chat->flags, CHAT_FLAG_DND))
+	{
+		close(fd);
+
+		return -1;
+	}
+
+	new_buddy = (struct chat_data *) calloc(1, sizeof(struct chat_data));
+
+	new_buddy->fd       = fd;
+
+	new_buddy->download = strdup("");
+	new_buddy->group    = strdup("");
+	new_buddy->ip       = strdup(inet_ntoa(sock.sin_addr));
+	new_buddy->name     = strdup("Unknown");
+	new_buddy->version  = strdup("");
+
+	new_buddy->timeout  = gtd->time + CALL_TIMEOUT;
+
+	LINK(new_buddy, gtd->chat->next, gtd->chat->prev);
+
+	chat_printf("New connection: %s D%d.", new_buddy->ip, new_buddy->fd);
+
+	return 0;
+}
+
+// getaddrinfo should be universally supported anno 2019, if not, let me know.
+
+void *threaded_chat_call(void *arg)
+{
+	int sock, error;
+	char host[BUFFER_SIZE], port[BUFFER_SIZE], name[BUFFER_SIZE];
+	struct addrinfo *address;
+	static struct addrinfo hints;
+	struct chat_data *new_buddy;
+	struct timeval to;
+	fd_set wds, rds;
+
+	chat_printf("Attempting to call %s ...", arg);
+
+	to.tv_sec = CALL_TIMEOUT;
+	to.tv_usec = 0;
+
+	arg = (void *) get_arg_in_braces(gtd->ses, (char *) arg, host, GET_ONE);
+	arg = (void *) get_arg_in_braces(gtd->ses, (char *) arg, port, GET_ONE);
+
+	if (*port == 0)
+	{
+		sprintf(port, "%d", DEFAULT_PORT);
+	}
+
+	hints.ai_family   = AF_INET;
+	hints.ai_protocol = IPPROTO_TCP;
+	hints.ai_socktype = SOCK_STREAM;
+
+	error = getaddrinfo(host, port, &hints, &address);
+
+	switch (error)
+	{
+		case 0:
+			break;
+
+		case -2:
+			chat_printf("Failed to call %s, unknown host.", host);
+			return NULL;
+
+		default:
+			chat_printf("Failed to call %s.", host);
+			return NULL;
+	}
+
+	sock = socket(address->ai_family, address->ai_socktype, address->ai_protocol);
+
+	if (sock < 0)
+	{
+		syserr_printf(gtd->ses, "threaded_chat_call: socket");
+
+		freeaddrinfo(address);
+
+		return NULL;
+	}
+
+	switch (address->ai_family)
+	{
+		case AF_INET:
+			inet_ntop(address->ai_family, &((struct sockaddr_in *)address->ai_addr)->sin_addr, host, address->ai_addrlen);
+			break;
+
+		case AF_INET6:
+			inet_ntop(address->ai_family, &((struct sockaddr_in6 *)address->ai_addr)->sin6_addr, host, address->ai_addrlen);
+			break;
+	}
+
+	if (connect(sock, address->ai_addr, address->ai_addrlen) != 0)
+	{
+		chat_printf("Failed to connect to %s:%s", host, port);
+
+		close(sock);
+
+		freeaddrinfo(address);
+
+		return NULL;
+	}
+
+	FD_ZERO(&wds);
+
+	FD_SET(sock, &wds);
+
+	if (select(FD_SETSIZE, NULL, &wds, NULL, &to) == -1)
+	{
+		chat_printf("Failed to connect to %s %s", host, port);
+
+		close(sock);
+		freeaddrinfo(address);
+
+		return NULL;
+	}
+
+	if (!FD_ISSET(sock, &wds))
+	{
+		chat_printf("Connection timed out.");
+
+		close(sock);
+		freeaddrinfo(address);
+
+		return NULL;
+	}
+
+	new_buddy = calloc(1, sizeof(struct chat_data));
+
+	new_buddy->fd       = sock;
+	new_buddy->port     = atoi(port);
+
+	new_buddy->group    = strdup("");
+	new_buddy->ip       = strdup(host);
+	new_buddy->name     = strdup("");
+	new_buddy->version  = strdup("");
+	new_buddy->download = strdup("");
+
+	strip_vt102_codes(gtd->chat->name, name);
+
+	chat_socket_printf(new_buddy, "CHAT:%s\n%s%-5u", name, gtd->chat->ip, gtd->chat->port);
+
+	chat_printf("Socket connected, negotiating protocol...");
+
+	FD_ZERO(&rds);
+	FD_SET(sock, &rds);
+
+	to.tv_sec  = CALL_TIMEOUT;
+	to.tv_usec = 0;
+
+	if (select(FD_SETSIZE, &rds, NULL, NULL, &to) == -1)
+	{
+		close_chat(new_buddy, FALSE);
+		freeaddrinfo(address);
+
+		return NULL;
+	}
+
+	if (process_chat_input(new_buddy) == -1)
+	{
+		FD_CLR(new_buddy->fd, &rds);
+		close_chat(new_buddy, FALSE);
+		freeaddrinfo(address);
+
+		return NULL;
+	}
+
+	if (gtd->chat == NULL || *new_buddy->name == 0)
+	{
+		close_chat(new_buddy, FALSE);
+	}
+	else
+	{
+		if (fcntl(sock, F_SETFL, O_NDELAY|O_NONBLOCK) == -1)
+		{
+			syserr_printf(gtd->ses, "chat_new: fcntl O_NDELAY|O_NONBLOCK");
+		}
+
+#ifdef HAVE_LIBPTHREAD
+		while (gtd->chat->update)
+		{
+			chat_printf("Blocking the linking of %s.", new_buddy->name);
+
+			usleep(1000);
+
+			continue;
+		}
+#endif
+		LINK(new_buddy, gtd->chat->next, gtd->chat->prev);
+
+		chat_printf("Connection made to %s.", new_buddy->name);
+	}
+	freeaddrinfo(address);
+
+	return NULL;
+}
+
+/*
+void *threaded_chat_call(void *arg)
+{
+	int sock, dig;
+	char host[BUFFER_SIZE], port[BUFFER_SIZE], name[BUFFER_SIZE];
+	struct sockaddr_in dest_addr;
+	struct chat_data *new_buddy;
+	struct timeval to;
+	fd_set wds, rds;
+
+	chat_printf("Attempting to call %s ...", arg);
+
+	to.tv_sec = CALL_TIMEOUT;
+	to.tv_usec = 0;
+
+	arg = (void *) get_arg_in_braces(gtd->ses, (char *) arg, host, GET_ONE);
+	arg = (void *) get_arg_in_braces(gtd->ses, (char *) arg, port, GET_ONE);
+
+	if (*port == 0)
+	{
+		sprintf(port, "%d", DEFAULT_PORT);
+	}
+
+	if (sscanf(host, "%d.%d.%d.%d", &dig, &dig, &dig, &dig) == 4)
+	{
+		dest_addr.sin_addr.s_addr = inet_addr(host);
+	}
+	else
+	{
+		struct hostent *hp;
+		int addr, address[4];
+
+		if ((hp = gethostbyname(host)) == NULL)
+		{
+			chat_printf("Failed to call %s, unknown host.", host);
+
+			return NULL;
+		}
+		memcpy((char *)&dest_addr.sin_addr, hp->h_addr, sizeof(dest_addr.sin_addr));
+
+		addr = ntohl(dest_addr.sin_addr.s_addr);
+
+		address[0] = ( addr >> 24 ) & 0xFF ; 
+		address[1] = ( addr >> 16 ) & 0xFF ;
+		address[2] = ( addr >>  8 ) & 0xFF ;
+		address[3] = ( addr       ) & 0xFF ;
+
+		sprintf(host, "%d.%d.%d.%d", address[0], address[1], address[2], address[3]);
+	}
+
+	if (is_number(port))
+	{
+		dest_addr.sin_port = htons(atoi(port));
+	}
+	else
+	{
+		chat_printf("The port should be a number.");
+
+		return NULL;
+	}
+
+	if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
+	{
+		syserr_printf(gtd->ses, "old_threaded_chat_call: socket");
+
+		return NULL;
+	}
+
+	dest_addr.sin_family = AF_INET;
+
+	if (connect(sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr)) != 0)
+	{
+		chat_printf("Failed to connect to %s:%s", host, port);
+
+		close(sock);
+
+		return NULL;
+	}
+
+	FD_ZERO(&wds);
+
+	FD_SET(sock, &wds);
+
+	if (select(FD_SETSIZE, NULL, &wds, NULL, &to) == -1)
+	{
+		chat_printf("Failed to connect to %s %s", host, port);
+
+		close(sock);
+
+		return NULL;
+	}
+
+	if (!FD_ISSET(sock, &wds))
+	{
+		chat_printf("Connection timed out.");
+
+		close(sock);
+
+		return NULL;
+	}
+
+	new_buddy = (struct chat_data *) calloc(1, sizeof(struct chat_data));
+
+	new_buddy->fd       = sock;
+	new_buddy->port     = atoi(port);
+
+	new_buddy->download = strdup("");
+	new_buddy->group    = strdup("");
+	new_buddy->ip       = strdup(host);
+	new_buddy->name     = strdup("");
+	new_buddy->version  = strdup("");
+
+	strip_vt102_codes(gtd->chat->name, name);
+
+	chat_socket_printf(new_buddy, "CHAT:%s\n%s%-5u", name, gtd->chat->ip, gtd->chat->port);
+
+	chat_printf("Socket connected, negotiating protocol...");
+
+	FD_ZERO(&rds);
+	FD_SET(sock, &rds);
+
+	to.tv_sec  = CALL_TIMEOUT;
+	to.tv_usec = 0;
+
+	if (select(FD_SETSIZE, &rds, NULL, NULL, &to) == -1)
+	{
+		close_chat(new_buddy, FALSE);
+
+		return NULL;
+	}
+
+	if (process_chat_input(new_buddy) == -1)
+	{
+		FD_CLR(new_buddy->fd, &rds);
+		close_chat(new_buddy, FALSE);
+
+		return NULL;
+	}
+
+	if (gtd->chat == NULL || *new_buddy->name == 0)
+	{
+		close_chat(new_buddy, FALSE);
+	}
+	else
+	{
+		if (fcntl(sock, F_SETFL, O_NDELAY|O_NONBLOCK) == -1)
+		{
+			syserr_printf(gtd->ses, "chat_new: fcntl O_NDELAY|O_NONBLOCK");
+		}
+
+		LINK(new_buddy, gtd->chat->next, gtd->chat->prev);
+		chat_printf("Connection made to %s.", new_buddy->name);
+	}
+
+	return NULL;
+}
+
+*/
+
+#ifdef HAVE_LIBPTHREAD
+
+DO_CHAT(chat_call)
+{
+	static char buf[100][200];
+	static int i;
+	pthread_t thread;
+
+	if (strlen(arg1) + strlen(arg2) + 5 >= 200)
+	{
+		chat_printf("The call arguments {%s} and {%s} exceed the maximum length of 200 characters.", arg1, arg2);
+
+		return;
+	}
+	sprintf(buf[i], "{%s} {%s}", arg1, arg2);
+
+	pthread_create(&thread, NULL, threaded_chat_call, (void *) buf[i]);
+
+	if (++i > 99)
+	{
+		i = 0;
+	}
+}
+
+#else
+
+DO_CHAT(chat_call)
+{
+	char buf[BUFFER_SIZE];
+
+	sprintf(buf, "{%s} {%s}", arg1, arg2);
+
+	threaded_chat_call((void *) buf);
+}
+
+#endif
+
+
+void close_chat(struct chat_data *buddy, int unlink)
+{
+	buddy->flags = 0;
+
+	if (buddy == gtd->chat->update)
+	{
+		gtd->chat->update = buddy->next;
+	}
+
+	if (unlink)
+	{
+		UNLINK(buddy, gtd->chat->next, gtd->chat->prev);
+	}
+
+	if (buddy != gtd->chat)
+	{
+		if (*buddy->name == 0)
+		{
+			chat_printf("Closing connection to %s D%d", buddy->ip, buddy->fd);
+		}
+		else
+		{
+			chat_printf("Closing connection to %s@%s.", buddy->name, buddy->ip);
+		}
+	}
+
+	close(buddy->fd);
+
+	free(buddy->download);
+	free(buddy->group);
+	free(buddy->ip);
+	free(buddy->name);
+	free(buddy->version);
+
+	free(buddy);
+}
+
+
+void process_chat_connections(fd_set *read_set, fd_set *write_set, fd_set *exc_set)
+{
+	struct chat_data *buddy;
+
+	push_call("process_chat_connections(%p,%p,%p)",read_set,write_set,exc_set);
+
+	if (FD_ISSET(gtd->chat->fd, read_set))
+	{
+		chat_new(gtd->chat->fd);
+	}
+
+	for (buddy = gtd->chat->next ; buddy ; buddy = gtd->chat->update)
+	{
+		gtd->chat->update = buddy->next;
+
+		if (HAS_BIT(buddy->flags, CHAT_FLAG_LINKLOST) || FD_ISSET(buddy->fd, exc_set))
+		{
+			FD_CLR(buddy->fd, write_set);
+			FD_CLR(buddy->fd, read_set);
+
+			close_chat(buddy, TRUE);
+		}
+		else if (FD_ISSET(buddy->fd, read_set) && process_chat_input(buddy) < 0)
+		{
+			FD_CLR(buddy->fd, write_set);
+			FD_CLR(buddy->fd, read_set);
+
+			close_chat(buddy, TRUE);
+		}
+	}
+	pop_call();
+	return;
+}
+
+void chat_socket_printf(struct chat_data *buddy, char *format, ...)
+{
+	char buf[BUFFER_SIZE];
+	va_list args;
+
+	va_start(args, format);
+	vsnprintf(buf, BUFFER_SIZE / 3, format, args);
+	va_end(args);
+
+	if (!HAS_BIT(buddy->flags, CHAT_FLAG_LINKLOST))
+	{
+		if (write(buddy->fd, buf, strlen(buf)) < 0)
+		{
+			chat_printf("%s went link lost.", buddy->name);
+
+			SET_BIT(buddy->flags, CHAT_FLAG_LINKLOST);
+		}
+	}
+}
+
+void chat_printf(char *format, ...)
+{
+	struct chat_data *buddy;
+	char buf[STRING_SIZE], tmp[STRING_SIZE];
+	va_list args;
+
+	va_start(args, format);
+	vsnprintf(buf, BUFFER_SIZE / 3, format, args);
+	va_end(args);
+
+	if (strncmp(buf, gtd->chat->prefix, strlen(gtd->chat->prefix)))
+	{
+		sprintf(tmp, "%s%s", gtd->chat->prefix, buf);
+	}
+	else
+	{
+		sprintf(tmp, "%s", buf);
+	}
+
+	strip_vt102_codes_non_graph(tmp, buf);
+
+	sprintf(tmp, "%c%s%c", CHAT_SNOOP_DATA, buf, CHAT_END_OF_COMMAND);
+
+	for (buddy = gtd->chat->next ; buddy ; buddy = buddy->next)
+	{
+		if (HAS_BIT(buddy->flags, CHAT_FLAG_FORWARD) && !HAS_BIT(buddy->flags, CHAT_FLAG_FORWARDALL))
+		{
+			chat_socket_printf(buddy, "%s", tmp);
+		}
+	}
+	sprintf(tmp, "%s%s%s", gtd->chat->color, buf, "\e[0m");
+
+	check_all_events(gtd->ses, SUB_ARG|SUB_SEC, 0, 2, "CHAT MESSAGE", tmp, buf);
+
+	if (!check_all_events(gtd->ses, SUB_ARG|SUB_SEC, 0, 2, "CATCH CHAT MESSAGE", tmp, buf))
+	{
+		tintin_puts(NULL, tmp);
+	}
+}
+
+
+int process_chat_input(struct chat_data *buddy)
+{
+	struct chat_data *node;
+
+	char buf[BUFFER_SIZE], name[BUFFER_SIZE], temp[BUFFER_SIZE], ip[BUFFER_SIZE], *sep;
+	int size;
+
+	push_call("process_chat_input(%p)",buddy);
+
+	size = read(buddy->fd, buf, BUFFER_SIZE / 3);
+
+	if (size <= 0)
+	{
+		pop_call();
+		return -1;
+	}
+
+	buf[size] = 0;
+
+	if (!strncmp(buf, "CHAT:", 5))
+	{
+		if ((sep = strchr(buf, '\n')) != NULL)
+		{
+			*sep = 0;
+
+			strcpy(temp, &buf[5]);
+			strcpy(ip,   &sep[1]);
+
+			if (strlen(ip) >= 5)
+			{
+				buddy->port = atoi(ip + strlen(ip) - 5);
+			}
+
+			strip_vt102_codes(temp, name);
+
+			RESTRING(buddy->name, name);
+
+			buddy->timeout = 0;
+
+			if (strlen(buddy->name) > 20)
+			{
+				chat_socket_printf(buddy, "%c\n%s has refused your connection because your name is too long.\n%c", CHAT_MESSAGE, gtd->chat->name, name, CHAT_END_OF_COMMAND);
+
+				chat_printf("Refusing connection from %.21s:%d, name too long. (%d characters)", buddy->ip, buddy->port, strlen(buddy->name));
+
+				pop_call();
+				return -1;
+			}
+
+			for (node = gtd->chat ; node ; node = node->next)
+			{
+				if (node != buddy && !strcasecmp(name, node->name))
+				{
+					if (!strcmp(buddy->ip, node->ip))
+					{
+						close_chat(node, TRUE);
+						break;
+					}
+					else
+					{
+						chat_socket_printf(buddy, "%c\n%s is already connected to someone named %s.\n%c", CHAT_MESSAGE, gtd->chat->name, name, CHAT_END_OF_COMMAND);
+
+						chat_printf("Refusing connection from %s:%d, already connected to someone named %s.", buddy->ip, buddy->port, name);
+
+						pop_call();
+						return -1;
+					}
+				}
+			}
+
+			if (!strcasecmp(buddy->name, "ALL"))
+			{
+				chat_socket_printf(buddy, "%c\n%s is an invalid name.\n%c", CHAT_MESSAGE, name, CHAT_END_OF_COMMAND);
+
+				chat_printf("Refusing connection from %s:%d, %s is an invalid name.", buddy->ip, buddy->port, name);
+
+				pop_call();
+				return -1;
+			}
+		}
+
+		strip_vt102_codes(gtd->chat->name, name);
+
+		chat_socket_printf(buddy, "YES:%s\n", name);
+
+		chat_printf("Connected to %s@%s:%d", buddy->name, buddy->ip, buddy->port);
+
+		pop_call();
+		return 1;
+	}
+
+	if (!strncmp(buf, "YES:", 4))
+	{
+		if ((sep = strchr(buf, '\n')) != NULL)
+		{
+			*sep++ = 0;
+
+			strcpy(temp, buf);
+
+			strip_vt102_codes(&temp[4], name);
+
+			RESTRING(buddy->name, name);
+
+			chat_socket_printf(buddy, "%c%s %s%c", CHAT_VERSION, CLIENT_NAME, CLIENT_VERSION, CHAT_END_OF_COMMAND);
+
+			get_chat_commands(buddy, sep, size - strlen(temp) - 1);
+
+			pop_call();
+			return 0;
+		}
+		else
+		{
+			chat_printf("Error in processing connection negotiation with %s@%s", buddy->name, buddy->ip);
+
+			pop_call();
+			return -1;
+		}
+	}
+
+	if (!strncmp(buf, "NO", 2))
+	{
+		chat_printf("Connection negotation refused by %s@%s", buddy->name, buddy->ip);
+
+		pop_call();
+		return -1;
+	}
+
+	get_chat_commands(buddy, buf, size);
+
+	pop_call();
+	return 0;
+}
+
+
+void get_chat_commands(struct chat_data *buddy, char *buf, int len)
+{
+	char txt[BUFFER_SIZE];
+	unsigned char *pto, *pti, ptc;
+	int size;
+
+	push_call("get_chat_commands(%s,%d,%s)",buddy->name,len,buf);
+
+	pti = (unsigned char *) buf;
+	pto = (unsigned char *) txt;
+
+	while (*pti == CHAT_FILE_BLOCK || gtd->chat->file_block_patch)
+	{
+		if (gtd->chat->file_block_patch)
+		{
+			size = receive_block(pti, buddy, len);
+
+			len -= size;
+			pti += size;
+		}
+		else
+		{
+			len--;
+			pti++;
+
+			size = receive_block(pti, buddy, len);
+
+			len -= size;
+			pti += size;
+
+			if (len <= 0)
+			{
+				pop_call();
+				return;
+			}
+		}
+	}
+
+	while (*pti && buddy)
+	{
+		ptc = *pti++;
+		pto = (unsigned char *) txt;
+
+		while (isspace(*pti))
+		{
+			pti++;
+		}
+
+		while (*pti != CHAT_END_OF_COMMAND)
+		{
+			if (*pti == 0)
+			{
+				chat_printf("Unterminated command: %d %s", ptc, buf);
+				pop_call();
+				return;
+			}
+			*pto++ = *pti++;
+		}
+
+		*pto-- = 0;
+
+		while (isspace(*pto))
+		{
+			*pto-- = 0;
+		}
+
+		switch (ptc)
+		{
+			case CHAT_NAME_CHANGE:
+				chat_name_change(buddy, txt);
+				break;
+
+			case CHAT_REQUEST_CONNECTIONS:
+				request_response(buddy);
+				break;
+
+			case CHAT_CONNECTION_LIST:
+				parse_requested_connections(buddy, txt);
+				break;
+
+			case CHAT_TEXT_EVERYBODY:
+				chat_receive_text_everybody(buddy, txt);
+				break;
+
+			case CHAT_TEXT_PERSONAL:
+				chat_receive_text_personal(buddy, txt);
+				break;
+
+			case CHAT_TEXT_GROUP:
+				chat_receive_text_group(buddy, txt);
+				break;
+
+			case CHAT_MESSAGE:
+				chat_receive_message(buddy, txt);
+				break;
+
+			case CHAT_DO_NOT_DISTURB:
+				chat_printf("%s has enabled DND.", buddy->name);
+				break;
+
+			case CHAT_SEND_ACTION:
+			case CHAT_SEND_ALIAS:
+			case CHAT_SEND_VARIABLE:
+			case CHAT_SEND_EVENT:
+			case CHAT_SEND_GAG:
+			case CHAT_SEND_HIGHLIGHT:
+			case CHAT_SEND_LIST:
+			case CHAT_SEND_ARRAY:
+			case CHAT_SEND_BARITEM:
+				chat_socket_printf(buddy, "%c%s%c", CHAT_MESSAGE, "\nTintin++ does not support this.\n", CHAT_END_OF_COMMAND);
+				break;
+
+			case CHAT_VERSION:
+				if (*buddy->version == 0 && *txt != 0)
+				{
+					chat_socket_printf(buddy, "%c%s %s%c", CHAT_VERSION, CLIENT_NAME, CLIENT_VERSION, CHAT_END_OF_COMMAND);
+
+					RESTRING(buddy->version, txt);
+				}
+				break;
+
+			case CHAT_FILE_START:
+				chat_receive_file(txt, buddy);
+				break;
+
+			case CHAT_FILE_DENY:
+				file_denied(buddy, txt);
+				break;
+
+			case CHAT_FILE_BLOCK_REQUEST:
+				send_block(buddy);
+				break;
+
+			case CHAT_FILE_END:
+				chat_printf("File transfer completion acknowledged.");
+				break;
+
+			case CHAT_FILE_CANCEL:
+				chat_printf("File cancel request received.");
+				file_cleanup(buddy);
+				break;
+
+			case CHAT_PING_REQUEST:
+				chat_socket_printf(buddy, "%c%s%c", CHAT_PING_RESPONSE, txt, CHAT_END_OF_COMMAND);
+				break;
+
+			case CHAT_PING_RESPONSE:
+				ping_response(buddy, txt);
+				break;
+
+			case CHAT_PEEK_CONNECTIONS:
+				peek_response(buddy);
+				break;
+
+			case CHAT_PEEK_LIST:
+				parse_peeked_connections(buddy, txt);
+				break;
+
+			case CHAT_SNOOP_START:
+				break;
+
+			case CHAT_SNOOP_DATA:
+				SET_BIT(buddy->flags, CHAT_FLAG_FORWARDBY);
+				DEL_BIT(buddy->flags, CHAT_FLAG_FORWARD);
+				DEL_BIT(buddy->flags, CHAT_FLAG_FORWARDALL);
+				chat_receive_snoop_data(buddy, txt);
+				break;
+
+			default:
+				chat_printf("get_chat_commands: unknown option [%d] from %s@%s:%d (%s)", ptc, buddy->name, buddy->ip, buddy->port, txt);
+				break;
+		}
+		pti++;
+	}
+	pop_call();
+	return;
+}
+
+
+void chat_name_change(struct chat_data *buddy, char *txt)
+{
+	char temp[BUFFER_SIZE], name[BUFFER_SIZE];
+	struct chat_data *node;
+
+	strip_vt102_codes(txt, name);
+
+	if (strlen(name) > 20)
+	{
+		chat_socket_printf(buddy, "%c\n%s has refused your name change because your name is too long.\n%c", CHAT_MESSAGE, gtd->chat->name, name, CHAT_END_OF_COMMAND);
+
+		chat_printf("Refusing connection from %.21s:%d, name too long. (%d characters)", buddy->ip, buddy->port, strlen(name));
+
+		close_chat(buddy, TRUE);
+
+		return;
+	}
+
+	for (node = gtd->chat ; node ; node = node->next)
+	{
+		if (node != buddy && !strcmp(name, node->name))
+		{
+			chat_socket_printf(buddy, "%c\n%s is already connected to someone named %s.\n%c", CHAT_MESSAGE, gtd->chat->name, name, CHAT_END_OF_COMMAND);
+
+			chat_printf("Refusing name change from %s@%s:%d, already connected to someone named %s.", buddy->name, buddy->ip, buddy->port, name);
+
+			close_chat(buddy, TRUE);
+
+			return;
+		}
+	}
+
+	if (!strcasecmp(name, "ALL"))
+	{
+		chat_socket_printf(buddy, "%c\n%s is an invalid name.\n%c", CHAT_MESSAGE, name, CHAT_END_OF_COMMAND);
+
+		chat_printf("Refusing name change from %s@%s:%d, %s is an invalid name.", buddy->name, buddy->ip, buddy->port, name);
+
+		close_chat(buddy, TRUE);
+
+		return;
+	}
+
+	if (strcmp(name, buddy->name))
+	{
+		strcpy(temp, buddy->name);
+
+		RESTRING(buddy->name, name);
+
+		chat_printf("%s is now %s.", temp, txt);
+	}
+}
+
+void chat_receive_text_everybody(struct chat_data *buddy, char *txt)
+{
+	struct chat_data *node;
+
+	if (HAS_BIT(buddy->flags, CHAT_FLAG_IGNORE))
+	{
+		return;
+	}
+
+	chat_printf("%s", txt);
+
+	if (HAS_BIT(buddy->flags, CHAT_FLAG_SERVE))
+	{
+		for (node = gtd->chat->next ; node ; node = node->next)
+		{
+			if (node != buddy)
+			{
+				chat_socket_printf(node, "%c\n%s %s[Served By %s%s]\n%c", CHAT_MESSAGE, txt, gtd->chat->color, gtd->chat->name, gtd->chat->color, CHAT_END_OF_COMMAND);
+			}
+		}
+	}
+	else
+	{
+		for (node = gtd->chat->next ; node ; node = node->next)
+		{
+			if (HAS_BIT(node->flags, CHAT_FLAG_SERVE))
+			{
+				chat_socket_printf(node, "%c\n%s %s[Served By %s%s]\n%c", CHAT_MESSAGE, txt, gtd->chat->color, gtd->chat->name, gtd->chat->color, CHAT_END_OF_COMMAND);
+			}
+		}
+	}
+}
+
+
+void chat_receive_text_personal(struct chat_data *buddy, char *txt)
+{
+	if (HAS_BIT(buddy->flags, CHAT_FLAG_IGNORE))
+	{
+		return;
+	}
+	RESTRING(gtd->chat->reply, buddy->name);
+
+	chat_printf("%s", txt);
+}
+
+void chat_receive_text_group(struct chat_data *buddy, char *txt)
+{
+	if (HAS_BIT(buddy->flags, CHAT_FLAG_IGNORE))
+	{
+		return;
+	}
+
+	if (strlen(txt) > 16)
+	{
+		chat_printf("%s", &txt[16]);
+	}
+}
+
+void chat_receive_message(struct chat_data *buddy, char *txt)
+{
+	if (HAS_BIT(buddy->flags, CHAT_FLAG_IGNORE))
+	{
+		return;
+	}
+
+	chat_printf("%s", txt);
+}
+
+
+void chat_receive_snoop_data(struct chat_data *buddy, char *txt)
+{
+	if (HAS_BIT(buddy->flags, CHAT_FLAG_IGNORE))
+	{
+		return;
+	}
+
+	chat_printf("%s", txt);
+}
+
+
+void peek_response(struct chat_data *peeker)
+{
+	struct chat_data *buddy;
+	char buf[BUFFER_SIZE];
+
+	for (buf[0] = 0, buddy = gtd->chat->next ; buddy ; buddy = buddy->next)
+	{
+		if (!HAS_BIT(buddy->flags, CHAT_FLAG_PRIVATE))
+		{
+			cat_sprintf(buf, "%s~%d~%s~", buddy->ip, buddy->port, buddy->name);
+		}
+	}
+	chat_socket_printf(peeker, "%c%s%c", CHAT_PEEK_LIST, buf, CHAT_END_OF_COMMAND);
+
+	return;
+}
+
+
+void parse_peeked_connections(struct chat_data *buddy, char *txt)
+{
+	char *comma, ip[BUFFER_SIZE], port[BUFFER_SIZE], name[BUFFER_SIZE];
+
+	if (*txt == 0)
+	{
+		chat_printf("%s has no public connections.", buddy->name);
+
+		return;
+	}
+
+	ip[0] = port[0] = name[0] = 0;
+
+	comma = txt;
+
+	chat_printf(" %-15s   %-15s   %-5s", "Name", "Address", "Port");
+	chat_printf(" ---------------   ---------------   ----- ");
+
+	while (comma)
+	{
+		comma = strchr(txt, '~');
+
+		if (comma)
+		{
+			*comma = 0;
+		}
+
+		if (*ip == 0)
+		{
+			strcpy(ip, txt);
+		}
+		else if (*port == 0)
+		{
+			strcpy(port, txt);
+		}
+		else
+		{
+			strcpy(name, txt);
+		}
+
+		if (comma)
+		{
+			txt += strlen(txt) + 1;
+		}
+
+		if (*ip && *port && *name)
+		{
+			chat_printf(" %-15s   %-15s   %-5s", name, ip, port);
+
+			*port = 0;
+			*ip   = 0;
+			*name = 0;
+		}
+	}
+	return;
+}
+
+
+void ping_response(struct chat_data *ch, char *time)
+{
+	chat_printf("Ping response time for %s: %lld ms", ch->name, (utime() - atoll(time)) / 1000);
+}
+
+
+void request_response(struct chat_data *requester)
+{
+	struct chat_data *buddy;
+	char buf[BUFFER_SIZE], tmp[BUFFER_SIZE];
+
+	chat_printf("%s has requested your public connections.", requester->name);
+
+	for (buf[0] = 0, buddy = gtd->chat->next ; buddy ; buddy = buddy->next)
+	{
+		if (buddy != requester && !HAS_BIT(buddy->flags, CHAT_FLAG_PRIVATE))
+		{
+			sprintf(tmp, "%s,%-5u", buddy->ip, buddy->port);
+
+			if (*buf)
+			{
+				strcat(buf, ",");
+			}
+			strcat(buf, tmp);
+		}
+	}
+	chat_socket_printf(requester, "%c%s%c", CHAT_CONNECTION_LIST, buf, CHAT_END_OF_COMMAND);
+
+	return;
+}
+
+
+void parse_requested_connections(struct chat_data *buddy, char *txt)
+{
+	struct chat_data *node;
+	char *comma, ip[BUFFER_SIZE], port[BUFFER_SIZE];
+
+	if (!HAS_BIT(buddy->flags, CHAT_FLAG_REQUEST))
+	{
+		chat_printf("%s tried to force your client to connect to: %s.", buddy->name, txt);
+
+		return;
+	}
+
+	ip[0] = 0;
+	port[0] = 0;
+	comma = txt;
+
+	while (comma)
+	{
+		comma = strchr(txt, ',');
+
+		if (comma)
+		{
+			*comma = 0;
+		}
+
+		if (*ip == 0)
+		{
+			strcpy(ip, txt);
+		}
+		else
+		{
+			strcpy(port, txt);
+		}
+
+		if (comma)
+		{
+			txt += strlen(txt) + 1;
+		}
+
+		if (*ip && *port)
+		{
+			for (node = gtd->chat->next ; node ; node = node->next)
+			{
+				if (!strcmp(ip, node->ip) && atoi(port) == node->port)
+				{
+					chat_printf("skipping known address: %s port %s", ip, port);
+					break;
+				}
+			}
+			if (node == NULL)
+			{
+				chat_call(ip, port);
+			}
+			*port = 0;
+			*ip   = 0;
+		}
+	}
+	DEL_BIT(buddy->flags, CHAT_FLAG_REQUEST);
+
+	return;
+}
+
+
+DO_CHAT(chat_downloaddir)
+{
+	char dir[BUFFER_SIZE];
+
+	sprintf(dir, "%s%s", arg1, !str_suffix(arg1, "/") ? "" : "/");
+
+	RESTRING(gtd->chat->download, dir);
+
+	chat_printf("Download directory set to '%s'", gtd->chat->download);
+}
+
+
+DO_CHAT(chat_emote)
+{
+	struct chat_data *buddy;
+
+	substitute(gtd->ses, arg2, arg2, SUB_COL|SUB_ESC);
+
+	if (!strcasecmp(arg1, "ALL"))
+	{
+		chat_printf("You emote to everyone: %s %s", gtd->chat->name, arg2);
+
+		for (buddy = gtd->chat->next ; buddy ; buddy = buddy->next)
+		{
+			chat_socket_printf(buddy, "%c\n%s %s\n%c", CHAT_TEXT_EVERYBODY, gtd->chat->name, arg2, CHAT_END_OF_COMMAND);
+		}
+	}
+	else
+	{
+		if ((buddy = find_buddy(arg1)) != NULL)
+		{
+			chat_printf("You emote to %s: %s %s", buddy->name, gtd->chat->name, arg2);
+
+			chat_socket_printf(buddy, "%c\n%s %s\n%c", CHAT_TEXT_PERSONAL, gtd->chat->name, arg2, CHAT_END_OF_COMMAND);
+		}
+		else if (find_group(arg1) != NULL)
+		{
+			chat_printf("You emote to %s: %s", arg1, arg2);
+
+			for (buddy = gtd->chat->next ; buddy ; buddy = buddy->next)
+			{
+				if (!strcmp(buddy->group, arg1))
+				{
+					chat_socket_printf(buddy, "%c%-15s\n%s %s\n%c", CHAT_TEXT_GROUP, buddy->group, gtd->chat->name, arg2, CHAT_END_OF_COMMAND);
+				}
+			}
+		}
+		else
+		{
+			chat_printf("You are not connected to anyone named '%s'.", arg1);
+		}
+	}
+}
+
+
+DO_CHAT(chat_info)
+{
+	tintin_printf2(NULL, "Name                 : %s", gtd->chat->name);
+	tintin_printf2(NULL, "IP Address           : %s", gtd->chat->ip);
+	tintin_printf2(NULL, "Chat Port            : %d", gtd->chat->port);
+	tintin_printf2(NULL, "Download Dir         : %s", gtd->chat->download);
+	tintin_printf2(NULL, "Reply                : %s", gtd->chat->reply);
+	tintin_printf2(NULL, "Prefix               : %s", gtd->chat->prefix);
+	tintin_printf2(NULL, "Color                : %s", str_convert_meta(gtd->chat->color, TRUE));
+	tintin_printf2(NULL, "DND                  : %s", HAS_BIT(gtd->chat->flags, CHAT_FLAG_DND) ? "Yes" : "No");
+}
+
+
+DO_CHAT(chat_ip)
+{
+	RESTRING(gtd->chat->ip, arg1);
+
+	chat_printf("IP changed to %s", gtd->chat->ip);
+}
+
+
+DO_CHAT(chat_message)
+{
+	struct chat_data *buddy;
+
+	substitute(gtd->ses, arg2, arg2, SUB_COL|SUB_ESC);
+
+	if (!strcasecmp(arg1, "ALL"))
+	{
+		chat_printf("You chat to everyone, '%s'", arg2);
+
+		for (buddy = gtd->chat->next ; buddy ; buddy = buddy->next)
+		{
+			chat_socket_printf(buddy, "%c\n%s chats to everyone, '%s'\n%c", CHAT_TEXT_EVERYBODY, gtd->chat->name, arg2, CHAT_END_OF_COMMAND);
+		}
+	}
+	else
+	{
+		if ((buddy = find_buddy(arg1)) != NULL)
+		{
+			chat_printf("You chat to %s, '%s'", buddy->name, arg2);
+
+			chat_socket_printf(buddy, "%c\n%s chats to you, '%s'\n%c", CHAT_TEXT_PERSONAL, gtd->chat->name, arg2, CHAT_END_OF_COMMAND);
+		}
+		else if (find_group(arg1) != NULL)
+		{
+			chat_printf("You chat to %s, '%s'", arg1, arg2);
+
+			for (buddy = gtd->chat->next ; buddy ; buddy = buddy->next)
+			{
+				if (!strcmp(buddy->group, arg1))
+				{
+					chat_socket_printf(buddy, "%c%-15s\n%s chats to the group, '%s'\n%c", CHAT_TEXT_GROUP, buddy->group, gtd->chat->name, arg2, CHAT_END_OF_COMMAND);
+				}
+			}
+		}
+		else
+		{
+			chat_printf("You are not connected to anyone named '%s'.", arg1);
+		}
+	}
+}
+
+
+DO_CHAT(chat_name)
+{
+	struct chat_data *buddy;
+
+	substitute(gtd->ses, arg1, arg1, SUB_COL|SUB_ESC);
+
+	if (!strcmp(gtd->chat->name, arg1))
+	{
+		chat_printf("Your name is already set to %s.", gtd->chat->name);
+
+		return;
+	}
+
+	if (strip_vt102_strlen(gtd->ses, arg1) > 20)
+	{
+		chat_printf("Your name cannot be longer than 20 characters.");
+
+		return;
+	}
+
+	RESTRING(gtd->chat->name, arg1);
+
+	for (buddy = gtd->chat->next ; buddy ; buddy = buddy->next)
+	{
+		chat_socket_printf(buddy, "%c%s%c", CHAT_NAME_CHANGE, gtd->chat->name, CHAT_END_OF_COMMAND);
+	}
+	chat_printf("Name changed to %s.", gtd->chat->name);
+}
+
+
+DO_CHAT(chat_paste)
+{
+	struct chat_data *buddy;
+	char temp[BUFFER_SIZE + 2], name[BUFFER_SIZE], *arg;
+
+	if (arg1 == NULL)
+	{
+		if (strlen(gtd->input_buf))
+		{
+			sprintf(temp, "%s\n%s", gtd->chat->paste_buf, gtd->input_buf);
+
+			RESTRING(gtd->chat->paste_buf, temp);
+
+			cursor_clear_line(gtd->ses, "");
+		}
+
+		arg = get_arg_in_braces(gtd->ses, gtd->chat->paste_buf, name, GET_ONE);
+
+		sprintf(temp, "%s\n<078>======================================================================", arg);
+
+		substitute(gtd->ses, temp, temp, SUB_COL|SUB_ESC);
+
+		RESTRING(gtd->chat->paste_buf, temp);
+
+		if (!strcasecmp(name, "ALL"))
+		{
+			chat_printf("You paste to everyone:\n%s", gtd->chat->paste_buf);
+
+			for (buddy = gtd->chat->next ; buddy ; buddy = buddy->next)
+			{
+				chat_socket_printf(buddy, "%c\n%s pastes to everyone:\n%s\n%c", CHAT_TEXT_EVERYBODY, gtd->chat->name, gtd->chat->paste_buf, CHAT_END_OF_COMMAND);
+			}
+		}
+		else
+		{
+			if ((buddy = find_buddy(name)) != NULL)
+			{
+				chat_printf("You paste to %s:\n%s", buddy->name, gtd->chat->paste_buf);
+	
+				chat_socket_printf(buddy, "%c\n%s pastes to you:\n%s\n%c", CHAT_TEXT_EVERYBODY, gtd->chat->name, gtd->chat->paste_buf, CHAT_END_OF_COMMAND);
+			}
+			else if (find_group(name) != NULL)
+			{
+				chat_printf("You paste to %s:\n%s", name, gtd->chat->paste_buf);
+
+				for (buddy = gtd->chat->next ; buddy ; buddy = buddy->next)
+				{
+					if (!strcmp(buddy->group, name))
+					{
+						chat_socket_printf(buddy, "%c%-15s\n%s pastes to the group:\n%s\n%c", CHAT_TEXT_GROUP, buddy->group, gtd->chat->name, gtd->chat->paste_buf, CHAT_END_OF_COMMAND);
+					}
+				}
+			}
+			else
+			{
+				chat_printf("You are not connected to anyone named '%s'.", name);
+			}
+		}
+
+		if (IS_SPLIT(gtd->ses))
+		{
+			erase_toeol();
+		}
+		gtd->chat->paste_time = 0;
+
+		return;
+	}
+
+	if (gtd->chat->paste_time)
+	{
+		sprintf(temp, "%s\n%s", gtd->chat->paste_buf, arg1);
+
+		RESTRING(gtd->chat->paste_buf, temp);
+
+		gtd->chat->paste_time = 200000LL + utime();
+
+		return;
+	}
+
+	gtd->chat->paste_time = 400000LL + utime();
+
+	sprintf(temp, "{%s}<078>======================================================================\n<068>%s", arg1, arg2);
+
+	RESTRING(gtd->chat->paste_buf, temp);
+}
+
+
+DO_CHAT(chat_peek)
+{
+	struct chat_data *buddy;
+
+	if ((buddy = find_buddy(arg1)) == NULL)
+	{
+		chat_printf("You are not connected to anyone named '%s'.", arg1);
+
+		return;
+	}
+	chat_socket_printf(buddy, "%c%c", CHAT_PEEK_CONNECTIONS, CHAT_END_OF_COMMAND);
+}
+
+
+DO_CHAT(chat_ping)
+{
+	struct chat_data *buddy;
+
+	if ((buddy = find_buddy(arg1)) == NULL)
+	{
+		chat_printf("You are not connected to anyone named '%s'.", arg1);
+
+		return;
+	}
+
+	chat_socket_printf(buddy, "%c%lld%c", CHAT_PING_REQUEST, utime(), CHAT_END_OF_COMMAND);
+
+	chat_printf("Ping request sent to %s.", buddy->name);
+}
+
+DO_CHAT(chat_prefix)
+{
+	RESTRING(gtd->chat->prefix, arg1);
+
+	chat_printf("Prefix set to '%s'", gtd->chat->prefix);
+}
+
+DO_CHAT(chat_reply)
+{
+	struct chat_data *buddy;
+
+	substitute(gtd->ses, arg1, arg1, SUB_COL|SUB_ESC);
+
+	if ((buddy = find_buddy(gtd->chat->reply)) != NULL)
+	{
+		chat_printf("You reply to %s, '%s'", buddy->name, arg1);
+
+		chat_socket_printf(buddy, "%c\n%s replies to you, '%s'\n%c", CHAT_TEXT_PERSONAL, gtd->chat->name, arg1, CHAT_END_OF_COMMAND);
+	}
+	else
+	{
+		chat_printf("You are not connected to anyone named '%s'.", gtd->chat->reply);
+	}
+}
+
+DO_CHAT(chat_request)
+{
+	struct chat_data *buddy;
+
+	if ((buddy = find_buddy(arg1)) == NULL)
+	{
+		chat_printf("You are not connected to anyone named '%s'.", arg1);
+
+		return;
+	}
+	chat_socket_printf(buddy, "%c%c", CHAT_REQUEST_CONNECTIONS, CHAT_END_OF_COMMAND);
+
+	chat_printf("You request %s's public connections.", buddy->name);
+
+	SET_BIT(buddy->flags, CHAT_FLAG_REQUEST);
+
+	return;
+}
+
+
+DO_CHAT(chat_send)
+{
+	struct chat_data *buddy;
+
+	substitute(gtd->ses, arg2, arg2, SUB_COL|SUB_ESC);
+
+	if (!strcasecmp(arg1, "ALL"))
+	{
+		for (buddy = gtd->chat->next ; buddy ; buddy = buddy->next)
+		{
+			chat_socket_printf(buddy, "%s", arg2);
+		}
+	}
+	else
+	{
+		if ((buddy = find_buddy(arg1)) != NULL)
+		{
+			chat_socket_printf(buddy, "%s", arg2);
+		}
+		else if (find_group(arg1) != NULL)
+		{
+			for (buddy = gtd->chat->next ; buddy ; buddy = buddy->next)
+			{
+				if (!strcmp(buddy->group, arg1))
+				{
+					chat_socket_printf(buddy, "%s", arg2);
+				}
+			}
+		}
+		else
+		{
+			chat_printf("You are not connected to anyone named '%s'.", arg1);
+		}
+	}
+}
+
+DO_CHAT(chat_serve)
+{
+	struct chat_data *buddy;
+
+	if ((buddy = find_buddy(arg1)) == NULL)
+	{
+		chat_printf("You are not connected to anyone named '%s'.", arg1);
+
+		return;
+	}
+
+	TOG_BIT(buddy->flags, CHAT_FLAG_SERVE);
+
+	if (HAS_BIT(buddy->flags, CHAT_FLAG_SERVE))
+	{
+		chat_printf("You are now chat serving %s.", buddy->name);
+		chat_socket_printf(buddy, "%c\n%s is now chat serving you.\n%c", CHAT_MESSAGE, gtd->chat->name, CHAT_END_OF_COMMAND); 
+	}
+	else
+	{
+		chat_printf("You are no longer chat serving %s.", buddy->name);
+		chat_socket_printf(buddy, "%c\n%s is no longer chat serving you.\n%c", CHAT_MESSAGE, gtd->chat->name, CHAT_END_OF_COMMAND); 
+	}
+}
+
+
+DO_CHAT(chat_who)
+{
+	struct chat_data *buddy;
+	int cnt = 1;
+
+	tintin_printf(NULL, "     %-15s  %-5s  %-20s  %-5s  %-15s", "Name", "Flags", "Address", "Port", "Client");
+	tintin_printf(NULL, "     ===============  =====  ====================  =====  ==================== ");
+
+	for (buddy = gtd->chat->next ; buddy ; buddy = buddy->next)
+	{
+		tintin_printf(NULL, " %03d %-15s  %s%s%s%s%s  %-20s  %-5u  %-20s",
+			cnt++,
+			buddy->name,
+			HAS_BIT(buddy->flags, CHAT_FLAG_PRIVATE)   ? "P" : " ",
+			HAS_BIT(buddy->flags, CHAT_FLAG_IGNORE)    ? "I" : " ",
+			HAS_BIT(buddy->flags, CHAT_FLAG_SERVE)     ? "S" : " ",
+			HAS_BIT(buddy->flags, CHAT_FLAG_FORWARD)   ? "F" :
+			HAS_BIT(buddy->flags, CHAT_FLAG_FORWARDBY) ? "f" : " ",
+			" ",
+			buddy->ip,
+			buddy->port,
+			buddy->version);
+	}
+	tintin_printf(NULL, "     ===============  =====  ====================  =====  ==================== ");
+}
+
+
+DO_CHAT(chat_zap)
+{
+	struct chat_data *buddy;
+
+	if (!strcasecmp(arg1, "ALL"))
+	{
+		while (gtd->chat->next)
+		{
+			close_chat(gtd->chat->next, TRUE);
+		}
+	}
+	else
+	{
+		if ((buddy = find_buddy(arg1)))
+		{
+			close_chat(buddy, TRUE);
+		}
+		else
+		{
+			chat_printf("You are not connected to anyone named '%s'.", arg1);
+		}
+	}
+}
+
+
+// file transfer
+
+DO_CHAT(chat_accept)
+{
+	int cnt;
+
+	struct chat_data *buddy;
+	char path[BUFFER_SIZE];
+
+	cnt = 1;
+
+	if (*arg2)
+	{
+		cnt = get_number(gtd->ses, arg2);
+
+		if (cnt < 1 || cnt > 1000)
+		{
+			chat_printf("ERROR: File transfer boost must be between 1 and 1000.");
+
+			return;
+		}
+	}
+
+	if ((buddy = find_buddy(arg1)) == NULL)
+	{
+		chat_printf("You are not connected to anyone named '%s'.", arg1);
+
+		return;
+	}
+
+	if (buddy->file_name == NULL)
+	{
+		chat_printf("ERROR: You don't have a file transfer in progress with %s.", buddy->name);
+
+		return;
+	}
+
+	if (buddy->file_start_time)
+	{
+		chat_printf("ERROR: You already have a file transfer in progress with %s.", buddy->name);
+
+		return;
+	}
+
+	if (*arg2)
+	{
+		cnt = get_number(gtd->ses, arg2);
+
+		if (cnt <= 0 || cnt >= 1000)
+		{
+			chat_printf("ERROR: File transfer boost (%s) must be between 1 and 1000.", arg2);
+
+			return;
+		}
+
+		if (cnt > buddy->file_size / (BLOCK_SIZE * 10))
+		{
+			cnt = UMAX(1, buddy->file_size / (BLOCK_SIZE * 10));
+		}
+	}
+
+	sprintf(path, "%s%s", gtd->chat->download, buddy->file_name);
+
+	if ((buddy->file_pt = fopen(path, "w")) == NULL)
+	{
+		deny_file(buddy, "\nCould not create that file on receiver's end.\n");
+
+		chat_printf("ERROR: Could not create the file '%s' on your end.", buddy->file_name);
+
+		file_cleanup(buddy);
+
+		return;
+	}
+
+	buddy->file_start_time = utime();
+
+	chat_printf("Started file transfer from %s, file: %s, size: %lld, boost: %d", buddy->name, buddy->file_name, buddy->file_size, cnt);
+
+	while (cnt--)
+	{
+		chat_socket_printf(buddy, "%c%c", CHAT_FILE_BLOCK_REQUEST, CHAT_END_OF_COMMAND);
+	}
+}
+
+
+DO_CHAT(chat_decline)
+{
+	struct chat_data *buddy;
+
+	if ((buddy = find_buddy(arg1)) == NULL)
+	{
+		chat_printf("You are not connected to anyone named '%s'.", arg1);
+
+		return;
+	}
+
+	if (buddy->file_pt == NULL)
+	{
+		chat_printf("You don't have a file transfer in progress with %s.", buddy->name);
+
+		return;
+	}
+
+	if (buddy->file_start_time)
+	{
+		chat_printf("You already have a file transfer in progress with %s.", buddy->name);
+
+		return;
+	}
+
+	deny_file(buddy, "\nYour file transfer was rejected.\n");
+}
+
+DO_CHAT(chat_sendfile)
+{
+	struct chat_data *buddy;
+
+	if (*arg1 == 0 || *arg2 == 0)
+	{
+		chat_printf("USAGE: #sendfile <person> <filename>");
+
+		return;
+	}
+
+	if ((buddy = find_buddy(arg1)) == NULL)
+	{
+		chat_printf("You are not connected to anyone named '%s'.", arg1);
+
+		return;
+	}
+
+	if (buddy->file_pt)
+	{
+		chat_printf("ERROR: You already have a file transfer in progress with that person.");
+
+		return;
+	}
+
+	buddy->file_block_cnt = 0;
+	buddy->file_block_tot = 0;
+
+	buddy->file_name = strdup(fix_file_name(arg2));
+
+	if ((buddy->file_pt = fopen(arg2, "r")) == NULL)
+	{
+		chat_printf("ERROR: No such file.");
+
+		file_cleanup(buddy);
+		return;
+	}
+
+	if ((buddy->file_size = get_file_size(arg2)) == 0)
+	{
+		chat_printf("Cannot send an empty file.");
+
+		file_cleanup(buddy);
+
+		return;
+	}
+
+	buddy->file_block_tot = buddy->file_size / BLOCK_SIZE + (buddy->file_size % BLOCK_SIZE ? 1 : 0);
+
+	if (*buddy->file_name == 0)
+	{
+		chat_printf("Must be a file, directories not accepted.");
+
+		file_cleanup(buddy);
+
+		return;
+	}
+
+	buddy->file_start_time = utime();
+
+	chat_socket_printf(buddy, "%c%s,%lld%c", CHAT_FILE_START, buddy->file_name, buddy->file_size, CHAT_END_OF_COMMAND);
+
+	chat_printf("Sending file to: %s, File: %s, Size: %lld", buddy->name, buddy->file_name, buddy->file_size);
+
+	return;
+}
+
+void chat_receive_file(char *arg, struct chat_data *buddy)
+{
+	char path[BUFFER_SIZE], *comma;
+
+	push_call("chat_receive_file(%p,%p)",arg,buddy);
+
+	if (buddy->file_pt)
+	{
+		deny_file(buddy, "\nThere is a transfer already in progress.\n");
+
+		pop_call();
+		return;
+	}
+	buddy->file_block_cnt = 0;
+	buddy->file_block_tot = 0;
+	buddy->file_size      = 0;
+
+	if ((comma = strchr(arg, ',')) == NULL)
+	{
+		deny_file(buddy, "\nFile protocol error. (no file size was transmitted)\n");
+
+		pop_call();
+		return;	
+	}
+	*comma = 0;
+
+	buddy->file_name = strdup(arg);
+	buddy->file_size = atoll(&comma[1]);
+
+	if (strcmp(fix_file_name(buddy->file_name), buddy->file_name))
+	{
+		deny_file(buddy, "\nFilename sent with directory info. (rejected)\n");
+
+		file_cleanup(buddy);
+
+		pop_call();
+		return;
+	}
+
+	if (buddy->file_size == 0)
+	{
+		deny_file(buddy, "\nFile protocol error. (no file size was transmitted)\n");
+
+		file_cleanup(buddy);
+
+		pop_call();
+		return;
+	}
+
+	buddy->file_block_tot = buddy->file_size / BLOCK_SIZE + (buddy->file_size % BLOCK_SIZE ? 1 : 0);
+
+
+	sprintf(path, "%s%s", gtd->chat->download, buddy->file_name);
+
+	chat_printf("File transfer from %s, file: %s, size: %d.", buddy->name, buddy->file_name, buddy->file_size);
+	chat_printf("Use %cchat <accept|decline> %s [boost] to proceed.", gtd->tintin_char, buddy->name);
+
+	if ((buddy->file_pt = fopen(path, "r")) != NULL)
+	{
+		chat_printf("Warning, the file already exists on your end.");
+
+		fclose(buddy->file_pt);
+
+		buddy->file_pt = NULL;
+	}
+
+	buddy->file_start_time = 0;
+
+	pop_call();
+	return;
+}
+
+void send_block(struct chat_data *buddy)
+{
+	unsigned char block[BUFFER_SIZE], *pto;
+	int i, c;
+
+	if (buddy->file_pt == NULL)
+	{
+		return;
+	}
+
+	if (buddy->file_block_cnt == 0)
+	{
+		buddy->file_start_time = utime();
+
+		chat_printf("%s started a file transfer, file: %s, size: %lld", buddy->name, buddy->file_name, buddy->file_size);
+	}
+
+	pto = block;
+
+	*pto++ = CHAT_FILE_BLOCK;
+
+	for (i = 0; i < BLOCK_SIZE; i++)
+	{
+		c = fgetc(buddy->file_pt);
+
+		if (c == EOF)
+		{
+			break;
+		}
+		*pto++ = (unsigned char) c;
+	}
+	write(buddy->fd, block, 501);
+
+	buddy->file_block_cnt++;
+
+	if (i < BLOCK_SIZE)
+	{
+		chat_printf("File transfer: %s, to %s completed at %lld.%lld KB/s.",
+			buddy->file_name,
+			buddy->name,
+			1000LL * buddy->file_size / (utime() - buddy->file_start_time),
+			10000LL * buddy->file_size / (utime() - buddy->file_start_time) % 10);
+
+		chat_socket_printf(buddy, "%c%c", CHAT_FILE_END, CHAT_END_OF_COMMAND);
+
+		file_cleanup(buddy);
+	}
+}
+
+// Last block is BLOCK_SIZE bytes as well, but don't write filler bytes.
+
+int receive_block(unsigned char *str, struct chat_data *buddy, int len)
+{
+	static unsigned char file_block_buf[1000];
+	int size;
+
+	if (buddy->file_pt == NULL)
+	{
+		return UMIN(len, BLOCK_SIZE);
+	}
+
+	if (gtd->chat->file_block_patch == 0 && len < BLOCK_SIZE)
+	{
+		chat_printf("receive_block %04d: packet length %03d: fragmentation detected.", buddy->file_block_cnt, len);
+
+		gtd->chat->file_block_patch = len;
+
+		memcpy(file_block_buf, str, len);
+
+		return len;
+	}
+
+	buddy->file_block_cnt++;
+
+	// Should be safe to assume there won't be more than 1 fragmentation per packet.
+
+	if (gtd->chat->file_block_patch)
+	{
+		len = BLOCK_SIZE - gtd->chat->file_block_patch;
+
+		memcpy(file_block_buf + gtd->chat->file_block_patch, str, len);
+
+		str = file_block_buf;
+
+		gtd->chat->file_block_patch = 0;
+
+		size = BLOCK_SIZE;
+	}
+	else
+	{
+		len = size = BLOCK_SIZE;
+	}
+
+	if (buddy->file_block_cnt == buddy->file_block_tot)
+	{
+		size = buddy->file_size % BLOCK_SIZE;
+
+		fwrite(str, 1, size, buddy->file_pt);
+
+		chat_printf("Transfer of %s completed, size: %lld, speed: %lld.%lld KB/s.",
+			buddy->file_name,
+			buddy->file_size,
+			1000LL * buddy->file_size / (utime() - buddy->file_start_time),
+			10000LL * buddy->file_size / (utime() - buddy->file_start_time) % 10);
+
+		file_cleanup(buddy);
+	}
+	else
+	{
+		fwrite(str, 1, size, buddy->file_pt);
+
+		chat_socket_printf(buddy, "%c%c", CHAT_FILE_BLOCK_REQUEST, CHAT_END_OF_COMMAND);
+	}
+	return len;
+}
+
+void deny_file(struct chat_data *ch, char *arg)
+{
+	chat_socket_printf(ch, "%c%s%c", CHAT_FILE_DENY, arg, CHAT_END_OF_COMMAND);
+}
+
+
+void file_denied(struct chat_data *buddy, char *txt)
+{
+	chat_printf("%s", txt);
+
+	file_cleanup(buddy);
+}
+
+void file_cleanup(struct chat_data *buddy)
+{
+	if (buddy->file_pt)
+	{
+		fclose(buddy->file_pt);
+
+		buddy->file_pt = NULL;
+	}
+	if (buddy->file_name)
+	{
+		FREE(buddy->file_name);
+	}
+}
+
+DO_CHAT(chat_cancelfile)
+{
+	struct chat_data *buddy;
+
+	if ((buddy = find_buddy(arg1)) == NULL)
+	{
+		chat_printf("You are not connected to anyone named '%s'.", arg1);
+
+		return;
+	}
+
+	if (buddy->file_pt == NULL)
+	{
+		return;
+	}
+
+	fclose(buddy->file_pt);
+
+	buddy->file_pt = NULL;
+
+	chat_printf("Okay, file transfer canceled");
+
+	chat_socket_printf(buddy, "%c%c", CHAT_FILE_CANCEL, CHAT_END_OF_COMMAND);
+}
+
+DO_CHAT(chat_color)
+{
+	if (*arg1 == 0 || get_color_names(gtd->ses, arg1, arg2) == FALSE)
+	{
+		chat_printf("Valid colors are:\n\nreset, bold, dim, light, dark, underscore, blink, reverse, black, red, green, yellow, blue, magenta, cyan, white, b black, b red, b green, b yellow, b blue, b magenta, b cyan, b white");
+
+		return;
+	}
+	RESTRING(gtd->chat->color, arg2);
+
+	chat_printf("Color has been set to %s", arg1);
+}
+
+DO_CHAT(chat_dnd)
+{
+	TOG_BIT(gtd->chat->flags, CHAT_FLAG_DND);
+
+	if (HAS_BIT(gtd->chat->flags, CHAT_FLAG_DND))
+	{
+		chat_printf("New connections are no longer accepted.");
+	}
+	else
+	{
+		chat_printf("New connections are accepted.");
+	}
+}
+
+DO_CHAT(chat_filestat)
+{
+	struct chat_data *buddy;
+
+	if ((buddy = find_buddy(arg1)) == NULL)
+	{
+		chat_printf("You are not connected to anyone named '%s'.", arg1);
+
+		return;
+	}
+
+	if (buddy->file_pt == NULL)
+	{
+		chat_printf("You have no file transfer in progress with %s.", buddy->name);
+
+		return;
+	}
+
+	tintin_printf(NULL, "  Contact: %s",   buddy->name);
+	tintin_printf(NULL, " Filename: %s",   buddy->file_name);
+	tintin_printf(NULL, " Filesize: %lld", buddy->file_size);
+	tintin_printf(NULL, " Received: %d",   buddy->file_block_cnt * BLOCK_SIZE);
+	tintin_printf(NULL, "    Speed: %lld KB/s", (1000 * buddy->file_block_cnt * BLOCK_SIZE) / (utime() - buddy->file_start_time));
+}
+
+
+DO_CHAT(chat_group)
+{
+	struct chat_data *buddy;
+	int cnt = 0;
+
+	if (*arg1 == 0)
+	{
+		tintin_printf(NULL, "     %-15s  %-20s  %-5s  %-15s", "Name", "Address", "Port", "Group");
+		tintin_printf(NULL, "     ===============  ====================  =====  ==================== ");
+
+		for (buddy = gtd->chat->next ; buddy ; buddy = buddy->next)
+		{
+			tintin_printf(NULL, " %03d %-15s  %-20s  %-5u  %-20s",
+				cnt++,
+				buddy->name,
+				buddy->ip,
+				buddy->port,
+				buddy->group);
+		}
+		tintin_printf(NULL, "     ===============  ====================  =====  ==================== ");
+	}
+	else if (!strcasecmp(arg1, "ALL"))
+	{
+		chat_printf("You set everyone's group to '%s'", arg2);
+
+		for (buddy = gtd->chat->next ; buddy ; buddy = buddy->next)
+		{
+			RESTRING(buddy->group, arg2);
+		}
+	}
+	else
+	{
+		if ((buddy = find_buddy(arg1)) != NULL)
+		{
+			RESTRING(buddy->group, arg2);
+
+			chat_printf("You set %s's group to '%s'", buddy->name, arg2);
+		}
+		else
+		{
+			chat_printf("You are not connected to anyone named '%s'.", arg1);
+		}
+	}
+}
+
+
+DO_CHAT(chat_forward)
+{
+	struct chat_data *buddy;
+
+	if ((buddy = find_buddy(arg1)) == NULL)
+	{
+		chat_printf("You are not connected to anyone named '%s'.", arg1);
+
+		return;
+	}
+
+	TOG_BIT(buddy->flags, CHAT_FLAG_FORWARD);
+
+	if (HAS_BIT(buddy->flags, CHAT_FLAG_FORWARD))
+	{
+		chat_socket_printf(buddy, "%c\n%s is now forwarding to you.\n%c", CHAT_MESSAGE, gtd->chat->name, CHAT_END_OF_COMMAND);
+
+		chat_printf("You are now forwarding to %s.", buddy->name);
+	}
+	else
+	{
+		chat_socket_printf(buddy, "%c\n%s is no longer forwarding to you.\n%c", CHAT_MESSAGE, gtd->chat->name, CHAT_END_OF_COMMAND);
+
+		chat_printf("You are no longer forwarding to %s.", buddy->name);
+	}
+}
+
+DO_CHAT(chat_forwardall)
+{
+	struct chat_data *buddy;
+
+	if ((buddy = find_buddy(arg1)) == NULL)
+	{
+		chat_printf("You are not connected to anyone named '%s'.", arg1);
+
+		return;
+	}
+
+	TOG_BIT(buddy->flags, CHAT_FLAG_FORWARDALL);
+
+	if (HAS_BIT(buddy->flags, CHAT_FLAG_FORWARDALL))
+	{
+		chat_socket_printf(buddy, "%c\n%s is now forwarding session output to you.\n%c", CHAT_MESSAGE, gtd->chat->name, CHAT_END_OF_COMMAND);
+
+		chat_printf("You are now forwarding session output to %s.", buddy->name);
+	}
+	else
+	{
+		chat_socket_printf(buddy, "%c\n%s is no longer forwarding session output to you.\n%c", CHAT_MESSAGE, gtd->chat->name, CHAT_END_OF_COMMAND);
+
+		chat_printf("You are no longer forwarding session output to %s.", buddy->name);
+	}
+}
+
+void chat_forward_session(struct session *ses, char *linelog)
+{
+	char tmp[BUFFER_SIZE];
+	struct chat_data *buddy;
+
+	if (ses != gtd->ses)
+	{
+		return;
+	}
+
+	sprintf(tmp, "%c%s%c", CHAT_SNOOP_DATA, linelog, CHAT_END_OF_COMMAND);
+
+	for (buddy = gtd->chat->next ; buddy ; buddy = buddy->next)
+	{
+		if (HAS_BIT(buddy->flags, CHAT_FLAG_FORWARDALL))
+		{
+			chat_socket_printf(buddy, "%s", tmp);
+		}
+	}
+}
+
+DO_CHAT(chat_ignore)
+{
+	struct chat_data *buddy;
+
+	if ((buddy = find_buddy(arg1)) == NULL)
+	{
+		chat_printf("You are not connected to anyone named '%s'.", arg1);
+
+		return;
+	}
+
+	TOG_BIT(buddy->flags, CHAT_FLAG_IGNORE);
+
+	if (HAS_BIT(buddy->flags, CHAT_FLAG_IGNORE))
+	{
+		// chat_socket_printf(buddy, "%c\n%s is now ignoring you.\n%c", CHAT_MESSAGE, gtd->chat->name, CHAT_END_OF_COMMAND);
+
+		chat_printf("You are now ignoring %s.", buddy->name);
+	}
+	else
+	{
+		// chat_socket_printf(buddy, "%c\n%s is no longer ignoring you.\n%c", CHAT_MESSAGE, gtd->chat->name, CHAT_END_OF_COMMAND);
+
+		chat_printf("You are no longer ignoring %s.", buddy->name);
+	}
+}
+
+
+DO_CHAT(chat_private)
+{
+	struct chat_data *buddy;
+
+	if (!strcasecmp(arg1, "ALL"))
+	{
+		for (buddy = gtd->chat->next ; buddy ; buddy = buddy->next)
+		{
+			if (!HAS_BIT(buddy->flags, CHAT_FLAG_PRIVATE))
+			{
+				chat_socket_printf(buddy, "%c\n%s marked your connection private.\n%c", CHAT_MESSAGE, gtd->chat->name, CHAT_END_OF_COMMAND);
+
+				chat_printf("Your connection with %s is now private.", buddy->name);				
+
+				SET_BIT(buddy->flags, CHAT_FLAG_PRIVATE);
+			}
+		}
+	}
+	else
+	{
+		if ((buddy = find_buddy(arg1)) != NULL)
+		{
+			if (!HAS_BIT(buddy->flags, CHAT_FLAG_PRIVATE))
+			{
+				chat_socket_printf(buddy, "%c\n%s marked your connection private.\n%c", CHAT_MESSAGE, gtd->chat->name, CHAT_END_OF_COMMAND);
+
+				chat_printf("Your connection with %s is now private.", buddy->name);
+
+				SET_BIT(buddy->flags, CHAT_FLAG_PRIVATE);
+			}
+			else
+			{
+				chat_printf("Your connection with %s is already private.", buddy->name);
+			}
+		}
+	}
+}
+
+DO_CHAT(chat_public)
+{
+	struct chat_data *buddy;
+
+	if (!strcasecmp(arg1, "ALL"))
+	{
+		for (buddy = gtd->chat->next ; buddy ; buddy = buddy->next)
+		{
+			if (HAS_BIT(buddy->flags, CHAT_FLAG_PRIVATE))
+			{
+				chat_socket_printf(buddy, "%c\n%s marked your connection public.\n%c", CHAT_MESSAGE, gtd->chat->name, CHAT_END_OF_COMMAND);
+
+				chat_printf("Your connection with %s is now public.", buddy->name);				
+
+				DEL_BIT(buddy->flags, CHAT_FLAG_PRIVATE);
+			}
+		}
+	}
+	else
+	{
+		if ((buddy = find_buddy(arg1)) != NULL)
+		{
+			if (HAS_BIT(buddy->flags, CHAT_FLAG_PRIVATE))
+			{
+				chat_socket_printf(buddy, "%c\n%s marked your connection public.\n%c", CHAT_MESSAGE, gtd->chat->name, CHAT_END_OF_COMMAND);
+
+				chat_printf("Your connection with %s is now public.", buddy->name);
+
+				DEL_BIT(buddy->flags, CHAT_FLAG_PRIVATE);
+			}
+			else
+			{
+				chat_printf("Your connection with %s is already public.", buddy->name);
+			}
+		}
+	}
+}
+
+int get_file_size(char *fpath)
+{
+	struct stat statbuf;
+
+	if (stat(fpath, &statbuf) == -1)
+	{
+		return 0;
+	}
+	return statbuf.st_size;
+}
+
+
+struct chat_data *find_buddy(char *arg)
+{
+	struct chat_data *buddy;
+	int cnt = 1;
+
+	if (*arg == 0)
+	{
+		return NULL;
+	}
+
+	if (is_number(arg))
+	{
+		for (buddy = gtd->chat->next ; buddy ; buddy = buddy->next)
+		{
+			if (atoi(arg) == cnt++)
+			{
+				return buddy;
+			}
+		}
+	}
+
+	for (buddy = gtd->chat->next ; buddy ; buddy = buddy->next)
+	{
+		if (!strcmp(arg, buddy->ip))
+		{
+			return buddy;
+		}
+	}
+
+	for (buddy = gtd->chat->next ; buddy ; buddy = buddy->next)
+	{
+		if (is_abbrev(arg, buddy->name))
+		{
+			return buddy;
+		}
+	}
+
+	return NULL;
+}
+
+
+struct chat_data *find_group(char *arg)
+{
+	struct chat_data *buddy;
+
+	if (*arg == 0)
+	{
+		return NULL;
+	}
+
+	for (buddy = gtd->chat->next ; buddy ; buddy = buddy->next)
+	{
+		if (!strcmp(arg, buddy->group))
+		{
+			return buddy;
+		}
+	}
+	return NULL;
+}
+
+
+char *fix_file_name(char *name)
+{
+	int len;
+
+	for (len = strlen(name) ; len > 0 ; len--)
+	{
+		switch (name[len])
+		{
+			case '/':
+			case '\\':
+			case ':':
+				return &name[len + 1];
+		}
+	}
+	return name;
+}

+ 381 - 0
class.c

@@ -0,0 +1,381 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2004                       *
+******************************************************************************/
+
+
+#include "tintin.h"
+
+
+DO_COMMAND(do_class)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], arg3[BUFFER_SIZE];
+	int i;
+	struct listroot *root;
+	struct listnode *node;
+
+	root = ses->list[LIST_CLASS];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg3, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		tintin_header(ses, " CLASSES ");
+
+		for (root->update = 0 ; root->update < root->used ; root->update++)
+		{
+			node = root->list[root->update];
+
+			tintin_printf2(ses, "%-20s  %4d  %6s  %6s  %3d", node->arg1, count_class(ses, node), !strcmp(ses->group, node->arg1) ? "ACTIVE" : "", atoi(node->arg3) ? "OPEN" : "CLOSED", atoi(node->arg3));
+		}
+	}
+	else if (*arg2 == 0)
+	{
+		class_list(ses, NULL, arg1, arg2);
+	}
+	else
+	{
+		for (i = 0 ; *class_table[i].name ; i++)
+		{
+			if (is_abbrev(arg2, class_table[i].name))
+			{
+				break;
+			}
+		}
+
+		if (*class_table[i].name == 0)
+		{
+			show_error(ses, LIST_COMMAND, "#SYNTAX: CLASS {name} {OPEN|CLOSE|READ|SIZE|WRITE|KILL}.", arg1, capitalize(arg2));
+		}
+		else
+		{
+			node = search_node_list(ses->list[LIST_CLASS], arg1);
+
+			if (node == NULL)
+			{
+				check_all_events(ses, SUB_ARG, 0, 1, "CLASS CREATED", arg1);
+
+				node = update_node_list(ses->list[LIST_CLASS], arg1, "", arg3, "");
+			}
+			class_table[i].fun(ses, node, arg1, arg3);
+		}
+	}
+	return ses;
+}
+
+
+int count_class(struct session *ses, struct listnode *group)
+{
+	int list, cnt, index;
+
+	for (cnt = list = 0 ; list < LIST_MAX ; list++)
+	{
+		if (!HAS_BIT(ses->list[list]->flags, LIST_FLAG_CLASS))
+		{
+			continue;
+		}
+
+		for (index = 0 ; index < ses->list[list]->used ; index++)
+		{
+			if (!strcmp(ses->list[list]->list[index]->group, group->arg1))
+			{
+				cnt++;
+			}
+		}
+	}
+	return cnt;
+}
+
+DO_CLASS(class_clear)
+{
+	int type, index;
+
+	for (type = 0 ; type < LIST_MAX ; type++)
+	{
+		if (!HAS_BIT(ses->list[type]->flags, LIST_FLAG_CLASS))
+		{
+			continue;
+		}
+
+		for (index = 0 ; index < ses->list[type]->used ; index++)
+		{
+			if (!strcmp(ses->list[type]->list[index]->group, arg1))
+			{
+				delete_index_list(ses->list[type], index--);
+			}
+		}
+	}
+
+	show_message(ses, LIST_CLASS, "#CLASS {%s} HAS BEEN CLEARED.", arg1);
+
+	if (!strcmp(ses->group, arg1))
+	{
+		class_close(ses, node, arg1, arg2);
+	}
+
+	return ses;
+}
+
+DO_CLASS(class_close)
+{
+	if (atoi(node->arg3) == 0)
+	{
+		show_message(ses, LIST_CLASS, "#CLASS {%s} IS ALREADY CLOSED.", arg1);
+	}
+	else
+	{
+		show_message(ses, LIST_CLASS, "#CLASS {%s} HAS BEEN CLOSED.", arg1);
+
+		update_node_list(ses->list[LIST_CLASS], arg1, "", "0","");
+
+		if (!strcmp(ses->group, arg1))
+		{
+			check_all_events(ses, SUB_ARG, 0, 1, "CLASS DEACTIVATED", ses->group);
+			check_all_events(ses, SUB_ARG, 1, 1, "CLASS DEACTIVATED %s", ses->group, ses->group);
+
+			node = ses->list[LIST_CLASS]->list[0];
+
+			if (atoi(node->arg3))
+			{
+				RESTRING(ses->group, node->arg1);
+
+				show_message(ses, LIST_CLASS, "#CLASS {%s} HAS BEEN ACTIVATED.", node->arg1);
+
+				check_all_events(ses, SUB_ARG, 0, 1, "CLASS ACTIVATED", node->arg1);
+				check_all_events(ses, SUB_ARG, 1, 1, "CLASS ACTIVATED %s", arg1, arg1);
+			}
+			else
+			{
+				RESTRING(ses->group, "");
+			}
+		}
+	}
+	return ses;
+}
+
+
+DO_CLASS(class_list)
+{
+	int i, j;
+
+	if (search_node_list(ses->list[LIST_CLASS], arg1))
+	{
+		tintin_header(ses, " %s ", arg1);
+
+		for (i = 0 ; i < LIST_MAX ; i++)
+		{
+			if (!HAS_BIT(ses->list[i]->flags, LIST_FLAG_CLASS))
+			{
+				continue;
+			}
+
+			if (*arg2 && !is_abbrev(arg2, list_table[i].name) && !is_abbrev(arg2, list_table[i].name_multi))
+			{
+				continue;
+			}
+
+			for (j = 0 ; j < ses->list[i]->used ; j++)
+			{
+				if (!strcmp(ses->list[i]->list[j]->group, arg1))
+				{
+					show_node(ses->list[i], ses->list[i]->list[j], 0);
+				}
+			}
+		}
+	}
+	else
+	{
+		show_error(ses, LIST_CLASS, "#CLASS {%s} DOES NOT EXIST.", arg1);
+	}
+	return ses;
+}
+
+
+DO_CLASS(class_kill)
+{
+	int group;
+
+	class_clear(ses, node, arg1, arg2);
+
+	group = search_index_list(ses->list[LIST_CLASS], arg1, NULL);
+
+	delete_index_list(ses->list[LIST_CLASS], group);
+
+	check_all_events(ses, SUB_ARG, 0, 1, "CLASS DESTROYED", arg1);
+	check_all_events(ses, SUB_ARG, 1, 1, "CLASS DESTROYED %s", arg1, arg1);
+
+	show_message(ses, LIST_CLASS, "#CLASS {%s} HAS BEEN KILLED.", arg1);
+
+	return ses;
+}
+
+DO_CLASS(class_load)
+{
+	FILE *file;
+
+	if (node->data == NULL)
+	{
+		show_error(ses, LIST_CLASS, "#CLASS {%s} DOES NOT HAVE ANY DATA SAVED.", arg1);
+
+		return ses;
+	}
+	file = fmemopen(node->data, node->val32[1], "r");
+
+	read_file(ses, file, arg1);
+
+	return ses;
+}
+
+DO_CLASS(class_open)
+{
+	int count;
+
+	if (!strcmp(ses->group, arg1))
+	{
+		show_message(ses, LIST_CLASS, "#CLASS {%s} IS ALREADY OPENED AND ACTIVATED.", arg1);
+	}
+	else
+	{
+		if (*ses->group)
+		{
+			check_all_events(ses, SUB_ARG, 0, 1, "CLASS DEACTIVATED", ses->group);
+			check_all_events(ses, SUB_ARG, 1, 1, "CLASS DEACTIVATED %s", ses->group, ses->group);
+		}
+		RESTRING(ses->group, arg1);
+
+		count = atoi(ses->list[LIST_CLASS]->list[0]->arg3);
+
+		update_node_list(ses->list[LIST_CLASS], arg1, "", ntos(--count), "");
+
+		show_message(ses, LIST_CLASS, "#CLASS {%s} HAS BEEN OPENED AND ACTIVATED.", arg1);
+
+		check_all_events(ses, SUB_ARG, 0, 1, "CLASS ACTIVATED", arg1);
+		check_all_events(ses, SUB_ARG, 1, 1, "CLASS ACTIVATED %s", arg1, arg1);
+	}
+
+	return ses;
+}
+
+
+DO_CLASS(class_read)
+{
+	class_open(ses, node, arg1, arg2);
+
+	do_read(ses, arg2);
+
+	class_close(ses, node, arg1, arg2);
+
+	return ses;
+}
+
+DO_CLASS(class_save)
+{
+	FILE *file;
+	int list, index;
+
+	str_cpy(&node->arg4, "");
+
+	file = open_memstream(&node->data, (size_t *) &node->val32[1]);
+
+	fprintf(file, "%cCLASS {%s} OPEN\n\n", gtd->tintin_char, arg1);
+
+	for (list = 0 ; list < LIST_MAX ; list++)
+	{
+		if (!HAS_BIT(ses->list[list]->flags, LIST_FLAG_CLASS))
+		{
+			continue;
+		}
+
+		for (index = 0 ; index < ses->list[list]->used ; index++)
+		{
+			if (!strcmp(ses->list[list]->list[index]->group, arg1))
+			{
+				write_node(ses, list, ses->list[list]->list[index], file);
+			}
+		}
+	}
+
+	fprintf(file, "\n%cCLASS {%s} CLOSE\n", gtd->tintin_char, arg1);
+
+	fclose(file);
+
+	show_message(ses, LIST_CLASS, "#CLASS {%s} HAS BEEN SAVED TO MEMORY (%d BYTES) (%s).", arg1, node->val32[1], node->data);
+
+	return ses;
+}	
+
+DO_CLASS(class_size)
+{
+	if (*arg1 == 0 || *arg2 == 0)
+	{
+		show_error(ses, LIST_CLASS, "#SYNTAX: #CLASS {<class name>} SIZE {<variable>}.");
+		
+		return ses;
+	}
+
+	set_nest_node_ses(ses, arg2, "%d", count_class(ses, node));
+
+	return ses;
+}
+
+
+DO_CLASS(class_write)
+{
+	FILE *file;
+	int list, index;
+
+	if (*arg2 == 0 || (file = fopen(arg2, "w")) == NULL)
+	{
+		show_error(ses, LIST_CLASS, "#ERROR: #CLASS WRITE {%s} - COULDN'T OPEN FILE TO WRITE.", arg2);
+		
+		return ses;
+	}
+
+	fprintf(file, "%cCLASS {%s} OPEN\n\n", gtd->tintin_char, arg1);
+
+	for (list = 0 ; list < LIST_MAX ; list++)
+	{
+		if (!HAS_BIT(ses->list[list]->flags, LIST_FLAG_CLASS))
+		{
+			continue;
+		}
+
+		for (index = 0 ; index < ses->list[list]->used ; index++)
+		{
+			if (!strcmp(ses->list[list]->list[index]->group, arg1))
+			{
+				write_node(ses, list, ses->list[list]->list[index], file);
+			}
+		}
+	}
+
+	fprintf(file, "\n%cCLASS {%s} CLOSE\n", gtd->tintin_char, arg1);
+
+	fclose(file);
+
+	show_message(ses, LIST_CLASS, "#CLASS {%s} HAS BEEN WRITTEN TO FILE.", arg1);
+
+	return ses;
+}

+ 1061 - 0
config.c

@@ -0,0 +1,1061 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2004                       *
+******************************************************************************/
+
+
+#include "tintin.h"
+
+
+DO_COMMAND(do_configure)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+	struct listnode *node;
+	int index;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		tintin_header(ses, " CONFIGURATIONS ");
+
+		for (index = 0 ; *config_table[index].name != 0 ; index++)
+		{
+			node = search_node_list(ses->list[LIST_CONFIG], config_table[index].name);
+
+			if (node)
+			{
+				strcpy(arg2, "");
+
+				config_table[index].config(ses, arg1, arg2, index);
+
+				tintin_printf2(ses, "[%-14s] [%12s] %s",
+					node->arg1,
+					arg2,
+					strcmp(node->arg2, "ON") == 0 ? config_table[index].msg_on : config_table[index].msg_off);
+			}
+		}
+
+		tintin_header(ses, "");
+	}
+	else
+	{
+		for (index = 0 ; *config_table[index].name != 0 ; index++)
+		{
+			if (is_abbrev(arg1, config_table[index].name))
+			{
+				if (*arg2)
+				{
+					if (config_table[index].config(ses, arg1, arg2, index) != NULL)
+					{
+						update_node_list(ses->list[LIST_CONFIG], config_table[index].name, arg2, "", "");
+
+						node = search_node_list(ses->list[LIST_CONFIG], config_table[index].name);
+
+						if (node)
+						{
+							show_message(ses, LIST_CONFIG, "#CONFIG {%s} HAS BEEN SET TO {%s}.", config_table[index].name, node->arg2);
+						}
+					}
+				}
+				else
+				{
+					config_table[index].config(ses, arg1, arg2, index);
+
+					show_message(ses, LIST_CONFIG, "#CONFIG {%s} IS SET TO {%s}.", config_table[index].name, arg2);
+				}
+				return ses;
+			}
+		}
+		show_error(ses, LIST_CONFIG, "#ERROR: #CONFIG {%s} IS NOT A VALID OPTION.", capitalize(arg1));
+	}
+	return ses;
+}
+
+DO_CONFIG(config_autotab)
+{
+	if (*arg2)
+	{
+		if (!is_number(arg2))
+		{
+			show_error(ses, LIST_CONFIG, "#SYNTAX: #CONFIG {AUTO TAB} <NUMBER>");
+
+			return NULL;
+		}
+
+		if (atoi(arg2) < 1 || atoi(arg2) > 999999)
+		{
+			show_error(ses, LIST_CONFIG, "#ERROR: #CONFIG BUFFER: PROVIDE A NUMBER BETWEEN 1 and 999999");
+
+			return NULL;
+		}
+
+		ses->auto_tab = atoi(arg2);
+	}
+	sprintf(arg2, "%d", ses->auto_tab);
+
+	return ses;
+}
+
+
+DO_CONFIG(config_buffersize)
+{
+	if (*arg2)
+	{
+		if (!is_number(arg2))
+		{
+			show_error(ses, LIST_CONFIG, "#SYNTAX: #CONFIG {BUFFER SIZE} <NUMBER>");
+
+			return NULL;
+		}
+
+		switch (atoi(arg2))
+		{
+			case 100:
+			case 1000:
+			case 10000:
+			case 100000:
+			case 1000000:
+				break;
+
+			default:
+				show_error(ses, LIST_CONFIG, "#ERROR: #CONFIG BUFFER: SIZE MUST BE 100, 1000, 10000, 100000, or 1000000.");
+				return NULL;
+		}
+		init_buffer(ses, atoi(arg2));
+	}
+	sprintf(arg2, "%d", ses->scroll->size);
+
+	return ses;
+}
+
+DO_CONFIG(config_charset)
+{
+	if (*arg2)
+	{
+		if (is_abbrev(arg2, "AUTO"))
+		{
+			if (strcasestr(gtd->lang, "UTF-8"))
+			{
+				DEL_BIT(ses->charset, CHARSET_FLAG_ALL);
+				SET_BIT(ses->charset, CHARSET_FLAG_UTF8);
+			}
+			else if (strcasestr(gtd->lang, "BIG-5"))
+			{
+				DEL_BIT(ses->charset, CHARSET_FLAG_ALL);
+				SET_BIT(ses->charset, CHARSET_FLAG_BIG5);
+			}
+			else if (strcasestr(gtd->term, "XTERM"))
+			{
+				DEL_BIT(ses->charset, CHARSET_FLAG_ALL);
+				SET_BIT(ses->charset, CHARSET_FLAG_UTF8);
+			}
+			else
+			{
+				DEL_BIT(ses->charset, CHARSET_FLAG_ALL);
+			}
+		}
+		else if (is_abbrev(arg2, "ASCII"))
+		{
+			DEL_BIT(ses->charset, CHARSET_FLAG_ALL);
+		}
+		else if (is_abbrev(arg2, "BIG-5"))
+		{
+			DEL_BIT(ses->charset, CHARSET_FLAG_ALL);
+			SET_BIT(ses->charset, CHARSET_FLAG_BIG5);
+		}
+		else if (is_abbrev(arg2, "GBK-1"))
+		{
+			DEL_BIT(ses->charset, CHARSET_FLAG_ALL);
+			SET_BIT(ses->charset, CHARSET_FLAG_GBK1);
+		}
+		else if (is_abbrev(arg2, "UTF-8"))
+		{
+			DEL_BIT(ses->charset, CHARSET_FLAG_ALL);
+			SET_BIT(ses->charset, CHARSET_FLAG_UTF8);
+		}
+		else if (is_abbrev(arg2, "BIG5TOUTF8") || is_abbrev(arg2, "BIG2UTF"))
+		{
+			DEL_BIT(ses->charset, CHARSET_FLAG_ALL);
+			SET_BIT(ses->charset, CHARSET_FLAG_UTF8|CHARSET_FLAG_BIG5TOUTF8);
+		}
+		else if (is_abbrev(arg2, "FANSI"))
+		{
+			DEL_BIT(ses->charset, CHARSET_FLAG_ALL);
+			SET_BIT(ses->charset, CHARSET_FLAG_UTF8|CHARSET_FLAG_FANSITOUTF8);
+		}
+		else if (is_abbrev(arg2, "GBK1TOUTF8"))
+		{
+			DEL_BIT(ses->charset, CHARSET_FLAG_ALL);
+			SET_BIT(ses->charset, CHARSET_FLAG_UTF8|CHARSET_FLAG_GBK1TOUTF8);
+		}
+		else if (is_abbrev(arg2, "ISO1TOUTF8"))
+		{
+			DEL_BIT(ses->charset, CHARSET_FLAG_ALL);
+			SET_BIT(ses->charset, CHARSET_FLAG_UTF8|CHARSET_FLAG_ISO1TOUTF8);
+		}
+		else if (is_abbrev(arg2, "ISO2TOUTF8"))
+		{
+			DEL_BIT(ses->charset, CHARSET_FLAG_ALL);
+			SET_BIT(ses->charset, CHARSET_FLAG_UTF8|CHARSET_FLAG_ISO2TOUTF8);
+		}
+		else if (is_abbrev(arg2, "KOI8TOUTF8"))
+		{
+			DEL_BIT(ses->charset, CHARSET_FLAG_ALL);
+			SET_BIT(ses->charset, CHARSET_FLAG_UTF8|CHARSET_FLAG_KOI8TOUTF8);
+		}
+		else
+		{
+			show_error(ses, LIST_CONFIG, "#SYNTAX: #CONFIG {%s} <AUTO|ASCII|BIG-5|BIG5TOUTF8|FANSI|GBK-1|GBK1TOUTF8|KOI8TOUTF8|UTF-8>", config_table[index].name);
+
+			return NULL;
+		}
+	}
+
+	switch (HAS_BIT(ses->charset, CHARSET_FLAG_ALL))
+	{
+		case CHARSET_FLAG_BIG5:
+			strcpy(arg2, "BIG-5");
+			break;
+		case CHARSET_FLAG_GBK1:
+			strcpy(arg2, "GBK-1");
+			break;
+		case CHARSET_FLAG_UTF8:
+			strcpy(arg2, "UTF-8");
+			break;
+		case CHARSET_FLAG_UTF8|CHARSET_FLAG_BIG5TOUTF8:
+			strcpy(arg2, "BIG5TOUTF8");
+			break;
+		case CHARSET_FLAG_UTF8|CHARSET_FLAG_FANSITOUTF8:
+			strcpy(arg2, "FANSI");
+			break;
+		case CHARSET_FLAG_UTF8|CHARSET_FLAG_GBK1TOUTF8:
+			strcpy(arg2, "GBK1TOUTF8");
+			break;
+		case CHARSET_FLAG_UTF8|CHARSET_FLAG_KOI8TOUTF8:
+			strcpy(arg2, "KOI8TOUTF8");
+			break;
+		case CHARSET_FLAG_UTF8|CHARSET_FLAG_ISO1TOUTF8:
+			strcpy(arg2, "ISO1TOUTF8");
+			break;
+		case CHARSET_FLAG_UTF8|CHARSET_FLAG_ISO2TOUTF8:
+			strcpy(arg2, "ISO2TOUTF8");
+			break;
+		default:
+			strcpy(arg2, "ASCII");
+			break;
+	}
+
+	return ses;
+}
+
+DO_CONFIG(config_childlock)
+{
+	if (*arg2)
+	{
+		if (is_abbrev(arg2, "ON"))
+		{
+			SET_BIT(gtd->flags, TINTIN_FLAG_CHILDLOCK);
+		}
+		else if (is_abbrev(arg2, "OFF"))
+		{
+			DEL_BIT(gtd->flags, TINTIN_FLAG_CHILDLOCK);
+		}
+		else
+		{
+			show_error(ses, LIST_CONFIG, "#SYNTAX: #CONFIG {%s} <ON|OFF>", config_table[index].name);
+
+			return NULL;
+		}
+	}
+	strcpy(arg2, HAS_BIT(gtd->flags, TINTIN_FLAG_CHILDLOCK) ? "ON" : "OFF");
+
+	return ses;
+}
+
+DO_CONFIG(config_colormode)
+{
+	if (*arg2)
+	{	
+		if (is_abbrev(arg2, "NONE") || is_abbrev(arg2, "OFF"))
+		{
+			ses->color = 0;
+		}
+		else if (is_abbrev(arg2, "ANSI"))
+		{
+			ses->color = 16;
+		}
+		else if (is_abbrev(arg2, "256"))
+		{
+			ses->color = 256;
+		}
+		else if (is_abbrev(arg2, "TRUE") || is_abbrev(arg2, "ON") || is_abbrev(arg2, "AUTO"))
+		{
+			ses->color = 4096;
+		}
+		else
+		{
+			show_error(ses, LIST_CONFIG, "#SYNTAX: #CONFIG {%s} <ON|OFF|ANSI|256|TRUE>", config_table[index].name);
+
+			return NULL;
+		}
+	}
+	strcpy(arg2, ses->color == 0 ? "OFF" : ses->color == 16 ? "ANSI" : ses->color == 256 ? "256" : "TRUE");
+
+	return ses;
+}
+
+DO_CONFIG(config_colorpatch)
+{
+	if (*arg2)
+	{
+		if (is_abbrev(arg2, "ON"))
+		{
+			SET_BIT(ses->flags, SES_FLAG_COLORPATCH);
+		}
+		else if (is_abbrev(arg2, "OFF"))
+		{
+			DEL_BIT(ses->flags, SES_FLAG_COLORPATCH);
+		}
+		else
+		{
+			show_error(ses, LIST_CONFIG, "#SYNTAX: #CONFIG {%s} <ON|OFF>", config_table[index].name);
+
+			return NULL;
+		}
+	}
+	strcpy(arg2, HAS_BIT(ses->flags, SES_FLAG_COLORPATCH) ? "ON" : "OFF");
+
+	return ses;
+}
+
+DO_CONFIG(config_commandcolor)
+{
+	if (*arg2)
+	{
+		if (!get_color_names(ses, arg2, arg1))
+		{
+			show_error(ses, LIST_CONFIG, "#CONFIG COMMAND COLOR: INVALID COLOR CODE {%s}", arg2);
+
+			return NULL;
+		}
+		RESTRING(ses->cmd_color, arg1);
+	}
+	convert_meta(ses->cmd_color, arg2, SUB_EOL);
+
+	return ses;
+}
+
+DO_CONFIG(config_commandecho)
+{
+	if (*arg2)
+	{
+		if (is_abbrev(arg2, "ON"))
+		{
+			SET_BIT(ses->flags, SES_FLAG_ECHOCOMMAND);
+		}
+		else if (is_abbrev(arg2, "OFF"))
+		{
+			DEL_BIT(ses->flags, SES_FLAG_ECHOCOMMAND);
+		}
+		else
+		{
+			show_error(ses, LIST_CONFIG, "#SYNTAX: #CONFIG {%s} <ON|OFF>", config_table[index].name);
+
+			return NULL;
+		}
+	}
+	strcpy(arg2, HAS_BIT(ses->flags, SES_FLAG_ECHOCOMMAND) ? "ON" : "OFF");
+
+	return ses;
+}
+
+DO_CONFIG(config_connectretry)
+{
+	if (*arg2)
+	{
+		if (!is_number(arg2))
+		{
+			show_error(ses, LIST_CONFIG, "#SYNTAX: #CONFIG {CONNECT RETRY} <NUMBER>");
+
+			return NULL;
+		}
+		else if (atof(arg2) < 0 || atof(arg2) > 10000)
+		{
+			show_error(ses, LIST_CONFIG, "#ERROR: #CONFIG CONNECT RETRY: PROVIDE A NUMBER BETWEEN 0.0 and 10000.0");
+
+			return NULL;
+		}
+		gts->connect_retry = atoll(arg2) * 1000000LL;
+	}
+
+	sprintf(arg2, "%6.1Lf", (long double) gts->connect_retry / 1000000);
+
+	return ses;
+}
+
+DO_CONFIG(config_convertmeta)
+{
+	if (*arg2)
+	{
+		if (is_abbrev(arg2, "ON"))
+		{
+			SET_BIT(ses->flags, SES_FLAG_CONVERTMETA);
+		}
+		else if (is_abbrev(arg2, "OFF"))
+		{
+			DEL_BIT(ses->flags, SES_FLAG_CONVERTMETA);
+		}
+		else
+		{
+			show_error(ses, LIST_CONFIG, "#SYNTAX: #CONFIG {%s} <ON|OFF>", config_table[index].name);
+
+			return NULL;
+		}
+	}
+	strcpy(arg2, HAS_BIT(ses->flags, SES_FLAG_CONVERTMETA) ? "ON" : "OFF");
+
+	return ses;
+}
+
+DO_CONFIG(config_debugtelnet)
+{
+	if (*arg2)
+	{
+		if (is_abbrev(arg2, "ON"))
+		{
+			SET_BIT(ses->telopts, TELOPT_FLAG_DEBUG);
+		}
+		else if (is_abbrev(arg2, "OFF"))
+		{
+			DEL_BIT(ses->telopts, TELOPT_FLAG_DEBUG);
+		}
+		else
+		{
+			show_error(ses, LIST_CONFIG, "#SYNTAX: #CONFIG {%s} <ON|OFF>", config_table[index].name);
+
+			return NULL;
+		}
+	}
+	strcpy(arg2, HAS_BIT(ses->telopts, TELOPT_FLAG_DEBUG) ? "ON" : "OFF");
+
+	return ses;
+}
+
+DO_CONFIG(config_historysize)
+{
+	if (*arg2)
+	{
+		if (!is_number(arg2))
+		{
+			show_error(ses, LIST_CONFIG, "#SYNTAX: #CONFIG {HISTORY SIZE} <NUMBER>");
+
+			return NULL;
+		}
+
+		if (atoi(arg2) < 0 || atoi(arg2) > 9999)
+		{
+			show_error(ses, LIST_CONFIG, "#ERROR: #CONFIG HISTORY: PROVIDE A NUMBER BETWEEN 0 and 9999");
+
+			return NULL;
+		}
+
+		gtd->history_size = atoi(arg2);
+	}
+	sprintf(arg2, "%d", gtd->history_size);
+
+	return ses;
+}
+
+DO_CONFIG(config_inheritance)
+{
+	if (*arg2)
+	{
+		if (is_abbrev(arg2, "ON"))
+		{
+			SET_BIT(gtd->flags, TINTIN_FLAG_INHERITANCE);
+		}
+		else if (is_abbrev(arg2, "OFF"))
+		{
+			DEL_BIT(gtd->flags, TINTIN_FLAG_INHERITANCE);
+		}
+		else
+		{
+			show_error(ses, LIST_CONFIG, "#SYNTAX: #CONFIG {%s} <ON|OFF>", config_table[index].name);
+
+			return NULL;
+		}
+	}
+	strcpy(arg2, HAS_BIT(gtd->flags, TINTIN_FLAG_INHERITANCE) ? "ON" : "OFF");
+
+	return ses;
+}
+
+
+DO_CONFIG(config_loglevel)
+{
+	if (*arg2)
+	{
+		if (is_abbrev(arg2, "LOW"))
+		{
+			SET_BIT(ses->logmode, LOG_FLAG_LOW);
+		}
+		else if (is_abbrev(arg2, "HIGH"))
+		{
+			DEL_BIT(ses->logmode, LOG_FLAG_LOW);
+		}
+		else
+		{
+			show_error(ses, LIST_CONFIG, "#SYNTAX: #CONFIG {%s} <LOW|HIGH>", config_table[index].name);
+
+			return NULL;
+		}
+	}
+	strcpy(arg2, HAS_BIT(ses->logmode, LOG_FLAG_LOW) ? "LOW" : "HIGH");
+
+	return ses;
+}
+
+
+DO_CONFIG(config_logmode)
+{
+	if (*arg2)
+	{
+		if (is_abbrev(arg2, "HTML"))
+		{
+			SET_BIT(ses->logmode, LOG_FLAG_HTML);
+			DEL_BIT(ses->logmode, LOG_FLAG_PLAIN);
+			DEL_BIT(ses->logmode, LOG_FLAG_RAW);
+		}
+		else if (is_abbrev(arg2, "PLAIN"))
+		{
+			DEL_BIT(ses->logmode, LOG_FLAG_HTML);
+			SET_BIT(ses->logmode, LOG_FLAG_PLAIN);
+			DEL_BIT(ses->logmode, LOG_FLAG_RAW);
+		}
+		else if (is_abbrev(arg2, "RAW"))
+		{
+			DEL_BIT(ses->logmode, LOG_FLAG_HTML);
+			DEL_BIT(ses->logmode, LOG_FLAG_PLAIN);
+			SET_BIT(ses->logmode, LOG_FLAG_RAW);
+		}
+		else
+		{
+			show_error(ses, LIST_CONFIG, "#SYNTAX: #CONFIG LOG <HTML|PLAIN|RAW>");
+
+			return NULL;
+		}
+	}
+	strcpy(arg2, HAS_BIT(ses->logmode, LOG_FLAG_HTML) ? "HTML" : HAS_BIT(ses->logmode, LOG_FLAG_PLAIN) ? "PLAIN" : "RAW");
+
+	return ses;
+}
+
+
+DO_CONFIG(config_mccp)
+{
+	if (*arg2)
+	{
+		if (is_abbrev(arg2, "ON"))
+		{
+			SET_BIT(ses->flags, SES_FLAG_MCCP);
+		}
+		else if (is_abbrev(arg2, "OFF"))
+		{
+			DEL_BIT(ses->flags, SES_FLAG_MCCP);
+		}
+		else
+		{
+			show_error(ses, LIST_CONFIG, "#SYNTAX: #CONFIG {%s} <ON|OFF>", config_table[index].name);
+
+			return NULL;
+		}
+	}
+	strcpy(arg2, HAS_BIT(ses->flags, SES_FLAG_MCCP) ? "ON" : "OFF");
+
+	return ses;
+}
+
+
+DO_CONFIG(config_mousetracking)
+{
+	if (*arg2)
+	{
+		if (is_abbrev(arg2, "ON"))
+		{
+			DEL_BIT(ses->flags, SES_FLAG_MOUSEDEBUG);
+			DEL_BIT(ses->flags, SES_FLAG_MOUSEINFO);
+			SET_BIT(gtd->flags, TINTIN_FLAG_MOUSETRACKING);
+			print_stdout("\e[?1000h\e[?1002h\e[?1004h\e[?1006h");
+		}
+		else if (is_abbrev(arg2, "OFF"))
+		{
+			if (HAS_BIT(gtd->flags, TINTIN_FLAG_MOUSETRACKING))
+			{
+				DEL_BIT(ses->flags, SES_FLAG_MOUSEDEBUG);
+				DEL_BIT(ses->flags, SES_FLAG_MOUSEINFO);
+				DEL_BIT(gtd->flags, TINTIN_FLAG_MOUSETRACKING);
+				print_stdout("\e[?1000l\e[?1002l\e[?1004l\e[?1006l");
+			}
+		}
+		else if (is_abbrev(arg2, "DEBUG"))
+		{
+			DEL_BIT(ses->flags, SES_FLAG_MOUSEINFO);
+			SET_BIT(ses->flags, SES_FLAG_MOUSEDEBUG);
+			SET_BIT(gtd->flags, TINTIN_FLAG_MOUSETRACKING);
+			print_stdout("\e[?1000h\e[?1002h\e[?1004h\e[?1006h");
+		}
+		else if (is_abbrev(arg2, "DEBUG INFO"))
+		{
+			SET_BIT(ses->flags, SES_FLAG_MOUSEDEBUG);
+			SET_BIT(ses->flags, SES_FLAG_MOUSEINFO);
+			SET_BIT(gtd->flags, TINTIN_FLAG_MOUSETRACKING);
+			print_stdout("\e[?1000h\e[?1002h\e[?1004h\e[?1006h");
+		}
+		else if (is_abbrev(arg2, "INFO"))
+		{
+			DEL_BIT(ses->flags, SES_FLAG_MOUSEDEBUG);
+			SET_BIT(ses->flags, SES_FLAG_MOUSEINFO);
+			SET_BIT(gtd->flags, TINTIN_FLAG_MOUSETRACKING);
+			print_stdout("\e[?1000h\e[?1002h\e[?1004h\e[?1006h");
+		}
+		else
+		{
+			show_error(ses, LIST_CONFIG, "#SYNTAX: #CONFIG {%s} <ON|OFF|DEBUG|INFO|DEBUG INFO>", config_table[index].name);
+
+			return NULL;
+		}
+	}
+	if (HAS_BIT(gtd->flags, TINTIN_FLAG_MOUSETRACKING))
+	{
+		switch (HAS_BIT(ses->flags, SES_FLAG_MOUSEDEBUG|SES_FLAG_MOUSEINFO))
+		{
+			case 0:
+				strcpy(arg2, "ON");
+				break;
+			case SES_FLAG_MOUSEDEBUG:
+				strcpy(arg2, "DEBUG");
+				break;
+			case SES_FLAG_MOUSEINFO:
+				strcpy(arg2, "INFO");
+				break;
+			default:
+				strcpy(arg2, "DEBUG INFO");
+				break;
+		}
+	}
+	else
+	{
+		strcpy(arg2, "OFF");
+	}
+	return ses;
+}
+
+
+DO_CONFIG(config_packetpatch)
+{
+	if (*arg2)
+	{
+		if (is_abbrev(arg2, "AUTO"))
+		{
+			ses->packet_patch = 0;
+
+			SET_BIT(ses->flags, SES_FLAG_AUTOPATCH);
+		}
+		else if (!is_number(arg2))
+		{
+			show_error(ses, LIST_CONFIG, "#SYNTAX: #CONFIG {PACKET PATCH} <NUMBER>");
+
+			return NULL;
+		}
+		else if (atof(arg2) < 0 || atof(arg2) > 10)
+		{
+			show_error(ses, LIST_CONFIG, "#ERROR: #CONFIG PACKET PATCH: PROVIDE A NUMBER BETWEEN 0.00 and 10.00");
+
+			return NULL;
+		}
+		else
+		{
+			DEL_BIT(ses->flags, SES_FLAG_AUTOPATCH);
+
+			ses->packet_patch = (unsigned long long) (tintoi(arg2) * 1000000ULL);
+		}
+	}
+
+	if (HAS_BIT(ses->flags, SES_FLAG_AUTOPATCH))
+	{
+		strcpy(arg2, "AUTO");
+	}
+	else
+	{
+		sprintf(arg2, "%4.2Lf", (long double) ses->packet_patch / 1000000);
+	}
+	return ses;
+}
+
+DO_CONFIG(config_pid)
+{
+	if (*arg2)
+	{
+		if (!is_number(arg2))
+		{
+			show_error(ses, LIST_CONFIG, "#SYNTAX: #CONFIG {PID} <NUMBER>");
+			
+			return NULL;
+		}
+		else if (atoi(arg2) < 0 || atoi(arg2) > 4194303)
+		{
+			show_error(ses, LIST_CONFIG, "#ERROR: #CONFIG PID: PROVIDE A NUMBER BETWEEN 3 and 4194304");
+
+			return NULL;
+		}
+		else
+		{
+			gtd->detach_pid = atoi(arg2);
+		}
+	}
+	sprintf(arg2, "%d", gtd->detach_pid);
+
+	return ses;
+}
+	
+DO_CONFIG(config_randomseed)
+{
+	if (*arg2)
+	{
+		if (is_abbrev(arg2, "AUTO"))
+		{
+			seed_rand(ses, utime());
+		}
+		else if (is_number(arg2))
+		{
+			seed_rand(ses, get_number(ses, arg2));
+		}
+		else
+		{
+			show_error(ses, LIST_CONFIG, "#SYNTAX: #CONFIG {%s} <AUTO|NUMBER>", config_table[index].name);
+
+			return NULL;
+		}
+	}
+	sprintf(arg2, "%llu", ses->rand);
+
+	return ses;
+}
+
+DO_CONFIG(config_repeatchar)
+{
+	if (*arg2)
+	{
+		if (!ispunct((int) arg2[0]))
+		{
+			show_error(ses, LIST_CONFIG, "#ERROR: #CONFIG REPEAT CHAR: INVALID CHARACTER {%c}", arg2[0]);
+
+			return NULL;
+		}
+
+		gtd->repeat_char = arg2[0];
+
+		arg2[1] = 0;
+	}
+
+	sprintf(arg2, "%c", gtd->repeat_char);
+
+	return ses;
+}
+
+DO_CONFIG(config_repeatenter)
+{
+	if (*arg2)
+	{
+		if (is_abbrev(arg2, "ON"))
+		{
+			SET_BIT(ses->flags, SES_FLAG_REPEATENTER);
+		}
+		else if (is_abbrev(arg2, "OFF"))
+		{
+			DEL_BIT(ses->flags, SES_FLAG_REPEATENTER);
+		}
+		else
+		{
+			show_error(ses, LIST_CONFIG, "#SYNTAX: #CONFIG {%s} <ON|OFF>", config_table[index].name);
+
+			return NULL;
+		}
+	}
+	strcpy(arg2, HAS_BIT(ses->flags, SES_FLAG_REPEATENTER) ? "ON" : "OFF");
+
+	return ses;
+}
+
+
+DO_CONFIG(config_screenreader)
+{
+	if (*arg2)
+	{
+		if (is_abbrev(arg2, "ON"))
+		{
+			SET_BIT(ses->flags, SES_FLAG_SCREENREADER);
+		}
+		else if (is_abbrev(arg2, "OFF"))
+		{
+			DEL_BIT(ses->flags, SES_FLAG_SCREENREADER);
+		}
+		else
+		{
+			show_error(ses, LIST_CONFIG, "#SYNTAX: #CONFIG {%s} <ON|OFF>", config_table[index].name);
+
+			return NULL;
+		}
+	}
+	strcpy(arg2, HAS_BIT(ses->flags, SES_FLAG_SCREENREADER) ? "ON" : "OFF");
+
+	return ses;
+}
+
+
+
+
+DO_CONFIG(config_scrolllock)
+{
+	if (*arg2)
+	{	
+		if (is_abbrev(arg2, "ON"))
+		{
+			SET_BIT(ses->flags, SES_FLAG_SCROLLLOCK);
+		}
+		else if (is_abbrev(arg2, "OFF"))
+		{
+			DEL_BIT(ses->flags, SES_FLAG_SCROLLLOCK);
+		}
+		else
+		{
+			show_error(ses, LIST_CONFIG, "#SYNTAX: #CONFIG {%s} <ON|OFF>", config_table[index].name);
+
+			return NULL;
+		}
+	}
+	strcpy(arg2, HAS_BIT(ses->flags, SES_FLAG_SCROLLLOCK) ? "ON" : "OFF");	
+
+	return ses;
+}
+
+DO_CONFIG(config_speedwalk)
+{
+	if (*arg2)
+	{
+		if (is_abbrev(arg2, "ON"))
+		{
+			SET_BIT(ses->flags, SES_FLAG_SPEEDWALK);
+		}
+		else if (is_abbrev(arg2, "OFF"))
+		{
+			DEL_BIT(ses->flags, SES_FLAG_SPEEDWALK);
+		}
+		else
+		{
+			show_error(ses, LIST_CONFIG, "#SYNTAX: #CONFIG {%s} <ON|OFF>", config_table[index].name);
+
+			return NULL;
+		}
+	}
+	strcpy(arg2, HAS_BIT(ses->flags, SES_FLAG_SPEEDWALK) ? "ON" : "OFF");
+
+	return ses;
+}
+
+DO_CONFIG(config_tabwidth)
+{
+	if (*arg2)
+	{
+		if (is_abbrev(arg2, "AUTO"))
+		{
+			ses->tab_width = 8;
+		}
+		else if (!is_number(arg2))
+		{
+			show_error(ses, LIST_CONFIG, "#SYNTAX: #CONFIG {TAB WIDTH} <NUMBER>");
+
+			return NULL;
+		}
+		else if (atof(arg2) < 1 || atof(arg2) > 16)
+		{
+			show_error(ses, LIST_CONFIG, "#ERROR: #CONFIG TAB WIDTH: PROVIDE A NUMBER BETWEEN 1 and 16");
+
+			return NULL;
+		}
+		else
+		{
+			ses->tab_width = (int) tintoi(arg2);
+		}
+	}
+	sprintf(arg2, "%d", ses->tab_width);
+
+	return ses;
+}
+	
+DO_CONFIG(config_telnet)
+{
+	if (*arg2)
+	{
+		if (is_abbrev(arg2, "ON"))
+		{
+			DEL_BIT(ses->telopts, TELOPT_FLAG_DEBUG);
+			SET_BIT(ses->flags, SES_FLAG_TELNET);
+		}
+		else if (is_abbrev(arg2, "OFF"))
+		{
+			DEL_BIT(ses->telopts, TELOPT_FLAG_DEBUG);
+			DEL_BIT(ses->flags, SES_FLAG_TELNET);
+		}
+		else if (is_abbrev(arg2, "DEBUG"))
+		{
+			SET_BIT(ses->telopts, TELOPT_FLAG_DEBUG);
+			SET_BIT(ses->flags, SES_FLAG_TELNET);
+		}
+		else if (is_abbrev(arg2, "INFO"))
+		{
+			SET_BIT(ses->telopts, TELOPT_FLAG_DEBUG);
+			SET_BIT(ses->flags, SES_FLAG_TELNET);
+		}
+		else
+		{
+			show_error(ses, LIST_CONFIG, "#SYNTAX: #CONFIG {%s} <ON|OFF|DEBUG|INFO>", config_table[index].name);
+
+			return NULL;
+		}
+	}
+	strcpy(arg2, HAS_BIT(ses->telopts, TELOPT_FLAG_DEBUG) ? "DEBUG" : HAS_BIT(ses->flags, SES_FLAG_TELNET) ? "ON" : "OFF");
+
+	return ses;
+}
+
+
+DO_CONFIG(config_tintinchar)
+{
+	if (*arg2)
+	{
+		if (!ispunct((int) arg2[0]))
+		{
+			show_error(ses, LIST_CONFIG, "#ERROR: #CONFIG TINTIN CHAR: INVALID CHARACTER {%c}", arg2[0]);
+
+			return NULL;
+		}
+
+		gtd->tintin_char = arg2[0];
+	}
+	sprintf(arg2, "%c", gtd->tintin_char);
+
+	return ses;
+}
+
+DO_CONFIG(config_verbatim)
+{
+	if (*arg2)
+	{
+		if (is_abbrev(arg2, "ON"))
+		{
+			SET_BIT(ses->flags, SES_FLAG_VERBATIM);
+		}
+		else if (is_abbrev(arg2, "OFF"))
+		{
+			DEL_BIT(ses->flags, SES_FLAG_VERBATIM);
+		}
+		else
+		{
+			show_error(ses, LIST_CONFIG, "#SYNTAX: #CONFIG {%s} <ON|OFF>", config_table[index].name);
+
+			return NULL;
+		}
+	}
+	strcpy(arg2, HAS_BIT(ses->flags, SES_FLAG_VERBATIM) ? "ON" : "OFF");
+
+	return ses;
+}
+
+DO_CONFIG(config_verbatimchar)
+{
+	if (*arg2)
+	{
+		if (!ispunct((int) arg2[0]))
+		{
+			show_error(ses, LIST_CONFIG, "#ERROR: #CONFIG VERBATIM CHAR: INVALID CHARACTER {%c}", arg2[0]);
+
+			return NULL;
+		}
+		gtd->verbatim_char = arg2[0];
+	}
+	sprintf(arg2, "%c", gtd->verbatim_char);
+
+	return ses;
+}
+
+
+DO_CONFIG(config_verbose)
+{
+	if (*arg2)
+	{
+		if (is_abbrev(arg2, "ON"))
+		{
+			SET_BIT(ses->flags, SES_FLAG_VERBOSE);
+		}
+		else if (is_abbrev(arg2, "OFF"))
+		{
+			DEL_BIT(ses->flags, SES_FLAG_VERBOSE);
+		}
+		else
+		{
+			show_error(ses, LIST_CONFIG, "#SYNTAX: #CONFIG {%s} <ON|OFF>", config_table[index].name);
+
+			return NULL;
+		}
+	}
+	strcpy(arg2, HAS_BIT(ses->flags, SES_FLAG_VERBOSE) ? "ON" : "OFF");
+
+	return ses;
+}
+
+DO_CONFIG(config_wordwrap)
+{
+	if (*arg2)
+	{
+		if (is_abbrev(arg2, "ON"))
+		{
+			SET_BIT(ses->flags, SES_FLAG_WORDWRAP);
+		}
+		else if (is_abbrev(arg2, "OFF"))
+		{
+			DEL_BIT(ses->flags, SES_FLAG_WORDWRAP);
+		}
+		else
+		{
+			show_error(ses, LIST_CONFIG, "#SYNTAX: #CONFIG {%s} <ON|OFF>", config_table[index].name);
+
+			return NULL;
+		}
+	}
+	strcpy(arg2, HAS_BIT(ses->flags, SES_FLAG_WORDWRAP) ? "ON" : "OFF");
+
+	return ses;
+}

+ 261 - 0
config.h

@@ -0,0 +1,261 @@
+/* config.h.  Generated from config.h.in by configure.  */
+/* config.h.in.  Generated from configure.in by autoheader.  */
+
+/* Define to 1 if you have the <arpa/inet.h> header file. */
+#define HAVE_ARPA_INET_H 1
+
+/* Define to 1 if you have the <ctype.h> header file. */
+#define HAVE_CTYPE_H 1
+
+/* Define to 1 if you have the <dirent.h> header file, and it defines `DIR'.
+   */
+#define HAVE_DIRENT_H 1
+
+/* Define to 1 if you don't have `vprintf' but do have `_doprnt.' */
+/* #undef HAVE_DOPRNT */
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#define HAVE_FCNTL_H 1
+
+/* Define to 1 if you have the `forkpty' function. */
+#define HAVE_FORKPTY 1
+
+/* Define to 1 if your system has /dev/ptms (STREAMS pseudoterminal mux). */
+#define HAVE__DEV_PTMX 1
+
+/* Define to 1 if you have the `getaddrinfo' function. */
+#define HAVE_GETADDRINFO 1
+
+/* Define to 1 if you have the `gethostbyname' function. */
+#define HAVE_GETHOSTBYNAME 1
+
+/* Define to 1 if you have the `gethostname' function. */
+#define HAVE_GETHOSTNAME 1
+
+/* Define to 1 if you have the `getmntent' function. */
+#define HAVE_GETMNTENT 1
+
+/* Define to 1 if you have the `gettimeofday' function. */
+#define HAVE_GETTIMEOFDAY 1
+
+/* Define to 1 if you have the <gnutls/gnutls.h> header file. */
+//#define HAVE_GNUTLS_H /**/
+
+/* Define to 1 if you have the `inet_ntoa' function. */
+#define HAVE_INET_NTOA 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the `nsl' library (-lnsl). */
+/* #undef HAVE_LIBNSL */
+
+/* Define to 1 if you have the `pthread' library (-lpthread). */
+#define HAVE_LIBPTHREAD 1
+
+/* Define to 1 if you have the `termcap' library (-ltermcap). */
+/* #undef HAVE_LIBTERMCAP */
+
+/* Define to 1 if you have the `util' library (-lutil). */
+#define HAVE_LIBUTIL 1
+
+/* Define to 1 if you have the `z' library (-lz). */
+#define HAVE_LIBZ 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the `memset' function. */
+#define HAVE_MEMSET 1
+
+/* Define to 1 if you have the <ndir.h> header file, and it defines `DIR'. */
+/* #undef HAVE_NDIR_H */
+
+/* Define to 1 if you have the <netdb.h> header file. */
+#define HAVE_NETDB_H 1
+
+/* Define to 1 if you have the <netinet/in.h> header file. */
+#define HAVE_NETINET_IN_H 1
+
+/* Define to 1 if you have the <net/errno.h> header file. */
+/* #undef HAVE_NET_ERRNO_H */
+
+/* Define to 1 if you have the <param.h> header file. */
+/* #undef HAVE_PARAM_H */
+
+/* Define to 1 if you have the `popen' function. */
+#define HAVE_POPEN 1
+
+/* Define to 1 if you have the <pthread.h> header file. */
+#define HAVE_PTHREAD_H 1
+
+/* Define to 1 if you have the <pty.h> header file. */
+#define HAVE_PTY_H 1
+
+/* Define to 1 if your system has a GNU libc compatible `realloc' function,
+   and to 0 otherwise. */
+#define HAVE_REALLOC 1
+
+/* Define to 1 if you have the `select' function. */
+#define HAVE_SELECT 1
+
+/* Define to 1 if you have the `socket' function. */
+#define HAVE_SOCKET 1
+
+/* Define to 1 if you have the <socks.h> header file. */
+/* #undef HAVE_SOCKS_H */
+
+/* Define to 1 if `stat' has the bug that it succeeds when given the
+   zero-length file name argument. */
+/* #undef HAVE_STAT_EMPTY_STRING_BUG */
+
+/* Define to 1 if stdbool.h conforms to C99. */
+#define HAVE_STDBOOL_H 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the `strcasecmp' function. */
+#define HAVE_STRCASECMP 1
+
+/* Define to 1 if you have the `strchr' function. */
+#define HAVE_STRCHR 1
+
+/* Define to 1 if you have the `strdup' function. */
+#define HAVE_STRDUP 1
+
+/* Define to 1 if you have the `strerror' function. */
+#define HAVE_STRERROR 1
+
+/* Define to 1 if you have the `strftime' function. */
+#define HAVE_STRFTIME 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the `strncasecmp' function. */
+#define HAVE_STRNCASECMP 1
+
+/* Define to 1 if you have the `strstr' function. */
+#define HAVE_STRSTR 1
+
+/* Define to 1 if you have the <stropts.h> header file. */
+
+/* #undef HAVE_STROPTS_H */
+
+/* Define to 1 if you have the <sys/dir.h> header file, and it defines `DIR'.
+   */
+/* #undef HAVE_SYS_DIR_H */
+
+/* Define to 1 if you have the <sys/ioctl.h> header file. */
+#define HAVE_SYS_IOCTL_H 1
+
+/* Define to 1 if you have the <sys/ndir.h> header file, and it defines `DIR'.
+   */
+/* #undef HAVE_SYS_NDIR_H */
+
+/* Define to 1 if you have the <sys/param.h> header file. */
+#define HAVE_SYS_PARAM_H 1
+
+/* Define to 1 if you have the <sys/ptem.h> header file. */
+/* #undef HAVE_SYS_PTEM_H */
+
+/* Define to 1 if you have the <sys/select.h> header file. */
+#define HAVE_SYS_SELECT_H 1
+
+/* Define to 1 if you have the <sys/socket.h> header file. */
+#define HAVE_SYS_SOCKET_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/termio.h> header file. */
+#define HAVE_SYS_TERMIO_H 1
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#define HAVE_SYS_TIME_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <termcap.h> header file. */
+/* #undef HAVE_TERMCAP_H */
+
+/* Define to 1 if you have the <time.h> header file. */
+#define HAVE_TIME_H 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to 1 if you have the <util.h> header file. */
+/* #undef HAVE_UTIL_H */
+
+/* Define to 1 if you have the `utime' function. */
+#define HAVE_UTIME 1
+
+/* Define to 1 if `utime(file, NULL)' sets file's timestamp to the present. */
+#define HAVE_UTIME_NULL 1
+
+/* Define to 1 if you have the `vprintf' function. */
+#define HAVE_VPRINTF 1
+
+/* Define to 1 if you have the <zlib.h> header file. */
+#define HAVE_ZLIB_H 1
+
+/* Define to 1 if the system has the type `_Bool'. */
+#define HAVE__BOOL 1
+
+/* Define to 1 if `lstat' dereferences a symlink specified with a trailing
+   slash. */
+#define LSTAT_FOLLOWS_SLASHED_SYMLINK 1
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT ""
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME ""
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING ""
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION ""
+
+/* Define as the return type of signal handlers (`int' or `void'). */
+#define RETSIGTYPE void
+
+/* Define to the type of arg 1 for `select'. */
+#define SELECT_TYPE_ARG1 int
+
+/* Define to the type of args 2, 3 and 4 for `select'. */
+#define SELECT_TYPE_ARG234 (fd_set *)
+
+/* Define to the type of arg 5 for `select'. */
+#define SELECT_TYPE_ARG5 (struct timeval *)
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */
+#define TIME_WITH_SYS_TIME 1
+
+/* Define to 1 if your <sys/time.h> declares `struct tm'. */
+/* #undef TM_IN_SYS_TIME */
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to rpl_realloc if the replacement function should be used. */
+/* #undef realloc */
+
+/* Define to `unsigned int' if <sys/types.h> does not define. */
+/* #undef size_t */

+ 1746 - 0
cursor.c

@@ -0,0 +1,1746 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2006                       *
+******************************************************************************/
+
+#include "tintin.h"
+
+DO_COMMAND(do_cursor)
+{
+	char all[BUFFER_SIZE], arg1[BUFFER_SIZE], temp[BUFFER_SIZE];
+	int cnt;
+
+	get_arg_in_braces(ses, arg, all, GET_ALL);
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+//	arg = sub_arg_in_braces(ses, arg, arg2, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		tintin_header(ses, " CURSOR OPTIONS ");
+
+		for (cnt = 0 ; *cursor_table[cnt].fun ; cnt++)
+		{
+			if (*cursor_table[cnt].name)
+			{
+				convert_meta(cursor_table[cnt].code, temp, FALSE);
+
+				tintin_printf2(ses, "  [%-18s] [%-6s] %s", cursor_table[cnt].name, temp, cursor_table[cnt].desc);
+			}
+		}
+		tintin_header(ses, "");
+	}
+	else
+	{
+		for (cnt = 0 ; ; cnt++)
+		{
+			if (HAS_BIT(cursor_table[cnt].flags, CURSOR_FLAG_GET_ALL))
+			{
+				if (is_abbrev(all, cursor_table[cnt].name))
+				{
+					cursor_table[cnt].fun(ses, arg);
+
+					return ses;
+				}
+			}
+			else if (HAS_BIT(cursor_table[cnt].flags, CURSOR_FLAG_GET_ONE))
+			{
+				if (is_abbrev(arg1, cursor_table[cnt].name))
+				{
+					cursor_table[cnt].fun(ses, arg);
+
+					return ses;
+				}
+			}
+			else
+			{
+				break;
+			}
+		}
+		show_error(ses, LIST_COMMAND, "#ERROR: #CURSOR {%s} IS NOT A VALID OPTION.", capitalize(all));
+	}
+	return ses;
+}
+
+int inputline_str_str_len(int start, int end)
+{
+	int raw_cnt, str_cnt, ret_cnt, width;
+
+	raw_cnt = str_cnt = ret_cnt = 0;
+
+	while (raw_cnt < gtd->input_len)
+	{
+		if (str_cnt >= end)
+		{
+			break;
+		}
+
+		if (HAS_BIT(gtd->ses->charset, CHARSET_FLAG_UTF8) && is_utf8_head(&gtd->input_buf[raw_cnt]))
+		{
+			raw_cnt += get_utf8_width(&gtd->input_buf[raw_cnt], &width);
+
+			if (str_cnt >= start)
+			{
+				ret_cnt += width;
+			}
+			str_cnt += width;
+		}
+		else
+		{
+			if (str_cnt >= start)
+			{
+				ret_cnt++;
+			}
+			raw_cnt++;
+			str_cnt++;
+		}
+	}
+	return ret_cnt;
+}
+
+// raw range
+
+int inputline_raw_str_len(int start, int end)
+{
+	int raw_cnt, ret_cnt, width;
+
+	raw_cnt = start;
+	ret_cnt = 0;
+
+	while (raw_cnt < gtd->input_len)
+	{
+		if (raw_cnt >= end)
+		{
+			break;
+		}
+
+		if (HAS_BIT(gtd->ses->charset, CHARSET_FLAG_UTF8) && is_utf8_head(&gtd->input_buf[raw_cnt]))
+		{
+			raw_cnt += get_utf8_width(&gtd->input_buf[raw_cnt], &width);
+			ret_cnt += width;
+		}
+		else
+		{
+			raw_cnt++;
+			ret_cnt++;
+		}
+	}
+	return ret_cnt;
+}
+
+// display range
+
+int inputline_str_raw_len(int start, int end)
+{
+	int raw_cnt, str_cnt, ret_cnt, width, tmp_cnt;
+
+	raw_cnt = str_cnt = ret_cnt = 0;
+
+	while (raw_cnt < gtd->input_len)
+	{
+		if (str_cnt >= end)
+		{
+			break;
+		}
+
+		if (HAS_BIT(gtd->ses->charset, CHARSET_FLAG_UTF8) && is_utf8_head(&gtd->input_buf[raw_cnt]))
+		{    
+			tmp_cnt = get_utf8_width(&gtd->input_buf[raw_cnt], &width);
+
+			if (str_cnt >= start)
+			{
+				ret_cnt += tmp_cnt;
+			}
+			raw_cnt += tmp_cnt;
+			str_cnt += width;
+		}
+		else
+		{
+			if (str_cnt >= start)
+			{
+				ret_cnt++;
+			}
+			raw_cnt++;
+			str_cnt++;
+		}
+	}
+	return ret_cnt;
+}
+
+int inputline_raw_raw_len(int start, int end)
+{
+	if (start > end)
+	{
+		return 0;
+	}
+	return end - start;
+}
+
+// Get string length of the input area
+
+int inputline_max_str_len(void)
+{
+	return gtd->screen->cols + 1 - gtd->input_off - (HAS_BIT(gtd->flags, TINTIN_FLAG_HISTORYSEARCH) ? 11 : 0);
+}
+
+int inputline_cur_str_len(void)
+{
+	return inputline_str_str_len(gtd->input_hid, gtd->input_hid + inputline_max_str_len());
+}
+
+// Get the position of the cursor
+
+int inputline_cur_pos(void)
+{
+	return gtd->input_off + gtd->input_pos - gtd->input_hid;
+}
+
+// Check for invalid characters.
+
+int inputline_str_chk(int offset, int totlen)
+{
+	int size;
+
+	while (offset < totlen)
+	{
+		if (HAS_BIT(gtd->ses->charset, CHARSET_FLAG_EUC))
+		{
+			if (is_euc_head(gtd->ses, &gtd->input_buf[offset]))
+			{
+				size = get_euc_size(gtd->ses, &gtd->input_buf[offset]);
+
+				if (size == 1 || offset + size > totlen)
+				{
+					return FALSE;
+				}
+				offset += size;
+			}
+			else
+			{
+				offset += 1;
+			}
+		}
+		else if (HAS_BIT(gtd->ses->charset, CHARSET_FLAG_UTF8))
+		{
+			if (is_utf8_head(&gtd->input_buf[offset]))
+			{
+				size = get_utf8_size(&gtd->input_buf[offset]);
+
+				if (size == 1 || offset + size > totlen)
+				{
+					return FALSE;
+				}
+				offset += size;
+			}
+			else
+			{
+				offset += 1;
+			}
+		}
+		else
+		{
+			return TRUE;
+		}
+	}
+	return TRUE;
+}
+
+DO_CURSOR(cursor_backspace)
+{
+	if (gtd->input_cur == 0)
+	{
+		return;
+	}
+
+	cursor_left(ses, "");
+	cursor_delete(ses, "");
+
+	modified_input();
+}
+
+DO_CURSOR(cursor_brace_open)
+{
+	ins_sprintf(&gtd->input_buf[gtd->input_cur], "{");
+
+	gtd->input_len++;
+	gtd->input_cur++;
+
+	gtd->input_pos += inputline_raw_str_len(gtd->input_cur - 1, gtd->input_cur);
+
+	cursor_redraw_line(ses, "");
+
+	modified_input();
+}
+
+DO_CURSOR(cursor_brace_close)
+{
+	ins_sprintf(&gtd->input_buf[gtd->input_cur], "}");
+
+	gtd->input_len++;
+	gtd->input_cur++;
+
+	gtd->input_pos += inputline_raw_str_len(gtd->input_cur - 1, gtd->input_cur);
+
+	cursor_redraw_line(ses, "");
+
+	modified_input();
+}
+
+DO_CURSOR(cursor_buffer_down)
+{
+	do_buffer(ses, "DOWN");
+}
+
+DO_CURSOR(cursor_buffer_end)
+{
+	do_buffer(ses, "END");
+}
+
+DO_CURSOR(cursor_buffer_home)
+{
+	do_buffer(ses, "HOME");
+}
+
+DO_CURSOR(cursor_buffer_lock)
+{
+	do_buffer(ses, "LOCK");
+}
+
+DO_CURSOR(cursor_buffer_up)
+{
+	do_buffer(ses, "UP");
+}
+
+
+DO_CURSOR(cursor_check_line)
+{
+	if (gtd->input_pos - gtd->input_hid > inputline_max_str_len() - 3)
+	{
+		return cursor_redraw_line(ses, "");
+	}
+
+	if (gtd->input_hid && gtd->input_pos - gtd->input_hid < 2)
+	{
+		return cursor_redraw_line(ses, "");
+	}
+}
+
+DO_CURSOR(cursor_check_line_modified)
+{
+	if (gtd->input_hid + inputline_max_str_len() < inputline_raw_str_len(0, gtd->input_len))
+	{
+		return cursor_redraw_line(ses, "");
+	}
+
+	return cursor_check_line(ses, "");
+}
+
+DO_CURSOR(cursor_clear_left)
+{
+	if (gtd->input_cur == 0)
+	{
+		return;
+	}
+
+	sprintf(gtd->paste_buf, "%.*s", gtd->input_cur, gtd->input_buf);
+
+	input_printf("\e[%dG\e[%dP", gtd->input_off, gtd->input_pos - gtd->input_hid);
+
+	memmove(&gtd->input_buf[0], &gtd->input_buf[gtd->input_cur], gtd->input_len - gtd->input_cur);
+
+	gtd->input_len -= gtd->input_cur;
+
+	gtd->input_buf[gtd->input_len] = 0;
+
+	gtd->input_cur  = 0;
+	gtd->input_pos  = 0;
+
+	cursor_check_line_modified(ses, "");
+
+	modified_input();
+}
+
+DO_CURSOR(cursor_clear_line)
+{
+	if (gtd->input_len == 0)
+	{
+		return;
+	}
+
+	sprintf(gtd->paste_buf, "%s", gtd->input_buf);
+
+	input_printf("\e[%dG\e[%dP", gtd->input_off, inputline_cur_str_len());
+
+	gtd->input_len = 0;
+	gtd->input_cur = 0;
+	gtd->input_hid = 0;
+	gtd->input_pos = 0;
+	gtd->input_buf[0] = 0;
+
+	modified_input();
+}
+
+DO_CURSOR(cursor_clear_right)
+{
+	if (gtd->input_cur == gtd->input_len)
+	{
+		return;
+	}
+
+	strcpy(gtd->paste_buf, &gtd->input_buf[gtd->input_cur]);
+
+	input_printf("\e[%dP", inputline_max_str_len() - inputline_str_str_len(gtd->input_hid, gtd->input_pos));
+
+	gtd->input_buf[gtd->input_cur] = 0;
+
+	gtd->input_len = gtd->input_cur;
+
+	modified_input();
+}
+
+DO_CURSOR(cursor_convert_meta)
+{
+	SET_BIT(gtd->flags, TINTIN_FLAG_CONVERTMETACHAR);
+}
+
+DO_CURSOR(cursor_delete_or_exit)
+{
+	if (gtd->input_len == 0)
+	{
+		cursor_exit(ses, "");
+	}
+	else
+	{
+		cursor_delete(ses, "");
+	}
+}
+
+DO_CURSOR(cursor_delete)
+{
+	int size, width;
+
+	if (gtd->input_len == 0)
+	{
+		return;
+	}
+
+	if (gtd->input_len == gtd->input_cur)
+	{
+		return;
+	}
+
+	if (HAS_BIT(ses->charset, CHARSET_FLAG_EUC) && is_euc_head(gtd->ses, &gtd->input_buf[gtd->input_cur]))
+	{
+		size = get_euc_width(gtd->ses, &gtd->input_buf[gtd->input_cur], &width);
+
+		gtd->input_len -= size;
+
+		memmove(&gtd->input_buf[gtd->input_cur], &gtd->input_buf[gtd->input_cur + size], gtd->input_len - gtd->input_cur + 1);
+
+		if (width)
+		{
+			input_printf("\e[%dP", width);
+		}
+	}
+	else if (HAS_BIT(ses->charset, CHARSET_FLAG_UTF8))
+	{
+		size = get_utf8_width(&gtd->input_buf[gtd->input_cur], &width);
+
+		gtd->input_len -= size;
+
+		memmove(&gtd->input_buf[gtd->input_cur], &gtd->input_buf[gtd->input_cur + size], gtd->input_len - gtd->input_cur + 1);
+
+		if (width)
+		{
+			input_printf("\e[%dP", width);
+		}
+
+		while (gtd->input_len > gtd->input_cur)
+		{
+			size = get_utf8_width(&gtd->input_buf[gtd->input_cur], &width);
+
+			if (width)
+			{
+				break;
+			}
+			gtd->input_len -= size;
+
+			memmove(&gtd->input_buf[gtd->input_cur], &gtd->input_buf[gtd->input_cur + size], gtd->input_len - gtd->input_cur + 1);
+		}
+	}
+	else
+	{
+		gtd->input_len--;
+
+		memmove(&gtd->input_buf[gtd->input_cur], &gtd->input_buf[gtd->input_cur+1], gtd->input_len - gtd->input_cur + 1);
+
+		input_printf("\e[1P");
+	}
+
+	if (gtd->input_hid + inputline_max_str_len() <= inputline_raw_str_len(0, gtd->input_len))
+	{
+		cursor_redraw_line(ses, "");
+	}
+
+	modified_input();
+}
+
+DO_CURSOR(cursor_delete_word_left)
+{
+	int index_cur, width;
+
+	if (gtd->input_cur == 0)
+	{
+		return;
+	}
+
+	index_cur = gtd->input_cur;
+
+	while (gtd->input_cur > 0 && gtd->input_buf[gtd->input_cur - 1] == ' ')
+	{
+		gtd->input_pos--;
+		gtd->input_cur--;
+	}
+
+	while (gtd->input_cur > 0 && gtd->input_buf[gtd->input_cur - 1] != ' ')
+	{
+		if (HAS_BIT(ses->charset, CHARSET_FLAG_UTF8))
+		{
+			if (is_utf8_tail(&gtd->input_buf[gtd->input_cur]))
+			{
+				gtd->input_cur--;
+			}
+			else if (is_utf8_head(&gtd->input_buf[gtd->input_cur]))
+			{
+				get_utf8_width(&gtd->input_buf[gtd->input_cur], &width);
+
+				gtd->input_cur -= 1;
+				gtd->input_pos -= width;
+			}
+			else
+			{
+				gtd->input_cur--;
+				gtd->input_pos--;
+			}
+		}
+		else
+		{
+			gtd->input_pos--;
+			gtd->input_cur--;
+		}
+	}
+
+	sprintf(gtd->paste_buf, "%.*s", index_cur - gtd->input_cur, &gtd->input_buf[gtd->input_cur]);
+
+	memmove(&gtd->input_buf[gtd->input_cur], &gtd->input_buf[index_cur], gtd->input_len - index_cur + 1);
+
+//	input_printf("\e[%dD\e[%dP", index_cur - gtd->input_cur, index_cur - gtd->input_cur);
+
+	gtd->input_len -= index_cur - gtd->input_cur;
+
+	cursor_redraw_line(ses, "");
+
+	modified_input();
+}
+
+
+DO_CURSOR(cursor_delete_word_right)
+{
+	int index_cur;
+
+	if (gtd->input_cur == gtd->input_len)
+	{
+		return;
+	}
+
+	index_cur = gtd->input_cur;
+
+	while (gtd->input_cur != gtd->input_len && gtd->input_buf[gtd->input_cur] == ' ')
+	{
+		gtd->input_cur++;
+	}
+
+	while (gtd->input_cur != gtd->input_len && gtd->input_buf[gtd->input_cur] != ' ')
+	{
+		gtd->input_cur++;
+	}
+
+	sprintf(gtd->paste_buf, "%.*s", gtd->input_cur - index_cur, &gtd->input_buf[gtd->input_cur]);
+
+	memmove(&gtd->input_buf[index_cur], &gtd->input_buf[gtd->input_cur], gtd->input_len - gtd->input_cur + 1);
+
+//	input_printf("\e[%dP", gtd->input_cur - index_cur);
+
+	gtd->input_len -= gtd->input_cur - index_cur;
+
+	gtd->input_cur = index_cur;
+
+	cursor_redraw_line(ses, "");
+
+	modified_input();
+}
+
+DO_CURSOR(cursor_echo)
+{
+	char arg1[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		TOG_BIT(ses->telopts, TELOPT_FLAG_ECHO);
+	}
+	else if (!strcasecmp(arg1, "ON"))
+	{
+		SET_BIT(ses->telopts, TELOPT_FLAG_ECHO);
+	}
+	else if (!strcasecmp(arg1, "OFF"))
+	{
+		DEL_BIT(ses->telopts, TELOPT_FLAG_ECHO);
+	}
+	else
+	{
+		show_error(gtd->ses, LIST_COMMAND, "#SYNTAX: #CURSOR {ECHO} {ON|OFF}.");
+	}
+}
+
+DO_CURSOR(cursor_end)
+{
+	gtd->input_cur = gtd->input_len;
+
+	gtd->input_pos = inputline_raw_str_len(0, gtd->input_len);
+
+	cursor_redraw_line(ses, "");
+}
+
+DO_CURSOR(cursor_enter)
+{
+	input_printf("\n");
+
+	gtd->input_buf[gtd->input_len] = 0;
+
+	gtd->input_len    = 0;
+	gtd->input_cur    = 0;
+	gtd->input_pos    = 0;
+	gtd->input_off    = 1;
+	gtd->input_hid    = 0;
+	gtd->macro_buf[0] = 0;
+	gtd->input_tmp[0] = 0;
+
+	if (HAS_BIT(gtd->flags, TINTIN_FLAG_HISTORYSEARCH))
+	{
+		struct listroot *root = ses->list[LIST_HISTORY];
+
+		if (root->update >= 0 && root->update < root->used)
+		{
+			strcpy(gtd->input_buf, root->list[root->update]->arg1);
+		}
+
+		DEL_BIT(gtd->flags, TINTIN_FLAG_HISTORYSEARCH);
+
+		gtd->input_off = 1;
+	}
+
+	SET_BIT(gtd->flags, TINTIN_FLAG_PROCESSINPUT);
+
+	modified_input();
+}
+
+DO_CURSOR(cursor_exit)
+{
+	if (ses == gts)
+	{
+		do_end(ses, "");
+	}
+	else
+	{
+		cleanup_session(ses);
+	}
+}
+
+DO_CURSOR(cursor_get)
+{
+	char arg1[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #CURSOR GET {variable}");
+	}
+	else
+	{
+		set_nest_node_ses(ses, arg1, "%s", gtd->input_buf);
+	}
+}
+
+DO_CURSOR(cursor_history_next)
+{
+	struct listroot *root = ses->list[LIST_HISTORY];
+
+	if (HAS_BIT(gtd->flags, TINTIN_FLAG_HISTORYSEARCH))
+	{
+		if (root->update == root->used)
+		{
+			return;
+		}
+
+		for (root->update++ ; root->update < root->used ; root->update++)
+		{
+			if (*gtd->input_buf && find(ses, root->list[root->update]->arg1, gtd->input_buf, SUB_NONE, REGEX_FLAG_NONE))
+			{
+				break;
+			}
+		}
+
+		if (root->update < root->used)
+		{
+			input_printf("\e[%dG  \e[0K%.*s\e[%dG", gtd->input_off + inputline_cur_str_len() + 2, gtd->input_off + inputline_max_str_len() - inputline_cur_str_len() - 4, root->list[root->update]->arg1, gtd->input_off + gtd->input_pos - gtd->input_hid);
+		}
+		return;
+	}
+
+	if (root->list[0] == NULL)
+	{
+		return;
+	}
+
+	if (!HAS_BIT(gtd->flags, TINTIN_FLAG_HISTORYBROWSE))
+	{
+		return;
+	}
+	else if (root->update < root->used)
+	{
+		for (root->update++ ; root->update < root->used ; root->update++)
+		{
+			if (!strncmp(gtd->input_tmp, root->list[root->update]->arg1, strlen(gtd->input_tmp)))
+			{
+				break;
+			}
+		}
+	}
+
+	cursor_clear_line(ses, "");
+
+	if (root->update == root->used)
+	{
+		strcpy(gtd->input_buf, gtd->input_tmp);
+	}
+	else
+	{
+		strcpy(gtd->input_buf, root->list[root->update]->arg1);
+	}
+
+	gtd->input_len = strlen(gtd->input_buf);
+
+	cursor_end(ses, "");
+
+	SET_BIT(gtd->flags, TINTIN_FLAG_HISTORYBROWSE);
+}
+
+DO_CURSOR(cursor_history_prev)
+{
+	struct listroot *root = ses->list[LIST_HISTORY];
+
+	if (HAS_BIT(gtd->flags, TINTIN_FLAG_HISTORYSEARCH))
+	{
+		if (root->update <= 0)
+		{
+			return;
+		}
+
+		for (root->update-- ; root->update >= 0 ; root->update--)
+		{
+			if (*gtd->input_buf && find(ses, root->list[root->update]->arg1, gtd->input_buf, SUB_NONE, REGEX_FLAG_NONE))
+			{
+				break;
+			}
+		}
+
+		if (root->update >= 0)
+		{
+			input_printf("\e[%dG  \e[0K%.*s\e[%dG", gtd->input_off + inputline_cur_str_len() + 2, gtd->input_off + inputline_max_str_len() - inputline_cur_str_len() - 4, root->list[root->update]->arg1, gtd->input_off + gtd->input_pos - gtd->input_hid);
+		}
+		return;
+	}
+
+	if (root->list[0] == NULL)
+	{
+		return;
+	}
+
+	if (!HAS_BIT(gtd->flags, TINTIN_FLAG_HISTORYBROWSE))
+	{
+		strcpy(gtd->input_tmp, gtd->input_buf);
+
+		for (root->update = root->used - 1 ; root->update >= 0 ; root->update--)
+		{
+			if (!strncmp(gtd->input_tmp, root->list[root->update]->arg1, strlen(gtd->input_tmp)))
+			{
+				break;
+			}
+		}
+	}
+	else if (root->update >= 0)
+	{
+		for (root->update-- ; root->update >= 0 ; root->update--)
+		{
+			if (!strncmp(gtd->input_tmp, root->list[root->update]->arg1, strlen(gtd->input_tmp)))
+			{
+				break;
+			}
+		}
+	}
+
+	cursor_clear_line(ses, "");
+
+	if (root->update == -1)
+	{
+		strcpy(gtd->input_buf, gtd->input_tmp);
+	}
+	else
+	{
+		strcpy(gtd->input_buf, root->list[root->update]->arg1);
+	}
+
+	gtd->input_len = strlen(gtd->input_buf);
+
+	cursor_end(ses, "");
+
+	SET_BIT(gtd->flags, TINTIN_FLAG_HISTORYBROWSE);
+}
+
+DO_CURSOR(cursor_history_search)
+{
+	struct listroot *root = ses->list[LIST_HISTORY];
+
+	if (root->list[0] == NULL)
+	{
+		return;
+	}
+
+	if (!HAS_BIT(gtd->flags, TINTIN_FLAG_HISTORYSEARCH))
+	{
+		strcpy(gtd->input_tmp, gtd->input_buf);
+
+		cursor_clear_line(ses, "");
+
+		SET_BIT(gtd->flags, TINTIN_FLAG_HISTORYSEARCH);
+
+		gtd->input_off = 11;
+
+		root->update = root->used - 1;
+
+		input_printf("(search) [ ] \e[3D");
+	}
+	else
+	{
+		if (root->update >= 0 && root->update < root->used)
+		{
+			strcpy(gtd->input_buf, root->list[root->update]->arg1);
+		}
+		input_printf("\e[1G\e[0K");
+
+		gtd->input_len = strlen(gtd->input_buf);
+		gtd->input_cur = gtd->input_len;
+		gtd->input_pos = 0;
+
+		root->update = -1;
+
+		DEL_BIT(gtd->flags, TINTIN_FLAG_HISTORYSEARCH);
+
+		gtd->input_off = 1;
+
+		cursor_redraw_line(ses, "");
+	}
+}
+
+DO_CURSOR(cursor_history_find)
+{
+	struct listroot *root = ses->list[LIST_HISTORY];
+
+	push_call("cursor_history_find(%s)", gtd->input_buf);
+
+	if (HAS_BIT(ses->charset, CHARSET_FLAG_UTF8|CHARSET_FLAG_EUC))
+	{
+		if (inputline_str_chk(0, gtd->input_len) == FALSE)
+		{
+			pop_call();
+			return;
+		}
+	}
+
+	gtd->level->quiet++;
+
+	for (root->update = root->used - 1 ; root->update >= 0 ; root->update--)
+	{
+		if (*gtd->input_buf && find(ses, root->list[root->update]->arg1, gtd->input_buf, SUB_NONE, REGEX_FLAG_NONE))
+		{
+			break;
+		}
+	}
+
+	gtd->level->quiet--;
+
+	if (root->update >= 0)
+	{
+		input_printf("\e[%dG ]  %.*s\e[%dG", gtd->input_off + inputline_cur_str_len(), gtd->input_off + inputline_max_str_len() - inputline_cur_str_len() - 4, root->list[root->update]->arg1, gtd->input_off + gtd->input_pos - gtd->input_hid);
+	}
+	else
+	{
+		input_printf("\e[%dG ] \e[0K\e[%dG", gtd->input_off + inputline_cur_str_len(), gtd->input_off + gtd->input_pos - gtd->input_hid);
+	}
+	pop_call();
+	return;
+}
+
+DO_CURSOR(cursor_home)
+{
+	if (gtd->input_cur == 0)
+	{
+		return;
+	}
+
+	input_printf("\e[%dD", gtd->input_pos - gtd->input_hid);
+
+	gtd->input_cur = 0;
+	gtd->input_pos = 0;
+
+	if (gtd->input_hid)
+	{
+		gtd->input_hid = 0;
+
+		cursor_redraw_line(ses, "");
+	}
+}
+
+DO_CURSOR(cursor_insert)
+{
+	char arg1[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		TOG_BIT(gtd->flags, TINTIN_FLAG_INSERTINPUT);
+	}
+	else if (!strcasecmp(arg1, "ON"))
+	{
+		SET_BIT(gtd->flags, TINTIN_FLAG_INSERTINPUT);
+	}
+	else if (!strcasecmp(arg1, "OFF"))
+	{
+		DEL_BIT(gtd->flags, TINTIN_FLAG_INSERTINPUT);
+	}
+	else
+	{
+		show_error(gtd->ses, LIST_COMMAND, "#SYNTAX: #CURSOR {INSERT} {ON|OFF}.");
+	}
+}
+
+
+DO_CURSOR(cursor_left)
+{
+	int width;
+
+	if (gtd->input_cur > 0)
+	{
+		if (HAS_BIT(ses->charset, CHARSET_FLAG_EUC))
+		{
+			gtd->input_cur--;
+			gtd->input_pos--;
+			input_printf("\e[1D");
+
+			if (inputline_str_chk(0, gtd->input_cur) == FALSE)
+			{
+				gtd->input_cur--;
+				gtd->input_pos--;
+				input_printf("\e[1D");
+			}
+		}
+		else if (HAS_BIT(ses->charset, CHARSET_FLAG_UTF8))
+		{
+			gtd->input_cur--;
+
+			if (gtd->input_cur > 0 && is_utf8_tail(&gtd->input_buf[gtd->input_cur]))
+			{
+				do
+				{
+					gtd->input_cur--;
+				}
+				while (gtd->input_cur > 0 && is_utf8_tail(&gtd->input_buf[gtd->input_cur]));
+
+				get_utf8_width(&gtd->input_buf[gtd->input_cur], &width);
+
+				if (width == 0)
+				{
+					return cursor_left(ses, "");
+				}
+				input_printf("\e[%dD", width);
+				gtd->input_pos -= width;
+			}
+			else
+			{
+				gtd->input_pos--;
+				input_printf("\e[1D");
+			}
+
+		}
+		else
+		{
+			gtd->input_cur--;
+			gtd->input_pos--;
+			input_printf("\e[1D");
+		}
+		cursor_check_line(ses, "");
+	}
+}
+
+DO_CURSOR(cursor_left_word)
+{
+	int width;
+//	int index_pos;
+
+	if (gtd->input_cur == 0)
+	{
+		return;
+	}
+
+//	index_pos = gtd->input_pos;
+
+	while (gtd->input_cur > 0 && gtd->input_buf[gtd->input_cur - 1] == ' ')
+	{
+		gtd->input_pos--;
+		gtd->input_cur--;
+	}
+
+	while (gtd->input_cur > 0 && gtd->input_buf[gtd->input_cur - 1] != ' ')
+	{
+		gtd->input_cur--;
+
+		if (HAS_BIT(ses->charset, CHARSET_FLAG_UTF8))
+		{
+			if (!is_utf8_tail(&gtd->input_buf[gtd->input_cur]))
+			{
+				get_utf8_width(&gtd->input_buf[gtd->input_cur], &width);
+
+				gtd->input_pos -= width;
+			}
+		}
+		else
+		{
+			gtd->input_pos--;
+		}
+	}
+
+//	input_printf("\e[%dD", index_pos - gtd->input_pos);
+
+	cursor_redraw_line(ses, "");
+}
+
+
+DO_CURSOR(cursor_paste_buffer)
+{
+	if (*gtd->paste_buf == 0)
+	{
+		return;
+	}
+
+	ins_sprintf(&gtd->input_buf[gtd->input_cur], "%s", gtd->paste_buf);
+
+	gtd->input_len += strlen(gtd->paste_buf);
+	gtd->input_cur += strlen(gtd->paste_buf);
+
+	gtd->input_pos += inputline_raw_str_len(gtd->input_cur - strlen(gtd->paste_buf), gtd->input_cur);
+
+	cursor_redraw_line(ses, "");
+
+	modified_input();
+}
+
+
+DO_CURSOR(cursor_redraw_input)
+{
+	if (IS_SPLIT(ses))
+	{
+		cursor_redraw_line(ses, "");
+	}
+	else
+	{
+/*		if (*gtd->ses->scroll->input)
+		{
+			input_printf("\e[G%s", gtd->ses->scroll->input);
+		}*/
+		cursor_redraw_line(ses, "");
+/*
+		gtd->input_cur = gtd->input_len;
+
+		gtd->input_pos = gtd->input_len % gtd->screen->cols;
+*/
+	}
+}
+
+
+DO_CURSOR(cursor_redraw_line)
+{
+	if (HAS_BIT(ses->charset, CHARSET_FLAG_UTF8|CHARSET_FLAG_EUC))
+	{
+		if (inputline_str_chk(0, gtd->input_len) == FALSE)
+		{
+			return;
+		}
+	}
+
+	// Erase current input
+
+	input_printf("\e[%dG\e[%dP", gtd->input_off, inputline_max_str_len());
+
+	// Center long lines of input
+
+	if (gtd->input_pos > inputline_max_str_len() - 3)
+	{
+		while (gtd->input_pos - gtd->input_hid > inputline_max_str_len() - 3)
+		{
+			gtd->input_hid += inputline_max_str_len() / 2;
+		}
+
+		while (gtd->input_pos - gtd->input_hid < 2)
+		{
+			gtd->input_hid -= inputline_max_str_len() / 2;
+		}
+	}
+	else
+	{
+		if (gtd->input_hid && gtd->input_pos - gtd->input_hid < 2)
+		{
+			gtd->input_hid = 0;
+		}
+	}
+
+	// Print the entire thing
+
+	if (gtd->input_hid)
+	{
+		if (gtd->input_hid + inputline_max_str_len() >= inputline_raw_str_len(0, gtd->input_len))
+		{
+			input_printf("<%.*s\e[%dG",  inputline_str_raw_len(gtd->input_hid + 1, gtd->input_hid + inputline_max_str_len() - 0), &gtd->input_buf[inputline_str_raw_len(0, gtd->input_hid + 1)], gtd->input_off + gtd->input_pos - gtd->input_hid);
+		}
+		else
+		{
+			input_printf("<%.*s>\e[%dG", inputline_str_raw_len(gtd->input_hid + 1, gtd->input_hid + inputline_max_str_len() - 1), &gtd->input_buf[inputline_str_raw_len(0, gtd->input_hid + 1)], gtd->input_off + gtd->input_pos - gtd->input_hid);
+		}
+	}
+	else
+	{
+		if (gtd->input_hid + inputline_max_str_len() >= inputline_raw_str_len(0, gtd->input_len))
+		{
+			input_printf("%.*s\e[%dG",   inputline_str_raw_len(gtd->input_hid + 0, gtd->input_hid + inputline_max_str_len() - 0), &gtd->input_buf[inputline_str_raw_len(0, gtd->input_hid + 0)], gtd->input_off + gtd->input_pos - gtd->input_hid);
+		}
+		else
+		{
+			input_printf("%.*s>\e[%dG",  inputline_str_raw_len(gtd->input_hid + 0, gtd->input_hid + inputline_max_str_len() - 1), &gtd->input_buf[inputline_str_raw_len(0, gtd->input_hid + 0)], gtd->input_off + gtd->input_pos - gtd->input_hid);
+		}
+	}
+}
+
+DO_CURSOR(cursor_right)
+{
+	int size, width;
+
+	if (gtd->input_cur < gtd->input_len)
+	{
+		if (HAS_BIT(ses->charset, CHARSET_FLAG_EUC))
+		{
+			gtd->input_cur += get_euc_width(gtd->ses, &gtd->input_buf[gtd->input_cur], &width);
+
+			input_printf("\e[%dC", width);
+			gtd->input_pos += width;
+		}
+		else if (HAS_BIT(ses->charset, CHARSET_FLAG_UTF8))
+		{
+			gtd->input_cur += get_utf8_width(&gtd->input_buf[gtd->input_cur], &width);
+
+			if (width == 0)
+			{
+				return cursor_right(ses, "");
+			}
+			input_printf("\e[%dC", width);
+			gtd->input_pos += width;
+
+			while (gtd->input_cur < gtd->input_len)
+			{
+				size = get_utf8_width(&gtd->input_buf[gtd->input_cur], &width);
+
+				if (width)
+				{
+					break;
+				}
+				gtd->input_cur += size;
+			}
+		}
+		else
+		{
+			input_printf("\e[1C");
+			gtd->input_cur++;
+			gtd->input_pos++;
+		}
+	}
+
+	cursor_check_line(ses, "");
+}
+
+DO_CURSOR(cursor_right_word)
+{
+	if (gtd->input_cur == gtd->input_len)
+	{
+		return;
+	}
+
+	while (gtd->input_cur < gtd->input_len && gtd->input_buf[gtd->input_cur] == ' ')
+	{
+		gtd->input_cur++;
+		gtd->input_pos++;
+	}
+
+	while (gtd->input_cur < gtd->input_len && gtd->input_buf[gtd->input_cur] != ' ')
+	{
+		if (!HAS_BIT(ses->charset, CHARSET_FLAG_UTF8) || (gtd->input_buf[gtd->input_cur] & 192) != 128)
+		{
+			gtd->input_pos++;
+		}
+		gtd->input_cur++;
+	}
+
+	cursor_redraw_line(ses, "");
+}
+
+DO_CURSOR(cursor_set)
+{
+	char arg1[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		return;
+	}
+
+	ins_sprintf(&gtd->input_buf[gtd->input_cur], "%s", arg1);
+
+	gtd->input_len += strlen(arg1);
+	gtd->input_cur += strlen(arg1);
+
+	gtd->input_pos += inputline_raw_str_len(gtd->input_cur - strlen(arg1), gtd->input_cur);
+
+	cursor_redraw_line(ses, "");
+
+	modified_input();
+}
+
+DO_CURSOR(cursor_suspend)
+{
+	do_suspend(ses, "");
+}
+
+DO_CURSOR(cursor_info)
+{
+	tintin_printf2(ses, "Width of input bar:             %10d", inputline_max_str_len());
+	tintin_printf2(ses, "Offset of input bar:            %10d", gtd->input_off);
+	tintin_printf2(ses, "Width of hidden text on left:   %10d", gtd->input_hid);
+	tintin_printf2(ses, "VT100 position of cursor:       %10d", gtd->input_pos);
+	tintin_printf2(ses, "internal position of cursor:    %10d", gtd->input_cur);
+	tintin_printf2(ses, "internal length of input line:  %10d", gtd->input_len);
+	tintin_printf2(ses, "VT100 length of displayed line: %10d", inputline_cur_str_len());
+}
+
+/*
+	Improved tab handling by Ben Love
+*/
+
+int cursor_tab_add(int input_now, int stop_after_first)
+{
+	struct listroot *root = gtd->ses->list[LIST_TAB];
+	struct listnode *node;
+
+	char tab[BUFFER_SIZE];
+
+	if (!HAS_BIT(root->flags, LIST_FLAG_IGNORE))
+	{
+		for (root->update = 0 ; root->update < root->used ; root->update++)
+		{
+			node = root->list[root->update];
+
+			substitute(gtd->ses, node->arg1, tab, SUB_VAR|SUB_FUN);
+
+			if (!strncmp(tab, &gtd->input_buf[input_now], strlen(&gtd->input_buf[input_now])))
+			{
+				if (search_node_list(gtd->ses->list[LIST_COMMAND], tab))
+				{
+					continue;
+				}
+				insert_node_list(gtd->ses->list[LIST_COMMAND], tab, "", "", "");
+
+				if (HAS_BIT(node->flags, NODE_FLAG_ONESHOT))
+				{
+					delete_node_list(gtd->ses, LIST_TAB, node);
+				}
+
+				if (stop_after_first)
+				{
+					return TRUE;
+				}
+			}
+		}
+	}
+	return FALSE;
+}
+
+
+int cursor_auto_tab_add(int input_now, int stop_after_first)
+{
+	char tab[BUFFER_SIZE], buf[BUFFER_SIZE];
+	int scroll_cnt, line_cnt, tab_len;
+	char *arg;
+
+	line_cnt = 0;
+
+	for (scroll_cnt = gtd->ses->scroll->used - 1 ; scroll_cnt > 0 ; scroll_cnt--)
+	{
+		if (HAS_BIT(gtd->ses->scroll->buffer[scroll_cnt]->flags, BUFFER_FLAG_GREP))
+		{
+			continue;
+		}
+
+		if (line_cnt++ >= gtd->ses->auto_tab)
+		{
+			break;
+		}
+
+		strip_vt102_codes(gtd->ses->scroll->buffer[scroll_cnt]->str, buf);
+
+		arg = buf;
+
+		while (*arg)
+		{
+			arg = get_arg_in_braces(gtd->ses, arg, tab, GET_ONE);
+
+			if (!strncmp(tab, &gtd->input_buf[input_now], strlen(&gtd->input_buf[input_now])))
+			{
+				tab_len = strlen(tab) -1;
+
+				if (tab_len > 0)
+				{
+					if (tab[tab_len] == '.' || tab[tab_len] == ',' || tab[tab_len] == ';')
+					{
+						tab[tab_len] = 0;
+					}
+				}
+
+				if (search_node_list(gtd->ses->list[LIST_COMMAND], tab))
+				{
+					continue;
+				}
+				insert_node_list(gtd->ses->list[LIST_COMMAND], tab, "", "", "");
+
+				if (stop_after_first)
+				{
+					return TRUE;
+				}
+			}
+
+			if (*arg == COMMAND_SEPARATOR)
+			{
+				arg++;
+			}
+
+		}
+	}
+
+	return FALSE;
+}
+
+void cursor_hide_completion(int input_now)
+{
+	struct listroot *root = gtd->ses->list[LIST_COMMAND];
+	struct listnode *f_node;
+	struct listnode *l_node;
+	int len_change;
+
+	f_node = root->list[0];
+	l_node = root->list[root->used - 1];
+
+	if (root->used && !strcmp(l_node->arg1, gtd->input_buf + input_now))
+	{
+		len_change = strlen(l_node->arg1) - strlen(f_node->arg1);
+
+		if (len_change > 0)
+		{
+/*
+			if (gtd->input_cur < gtd->input_len)
+			{
+				input_printf("\e[%dC", gtd->input_len - gtd->input_cur);
+			}
+			input_printf("\e[%dD\e[%dP", len_change, len_change);
+*/
+			gtd->input_len = gtd->input_len - len_change;
+			gtd->input_buf[gtd->input_len] = 0;
+			gtd->input_cur = gtd->input_len;
+			gtd->input_pos = gtd->input_pos;
+		}
+	}
+	return;
+}
+
+void cursor_show_completion(int input_now, int show_last_node)
+{
+	struct listroot *root = gtd->ses->list[LIST_COMMAND];
+	struct listnode *node;
+
+	if (!root->used)
+	{
+		return;
+	}
+
+	node = show_last_node ? root->list[root->used - 1] : root->list[0];
+/*
+	if (gtd->input_cur < gtd->input_len)
+	{
+		input_printf("\e[%dC", gtd->input_len - gtd->input_cur);
+	}
+	if (gtd->input_len > input_now)
+	{
+		input_printf("\e[%dD\e[%dP", gtd->input_len - input_now, gtd->input_len - input_now);
+	}
+	if (input_now + (int) strlen(node->arg1) < gtd->ses->cols - 2)
+	{
+		input_printf("%s", node->arg1);
+	}
+*/
+	strcpy(&gtd->input_buf[input_now], node->arg1);
+
+	gtd->input_len = input_now + strlen(node->arg1);
+	gtd->input_cur = gtd->input_len;
+
+	cursor_end(gtd->ses, "");
+
+	if (HAS_BIT(gtd->flags, TINTIN_FLAG_HISTORYSEARCH))
+	{
+		cursor_history_find(gtd->ses, "");
+	}
+
+	if (node == root->list[0])
+	{
+		kill_list(root);
+	}
+
+	return;
+}
+
+int cursor_calc_input_now(void)
+{
+	int input_now;
+
+	if (gtd->input_len == 0 || gtd->input_buf[gtd->input_len - 1] == ' ')
+	{
+		return -1;
+	}
+
+	for (input_now = gtd->input_len - 1 ; input_now ; input_now--)
+	{
+		if (gtd->input_buf[input_now] == ' ')
+		{
+			return input_now + 1;
+		}
+	}
+
+	return input_now;
+}
+
+DO_CURSOR(cursor_tab)
+{
+	char arg1[BUFFER_SIZE];
+	int flags;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+
+	while (*arg)
+	{
+		if (is_abbrev(arg1, "LIST"))
+		{
+			SET_BIT(flags, TAB_FLAG_LIST);
+		}
+		else if (is_abbrev(arg1, "SCROLLBACK"))
+		{
+			SET_BIT(flags, TAB_FLAG_SCROLLBACK);
+		}
+
+		arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+
+		if (*arg == COMMAND_SEPARATOR)
+		{
+			arg++;
+		}
+	}
+
+	if (is_abbrev(arg1, "FORWARD"))
+	{
+		if (!HAS_BIT(flags, TAB_FLAG_LIST|TAB_FLAG_SCROLLBACK))
+		{
+			show_error(ses, LIST_COMMAND, "#SYNTAX: #CURSOR TAB <LIST|SCROLLBACK> FORWARD");
+		}
+		else
+		{
+			if (HAS_BIT(flags, TAB_FLAG_LIST|TAB_FLAG_SCROLLBACK) == (TAB_FLAG_LIST|TAB_FLAG_SCROLLBACK))
+			{
+				cursor_mixed_tab_forward(ses, "");
+			}
+			else if (HAS_BIT(flags, TAB_FLAG_LIST))
+			{
+				cursor_tab_forward(ses, "");
+			}
+			else
+			{
+				cursor_auto_tab_forward(ses, "");
+			}
+		}
+		SET_BIT(flags, TAB_FLAG_FORWARD);
+	}
+	else if (is_abbrev(arg1, "BACKWARD"))
+	{
+		if (!HAS_BIT(flags, TAB_FLAG_LIST|TAB_FLAG_SCROLLBACK))
+		{
+			show_error(ses, LIST_COMMAND, "#SYNTAX: #CURSOR TAB <LIST|SCROLLBACK> BACKWARD");
+		}
+		else
+		{
+			if (HAS_BIT(flags, TAB_FLAG_LIST|TAB_FLAG_SCROLLBACK) == (TAB_FLAG_LIST|TAB_FLAG_SCROLLBACK))
+			{
+				cursor_mixed_tab_backward(ses, "");
+			}
+			else if (HAS_BIT(flags, TAB_FLAG_LIST))
+			{
+				cursor_tab_backward(ses, "");
+			}
+			else
+			{
+				cursor_auto_tab_backward(ses, "");
+			}
+		}
+		SET_BIT(flags, TAB_FLAG_BACKWARD);
+	}
+	else
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #CURSOR TAB <LIST|SCROLLBACK> <BACKWARD|FORWARD>");
+	}
+}
+
+DO_CURSOR(cursor_tab_forward)
+{
+	struct listroot *root = ses->list[LIST_COMMAND];
+	int tab_found;
+
+	if (!root->list[0])
+	{
+		gtd->input_tab = cursor_calc_input_now();
+	}
+
+	if (gtd->input_tab == -1)
+	{
+		return;
+	}
+
+	cursor_hide_completion(gtd->input_tab);
+
+	if (!root->list[0])
+	{
+		insert_node_list(root, &gtd->input_buf[gtd->input_tab], "", "", "");
+	}
+	tab_found = cursor_tab_add(gtd->input_tab, TRUE);
+
+	cursor_show_completion(gtd->input_tab, tab_found);
+}
+
+DO_CURSOR(cursor_tab_backward)
+{
+	struct listroot *root = ses->list[LIST_COMMAND];
+
+	if (!root->list[0])
+	{
+		gtd->input_tab = cursor_calc_input_now();
+	}
+
+	if (gtd->input_tab == -1)
+	{
+		return;
+	}
+
+	cursor_hide_completion(gtd->input_tab);
+
+	if (root->used)
+	{
+		delete_index_list(root, root->used - 1);
+	}
+
+	if (!root->list[0])
+	{
+		insert_node_list(root, &gtd->input_buf[gtd->input_tab], "", "", "");
+
+		cursor_tab_add(gtd->input_tab, FALSE);
+	}
+	cursor_show_completion(gtd->input_tab, TRUE);
+}
+
+DO_CURSOR(cursor_auto_tab_forward)
+{
+	struct listroot *root = ses->list[LIST_COMMAND];
+	int tab_found;
+
+	if (!root->list[0])
+	{
+		gtd->input_tab = cursor_calc_input_now();
+	}
+
+	if (gtd->input_tab == -1)
+	{
+		return;
+	}
+
+	cursor_hide_completion(gtd->input_tab);
+
+	if (!root->list[0])
+	{
+		insert_node_list(root, &gtd->input_buf[gtd->input_tab], "", "", "");
+	}
+
+	tab_found = cursor_auto_tab_add(gtd->input_tab, TRUE);
+
+	cursor_show_completion(gtd->input_tab, tab_found);
+}
+
+DO_CURSOR(cursor_auto_tab_backward)
+{
+	struct listroot *root = ses->list[LIST_COMMAND];
+
+	if (!root->list[0])
+	{
+		gtd->input_tab = cursor_calc_input_now();
+	}
+
+	if (gtd->input_tab == -1)
+	{
+		return;
+	}
+
+	cursor_hide_completion(gtd->input_tab);
+
+	if (root->used)
+	{
+		delete_index_list(root, root->used - 1);
+	}
+
+	if (!root->list[0])
+	{
+		insert_node_list(root, &gtd->input_buf[gtd->input_tab], "", "", "");
+
+		cursor_auto_tab_add(gtd->input_tab, FALSE);
+	}
+
+	cursor_show_completion(gtd->input_tab, TRUE);
+}
+
+
+DO_CURSOR(cursor_mixed_tab_forward)
+{
+	struct listroot *root = ses->list[LIST_COMMAND];
+	int tab_found;
+
+	if (!root->list[0])
+	{
+		gtd->input_tab = cursor_calc_input_now();
+	}
+
+	if (gtd->input_tab == -1)
+	{
+		return;
+	}
+
+	cursor_hide_completion(gtd->input_tab);
+
+	if (!root->list[0])
+	{
+		insert_node_list(root, &gtd->input_buf[gtd->input_tab], "", "", "");
+	}
+	tab_found = cursor_tab_add(gtd->input_tab, TRUE) || cursor_auto_tab_add(gtd->input_tab, TRUE);
+
+	cursor_show_completion(gtd->input_tab, tab_found);
+}
+
+DO_CURSOR(cursor_mixed_tab_backward)
+{
+	struct listroot *root = ses->list[LIST_COMMAND];
+
+	if (!root->list[0])
+	{
+		gtd->input_tab = cursor_calc_input_now();
+	}
+
+	if (gtd->input_tab == -1)
+	{
+		return;
+	}
+
+	cursor_hide_completion(gtd->input_tab);
+
+	if (root->used)
+	{
+		delete_index_list(root, root->used - 1);
+	}
+
+	if (!root->list[0])
+	{
+		insert_node_list(root, &gtd->input_buf[gtd->input_tab], "", "", "");
+
+		cursor_tab_add(gtd->input_tab, FALSE);
+		cursor_auto_tab_add(gtd->input_tab, FALSE);
+	}
+
+	cursor_show_completion(gtd->input_tab, TRUE);
+}
+
+DO_CURSOR(cursor_screen_focus_in)
+{
+	gtd->screen->focus = 1;
+
+	check_all_events(gtd->ses, SUB_ARG, 0, 1, "SCREEN FOCUS", ntos(gtd->screen->focus));
+
+	msdp_update_all("SCREEN_FOCUS", "%d", gtd->screen->focus);
+}
+
+DO_CURSOR(cursor_screen_focus_out)
+{
+	gtd->screen->focus = 0;
+
+	check_all_events(gtd->ses, SUB_ARG, 0, 1, "SCREEN FOCUS", ntos(gtd->screen->focus));
+
+	msdp_update_all("SCREEN_FOCUS", "%d", gtd->screen->focus);
+}

+ 672 - 0
daemon.c

@@ -0,0 +1,672 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2019                       *
+******************************************************************************/
+
+#include "tintin.h"
+
+#ifdef HAVE_PTY_H
+#include <pty.h>
+#else
+#ifdef HAVE_UTIL_H
+#include <util.h>
+#endif
+#endif
+#include <fcntl.h>  
+#include <dirent.h>
+#include <termios.h>
+#include <sys/un.h>
+
+int get_daemon_dir(struct session *ses, char *filename);
+
+DO_COMMAND(do_daemon)
+{
+	char arg1[BUFFER_SIZE];
+	int cnt;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		tintin_header(ses, " DAEMON OPTIONS ");
+
+		for (cnt = 0 ; *daemon_table[cnt].fun != NULL ; cnt++)
+		{
+			if (*daemon_table[cnt].desc)
+			{
+				tintin_printf2(ses, "  [%-13s] %s", daemon_table[cnt].name, daemon_table[cnt].desc);
+			}
+		}
+		tintin_header(ses, "");
+
+		return ses;
+	}
+	else
+	{
+		for (cnt = 0 ; *daemon_table[cnt].name ; cnt++)
+		{
+			if (is_abbrev(arg1, daemon_table[cnt].name))
+			{
+				break;
+			}
+		}
+
+		if (*daemon_table[cnt].name == 0)
+		{
+			do_daemon(ses, "");
+		}
+		else
+		{
+			daemon_table[cnt].fun(ses, arg);
+		}
+	}
+
+	return ses;
+}
+
+
+DO_DAEMON(daemon_attach)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], filename[BUFFER_SIZE], sock_file[BUFFER_SIZE];
+	struct dirent **dirlist;
+	struct sockaddr_un addr_un;
+	int size, index, pid, error, repeat = 0;
+	struct timeval timeout;
+	fd_set wds, rds;
+
+	timeout.tv_sec  = 0;
+	timeout.tv_usec = 100000;
+
+	if (gtd->attach_sock)
+	{
+		show_error(ses, LIST_COMMAND, "#DAEMON ATTACH: YOU ARE ALREADY ATTACHED TO {%s}.", gtd->attach_file);
+
+		return;
+	}
+	sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (!get_daemon_dir(ses, filename))
+	{
+		return;
+	}
+
+	start:
+
+	size = scandir(filename, &dirlist, 0, alphasort);
+
+	if (size == -1)
+	{
+		syserr_printf(ses, "do_attach: scandir:");
+
+		return;
+	}
+
+	for (*arg2 = index = pid = 0 ; index < size ; index++)
+	{
+		if (strlen(dirlist[index]->d_name) > 2)
+		{
+			if (*arg1)
+			{
+				if (!strstr(dirlist[index]->d_name, arg1))
+				{
+					continue;
+				}
+			}
+			arg = strchr(dirlist[index]->d_name, '.');
+
+			if (arg)
+			{
+				*arg = 0;
+
+				strcpy(arg2, dirlist[index]->d_name);
+
+				arg = strchr(dirlist[index]->d_name, '_');
+
+				if (arg)
+				{
+					pid = atoi(arg + 1);
+				}
+				break;
+			}
+		}
+	}
+
+	for (index = 0 ; index < size ; index++)
+	{
+		free(dirlist[index]);
+	}
+	free(dirlist);
+
+	if (pid == 0)
+	{
+		if (HAS_BIT(gtd->flags, TINTIN_FLAG_DAEMONIZE))
+		{
+			daemon_detach(ses, arg1);
+
+			return;
+		}
+
+		if (*arg1 && ++repeat < 10)
+		{
+			usleep(2000);
+
+			goto start;
+		}
+
+		if (*arg1)
+		{
+			show_message(ses, LIST_COMMAND, "#DAEMON ATTACH: UNABLE TO FIND DAEMON FILE {%s} IN {%s}.", arg1, filename);
+		}
+		else
+		{
+			show_message(ses, LIST_COMMAND, "#DAEMON ATTACH: NO AVAILABLE DAEMON FILES FOUND IN {%s}.", filename);
+		}
+
+		return;
+	}
+
+	sprintf(sock_file, "%s/%s.s", filename, arg2);
+
+	if (access(sock_file, F_OK) == -1)
+	{
+		show_error(ses, LIST_COMMAND, "#ERROR: DAEMON ATTACH: FILE {%s} CANNOT BE ACCESSED.", sock_file);
+
+		return;
+	}
+
+	if (kill((pid_t) pid, 0) == -1)
+	{
+		show_error(ses, LIST_COMMAND, "#ERROR: DAEMON ATTACH: REMOVING INVALID DAEMON FILE {%s}.", sock_file);
+
+		remove(sock_file);
+
+		if (HAS_BIT(gtd->flags, TINTIN_FLAG_DAEMONIZE) || *arg1 == 0)
+		{
+			goto start;
+		}
+		return;
+	}
+	DEL_BIT(gtd->flags, TINTIN_FLAG_DAEMONIZE);
+
+	memset(&addr_un, 0, sizeof(addr_un));
+
+	if (strlen(sock_file) >= sizeof(addr_un.sun_path))
+	{
+		show_error(ses, LIST_COMMAND, "#ERROR: #DAEMON ATTACH: {%s} FILENAME EXCEEDS MAXIMUM LENGTH OF %d.", filename, sizeof(addr_un.sun_path));
+
+		return;
+	}
+
+	if (pid == getpid())
+	{
+		show_error(ses, LIST_COMMAND, "#ERROR: #DAEMON ATTACH: {%s} CANNOT ATTACH TO ITSELF.", filename);
+		
+		return;
+	}
+
+	gtd->attach_file = restringf(gtd->attach_file, "%s", sock_file);
+	gtd->attach_pid  = pid;
+	gtd->attach_sock = socket(AF_UNIX, SOCK_STREAM, 0);
+
+	if (gtd->attach_sock == -1)
+	{
+		syserr_printf(ses, "do_attach: %s: socket:");
+
+		gtd->attach_sock = 0;
+
+		return;
+	}
+
+	strcpy(addr_un.sun_path, sock_file);
+	addr_un.sun_family = AF_UNIX;
+
+	show_message(ses, LIST_COMMAND, "#DAEMON ATTACH: CONNECTING {%d} TO {%d} {%s}", getpid(), gtd->attach_pid, sock_file);
+
+/*
+	error = select(gtd->attach_sock, NULL, &wds, NULL, &timeout);
+
+	if (error == -1)
+	{
+		syserr_printf(ses, "do_attach: %s: select:", sock_file);
+
+		return;
+	}
+*/
+	if (connect(gtd->attach_sock, (struct sockaddr *)&addr_un, sizeof(addr_un)) == -1)
+	{
+		syserr_printf(ses, "do_attach: %s: connect:", sock_file);
+
+		gtd->attach_sock = close(gtd->attach_sock);
+
+		return;
+	}
+
+
+	FD_ZERO(&wds);
+
+	FD_SET(gtd->attach_sock, &wds);
+
+	error = select(FD_SETSIZE, NULL, &wds, NULL, &timeout);
+
+	if (error < 0)
+	{
+		syserr_printf(ses, "do_attach: select wds:");
+
+		show_error(ses, LIST_COMMAND, "#ERROR: #DAEMON ATTACH: UNABLE TO WRITE TO {%s}.", sock_file);
+
+		gtd->attach_sock = close(gtd->attach_sock);
+
+		return;
+	}
+
+	if (!FD_ISSET(gtd->attach_sock, &wds))
+	{
+		show_error(ses, LIST_COMMAND, "#ERROR: #DAEMON ATTACH: UNABLE TO WRITE TO {%s}.", sock_file);
+
+		gtd->attach_sock = close(gtd->attach_sock);
+
+		return;
+	}
+
+	FD_ZERO(&rds);
+	FD_SET(gtd->attach_sock, &rds);
+
+	error = select(FD_SETSIZE, &rds, NULL, NULL, &timeout);
+
+	if (error < 0)
+	{
+		syserr_printf(ses, "do_attach: select rds:");
+
+		gtd->attach_sock = close(gtd->attach_sock);
+
+		return;
+	}
+
+	if (error == 0)
+	{
+		tintin_printf2(ses, "do_attach: select rds: timeout");
+
+		gtd->attach_sock = close(gtd->attach_sock);
+		
+		return;
+	}
+
+	return;
+}
+
+DO_DAEMON(daemon_detach)
+{
+	char arg1[BUFFER_SIZE], filename[BUFFER_SIZE];
+	struct sockaddr_un addr_un;
+	pid_t pid, sid;
+	int dev_null;
+
+	sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (gtd->detach_port)
+	{
+		if (gtd->detach_sock)
+		{
+			show_message(gtd->ses, LIST_COMMAND, "#DAEMON DETACH: DETACHING FROM {%s}", gtd->detach_file);
+
+//			kill((pid_t) gtd->detach_sock, SIGTSTP);
+
+//			print_stdout("%c", (char) 255);
+
+			gtd->detach_sock = close(gtd->detach_sock);
+		}
+		else
+		{
+			show_error(gtd->ses, LIST_COMMAND, "#DAEMON DETACH: ALREADY FULLY DETACHED.");
+		}
+		return;
+	}
+
+	if (!get_daemon_dir(ses, filename))
+	{
+		return;
+	}
+
+	pid = fork();
+
+	if (pid < 0)
+	{
+		syserr_printf(ses, "do_detatch: fork:");
+
+		return;
+	}
+
+	if (pid > 0)
+	{
+		if (HAS_BIT(gtd->flags, TINTIN_FLAG_DAEMONIZE))
+		{
+			DEL_BIT(gtd->flags, TINTIN_FLAG_DAEMONIZE);
+
+			usleep(2000);
+
+			daemon_attach(ses, *arg1 ? arg1 : "pid");
+
+			return;
+		}
+		reset_terminal(gtd->ses);
+
+		print_stdout("\e[r\e[%d;%dH", gtd->screen->rows, 1);
+
+		_exit(0);
+	}
+
+	DEL_BIT(gtd->flags, TINTIN_FLAG_DAEMONIZE);
+
+	sid = setsid();
+
+	if (sid < 0)
+	{
+		syserr_printf(ses, "do_detach: setsid:");
+		return;
+	}
+
+	if (!get_daemon_dir(ses, filename))
+	{
+		return;
+	}
+
+	memset(&addr_un, 0, sizeof(addr_un));
+
+	cat_sprintf(filename, "/%s_%d.s", *arg1 ? arg1 : "pid", getpid());
+
+	if (strlen(filename) >= sizeof(addr_un.sun_path))
+	{
+		tintin_printf(ses, "#DAEMON DETACH: FILE NAME LENGTH OF {%s} EXCEEDS MAXIMUM OF %d.", filename, sizeof(addr_un.sun_path));
+
+		return;
+	}
+
+	strcpy(addr_un.sun_path, filename);
+	addr_un.sun_family = AF_UNIX;
+
+	show_message(ses, LIST_COMMAND, "#DAEMON DETACH: DAEMONIZING PROCESS %d AS {%s}", getpid(), filename);
+
+	gtd->detach_port = socket(AF_UNIX, SOCK_STREAM, 0);
+
+	if (gtd->detach_port <= 0)
+	{
+		syserr_printf(ses, "do_detach: socket:");
+
+		return;
+	}
+
+	if (bind(gtd->detach_port, (struct sockaddr *) &addr_un, sizeof(struct sockaddr_un)) < 0)
+	{
+		syserr_printf(ses, "do_detach: bind:");
+
+		gtd->detach_port = close(gtd->detach_port);
+
+		return;
+	}
+
+	if (listen(gtd->detach_port, 32) < 0)
+	{
+		syserr_printf(ses, "do_detach: listen:");
+
+		gtd->detach_port = close(gtd->detach_port);
+
+		return;
+	}
+
+	dev_null = open("/dev/null", O_RDWR, 0);
+
+	if (dev_null == -1)
+	{
+		syserr_printf(ses, "daemon_detach: dev_null open:");
+
+		return;
+	}
+
+	if (dup2(dev_null, STDIN_FILENO) == -1)
+	{
+		syserr_printf(ses, "daemon_detach: dup2 STDIN-fileno:");
+	}
+
+//	dup2(dev_null, STDOUT_FILENO);
+//	dup2(dev_null, STDERR_FILENO);
+
+	close(dev_null);
+
+	gtd->detach_file = restringf(gtd->detach_file, "%s", filename);
+
+	return;
+}
+
+DO_DAEMON(daemon_input)
+{
+	char arg1[BUFFER_SIZE], out[BUFFER_SIZE];
+	int size;
+
+	if (*arg == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #DAEMON INPUT <TEXT>");
+
+		return;
+	}
+
+	if (gtd->attach_sock <= 0)
+	{
+		show_error(ses, LIST_COMMAND, "#DAEMON INPUT: YOU MUST BE ATTACHED TO A DAEMON TO SEND INPUT.");
+
+		return;
+	}
+
+	get_arg_in_braces(ses, arg, arg1, GET_ALL);
+
+	size = substitute(ses, arg1, out, SUB_VAR|SUB_FUN|SUB_COL|SUB_ESC|SUB_EOL);
+
+	if (write(gtd->attach_sock, out, size) < 0)
+	{
+		gtd->attach_sock = close(gtd->attach_sock);
+
+		show_message(gtd->ses, LIST_COMMAND, "#DAEMON INPUT: WRITE ERROR: UNATTACHING.");
+	}
+}
+
+DO_DAEMON(daemon_kill)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], filename[BUFFER_SIZE], sock_file[BUFFER_SIZE];
+	struct dirent **dirlist;
+	int size, index, pid;
+
+	sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (!get_daemon_dir(ses, filename))
+	{
+		return;
+	}
+
+	size = scandir(filename, &dirlist, 0, alphasort);
+
+	if (size == -1)
+	{
+		syserr_printf(ses, "do_attach: scandir:");
+
+		return;
+	}
+
+	for (*arg2 = index = pid = 0 ; index < size ; index++)
+	{
+		if (strlen(dirlist[index]->d_name) > 2)
+		{
+			tintin_printf2(ses, "#DAEMON KILL: CHECKING FILE {%s}", dirlist[index]->d_name);
+
+			if (*arg1)
+			{
+				if (!strstr(dirlist[index]->d_name, arg1))
+				{
+					continue;
+				}
+			}
+			arg = strchr(dirlist[index]->d_name, '.');
+
+			if (arg)
+			{
+				*arg = 0;
+
+				strcpy(arg2, dirlist[index]->d_name);
+
+				arg = strchr(dirlist[index]->d_name, '_');
+
+				if (arg)
+				{
+					pid = atoi(arg + 1);
+
+					sprintf(sock_file, "%s/%s.s", filename, arg2);
+
+					show_message(ses, LIST_COMMAND, "#DAEMON {%s} KILLED.", sock_file, pid);
+
+					kill((pid_t) pid, SIGKILL);
+
+					remove(sock_file);
+				}
+			}
+		}
+	}
+
+	for (index = 0 ; index < size ; index++)
+	{
+		free(dirlist[index]);
+	}
+	free(dirlist);
+
+	return;
+}
+
+DO_DAEMON(daemon_list)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], filename[BUFFER_SIZE], sock_file[BUFFER_SIZE];
+	struct dirent **dirlist;
+	int size, index, pid;
+
+	sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (!get_daemon_dir(ses, filename))
+	{
+		return;
+	}
+
+	size = scandir(filename, &dirlist, 0, alphasort);
+
+	if (size == -1)
+	{
+		syserr_printf(ses, "do_attach: scandir:");
+
+		return;
+	}
+
+	tintin_printf2(ses, "#THESE DAEMONS HAVE BEEN DEFINED:");
+
+	for (*arg2 = index = pid = 0 ; index < size ; index++)
+	{
+		if (strlen(dirlist[index]->d_name) > 2)
+		{
+			if (*arg1)
+			{
+				if (!strstr(dirlist[index]->d_name, arg1))
+				{
+					continue;
+				}
+			}
+			arg = strchr(dirlist[index]->d_name, '.');
+
+			if (arg)
+			{
+				*arg = 0;
+
+				strcpy(arg2, dirlist[index]->d_name);
+
+				arg = strchr(dirlist[index]->d_name, '_');
+
+				if (arg)
+				{
+					pid = atoi(arg + 1);
+
+					sprintf(sock_file, "%s/%s.s", filename, arg2);
+
+					tintin_printf2(ses, "%-40s [%6d]", sock_file, pid);
+				}
+			}
+		}
+	}
+	for (index = 0 ; index < size ; index++)
+	{
+		free(dirlist[index]);
+	}
+	free(dirlist);
+
+	return;
+}
+
+int get_daemon_dir(struct session *ses, char *filename)
+{
+	sprintf(filename, "%s/%s", gtd->home, TINTIN_DIR);
+
+	if (mkdir(filename, 0755) && errno != EEXIST)
+	{
+		show_error(ses, LIST_COMMAND, "#DAEMON CHECK DIR: FAILED TO CREATE TINTIN DIR %s (%s)", filename, strerror(errno));
+
+		return 0;
+	}
+
+	sprintf(filename, "%s/%s/%s", gtd->home, TINTIN_DIR, DAEMON_DIR);
+
+	if (mkdir(filename, 0755) && errno != EEXIST)
+	{
+		show_error(ses, LIST_COMMAND, "#DAEMON CHECK DIR: CANNOT CREATE DAEMON DIR %s (%s)", filename, strerror(errno));
+
+		return 0;
+	}
+	return 1;
+}
+
+void reset_daemon()
+{
+	if (gtd->detach_sock > 0)
+	{
+		print_stdout("removing(%s)\n", gtd->detach_file);
+
+		remove(gtd->detach_file);
+	}
+/*
+	if (gtd->attach_sock > 0)
+	{
+		print_stdout("unlinking(%s)\n", gtd->attach_file);
+
+		unlink(gtd->attach_file);
+	}
+*/
+}
+
+void winch_daemon()
+{
+	if (gtd->attach_sock)
+	{
+		kill((pid_t) gtd->attach_pid, SIGWINCH);
+	}
+}

+ 1159 - 0
data.c

@@ -0,0 +1,1159 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2004                       *
+******************************************************************************/
+
+#include "tintin.h"
+
+
+struct listroot *init_list(struct session *ses, int type, int size)
+{
+	struct listroot *listhead;
+
+	if ((listhead = (struct listroot *) calloc(1, sizeof(struct listroot))) == NULL)
+	{
+		syserr_fatal(-1, "init_list: calloc");
+	}
+
+	listhead->ses  = ses;
+	listhead->list = (struct listnode **) calloc(size, sizeof(struct listnode *));
+	listhead->size = size;
+	listhead->type = type;
+
+	listhead->flags = list_table[type].flags;
+
+	return listhead;
+}
+
+
+void kill_list(struct listroot *root)
+{
+	while (root->used)
+	{
+		delete_index_list(root, root->used - 1);
+	}
+//	root->update = 0;
+}
+
+void free_list(struct listroot *root)
+{
+	kill_list(root);
+
+	free(root->list);
+
+	free(root);
+}
+
+struct listroot *copy_list(struct session *ses, struct listroot *sourcelist, int type)
+{
+	int i;
+	struct listnode *node;
+
+	push_call("copy_list(%p,%p,%p)",ses,sourcelist,type);
+
+	ses->list[type] = init_list(ses, type, sourcelist->size);
+
+	if (HAS_BIT(sourcelist->flags, LIST_FLAG_INHERIT))
+	{
+		for (i = 0 ; i < sourcelist->used ; i++)
+		{
+			node = (struct listnode *) calloc(1, sizeof(struct listnode));
+
+			node->arg1  = str_dup_clone(sourcelist->list[i]->arg1);
+			node->arg2  = str_dup_clone(sourcelist->list[i]->arg2);
+			node->arg3  = str_dup_clone(sourcelist->list[i]->arg3);
+			node->arg4  = str_dup_clone(sourcelist->list[i]->arg4);
+			node->flags = sourcelist->list[i]->flags;
+			node->group = strdup(sourcelist->list[i]->group);
+
+			switch (type)
+			{
+				case LIST_ALIAS:
+					node->regex = tintin_regexp_compile(ses, node, node->arg1, PCRE_ANCHORED);
+					break;
+
+				case LIST_ACTION:
+				case LIST_GAG:
+				case LIST_HIGHLIGHT:
+				case LIST_PROMPT:
+				case LIST_SUBSTITUTE:
+					node->regex = tintin_regexp_compile(ses, node, node->arg1, 0);
+					break;
+
+				case LIST_BUTTON:
+					node->val16[0] = sourcelist->list[i]->val16[0];
+					node->val16[1] = sourcelist->list[i]->val16[1];
+					node->val16[2] = sourcelist->list[i]->val16[2];
+					node->val16[3] = sourcelist->list[i]->val16[3];
+					break;
+
+				case LIST_VARIABLE:
+					copy_nest_node(ses->list[type], node, sourcelist->list[i]);
+					break;
+			}
+			ses->list[type]->list[i] = node;
+		}
+		ses->list[type]->used = sourcelist->used;
+	}
+	ses->list[type]->flags = sourcelist->flags;
+
+	pop_call();
+	return ses->list[type];
+}
+
+
+struct listnode *insert_node_list(struct listroot *root, char *arg1, char *arg2, char *arg3, char *arg4)
+{
+	int index;
+	struct listnode *node;
+
+	node = (struct listnode *) calloc(1, sizeof(struct listnode));
+
+	if (list_table[root->type].priority_arg == 3 && *arg3 == 0)
+	{
+		strcpy(arg3, "5");
+	}
+
+	node->arg1 = str_dup(arg1);
+	node->arg2 = str_dup(arg2);
+	node->arg3 = str_dup(arg3);
+	node->arg4 = str_dup(arg4);
+
+	if (gtd->level->oneshot)
+	{
+		SET_BIT(node->flags, NODE_FLAG_ONESHOT);
+	}
+
+	node->group = HAS_BIT(root->flags, LIST_FLAG_CLASS) ? strdup(root->ses->group) : strdup("");
+
+	switch (root->type)
+	{
+		case LIST_ALIAS:
+			node->regex = tintin_regexp_compile(root->ses, node, node->arg1, PCRE_ANCHORED);
+			break;
+
+		case LIST_ACTION:
+		case LIST_GAG:
+		case LIST_HIGHLIGHT:
+		case LIST_PROMPT:
+		case LIST_SUBSTITUTE:
+			node->regex = tintin_regexp_compile(root->ses, node, node->arg1, 0);
+			break;
+	}
+
+	index = locate_index_list(root, arg1, arg3);
+
+	return insert_index_list(root, node, index);
+}
+
+
+struct listnode *update_node_list(struct listroot *root, char *arg1, char *arg2, char *arg3, char *arg4)
+{
+	int index;
+	struct listnode *node;
+
+	index = search_index_list(root, arg1, NULL);
+
+	if (index != -1)
+	{
+		if (list_table[root->type].mode == SORT_DELAY && is_number(arg1))
+		{
+			return insert_node_list(root, arg1, arg2, arg3, arg4);
+		}
+
+		node = root->list[index];
+
+		if (gtd->level->oneshot)
+		{
+			SET_BIT(node->flags, NODE_FLAG_ONESHOT);
+		}
+
+		if (strcmp(node->arg2, arg2) != 0)
+		{
+			node->arg2 = str_cpy(&node->arg2, arg2);
+		}
+
+		switch (root->type)
+		{
+			case LIST_DELAY:
+			case LIST_TICKER:
+				node->val64 = 0;
+				break;
+		}
+
+		if (list_table[root->type].priority_arg == 3 && *arg3 == 0)
+		{
+			strcpy(arg3, "5");
+		}
+
+		switch (list_table[root->type].mode)
+		{
+			case SORT_PRIORITY:
+				if (atof(node->arg3) != atof(arg3))
+				{
+					delete_index_list(root, index);
+					return insert_node_list(root, arg1, arg2, arg3, arg4);
+				}
+				break;
+
+			case SORT_APPEND:
+				delete_index_list(root, index);
+				return insert_node_list(root, arg1, arg2, arg3, arg4);
+				break;
+
+			case SORT_ALPHA:
+			case SORT_DELAY:
+				if (strcmp(node->arg3, arg3) != 0)
+				{
+					str_cpy(&node->arg3, arg3);
+				}
+				break;
+
+			default:
+				tintin_printf2(root->ses, "#BUG: update_node_list: unknown mode: %d", list_table[root->type].mode);
+				break;
+		}
+		return node;
+	}
+	else
+	{
+		return insert_node_list(root, arg1, arg2, arg3, arg4);
+	}
+}
+
+struct listnode *insert_index_list(struct listroot *root, struct listnode *node, int index)
+{
+	root->used++;
+
+	if (root->used == root->size)
+	{
+		root->size *= 2;
+
+		root->list = (struct listnode **) realloc(root->list, (root->size) * sizeof(struct listnode *));
+	}
+
+	memmove(&root->list[index + 1], &root->list[index], (root->used - index) * sizeof(struct listnode *));
+
+	root->list[index] = node;
+
+	return node;
+}
+
+void delete_node_list(struct session *ses, int type, struct listnode *node)
+{
+	int index = search_index_list(ses->list[type], node->arg1, node->arg3);
+
+	delete_index_list(ses->list[type], index);
+}
+
+void delete_index_list(struct listroot *root, int index)
+{
+	struct listnode *node = root->list[index];
+
+	if (node->root)
+	{
+		free_list(node->root);
+	}
+
+	if (index <= root->update)
+	{
+		root->update--;
+	}
+
+	str_free(node->arg1);
+	str_free(node->arg2);
+	str_free(node->arg3);
+	str_free(node->arg4);
+
+	free(node->group);
+
+	if (HAS_BIT(list_table[root->type].flags, LIST_FLAG_REGEX))
+	{
+		if (node->regex)
+		{
+			free(node->regex);
+		}
+	}
+
+	switch (root->type)
+	{
+		case LIST_TERRAIN:
+			free(node->room);
+			break;
+
+		case LIST_CLASS:
+			if (node->data)
+			{
+				free(node->data);
+			}
+			break;
+	}
+
+	free(node);
+
+	memmove(&root->list[index], &root->list[index + 1], (root->used - index) * sizeof(struct listnode *));
+
+	root->used--;
+
+	return;
+}
+
+struct listnode *search_node_list(struct listroot *root, char *text)
+{
+	int index;
+
+	push_call("search_node_list(%p,%p)",root,text);
+
+	switch (list_table[root->type].mode)
+	{
+		case SORT_ALPHA:
+		case SORT_DELAY:
+			index = bsearch_alpha_list(root, text, 0);
+			break;
+
+		default:
+			index = nsearch_list(root, text);
+			break;
+	}
+
+	if (index != -1)
+	{
+		pop_call();
+		return root->list[index];
+	}
+
+	pop_call();
+	return NULL;
+}
+
+int search_index_list(struct listroot *root, char *text, char *priority)
+{
+	if (list_table[root->type].mode == SORT_ALPHA || list_table[root->type].mode == SORT_DELAY)
+	{
+		return bsearch_alpha_list(root, text, 0);
+	}
+
+	if (list_table[root->type].mode == SORT_PRIORITY && priority)
+	{
+		return bsearch_priority_list(root, text, priority, 0);
+	}
+
+	return nsearch_list(root, text);
+}
+ 
+/*
+	Return insertion index.
+*/
+
+int locate_index_list(struct listroot *root, char *text, char *priority)
+{
+	switch (list_table[root->type].mode)
+	{
+		case SORT_ALPHA:
+		case SORT_DELAY:
+			return bsearch_alpha_list(root, text, 1);
+
+		case SORT_PRIORITY:
+			return bsearch_priority_list(root, text, priority, 1);
+
+		default:
+			return root->used;
+	}
+}
+
+/*
+	binary search on alphabetically sorted list
+*/
+
+int bsearch_alpha_list(struct listroot *root, char *text, int seek)
+{
+	long long bot, top, val;
+	long double toi, toj, srt;
+
+	push_call("bsearch_alpha_list(%p,%p,%d)",root,text,seek);
+
+	bot = 0;
+	top = root->used - 1;
+	val = top;
+
+//	toi = get_number(root->ses, text);
+
+	if (seek == 0 && (*text == '+' || *text == '-') && is_math(root->ses, text) && HAS_BIT(root->flags, LIST_FLAG_NEST))
+	{
+		toi = get_number(root->ses, text);
+
+		if (toi > 0 && toi <= root->used)
+		{
+			pop_call();
+			return toi - 1;
+		}
+		if (toi < 0 && toi + root->used >= 0)
+		{
+			pop_call();
+			return root->used + toi;
+		}
+		else
+		{
+			pop_call();
+			return -1;
+		}
+	}
+
+	toi = is_number(text) ? tintoi(text) : 0;
+
+	while (bot <= top)
+	{
+		toj = is_number(root->list[val]->arg1) ? tintoi(root->list[val]->arg1) : 0;
+
+		if (toi)
+		{
+			srt = toi - toj;
+		}
+		else if (toj)
+		{
+			srt = -1;
+		}
+		else
+		{
+			srt = strcmp(text, root->list[val]->arg1);
+		}
+
+		if (srt == 0)
+		{
+			pop_call();
+			return val;
+		}
+
+		if (srt < 0)
+		{
+			top = val - 1;
+		}
+		else
+		{
+			bot = val + 1;
+		}
+
+		val = bot + (top - bot) / 2;
+	}
+
+	if (seek)
+	{
+		pop_call();
+		return UMAX(0, val);
+	}
+	pop_call();
+	return -1;
+}
+
+/*
+	Binary search on priorially and alphabetically sorted list
+*/
+
+int bsearch_priority_list(struct listroot *root, char *text, char *priority, int seek)
+{
+	int bot, top, val;
+	double srt;
+
+	bot = 0;
+	top = root->used - 1;
+	val = top;
+
+	while (bot <= top)
+	{
+		srt = atof(priority) - atof(root->list[val]->arg3);
+
+		if (!srt)
+		{
+			srt = strcmp(text, root->list[val]->arg1);
+		}
+
+		if (srt == 0)
+		{
+			return val;
+		}
+
+		if (srt < 0)
+		{
+			top = val - 1;
+		}
+		else
+		{
+			bot = val + 1;
+		}
+
+		val = bot + (top - bot) / 2;
+	}
+
+	if (seek)
+	{
+		return UMAX(0, val);
+	}
+	else
+	{
+		return -1;
+	}
+}
+
+/*
+	Linear search
+*/
+
+int nsearch_list(struct listroot *root, char *text)
+{
+	int i;
+
+	for (i = 0 ; i < root->used ; i++)
+	{
+		if (!strcmp(text, root->list[i]->arg1))
+		{
+			return i;
+		}
+	}
+	return -1;
+}
+
+/*
+	show content of a node on screen
+*/
+
+void show_node(struct listroot *root, struct listnode *node, int level)
+{
+	char *str_arg2 = str_dup("");
+
+	show_nest_node(node, &str_arg2, TRUE);
+
+	switch (list_table[root->type].args)
+	{
+		case 4:
+			tintin_printf2(root->ses, "%s" COLOR_TINTIN "#" COLOR_COMMAND "%s " COLOR_BRACE "{" COLOR_STRING "%s" COLOR_BRACE "} {" COLOR_STRING "%s" COLOR_BRACE "} {" COLOR_STRING "%s" COLOR_BRACE "} {" COLOR_STRING "%s" COLOR_BRACE "}", indent(level), list_table[root->type].name, node->arg1, str_arg2, node->arg3, node->arg4);
+			break;
+		case 3:
+			if (list_table[root->type].priority_arg == 3 && !strcmp(node->arg3, "5"))
+			{
+				tintin_printf2(root->ses, "%s" COLOR_TINTIN "#" COLOR_COMMAND "%s " COLOR_BRACE "{" COLOR_STRING "%s" COLOR_BRACE "} {" COLOR_STRING "%s" COLOR_BRACE "}", indent(level), list_table[root->type].name, node->arg1, str_arg2);
+			}
+			else
+			{
+				tintin_printf2(root->ses, "%s" COLOR_TINTIN "#" COLOR_COMMAND "%s " COLOR_BRACE "{" COLOR_STRING "%s" COLOR_BRACE "} {" COLOR_STRING "%s" COLOR_BRACE "} {" COLOR_STRING "%s" COLOR_BRACE "}", indent(level), list_table[root->type].name, node->arg1, str_arg2, node->arg3);
+			}
+			break;
+		case 2:
+			tintin_printf2(root->ses, "%s" COLOR_TINTIN "#" COLOR_COMMAND "%s " COLOR_BRACE "{" COLOR_STRING "%s" COLOR_BRACE "} {" COLOR_STRING "%s" COLOR_BRACE "}", indent(level), list_table[root->type].name, node->arg1, str_arg2);
+			break;
+		case 1:
+			tintin_printf2(root->ses, "%s" COLOR_TINTIN "#" COLOR_COMMAND "%s " COLOR_BRACE "{" COLOR_STRING "%s" COLOR_BRACE "}", indent(level), list_table[root->type].name, node->arg1);
+			break;
+	}
+	str_free(str_arg2);
+}
+
+/*
+	list content of a list on screen
+*/
+
+void show_list(struct listroot *root, int level)
+{
+	int i;
+
+	if (root == root->ses->list[root->type])
+	{
+		tintin_header(root->ses, " %s ", list_table[root->type].name_multi);
+	}
+
+	for (i = 0 ; i < root->used ; i++)
+	{
+		show_node(root, root->list[i], level);
+	}
+}
+
+
+int show_node_with_wild(struct session *ses, char *text, struct listroot *root)
+{
+	struct listnode *node;
+	int i, found = FALSE;
+
+	push_call("show_node_with_wild(%p,%p,%p)",ses,text,root);
+
+	node = search_node_list(root, text);
+
+	if (node)
+	{
+		if (list_table[root->type].script_arg == 2)
+		{
+			if (list_table[root->type].args == 2)
+			{
+				tintin_printf2(ses, COLOR_TINTIN "%c" COLOR_COMMAND "%s " COLOR_BRACE "{" COLOR_STRING "%s" COLOR_BRACE "}\n{\n" COLOR_STRING "%s\n" COLOR_BRACE "}\n", gtd->tintin_char, list_table[root->type].name, node->arg1, script_viewer(ses, node->arg2));
+			}
+			else if (list_table[root->type].args == 3)
+			{
+				if (list_table[root->type].priority_arg == 3)
+				{
+					if (!strcmp(node->arg3, "5"))
+					{
+						tintin_printf2(ses, COLOR_TINTIN "%c" COLOR_COMMAND "%s " COLOR_BRACE "{" COLOR_STRING "%s" COLOR_BRACE "}\n{\n" COLOR_STRING "%s\n" COLOR_BRACE "}\n", gtd->tintin_char, list_table[root->type].name, node->arg1, script_viewer(ses, node->arg2));
+					}
+					else
+					{
+						tintin_printf2(ses, COLOR_TINTIN "%c" COLOR_COMMAND "%s " COLOR_BRACE "{" COLOR_STRING "%s" COLOR_BRACE "}\n{\n" COLOR_STRING "%s\n" COLOR_BRACE "}\n{" COLOR_STRING "%s" COLOR_BRACE "}\n", gtd->tintin_char, list_table[root->type].name, node->arg1, script_viewer(ses, node->arg2), node->arg3);
+					}
+				}
+				else
+				{
+					tintin_printf2(ses, COLOR_TINTIN "%c" COLOR_COMMAND "%s " COLOR_BRACE "{" COLOR_STRING "%s" COLOR_BRACE "}\n{\n" COLOR_STRING "%s\n" COLOR_BRACE "}\n{" COLOR_STRING "%s" COLOR_BRACE "}\n", gtd->tintin_char, list_table[root->type].name, node->arg1, script_viewer(ses, node->arg2), node->arg3);
+				}
+			}
+		}
+		else
+		{
+			show_node(root, node, 0);
+		}
+
+		switch(root->type)
+		{
+//			case LIST_EVENT:
+//			case LIST_FUNCTION:
+//			case LIST_MACRO:
+//				tintin_printf2(ses, COLOR_TINTIN "%c" COLOR_COMMAND "%s " COLOR_BRACE "{" COLOR_STRING "%s" COLOR_BRACE "}\n{\n" COLOR_STRING "%s\n" COLOR_BRACE "}\n", gtd->tintin_char, list_table[root->type].name, node->arg1, script_viewer(ses, node->arg2));
+//				break;
+
+//			case LIST_ACTION:
+//			case LIST_ALIAS:
+//			case LIST_BUTTON:
+//				if (!strcmp(node->arg3, "5"))
+//				{
+//					tintin_printf2(ses, COLOR_TINTIN "%c" COLOR_COMMAND "%s " COLOR_BRACE "{" COLOR_STRING "%s" COLOR_BRACE "}\n{\n" COLOR_STRING "%s\n" COLOR_BRACE "}\n", gtd->tintin_char, list_table[root->type].name, node->arg1, script_viewer(ses, node->arg2));
+//				}
+//				else
+//				{
+//					tintin_printf2(ses, COLOR_TINTIN "%c" COLOR_COMMAND "%s " COLOR_BRACE "{" COLOR_STRING "%s" COLOR_BRACE "}\n{\n" COLOR_STRING "%s\n" COLOR_BRACE "}\n{" COLOR_STRING "%s" COLOR_BRACE "}\n", gtd->tintin_char, list_table[root->type].name, node->arg1, script_viewer(ses, node->arg2), node->arg3);
+//				}
+//				break;
+
+//			case LIST_DELAY:
+//			case LIST_TICKER:
+//				tintin_printf2(ses, COLOR_TINTIN "%c" COLOR_COMMAND "%s " COLOR_BRACE "{" COLOR_STRING "%s" COLOR_BRACE "}\n{\n" COLOR_STRING "%s\n" COLOR_BRACE "}\n{" COLOR_STRING "%s" COLOR_BRACE "}\n\n", gtd->tintin_char, list_table[root->type].name, node->arg1, script_viewer(ses, node->arg2), node->arg3);
+//				break;
+
+//			default:
+//				show_node(root, node, 0);
+//				break;
+		}
+		pop_call();
+		return TRUE;
+	}
+
+	for (i = 0 ; i < root->used ; i++)
+	{
+		if (match(ses, root->list[i]->arg1, text, SUB_VAR|SUB_FUN))
+		{
+			show_node(root, root->list[i], 0);
+
+			found = TRUE;
+		}
+	}
+	pop_call();
+	return found;
+}
+
+int delete_node_with_wild(struct session *ses, int type, char *text)
+{
+	struct listroot *root = ses->list[type];
+	struct listnode *node;
+	char arg1[BUFFER_SIZE];
+	int i, found = FALSE;
+
+	sub_arg_in_braces(ses, text, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+
+	node = search_node_list(root, arg1);
+
+	if (node)
+	{
+		show_message(ses, type, "#OK. {%s} IS NO LONGER %s %s.", node->arg1, (*list_table[type].name == 'A' || *list_table[type].name == 'E') ? "AN" : "A", list_table[type].name);
+
+		delete_node_list(ses, type, node);
+
+		return TRUE;
+	}
+
+	for (i = root->used - 1 ; i >= 0 ; i--)
+	{
+		if (match(ses, root->list[i]->arg1, arg1, SUB_VAR|SUB_FUN))
+		{
+			show_message(ses, type, "#OK. {%s} IS NO LONGER %s %s.", root->list[i]->arg1, (*list_table[type].name == 'A' || *list_table[type].name == 'E') ? "AN" : "A", list_table[type].name);
+
+			delete_index_list(root, i);
+
+			found = TRUE;
+		}
+	}
+
+	if (found == 0)
+	{
+		show_message(ses, type, "#KILL: NO MATCHES FOUND FOR %s {%s}.", list_table[type].name, arg1);
+
+		return FALSE;
+	}
+	return TRUE;
+}
+
+
+DO_COMMAND(do_killall)
+{
+	tintin_printf2(ses, "\e[1;31m#NOTICE: PLEASE CHANGE #KILLALL TO #KILL ALL.");
+
+	do_kill(ses, arg);
+
+	return ses;
+}
+
+DO_COMMAND(do_kill)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+	int index;
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+	      get_arg_in_braces(ses, arg, arg2, GET_ALL);
+
+	if (*arg1 == 0 || !strcasecmp(arg1, "ALL"))
+	{
+		for (index = 0 ; index < LIST_MAX ; index++)
+		{
+			if (!HAS_BIT(ses->list[index]->flags, LIST_FLAG_HIDE))
+			{
+				kill_list(ses->list[index]);
+			}
+		}
+		show_message(ses, LIST_COMMAND, "#KILL - ALL LISTS CLEARED.");
+
+		return ses;
+	}
+
+	for (index = 0 ; index < LIST_MAX ; index++)
+	{
+		if (!is_abbrev(arg1, list_table[index].name) && !is_abbrev(arg1, list_table[index].name_multi))
+		{
+			continue;
+		}
+
+		if (*arg2 == 0 || !strcasecmp(arg2, "ALL"))
+		{
+			kill_list(ses->list[index]);
+
+			show_message(ses, LIST_COMMAND, "#OK: #%s LIST CLEARED.", list_table[index].name);
+		}
+		else
+		{
+			delete_node_with_wild(ses, index, arg);
+		}
+		break;
+	}
+
+	if (index == LIST_MAX)
+	{
+		show_error(ses, LIST_COMMAND, "#ERROR: #KILL {%s} {%s} - NO MATCH FOUND.", arg1, arg2);
+	}
+	return ses;
+}
+
+
+DO_COMMAND(do_message)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+	int index, found = FALSE;
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+	arg = get_arg_in_braces(ses, arg, arg2, GET_ONE);
+
+	if (*arg1 == 0)
+	{
+		tintin_header(ses, " MESSAGES ");
+
+		for (index = 0 ; index < LIST_MAX ; index++)
+		{
+			if (!HAS_BIT(list_table[index].flags, LIST_FLAG_HIDE))
+			{
+				tintin_printf2(ses, "  %-20s %3s", list_table[index].name_multi, HAS_BIT(ses->list[index]->flags, LIST_FLAG_MESSAGE) ? "ON" : "OFF");
+			}
+		}
+
+		tintin_header(ses, "");
+	}
+	else
+	{
+		for (index = found = 0 ; index < LIST_MAX ; index++)
+		{
+			if (HAS_BIT(list_table[index].flags, LIST_FLAG_HIDE))
+			{
+				continue;
+			}
+
+			if (!is_abbrev(arg1, list_table[index].name) && !is_abbrev(arg1, list_table[index].name_multi) && strcasecmp(arg1, "ALL"))
+			{
+				continue;
+			}
+
+			if (*arg2 == 0)
+			{
+				TOG_BIT(ses->list[index]->flags, LIST_FLAG_MESSAGE);
+			}
+			else if (is_abbrev(arg2, "ON"))
+			{
+				SET_BIT(ses->list[index]->flags, LIST_FLAG_MESSAGE);
+			}
+			else if (is_abbrev(arg2, "OFF"))
+			{
+				DEL_BIT(ses->list[index]->flags, LIST_FLAG_MESSAGE);
+			}
+			else
+			{
+				show_error(ses, LIST_COMMAND, "#SYNTAX: #MESSAGE {%s} [ON|OFF]",  arg1);
+				
+				return ses;
+			}
+			show_message(ses, LIST_COMMAND, "#OK: #%s MESSAGES HAVE BEEN SET TO: %s.", list_table[index].name, HAS_BIT(ses->list[index]->flags, LIST_FLAG_MESSAGE) ? "ON" : "OFF");
+
+			found = TRUE;
+		}
+
+		if (found == FALSE)
+		{
+			show_error(ses, LIST_COMMAND, "#ERROR: #MESSAGE {%s} - NO MATCH FOUND.", arg1);
+		}
+	}
+	return ses;
+}
+
+
+DO_COMMAND(do_ignore)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+	int index, found = FALSE;
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+	arg = get_arg_in_braces(ses, arg, arg2, GET_ONE);
+
+	if (*arg1 == 0)
+	{
+		tintin_header(ses, " IGNORES ");
+
+		for (index = 0 ; index < LIST_MAX ; index++)
+		{
+			if (!HAS_BIT(list_table[index].flags, LIST_FLAG_HIDE))
+			{
+				tintin_printf2(ses, "  %-20s %3s", list_table[index].name_multi, HAS_BIT(ses->list[index]->flags, LIST_FLAG_IGNORE) ? "ON" : "OFF");
+			}
+		}
+
+		tintin_header(ses, "");
+	}
+	else
+	{
+		for (index = found = 0 ; index < LIST_MAX ; index++)
+		{
+			if (HAS_BIT(list_table[index].flags, LIST_FLAG_HIDE))
+			{
+				continue;
+			}
+
+			if (!is_abbrev(arg1, list_table[index].name) && !is_abbrev(arg1, list_table[index].name_multi) && strcasecmp(arg1, "ALL"))
+			{
+				continue;
+			}
+
+			if (*arg2 == 0)
+			{
+				TOG_BIT(ses->list[index]->flags, LIST_FLAG_IGNORE);
+			}
+			else if (is_abbrev(arg2, "ON"))
+			{
+				SET_BIT(ses->list[index]->flags, LIST_FLAG_IGNORE);
+			}
+			else if (is_abbrev(arg2, "OFF"))
+			{
+				DEL_BIT(ses->list[index]->flags, LIST_FLAG_IGNORE);
+			}
+			else
+			{
+				show_error(ses, LIST_COMMAND, "#SYNTAX: #IGNORE {%s} [ON|OFF]", arg1);
+				
+				return ses;
+			}
+			show_message(ses, LIST_COMMAND, "#OK: #%s IGNORE STATUS HAS BEEN SET TO: %s.", list_table[index].name, HAS_BIT(ses->list[index]->flags, LIST_FLAG_IGNORE) ? "ON" : "OFF");
+
+			found = TRUE;
+		}
+
+		if (found == FALSE)
+		{
+			show_error(ses, LIST_COMMAND, "#ERROR: #IGNORE {%s} - NO MATCH FOUND.", arg1);
+		}
+	}
+	return ses;
+}
+
+
+DO_COMMAND(do_debug)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+	int index, found = FALSE;
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+	arg = get_arg_in_braces(ses, arg, arg2, GET_ONE);
+
+	if (*arg1 == 0)
+	{
+		tintin_header(ses, " DEBUGS ");
+
+		for (index = 0 ; index < LIST_MAX ; index++)
+		{
+			if (!HAS_BIT(list_table[index].flags, LIST_FLAG_HIDE))
+			{
+				tintin_printf2(ses, "  %-20s %3s", list_table[index].name_multi, HAS_BIT(ses->list[index]->flags, LIST_FLAG_DEBUG) ? "ON" : "OFF");
+			}
+		}
+
+		tintin_header(ses, "");
+	}
+	else
+	{
+		for (index = found = 0 ; index < LIST_MAX ; index++)
+		{
+			if (HAS_BIT(list_table[index].flags, LIST_FLAG_HIDE))
+			{
+				continue;
+			}
+
+			if (!is_abbrev(arg1, list_table[index].name) && !is_abbrev(arg1, list_table[index].name_multi) && strcasecmp(arg1, "ALL"))
+			{
+				continue;
+			}
+
+			if (*arg2 == 0)
+			{
+				TOG_BIT(ses->list[index]->flags, LIST_FLAG_DEBUG);
+			}
+			else if (is_abbrev(arg2, "ON"))
+			{
+				SET_BIT(ses->list[index]->flags, LIST_FLAG_DEBUG);
+			}
+			else if (is_abbrev(arg2, "OFF"))
+			{
+				DEL_BIT(ses->list[index]->flags, LIST_FLAG_DEBUG);
+				DEL_BIT(ses->list[index]->flags, LIST_FLAG_LOG);
+			}
+			else if (is_abbrev(arg2, "LOG"))
+			{
+				SET_BIT(ses->list[index]->flags, LIST_FLAG_LOG);
+			}
+			else
+			{
+				show_error(ses, LIST_COMMAND, "#SYNTAX: #DEBUG {%s} [ON|OFF|LOG]", arg1);
+				
+				return ses;
+			}
+			show_message(ses, LIST_COMMAND, "#OK: #%s DEBUG STATUS HAS BEEN SET TO: %s.", list_table[index].name, is_abbrev(arg2, "LOG") ? "LOG" : HAS_BIT(ses->list[index]->flags, LIST_FLAG_DEBUG) ? "ON" : "OFF");
+
+			found = TRUE;
+		}
+
+		if (found == FALSE)
+		{
+			show_error(ses, LIST_COMMAND, "#DEBUG {%s} - NO MATCH FOUND.", arg1);
+		}
+	}
+	return ses;
+}
+
+DO_COMMAND(do_info)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], name[BUFFER_SIZE];
+	int cnt, index, found = FALSE;
+	struct listroot *root;
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+	arg = get_arg_in_braces(ses, arg, arg2, GET_ONE);
+
+	if (*arg1 == 0)
+	{
+		tintin_header(ses, " INFORMATION ");
+
+		for (index = 0 ; index < LIST_MAX ; index++)
+		{
+			if (!HAS_BIT(ses->list[index]->flags, LIST_FLAG_HIDE))
+			{
+				tintin_printf2(ses, "%-15s %5d   IGNORE %3s   MESSAGE %3s   INFO %3s   DEBUG %3s %3s",
+					list_table[index].name_multi,
+					ses->list[index]->used,
+					HAS_BIT(ses->list[index]->flags, LIST_FLAG_IGNORE)  ?  "ON" : "OFF",
+					HAS_BIT(ses->list[index]->flags, LIST_FLAG_MESSAGE) ?  "ON" : "OFF",
+					HAS_BIT(ses->list[index]->flags, LIST_FLAG_INFO)    ?  "ON" : "OFF",
+					HAS_BIT(ses->list[index]->flags, LIST_FLAG_DEBUG)   ?  "ON" : "OFF",
+					HAS_BIT(ses->list[index]->flags, LIST_FLAG_LOG)     ? "LOG" : "   ");
+			}
+		}
+		tintin_header(ses, "");
+	}
+	else
+	{
+		for (index = found = 0 ; index < LIST_MAX ; index++)
+		{
+			if (HAS_BIT(list_table[index].flags, LIST_FLAG_HIDE))
+			{
+				continue;
+			}
+
+			if (!is_abbrev(arg1, list_table[index].name) && !is_abbrev(arg1, list_table[index].name_multi) && strcasecmp(arg1, "ALL"))
+			{
+				continue;
+			}
+
+			if (*arg2 == 0)
+			{
+				TOG_BIT(ses->list[index]->flags, LIST_FLAG_INFO);
+			}
+			else if (is_abbrev(arg2, "ON"))
+			{
+				SET_BIT(ses->list[index]->flags, LIST_FLAG_INFO);
+			}
+			else if (is_abbrev(arg2, "OFF"))
+			{
+				DEL_BIT(ses->list[index]->flags, LIST_FLAG_INFO);
+			}
+			else
+			{
+				root = ses->list[index];
+
+				if (is_abbrev(arg2, "LIST"))
+				{
+					for (cnt = 0 ; cnt < root->used ; cnt++)
+					{
+						tintin_printf2(ses, "#INFO %s %4d {arg1}{%s} {arg2}{%s} {arg3}{%s} {arg4}{%s} {class}{%s} {flags}{%d}", list_table[index].name, cnt, root->list[cnt]->arg1, root->list[cnt]->arg2, root->list[cnt]->arg3, root->list[cnt]->arg4, root->list[cnt]->group, root->list[cnt]->flags);
+					}
+				}
+				else if (is_abbrev(arg2, "SAVE"))
+				{
+					sprintf(name, "info[%s]", list_table[index].name);
+//					delete_nest_node(ses->list[LIST_VARIABLE], name);
+
+					for (cnt = 0 ; cnt < root->used ; cnt++)
+					{
+						sprintf(name, "info[%s][%d]", list_table[index].name, cnt);
+
+						set_nest_node_ses(ses, name, "{arg1}{%s}{arg2}{%s}{arg3}{%s}{arg4}{%s}{class}{%s}{flags}{%d}", root->list[cnt]->arg1, root->list[cnt]->arg2, root->list[cnt]->arg3, root->list[cnt]->arg4, root->list[cnt]->group, root->list[cnt]->flags);
+					}
+					show_message(ses, LIST_COMMAND, "#INFO: DATA WRITTEN TO {info[%s]}", list_table[index].name);
+				}
+				else
+				{
+					show_error(ses, LIST_COMMAND, "#SYNTAX: #INFO {%s} [ON|OFF|LIST|SAVE|SYSTEM]", arg1);
+				}
+				return ses;
+			}
+			show_message(ses, LIST_COMMAND, "#OK: #%s INFO STATUS HAS BEEN SET TO: %s.", list_table[index].name, HAS_BIT(ses->list[index]->flags, LIST_FLAG_INFO) ? "ON" : "OFF");
+
+			found = TRUE;
+		}
+
+		if (found == FALSE)
+		{
+			if (is_abbrev(arg1, "CPU"))
+			{
+				show_cpu(ses);
+			}
+			else if (is_abbrev(arg1, "MCCP"))
+			{
+				if (ses->mccp2)
+				{
+					tintin_printf2(ses, "#INFO MCCP2: TOTAL IN: %9ull TOTAL OUT: %9ull RATIO: %3d", ses->mccp2->total_in, ses->mccp2->total_out, 100 * ses->mccp2->total_out / ses->mccp2->total_in);
+				}
+				if (ses->mccp3)
+				{
+					tintin_printf2(ses, "#INFO MCCP3: TOTAL IN: %9ull TOTAL OUT: %9ull RATIO: %3d", ses->mccp3->total_in, ses->mccp3->total_out, 100 * ses->mccp3->total_out / ses->mccp3->total_in);
+				}
+			}
+			else if (is_abbrev(arg1, "SESSION"))
+			{
+				if (is_abbrev(arg2, "SAVE"))
+				{
+					sprintf(name, "info[SESSION]");
+
+					set_nest_node_ses(ses, name, "{SESSION_NAME}{%s}", ses->name);
+					add_nest_node_ses(ses, name, "{SESSION_CLASS}{%s}", ses->group);
+					add_nest_node_ses(ses, name, "{SESSION_CREATED}{%d}", ses->created);
+					add_nest_node_ses(ses, name, "{SESSION_HOST} {%s}", ses->session_host);
+					add_nest_node_ses(ses, name, "{SESSION_IP} {%s}", ses->session_ip);
+					add_nest_node_ses(ses, name, "{SESSION_PORT} {%s}", ses->session_port);
+
+					show_message(ses, LIST_COMMAND, "#INFO: DATA WRITTEN TO {info[SESSION]}");
+				}
+				else
+				{
+					tintin_printf2(ses, "{SESSION_NAME}{%s}", ses->name);
+					tintin_printf2(ses, "{SESSION_CLASS}{%s}", ses->group);
+					tintin_printf2(ses, "{SESSION_CREATED}{%d}", ses->created);
+					tintin_printf2(ses, "{SESSION_HOST} {%s}", ses->session_host);
+					tintin_printf2(ses, "{SESSION_IP} {%s}", ses->session_ip);
+					tintin_printf2(ses, "{SESSION_PORT} {%s}", ses->session_port);
+				}
+			}
+			else if (is_abbrev(arg1, "STACK"))
+			{
+				dump_stack();
+			}
+			else if (is_abbrev(arg1, "SYSTEM"))
+			{
+				if (is_abbrev(arg2, "SAVE"))
+				{
+					sprintf(name, "info[SYSTEM]");
+
+					set_nest_node_ses(ses, name, "{CLIENT_NAME}{%s}{CLIENT_VERSION}{%s}", CLIENT_NAME, CLIENT_VERSION);
+					add_nest_node_ses(ses, name, "{CLIENT}{{NAME}{%s}{VERSION}{%s}}", CLIENT_NAME, CLIENT_VERSION);
+					add_nest_node_ses(ses, name, "{EXEC}{%s}{HOME}{%s}{LANG}{%s}{OS}{%s}{TERM}{%s}", gtd->exec, gtd->home, gtd->lang, gtd->os, gtd->term);
+					add_nest_node_ses(ses, name, "{DETACH_FILE}{%s}{ATTACH_FILE}{%s}", gtd->detach_port > 0 ? gtd->detach_file : "", gtd->attach_sock > 0 ? gtd->attach_file : "");
+
+					show_message(ses, LIST_COMMAND, "#INFO: DATA WRITTEN TO {info[SYSTEM]}");
+				}
+				else
+				{
+					tintin_printf2(ses, "#INFO SYSTEM: CLIENT_NAME    = %s", CLIENT_NAME);
+					tintin_printf2(ses, "#INFO SYSTEM: CLIENT_VERSION = %s", CLIENT_VERSION);
+					tintin_printf2(ses, "#INFO SYSTEM: EXEC           = %s", gtd->exec);
+					tintin_printf2(ses, "#INFO SYSTEM: HOME           = %s", gtd->home);
+					tintin_printf2(ses, "#INFO SYSTEM: LANG           = %s", gtd->lang);
+					tintin_printf2(ses, "#INFO SYSTEM: OS             = %s", gtd->os);
+					tintin_printf2(ses, "#INFO SYSTEM: TERM           = %s", gtd->term);
+					tintin_printf2(ses, "#INFO SYSTEM: DETACH_PORT    = %d", gtd->detach_port);
+					tintin_printf2(ses, "#INFO SYSTEM: DETACH_FILE    = %s", gtd->detach_port ? gtd->detach_file : "");
+					tintin_printf2(ses, "#INFO SYSTEM: ATTACH_SOCK    = %d", gtd->attach_sock);
+					tintin_printf2(ses, "#INFO SYSTEM: ATTACH_FILE    = %s", gtd->attach_sock ? gtd->attach_file : "");
+				}
+			}
+			else
+			{
+				show_error(ses, LIST_COMMAND, "#INFO {%s} - NO MATCH FOUND.", arg1);
+			}
+		}
+	}
+	return ses;
+
+}

+ 116 - 0
debug.c

@@ -0,0 +1,116 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2004                       *
+******************************************************************************/
+
+
+#include "tintin.h"
+
+#define MAX_STACK_SIZE     100
+#define MAX_DEBUG_SIZE     400
+
+char debug_stack[MAX_STACK_SIZE][MAX_DEBUG_SIZE];
+
+int debug_index = 0;
+
+int push_call(char *f, ...)
+{
+	va_list ap;
+
+	if (debug_index < MAX_STACK_SIZE)
+	{
+		va_start(ap, f);
+
+		vsnprintf(debug_stack[debug_index], MAX_DEBUG_SIZE - 1, f, ap);
+
+		va_end(ap);
+	}
+
+	if (++debug_index == 10000)
+	{
+		dump_stack();
+
+		return 1;
+	}
+
+	return 0;
+}
+
+void pop_call(void)
+{
+	if (debug_index > 0)
+	{
+		debug_index--;
+	}
+	else
+	{
+		tintin_printf2(gtd->ses, "pop_call: index is zero.");
+		dump_full_stack();
+	}
+}
+
+void dump_stack(void)
+{
+	unsigned char i;
+
+	if (gtd && gtd->ses)
+	{
+		for (i = 0 ; i < debug_index && i < MAX_STACK_SIZE ; i++)
+		{
+			tintin_printf2(gtd->ses, "\e[1;32mDEBUG_STACK[\e[1;31m%03d\e[1;32m] = \e[1;31m%s\e[0m", i, debug_stack[i]);
+		}
+	}
+	else
+	{
+		for (i = 0 ; i < debug_index && i < MAX_STACK_SIZE ; i++)
+		{
+			tintin_printf2(gtd->ses, "\e[1;32mDEBUG_STACK[\e[1;31m%03d\e[1;32m] = \e[1;31m%s\e[0m\n", i, debug_stack[i]);
+		}
+	}
+}
+
+void dump_stack_fatal(void)
+{
+	unsigned char i;
+
+	for (i = 0 ; i < debug_index && i < MAX_STACK_SIZE ; i++)
+	{
+		print_stdout("\e[1;32mDEBUG_STACK[\e[1;31m%03d\e[1;32m] = \e[1;31m%s\e[0m\n", i, debug_stack[i]);
+	}
+}
+
+void dump_full_stack(void)
+{
+	unsigned char i;
+
+	tintin_header(gtd->ses, " FULL DEBUG STACK ");
+
+	for (i = 0 ; i < MAX_STACK_SIZE ; i++)
+	{
+		if (*debug_stack[i])
+		{
+			tintin_printf2(gtd->ses, "\e[1;31mDEBUG_STACK[%03d] = %s", i, debug_stack[i]);
+		}
+	}
+	tintin_header(gtd->ses, "");
+}

+ 186 - 0
dict.c

@@ -0,0 +1,186 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2019                       *
+******************************************************************************/
+
+#include "tintin.h"
+
+#include "dict.h"
+
+int dictionary_search(char **array, int array_size, char *key)
+{
+        // Copyright 2014-2020 Igor van den Hoven
+
+	register int mid, i, bot;
+	register char val;
+
+	bot = 0;
+	i = array_size - 1;
+	mid = i / 2;
+
+	while (mid)
+	{
+		val = strcmp(key, array[i - mid]);
+
+		if (val < 0)
+		{
+			i -= mid + 1;
+		}
+		else if (val > 0)
+		{
+			bot = i - mid;
+		}
+		else
+		{
+			return i - mid;
+		}
+		mid = (i - bot) / 2;
+	}
+
+	if (i > bot)
+	{
+		val = strcmp(key, array[i]);
+
+		if (val > 0)
+		{
+			return -1;
+		}
+		else if (val < 0)
+		{
+			--i;
+		}
+		else
+		{
+			return i;
+		}
+	}
+
+	if (!strcmp(key, array[i]))
+	{
+		return i;
+	}
+	return -1;
+}
+
+void dictionary_lowerstring(char *in, char *out)
+{
+	char *pti, *pto;
+
+	pti = in;
+	pto = out;
+
+	while (*pti)
+	{
+		if (isalpha(*pti))
+		{
+			*pto++ = tolower(*pti++);
+		}
+		else
+		{
+			pti++;
+		}
+	}
+	*pto = 0;
+}
+
+int spellcheck_count(struct session *ses, char *in)
+{
+	char *arg, arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+	int cnt, hash, index;
+
+	cnt = 0;
+	arg = in;
+
+	while (*arg)
+	{
+		arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+
+		dictionary_lowerstring(arg1, arg2);
+
+		if (isalpha(*arg2))
+		{
+			hash = *arg2 - 'a';
+
+			index = dictionary_search(wordlist[hash], wordlist_size[hash], arg2 + 1);
+
+			if (index == -1)
+			{
+				cnt++;
+			}
+		}
+
+		if (*arg == COMMAND_SEPARATOR)
+		{
+			arg++;
+		}
+	}
+	return cnt;
+}
+
+DO_COMMAND(do_dictionary)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], arg3[BUFFER_SIZE];
+	int hash, size, index;
+
+	sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0 || !isalpha(*arg1))
+	{
+		show_message(ses, LIST_COMMAND, "#SYNTAX: #DICTIONARY {WORD}");
+
+		return ses;
+	}
+
+	arg = arg1;
+
+	while (*arg)
+	{
+		arg = get_arg_in_braces(ses, arg, arg2, GET_ONE);
+
+		dictionary_lowerstring(arg2, arg3);
+
+		if (isalpha(*arg3))
+		{
+			hash = *arg3 - 'a';
+
+			size = wordlist_size[hash];
+
+			index = dictionary_search(wordlist[hash], size, arg3 + 1);
+
+			if (index == -1)
+			{
+				tintin_printf2(ses, "\e[1;31m%s", arg2);
+			}
+			else
+			{
+				tintin_printf2(ses, "\e[1;32m%s", arg2);
+			}
+		}
+
+		if (*arg == COMMAND_SEPARATOR)
+		{
+			arg++;
+		}
+	}
+	return ses;
+}

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 25 - 0
dict.h


+ 249 - 130
docs/help.html

@@ -1,13 +1,15 @@
 <html>
 <head>
 <meta http-equiv='content-type' content='text/html; charset=utf-8'>
-<meta name='description' content='Generated by TinTin++ 2.01.92b - http://tintin.sourceforge.net'>
+<meta name='viewport' content='width=device-width, initial-scale=1.0'>
+<meta name='description' content='Generated by TinTin++ 2.01.93b - http://tintin.sourceforge.net'>
 <style type='text/css'>
-{
-	font-family: Courier;
-	font-size: 10pt;
-}
-a:link {color: cyan;} a:visited {color: green;} a:hover {color: white;} a:active {color:purple;}
+body {font-family:Consolas;font-size:12pt;}
+a {text-decoration:none;}
+a:link {color:#06b;}
+a:visited {color:#6b0;}
+a:hover {text-decoration:underline;}
+a:active {color:#b06;}
 .d30{ color: #000; } .l30{ color: #555; } .b40{ background-color: #000; } .b50{ background-color: #555 }
 .d31{ color: #B00; } .l31{ color: #F55; } .b41{ background-color: #B00; } .b51{ background-color: #F55 }
 .d32{ color: #0B0; } .l32{ color: #5F5; } .b42{ background-color: #0B0; } .b52{ background-color: #5F5 }
@@ -25,28 +27,28 @@ a:link {color: cyan;} a:visited {color: green;} a:hover {color: white;} a:active
 <span class='b49'><span class='d39'>
      <a href='#ACTION'>         ACTION</a>     <a href='#ALIAS'>          ALIAS</a>     <a href='#ALL'>            ALL</a>     <a href='#BELL'>           BELL</a>
      <a href='#BREAK'>          BREAK</a>     <a href='#BUFFER'>         BUFFER</a>     <a href='#BUTTON'>         BUTTON</a>     <a href='#CASE'>           CASE</a>
-     <a href='#CHARACTERS'>     CHARACTERS</a>     <a href='#CHAT'>           CHAT</a>     <a href='#CLASS'>          CLASS</a>     <a href='#COLORS'>         COLORS</a>
-     <a href='#COMMANDS'>       COMMANDS</a>     <a href='#COORDINATES'>    COORDINATES</a>     <a href='#CONFIG'>         CONFIG</a>     <a href='#CONTINUE'>       CONTINUE</a>
-     <a href='#CR'>             CR</a>     <a href='#CURSOR'>         CURSOR</a>     <a href='#DAEMON'>         DAEMON</a>     <a href='#DEBUG'>          DEBUG</a>
-     <a href='#DEFAULT'>        DEFAULT</a>     <a href='#DELAY'>          DELAY</a>     <a href='#DRAW'>           DRAW</a>     <a href='#ECHO'>           ECHO</a>
-     <a href='#ELSE'>           ELSE</a>     <a href='#ELSEIF'>         ELSEIF</a>     <a href='#END'>            END</a>     <a href='#ESCAPE CODES'>   ESCAPE CODES</a>
-     <a href='#EVENT'>          EVENT</a>     <a href='#FORALL'>         FORALL</a>     <a href='#FOREACH'>        FOREACH</a>     <a href='#FORMAT'>         FORMAT</a>
-     <a href='#FUNCTION'>       FUNCTION</a>     <a href='#GAG'>            GAG</a>     <a href='#GREETING'>       GREETING</a>     <a href='#GREP'>           GREP</a>
-     <a href='#HELP'>           HELP</a>     <a href='#HIGHLIGHT'>      HIGHLIGHT</a>     <a href='#HISTORY'>        HISTORY</a>     <a href='#IF'>             IF</a>
-     <a href='#IGNORE'>         IGNORE</a>     <a href='#INDEX'>          INDEX</a>     <a href='#INFO'>           INFO</a>     <a href='#KEYPAD'>         KEYPAD</a>
-     <a href='#KILL'>           KILL</a>     <a href='#LINE'>           LINE</a>     <a href='#LIST'>           LIST</a>     <a href='#LOCAL'>          LOCAL</a>
-     <a href='#LOG'>            LOG</a>     <a href='#LOOP'>           LOOP</a>     <a href='#MACRO'>          MACRO</a>     <a href='#MAP'>            MAP</a>
-     <a href='#MATH'>           MATH</a>     <a href='#MATHEMATICS'>    MATHEMATICS</a>     <a href='#MESSAGE'>        MESSAGE</a>     <a href='#MSDP'>           MSDP</a>
-     <a href='#NOP'>            NOP</a>     <a href='#PARSE'>          PARSE</a>     <a href='#PATH'>           PATH</a>     <a href='#PATHDIR'>        PATHDIR</a>
-     <a href='#PCRE'>           PCRE</a>     <a href='#PORT'>           PORT</a>     <a href='#PROMPT'>         PROMPT</a>     <a href='#READ'>           READ</a>
-     <a href='#REGEXP'>         REGEXP</a>     <a href='#REPEAT'>         REPEAT</a>     <a href='#REPLACE'>        REPLACE</a>     <a href='#RETURN'>         RETURN</a>
-     <a href='#RUN'>            RUN</a>     <a href='#SCAN'>           SCAN</a>     <a href='#SCREEN'>         SCREEN</a>     <a href='#SCREEN READER'>  SCREEN READER</a>
-     <a href='#SCRIPT'>         SCRIPT</a>     <a href='#SEND'>           SEND</a>     <a href='#SESSION'>        SESSION</a>     <a href='#SESSIONNAME'>    SESSIONNAME</a>
-     <a href='#SHOWME'>         SHOWME</a>     <a href='#SNOOP'>          SNOOP</a>     <a href='#SPEEDWALK'>      SPEEDWALK</a>     <a href='#SPLIT'>          SPLIT</a>
-     <a href='#SSL'>            SSL</a>     <a href='#STATEMENTS'>     STATEMENTS</a>     <a href='#SUBSTITUTE'>     SUBSTITUTE</a>     <a href='#SUSPEND'>        SUSPEND</a>
-     <a href='#SWITCH'>         SWITCH</a>     <a href='#SYSTEM'>         SYSTEM</a>     <a href='#TAB'>            TAB</a>     <a href='#TEXTIN'>         TEXTIN</a>
-     <a href='#TICKER'>         TICKER</a>     <a href='#TIME'>           TIME</a>     <a href='#VARIABLE'>       VARIABLE</a>     <a href='#WHILE'>          WHILE</a>
-     <a href='#WRITE'>          WRITE</a>     <a href='#ZAP'>            ZAP</a>
+     <a href='#CAT'>            CAT</a>     <a href='#CHARACTERS'>     CHARACTERS</a>     <a href='#CHAT'>           CHAT</a>     <a href='#CLASS'>          CLASS</a>
+     <a href='#COLORS'>         COLORS</a>     <a href='#COMMANDS'>       COMMANDS</a>     <a href='#COORDINATES'>    COORDINATES</a>     <a href='#CONFIG'>         CONFIG</a>
+     <a href='#CONTINUE'>       CONTINUE</a>     <a href='#CR'>             CR</a>     <a href='#CURSOR'>         CURSOR</a>     <a href='#DAEMON'>         DAEMON</a>
+     <a href='#DEBUG'>          DEBUG</a>     <a href='#DEFAULT'>        DEFAULT</a>     <a href='#DELAY'>          DELAY</a>     <a href='#DRAW'>           DRAW</a>
+     <a href='#ECHO'>           ECHO</a>     <a href='#ELSE'>           ELSE</a>     <a href='#ELSEIF'>         ELSEIF</a>     <a href='#END'>            END</a>
+     <a href='#ESCAPE CODES'>   ESCAPE CODES</a>     <a href='#EVENT'>          EVENT</a>     <a href='#FORALL'>         FORALL</a>     <a href='#FOREACH'>        FOREACH</a>
+     <a href='#FORMAT'>         FORMAT</a>     <a href='#FUNCTION'>       FUNCTION</a>     <a href='#GAG'>            GAG</a>     <a href='#GREETING'>       GREETING</a>
+     <a href='#GREP'>           GREP</a>     <a href='#HELP'>           HELP</a>     <a href='#HIGHLIGHT'>      HIGHLIGHT</a>     <a href='#HISTORY'>        HISTORY</a>
+     <a href='#IF'>             IF</a>     <a href='#IGNORE'>         IGNORE</a>     <a href='#INDEX'>          INDEX</a>     <a href='#INFO'>           INFO</a>
+     <a href='#KEYPAD'>         KEYPAD</a>     <a href='#KILL'>           KILL</a>     <a href='#LINE'>           LINE</a>     <a href='#LIST'>           LIST</a>
+     <a href='#LOCAL'>          LOCAL</a>     <a href='#LOG'>            LOG</a>     <a href='#LOOP'>           LOOP</a>     <a href='#MACRO'>          MACRO</a>
+     <a href='#MAP'>            MAP</a>     <a href='#MATH'>           MATH</a>     <a href='#MATHEMATICS'>    MATHEMATICS</a>     <a href='#MESSAGE'>        MESSAGE</a>
+     <a href='#METRIC SYSTEM'>  METRIC SYSTEM</a>     <a href='#MSDP'>           MSDP</a>     <a href='#NOP'>            NOP</a>     <a href='#PARSE'>          PARSE</a>
+     <a href='#PATH'>           PATH</a>     <a href='#PATHDIR'>        PATHDIR</a>     <a href='#PCRE'>           PCRE</a>     <a href='#PORT'>           PORT</a>
+     <a href='#PROMPT'>         PROMPT</a>     <a href='#READ'>           READ</a>     <a href='#REGEXP'>         REGEXP</a>     <a href='#REPEAT'>         REPEAT</a>
+     <a href='#REPLACE'>        REPLACE</a>     <a href='#RETURN'>         RETURN</a>     <a href='#RUN'>            RUN</a>     <a href='#SCAN'>           SCAN</a>
+     <a href='#SCREEN'>         SCREEN</a>     <a href='#SCREEN READER'>  SCREEN READER</a>     <a href='#SCRIPT'>         SCRIPT</a>     <a href='#SEND'>           SEND</a>
+     <a href='#SESSION'>        SESSION</a>     <a href='#SESSIONNAME'>    SESSIONNAME</a>     <a href='#SHOWME'>         SHOWME</a>     <a href='#SNOOP'>          SNOOP</a>
+     <a href='#SPEEDWALK'>      SPEEDWALK</a>     <a href='#SPLIT'>          SPLIT</a>     <a href='#SSL'>            SSL</a>     <a href='#STATEMENTS'>     STATEMENTS</a>
+     <a href='#SUBSTITUTE'>     SUBSTITUTE</a>     <a href='#SUSPEND'>        SUSPEND</a>     <a href='#SWITCH'>         SWITCH</a>     <a href='#SYSTEM'>         SYSTEM</a>
+     <a href='#TAB'>            TAB</a>     <a href='#TEXTIN'>         TEXTIN</a>     <a href='#TICKER'>         TICKER</a>     <a href='#TIME'>           TIME</a>
+     <a href='#VARIABLE'>       VARIABLE</a>     <a href='#WHILE'>          WHILE</a>     <a href='#WRITE'>          WRITE</a>     <a href='#ZAP'>            ZAP</a>
 
 
 <a name='ACTION'></a>
@@ -92,7 +94,7 @@ a:link {color: cyan;} a:visited {color: green;} a:hover {color: white;} a:active
 
 </span><span class='l32'>         ALIAS
 
-</span><span class='l37'>Command</span><span class='d37'>: #alias </span><span class='l37'>{</span><span class='d37'>name</span><span class='l37'>} {</span><span class='d37'>commands</span><span class='l37'>}</span><span class='d37'>
+</span><span class='l37'>Command</span><span class='d37'>: #alias </span><span class='l37'>{</span><span class='d37'>name</span><span class='l37'>} {</span><span class='d37'>commands</span><span class='l37'>} {</span><span class='d37'>priority</span><span class='l37'>}</span><span class='d37'>
 
          The #alias command can be used to shorten up long or oftenly used
          commands. The %1-99 variables are substituted from the arguments when
@@ -325,6 +327,15 @@ a:link {color: cyan;} a:visited {color: green;} a:hover {color: white;} a:active
          would return south.
 
 </span><span class='l37'>Related</span><span class='d37'>: default, statements and switch.
+<a name='CAT'></a>
+
+</span><span class='l32'>         CAT
+
+</span><span class='l37'>Command</span><span class='d37'>: #cat </span><span class='l37'>{</span><span class='d37'>variable</span><span class='l37'>} {</span><span class='d37'>argument</span><span class='l37'>}</span><span class='d37'>
+
+         The cat command will concatinate the argument to the given variable.
+
+</span><span class='l37'>Related</span><span class='d37'>: format, function, local, math, replace, script and variable.
 <a name='CHARACTERS'></a>
 
 </span><span class='l32'>         CHARACTERS
@@ -343,7 +354,7 @@ a:link {color: cyan;} a:visited {color: green;} a:hover {color: white;} a:active
          well. Trailing semi-colons are ignored when reading a script file
          as this is a common error.
 
-{ }      Curly brackets aka braces are used for seperating multi word command
+{ }      Curly brackets aka braces are used for separating multi word command
          arguments, nesting commands, and nesting variables. Braces cannot
          easily be escaped and must always be used in pairs.
 
@@ -405,13 +416,27 @@ a:link {color: cyan;} a:visited {color: green;} a:hover {color: white;} a:active
 
 </span><span class='l37'>Command</span><span class='d37'>: #class </span><span class='l37'>{</span><span class='d37'>name</span><span class='l37'>} {</span><span class='d37'>open</span><span class='l37'>|</span><span class='d37'>close</span><span class='l37'>|</span><span class='d37'>list</span><span class='l37'>|</span><span class='d37'>read</span><span class='l37'>|</span><span class='d37'>size</span><span class='l37'>|</span><span class='d37'>write</span><span class='l37'>|</span><span class='d37'>kill</span><span class='l37'>} {</span><span class='d37'>arg</span><span class='l37'>}</span><span class='d37'>
 
-         The {open} option will open a class, closing a previously opened
-         class. All triggers added afterwards are assigned to this class.
-         The {close} option will close the given class.
-         The {list} option will show the given list of the class.
-         The {read} option will open the class, read, and close afterwards.
-         The {size} option will store the size of the class in a variable.
-         The {write} option will write all triggers of the given class to file.
+         </span><span class='l37'>#class {&lt;name&gt;} {open}
+         </span><span class='d37'>  Open a class, closing a previously opened class. All triggers
+           added afterwards are assigned to this class.
+         </span><span class='l37'>#class {&lt;name&gt;} {clear}
+         </span><span class='d37'>  Will delete all triggers associated with the given class.
+         </span><span class='l37'>#class {&lt;name&gt;} {close}
+         </span><span class='d37'>  Close the given class, opening the last open class, if any.
+         </span><span class='l37'>#class {&lt;name&gt;} {kill}
+         </span><span class='d37'>  Will clear, close, and remove the class.
+         </span><span class='l37'>#class {&lt;name&gt;} {list}
+         </span><span class='d37'>  List all triggers associated with the given class.
+         </span><span class='l37'>#class {&lt;name&gt;} {load}
+         </span><span class='d37'>  Will load the saved copy of the class from memory.
+         </span><span class='l37'>#class {&lt;name&gt;} {read} {&lt;filename&gt;
+         </span><span class='d37'>  Will open the class, read the file, and close afterwards.
+         </span><span class='l37'>#class {&lt;name&gt;} {save}
+         </span><span class='d37'>  Will save all triggers of the given class to memory.
+         </span><span class='l37'>#class {&lt;name&gt;} {size} {&lt;variable&gt;}
+         </span><span class='d37'>  Will store the size of the class in a variable.
+         </span><span class='l37'>#class {&lt;name&gt;} {write} {&lt;filename&gt;}
+         </span><span class='d37'>  Will write all triggers of the given class to file.
          The {kill} option will delete all triggers of the given class.
 
          Keep in mind that the kill and read option are very fast allowing
@@ -503,7 +528,7 @@ a:link {color: cyan;} a:visited {color: green;} a:hover {color: white;} a:active
 </span><span class='l37'>Command</span><span class='d37'>: #config </span><span class='l37'>{</span><span class='d37'>option</span><span class='l37'>} {</span><span class='d37'>argument</span><span class='l37'>}</span><span class='d37'>
 
          This allows you to configure various settings, the settings can be
-         written to file with the #write or #writesession command.
+         written to file with the #write command.
 
          If you configure the global session (the one you see as you start up
          tintin) all sessions started will inherite these settings.
@@ -517,7 +542,7 @@ a:link {color: cyan;} a:visited {color: green;} a:hover {color: white;} a:active
          #CONFIG {CHILD LOCK}   {ON|OFF} Enable or disable command input.
          #CONFIG {CONVERT META} {ON|OFF} Shows color codes and key bindings.
          #CONFIG {DEBUG TELNET} {ON|OFF} Shows telnet negotiations y/n.
-         #CONFIG {LOG LEVEL}  {LOW|HIGH} LOW logs mud output before triggers.
+         #CONFIG {LOG LEVEL}  {LOW|HIGH} LOW logs server output before triggers.
          #CONFIG {INHERITANCE}  {ON|OFF} Session trigger inheritance y/n.
          #CONFIG {MCCP}         {ON|OFF} Enable or disable MCCP support.
          #CONFIG {PID}          {NUMBER} Set the PID of the master process.
@@ -629,7 +654,7 @@ a:link {color: cyan;} a:visited {color: green;} a:hover {color: white;} a:active
          before executing the given command. tintin won't wait before
          executing following input commands if any.
 
-         Floating point precision for miliseconds is possible.
+         Floating point precision for milliseconds is possible.
 
 </span><span class='l37'>Example</span><span class='d37'>: #showme first;#delay {1} {#showme last}
          This will print 'first', and 'last' around one second later.
@@ -665,15 +690,21 @@ a:link {color: cyan;} a:visited {color: green;} a:hover {color: white;} a:active
          BUMPED     will precede the draw with an enter.
          CIRCLED    will circle the corners.
          CONVERT    will draw text with meta conversion.
-         CORNERED   will draw lines with corners if possible.
          CROSSED    will cross the corners.
+         FILLED     will fill circles and jewels.
+         GRID       will draw TABLE as a grid.
          HORIZONTAL will draw horizontal if possible.
+         HUGE       will draw text in huge letters.
          JEWELED    will diamond the corners.
+         JOINTED    will draw corners.
          LEFT       will draw on the left side if possible.
+         NUMBERED   will draw numbered, mostly for debugging.
          PRUNED     will prune the corners.
          RIGHT      will draw on the right side if possible.
          ROUNDED    will round the corners.
+         SHADOWED   will shadow HUGE text.
          TEED       will tee the corners.
+         TRACED     will trace HUGE text.
          TOP        will draw on the top side if possible.
          TUBED      will draw tubes instead of lines.
          UNICODE    will draw in unicode mode.
@@ -681,16 +712,26 @@ a:link {color: cyan;} a:visited {color: green;} a:hover {color: white;} a:active
 
          The following types are available.
 
-         BOX        will draw a box.
-         LINE       will draw a line.
-         SIDE       will draw one or more sides of a box.
-         TILE       will draw a tile
+         </span><span class='l37'>[ASCII|UNICODE|HUGE] BOX {[TEXT1]} {[TEXT2]}
+         </span><span class='d37'>  will draw a box.
+         </span><span class='l37'>[BLANKED|CIRCLED|CROSSED|JEWELED|ROUNDED|TEED|PRUNED] CORNER
+         </span><span class='d37'>  will draw a corner.
+         </span><span class='l37'>[BLANKED|HORIZONTAL|NUMBERED|TUBED|VERTICAL] LINE {[TEXT]}
+         </span><span class='d37'>  will draw a line.
+         </span><span class='l37'>RAIN {&lt;VARIABLE&gt;} {[SPAWN]} {[FADE]} {[LEGEND]}
+         </span><span class='d37'>  will draw digital rain.
+         </span><span class='l37'>[JOINTED|TOP|LEFT|BOTTOM|RIGHT] SIDE
+         </span><span class='d37'>  will draw one or more sides of a box.
+         </span><span class='l37'>[GRID] TABLE {[LIST1]} {[LIST2]}
+         </span><span class='d37'> will draw a table.
+         </span><span class='l37'>[HUGE] TILE {[TEXT1]} {[TEXT2]}
+         </span><span class='d37'>  will draw a tile
 
          All draw types take an optional text argument as long as a valid
          square with enough space has been defined. Text is automatically
          word wrapped.
 
-</span><span class='l37'>Example</span><span class='d37'>: #draw {box} 1 1 3 20 {Hello world!}
+</span><span class='l37'>Example</span><span class='d37'>: #draw Blue box 1 1 3 20 {Hello world!}
 
 </span><span class='l37'>Related</span><span class='d37'>: buffer, echo, grep and showme.
 <a name='ECHO'></a>
@@ -700,7 +741,7 @@ a:link {color: cyan;} a:visited {color: green;} a:hover {color: white;} a:active
 </span><span class='l37'>Command</span><span class='d37'>: #echo </span><span class='l37'>{</span><span class='d37'>format</span><span class='l37'>} {</span><span class='d37'>argument1</span><span class='l37'>} {</span><span class='d37'>argument2</span><span class='l37'>} {</span><span class='d37'>etc</span><span class='l37'>}</span><span class='d37'>
 
          Echo command displays text on the screen with formatting options. See
-         the help file for the format command for more informations.
+         the help file for the format command for more information.
 
          The echo command does not trigger actions.
 
@@ -711,7 +752,7 @@ a:link {color: cyan;} a:visited {color: green;} a:hover {color: white;} a:active
          #echo {[%38s][%-38s]} {Hello World} {Hello World}
          #echo {{this is %s on the top row} {-1}} {printed}
 
-</span><span class='l37'>Related</span><span class='d37'>: buffer, grep and showme.
+</span><span class='l37'>Related</span><span class='d37'>: buffer, format, grep and showme.
 <a name='ELSE'></a>
 
 </span><span class='l32'>         ELSE
@@ -799,8 +840,8 @@ a:link {color: cyan;} a:visited {color: green;} a:hover {color: white;} a:active
          IAC &lt;VAR&gt; &lt;VAR&gt;
          IAC SB GMCP &lt;MODULE&gt;   %0 data     %1 raw data
          IAC SB MSSP            %0 variable %1 value
-         IAC SB MSDP            %0 variable %1 value
-         IAC SB MSDP &lt;VAR&gt;      %1 value
+         IAC SB MSDP            %0 variable %1 value %2 plain value
+         IAC SB MSDP &lt;VAR&gt;      %0 variable %1 value %2 plain value
          IAC SB NEW-ENVIRON     %0 variable %1 value
          IAC SB ZMP &lt;VAR&gt;       %0 value
          IAC SB &lt;VAR&gt;           %0 raw text %1 raw data
@@ -812,6 +853,7 @@ a:link {color: cyan;} a:visited {color: green;} a:hover {color: white;} a:active
          MAP EXIT ROOM          %0 old vnum %1 new vnum
          MAP EXIT ROOM &lt;VAR&gt;    %0 old vnum %1 new vnum
          MAP FOLLOW MAP         %0 old vnum %1 new vnum %2 exit name
+         MAP MOUSE LOCATION     %0 vnum %1 location
          MAP UPDATED VTMAP
          MINUTE                 %5 minute
          MONTH                  %1 month
@@ -835,7 +877,10 @@ a:link {color: cyan;} a:visited {color: green;} a:hover {color: white;} a:active
          SCAN CSV LINE          %0 all args %1 arg1 %2 arg3 .. %99 arg99
          SCAN TSV HEADER        %0 all args %1 arg1 %2 arg3 .. %99 arg99
          SCAN TSV LINE          %0 all args %1 arg1 %2 arg3 .. %99 arg99
-         SCREEN RESIZE          %0 rows %1 cols %1 height %2 width
+         SCREEN FOCUS           %0 focus (0 or 1)
+         SCREEN LOCATION        %0 rows %1 cols  %2 height %3 width
+         SCREEN MOUSE LOCATION  %0-3 screen row/col %4-7 cell row/col %8 loc
+         SCREEN RESIZE          %0 rows %1 cols %2 height %3 width
          SCREEN SPLIT           %0 top row %1 top col %2 bot row %3 bot col
          SCREEN UNSPLIT         %0 top row %1 top col %2 bot row %3 bot col
          SCROLLED &lt;VAR&gt;         %0 row %1 col %2 -row %3 -col %4 word %5 line
@@ -849,6 +894,9 @@ a:link {color: cyan;} a:visited {color: green;} a:hover {color: white;} a:active
          SESSION DISCONNECTED   %0 name %1 host %2 ip %3 port
          SESSION TIMED OUT      %0 name %1 host %2 ip %3 port
          SHORT-CLICKED &lt;VAR&gt;    %0 row %1 col %2 -row %3 -col %4 word %5 line
+         SWIPED &lt;DIR&gt;
+           %0 dir %1 button %2 row %3 col %4 -row %5 -col %6 row %7 col %8 -row
+           %9 -col %10 rows %11 cols
          SYSTEM ERROR           %0 name %1 system msg %2 error %3 error msg
          TIME                   %4 hour : %5 minute : %6 second
          TRIPLE-CLICKED &lt;VAR&gt;   %0 row %1 col %2 -row %3 -col %4 word %5 line
@@ -857,8 +905,6 @@ a:link {color: cyan;} a:visited {color: green;} a:hover {color: white;} a:active
          VARIABLE UPDATED &lt;VAR&gt; %0 name %1 new value
          VT100 SCROLL REGION    %0 top row %1 bot row %2 rows %3 cols %4 wrap
          WEEK &lt;DAY&gt;             %2 day of the week
-         WINDOW FOCUS IN        %0 name
-         WINDOW FOCUS OUT       %0 name
          WRITE ERROR            %0 filename %1 error message
          YEAR                   %0 year
 
@@ -876,7 +922,7 @@ a:link {color: cyan;} a:visited {color: green;} a:hover {color: white;} a:active
 
 </span><span class='l37'>This command is obsolete, please use foreach instead.
 
-Related</span><span class='d37'>: cr
+Related</span><span class='d37'>: foreach
 <a name='FOREACH'></a>
 
 </span><span class='l32'>         FOREACH
@@ -922,13 +968,16 @@ Related</span><span class='d37'>: cr
          #format {test} {%t}   {format}  display time with strftime format
                                          optional {{format}{time}} syntax
          #format {test} {%u}   {string}  uppercase text
-         #format {list} {%w}   {string}  store wordwrapped text in {list}
+         #format {list} {%w}   {string}  store word wrapped text in {list}
                                          optional {{string}{width}} syntax
          #format {test} {%x}      {hex}  print corresponding charset character
          #format {test} {%A}     {char}  store corresponding character value
+         #format {test} {%C}   {number}  store number in chronological notation
          #format {test} {%D}      {hex}  convert hex to decimal in {test}
          #format {hash} {%H}   {string}  store a 64 bit string hash in {hash}
          #format {test} {%L}   {string}  store the string length in {test}
+         #format {test} {%M}   {number}  convert number to metric in {test}
+         #format {test} {%S}   {string}  store the number of spelling errors
          #format {time} {%T}         {}  store the epoch time in {time}
          #format {time} {%U}         {}  store the micro epoch time in {time}
          #format {test} {%X}      {dec}  convert dec to hexadecimal in {test}
@@ -937,7 +986,7 @@ Related</span><span class='d37'>: cr
 
 </span><span class='l37'>Comment</span><span class='d37'>: See #help TIME for help on the %t argument.
 
-</span><span class='l37'>Related</span><span class='d37'>: echo, function, local, math, replace, script, time and variable.
+</span><span class='l37'>Related</span><span class='d37'>: cat, echo, function, local, math, replace, script, time and variable.
 <a name='FUNCTION'></a>
 
 </span><span class='l32'>         FUNCTION
@@ -985,11 +1034,11 @@ Related</span><span class='d37'>: cr
 </span><span class='l32'>         GREETING
 
 </span><span class='d36'>      ####################################################################
-      #</span><span class='d37'>                     T I N T I N + +   2.01.92b                   </span><span class='d36'>#
       #</span><span class='d37'>                                                                  </span><span class='d36'>#
-      #</span><span class='d37'>                 </span><span class='d36'>T</span><span class='d37'>he K</span><span class='d36'>i</span><span class='d37'>cki</span><span class='d36'>n</span><span class='d37'> </span><span class='d36'>T</span><span class='d37'>ickin D</span><span class='d36'>i</span><span class='d37'>kuMUD Clie</span><span class='d36'>n</span><span class='d37'>t </span><span class='d36'>                #
+      #</span><span class='d37'>                    T I N T I N + +   2.01.93b                    </span><span class='d36'>#
       #</span><span class='d37'>                                                                  </span><span class='d36'>#
       #</span><span class='d37'>      Code by Peter Unold, Bill Reis, and Igor van den Hoven      </span><span class='d36'>#
+      #</span><span class='d37'>                                                                  </span><span class='d36'>#
       ####################################################################
 
 <a name='GREP'></a>
@@ -1026,7 +1075,7 @@ Related</span><span class='d37'>: cr
 
 </span><span class='l32'>         HIGHLIGHT
 
-</span><span class='l37'>Command</span><span class='d37'>: #highlight </span><span class='l37'>{</span><span class='d37'>string</span><span class='l37'>} {</span><span class='d37'>color names</span><span class='l37'>}</span><span class='d37'>
+</span><span class='l37'>Command</span><span class='d37'>: #highlight </span><span class='l37'>{</span><span class='d37'>string</span><span class='l37'>} {</span><span class='d37'>color names</span><span class='l37'>} {</span><span class='d37'>priority</span><span class='l37'>}</span><span class='d37'>
 
          The highlight command is used to allow you to highlight strings of text.
 
@@ -1112,7 +1161,7 @@ Related</span><span class='d37'>: cr
          when you press enter on an empty line.
 
          You can press ctrl-r to enter an interactive regex enabled history
-         search mode, or by issueing #cursor {history search}.
+         search mode, or by issuing #cursor {history search}.
 
          TinTin++ tries to bind the arrow up and down keys to scroll through
          the history list by default. You can bind these with a macro yourself
@@ -1186,7 +1235,7 @@ Related</span><span class='d37'>: cr
 
          All TinTin++ commands starts with a '#'.
 
-</span><span class='l37'>Example</span><span class='d37'>: #help -- #help is a mud client command, and isn't send to the mud
+</span><span class='l37'>Example</span><span class='d37'>: #help -- #help is a client command, and isn't send to the
          server.
 
          All TinTin++ commands can be abbreviated when typed.
@@ -1201,12 +1250,12 @@ Related</span><span class='d37'>: cr
          There are 3 ways ';'s can be overruled.
 
          &bsol;say Hello ;) -- Lines starting with a '&bsol;' aren't parsed by TinTin++.
-         say Hello &bsol;;) -- The escape character can esape 1 letter.
+         say Hello &bsol;;) -- The escape character can escape 1 letter.
          #config verbatim on -- Everything is send as is except '#' commands.
 </span><span class='l32'>
-         Connecting to a mud
+         Connecting to a server
 </span><span class='l37'>
-Command</span><span class='d37'>: #session </span><span class='l37'>{</span><span class='d37'>session name</span><span class='l37'>} {</span><span class='d37'>mud address</span><span class='l37'>} {</span><span class='d37'>port</span><span class='l37'>}</span><span class='d37'>
+Command</span><span class='d37'>: #session </span><span class='l37'>{</span><span class='d37'>session name</span><span class='l37'>} {</span><span class='d37'>server address</span><span class='l37'>} {</span><span class='d37'>port</span><span class='l37'>}</span><span class='d37'>
 
          Example: #session someone tintin.sourceforge.net 4321
 
@@ -1215,8 +1264,7 @@ Command</span><span class='d37'>: #session </span><span class='l37'>{</span><spa
 
          You can get a list of all sessions by typing: #session. The current
          active session is marked with (active). Snooped sessions with
-         (snooped). MCCP sessions (mud client compression protocol) with
-         (mccp 2).
+         (snooped). MCCP sessions (compression) with (mccp 2) and (mccp 3).
 
 </span><span class='l32'>
          Split
@@ -1299,7 +1347,7 @@ Command</span><span class='d37'>: #alias </span><span class='l37'>{</span><span
 </span><span class='l37'>Command</span><span class='d37'>: #highlight </span><span class='l37'>{</span><span class='d37'>text</span><span class='l37'>} {</span><span class='d37'>color</span><span class='l37'>}</span><span class='d37'>
 
          This command works a bit like #action. The purpose of this command is
-         to substitute text from the mud with color you provide. This command
+         to substitute text from the server with color you provide. This command
          is a simplified version of the #substitute command.
 
 </span><span class='l37'>Example</span><span class='d37'>: #high </span><span class='l37'>{</span><span class='d37'>Snowy</span><span class='l37'>} {</span><span class='d37'>light yellow</span><span class='l37'>}</span><span class='d37'>
@@ -1328,10 +1376,9 @@ Command</span><span class='d37'>: #alias </span><span class='l37'>{</span><span
 
 </span><span class='l37'>Command</span><span class='d37'>: #ticker </span><span class='l37'>{</span><span class='d37'>name</span><span class='l37'>} {</span><span class='d37'>commands</span><span class='l37'>} {</span><span class='d37'>seconds</span><span class='l37'>}</span><span class='d37'>
 
-         Every 60 seconds on a standard dikumud a so called tick occures. You
-         regenerate hp/mana/move faster if you're sleeping/resting during a
-         tick. So it's pretty nice to know when the next tick occures. TinTin++
-         helps you with that.
+         The name can be whatever you want it to be, and is only required for
+         the unticker command. The commands will be executed every x amount of
+         seconds, which is specified in the interval part.
 
 </span><span class='l37'>Example</span><span class='d37'>: #tick </span><span class='l37'>{</span><span class='d37'>tick</span><span class='l37'>} {</span><span class='d37'>#delay 50 #show 10 SECONDS TO TICK!;#show TICK!!!</span><span class='l37'>} {</span><span class='d37'>60</span><span class='l37'>}</span><span class='d37'>
 
@@ -1345,7 +1392,7 @@ Command</span><span class='d37'>: #alias </span><span class='l37'>{</span><span
 </span><span class='d37'>
          When you order TinTin++ to read a command file, it parses all the text
          in the file. You can use command files to keep aliases/actions in,
-         login to a mud (name, password etc..) and basically all kinds of
+         login to a server (name, password etc..) and basically all kinds of
          commands.
 
          You can make the command files with either a text editor (suggested),
@@ -1436,6 +1483,9 @@ Command</span><span class='d37'>: #alias </span><span class='l37'>{</span><span
          This data is written to the info variable.
 
          #info cpu will show information about tintin's cpu usage.
+         #info mccp will show information about data compression.
+         #info stack will show the low level debugging stack.
+         #info session will show some session information.
          #info system will show some system information.
 
 </span><span class='l37'>Related</span><span class='d37'>: class, debug, ignore, kill and message.
@@ -1446,19 +1496,19 @@ Command</span><span class='d37'>: #alias </span><span class='l37'>{</span><span
 </span><span class='d37'>When TinTin++ starts up it sends &bsol;e= to the terminal to enable the terminal's
 application keypad mode, which can be disabled using #showme {&bsol;e&gt;}
 
-</span><span class='l37'>     Configuration A            Configuration B            Configuration C</span><span class='d37'>
-+-----+-----+-----+-----+  +-----+-----+-----+-----+  +-----+-----+-----+-----+
-|Num  |/    |*    |-    |  |Num  |/    |*    |-    |  |Num  |nkp/ |nkp* |nkp- |
-+-----+-----+-----+-----+  +-----+-----+-----+-----+  +-----+-----+-----+-----+
-|7    |8    |9    |     |  |Home |Up   |PgUp |     |  |nkp7 |nkp8 |nkp9 |     |
-+-----+-----+-----+     |  +-----+-----+-----+     |  +-----+-----+-----+     |
-|4    |5    |6    |+    |  |Left |Centr|Right|+    |  |nkp4 |nkp5 |nkp6 |nkp+ |
-+-----+-----+-----+-----+  +-----+-----+-----+-----+  +-----+-----+-----+-----+
-|1    |2    |3    |     |  |End  |Down |PgDn |     |  |nkp1 |nkp2 |nkp3 |     |
-+-----+-----+-----+     |  +-----+-----+-----+     |  +-----+-----+-----+     |
-|0          |.    |Enter|  |Ins        |Del  |Enter|  |nkp0       |nkp. |nkpEn|
-+-----------+-----+-----+  +-----------+-----+-----+  +-----------+-----+-----+
-
+</span><span class='l37'>      Configuration A           Configuration B           Configuration C</span><span class='d36'>
+ ╭─────┬─────┬─────┬─────╮ ╭─────┬─────┬─────┬─────╮ ╭─────┬─────┬─────┬─────╮
+ │</span><span class='l37'>num</span><span class='d36'>  │</span><span class='l37'>/</span><span class='d36'>    │</span><span class='l37'>*</span><span class='d36'>    │</span><span class='l37'>-</span><span class='d36'>    │ │</span><span class='l37'>num</span><span class='d36'>  │</span><span class='l37'>/</span><span class='d36'>    │</span><span class='l37'>*</span><span class='d36'>    │</span><span class='l37'>-</span><span class='d36'>    │ │</span><span class='l37'>Num</span><span class='d36'>  │</span><span class='l37'>nkp/</span><span class='d36'> │</span><span class='l37'>nkp*</span><span class='d36'> │</span><span class='l37'>nkp-</span><span class='d36'> │
+ ├─────┼─────┼─────┼─────┤ ├─────┼─────┼─────┼─────┤ ├─────┼─────┼─────┼─────┤
+ │</span><span class='l37'>7</span><span class='d36'>    │</span><span class='l37'>8</span><span class='d36'>    │</span><span class='l37'>9</span><span class='d36'>    │</span><span class='l37'>+</span><span class='d36'>    │ │</span><span class='l37'>Home</span><span class='d36'> │</span><span class='l37'>Up</span><span class='d36'>   │</span><span class='l37'>PgUp</span><span class='d36'> │</span><span class='l37'>+</span><span class='d36'>    │ │</span><span class='l37'>nkp7</span><span class='d36'> │</span><span class='l37'>nkp8</span><span class='d36'> │</span><span class='l37'>nkp9</span><span class='d36'> │</span><span class='l37'>nkp+</span><span class='d36'> │
+ ├─────┼─────┼─────┤     │ ├─────┼─────┼─────┤     │ ├─────┼─────┼─────┤     │
+ │</span><span class='l37'>4</span><span class='d36'>    │</span><span class='l37'>5</span><span class='d36'>    │</span><span class='l37'>6</span><span class='d36'>    │     │ │</span><span class='l37'>Left</span><span class='d36'> │</span><span class='l37'>Cntr</span><span class='d36'> │</span><span class='l37'>Right</span><span class='d36'>│     │ │</span><span class='l37'>nkp4</span><span class='d36'> │</span><span class='l37'>nkp5</span><span class='d36'> │</span><span class='l37'>nkp6</span><span class='d36'> │     │
+ ├─────┼─────┼─────┼─────┤ ├─────┼─────┼─────┼─────┤ ├─────┼─────┼─────┼─────┤
+ │</span><span class='l37'>1</span><span class='d36'>    │</span><span class='l37'>2</span><span class='d36'>    │</span><span class='l37'>3</span><span class='d36'>    │</span><span class='l37'>Enter</span><span class='d36'>│ │</span><span class='l37'>End</span><span class='d36'>  │</span><span class='l37'>Down</span><span class='d36'> │</span><span class='l37'>PgDn</span><span class='d36'> │</span><span class='l37'>Enter</span><span class='d36'>│ │</span><span class='l37'>nkp1</span><span class='d36'> │</span><span class='l37'>nkp2</span><span class='d36'> │</span><span class='l37'>nkp3</span><span class='d36'> │</span><span class='l37'>nkpEn</span><span class='d36'>│
+ ├─────┴─────┼─────┤     │ ├─────┴─────┼─────┤     │ ├─────┴─────┼─────┤     │
+ │</span><span class='l37'>0</span><span class='d36'>          │</span><span class='l37'>.</span><span class='d36'>    │     │ │</span><span class='l37'>Ins</span><span class='d36'>        │</span><span class='l37'>Del</span><span class='d36'>  │     │ │</span><span class='l37'>nkp0</span><span class='d36'>       │</span><span class='l37'>nkp.</span><span class='d36'> │     │
+ ╰───────────┴─────┴─────╯ ╰───────────┴─────┴─────╯ ╰───────────┴─────┴─────╯
+</span><span class='d37'>
 With keypad mode disabled numlock on will give you configuration A, and numlock
 off will give you configuration B. With keypad mode enabled you'll get
 configuration C.
@@ -1507,6 +1557,9 @@ Terminal -&gt; Window Settings -&gt; Emulation.
          </span><span class='l37'>#line background &lt;argument&gt;
          </span><span class='d37'>  Prevent new session activation.
 
+         </span><span class='l37'>#line capture &lt;variable&gt; &lt;argument.
+         </span><span class='d37'>  Argument is executed and output stored in &lt;variable&gt;.
+
          </span><span class='l37'>#line debug &lt;argument&gt;
          </span><span class='d37'>  Argument is executed in debug mode.
 
@@ -1558,12 +1611,15 @@ Terminal -&gt; Window Settings -&gt; Emulation.
 
          #list {var} {add} {item}               Add {item} to the list
          #list {var} {clear}                    Empty the given list
+         #list {var} {collapse}                 Turn list into a variable
          #list {var} {create} {item}            Create a list using {items}
          #list {var} {delete} {index} {number}  Delete the item at {index},
                                                 the {number} is optional.
+         #list {var} {explode}                  Turn list into a character list
          #list {var} {insert} {index} {string}  Insert {string} at given index
          #list {var} {find} {string} {variable} Return the found index
          #list {var} {get} {index} {variable}   Copy an item to {variable}
+         #list {var} {shuffle}                  Shuffle the list
          #list {var} {set} {index} {string}     Change the item at {index}
          #list {var} {simplify} {variable}      Copy simple list to {variable}
          #list {var} {size} {variable}          Copy list size to {variable}
@@ -1609,10 +1665,10 @@ Terminal -&gt; Window Settings -&gt; Emulation.
 
 </span><span class='l32'>         LOG
 
-</span><span class='l37'>Command</span><span class='d37'>: #log </span><span class='l37'>{</span><span class='d37'>append</span><span class='l37'>|</span><span class='d37'>overwrite</span><span class='l37'>} {</span><span class='d37'>filename</span><span class='l37'>}</span><span class='d37'>
+</span><span class='l37'>Command</span><span class='d37'>: #log </span><span class='l37'>{</span><span class='d37'>append</span><span class='l37'>|</span><span class='d37'>overwrite</span><span class='l37'>|</span><span class='d37'>off</span><span class='l37'>} {</span><span class='d37'>[filename]</span><span class='l37'>}</span><span class='d37'>
 
-         Logs session to a file, you can set the data type to either plain,
-         raw, or html with the config command.
+         Logs session output to a file, you can set the data type to either
+         plain, raw, or html with the config command.
 
 </span><span class='l37'>Related</span><span class='d37'>: read, scan, textin and write.
 <a name='LOOP'></a>
@@ -1689,8 +1745,8 @@ Terminal -&gt; Window Settings -&gt; Emulation.
          </span><span class='l37'>#map create &lt;size&gt;
          </span><span class='d37'>  Creates a new map and room 1. The default size is 50000 rooms.
 
-         </span><span class='l37'>#map destroy
-         </span><span class='d37'>  Deletes the map.
+         </span><span class='l37'>#map destroy {area|world} &lt;name&gt;
+         </span><span class='d37'>  Deletes the map or given area.
 
          </span><span class='l37'>#map delete &lt;exit|vnum&gt;
          </span><span class='d37'>  Deletes the room for the given exit or vnum.
@@ -1705,12 +1761,16 @@ Terminal -&gt; Window Settings -&gt; Emulation.
            to the given room vnum. If the room vnum doesn't exist a new
            room is created.
 
+         </span><span class='l37'>#map entrance &lt;exit&gt; [option] [arg]
+         </span><span class='d37'>  Set the entrance data for the given exit. You must specify a
+           valid two-way exit for this to work.
+
          </span><span class='l37'>#map exit &lt;exit&gt; &lt;option&gt; &lt;arg&gt;
          </span><span class='d37'>  Set the exit data. Useful with a closed door where you can
            set the exit command: '#map exit e command {open east;e}'.
            Use #map exit &lt;exit&gt; for a list of available options.
 
-         </span><span class='l37'>#map exitflag &lt;exit&gt; &lt;HIDE|AVOID&gt; [on|off]
+         </span><span class='l37'>#map exitflag &lt;exit&gt; &lt;AVOID|BLOCK|HIDE|INVIS&gt; [on|off]
          </span><span class='d37'>  Set exit flags. See #map roomflag for more info.
 
          </span><span class='l37'>#map explore &lt;exit&gt;
@@ -1788,6 +1848,12 @@ Terminal -&gt; Window Settings -&gt; Emulation.
          </span><span class='d37'>  Jump to the given coordinate, which is relative
            to your current room.
 
+         </span><span class='l37'>#map landmark &lt;name&gt; &lt;vnum&gt; [description] [size]
+         </span><span class='d37'>  Creates an alias to target the provided room vnum. The
+           description is optional and should be brief. The size
+           determines from how many rooms away the landmark can be
+           seen.
+
          </span><span class='l37'>#map leave
          </span><span class='d37'>  Makes you leave the map. Useful when entering a maze. You
            can return to your last known room using #map return.
@@ -1862,12 +1928,17 @@ Terminal -&gt; Window Settings -&gt; Emulation.
          </span><span class='d37'>
          </span><span class='l37'>#map roomflag avoid
          </span><span class='d37'>  When set, '#map find' will avoid a route leading
-           through that room. Useful when you want to avoid death traps.
+           through that room. Useful for locked doors, etc.
+         </span><span class='l37'>#map roomflag block
+         </span><span class='d37'>  When set the automapper will prevent movement into or through
+           the room. Useful for death traps.
          </span><span class='l37'>#map roomflag hide
          </span><span class='d37'>  When set, '#map' will not display the map beyond
            this room. When mapping overlapping areas or areas that aren't
            build consistently you need this flag as well to stop
            auto-linking, unless you use void rooms.
+         </span><span class='l37'>#map roomflag invis
+         </span><span class='d37'>  When set the room will be colored with the INVIS color.
          </span><span class='l37'>#map roomflag leave
          </span><span class='d37'>  When entering a room with this flag, you will
            automatically leave the map. Useful when set at the entrance
@@ -1894,6 +1965,22 @@ Terminal -&gt; Window Settings -&gt; Emulation.
          </span><span class='d37'>  Set a map value for your current room, or given room if a room
            vnum is provided.
 
+         </span><span class='l37'>#map sync &lt;filename&gt;
+         </span><span class='d37'>  Similar to #map read except the current map won't be unloaded
+           or overwritten.
+
+         </span><span class='l37'>#map terrain &lt;name&gt; &lt;symbol&gt; [flag]
+         </span><span class='d37'>  Set the terrain symbol and flag.
+
+         </span><span class='l37'>#map terrain &lt;name&gt; &lt;symbol&gt; [DENSE|SPARSE|SCANT]
+         </span><span class='d37'>  Determine symbol density, omit for the default.
+
+         </span><span class='l37'>#map terrain &lt;name&gt; &lt;symbol&gt; [NARROW|WIDE|VAST]
+         </span><span class='d37'>  Determine symbol spread range, omit for the default.
+
+         </span><span class='l37'>#map terrain &lt;name&gt; &lt;symbol&gt; [FADEIN|FADEOUT]
+         </span><span class='d37'>  Determine symbol spread density, omit for the default.
+
          </span><span class='l37'>#map travel &lt;direction&gt; &lt;delay&gt;
          </span><span class='d37'>  Follows the direction until a dead end or an intersection is
            found. Use braces around the direction if you use the delay,
@@ -1908,11 +1995,17 @@ Terminal -&gt; Window Settings -&gt; Emulation.
          </span><span class='l37'>#map uninsert &lt;direction&gt;
          </span><span class='d37'>  Exact opposite of the insert command.
 
+         </span><span class='l37'>#map unlandmark &lt;name&gt;
+         </span><span class='d37'>  Removes a landmark.
+
          </span><span class='l37'>#map unlink &lt;direction&gt; [both]
          </span><span class='d37'>  Will remove the exit, this isn't two way so you can have the
            properly display no exit rooms and mazes.
            If you use the both argument the exit is removed two-ways.
 
+         </span><span class='l37'>#map unterrain &lt;name&gt;
+         </span><span class='d37'>  Removes a terrain.
+
          </span><span class='l37'>#map update
          </span><span class='d37'>  Sets the vtmap to update within the next 0.1 seconds.
 
@@ -1990,7 +2083,7 @@ Terminal -&gt; Window Settings -&gt; Emulation.
            {#if {{%0} == {Bubba} &amp;&amp; &dollar;afk} {reply I'm away, my friend.}}
          When you are away from keyboard, it will only reply to your friend.
 
-</span><span class='l37'>Related</span><span class='d37'>: format, function, local, mathematics, replace, script and variable.
+</span><span class='l37'>Related</span><span class='d37'>: cat, format, function, local, mathematics, replace, script and variable.
 <a name='MATHEMATICS'></a>
 
 </span><span class='l32'>         MATHEMATICS
@@ -2023,7 +2116,7 @@ Terminal -&gt; Window Settings -&gt; Emulation.
          ^^             10            logical xor
          ||             11            logical or
 
-Operator priority can be ignored by using paranthesis, for example (1 + 1) * 2
+Operator priority can be ignored by using parentheses, for example (1 + 1) * 2
 equals 4, while 1 + 1 * 2 equals 3.
 
 </span><span class='l37'>String operations</span><span class='d37'>
@@ -2053,6 +2146,19 @@ on the right being the regex. For example {bla} == {%*a} would evaluate as 1.
          spammed when correctly using the #VARIABLE and #UNVARIABLE commands.
 
 </span><span class='l37'>Related</span><span class='d37'>: class, debug, ignore, info and kill.
+<a name='METRIC SYSTEM'></a>
+
+</span><span class='l32'>         METRIC SYSTEM
+
+  Name  Symbol                              Factor
+--------------------------------------------------
+  Mega       M                           1 000 000
+  Kilo       K                               1 000
+
+ milli       m                               0.001
+ micro       u                           0.000 001
+
+</span><span class='l37'>Related</span><span class='d37'>: echo, format and math.
 <a name='MSDP'></a>
 
 </span><span class='l32'>         MSDP
@@ -2233,7 +2339,7 @@ Example: %3 says '%*'} {#if {&quot;%4&quot; == &quot;Tickle me&quot;} {tickle %3
          Perl Compatible Regular Expressions</span><span class='d37'>
 
          You can embed a PCRE (Perl Compatible Regular Expression) using curley
-         braces { }, these braces are replaced with paranthesis ( ) unless you
+         braces { }, these braces are replaced with parentheses ( ) unless you
          use %!{ }.
 </span><span class='l37'>
          Or</span><span class='d37'>
@@ -2277,17 +2383,17 @@ Example: #act {%* says 'Who is number {[1-9][0-9]{0,2}} {Say &dollar;number[%2]
          The example only triggers if someone provides a number between 1 and
          999.
 
-         </span><span class='l37'>Paranthesis</span><span class='d37'>
+         </span><span class='l37'>Parantheses</span><span class='d37'>
 
          TinTin Regular Expressions automatically add parenthesis, for example
          %* translates to (.*?) in PCRE unless the %* is found at the start or
          end of the line, in which cases it translates to (.*). Paranthesis in
          PCRE causes a change in execution priority similar to mathematical
-         expressions, but paranthesis also causes the match to be stored to a
+         expressions, but parentheses also causes the match to be stored to a
          variable.
 
-         When nesting multiple sets of paranthesis each nest is assigned its
-         numercial variable in order of appearance.
+         When nesting multiple sets of parentheses each nest is assigned its
+         numerical variable in order of appearance.
 
 Example: #act {%* chats '{Mu(ha)+}'} {chat %2ha!}
 
@@ -2344,8 +2450,8 @@ Example: #regex {bli bla blo} {^{.*?} {.*?}&dollar;} {#showme Arg1=(&amp;1) Arg2
 
 Example: #action {~&bsol;e[1;37m%1} {#var roomname %1}
 
-         If the room name is the only line on the mud in bright white this
-         color trigger will save the roomname.
+         If the room name is the only line on the server in bright white
+         white color trigger will save the roomname.
 
 
          This covers the basics. PCRE has more options, most of which are
@@ -2390,7 +2496,7 @@ Example: #action {~&bsol;e[1;37m%1} {#var roomname %1}
          </span><span class='d37'>  Send data to socket
 
          </span><span class='l37'>#port {uninitialize}
-         </span><span class='d37'>  Unitialize the port session.
+         </span><span class='d37'>  Uninitialize the port session.
 
          </span><span class='l37'>#port {who}
          </span><span class='d37'>  Show all connections
@@ -2402,6 +2508,8 @@ Example: #action {~&bsol;e[1;37m%1} {#var roomname %1}
          new session dedicated to receiving socket connections at the given
          port number without built-in support for a communication protocol.
 
+         You can init with 0 as the port number to create a dummy session.
+
 </span><span class='l37'>Related</span><span class='d37'>: all, chat, run, session, sessionname, snoop, ssl and zap.
 <a name='PROMPT'></a>
 
@@ -2475,16 +2583,26 @@ Example: #action {~&bsol;e[1;37m%1} {#var roomname %1}
 
          Of the following the (lazy) match is available at %1-%99 + 1
 
-      %w match zero to any number of word characters.
-      %W match zero to any number of non word characters.
+      %a match zero to any number of characters except newlines.
+      %A match zero to any number of newlines.
       %d match zero to any number of digits.
       %D match zero to any number of non digits.
+      %p match zero to any number of printable characters.
+      %P match zero to any number of non printable characters.
       %s match zero to any number of spaces.
       %S match zero to any number of non spaces.
+      %u match zero to any number of unicode characters.
+      %U match zero to any number of non unicode characters.
+      %w match zero to any number of word characters.
+      %W match zero to any number of non word characters.
+
+      If you want to match 1 digit use %+1d, if you want to match between 3
+      and 5 spaces use %+3..5s, if you want to match between 0 and 1 word
+      characters use %+0..1w
 
+      %+ match one to any number of characters.
       %? match zero or one character.
-      %. match one character. (</span><span class='l31'>do not use</span><span class='d37'>)
-      %+ match one to any number of characters. (</span><span class='l31'>do not use</span><span class='d37'>)
+      %. match one character.
       %* match zero to any number of characters.
 
       %i matching becomes case insensitive.
@@ -2516,10 +2634,10 @@ easiest way to accomplish that.
 
 </span><span class='l37'>Command</span><span class='d37'>: #replace </span><span class='l37'>{</span><span class='d37'>variable</span><span class='l37'>} {</span><span class='d37'>oldtext</span><span class='l37'>} {</span><span class='d37'>newtext</span><span class='l37'>}</span><span class='d37'>
 
-         Searches the variable text replacing each occurance of 'oldtext' with
+         Searches the variable text replacing each occurrence of 'oldtext' with
          'newtext'.
 
-</span><span class='l37'>Related</span><span class='d37'>: format, function, local, math, script and variable.
+</span><span class='l37'>Related</span><span class='d37'>: cat, format, function, local, math, script and variable.
 <a name='RETURN'></a>
 
 </span><span class='l32'>         RETURN
@@ -2541,7 +2659,7 @@ easiest way to accomplish that.
 
          The run command works much like the system command except that it
          runs the command in a pseudo terminal. The run command also creates
-         a session that treats the given shell command as a mud server. This
+         a session that treats the given shell command as a server. This
          allows you to run ssh, as well as any other shell application, with
          full tintin scripting capabilities. If a file name is given the file
          is loaded prior to execution.
@@ -2594,7 +2712,7 @@ easiest way to accomplish that.
          </span><span class='l37'>#scan {txt} &lt;filename&gt;
 
          </span><span class='d37'>  The scan txt &lt;filename&gt; command reads in a file and sends its content
-           to the screen as if it was send by a mud. After using scan you can
+           to the screen as if it was send by a server. After using scan you can
            use page-up and down to view the file.
 
            This command is useful to convert ansi color files to html or viewing
@@ -2674,10 +2792,10 @@ easiest way to accomplish that.
 
 </span><span class='l37'>Command</span><span class='d37'>: #config </span><span class='l37'>{</span><span class='d37'>SCREEN READER</span><span class='l37'>} {</span><span class='d37'>ON|OFF</span><span class='l37'>}</span><span class='d37'>
 
-         Screen reader mode is enabled by using #config screen on. The main
-         purpose of the screen reader mode is to tell MUDs that a screen reader
-         is being used by using the MTTS standard. The MTTS specification is
-         available at:
+         Screen reader mode is enabled by using #config screen on.  The main
+         purpose of the screen reader mode is to report to servers that a
+         screen reader is being used by utilizing the MTTS standard.  The MTTS
+         specification is available at:
 
          http://tintin.sourceforge.net/protocols/mtts
 
@@ -2712,8 +2830,8 @@ easiest way to accomplish that.
 
 </span><span class='l37'>Command</span><span class='d37'>: #send </span><span class='l37'>{</span><span class='d37'>text</span><span class='l37'>}</span><span class='d37'>
 
-         Sends the text directly to the MUD, useful if you want to start with an
-         escape code.
+         Sends the text directly to the server, useful if you want to start
+         with an escape code.
 
 </span><span class='l37'>Related</span><span class='d37'>: textin
 <a name='SESSION'></a>
@@ -2754,7 +2872,7 @@ easiest way to accomplish that.
          The startup session is named 'gts' and can be used for relog scripts. Do
          keep in mind that tickers do not work in the startup session.
 
-</span><span class='l37'>Example</span><span class='d37'>: #event {SESSION DISCONNECTED} {#gts #delay 10 #ses %0 mymud.com 4321}
+</span><span class='l37'>Example</span><span class='d37'>: #event {SESSION DISCONNECTED} {#gts #delay 10 #ses %0 tintin.net 4321}
 
 </span><span class='l37'>Related</span><span class='d37'>: all, port, run, sessionname, snoop, ssl and zap.
 <a name='SESSIONNAME'></a>
@@ -2773,7 +2891,7 @@ name, the session name, prepended with a hashtag, can be used to activate the
 session when used without an argument. If an argument is given it will be
 executed by that session as a command, the session will not be activated.
 
-</span><span class='l37'>Example</span><span class='d37'>: #ses one mymud.com 23;#ses two mymud.com 23;#one;#two grin
+</span><span class='l37'>Example</span><span class='d37'>: #ses one tintin.net 23;#ses two tintin.net 23;#one;#two grin
 
 This will create two sessions, the session that was created last (two in this
 case) will be automatically activated upon creation. Using #one, session one is
@@ -2787,9 +2905,9 @@ session one will remain the active session.
 
 </span><span class='l37'>Command</span><span class='d37'>: #showme </span><span class='l37'>{</span><span class='d37'>string</span><span class='l37'>} {</span><span class='d37'>row</span><span class='l37'>} {</span><span class='d37'>col</span><span class='l37'>}</span><span class='d37'>
 
-         Display the string to the terminal, do not send to the mud.  Useful for
-         status, warnings, etc.  The {row} and col number are optional and work
-         the same way as the row number of the #prompt trigger.
+         Display the string to the terminal, do not send to the server.  Useful
+         for status, warnings, etc.  The {row} and col number are optional and
+         work the same way as the row number of the #prompt trigger.
 
          Actions can be triggered by the showme command. If you want to avoid
          this from happening use: #line ignore #showme {&lt;string&gt;}.
@@ -2817,15 +2935,16 @@ session one will remain the active session.
 
 </span><span class='l32'>         SPEEDWALK
 
-         </span><span class='d37'>Speedwalking allows you to type multiple directions not separated by
-         semicolons, and now it lets you prefix a direction with a number, to
-         signify how many times to go that direction. You can turn it on/off
-         with #config.
+         </span><span class='d37'>Speedwalking allows you to enter multiple directions without using
+         semicolons. Directions should be prefixed with a number and will be
+         executed the given number of times.
+
+         You can enable speedwalking with #CONFIG {SPEEDWALK} {ON}.
 
 </span><span class='l37'>Example</span><span class='d37'>: Without speedwalk, you have to type:
          s;s;w;w;w;w;w;s;s;s;w;w;w;n;n;w
          With speedwalk, you only have to type:
-         2s5w3s3w2nw
+         2s5w3s3w2n1w
 
 </span><span class='l37'>Related</span><span class='d37'>: alias, cursor, history, keypad, macro and tab.
 <a name='SPLIT'></a>
@@ -2912,9 +3031,9 @@ Related</span><span class='d37'>: all, port, run, sessionname, snoop, ssl and za
 
 </span><span class='l32'>         SUBSTITUTE
 
-</span><span class='l37'>Command</span><span class='d37'>: #substitute </span><span class='l37'>{</span><span class='d37'>text</span><span class='l37'>} {</span><span class='d37'>new text</span><span class='l37'>}</span><span class='d37'>
+</span><span class='l37'>Command</span><span class='d37'>: #substitute </span><span class='l37'>{</span><span class='d37'>text</span><span class='l37'>} {</span><span class='d37'>new text</span><span class='l37'>} {</span><span class='d37'>priority</span><span class='l37'>}</span><span class='d37'>
 
-         Allows you to replace original text from the mud with different text.
+         Allows you to replace text from the server with the new text.
          This is helpful for complex coloring and making things more readable.
          The %1-%99 variables can be used to capture text and use it as part of
          the new output, and the ^ char is valid to only check the beginning of
@@ -3005,7 +3124,7 @@ Related</span><span class='d37'>: all, port, run, sessionname, snoop, ssl and za
 </span><span class='l37'>Command</span><span class='d37'>: #textin </span><span class='l37'>{</span><span class='d37'>filename</span><span class='l37'>} {</span><span class='d37'>delay</span><span class='l37'>}</span><span class='d37'>
 
          Textin allows the user to read in a file, and send its contents
-         directly to the mud.  Useful for doing online creation, or message
+         directly to the server.  Useful for doing online creation, or message
          writing.
 
          The delay is in seconds and takes a floating point number which is
@@ -3123,7 +3242,7 @@ Related</span><span class='d37'>: all, port, run, sessionname, snoop, ssl and za
 
 </span><span class='l37'>Comment</span><span class='d37'>: You can remove a variable with the #unvariable command.
 
-</span><span class='l37'>Related</span><span class='d37'>: format, function, local, math, replace and script.
+</span><span class='l37'>Related</span><span class='d37'>: cat, format, function, local, math, replace and script.
 <a name='WHILE'></a>
 
 </span><span class='l32'>         WHILE

+ 5 - 4
docs/tintin19.txt

@@ -133,13 +133,14 @@
 
      Usage: #alias {name} {commands}
 
-     Use this command to define aliases. The variables %0, %1.. %9 contain the
-     arguments to the aliases-command as follows:
+     Use this command to define aliases. The variables %0, %1.. %99 contain
+     the arguments to the aliases-command as follows:
 
      the %0 variable contains ALL the arguments.
-     the %1 variable contains the 1. argument
+     the %1 variable contains the 1st argument
+     the %2 variable contains the 2nd argument
      ....
-     the %9 variable contains the 9. argument
+     the %99 variable contains the 99th argument
 
      Example: #alias nice say Hello Mr %1
 

+ 1790 - 0
draw.c

@@ -0,0 +1,1790 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2019                       *
+******************************************************************************/
+
+static char draw_buf[100][10];
+static int  draw_cnt;
+
+#include "tintin.h"
+
+DO_COMMAND(do_draw)
+{
+	char *sub_arg, arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], color[BUFFER_SIZE], code[BUFFER_SIZE], arg3[BUFFER_SIZE], input[BUFFER_SIZE];
+	int index, flags;
+	int top_row, top_col, bot_row, bot_col, rows, cols;
+
+	substitute(ses, arg, input, SUB_VAR|SUB_FUN);
+
+	arg = input;
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+
+	draw_cnt = 0;
+	*color = 0;
+	*code = 0;
+
+	if (*arg1 == 0)
+	{
+		tintin_header(ses, " DRAW OPTIONS ");
+
+		for (index = 0 ; *draw_table[index].fun ; index++)
+		{
+			if (*draw_table[index].name)
+			{
+				tintin_printf2(ses, "  [%-24s] %s", draw_table[index].name, draw_table[index].desc);
+			}
+		}
+		tintin_header(ses, "");
+	}
+	else
+	{
+		flags = HAS_BIT(ses->charset, CHARSET_FLAG_UTF8) ? DRAW_FLAG_UTF8 : 0;
+
+		while (*arg)
+		{
+			if (!HAS_BIT(flags, DRAW_FLAG_COLOR) && translate_color_names(ses, arg1, code))
+			{
+				get_color_names(ses, arg1, color);
+
+				SET_BIT(flags, DRAW_FLAG_COLOR);
+			}
+			else if (!HAS_BIT(flags, DRAW_FLAG_COLOR) && strip_vt102_strlen(ses, arg1) == 0)
+			{
+				strcpy(color, arg1);
+				SET_BIT(flags, DRAW_FLAG_COLOR);
+			}
+			else if (is_abbrev(arg1, "ASCII"))
+			{
+				DEL_BIT(flags, DRAW_FLAG_UTF8);
+			}
+			else if (is_abbrev(arg1, "BLANKED"))
+			{
+				SET_BIT(flags, DRAW_FLAG_BLANKED);
+			}
+			else if (is_abbrev(arg1, "BOTTOM"))
+			{
+				SET_BIT(flags, DRAW_FLAG_BOT);
+			}
+			else if (is_abbrev(arg1, "BUMPED"))
+			{
+				SET_BIT(flags, DRAW_FLAG_BUMP);
+			}
+			else if (is_abbrev(arg1, "CIRCLED"))
+			{
+				SET_BIT(flags, DRAW_FLAG_CIRCLED);
+			}
+			else if (is_abbrev(arg1, "CONVERT"))
+			{
+				SET_BIT(flags, DRAW_FLAG_CONVERT);
+			}
+			else if (is_abbrev(arg1, "CROSSED"))
+			{
+				SET_BIT(flags, DRAW_FLAG_CROSSED);
+			}
+			else if (is_abbrev(arg1, "FILLED"))
+			{
+				SET_BIT(flags, DRAW_FLAG_FILLED);
+			}
+			else if (is_abbrev(arg1, "HORIZONTAL"))
+			{
+				SET_BIT(flags, DRAW_FLAG_HOR);
+			}
+			else if (is_abbrev(arg1, "JEWELED"))
+			{
+				SET_BIT(flags, DRAW_FLAG_JEWELED);
+			}
+			else if (is_abbrev(arg1, "JOINTED"))
+			{
+				SET_BIT(flags, DRAW_FLAG_CORNERED);
+			}
+			else if (is_abbrev(arg1, "LEFT"))
+			{
+				SET_BIT(flags, DRAW_FLAG_LEFT);
+			}
+			else if (is_abbrev(arg1, "GRID"))
+			{
+				SET_BIT(flags, DRAW_FLAG_GRID);
+			}
+			else if (is_abbrev(arg1, "HUGE"))
+			{
+				SET_BIT(flags, DRAW_FLAG_HUGE);
+			}
+			else if (is_abbrev(arg1, "NUMBERED"))
+			{
+				SET_BIT(flags, DRAW_FLAG_NUMBERED);
+			}
+			else if (is_abbrev(arg1, "PRUNED"))
+			{
+				SET_BIT(flags, DRAW_FLAG_PRUNED);
+			}
+			else if (is_abbrev(arg1, "RIGHT"))
+			{
+				SET_BIT(flags, DRAW_FLAG_RIGHT);
+			}
+			else if (is_abbrev(arg1, "ROUNDED"))
+			{
+				SET_BIT(flags, DRAW_FLAG_ROUNDED);
+			}
+			else if (is_abbrev(arg1, "SHADOWED"))
+			{
+				SET_BIT(flags, DRAW_FLAG_SHADOWED);
+			}
+			else if (is_abbrev(arg1, "TEED"))
+			{
+				SET_BIT(flags, DRAW_FLAG_TEED);
+			}
+			else if (is_abbrev(arg1, "TOP"))
+			{
+				SET_BIT(flags, DRAW_FLAG_TOP);
+			}
+			else if (is_abbrev(arg1, "TRACED"))
+			{
+				SET_BIT(flags, DRAW_FLAG_TRACED);
+			}
+			else if (is_abbrev(arg1, "TUBED"))
+			{
+				SET_BIT(flags, DRAW_FLAG_TUBED);
+			}
+			else if (is_abbrev(arg1, "UNICODE"))
+			{
+				SET_BIT(flags, DRAW_FLAG_UTF8);
+			}
+			else if (is_abbrev(arg1, "VERTICAL"))
+			{
+				SET_BIT(flags, DRAW_FLAG_VER);
+			}
+			else
+			{
+				break;
+			}
+			arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+		}
+
+		for (index = 0 ; *draw_table[index].name ; index++)
+		{
+			if (is_abbrev(arg1, draw_table[index].name))
+			{
+				arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+
+				sub_arg = get_arg_in_braces(ses, arg1, arg3, GET_ONE);
+
+				if (*sub_arg)
+				{
+					strcpy(arg1, arg3);
+					sub_arg = get_arg_in_braces(ses, sub_arg, arg2, GET_ONE);
+				}
+				else
+				{
+					arg = get_arg_in_braces(ses, arg, arg2, GET_ONE);
+				}
+
+				top_row = get_row_index(ses, arg1);
+				top_col = get_col_index(ses, arg2);
+
+				if (*sub_arg)
+				{
+					sub_arg = get_arg_in_braces(ses, sub_arg, arg1, GET_ONE);
+					sub_arg = get_arg_in_braces(ses, sub_arg, arg2, GET_ONE);
+				}
+				else
+				{
+					arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+					arg = get_arg_in_braces(ses, arg, arg2, GET_ONE);
+				}
+				bot_row = get_row_index(ses, arg1);
+				bot_col = get_col_index(ses, arg2);
+
+				if (top_row == 0 && top_col == 0)
+				{
+					show_error(ses, LIST_COMMAND, "#SYNTAX: #DRAW [COLOR] [OPTIONS] {%s} <TOP_ROW> <TOP_COL> <BOT_ROW> <BOT_COL> [TEXT]", draw_table[index].name);
+
+					return ses;
+				}
+
+				if (top_row == 0)
+				{
+					top_row = 1;
+
+					SET_BIT(flags, DRAW_FLAG_SCROLL);
+				}
+
+				if (top_col == 0)
+				{
+					top_col = 1;
+				}
+
+				if (bot_row == 0)
+				{
+					bot_row = 1;
+				}
+
+				if (bot_col == 0)
+				{
+					bot_col = 1;
+				}
+
+				if (top_row > bot_row || top_col > bot_col)
+				{
+					show_error(ses, LIST_COMMAND, "#ERROR: #DRAW INVALID SQUARE: %s {%d %d %d %d} ROWS: %d COLS: %d", draw_table[index].name, top_row, top_col, bot_row, bot_col, 1 + bot_row - top_row, 1 + bot_col - top_col);
+
+					return ses;
+				}
+				
+				rows = URANGE(1, 1 + bot_row - top_row, gtd->screen->rows);
+				cols = URANGE(1, 1 + bot_col - top_col, gtd->screen->cols);
+
+				*arg1 = 0;
+				*arg2 = 0;
+				*arg3 = 0;
+
+				if (*arg == 0)
+				{
+					arg = arg3;
+				}
+
+				save_pos(ses);
+
+				if (HAS_BIT(flags, DRAW_FLAG_BUMP))
+				{
+					tintin_printf2(ses, "");
+				}
+
+				strcpy(arg2, code);
+
+				draw_table[index].fun(ses, top_row, top_col, bot_row, bot_col, rows, cols, draw_table[index].flags | flags, color, arg, arg1, arg2);
+
+				print_stdout("\e[0m");
+
+				restore_pos(ses);
+
+				return ses;
+			}
+		}
+		show_error(ses, LIST_COMMAND, "#ERROR: #DRAW {%s} IS NOT A VALID OPTION.", capitalize(arg1));
+	}
+
+	return ses;
+}
+
+// utilities
+
+int find_stamp(char *in, char *out)
+{
+	int cnt;
+
+	for (cnt = 0 ; huge_stamp_table[cnt].name != NULL ; cnt++)
+	{
+		if (!strcmp(in, huge_stamp_table[cnt].name))
+		{
+			strcpy(out, huge_stamp_table[cnt].desc);
+
+			return huge_stamp_table[cnt].length;
+		}
+	}
+	tintin_printf2(gtd->ses, "debug: didn't find stamp {%s}", in);
+
+	return 0;
+}
+
+void stamp_cat(char *color, int flags, char *str, char *cat, char *out)
+{
+	char *pts = str;
+	char *ptc = cat;
+	char *pto = out;
+
+	while (*pts || *ptc)
+	{
+		while (*pts && *pts != '\n')
+		{
+			*pto++ = *pts++;
+		}
+
+		pto += sprintf(pto, "%s", color);
+
+		while (*ptc && *ptc != '\n')
+		{
+			if (HAS_BIT(flags, DRAW_FLAG_SHADOWED))
+			{
+				*pto++ = *ptc++;
+			}
+			else if (HAS_BIT(flags, DRAW_FLAG_TRACED))
+			{
+				if (!strncmp(ptc, "╗", 3))
+				{
+					pto += sprintf(pto, "┐");
+					ptc += strlen("╗");
+				}
+				else if (!strncmp(ptc, "║", 3))
+				{
+					pto += sprintf(pto, "│");
+					ptc += strlen("║");
+				}
+				else if (!strncmp(ptc, "╝", 3))
+				{
+					pto += sprintf(pto, "┘");
+					ptc += strlen("╝");
+				}
+				else if (!strncmp(ptc, "╚", 3))
+				{
+					pto += sprintf(pto, "└");
+					ptc += strlen("╚");
+				}
+				else if (!strncmp(ptc, "╔", 3))
+				{
+					pto += sprintf(pto, "┌");
+					ptc += strlen("╔");
+				}
+				else if (!strncmp(ptc, "═", 3))
+				{
+					pto += sprintf(pto, "─");
+					ptc += strlen("═");
+				}
+				else
+				{
+					*pto++ = *ptc++;
+				}
+			}
+			else
+			{
+				if (!strncmp(ptc, "╗", 3))
+				{
+					pto += sprintf(pto, " ");
+					ptc += strlen("╗");
+				}
+				else if (!strncmp(ptc, "║", 3))
+				{
+					pto += sprintf(pto, " ");
+					ptc += strlen("║");
+				}
+				else if (!strncmp(ptc, "╝", 3))
+				{
+					pto += sprintf(pto, " ");
+					ptc += strlen("╝");
+				}
+				else if (!strncmp(ptc, "╚", 3))
+				{
+					pto += sprintf(pto, " ");
+					ptc += strlen("╚");
+				}
+				else if (!strncmp(ptc, "╔", 3))
+				{
+					pto += sprintf(pto, " ");
+					ptc += strlen("╔");
+				}
+				else if (!strncmp(ptc, "═", 3))
+				{
+					pto += sprintf(pto, " ");
+					ptc += strlen("═");
+				}
+				else
+				{
+					*pto++ = *ptc++;
+				}
+			}
+		}
+
+		if (*pts == '\n' && *ptc == '\n')
+		{
+			*pto++ = *pts++;
+			ptc++;
+		}
+		else if (*ptc == '\n')
+		{
+			*pto++ = *ptc++;
+		}
+	}
+	*pto = 0;
+}
+
+void string_to_stamp(struct session *ses, int flags, char *in, char *out)
+{
+	char *pti, buf1[BUFFER_SIZE], buf2[BUFFER_SIZE], buf3[BUFFER_SIZE], chr1[CHAR_SIZE], color[COLOR_SIZE] = { 0 };
+	int skip;
+
+	push_call("string_to_stamp(%p,%d,%p,%p)",ses,flags,in,out);
+
+	sub_arg_in_braces(ses, in, buf1, GET_ALL, SUB_COL|SUB_ESC);
+
+	pti = buf1;
+
+	buf3[0] = 0;
+
+	while (*pti)
+	{
+		skip = skip_vt102_codes(pti);
+
+		if (skip)
+		{
+			get_color_codes(color, pti, color, GET_ONE);
+
+			pti += skip;
+			
+			continue;
+		}
+		pti = get_char(ses, pti, chr1);
+
+		find_stamp(chr1, buf2);
+
+		stamp_cat(color, flags, buf3, buf2, out);
+
+		strcpy(buf3, out);
+	}
+	strcat(out, "\n");
+
+	pop_call();
+	return;
+}
+
+char *get_draw_corner(int flags, char *str)
+{
+	draw_cnt = (draw_cnt + 1) % 100;
+
+	if (HAS_BIT(flags, DRAW_FLAG_BLANKED))
+	{
+		strcpy(draw_buf[draw_cnt], " ");
+	}
+	else if (HAS_BIT(flags, DRAW_FLAG_NUMBERED))
+	{
+		sprintf(draw_buf[draw_cnt], "%d", draw_cnt % 10);
+	}
+	else if (HAS_BIT(flags, DRAW_FLAG_UTF8))
+	{
+		if (HAS_BIT(flags, DRAW_FLAG_PRUNED))
+		{
+			if (HAS_BIT(flags, DRAW_FLAG_SCROLL))
+			{
+				strcpy(draw_buf[draw_cnt], " ");
+			}
+			else
+			{
+				strcpy(draw_buf[draw_cnt], "\e[C");
+			}
+		}
+		else if (HAS_BIT(flags, DRAW_FLAG_CIRCLED))
+		{
+			if (HAS_BIT(flags, DRAW_FLAG_CROSSED))
+			{
+				strcpy(draw_buf[draw_cnt], "ϴ");
+			}
+			else if (HAS_BIT(flags, DRAW_FLAG_FILLED))
+			{
+				strcpy(draw_buf[draw_cnt], "⬤");
+			}
+			else if (HAS_BIT(flags, DRAW_FLAG_VER))
+			{
+				strcpy(draw_buf[draw_cnt], "O");
+			}
+			else
+			{
+				strcpy(draw_buf[draw_cnt], "○");
+			}
+		}
+		else if (HAS_BIT(flags, DRAW_FLAG_CROSSED))
+		{
+			if (HAS_BIT(flags, DRAW_FLAG_TUBED))
+			{
+				strcpy(draw_buf[draw_cnt], "╬");
+			}
+			else
+			{
+				strcpy(draw_buf[draw_cnt], "┼");
+			}
+		}
+		else if (HAS_BIT(flags, DRAW_FLAG_JEWELED))
+		{
+			if (HAS_BIT(flags, DRAW_FLAG_FILLED))
+			{
+				strcpy(draw_buf[draw_cnt], "⧫");
+			}
+			else
+			{
+				strcpy(draw_buf[draw_cnt], "◊");
+			}
+		}
+		else if (HAS_BIT(flags, DRAW_FLAG_TEED))
+		{
+			if (HAS_BIT(flags, DRAW_FLAG_HOR))
+			{
+				if (HAS_BIT(flags, DRAW_FLAG_LEFT))
+				{
+					if (HAS_BIT(flags, DRAW_FLAG_TUBED))
+					{
+						strcpy(draw_buf[draw_cnt], "╠");
+					}
+					else
+					{
+						strcpy(draw_buf[draw_cnt], "├");
+					}
+				}
+				else
+				{
+					if (HAS_BIT(flags, DRAW_FLAG_TUBED))
+					{
+						strcpy(draw_buf[draw_cnt], "╣");
+					}
+					else
+					{
+						strcpy(draw_buf[draw_cnt], "┤");
+					}
+				}
+			}
+			else
+			{
+				if (HAS_BIT(flags, DRAW_FLAG_TOP))
+				{
+					if (HAS_BIT(flags, DRAW_FLAG_TUBED))
+					{
+						strcpy(draw_buf[draw_cnt], "╦");
+					}
+					else
+					{
+						strcpy(draw_buf[draw_cnt], "┬");
+					}
+				}
+				else
+				{
+					if (HAS_BIT(flags, DRAW_FLAG_TUBED))
+					{
+						strcpy(draw_buf[draw_cnt], "╩");
+					}
+					else
+					{
+						strcpy(draw_buf[draw_cnt], "┴");
+					}
+				}
+			}
+		}
+		else if (HAS_BIT(flags, DRAW_FLAG_BOXED) || HAS_BIT(flags, DRAW_FLAG_CORNERED))
+		{
+			if (HAS_BIT(flags, DRAW_FLAG_LEFT))
+			{
+				if (HAS_BIT(flags, DRAW_FLAG_TOP))
+				{
+					if (HAS_BIT(flags, DRAW_FLAG_ROUNDED))
+					{
+						strcpy(draw_buf[draw_cnt], "╭");
+					}
+					else
+					{
+						if (HAS_BIT(flags, DRAW_FLAG_TUBED))
+						{
+							strcpy(draw_buf[draw_cnt], "╔");
+						}
+						else
+						{
+							strcpy(draw_buf[draw_cnt], "┌");
+						}
+					}
+				}
+				else if (HAS_BIT(flags, DRAW_FLAG_BOT))
+				{
+					if (HAS_BIT(flags, DRAW_FLAG_ROUNDED))
+					{
+						strcpy(draw_buf[draw_cnt], "╰");
+					}
+					else
+					{
+						if (HAS_BIT(flags, DRAW_FLAG_TUBED))
+						{
+							strcpy(draw_buf[draw_cnt], "╚");
+						}
+						else
+						{
+							strcpy(draw_buf[draw_cnt], "└");
+						}
+					}
+				}
+				else
+				{
+					if (HAS_BIT(flags, DRAW_FLAG_HOR))
+					{
+						if (HAS_BIT(flags, DRAW_FLAG_TUBED))
+						{
+							strcpy(draw_buf[draw_cnt], "═");
+						}
+						else
+						{
+							strcpy(draw_buf[draw_cnt], "─");
+						}
+					}
+					else
+					{
+						if (HAS_BIT(flags, DRAW_FLAG_TUBED))
+						{
+							strcpy(draw_buf[draw_cnt], "║");
+						}
+						else
+						{
+							strcpy(draw_buf[draw_cnt], "│");
+						}
+					}
+				}
+			}
+			else if (HAS_BIT(flags, DRAW_FLAG_RIGHT))
+			{
+				if (HAS_BIT(flags, DRAW_FLAG_TOP))
+				{
+					if (HAS_BIT(flags, DRAW_FLAG_ROUNDED))
+					{
+						strcpy(draw_buf[draw_cnt], "╮");
+					}
+					else
+					{
+						if (HAS_BIT(flags, DRAW_FLAG_TUBED))
+						{
+							strcpy(draw_buf[draw_cnt], "╗");
+						}
+						else
+						{
+							strcpy(draw_buf[draw_cnt], "┐");
+						}
+					}
+				}
+				else if (HAS_BIT(flags, DRAW_FLAG_BOT))
+				{
+					if (HAS_BIT(flags, DRAW_FLAG_ROUNDED))
+					{
+						strcpy(draw_buf[draw_cnt], "╯");
+					}
+					else
+					{
+						if (HAS_BIT(flags, DRAW_FLAG_TUBED))
+						{
+							strcpy(draw_buf[draw_cnt], "╝");
+						}
+						else
+						{
+							strcpy(draw_buf[draw_cnt], "┘");
+						}
+					}
+				}
+				else
+				{
+					if (HAS_BIT(flags, DRAW_FLAG_HOR))
+					{
+						if (HAS_BIT(flags, DRAW_FLAG_TUBED))
+						{
+							strcpy(draw_buf[draw_cnt], "═");
+						}
+						else
+						{
+							strcpy(draw_buf[draw_cnt], "─");
+						}
+					}
+					else
+					{
+						if (HAS_BIT(flags, DRAW_FLAG_TUBED))
+						{
+							strcpy(draw_buf[draw_cnt], "║");
+						}
+						else
+						{
+							strcpy(draw_buf[draw_cnt], "│");
+						}
+					}
+				}
+			}
+			else
+			{
+				if (HAS_BIT(flags, DRAW_FLAG_HOR))
+				{
+					if (HAS_BIT(flags, DRAW_FLAG_TUBED))
+					{
+						strcpy(draw_buf[draw_cnt], "═");
+					}
+					else
+					{
+						strcpy(draw_buf[draw_cnt], "─");
+					}
+				}
+				else
+				{
+					if (HAS_BIT(flags, DRAW_FLAG_TUBED))
+					{
+						strcpy(draw_buf[draw_cnt], "║");
+					}
+					else
+					{
+						strcpy(draw_buf[draw_cnt], "│");
+					}
+				}
+			}
+		}
+		else
+		{
+			if (HAS_BIT(flags, DRAW_FLAG_HOR))
+			{
+				if (HAS_BIT(flags, DRAW_FLAG_TUBED))
+				{
+					strcpy(draw_buf[draw_cnt], "═");
+				}
+				else
+				{
+					strcpy(draw_buf[draw_cnt], "─");
+				}
+			}
+			else if (HAS_BIT(flags, DRAW_FLAG_VER))
+			{
+				if (HAS_BIT(flags, DRAW_FLAG_TUBED))
+				{
+					strcpy(draw_buf[draw_cnt], "║");
+				}
+				else
+				{
+					strcpy(draw_buf[draw_cnt], "│");
+				}
+			}
+			else
+			{
+				strcpy(draw_buf[draw_cnt], "?");
+			}
+		}
+	}
+	else
+	{
+		if (HAS_BIT(flags, DRAW_FLAG_PRUNED))
+		{
+			strcpy(draw_buf[draw_cnt], "\e[C");
+		}
+		else if (HAS_BIT(flags, DRAW_FLAG_CIRCLED) || HAS_BIT(flags, DRAW_FLAG_ROUNDED))
+		{
+			strcpy(draw_buf[draw_cnt], "o");
+		}
+		else if (HAS_BIT(flags, DRAW_FLAG_CROSSED))
+		{
+			strcpy(draw_buf[draw_cnt], "+");
+		}
+		else
+		{
+			strcpy(draw_buf[draw_cnt], "+");
+		}
+	}
+	return draw_buf[draw_cnt];
+}
+
+char *draw_horizontal(int flags, char *str)
+{
+	draw_cnt = (draw_cnt + 1) % 100;
+
+	if (HAS_BIT(flags, DRAW_FLAG_BLANKED))
+	{
+		strcpy(draw_buf[draw_cnt], " ");
+	}
+	else if (HAS_BIT(flags, DRAW_FLAG_NUMBERED))
+	{
+		sprintf(draw_buf[draw_cnt], "%d", draw_cnt % 10);
+	}
+	else if (HAS_BIT(flags, DRAW_FLAG_UTF8))
+	{
+		if (HAS_BIT(flags, DRAW_FLAG_TUBED))
+		{
+			strcpy(draw_buf[draw_cnt], "═");
+		}
+		else
+		{
+			strcpy(draw_buf[draw_cnt], "─");
+		}
+	}
+	else
+	{
+		strcpy(draw_buf[draw_cnt], "-");
+	}
+	return draw_buf[draw_cnt];
+}
+
+char *draw_vertical(int flags, char *str)
+{
+	draw_cnt = (draw_cnt + 1) % 100;
+
+	if (HAS_BIT(flags, DRAW_FLAG_BLANKED))
+	{
+		strcpy(draw_buf[draw_cnt], " ");
+	}
+	else if (HAS_BIT(flags, DRAW_FLAG_NUMBERED))
+	{
+		sprintf(draw_buf[draw_cnt], "%d", draw_cnt % 10);
+	}
+	else if (HAS_BIT(flags, DRAW_FLAG_UTF8))
+	{
+		if (HAS_BIT(flags, DRAW_FLAG_TUBED))
+		{
+			strcpy(draw_buf[draw_cnt], "║");
+		}
+		else
+		{
+			strcpy(draw_buf[draw_cnt], "│");
+		}
+	}
+	else
+	{
+		strcpy(draw_buf[draw_cnt], "|");
+	}
+	return draw_buf[draw_cnt];
+}
+
+// options
+
+DO_DRAW(draw_bot_side)
+{
+	int col, corner;
+
+	if (!HAS_BIT(flags, DRAW_FLAG_LEFT) && !HAS_BIT(flags, DRAW_FLAG_RIGHT) && !HAS_BIT(flags, DRAW_FLAG_BOT))
+	{
+		return;
+	}
+
+	SET_BIT(flags, HAS_BIT(flags, DRAW_FLAG_VER) ? DRAW_FLAG_VER : DRAW_FLAG_HOR);
+
+	corner = flags;
+
+	DEL_BIT(corner, DRAW_FLAG_RIGHT|DRAW_FLAG_TOP);
+
+	arg = arg1;
+
+	if (HAS_BIT(flags, DRAW_FLAG_LEFT) || HAS_BIT(flags, DRAW_FLAG_BOT))
+	{
+		SET_BIT(corner, DRAW_FLAG_LEFT|DRAW_FLAG_BOT);
+
+		arg1 += sprintf(arg1, "%s%s", color, get_draw_corner(corner, "└"));
+	}
+
+	if (cols - 2 >= 0)
+	{
+		if (HAS_BIT(flags, DRAW_FLAG_BOT))
+		{
+			for (col = top_col + 1 ; col < bot_col ; col++)
+			{
+				arg1 += sprintf(arg1, "%s", draw_horizontal(flags, "─"));
+			}
+		}
+		else if (HAS_BIT(flags, DRAW_FLAG_RIGHT) && cols - 2 > 0)
+		{
+			arg1 += sprintf(arg1, "\e[%dC", cols - 2);
+		}
+
+		corner = flags;
+
+		DEL_BIT(corner, DRAW_FLAG_LEFT|DRAW_FLAG_TOP);
+
+		if (HAS_BIT(flags, DRAW_FLAG_RIGHT) || HAS_BIT(flags, DRAW_FLAG_BOT))
+		{
+			arg1 += sprintf(arg1, "%s", get_draw_corner(corner, "┘"));
+		}
+	}
+
+	if (HAS_BIT(flags, DRAW_FLAG_SCROLL))
+	{
+
+		tintin_printf2(ses, "%s%s", indent_one(top_col - 1), arg);
+	}
+	else
+	{
+		goto_pos(ses, bot_row, top_col);
+
+		print_stdout("%s", arg);
+	}
+}
+
+
+DO_DRAW(draw_box)
+{
+	if (HAS_BIT(flags, DRAW_FLAG_TOP))
+	{
+		draw_top_side(ses, top_row, top_col, bot_row, bot_col, rows, cols, flags, color, arg, arg1, arg2);
+	}
+
+	draw_vertical_lines(ses, top_row, top_col, bot_row, bot_col, rows, cols, flags, color, arg, arg1, arg2);
+
+	if (HAS_BIT(flags, DRAW_FLAG_BOT))
+	{
+		draw_bot_side(ses, top_row, top_col, bot_row, bot_col, rows, cols, flags, color, arg, arg1, arg2);
+	}
+}
+
+DO_DRAW(draw_corner)
+{
+	if (*arg)
+	{
+		strcpy(arg1, arg);
+	}
+	else
+	{
+		strcpy(arg1, get_draw_corner(flags, " "));
+	}
+
+	if (HAS_BIT(flags, DRAW_FLAG_SCROLL))
+	{
+		tintin_printf2(ses, "%s%s%s", indent_one(top_col - 1), color, arg1);
+	}
+	else
+	{
+		goto_pos(ses, top_row, top_col);
+		
+		print_stdout("%s%s", color, arg1);
+	}
+}
+
+DO_DRAW(draw_line)
+{
+	SET_BIT(flags, DRAW_FLAG_LINED);
+
+	if (top_row == bot_row && !HAS_BIT(flags, DRAW_FLAG_VER))
+	{
+		SET_BIT(flags, DRAW_FLAG_LINED|DRAW_FLAG_HOR|DRAW_FLAG_LEFT|DRAW_FLAG_RIGHT);
+
+		if (!HAS_BIT(flags, DRAW_FLAG_BOT))
+		{
+			SET_BIT(flags, DRAW_FLAG_TOP);
+		}
+	}
+
+	if (top_col == bot_col && !HAS_BIT(flags, DRAW_FLAG_HOR))
+	{
+		SET_BIT(flags, DRAW_FLAG_LINED|DRAW_FLAG_VER|DRAW_FLAG_TOP|DRAW_FLAG_BOT);
+
+		if (!HAS_BIT(flags, DRAW_FLAG_RIGHT))
+		{
+			SET_BIT(flags, DRAW_FLAG_LEFT);
+		}
+	}
+/*
+	if (HAS_BIT(flags, DRAW_FLAG_TOP))
+	{
+		draw_top_side(ses, top_row, top_col, bot_row, bot_col, rows, cols, flags, color, arg, arg1, arg2);
+	}
+
+	if (HAS_BIT(flags, DRAW_FLAG_LEFT) || HAS_BIT(flags, DRAW_FLAG_RIGHT))
+	{
+		draw_vertical_lines(ses, top_row, top_col, bot_row, bot_col, rows, cols, flags, color, arg, arg1, arg2);
+	}
+
+	if (HAS_BIT(flags, DRAW_FLAG_BOT))
+	{
+		draw_bot_side(ses, top_row, top_col, bot_row, bot_col, rows, cols, flags, color, arg, arg1, arg2);
+	}
+*/
+	draw_box(ses, top_row, top_col, bot_row, bot_col, rows, cols, flags, color, arg, arg1, arg2);
+}
+
+DO_DRAW(draw_map)
+{
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+	top_row = get_row_index(ses, arg1);
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+	top_col = get_col_index(ses, arg1);
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+	bot_row = get_row_index(ses, arg1);
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+	bot_col = get_col_index(ses, arg1);
+
+	rows = URANGE(1, 1 + bot_row - top_row, gtd->screen->rows);
+	cols = URANGE(1, 1 + bot_col - top_col, gtd->screen->cols);
+
+	save_pos(ses);
+
+	draw_text(ses, top_row, top_col, bot_row, bot_col, rows, cols, flags, color, arg2, arg1, arg);
+
+	restore_pos(ses);
+}
+char *rain_symbols = "ロヲアウエオカキケコサシスセソタツテナニヌネハヒホマミムメモヤユラリワ01SԐ45789Z=*+-¦|_ʺ╌";
+char *braille_symbols = "⠁⠂⠃⠄⠅⠆⠇⠈⠊⠌⠎⠐⠑⠔⠕⠘⠜⠠⠡⠢⠣⠨⠪⠰⠱⠸⡀⡁⡂⡃⡄⡅⡆⡇⡈⡊⡌⡎⡐⡑⡔⡕⡘⡜⡠⡡⡢⡣⡨⡪⡰⡱⡸⢀⢁⢂⢃⢄⢅⢆⢇⢈⢉⢊⢌⢎⢐⢑⢔⢕⢘⢜⢠⢡⢢⢣⢨⢪⢰⢱";
+
+DO_DRAW(draw_rain)
+{
+	char code[BUFFER_SIZE], arg3[BUFFER_SIZE], arg4[BUFFER_SIZE], *rain[400];
+	struct listnode *node;
+	int row, col, len, rand, cnt, size, max, utfs[400];
+	long double density, fade;
+
+	strcpy(code, arg2);
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+
+
+	if (!valid_variable(ses, arg1))
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #DRAW {<COLOR>} RAIN %d %d %d %d {<VARIABLE>} {[SPAWN]} {[FADE]} {[LEGEND]}.", top_row, top_col, bot_row, bot_col);
+
+		return;
+	}
+
+	node = search_nest_node_ses(ses, arg1);
+
+	if (node == NULL || node->root == NULL)
+	{
+		node = set_nest_node(ses->list[LIST_VARIABLE], arg1, "{0}{}");
+	}
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+
+	if (*arg1 == 0)
+	{
+		density = 1;
+	}
+	else
+	{
+		density = URANGE(0.01, get_number(ses, arg1), 100);
+	}
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+
+	if (*arg1 == 0)
+	{
+		fade = 10;
+	}
+	else
+	{
+		fade = URANGE(1, get_number(ses, arg1), 100);
+	}
+
+	arg = get_arg_in_braces(ses, arg, arg4, GET_ONE);
+
+	if (*arg4 == 0)
+	{
+		if (HAS_BIT(ses->flags, SES_FLAG_SCREENREADER))
+		{
+			for (max = len = 0 ; braille_symbols[len] ; max++)
+			{
+				rain[max] = &braille_symbols[len];
+				utfs[max] = get_utf8_size(&braille_symbols[len]);
+				len += utfs[max];
+			}
+		}
+		else
+		{
+			for (max = len = 0 ; rain_symbols[len] ; max++)
+			{
+				rain[max] = &rain_symbols[len];
+				utfs[max] = get_utf8_size(&rain_symbols[len]);
+				len += utfs[max];
+			}
+		}
+	}
+	else
+	{
+		for (max = len = 0 ; arg4[len] && max < 400 ; max++)
+		{
+			rain[max] = &arg4[len];
+			utfs[max] = get_utf8_size(&arg4[len]);
+			len += utfs[max];
+		}
+	}
+
+//	tintin_printf2(ses, "debug: [%s] (%s) <%s>", code, fuzzy_color_code(ses, code), dim_color_code(ses, code, 20));
+
+	for (col = 0 ; col < 1 + bot_col - top_col ; col++)
+	{
+		if (node->root->used <= col)
+		{
+			set_nest_node(node->root, ntos(col), "");
+		}
+
+		if (node->root->list[col]->val16[0] == 0)
+		{
+			if (generate_rand(ses) % 10000 / (long double) 100 < density)
+			{
+				rand = generate_rand(ses) % rows;
+
+				node->root->list[col]->val16[0] = 1;
+				node->root->list[col]->val16[1] = rand;
+				node->root->list[col]->val16[2] = rand < rows / 2 ? 2 : 0;
+				node->root->list[col]->val16[3] = 0;
+
+				str_cpy_printf(&node->root->list[col]->arg2, "%*s", rand, "");
+			}
+			continue;
+		}
+		else if (node->root->list[col]->val16[0] == 1)
+		{
+			if (node->root->list[col]->val16[2])
+			{
+				node->root->list[col]->val16[3]++;
+
+				if (node->root->list[col]->val16[3] % 2)
+				{
+					goto_pos(ses, top_row + node->root->list[col]->val16[1], top_col + col);
+
+					rand = generate_rand(ses) % max;
+
+					sprintf(arg2, "%s%.*s", lit_color_code(ses, code, 10), utfs[rand], rain[rand]);
+
+					substitute(ses, arg2, arg3, SUB_COL);
+
+					print_stdout("%s", arg3);
+
+					continue;
+				}
+			}
+
+			rand = generate_rand(ses) % max;
+
+			str_cat_printf(&node->root->list[col]->arg2, "%.*s", utfs[rand], rain[rand]);
+
+			node->root->list[col]->val16[1]++;
+
+			if (generate_rand(ses) % 10000 / (long double) 100 < fade)
+			{
+				node->root->list[col]->val16[0] = 2;
+			}
+			else if (node->root->list[col]->val16[1] > rows - generate_rand(ses) % 8)
+			{
+				node->root->list[col]->val16[0] = 2;
+			}
+		}
+		else
+		{
+			node->root->list[col]->val16[0]++;
+		}
+
+		len = str_len(node->root->list[col]->arg2);
+
+		row = 0;
+		cnt = 0;
+
+		if (node->root->list[col]->val16[0] == 1)
+		{
+			while (row < len)
+			{
+				if (node->root->list[col]->arg2[row] == ' ')
+				{
+					cnt++;
+					row++;
+
+					continue;
+				}
+				goto_pos(ses, top_row + cnt, top_col + col);
+
+				cnt++;
+
+				size = get_utf8_size(&node->root->list[col]->arg2[row]);
+
+				if (cnt == node->root->list[col]->val16[1])
+				{
+					sprintf(arg2, "%s%.*s", lit_color_code(ses, code, 5), size, &node->root->list[col]->arg2[row]);
+				}
+				else
+				{
+					if (node->root->list[col]->val16[1] % 2 == 0)
+					{
+						row += size;
+							
+						continue;
+					}
+
+					sprintf(arg2, "%s%.*s", fuzzy_color_code(ses, code), size, &node->root->list[col]->arg2[row]);
+				}
+
+				substitute(ses, arg2, arg3, SUB_COL);
+
+				print_stdout("%s", arg3);
+
+				row += size;
+			}
+		}
+		else if (node->root->list[col]->val16[0] > 1)
+		{
+			while (row < len)
+			{
+				if (node->root->list[col]->arg2[row] == ' ')
+				{
+					cnt++;
+					row++;
+					continue;
+				}
+
+				goto_pos(ses, top_row + cnt, top_col + col);
+
+				cnt++;
+
+				size = get_utf8_size(&node->root->list[col]->arg2[row]);
+
+				if (node->root->list[col]->val16[0] - cnt > 15)
+				{
+					sprintf(arg2, "%s ", dim_color_code(ses, code, node->root->list[col]->val16[0] - cnt));
+					substitute(ses, arg2, arg3, SUB_COL);
+					print_stdout("%s", arg3);
+				}
+				else if (node->root->list[col]->val16[0] - cnt < 0)
+				{
+					sprintf(arg2, "%s%.*s", fuzzy_color_code(ses, code), size, &node->root->list[col]->arg2[row]);
+					substitute(ses, arg2, arg3, SUB_COL);
+					print_stdout("%s", arg3);
+				}
+				else
+				{
+					sprintf(arg2, "%s%.*s", dim_color_code(ses, code, node->root->list[col]->val16[0] - cnt), size, &node->root->list[col]->arg2[row]);
+					substitute(ses, arg2, arg3, SUB_COL);
+					print_stdout("%s", arg3);
+				}
+
+				row += size;
+			}
+
+			if (node->root->list[col]->val16[0] - cnt > 20)
+			{
+				node->root->list[col]->val16[0] = 0;
+				node->root->list[col]->val16[1] = 0;
+
+				str_cpy(&node->root->list[col]->arg2, "");
+			}
+		}
+		else
+		{
+			tintin_printf2(ses, "debug: problemo");
+		}
+	}
+}
+
+DO_DRAW(draw_side)
+{
+	push_call("draw_side()");
+
+	draw_box(ses, top_row, top_col, bot_row, bot_col, rows, cols, flags, color, arg, arg1, arg2);
+
+	pop_call();
+	return;
+}
+
+DO_DRAW(draw_square)
+{
+	draw_text(ses, top_row, top_col, bot_row, bot_col, rows, cols, flags, color, arg, arg1, arg2);
+}
+
+
+DO_DRAW(draw_stamp)
+{
+	draw_text(ses, top_row, top_col, bot_row, bot_col, rows, cols, flags, color, arg, arg1, arg2);
+}
+
+
+DO_DRAW(draw_table_grid)
+{
+	char buf1[BUFFER_SIZE], *str, buf2[BUFFER_SIZE], buf3[BUFFER_SIZE], row_color[COLOR_SIZE];
+	int corner, row, col, max_r, max_c, r, c, top_r, top_c, bot_r, bot_c, tot_r, tot_c;
+
+	row = cnt_arg_all(ses, arg, GET_ONE);
+
+	get_arg_in_braces(ses, arg, buf1, GET_ONE);
+
+	col = cnt_arg_all(ses, buf1, GET_ONE);
+
+	if (row == 0 || col == 0)
+	{
+		tintin_printf2(ses, "#ERROR: #DRAW TABLE: NEED AT LEAST 1 ROW AND 1 COLUMN.");
+
+		return;
+	}
+
+	row_color[0] = 0;
+	corner = flags;
+
+	DEL_BIT(corner, DRAW_FLAG_LEFT | DRAW_FLAG_RIGHT | DRAW_FLAG_TOP | DRAW_FLAG_BOT);
+
+	if (HAS_BIT(flags, DRAW_FLAG_GRID))
+	{
+		max_r = (rows - 1) / row;
+		max_c = (cols - 1) / col;
+
+		tot_r = 1 + max_r * row;
+		tot_c = 1 + max_c * col;
+
+		for (r = 0 ; r < tot_r ; r++)
+		{
+			buf3[0] = 0;
+
+			if (r % max_r == 0)
+			{
+				arg = get_arg_in_braces(ses, arg, buf1, GET_ONE);
+				str = buf1;
+
+				if (*arg == COMMAND_SEPARATOR)
+				{
+					arg++;
+				}
+			}
+
+			for (c = 0 ; c < tot_c ; c++)
+			{
+				if (r == 0)
+				{
+					if (c == 0)
+					{
+						strcat(buf3, get_draw_corner(corner | DRAW_FLAG_LEFT | DRAW_FLAG_TOP, "┌"));
+					}
+					else if (c == tot_c - 1)
+					{
+						strcat(buf3, get_draw_corner(corner | DRAW_FLAG_RIGHT | DRAW_FLAG_TOP, "┐"));
+					}
+					else if (c % max_c == 0)
+					{
+						strcat(buf3, get_draw_corner(corner | DRAW_FLAG_TEED | DRAW_FLAG_TOP, "┬"));
+					}
+					else
+					{
+						strcat(buf3, draw_horizontal(flags, "?"));
+					}
+				}
+				else if (r == tot_r - 1)
+				{
+					if (c == 0)
+					{
+						strcat(buf3, get_draw_corner(corner | DRAW_FLAG_LEFT | DRAW_FLAG_BOT, "?"));
+					}
+					else if (c == tot_c - 1)
+					{
+						strcat(buf3, get_draw_corner(corner | DRAW_FLAG_RIGHT | DRAW_FLAG_BOT, "?"));
+					}
+					else if (c % max_c == 0)
+					{
+						strcat(buf3, get_draw_corner(corner | DRAW_FLAG_TEED | DRAW_FLAG_BOT, "┬"));
+					}
+					else
+					{
+						strcat(buf3, draw_horizontal(flags, "?"));
+					}
+				}
+				else if (r % max_r == 0)
+				{
+					if (c == 0)
+					{
+						strcat(buf3, get_draw_corner(corner | DRAW_FLAG_TEED | DRAW_FLAG_HOR | DRAW_FLAG_LEFT, "?"));
+					}
+					else if (c == tot_c - 1)
+					{
+						strcat(buf3, get_draw_corner(corner | DRAW_FLAG_TEED | DRAW_FLAG_HOR | DRAW_FLAG_RIGHT, "?"));
+					}
+					else if (c % max_c == 0)
+					{
+						strcat(buf3, get_draw_corner(corner | DRAW_FLAG_CROSSED, "?"));
+					}
+					else
+					{
+						strcat(buf3, draw_horizontal(flags, "?"));
+					}
+				}
+				else if (r % max_r == 1)
+				{
+					if (c == tot_c - 1)
+					{
+						if (HAS_BIT(flags, DRAW_FLAG_SCROLL))
+						{
+							cat_sprintf(buf3, "%s", draw_vertical(flags, "│"));
+						}
+						else
+						{
+							goto_pos(ses, top_row + r, top_col + c);
+
+							print_stdout("%s%s", color, draw_vertical(flags, "│"));
+						}
+					}
+					else if (c == 0 || c % max_c == 0)
+					{
+						strcpy(buf2, row_color);
+
+						str = sub_arg_in_braces(ses, str, buf2 + strlen(buf2), GET_ONE, SUB_VAR|SUB_FUN|SUB_ESC|SUB_COL);
+
+						get_color_codes(row_color, buf2, row_color, GET_ALL);
+
+						top_r = top_row + r - 1;
+						top_c = top_col + c;
+						bot_r = top_row + r - 1 + max_r;
+						bot_c = top_col + c + max_c;
+
+						draw_vertical_lines(ses, top_r, top_c, top_r, bot_c, 1 + max_r, 1 + max_c, corner | DRAW_FLAG_LEFT, color, buf2, arg1, arg2);
+
+						if (HAS_BIT(flags, DRAW_FLAG_SCROLL))
+						{
+							strcat(buf3, arg2);
+						}
+	
+						if (*str == COMMAND_SEPARATOR)
+						{
+							str++;
+						}
+					}
+				}
+				else
+				{
+					if (c == tot_c - 1)
+					{
+						if (HAS_BIT(flags, DRAW_FLAG_SCROLL))
+						{
+							cat_sprintf(buf3, "%s", draw_vertical(flags, "│"));
+						}
+						else
+						{
+							goto_pos(ses, top_row + r, top_col + c);
+
+							print_stdout("%s%s", color, draw_vertical(flags, "│"));
+						}
+					}
+				}
+			}
+
+			if (*buf3)
+			{
+				if (HAS_BIT(flags, DRAW_FLAG_SCROLL))
+				{
+					tintin_printf2(ses, "%s%s%s", indent_one(top_col - 1), color, buf3);
+				}
+				else
+				{
+					goto_pos(ses, top_row + r, top_col);
+
+					print_stdout("%s%s", color, buf3);
+				}
+			}
+
+		}
+		return;
+	}
+
+	max_r = rows / row;
+	max_c = cols / col;
+	
+	for (r = 0 ; r < row ; r++)
+	{
+		arg = get_arg_in_braces(ses, arg, buf1, GET_ONE);
+
+		str = buf1;
+
+		for (c = 0 ; c < col ; c++)
+		{
+			str = get_arg_in_braces(ses, str, buf2, GET_ONE);
+
+			top_r = top_row + r * max_r;
+			top_c = top_col + c * max_c;
+			bot_r = top_row + r * max_r + max_r - 1;
+			bot_c = top_col + c * max_c + max_c - 1;
+
+			draw_box(ses, top_r, top_c, bot_r, bot_c, 1 + bot_r - top_r, 1 + bot_c - top_c, flags, color, buf2, arg1, arg2);
+
+//			tintin_printf2(ses, "#draw box %d %d %d %d %s", top_row + r * max_r, top_col + c * max_c, top_row + r * max_r + max_r, top_col + c * max_c + max_c, buf1);
+
+			if (*str == COMMAND_SEPARATOR)
+			{
+				str++;
+			}
+
+		}
+
+		if (*arg == COMMAND_SEPARATOR)
+		{
+			arg++;
+		}
+
+	}
+}
+
+DO_DRAW(draw_text)
+{
+	char *txt, buf1[BUFFER_SIZE], buf2[BUFFER_SIZE], buf3[BUFFER_SIZE], side1[100], side2[100];
+	int row, col, height, width;
+
+	push_call("draw_text(%p,%d,%p,%p,%p)",ses,flags,arg,arg1,arg2);
+
+	buf1[0] = buf2[0] = side1[0] = side2[0] = arg2[0] = 0;
+
+	txt = buf2;
+
+	substitute(ses, arg, buf3, SUB_VAR|SUB_FUN);
+
+	arg = buf3;
+
+	if (HAS_BIT(flags, DRAW_FLAG_HUGE))
+	{
+		string_to_stamp(ses, flags, arg, txt);
+	}
+	else
+	{
+		while (*arg)
+		{
+			arg = sub_arg_in_braces(ses, arg, buf1, GET_ALL, SUB_COL|SUB_ESC|SUB_VAR|SUB_FUN);
+
+			txt += sprintf(txt, "%s\n", buf1);
+
+			if (*arg == COMMAND_SEPARATOR)
+			{
+				arg++;
+			}
+		}
+	}
+
+	if (HAS_BIT(flags, DRAW_FLAG_BOXED) /*|| HAS_BIT(flags, DRAW_FLAG_LINED)*/ || HAS_BIT(flags, DRAW_FLAG_TOP) || HAS_BIT(flags, DRAW_FLAG_PRUNED))
+	{
+		top_row++;
+		rows--;
+	}
+
+	if (HAS_BIT(flags, DRAW_FLAG_BOXED) /*|| HAS_BIT(flags, DRAW_FLAG_LINED)*/ || HAS_BIT(flags, DRAW_FLAG_BOT) || HAS_BIT(flags, DRAW_FLAG_PRUNED))
+	{
+		bot_row--;
+		rows--;
+	}
+
+	if (HAS_BIT(flags, DRAW_FLAG_BOXED) || HAS_BIT(flags, DRAW_FLAG_LEFT) || HAS_BIT(flags, DRAW_FLAG_PRUNED))
+	{
+		strcpy(side1, " ");
+		cols--;
+	}
+
+	if (HAS_BIT(flags, DRAW_FLAG_BOXED) || HAS_BIT(flags, DRAW_FLAG_RIGHT) || HAS_BIT(flags, DRAW_FLAG_PRUNED))
+	{
+		if (!HAS_BIT(flags, DRAW_FLAG_GRID) || HAS_BIT(flags, DRAW_FLAG_RIGHT))
+		{
+			strcpy(side2, " ");
+		}
+		cols--;
+	}
+
+	word_wrap_split(ses, buf2, buf1, cols, 0, 0, FLAG_NONE, &height, &width);
+
+	height--;
+
+	txt = buf1;
+
+	while (*txt && height > rows)
+	{
+		txt = strchr(txt, '\n');
+		txt++;
+		height--;
+	}
+
+	arg = txt;
+	row = top_row;
+	col = top_col;
+
+	while (*arg)
+	{
+		arg = strchr(arg, '\n');
+
+		*arg++ = 0;
+
+		justify_string(ses, txt, buf2, 0 - cols, cols);
+
+		if (HAS_BIT(flags, DRAW_FLAG_LEFT))
+		{
+			strcpy(side1, draw_vertical(flags, "│"));
+		}
+
+		if (HAS_BIT(flags, DRAW_FLAG_RIGHT))
+		{
+			strcpy(side2, draw_vertical(flags, "│"));
+		}
+
+		if (HAS_BIT(flags, DRAW_FLAG_CONVERT))
+		{
+			convert_meta(buf2, buf3, FALSE);
+			strcpy(buf2, buf3);
+		}
+
+		if (HAS_BIT(flags, DRAW_FLAG_SCROLL))
+		{
+			if (HAS_BIT(flags, DRAW_FLAG_GRID))
+			{
+				cat_sprintf(arg2, "%s%s%s%s%s", color, side1, buf2, color, side2);
+			}
+			else
+			{
+				tintin_printf2(ses, "%s%s%s%s%s%s", indent_one(top_col - 1), color, side1, buf2, color, side2);
+			}
+		}
+		else
+		{
+			goto_pos(ses, row++, col);
+
+			print_stdout("%s%s%s%s%s", color, side1, buf2, color, side2);
+		}
+
+		txt = arg;
+	}
+
+	while (height < rows)
+	{
+		if (HAS_BIT(flags, DRAW_FLAG_LEFT))
+		{
+			strcpy(side1, draw_vertical(flags, "│"));
+		}
+
+		if (HAS_BIT(flags, DRAW_FLAG_RIGHT))
+		{
+			strcpy(side2, draw_vertical(flags, "│"));
+		}
+
+		if (HAS_BIT(flags, DRAW_FLAG_SCROLL))
+		{
+			if (HAS_BIT(flags, DRAW_FLAG_GRID))
+			{
+				cat_sprintf(arg2, "%s%s%*s%s%s", color, side1, cols, "", color, side2);
+			}
+			else
+			{
+				tintin_printf2(ses, "%s%s%s%-*s%s%s", indent_one(top_col - 1), color, side1, cols, "", color, side2);
+			}
+		}
+		else
+		{
+			goto_pos(ses, row++, col);
+
+			print_stdout("%s%s%*s%s%s", color, side1, cols, "", color, side2);
+		}
+		height++;
+	}
+	pop_call();
+	return;
+}
+
+DO_DRAW(draw_top_side)
+{
+	int col, corner;
+
+	SET_BIT(flags, HAS_BIT(flags, DRAW_FLAG_VER) ? DRAW_FLAG_VER : DRAW_FLAG_HOR);
+
+	corner = flags;
+
+	DEL_BIT(corner, DRAW_FLAG_RIGHT|DRAW_FLAG_BOT);
+
+	arg = arg1;
+
+	if (HAS_BIT(flags, DRAW_FLAG_LEFT) || HAS_BIT(flags, DRAW_FLAG_TOP))
+	{
+		SET_BIT(corner, DRAW_FLAG_LEFT|DRAW_FLAG_RIGHT);
+
+		arg1 += sprintf(arg1, "%s%s", color, get_draw_corner(corner, "┌"));
+	}
+
+	if (cols - 2 >= 0)
+	{
+		if (HAS_BIT(flags, DRAW_FLAG_TOP))
+		{
+			for (col = top_col + 1 ; col < bot_col ; col++)
+			{
+				arg1 += sprintf(arg1, "%s", draw_horizontal(flags, "─"));
+			}
+		}
+		else if (HAS_BIT(flags, DRAW_FLAG_RIGHT) && cols - 2 > 0)
+		{
+			arg1 += sprintf(arg1, "\e[%dC", cols - 2);
+		}
+
+		corner = flags;
+
+		DEL_BIT(corner, DRAW_FLAG_LEFT|DRAW_FLAG_BOT);
+
+		if (HAS_BIT(flags, DRAW_FLAG_TOP) || HAS_BIT(flags, DRAW_FLAG_RIGHT))
+		{
+			SET_BIT(corner, DRAW_FLAG_RIGHT|DRAW_FLAG_TOP);
+
+			arg1 += sprintf(arg1, "%s", get_draw_corner(corner, "┐"));
+		}
+	}
+
+	if (HAS_BIT(flags, DRAW_FLAG_SCROLL))
+	{
+		tintin_printf2(ses, "%*s%s", top_col - 1, "", arg);
+	}
+	else
+	{
+		goto_pos(ses, top_row, top_col);
+
+		print_stdout("%s", arg);
+	}
+}
+
+DO_DRAW(draw_vertical_lines)
+{
+	int row, col, lines;
+
+	push_call("draw_vertical_lines(%p,%d,%p,%p,%p)",ses,flags,arg,arg1,arg2);
+
+	if (HAS_BIT(flags, DRAW_FLAG_SCROLL) || *arg)
+	{
+		draw_text(ses, top_row, top_col, bot_row, top_col, rows, cols, flags, color, arg, arg1, arg2);
+
+		pop_call();
+		return;
+	}
+
+	arg1[0] = arg2[0] = 0;
+
+	if (HAS_BIT(flags, DRAW_FLAG_BOXED) || HAS_BIT(flags, DRAW_FLAG_TOP))
+	{
+		top_row++;
+		rows--;
+	}
+
+	if (HAS_BIT(flags, DRAW_FLAG_BOXED) || HAS_BIT(flags, DRAW_FLAG_BOT))
+	{
+		bot_row--;
+		rows--;
+	}
+
+	if (HAS_BIT(flags, DRAW_FLAG_LEFT))
+	{
+		if (HAS_BIT(flags, DRAW_FLAG_PRUNED))
+		{
+			strcpy(arg1, " ");
+		}
+		else
+		{
+			strcpy(arg1, draw_vertical(flags, "│"));
+		}
+		cols--;
+	}
+
+	if (HAS_BIT(flags, DRAW_FLAG_RIGHT))
+	{
+		if (HAS_BIT(flags, DRAW_FLAG_PRUNED))
+		{
+			strcpy(arg1, " ");
+		}
+		else
+		{
+			strcpy(arg2, draw_vertical(flags, "│"));
+		}
+		cols--;
+	}
+
+//	tintin_printf2(ses, "debug: rows = %d", rows);
+
+	lines = 0;
+
+	row = top_row;
+	col = top_col;
+
+	while (lines < rows)
+	{
+		goto_pos(ses, row, col);
+
+		print_stdout("%s%s\e[%dG%s%s", color, arg1, col + cols + strip_vt102_strlen(ses, arg1), color, arg2);
+
+		row++;
+		lines++;
+	}
+	pop_call();
+	return;
+}

+ 453 - 0
event.c

@@ -0,0 +1,453 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2007                       *
+******************************************************************************/
+
+
+#include "tintin.h"
+
+DO_COMMAND(do_event)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+	struct listnode *node;
+	int cnt;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = get_arg_in_braces(ses, arg, arg2, GET_ALL);
+
+	if (*arg1 == 0)
+	{
+		tintin_header(ses, " EVENTS ");
+
+		for (cnt = 0 ; *event_table[cnt].name != 0 ; cnt++)
+		{
+			tintin_printf2(ses, "%s [%-27s] %s", search_node_list(ses->list[LIST_EVENT], event_table[cnt].name) ? "+" : " ", event_table[cnt].name, event_table[cnt].desc);
+		}
+		tintin_header(ses, "");
+	}
+	else if (*arg2 == 0)
+	{
+		if (show_node_with_wild(ses, arg1, ses->list[LIST_EVENT]) == FALSE)
+		{
+			show_message(ses, LIST_ALIAS, "#EVENT: NO MATCH(ES) FOUND FOR {%s}.", arg1);
+		}
+	}
+	else
+	{
+		for (cnt = 0 ; *event_table[cnt].name != 0 ; cnt++)
+		{
+			if (!strncmp(event_table[cnt].name, arg1, strlen(event_table[cnt].name)))
+			{
+				show_message(ses, LIST_EVENT, "#EVENT {%s} HAS BEEN SET TO {%s}.", arg1, arg2);
+
+				SET_BIT(ses->event_flags, event_table[cnt].flags);
+
+				node = update_node_list(ses->list[LIST_EVENT], arg1, arg2, "", "");
+
+				node->val64 = event_table[cnt].flags;
+
+				return ses;
+			}
+		}
+		show_error(ses, LIST_EVENT, "#EVENT {%s} IS NOT AN EXISTING EVENT.", arg1);
+	}
+	return ses;
+}
+
+DO_COMMAND(do_unevent)
+{
+	delete_node_with_wild(ses, LIST_EVENT, arg);
+
+	return ses;
+}
+
+int check_all_events(struct session *ses, int flags, int args, int vars, char *fmt, ...)
+{
+	struct session *ses_ptr;
+	struct listnode *node;
+	char *name, buf[BUFFER_SIZE];
+	va_list list;
+	int cnt;
+
+	if (gtd->level->ignore)
+	{
+		return 0;
+	}
+
+	if (args)
+	{
+		va_start(list, fmt);
+
+		vasprintf(&name, fmt, list);
+
+		va_end(list); 
+	}
+	else
+	{
+		name = strdup(fmt);
+	}
+
+	push_call("check_all_events(%p,%d,%d,%d,%s, ...)",ses,flags,args,vars,name);
+
+	for (ses_ptr = ses ? ses : gts ; ses_ptr ; ses_ptr = ses_ptr->next)
+	{
+		if (!HAS_BIT(ses_ptr->list[LIST_EVENT]->flags, LIST_FLAG_IGNORE))
+		{
+			show_info(ses_ptr, LIST_EVENT, "#INFO EVENT {%s}", name);
+
+			node = search_node_list(ses_ptr->list[LIST_EVENT], name);
+
+			if (node)
+			{
+				if (vars)
+				{
+					va_start(list, fmt);
+
+					for (cnt = 0 ; cnt < args ; cnt++)
+					{
+						va_arg(list, char *);
+					}
+
+					for (cnt = 0 ; cnt < vars ; cnt++)
+					{
+						RESTRING(gtd->vars[cnt], va_arg(list, char *));
+					}
+					va_end(list);
+				}
+
+				substitute(ses_ptr, node->arg2, buf, flags);
+
+				if (HAS_BIT(ses_ptr->list[LIST_EVENT]->flags, LIST_FLAG_DEBUG))
+				{
+					show_debug(ses_ptr, LIST_EVENT, "#DEBUG EVENT {%s} (%s}", node->arg1, node->arg2);
+				}
+
+				if (HAS_BIT(node->flags, NODE_FLAG_ONESHOT))
+				{
+					delete_node_list(ses, LIST_EVENT, node);
+				}
+
+				script_driver(ses_ptr, LIST_EVENT, buf);
+
+				if (ses)
+				{
+					free(name);
+
+					pop_call();
+					return 1;
+				}
+			}
+		}
+
+		if (ses)
+		{
+			free(name);
+
+			pop_call();
+			return 0;
+		}
+	}
+	free(name);
+
+	pop_call();
+	return 0;
+}
+
+void mouse_handler(struct session *ses, int flags, int row, int col, char type)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], line[BUFFER_SIZE], word[BUFFER_SIZE];
+	static char last[100], dir[10];
+	static long long click[3];
+	static int swipe[10];
+	int debug, info, rev_row, rev_col;
+
+	push_call("mouse_handler(%p,%d,%d,%d,%c)",ses,flags,row,col,type);
+
+	if (!HAS_BIT(ses->event_flags, EVENT_FLAG_MOUSE))
+	{
+		if (!HAS_BIT(ses->flags, SES_FLAG_MOUSEINFO) && !HAS_BIT(ses->list[LIST_EVENT]->flags, LIST_FLAG_INFO))
+		{
+			return;
+		}
+	}
+
+	if (HAS_BIT(flags, MOUSE_FLAG_MOTION))
+	{
+		strcpy(arg1, "MOVED");
+	}
+	else
+	{
+		switch (type)
+		{
+			case 'M':
+				if (HAS_BIT(flags, MOUSE_FLAG_WHEEL))
+				{
+					strcpy(arg1, "SCROLLED");
+				}
+				else
+				{
+					strcpy(arg1, "PRESSED");
+				}
+				break;
+			case 'm':
+				strcpy(arg1, "RELEASED");
+				break;
+			default:
+				strcpy(arg1, "UNKNOWN");
+				break;
+		}
+	}
+
+	arg2[0] = 0;
+
+	if (HAS_BIT(flags, MOUSE_FLAG_CTRL))
+	{
+		strcat(arg2, "CTRL ");
+	}
+	if (HAS_BIT(flags, MOUSE_FLAG_ALT))
+	{
+		strcat(arg2, "ALT ");
+	}
+	if (HAS_BIT(flags, MOUSE_FLAG_SHIFT))
+	{
+		strcat(arg2, "SHIFT ");
+	}
+
+	if (HAS_BIT(flags, MOUSE_FLAG_EXTRA))
+	{
+		strcat(arg2, "EXTRA ");
+	}
+
+	if (HAS_BIT(flags, MOUSE_FLAG_UNKNOWN))
+	{
+		strcat(arg2, "256 ");
+	}
+
+	if (HAS_BIT(flags, MOUSE_FLAG_WHEEL))
+	{
+		strcat(arg2, "MOUSE WHEEL ");
+	}
+	else
+	{
+		strcat(arg2, "MOUSE BUTTON ");
+	}
+
+	if (row - 1 < 0)
+	{
+		tintin_printf2(ses, "mouse_handler: bad row: (row,col)=(%d,%d)", row, col);
+		pop_call();
+		return;
+	}
+	else if (row - 1 > gtd->screen->rows)
+	{
+		tintin_printf2(ses, "mouse_handler: bad col: (row,col)=(%d,%d)", row, col);
+		pop_call();
+		return;
+	}
+	else
+	{
+		strcpy(line, "under development");
+		strcpy(word, "under development");
+//		get_line_screen(line, row - 1);
+//		get_word_screen(word, row - 1, col - 1);
+	}
+
+	if (HAS_BIT(flags, MOUSE_FLAG_WHEEL))
+	{
+		if (HAS_BIT(flags, MOUSE_FLAG_BUTTON_A) && HAS_BIT(flags, MOUSE_FLAG_BUTTON_B))
+		{
+			strcat(arg2, "RIGHT");
+		}
+		else if (HAS_BIT(flags, MOUSE_FLAG_BUTTON_B))
+		{
+			strcat(arg2, "LEFT");
+		}
+		else if (HAS_BIT(flags, MOUSE_FLAG_BUTTON_A))
+		{
+			strcat(arg2, "DOWN");
+		}
+		else
+		{
+			strcat(arg2, "UP");
+		}
+	}
+	else
+	{
+		if (HAS_BIT(flags, MOUSE_FLAG_BUTTON_A) && HAS_BIT(flags, MOUSE_FLAG_BUTTON_B))
+		{
+			strcat(arg2, "FOUR");
+		}
+		else if (HAS_BIT(flags, MOUSE_FLAG_BUTTON_B))
+		{
+			strcat(arg2, "THREE");
+		}
+		else if (HAS_BIT(flags, MOUSE_FLAG_BUTTON_A))
+		{
+			strcat(arg2, "TWO");
+		}
+		else
+		{
+			strcat(arg2, "ONE");
+		}
+	}
+
+	debug = HAS_BIT(ses->flags, SES_FLAG_MOUSEDEBUG) ? 1 : 0;
+	info  = HAS_BIT(ses->flags, SES_FLAG_MOUSEINFO) ? 1 : 0;
+
+	gtd->level->debug += debug;
+	gtd->level->info  += info;
+
+	check_all_buttons(ses, row, col, arg1, arg2, word, line);
+
+	rev_row = -1 - (gtd->screen->rows - row);
+	rev_col = -1 - (gtd->screen->cols - col);
+
+	check_all_events(ses, SUB_ARG, 2, 6, "%s %s", arg1, arg2, ntos(row), ntos(col), ntos(rev_row), ntos(-1 - (gtd->screen->cols - col)), word, line);
+
+	check_all_events(ses, SUB_ARG, 3, 6, "%s %s %d", arg1, arg2, row, ntos(row), ntos(col), ntos(rev_row), ntos(rev_col), word, line);
+
+	check_all_events(ses, SUB_ARG, 3, 6, "%s %s %d", arg1, arg2, rev_row, ntos(row), ntos(col), ntos(rev_row), ntos(rev_col), word, line);
+
+	map_mouse_handler(ses, arg1, arg2, col, row, 0, 0);
+
+	if (!strcmp(arg1, "PRESSED"))
+	{
+		sprintf(arg1, "PRESSED %s %d %d", arg2, col, row);
+
+		swipe[0] = row;
+		swipe[1] = col;
+		swipe[2] = rev_row;
+		swipe[3] = rev_col;
+
+		if (!strcmp(arg1, last))
+		{
+			click[2] = click[1];
+			click[1] = click[0];
+			click[0] = utime();
+
+			if (click[0] - click[1] < 500000)
+			{
+				if (click[0] - click[2] < 500000)
+				{
+					check_all_buttons(ses, row, col, "TRIPLE-CLICKED", arg2, word, line);
+
+					check_all_events(ses, SUB_ARG, 1, 6, "TRIPLE-CLICKED %s", arg2, ntos(row), ntos(col), ntos(rev_row), ntos(rev_col), word, line);
+
+					check_all_events(ses, SUB_ARG, 2, 6, "TRIPLE-CLICKED %s %d", arg2, row, ntos(row), ntos(col), ntos(rev_row), ntos(rev_col), word, line);
+
+					check_all_events(ses, SUB_ARG, 2, 6, "TRIPLE-CLICKED %s %d", arg2, rev_row, ntos(row), ntos(col), ntos(rev_row), ntos(rev_col), word, line);
+
+					map_mouse_handler(ses, "TRIPPLE-CLICKED", arg2, col, row, 0, 0);
+
+					strcpy(last, "");
+				}
+				else
+				{
+					check_all_buttons(ses, row, col, "DOUBLE-CLICKED", arg2, word, line);
+
+					check_all_events(ses, SUB_ARG, 1, 6, "DOUBLE-CLICKED %s", arg2, ntos(row), ntos(col), ntos(rev_row), ntos(rev_col), word, line);
+
+					check_all_events(ses, SUB_ARG, 2, 6, "DOUBLE-CLICKED %s %d", arg2, row, ntos(row), ntos(col), ntos(rev_row), ntos(rev_col), word, line);
+
+					check_all_events(ses, SUB_ARG, 2, 6, "DOUBLE-CLICKED %s %d", arg2, rev_row, ntos(row), ntos(col), ntos(rev_row), ntos(rev_col), word, line);
+
+					map_mouse_handler(ses, "DOUBLE-CLICKED", arg2, col, row, 0, 0);
+				}
+			}
+		}
+		else
+		{
+			click[2] = 0;
+			click[1] = 0;
+			click[0] = utime();
+
+			sprintf(last, "PRESSED %s %d %d", arg2, col, row);
+		}
+	}
+	else if (!strcmp(arg1, "RELEASED"))
+	{
+		swipe[4] = row;
+		swipe[5] = col;
+		swipe[6] = rev_row;
+		swipe[7] = rev_col;
+
+		swipe[8] = swipe[4] - swipe[0];
+		swipe[9] = swipe[5] - swipe[1];
+
+		if (abs(swipe[8]) > 3 || abs(swipe[9]) > 3)
+		{
+			if (abs(swipe[8]) <= 3)
+			{
+				strcpy(dir, swipe[9] > 0 ? "E" : "W");
+			}
+			else if (abs(swipe[9]) <= 3)
+			{
+				strcpy(dir, swipe[8] > 0 ? "S" : "N");
+			}
+			else if (swipe[8] > 0)
+			{
+				strcpy(dir, swipe[9] > 0 ? "SE" : "SW");
+			}
+			else
+			{
+				strcpy(dir, swipe[9] > 0 ? "NE" : "NW");
+			}
+			check_all_events(ses, SUB_ARG, 0, 12, "SWIPED", dir, arg2, ntos(swipe[0]), ntos(swipe[1]), ntos(swipe[2]), ntos(swipe[3]), ntos(swipe[4]), ntos(swipe[5]), ntos(swipe[6]), ntos(swipe[7]), ntos(swipe[8]), ntos(swipe[9]));			
+			check_all_events(ses, SUB_ARG, 1, 12, "SWIPED %s", dir, dir, arg2, ntos(swipe[0]), ntos(swipe[1]), ntos(swipe[2]), ntos(swipe[3]), ntos(swipe[4]), ntos(swipe[5]), ntos(swipe[6]), ntos(swipe[7]), ntos(swipe[8]), ntos(swipe[9]));
+			check_all_events(ses, SUB_ARG, 2, 12, "SWIPED %s %s", arg2, dir, dir, arg2, ntos(swipe[0]), ntos(swipe[1]), ntos(swipe[2]), ntos(swipe[3]), ntos(swipe[4]), ntos(swipe[5]), ntos(swipe[6]), ntos(swipe[7]), ntos(swipe[8]), ntos(swipe[9]));
+		}
+		else if (utime() - click[0] >= 500000)
+		{
+			check_all_buttons(ses, row, col, "LONG-CLICKED", arg2, word, line);
+
+			check_all_events(ses, SUB_ARG, 1, 6, "LONG-CLICKED %s", arg2, ntos(row), ntos(col), ntos(rev_row), ntos(rev_col), word, line);
+
+			check_all_events(ses, SUB_ARG, 2, 6, "LONG-CLICKED %s %d", arg2, row, ntos(row), ntos(col), ntos(rev_row), ntos(rev_col), word, line);
+
+			check_all_events(ses, SUB_ARG, 2, 6, "LONG-CLICKED %s %d", arg2, rev_row, ntos(row), ntos(col), ntos(rev_row), ntos(rev_col), word, line);
+
+			map_mouse_handler(ses, "LONG-CLICKED", arg2, col, row, 0, 0);
+		}
+		else if (click[0] - click[1] >= 500000)
+		{
+			if (abs(swipe[0] - swipe[4]) <= 3 && abs(swipe[1] - swipe[5]) <= 3)
+			{
+				check_all_buttons(ses, row, col, "SHORT-CLICKED", arg2, word, line);
+
+				check_all_events(ses, SUB_ARG, 1, 6, "SHORT-CLICKED %s", arg2, ntos(row), ntos(col), ntos(rev_row), ntos(rev_col), word, line);
+
+				check_all_events(ses, SUB_ARG, 2, 6, "SHORT-CLICKED %s %d", arg2, row, ntos(row), ntos(col), ntos(rev_row), ntos(rev_col), word, line);
+
+				check_all_events(ses, SUB_ARG, 2, 6, "SHORT-CLICKED %s %d", arg2, rev_row, ntos(row), ntos(col), ntos(rev_row), ntos(rev_col), word, line);
+
+				map_mouse_handler(ses, "SHORT-CLICKED", arg2, col, row, 0, 0);
+			}
+		}
+	}
+
+	gtd->level->debug -= debug;
+	gtd->level->info  -= info;
+
+	pop_call();
+	return;
+}

+ 535 - 0
files.c

@@ -0,0 +1,535 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2019 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*               (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                  *
+*                                                                             *
+*                        coded by Peter Unold 1992                            *
+*                    recoded by Igor van den Hoven 2004                       *
+******************************************************************************/
+
+#include "tintin.h"
+#include <sys/stat.h>
+
+
+DO_COMMAND(do_read)
+{
+	char filename[BUFFER_SIZE];
+	FILE *fp;
+
+	sub_arg_in_braces(ses, arg, filename, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if ((fp = fopen(filename, "r")) == NULL)
+	{
+		check_all_events(ses, SUB_ARG, 0, 2, "READ ERROR", filename, "FILE NOT FOUND.");
+
+		tintin_printf(ses, "#READ {%s} - FILE NOT FOUND.", filename);
+
+		return ses;
+	}
+
+	return read_file(ses, fp, filename);
+}
+
+struct session *read_file(struct session *ses, FILE *fp, char *filename)
+{
+	struct stat filedata;
+	char *bufi, *bufo, temp[INPUT_SIZE], *pti, *pto, last = 0;
+	int lvl, cnt, com, lnc, fix, ok;
+	int counter[LIST_MAX];
+
+	temp[0] = getc(fp);
+
+	if (!ispunct((int) temp[0]))
+	{
+		check_all_events(ses, SUB_ARG, 0, 2, "READ ERROR", filename, "INVALID START OF FILE");
+
+		tintin_printf(ses, "#ERROR: #READ {%s} - INVALID START OF FILE '%c'.", filename, temp[0]);
+
+		fclose(fp);
+
+		return ses;
+	}
+
+	ungetc(temp[0], fp);
+
+	for (cnt = 0 ; cnt < LIST_MAX ; cnt++)
+	{
+		if (HAS_BIT(list_table[cnt].flags, LIST_FLAG_READ))
+		{
+			counter[cnt] = ses->list[cnt]->used;
+		}
+	}
+
+	stat(filename, &filedata);
+
+	if ((bufi = (char *) calloc(1, filedata.st_size + 2)) == NULL || (bufo = (char *) calloc(1, filedata.st_size + 2)) == NULL)
+	{
+		check_all_events(ses, SUB_ARG, 0, 2, "READ ERROR", filename, "FAILED TO ALLOCATE MEMORY");
+
+		tintin_printf(ses, "#ERROR: #READ {%s} - FAILED TO ALLOCATE MEMORY.", filename);
+
+		fclose(fp);
+
+		return ses;
+	}
+
+	fread(bufi, 1, filedata.st_size, fp);
+
+	pti = bufi;
+	pto = bufo;
+
+	lvl = com = lnc = fix = ok = 0;
+
+	while (*pti)
+	{
+		if (com == 0)
+		{
+			if (HAS_BIT(ses->charset, CHARSET_FLAG_EUC) && is_euc_head(ses, pti))
+			{
+				*pto++ = *pti++;
+				*pto++ = *pti++;
+
+				continue;
+			}
+
+			switch (*pti)
+			{
+				case DEFAULT_OPEN:
+					*pto++ = *pti++;
+					lvl++;
+					last = DEFAULT_OPEN;
+					break;
+
+				case DEFAULT_CLOSE:
+					*pto++ = *pti++;
+					lvl--;
+					last = DEFAULT_CLOSE;
+					break;
+
+				case COMMAND_SEPARATOR:
+					*pto++ = *pti++;
+					last = COMMAND_SEPARATOR;
+					break;
+
+				case ' ':
+					*pto++ = *pti++;
+					break;
+
+				case '/':
+					if (lvl == 0 && pti[1] == '*')
+					{
+						pti += 2;
+						com += 1;
+					}
+					else
+					{
+						*pto++ = *pti++;
+					}
+					break;
+
+				case '\t':
+					*pto++ = *pti++;
+					break;
+
+				case '\r':
+					pti++;
+					break;
+
+				case '\n':
+					lnc++;
+
+					pto--;
+
+					while (isspace((int) *pto))
+					{
+						pto--;
+					}
+
+					pto++;
+
+					if (fix == 0 && pti[1] == gtd->tintin_char)
+					{
+						if (lvl == 0)
+						{
+							ok = lnc + 1;
+						}
+						else
+						{
+							fix = lnc;
+						}
+					}
+
+					if (lvl)
+					{
+						pti++;
+
+						while (isspace((int) *pti))
+						{
+							if (*pti == '\n')
+							{
+								lnc++;
+
+								if (fix == 0 && pti[1] == gtd->tintin_char)
+								{
+									fix = lnc;
+								}
+							}
+							pti++;
+
+						}
+
+						if (*pti != DEFAULT_CLOSE && last == 0)
+						{
+							*pto++ = ' ';
+						}
+					}
+					else for (cnt = 1 ; ; cnt++)
+					{
+						if (pti[cnt] == 0)
+						{
+							*pto++ = *pti++;
+							break;
+						}
+
+						if (pti[cnt] == DEFAULT_OPEN)
+						{
+							pti++;
+							while (isspace((int) *pti))
+							{
+								pti++;
+							}
+							*pto++ = ' ';
+							break;
+						}
+
+						if (!isspace((int) pti[cnt]))
+						{
+							*pto++ = *pti++;
+							break;
+						}
+					}
+					break;
+
+				default:
+					*pto++ = *pti++;
+					last = 0;
+					break;
+			}
+		}
+		else
+		{
+			switch (*pti)
+			{
+				case '/':
+					if (pti[1] == '*')
+					{
+						pti += 2;
+						com += 1;
+					}
+					else
+					{
+						pti += 1;
+					}
+					break;
+
+				case '*':
+					if (pti[1] == '/')
+					{
+						pti += 2;
+						com -= 1;
+					}
+					else
+					{
+						pti += 1;
+					}
+					break;
+
+				case '\n':
+					lnc++;
+					pti++;
+					break;
+
+				default:
+					pti++;
+					break;
+			}
+		}
+	}
+	*pto++ = '\n';
+	*pto   = '\0';
+
+	if (lvl)
+	{
+		check_all_events(ses, SUB_ARG, 0, 2, "READ ERROR", filename, "MISSING BRACE OPEN OR CLOSE");
+
+		tintin_printf(ses, "#ERROR: #READ {%s} - MISSING %d '%c' BETWEEN LINE %d AND %d.", filename, abs(lvl), lvl < 0 ? DEFAULT_OPEN : DEFAULT_CLOSE, fix == 0 ? 1 : ok, fix == 0 ? lnc + 1 : fix);
+
+		fclose(fp);
+
+		free(bufi);
+		free(bufo);
+
+		return ses;
+	}
+
+	if (com)
+	{
+		check_all_events(ses, SUB_ARG, 0, 2, "READ ERROR", filename, "MISSING COMMENT OPEN OR CLOSE");
+
+		tintin_printf(ses, "#ERROR: #READ {%s} - MISSING %d '%s'", filename, abs(com), com < 0 ? "/*" : "*/");
+
+		fclose(fp);
+
+		free(bufi);
+		free(bufo);
+
+		return ses;
+	}
+
+	sprintf(temp, "{TINTIN CHAR} {%c}", bufo[0]);
+
+	if (bufo[0] != '#')
+	{
+		gtd->level->verbose++;
+		gtd->level->debug++;
+
+		show_error(ses, LIST_COMMAND, "\e[1;5;31mWARNING: SETTING THE COMMAND CHARACTER TO '%c' BECAUSE IT'S THE FIRST CHARACTER IN THE FILE.", bufo[0]);
+
+		gtd->level->debug--;
+		gtd->level->verbose--;
+	}
+
+	gtd->level->quiet++;
+
+	do_configure(ses, temp);
+
+	lvl = 0;
+	lnc = 0;
+	pti = bufo;
+	pto = bufi;
+
+	while (*pti)
+	{
+		if (*pti != '\n')
+		{
+			*pto++ = *pti++;
+			continue;
+		}
+		lnc++;
+		*pto = 0;
+
+		if (strlen(bufi) >= BUFFER_SIZE)
+		{
+			tintin_printf(ses, "#ERROR: #READ {%s} - BUFFER OVERFLOW AT COMMAND: %.30s", filename, bufi);
+		}
+
+		if (bufi[0])
+		{
+			ses = script_driver(ses, LIST_COMMAND, bufi);
+		}
+		pto = bufi;
+		pti++;
+	}
+
+	gtd->level->quiet--;
+
+	if (!HAS_BIT(ses->flags, SES_FLAG_VERBOSE))
+	{
+		for (cnt = 0 ; cnt < LIST_MAX ; cnt++)
+		{
+			if (HAS_BIT(list_table[cnt].flags, LIST_FLAG_READ))
+			{
+				switch (ses->list[cnt]->used - counter[cnt])
+				{
+					case 0:
+						break;
+
+					case 1:
+						show_message(ses, cnt, "#OK: %3d %s LOADED.", ses->list[cnt]->used - counter[cnt], list_table[cnt].name);
+						break;
+
+					default:
+						show_message(ses, cnt, "#OK: %3d %s LOADED.", ses->list[cnt]->used - counter[cnt], list_table[cnt].name_multi);
+						break;
+				}
+			}
+		}
+	}
+	fclose(fp);
+
+	free(bufi);
+	free(bufo);
+
+	return ses;
+}
+
+
+DO_COMMAND(do_write)
+{
+	FILE *file;
+	char filename[BUFFER_SIZE], forceful[BUFFER_SIZE];
+	struct listroot *root;
+	struct listnode *node;
+
+	int i, j, fix, cnt = 0;
+
+	arg = get_arg_in_braces(ses, arg, filename, GET_ONE);
+	arg = get_arg_in_braces(ses, arg, forceful, GET_ONE);
+
+	if (*filename == 0)
+	{
+		check_all_events(ses, SUB_ARG, 0, 2, "WRITE ERROR", filename, "INVALID FILE NAME");
+
+		tintin_printf2(ses, "#SYNTAX: #WRITE {<filename>} {[FORCE]}");
+
+		return ses;
+	}
+	
+	if (!str_suffix(filename, ".map") && !is_abbrev(forceful, "FORCE"))
+	{
+		check_all_events(ses, SUB_ARG, 0, 2, "WRITE ERROR", filename, "INVALID FILE EXTENSION");
+		tintin_printf2(ses, "#WRITE {%s}: USE {FORCE} TO OVERWRITE .map FILES.", filename);
+
+		return ses;
+	}
+
+	if ((file = fopen(filename, "w")) == NULL)
+	{
+		check_all_events(ses, SUB_ARG, 0, 2, "WRITE ERROR", filename, "FAILED TO OPEN");
+
+		tintin_printf(ses, "#ERROR: #WRITE: COULDN'T OPEN {%s} TO WRITE.", filename);
+
+		return ses;
+	}
+
+	for (i = 0 ; i < LIST_MAX ; i++)
+	{
+		root = ses->list[i];
+
+		if (!HAS_BIT(root->flags, LIST_FLAG_WRITE))
+		{
+			continue;
+		}
+
+		fix = 0;
+
+		for (j = 0 ; j < root->used ; j++)
+		{
+			node = root->list[j];
+
+			if (HAS_BIT(node->flags, NODE_FLAG_ONESHOT))
+			{
+				continue;
+			}
+
+			if (*node->group == 0)
+			{
+				write_node(ses, i, node, file);
+
+				cnt++;
+				fix++;
+			}
+		}
+
+		if (fix)
+		{
+			fputs("\n", file);
+		}
+	}
+
+	fclose(file);
+
+	show_message(ses, LIST_COMMAND, "#WRITE: %d COMMANDS WRITTEN TO {%s}.", cnt, filename);
+
+	return ses;
+}
+
+
+void write_node(struct session *ses, int list, struct listnode *node, FILE *file)
+{
+	char *result, *str;
+
+	int llen = UMAX(20, strlen(node->arg1));
+	int rlen = UMAX(25, strlen(node->arg2));
+
+	push_call("write_node(%d,%p,%p)",list,node,file);
+
+	switch (list)
+	{
+		case LIST_EVENT:
+		case LIST_FUNCTION:
+		case LIST_MACRO:
+			asprintf(&result, "%c%s {%s}\n{\n%s\n}\n\n", gtd->tintin_char, list_table[list].name, node->arg1, script_writer(ses, node->arg2));
+			break;
+
+		case LIST_ACTION:
+		case LIST_ALIAS:
+		case LIST_BUTTON:
+			if (!strcmp(node->arg3, "5"))
+			{
+				asprintf(&result, "%c%s {%s}\n{\n%s\n}\n\n", gtd->tintin_char, list_table[list].name, node->arg1, script_writer(ses, node->arg2));
+			}
+			else
+			{
+				asprintf(&result, "%c%s {%s}\n{\n%s\n}\n{%s}\n\n", gtd->tintin_char, list_table[list].name, node->arg1, script_writer(ses, node->arg2), node->arg3);
+			}
+			break;
+
+		case LIST_VARIABLE:
+			str = str_dup("");
+
+			show_nest_node(node, &str, 1);
+
+			asprintf(&result, "%c%-16s {%s} %*s {%s}\n", gtd->tintin_char, list_table[list].name, node->arg1, 20 - llen, "", str);
+
+			str_free(str);
+
+			break;
+
+		default:
+			switch (list_table[list].args)
+			{
+				case 0:
+					result = strdup("");
+					break;
+				case 1:
+					asprintf(&result, "%c%-16s {%s}\n", gtd->tintin_char, list_table[list].name, node->arg1);
+					break;
+				case 2:
+					asprintf(&result, "%c%-16s {%s} %*s {%s}\n", gtd->tintin_char, list_table[list].name, node->arg1, 20 - llen, "", node->arg2);
+					break;
+				case 3:
+					asprintf(&result, "%c%-16s {%s} %*s {%s} %*s {%s}\n", gtd->tintin_char, list_table[list].name, node->arg1, 20 - llen, "", node->arg2, 25 - rlen, "", node->arg3);
+					break;
+				case 4:
+					asprintf(&result, "%c%-16s {%s} %*s {%s} %*s {%s} {%s}\n", gtd->tintin_char, list_table[list].name, node->arg1, 20 - llen, "", node->arg2, 25 - rlen, "", node->arg3, node->arg4);
+					break;
+			}
+			break;
+	}
+	fputs(result, file);
+
+	free(result);
+
+	pop_call();
+	return;
+}
+

+ 180 - 0
forkpty.c

@@ -0,0 +1,180 @@
+#include "config.h"
+
+#if !defined(HAVE_PTY_H) && defined(HAVE__DEV_PTMX) && !defined(HAVE_FORKPTY)
+
+/*
+ * Substitute for the nonstandard BSD/GNU extension 'forkpty' using
+ * SysV STREAMS (the /dev/ptmx pseudoterminal multiplexer).
+ *
+ * dgc@uchicago.edu
+ */
+
+#ifdef HAVE_SYS_IOCTL_H
+  #include <sys/ioctl.h>
+#endif
+
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <limits.h> /* PATH_MAX */
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/termios.h>
+#include <stropts.h>
+
+#define DEV_PTMX "/dev/ptmx"
+
+enum
+{
+	EPTMX_OK = 0,
+	EPTMX_OPEN,
+	EPTMX_GRANT,
+	EPTMX_UNLOCK,
+	EPTMX_NAME,
+	EPTMX_FIND,
+	EPTMX_PUSH_PTEM,
+	EPTMX_PUSH_LDTERM,
+	EPTMX_PUSH_TTCOMPAT,
+	EPTMX_END
+} ptmx_errs;
+
+int open_master(char *name, int sz)
+{
+	char *sname;
+	int fd;
+
+	strncpy(name, DEV_PTMX, sz);
+	name[sz-1] = '\0';
+
+	fd = open(name, O_RDWR);
+	if (fd < 0)
+		return EPTMX_OPEN;
+
+	if (grantpt(fd) < 0)
+	{
+		close(fd);
+		return EPTMX_GRANT;
+	}
+
+	if (unlockpt(fd) < 0)
+	{
+		close(fd);
+		return EPTMX_UNLOCK;
+	}
+
+	sname = ptsname(fd);
+	if (sname == NULL)
+	{
+		close(fd);
+		return EPTMX_NAME;
+	}
+
+	strncpy(name, sname, sz);
+	name[sz-1] = '\0';
+	return fd;
+}
+
+int open_slave(char *name)
+{
+	int fd;
+	int status;
+
+	fd = open(name, O_RDWR);
+	if (fd < 0)
+		return EPTMX_OPEN;
+
+	status = ioctl(fd, I_FIND, "ldterm");
+	if (status < 0)
+	{
+		close(fd);
+		return EPTMX_FIND;
+	}
+
+	if (status > 0)
+		return fd;
+
+	if (ioctl(fd, I_PUSH, "ptem") < 0)
+	{
+		close(fd);
+		return EPTMX_PUSH_PTEM;
+	}
+
+	if (ioctl(fd, I_PUSH, "ldterm") < 0)
+	{
+		close(fd);
+		return EPTMX_PUSH_LDTERM;
+	}
+
+	if (ioctl(fd, I_PUSH, "ttcompat") < 0)
+	{
+		close(fd);
+		return EPTMX_PUSH_TTCOMPAT;
+	}
+
+	return fd;
+}
+
+
+int login_tty(int fd)
+{
+	setsid();
+	if (ioctl(fd, TIOCSCTTY, NULL) == -1)
+		return -1;
+	dup2(fd, 0);
+	dup2(fd, 1);
+	dup2(fd, 2);
+	if (fd > 2)
+		close(fd);
+	return 0;
+}
+
+
+pid_t forkpty(int *masterp, char *name, struct termios *termp, struct winsize *winp)
+{
+	int master, slave;
+	char ptname[PATH_MAX];
+	pid_t pid;
+
+	master = open_master(ptname, sizeof(ptname));
+	if (master < 0)
+	{
+		return -1;
+	}
+
+	slave = open_slave(ptname);
+	if (slave < 0)
+	{
+		close(master);
+		return -1;
+	}
+
+	if (name)
+		strcpy(name, ptname);
+
+	if (termp)
+		tcsetattr(slave, TCSAFLUSH, termp);
+	if (winp)
+		ioctl(slave, TIOCSWINSZ, winp);
+
+	pid = fork();
+	if (pid < 0)
+	{
+		close(slave);
+		close(master);
+		return -1;
+	}
+	else if (pid == 0)
+	{
+		/* child/slave */
+		close(master);
+		login_tty(slave);
+		return 0;
+	}
+
+	/* parent/master */
+	*masterp = master;
+	close(slave);
+	return pid;
+}
+
+#endif

+ 65 - 0
function.c

@@ -0,0 +1,65 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2019 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*               (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                  *
+*                                                                             *
+*                       coded by Sverre Normann 1999                          *
+*                    recoded by Igor van den Hoven 2004                       *
+******************************************************************************/
+
+#include "tintin.h"
+
+
+DO_COMMAND(do_function)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = get_arg_in_braces(ses, arg, arg2, GET_ALL);
+
+	if (*arg1 == 0)
+	{
+		show_list(ses->list[LIST_FUNCTION], 0);
+	}
+
+	else if (*arg1 && *arg2 == 0)
+	{
+		if (show_node_with_wild(ses, arg1, ses->list[LIST_FUNCTION]) == FALSE)
+		{
+			show_message(ses, LIST_FUNCTION, "#FUNCTION: NO MATCH(ES) FOUND FOR {%s}.", arg1);
+		}
+	}
+	else
+	{
+		update_node_list(ses->list[LIST_FUNCTION], arg1, arg2, "", "");
+
+		show_message(ses, LIST_FUNCTION, "#OK. FUNCTION {%s} HAS BEEN SET TO {%s}.", arg1, arg2);
+	}
+	return ses;
+}
+
+
+DO_COMMAND(do_unfunction)
+{
+	delete_node_with_wild(ses, LIST_FUNCTION, arg);
+
+	return ses;
+}

+ 3546 - 0
help.c

@@ -0,0 +1,3546 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2004                       *
+******************************************************************************/
+
+#include "tintin.h"
+
+struct help_type
+{
+	char                  * name;
+	char                  * text;
+	char                  * also;
+};
+
+struct help_type help_table[];
+
+char *help_related(struct session *ses, int index, int html)
+{
+	char *arg;
+	char tmp[BUFFER_SIZE], link[BUFFER_SIZE];
+	static char buf[INPUT_SIZE];
+
+	push_call("help_related(%p,%d,%d)",ses,index,html);
+
+	arg = help_table[index].also;
+
+	buf[0] = 0;
+
+	while (*arg)
+	{
+		arg = get_arg_in_braces(ses, arg, tmp, GET_ONE);
+
+		if (html)
+		{
+			sprintf(link, "\\c<a href='%s.php'\\c>%s\\c</a\\c>", tmp, tmp);
+			sprintf(tmp, "%s", link);
+		}
+
+		if (*buf == 0)
+		{
+			sprintf(buf, "<178>Related<278>: %s", tmp);
+		}
+		else
+		{
+			if (*arg)
+			{
+				cat_sprintf(buf, ", %s", tmp);
+			}
+			else
+			{
+				cat_sprintf(buf, " and %s.", tmp);
+			}
+		}
+	}
+	pop_call();
+	return buf;
+}
+
+DO_COMMAND(do_help)
+{
+	char arg1[BUFFER_SIZE], buf[BUFFER_SIZE];
+	int cnt, found;
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ALL);
+
+	if (*arg1 == 0)
+	{
+		*buf = 0;
+
+		for (cnt = 0 ; *help_table[cnt].name != 0 ; cnt++)
+		{
+			if (strlen(buf) + 19 > ses->wrap)
+			{
+				print_lines(ses, SUB_COL, "<088>%s<088>\n", buf);
+
+				*buf = 0;
+			}
+			cat_sprintf(buf, "%19s ", help_table[cnt].name);
+		}
+
+		if (*buf)
+		{
+			print_lines(ses, SUB_COL, "<088>%s<088>\n", buf);
+		}
+	}
+	else if (!strcasecmp(arg1, "dump"))
+	{
+		FILE *logfile = fopen("../docs/help.html", "w");
+
+		do_configure(ses, "{log} {html}");
+
+		if (HAS_BIT(ses->logmode, LOG_FLAG_HTML))
+		{
+			write_html_header(ses, logfile);
+		}
+
+		*buf = 0;
+
+		for (cnt = 0 ; *help_table[cnt].name != 0 ; cnt++)
+		{
+			if (cnt && cnt % 4 == 0)
+			{
+				substitute(ses, buf, buf, SUB_ESC|SUB_COL);
+
+				logit(ses, buf, logfile, LOG_FLAG_LINEFEED);
+
+				*buf = 0;
+			}
+			cat_sprintf(buf, "     \\c<a href='#%s'\\c>%15s\\c</a\\c>", help_table[cnt].name, help_table[cnt].name);
+		}
+
+		cat_sprintf(buf, "\n\n");
+
+		substitute(ses, buf, buf, SUB_ESC|SUB_COL);
+
+		logit(ses, buf, logfile, LOG_FLAG_LINEFEED);
+
+		for (cnt = 0 ; *help_table[cnt].name != 0 ; cnt++)
+		{
+			sprintf(buf, "\\c<a name='%s'\\c>\\c</a\\c>\n", help_table[cnt].name);
+
+			substitute(ses, buf, buf, SUB_ESC|SUB_COL);
+
+			logit(ses, buf, logfile, LOG_FLAG_LINEFEED);
+
+			sprintf(buf, "<128>         %s\n", help_table[cnt].name);
+
+			substitute(ses, buf, buf, SUB_ESC|SUB_COL);
+
+			logit(ses, buf, logfile, LOG_FLAG_LINEFEED);
+
+			substitute(ses, help_table[cnt].text, buf, SUB_COL);
+
+			logit(ses, buf, logfile, LOG_FLAG_LINEFEED);
+
+			if (*help_table[cnt].also)
+			{
+				substitute(ses, help_related(ses, cnt, 0), buf, SUB_COL);
+
+				logit(ses, buf, logfile, LOG_FLAG_LINEFEED);
+			}
+		}
+		fclose(logfile);
+	}
+	else if (!strcasecmp(arg1, "dump.php"))
+	{
+		FILE *logfile;
+
+		do_configure(ses, "{log} {html}");
+
+		*buf = 0;
+
+		for (cnt = 0 ; *help_table[cnt].name != 0 ; cnt++)
+		{
+			filename_string(help_table[cnt].name, arg1);
+
+			lowerstring(arg1);
+
+			sprintf(buf, "../../manual/%s.php", arg1);
+
+			logfile = fopen(buf, "w");
+
+			fprintf(logfile, "<?php\n\tinclude 'manual.php';\n\n\tshow_head(\"%s.php\");\n\n\tshow_example(\"\n", arg1);
+
+			if (*help_table[cnt].also)
+			{
+				sprintf(buf, "<128>         %s\n", help_table[cnt].name);
+
+				substitute(ses, buf, buf, SUB_ESC|SUB_COL);
+
+				logit(ses, buf, logfile, LOG_FLAG_LINEFEED);
+			}
+			substitute(ses, help_table[cnt].text, buf, SUB_COL);
+
+			logit(ses, buf, logfile, LOG_FLAG_LINEFEED);
+
+			if (*help_table[cnt].also)
+			{
+				substitute(ses, help_related(ses, cnt, 1), buf, SUB_ESC|SUB_COL);
+
+				logit(ses, buf, logfile, LOG_FLAG_LINEFEED);
+			}
+			fprintf(logfile, "\n\t\");\n\n");
+
+			fprintf(logfile, "\tshow_tail();\n?>\n");
+
+			fclose(logfile);
+		}
+	}
+	else
+	{
+		for (cnt = 0 ; *help_table[cnt].name != 0 ; cnt++)
+		{
+			if (is_abbrev(arg1, help_table[cnt].name))
+			{
+				print_lines(ses, SUB_COL, "%s<088>\n", help_table[cnt].text);
+				
+				if (*help_table[cnt].also)
+				{
+					print_lines(ses, SUB_COL, "%s<088>\n\n", help_related(ses, cnt, 0));
+				}
+				return ses;
+			}
+		}
+		found = FALSE;
+
+		for (cnt = 0 ; *help_table[cnt].name != 0 ; cnt++)
+		{
+			if (match(ses, help_table[cnt].name, arg1, SUB_VAR|SUB_FUN))
+			{
+				print_lines(ses, SUB_COL, "%s<088>\n", help_table[cnt].text);
+
+				if (*help_table[cnt].also)
+				{
+					print_lines(ses, SUB_COL, "%s<088>\n\n", help_related(ses, cnt, 0));
+				}
+				found = TRUE;
+			}
+		}
+
+		if (found == FALSE)
+		{
+			tintin_printf2(ses, "No help found for '%s'", arg1);
+		}
+	}
+	return ses;
+}
+
+
+
+/*
+	This help table is a mess, but I got better things to do - Igor
+*/
+
+struct help_type help_table[] =
+{
+	{
+		"ACTION",
+
+		"<178>Command<278>: #action <178>{<278>message<178>} {<278>commands<178>} {<278>priority<178>}<278>\n"
+		"\n"
+		"         The #action command can be used to respond with one or several\n"
+		"         commands to a specific message send by the server. The %1-%99\n"
+		"         variables are substituted from the message and can be used in the\n"
+		"         command part of the action.\n"
+		"\n"
+		"         The priority part is optional and determines the priority of the\n"
+		"         action, it defaults to 5.\n"
+		"\n"
+		"         If the message starts with a ~ color codes must be matched. You can\n"
+		"         enable #config {convert meta} on to display meta characters.\n"
+		"\n"
+		"         For more information on pattern matching see the section on PCRE.\n"
+		"\n"
+		"<178>Example<278>: #action {%1 tells you '%2'} {tell %1 I'm afk.}\n"
+		"\n"
+		"         Actions can be triggered by the showme command and certain system\n"
+		"         messages.\n"
+		"\n"
+		"         Actions can be triggered by the #showme command. If you don't want a\n"
+		"         #showme to get triggered use: #line ignore #showme {text}\n"
+		"\n"
+		"         Actions are ordered alphabetically and only one action can trigger at\n"
+		"         a time. To change the order you can assign a priority, which defaults\n"
+		"         to 5, with a lower number indicating a higher priority. The priority\n"
+		"         can be a floating point number.\n"
+		"\n"
+		"         To remove action with %* as the message, use #unaction {%%*} or\n"
+		"         #unaction {\%*}. Alternatively you could wrap the action inside a\n"
+		"         class, and kill that class when you no longer need the action.\n"
+		"\n"
+		"<178>Comment<278>: You can remove an action with the #unaction command.\n",
+
+		"pcre gag highlight prompt substitute"
+	},
+	{
+		"ALIAS",
+
+		"<178>Command<278>: #alias <178>{<278>name<178>} {<278>commands<178>} {<278>priority<178>}<278>\n"
+		"\n"
+		"         The #alias command can be used to shorten up long or oftenly used\n"
+		"         commands. The %1-99 variables are substituted from the arguments when\n"
+		"         using an alias and represent the 1st till 99th word which can be used\n"
+		"         in the commands part of the alias. If %0 is used it will contain all\n"
+		"         arguments. The priority part is optional and determines the priority\n"
+		"         of the alias, it defaults to 5.\n"
+		"\n"
+		"<178>Example<278>: #alias {k} {kill %1;kick}\n"
+		"\n"
+		"         Typing 'k orc' would result in attacking the orc followed by a kick.\n"
+		"\n"
+		"         You can create multi-word aliases by using variables in the name\n"
+		"         section.\n"
+		"\n"
+		"<178>Example<278>: #alias {k %1 with %2} {draw %2;attack %1;slash %1 with %2;\n"
+		"           kick at %2;strike %1 with %2}\n"
+		"\n"
+		"         Using the above alias you could type k blue smurf with battle axe\n"
+		"\n"
+		"         To have an alias that matches all user input, use %* as the name.\n"
+		"\n"
+		"<178>Example<278>: #alias {%*} {#showme You wrote: %0}\n"
+		"\n"
+		"         Aliases are ordered alphabetically and only one alias can trigger at\n"
+		"         a time. To change the order you can assign a priority, which defaults\n"
+		"         to 5, with a lower number indicating a higher priority. The priority\n"
+		"         can be a floating point number.\n"
+		"\n"
+		"         To remove an alias with %* as the name, use #unalias {%%*} or #unalias\n"
+		"         {\%*}. Alternatively you can wrap the alias inside a class, and kill\n"
+		"         that class when you no longer need the alias.\n"
+		"\n"
+		"         For more information on pattern matching see the section on PCRE.\n"
+		"\n"
+		"<178>Comment<278>: You can remove an alias with the #unalias command.\n",
+
+		"cursor history keypad macro speedwalk tab"
+	},
+	{
+		"ALL",
+
+		"<178>Command<278>: #all <178>{<278>string<178>}<278>\n"
+		"\n"
+		"         If you have multiple sessions in one terminal you can use #all to\n"
+		"         execute the command with all sessions, excluding the startup session.\n"
+		"\n"
+		"<178>Example<278>: #all quit\n"
+		"\n"
+		"         Sends 'quit' to all sessions.\n",
+
+		"port run session sessionname snoop ssl zap"
+	},
+	{
+		"BELL",
+		"<178>Command<278>: #bell <178>{<278>flash<178>|<278>focus<178>|<278>margin<178>|<278>ring<178>|<278>volume<178>} {<278>argument<178>}<278>\n"
+		"\n"
+		"         The #bell command without an argument will ring the terminal bell.\n"
+		"\n"
+		"<178>Example<278>: #action {Bubba tells you} {#bell}\n"
+		"\n"
+		"         If you aren't watching the screen this could be useful if you don't\n"
+		"         want to miss out on a conversation with Bubba. Alternatively you can\n"
+		"         use #system to play a sound file.\n"
+		"\n"
+		"         Some terminals will allow you to use VT100 Operating System Commands\n"
+		"         to change the terminal's bell behavior which can be used to flash the\n"
+		"         taskbar icon and or focus the window on receival of a bell.\n"
+		"\n"
+		"<178>Example<278>: #action {Bubba tells you} {#screen save title;#screen set title Tell!;\n"
+		"           #bell ring;#delay 10 #screen load title}\n"
+		"\n"
+		"         The above example will save your window title, change the title to\n"
+		"         'Tell!', ring the bell, next reset the window title after 10 seconds.\n"
+		"\n"
+		"         It's possible to set the terminal to pop to the foreground upon\n"
+		"         ringing of the alarm bell.\n"
+		"\n"
+		"<178>Example<278>: #bell focus on;#bell ring;#bell focus off\n"
+		"\n"
+		"         It's possible to adjust the alarm bell volume on some terminals.\n"
+		"\n"
+		"<178>Example<278>: #loop {1} {8} {cnt} {#line substitute variables\n"
+		"           #delay {$cnt} #showme {Volume $cnt: #bell volume $cnt;#bell}\n",
+
+		"screen"
+	},
+	{
+		"BREAK",
+
+		"<178>Command<278>: #break\n"
+		"\n"
+		"         The break command can be used inside the #foreach, #loop, #parse,\n"
+		"         #while and #switch statements. When #break is found, tintin will stop\n"
+		"         executing the statement it is currently in and move on to the next.\n"
+		"\n"
+		"<178>Example<278>: #while {1} {#math cnt $cnt + 1;#if {$cnt == 20} {#break}}\n",
+		
+		"statements"
+	},
+	{
+		"BUFFER",
+
+		"<178>Command<278>: #buffer <178>{<278>home<178>|<278>up<178>|<278>down<178>|<278>end<178>|<278>lock<178>|<278>find<178>|<278>get<178>|<278>clear<178>}<278>\n"
+		"\n"
+		"         The buffer command has various options to manipulate your scrollback\n"
+		"         buffer.\n"
+		"\n"
+		"         <178>#buffer {home}\n"
+		"<278>\n"
+		"         Moves you to the top of your scrollback buffer and displays the page.\n"
+		"         Enables scroll lock mode. Most useful when used in a #macro.\n"
+		"\n"
+		"         <178>#buffer {up} [lines]\n"
+		"<278>\n"
+		"         Moves your scrollback buffer up one page and displays the page.\n"
+		"         Enables scroll lock mode. Most useful when used in a #macro. You\n"
+		"         can use #buffer {up} {1} to move the scrollback buffer up 1 line.\n"
+		"\n"
+		"         <178>#buffer {down} [lines]\n"
+		"<278>\n"
+		"         Moves your scrollback buffer down one page and displays the page.\n"
+		"         Enables scroll lock mode unless at the end. Most useful when used in\n"
+		"         a #macro.\n"
+		"\n"
+		"         <178>#buffer {end}\n"
+		"<278>\n"
+		"         Moves you to the end of your scrollback buffer and displays the page.\n"
+		"         Disables scroll lock mode. Most useful when used in a #macro.\n"
+		"\n"
+		"         <178>#buffer {find} {[number]} {<string>}\n"
+		"<278>\n"
+		"         Moves the buffer to the given string which can contain a regular\n"
+		"         expression. Optionally you can provide the number of matches to skip,\n"
+		"         allowing you to jump further back in the buffer.\n"
+		"\n"
+		"         <178>#buffer {get} {<variable>} {<lower bound>} {[upper bound]}\n"
+		"<278>\n"
+		"         Allows you to store one or several lines from your scrollback buffer\n"
+		"         (including color codes) into a variable. The lower and upper bound\n"
+		"         must be between 1 and the size of the buffer. If the upper bound is\n"
+		"         omitted the given line is stored as a standard variable. If an upper\n"
+		"         bound is given the lines between the two bounds are stored as a list.\n"
+		"\n"
+		"         <178>#buffer {lock} {on|off}\n"
+		"<278>\n"
+		"         Toggles the lock on the scrollback buffer. When locked, newly incoming\n"
+		"         text won't be displayed, any command will disable the lock, though\n"
+		"         several buffer commands will re-enable the lock. When unlocking it'll\n"
+		"         move you to the end of your scrollback buffer and display the page.\n"
+		"\n"
+		"         <178>#buffer {write} {<filename>}\n"
+		"<278>\n"
+		"         Writes the scrollback buffer to the given file.\n"
+		"\n"
+		"<178>Example<278>: #macro {\\e[F} {#buffer end}\n",
+
+		"echo grep macro showme screen"
+	},
+	{
+		"BUTTON",
+
+		"<178>Command<278>: #button <178>{<278>square<178>} {<278>commands<178>} {<278>priority<178>}<278>\n"
+		"\n"
+		"         The #button command can be used to respond with one or several\n"
+		"         commands to a mouse click received within the specified square.\n"
+		"         The click coordinates are stored in %0-%3 and can be used in the\n"
+		"         command part of the button.\n"
+		"\n"
+		"         The square part should exists of two coordinates defining the\n"
+		"         upper left and bottom right corner using row, col, row, col syntax.\n"
+		"         The square arguments should be separated by spaces, semi-colons or\n"
+		"         braces.\n"
+		"\n"
+		"         By default the button is set to respond to a mouse button press, to\n"
+		"         respond to other button presses you must add a 5th argument to the\n"
+		"         square that defines the button press type. You can enable #info\n"
+		"         button on to see button events and their type as they happen.\n"
+		"\n"
+		"         The priority part is optional and determines the priority of the\n"
+		"         button, it defaults to 5.\n"
+		"\n"
+		"         You must enable #config {mouse tracking} on for buttons to work.\n"
+		"\n"
+		"         This command draws no visible button, you'll have to do so separately\n"
+		"         if needed.\n"
+		"\n"
+		"<178>Example<278>: #button {1;1;2;2} {#showme You clicked the upper left corner.}\n"
+		"\n"
+		"         Buttons are ordered alphabetically and only one button can trigger at\n"
+		"         a time. To change the order you can assign a priority, which defaults\n"
+		"         to 5, with a lower number indicating a higher priority. The priority\n"
+		"         can be a floating point number.\n"
+		"\n"
+		"<178>Comment<278>: To see button clicks trigger use #info button on.\n"
+		"\n"
+		"<178>Comment<278>: You can remove a button with the #unbutton command.\n",
+
+		"delay event ticker"
+	},
+	{
+		"CASE",
+
+		"<178>Command<278>: #case <178>{<278>conditional<178>} {<278>arguments<178>}<278>\n"
+		"\n"
+		"         The case command must be used within the #switch command. When the\n"
+		"         conditional argument of the case command matches the conditional\n"
+		"         argument of the switch command the body of the case is executed.\n"
+		"\n"
+		"         When comparing strings both the switch and case arguments must be\n"
+		"         surrounded in quotes.\n"
+		"\n"
+		"<178>Example<278>:\n"
+		"\n"
+		"         #function {reverse_direction}\n"
+		"         {\n"
+		"             #switch {\"%1\"}\n"
+		"             {\n"
+		"                 #case {\"north\"} {#return south};\n"
+		"                 #case {\"east\"}  {#return west};\n"
+		"                 #case {\"south\"} {#return north};\n"
+		"                 #case {\"west\"}  {#return east};\n"
+		"                 #case {\"up\"}    {#return down};\n"
+		"                 #case {\"down\"}  {#return up}\n"
+		"             }\n"
+		"         }\n"
+		"\n"
+		"         This function returns the reverse direction. @reverse_direction{north}\n"
+		"         would return south.\n",
+
+		"default statements switch"
+	},
+	{
+		"CAT",
+
+		"<178>Command<278>: #cat <178>{<278>variable<178>} {<278>argument<178>}<278>\n"
+		"\n"
+		"         The cat command will concatinate the argument to the given variable.\n",
+		
+		"format function local math replace script variable"
+	},
+
+	{
+		"CHARACTERS",
+
+		"<278>\n"
+		"         The following special characters are defined:\n"
+		"\n"
+
+		"#        The hashtag is the default character for starting a command and is\n"
+		"         subsequently known as the command character or tintin character.\n"
+		"         When loading a command file the command character is set to the\n"
+		"         first character in the file. The character can also be redefined\n"
+		"         using #config.\n"
+		"\n"
+		";        The semi-colon is used as the command separator and can be used to\n"
+		"         separate two commands. Multiple commands can be strung together as\n"
+		"         well. Trailing semi-colons are ignored when reading a script file\n"
+		"         as this is a common error.\n"
+		"\n"
+		"{ }      Curly brackets aka braces are used for separating multi word command\n"
+		"         arguments, nesting commands, and nesting variables. Braces cannot\n"
+		"         easily be escaped and must always be used in pairs.\n"
+		"\n"
+		"\" \"      Quote characters are used for strings in the #math, #if, #switch,\n"
+		"         and #case commands. It is however suggested to use an extra\n"
+		"         set of braces { } to define strings.\n"
+		"\n"
+		"!        The exclamation sign is used to repeat commands, see #help history.\n"
+		"         The character can be redefined using #config.\n"
+		"\n"
+		"\\        An input line starting with a backslash is send verbatim if you are\n"
+		"         connected to a server. This character can be configured with\n"
+		"         #config.\n",
+
+		"colors escape mathematics pcre"
+	},
+	{
+		"CHAT",
+
+		"<178>Command<278>: #chat <178>{<278>option<178>} {<278>argument<178>}<278>\n"
+		"\n"
+		"         #chat {init}       {port}             Initilizes a chat port.\n"
+		"         #chat {name}       {name}             Sets your chat name.\n"
+		"         #chat {message}    {buddy|all} {text} Sends a chat message\n"
+		"\n"
+		"         #chat {accept}     {buddy} {boost}    Accept a file transfer\n"
+		"         #chat {call}       {address} {port}   Connect to a buddy\n"
+		"         #chat {cancel}     {buddy}            Cancel a file transfer\n"
+		"         #chat {color}      {color names}      Set the default color\n"
+		"         #chat {decline}    {buddy}            Decline a file transfer\n"
+		"         #chat {dnd}                           Decline new connections\n"
+		"         #chat {download}   {directory}        Set your download directory\n"
+		"         #chat {emote}      {buddy|all} {text} Send an emote message\n"
+		"         #chat {forward}    {buddy}            Forward all chat messages\n"
+		"         #chat {forwardall} {buddy}            Forward all session output\n"
+		"         #chat {filestat}   {buddy}            Show file transfer data\n"
+		"         #chat {group}      {buddy} {name}     Assign a chat group\n"
+		"         #chat {ignore}     {buddy}            Ignores someone\n"
+		"         #chat {info}                          Displays your info\n"
+		"         #chat {ip}         {address}          Changes your IP address\n"
+		"         #chat {paste}      {buddy|all} {text} Pastes a block of text\n"
+		"         #chat {peek}       {buddy}            Show one's public connections\n"
+		"         #chat {ping}       {buddy}            Display response time\n"
+		"         #chat {private}    {buddy|all}        Make a connection private\n"
+		"         #chat {public}     {buddy|all}        Make a connection public\n"
+		"         #chat {reply}      {text}             Reply to last private message\n"
+		"         #chat {request}    {buddy}            Request one's public connections\n"
+		"         #chat {send}       {buddy|all} {text} Sends a raw data string\n"
+		"         #chat {sendfile}   {buddy} {filename} Start a file transfer\n"
+		"         #chat {serve}      {buddy}            Forward all public chat messages\n"
+		"         #chat {uninitialize}                  Uninitialize the chat port.\n"
+		"         #chat {who}                           Show all connections\n"
+		"         #chat {zap}        {buddy}            Close a connection\n",
+		
+		"port"
+	},
+	{
+		"CLASS",
+
+		"<178>Command<278>: #class <178>{<278>name<178>} {<278>open<178>|<278>close<178>|<278>list<178>|<278>read<178>|<278>size<178>|<278>write<178>|<278>kill<178>} {<278>arg<178>}<278>\n"
+		"\n"
+		"         <178>#class {<name>} {open}\n"
+		"         <278>  Open a class, closing a previously opened class. All triggers\n"
+		"         <278>  added afterwards are assigned to this class.\n"
+		"         <178>#class {<name>} {clear}\n"
+		"         <278>  Will delete all triggers associated with the given class.\n"
+		"         <178>#class {<name>} {close}\n"
+		"         <278>  Close the given class, opening the last open class, if any.\n"
+		"         <178>#class {<name>} {kill}\n"
+		"         <278>  Will clear, close, and remove the class.\n"
+		"         <178>#class {<name>} {list}\n"
+		"         <278>  List all triggers associated with the given class.\n"
+		"         <178>#class {<name>} {load}\n"
+		"         <278>  Will load the saved copy of the class from memory.\n"
+		"         <178>#class {<name>} {read} {<filename>\n"
+		"         <278>  Will open the class, read the file, and close afterwards.\n"
+		"         <178>#class {<name>} {save}\n"
+		"         <278>  Will save all triggers of the given class to memory.\n"
+		"         <178>#class {<name>} {size} {<variable>}\n"
+		"         <278>  Will store the size of the class in a variable.\n"
+		"         <178>#class {<name>} {write} {<filename>}\n"
+		"         <278>  Will write all triggers of the given class to file.\n"
+		"         The {kill} option will delete all triggers of the given class.\n"
+		"\n"
+		"         Keep in mind that the kill and read option are very fast allowing\n"
+		"         them to be used to enable and disable classes.\n"
+		"\n"
+		"<178>Example<278>: #class extra kill;#class extra read extra.tin\n"
+		"         Deletes all triggers of 'extra' class if any. Read 'extra.tin' file,\n"
+		"         all triggers loaded will be assigned to the fresh new 'extra' class.\n",
+		
+		"config debug ignore info kill line message"
+	},
+	{
+		"COLORS",
+
+		"<178>Syntax<278>:  <<888>xyz>  with x, y, z being parameters\n"
+		"\n"
+		"         Parameter 'x': VT100 code\n"
+		"\n"
+		"         0 - Reset all colors and codes to default\n"
+		"         1 - Bold\n"
+		"         2 - Dim\n"
+		"         4 - Underscore\n"
+		"         5 - Blink\n"
+		"         7 - Reverse\n"
+		"         8 - Skip (use previous code)\n"
+		"\n"
+		"         Parameter 'y':  Foreground color\n"
+		"         Parameter 'z':  Background color\n"
+		"\n"
+		"         0 - Black                5 - Magenta\n"
+		"         1 - Red                  6 - Cyan\n"
+		"         2 - Green                7 - White\n"
+		"         3 - Yellow               8 - Skip\n"
+		"         4 - Blue                 9 - Default\n"
+		"\n"
+		"         For xterm 256 colors support use <<888>aaa> to <<888>fff> for RGB foreground\n"
+		"         colors and <<888>AAA> to <<888>FFF> for RGB background colors. For the grayscale\n"
+		"         foreground colors use <<888>g00> to <<888>g23>, for grayscale background colors\n"
+		"         use <<888>G00> to <<888>G23>.\n"
+		"\n"
+		"         The tertiary colors are as follows:\n"
+		"\n"
+		"         <<888>acf> - Azure            <<888>afc> - Jade\n"
+		"         <<888>caf> - Violet           <<888>cfa> - Lime\n"
+		"         <<888>fac> - Pink             <<888>fca> - Orange\n"
+		"\n"
+		"<178>Example<278>: #showme <<888>acf>Azure    <<888>afc>Jade     <<888>caf>Violet\n"
+		"<178>Example<278>: #showme <<888>cfa>Lime     <<888>fac>Pink     <<888>fca>Orange\n"
+		"\n"
+		"         For 12 bit truecolor use <<888>F000> to <<888>FFFF> for foreground colors and\n"
+		"         <<888>B000> to <<888>BFFF> for background colors.\n"
+		"\n"
+		"         For 24 bit truecolor use <<888>F000000> to <<888>FFFFFFF> for foreground\n"
+		"         colors and <<888>B000000> to <<888>BFFFFFF> for background colors.\n"
+		"\n"
+		"         If the color code exceeds your configured color mode it will be\n"
+		"         downgraded to the closest match.\n",
+
+		"characters coordinates escape mathematics pcre"
+	},
+	{
+		"COMMANDS",
+		
+		"<178>Command<278>: #commands <178>{<278>regex<178>}\n"
+		"<278>\n"
+		"         Shows all commands or all commands matching the given search\n"
+		"         string.\n",
+		
+		"help info statements"
+	},
+
+	{
+		"COORDINATES",
+
+		"<278>\n"
+		"         When the 0,0 coordinate is in the upper left corner TinTin++ uses\n"
+		"         a y,x / rows,cols notation. When the 0,0 coordinate is in the\n"
+		"         bottom left corner tintin uses a x,y / cols/rows notation.\n"
+		"\n"
+		"         When a square is defined this is done by specifying the upper left\n"
+		"         and bottom right corner of the square using four coordinates.\n"
+		"\n"
+		"         The vast majority of tintin commands use row,col notation.\n",
+		
+		"characters colors escape mathematics pcre"
+	},
+	{
+		"CONFIG",
+
+		"<178>Command<278>: #config <178>{<278>option<178>} {<278>argument<178>}<278>\n"
+		"\n"
+		"         This allows you to configure various settings, the settings can be\n"
+		"         written to file with the #write command.\n"
+		"\n"
+		"         If you configure the global session (the one you see as you start up\n"
+		"         tintin) all sessions started will inherite these settings.\n"
+		"\n"
+		"         It's advised to make a configuration file to read on startup if you\n"
+		"         do not like the default settings.\n"
+		"\n"
+		"         Config options which aren't listed by default:\n"
+		"\n"
+		"         #CONFIG {BUFFER SIZE}    {SIZE} Set the scrollback buffer size.\n"
+		"         #CONFIG {CHILD LOCK}   {ON|OFF} Enable or disable command input.\n"
+		"         #CONFIG {CONVERT META} {ON|OFF} Shows color codes and key bindings.\n"
+		"         #CONFIG {DEBUG TELNET} {ON|OFF} Shows telnet negotiations y/n.\n"
+		"         #CONFIG {LOG LEVEL}  {LOW|HIGH} LOW logs server output before triggers.\n"
+		"         #CONFIG {INHERITANCE}  {ON|OFF} Session trigger inheritance y/n.\n"
+		"         #CONFIG {MCCP}         {ON|OFF} Enable or disable MCCP support.\n"
+		"         #CONFIG {PID}          {NUMBER} Set the PID of the master process.\n",
+
+		"class line"
+	},
+	{
+		"CONTINUE",
+
+		"<178>Command<278>: #continue\n"
+		"\n"
+		"         The continue command can be used inside the #FOREACH, #LOOP, #PARSE,\n"
+		"         #WHILE and #SWITCH commands. When #CONTINUE is found, tintin will go\n"
+		"         to the end of the command and proceed as normal, which may be to\n"
+		"         reiterate the command.\n"
+		"\n"
+		"<178>Example<278>: #loop 1 10 cnt {#if {$cnt % 2 == 0} {#continue} {say $cnt}}\n",
+		
+		"break foreach list loop parse repeat return while"
+	},
+	{
+		"CR",
+
+		"<178>Command<278>: #cr\n"
+		"\n"
+		"         Sends a carriage return to the session.  Useful for aliases that need\n"
+		"         extra carriage returns.\n"
+		"\n"
+		"         This command is obsolete as you can accomplish the same using #send\n"
+		"         without an argument or #send {}.\n",
+
+		"forall"
+	},
+	{
+		"CURSOR",
+
+		"<178>Command<278>: #cursor <178>{<278>option<178>} {<278>argument<178>}<278>\n"
+		"\n"
+		"         Typing #cursor without an option will show all available cursor\n"
+		"         options, their default binding, and an explanation of their function.\n"
+		"\n"
+		"         The cursor command's primarly goal is adding customizable input editing\n"
+		"         with macros. Subsequently many cursor commands only work properly when\n"
+		"         used within a macro or event.\n",
+		
+		"alias history keypad macro speedwalk tab"
+	},
+	{
+		"DAEMON",
+		
+		"<178>Command<278>: #daemon <178>{<278>attach<178>|<278>detach<178>|<278>kill<178>|<278>list<178>} <178>[<278>name<178>]\n"
+		"\n"
+		"         <278>#daemon provides functionality similar to that of the screen and tmux\n"
+		"         utilities.\n"
+		"\n"
+		"         <178>#daemon attach [name]\n"
+		"         <278>  The attach option will try to find a daemonized tintin instance and\n"
+		"           take over control. The name argument is optional.\n"
+		"\n"
+		"         <178>#daemon detach [name]\n"
+		"         <278>  The detach option will daemonize tintin, turning it into a background\n"
+		"           process. The name argument is optional and is useful if you have\n"
+		"           several daemonized tt++ instances running so you can keep them apart.\n"
+		"\n"
+		"         <178>#daemon kill [name]\n"
+		"         <278>  Kills all daemons or daemons with matching name.\n"
+		"\n"
+		"         <178>#daemon list [name]\n"
+		"         <278>  List all daemons or daemons with matching name.\n",
+		
+		"script system run"
+	},
+	{
+		"DEBUG",
+
+		"<178>Command<278>: #debug <178>{<278>listname<178>} {<278>on<178>|<278>off<178>|<278>log<178>}<278>\n"
+		"\n"
+		"         Toggles a list on or off. With no argument it shows your current\n"
+		"         settings, as well as the list names that you can debug.\n"
+		"\n"
+		"         If you for example set ACTIONS to ON you will get debug information\n"
+		"         whenever an action is triggered.\n"
+		"\n"
+		"         #debug {listname} {log} will silently write debugging information to\n"
+		"         the log file, you must be logging in order for this to work.\n"
+		"\n"
+		"         Not every list has debug support yet.\n",
+		
+		"class ignore info kill message"
+	},
+	{
+		"DEFAULT",
+
+		"<178>Command<278>: #default <178>{<278>commands<178>}<278>\n"
+		"\n"
+		"         The default command can only be used within the switch command. When\n"
+		"         the conditional argument of non of the case commands matches the switch\n"
+		"         command's conditional statement the default command is executed.\n",
+		
+		"case default else elseif if switch regex"
+	},
+	{
+		"DELAY",
+
+		"<178>Command<278>: #delay <178>{<278>seconds<178>} {<278>command<178>}<278>\n"
+		"<178>Command<278>: #delay <178>{<278>name<178>} {<278>command<178>} {<278>seconds<178>}<278>\n"
+		"\n"
+		"         Delay allows you to have tintin wait the given amount of seconds\n"
+		"         before executing the given command. tintin won't wait before\n"
+		"         executing following input commands if any.\n"
+		"\n"
+		"         Floating point precision for milliseconds is possible.\n"
+		"\n"
+		"<178>Example<278>: #showme first;#delay {1} {#showme last}\n"
+		"         This will print 'first', and 'last' around one second later.\n"
+		"\n"
+		"<178>Comment<278>: If you want to remove a delay with the #undelay command you can add\n"
+		"         a name as the first argument, be aware this changes the syntax. If\n"
+		"         the name is a number keep in mind that delays with the same numeric\n"
+		"         name will not be overwritten\n",
+		
+		"event ticker"
+	},
+
+	{
+		"DRAW",
+
+		"<178>Command<278>: #draw <178>[<278>color<178>] <178>[<278>options<178>] <178><<278>type<178>> <<278>square<178>> {<278>text<178>}\n"
+		"<278>\n"
+		"         The draw commands allows you to draw various types of lines and shapes\n"
+		"         on the screen. Common options and types with a brief description are\n"
+		"         provided when you type #draw without an argument.\n"
+		"\n"
+		"         The square arguments should exists of two coordinates defining the\n"
+		"         upper left and bottom right corner using row, col, row, col syntax.\n"
+		"\n"
+		"\n       You can prefix the option with a color code or color name to color the\n"
+		"         lines and shapes.\n"
+		"\n"
+		"         You can further prefix the option as following:\n"
+		"\n"
+		"         ASCII      will draw in ASCII mode.\n"
+		"         BLANKED    will blank the lines and corners.\n"
+		"         BOTTOM     will draw on the bottom side if possible.\n"
+		"         BUMPED     will precede the draw with an enter.\n"
+		"         CIRCLED    will circle the corners.\n"
+		"         CONVERT    will draw text with meta conversion.\n"
+		"         CROSSED    will cross the corners.\n"
+		"         FILLED     will fill circles and jewels.\n"
+		"         GRID       will draw TABLE as a grid.\n"
+		"         HORIZONTAL will draw horizontal if possible.\n"
+		"         HUGE       will draw text in huge letters.\n"
+		"         JEWELED    will diamond the corners.\n"
+		"         JOINTED    will draw corners.\n"
+		"         LEFT       will draw on the left side if possible.\n"
+		"         NUMBERED   will draw numbered, mostly for debugging.\n"
+		"         PRUNED     will prune the corners.\n"
+		"         RIGHT      will draw on the right side if possible.\n"
+		"         ROUNDED    will round the corners.\n"
+		"         SHADOWED   will shadow HUGE text.\n"
+		"         TEED       will tee the corners.\n"
+		"         TRACED     will trace HUGE text.\n"
+		"         TOP        will draw on the top side if possible.\n"
+		"         TUBED      will draw tubes instead of lines.\n"
+		"         UNICODE    will draw in unicode mode.\n"
+		"         VERTICAL   will draw vertical if possible.\n"
+		"\n"
+		"         The following types are available.\n"
+		"\n"
+		"         <178>[ASCII|UNICODE|HUGE] BOX {[TEXT1]} {[TEXT2]}\n"
+		"         <278>  will draw a box.\n"
+		"         <178>[BLANKED|CIRCLED|CROSSED|JEWELED|ROUNDED|TEED|PRUNED] CORNER\n"
+		"         <278>  will draw a corner.\n"
+		"         <178>[BLANKED|HORIZONTAL|NUMBERED|TUBED|VERTICAL] LINE {[TEXT]}\n"
+		"         <278>  will draw a line.\n"
+		"         <178>RAIN {<VARIABLE>} {[SPAWN]} {[FADE]} {[LEGEND]}\n"
+		"         <278>  will draw digital rain.\n"
+		"         <178>[JOINTED|TOP|LEFT|BOTTOM|RIGHT] SIDE\n"
+		"         <278>  will draw one or more sides of a box.\n"
+		"         <178>[GRID] TABLE {[LIST1]} {[LIST2]}\n"
+		"         <278> will draw a table.\n"
+		"         <178>[HUGE] TILE {[TEXT1]} {[TEXT2]}\n"
+		"         <278>  will draw a tile\n"
+		"\n"
+		"         All draw types take an optional text argument as long as a valid\n"
+		"         square with enough space has been defined. Text is automatically\n"
+		"         word wrapped.\n"
+		"\n"
+		"<178>Example<278>: #draw Blue box 1 1 3 20 {Hello world!}\n",
+
+		"buffer echo grep showme"
+	},
+
+	{
+		"ECHO",
+
+		"<178>Command<278>: #echo <178>{<278>format<178>} {<278>argument1<178>} {<278>argument2<178>} {<278>etc<178>}<278>\n"
+		"\n"
+		"         Echo command displays text on the screen with formatting options. See\n"
+		"         the help file for the format command for more information.\n"
+		"\n"
+		"         The echo command does not trigger actions.\n"
+		"\n"
+		"         As with the #showme command you can split the {format} argument up into\n"
+		"         two braced arguments, in which case the 2nd argument is the row number.\n"
+		"\n"
+		"<178>Example<278>: #echo {The current date is %t.} {%Y-%m-%d %H:%M:%S}\n"
+		"         #echo {[%38s][%-38s]} {Hello World} {Hello World}\n"
+		"         #echo {{this is %s on the top row} {-1}} {printed}\n",
+		
+		"buffer format grep showme"
+	},
+	{
+		"ELSE",
+
+		"<178>Command<278>: #else <178>{<278>commands<178>}<278>\n"
+		"\n"
+		"         The else statement should follow an #IF or #ELSEIF statement and is\n"
+		"         only called if the proceeding #IF or #ELSEIF is false.\n"
+		"\n"
+		"<178>Example<278>: #if {1d2 == 1} {smile};#else {grin}\n",
+		
+		"case default elseif if switch regex"
+	},
+	{
+		"ELSEIF",
+
+		"<178>Command<278>: #elseif <178>{<278>conditional<178>} {<278>commands<178>}<278>\n"
+		"\n"
+		"         The elseif statement should follow an #IF or #ELSEIF statement and is\n"
+		"         only called when the statement is true and the proceeding #IF and\n"
+		"         #ELSEIF statements are false.\n"
+		"\n"
+		"<178>Example<278>: #if {1d3 == 1} {smirk};#elseif {1d2 == 1} {snicker}\n",
+		
+		"case default else if switch regex"
+	},
+	{
+		"END",
+
+		"<178>Command<278>: #end {<message>}\n"
+		"\n"
+		"         Terminates tintin and return to unix.  On most systems, ctrl-c has\n"
+		"         the same result.\n"
+		"\n"
+		"         The message is optional and is printed before tintin exits. When\n"
+		"         using #end {\\} tintin will terminate silently.\n",
+		
+		"zap"
+	},
+	{
+		"ESCAPE CODES",
+
+		"<278>         You may use the escape character \\ for various special characters.\n"
+		"\n"
+		"         \\a    beep the terminal.\n"
+		"         \\c    send a control character, \\ca for ctrl-a.\n"
+		"         \\e    start an escape sequence.\n"
+		"         \\n    send a line feed.\n"
+		"         \\r    send a carriage return.\n"
+		"         \\t    send a horizontal tab.\n"
+		"         \\x    print an 8 bit character using hexadecimal, \\xFF for example.\n"
+		"         \\x7B  send the '{' character.\n"
+		"         \\x7D  send the '}' character.\n"
+		"         \\u    print a 16 bit unicode character, \\uFFFD for example.\n"
+		"         \\U    print a 21 bit unicode character, \\U02AF21 for example.\n"
+		"         \\v    send a vertical tab\n"
+		"\n"
+		"         Ending a line with \\ will stop tintin from appending a line feed.\n"
+		"         To escape arguments in an alias or action use %%0 %%1 %%2 etc.\n",
+		
+		"characters colors coordinates mathematics pcre"
+	},
+	{
+		"EVENT",
+
+		"<178>Command<278>: #event <178>{<278>event type<178>}<278>\n"
+		"\n"
+		"         Events allow you to create triggers for predetermined client events.\n"
+		"\n"
+		"         Use #event without an argument to see a list of possible events with\n"
+		"         a brief description. Use #event %* to see the current list of defined\n"
+		"         events. Use #info {events} {on} to see events get thrown.\n"
+		"\n"
+		"         Some events can be prefixed with CATCH to interrupt default behavior.\n"
+		"\n"
+		"         CATCH <EVENT>\n"
+		"         CHAT MESSAGE           %0 default %1 plain\n"
+		"         CLASS ACTIVATED        %0 class name\n"
+		"         CLASS DEACTIVATED      %0 class name\n"
+		"         DATE                   %1 month - %3 day   %4 hour : %5 minute\n"
+		"         DAY <DAY>              %3 day of the month\n"
+		"         DOUBLE-CLICKED <VAR>   %0 row %1 col %2 -row %3 -col %4 word %5 line\n"
+		"         END OF PATH\n"
+		"         HOUR                   %4 hour\n"
+		"         IAC <VAR> <VAR>\n"
+		"         IAC SB GMCP <MODULE>   %0 data     %1 raw data\n"
+		"         IAC SB MSSP            %0 variable %1 value\n"
+		"         IAC SB MSDP            %0 variable %1 value %2 plain value\n"
+		"         IAC SB MSDP <VAR>      %0 variable %1 value %2 plain value\n"
+		"         IAC SB NEW-ENVIRON     %0 variable %1 value\n"
+		"         IAC SB ZMP <VAR>       %0 value\n"
+		"         IAC SB <VAR>           %0 raw text %1 raw data\n"
+		"         LONG-CLICKED <VAR>     %0 row %1 col %2 -row %3 -col %4 word %5 line\n"
+		"         MAP ENTER MAP          %0 new vnum\n"
+		"         MAP ENTER ROOM         %0 new vnum %1 old vnum\n"
+		"         MAP ENTER ROOM <VAR>   %0 new vnum %1 old vnum\n"
+		"         MAP EXIT MAP           %0 old vnum\n"
+		"         MAP EXIT ROOM          %0 old vnum %1 new vnum\n"
+		"         MAP EXIT ROOM <VAR>    %0 old vnum %1 new vnum\n"
+		"         MAP FOLLOW MAP         %0 old vnum %1 new vnum %2 exit name\n"
+		"         MAP MOUSE LOCATION     %0 vnum %1 location\n"
+		"         MAP UPDATED VTMAP\n"
+		"         MINUTE                 %5 minute\n"
+		"         MONTH                  %1 month\n"
+		"         MOVED <VAR>            %0 row %1 col %2 -row %3 -col %4 word %5 line\n"
+		"         PORT CONNECTION        %0 name %1 ip %2 port\n"
+		"         PORT DISCONNECTION     %0 name %1 ip %2 port\n"
+		"         PORT MESSAGE           %0 data %1 plain data\n"
+		"         PORT LOG MESSAGE       %0 name %1 ip %2 port %3 data %4 plain data\n"
+		"         PORT RECEIVED MESSAGE  %0 name %1 ip %2 port %3 data %4 plain data\n"
+		"         PRESSED <VAR>          %0 row %1 col %2 -row %3 -col %4 word %5 line\n"
+		"         PROGRAM START          %0 startup arguments\n"
+		"         PROGRAM TERMINATION    %0 goodbye message\n"
+		"         READ ERROR             %0 filename %1 error message\n"
+		"         RECEIVED INPUT         %0 raw text\n"
+		"         RECEIVED KEYPRESS      %0 raw text %1 unicode index\n"
+		"         RECEIVED LINE          %0 raw text %1 plain text\n"
+		"         RECEIVED OUTPUT        %0 raw text\n"
+		"         RECEIVED PROMPT        %0 raw text %1 plain text\n"
+		"         RELEASED <VAR>         %0 row %1 col %2 -row %3 -col %4 word %5 line\n"
+		"         SCAN CSV HEADER        %0 all args %1 arg1 %2 arg2 .. %99 arg99\n"
+		"         SCAN CSV LINE          %0 all args %1 arg1 %2 arg3 .. %99 arg99\n"
+		"         SCAN TSV HEADER        %0 all args %1 arg1 %2 arg3 .. %99 arg99\n"
+		"         SCAN TSV LINE          %0 all args %1 arg1 %2 arg3 .. %99 arg99\n"
+		"         SCREEN FOCUS           %0 focus (0 or 1)\n"
+		"         SCREEN LOCATION        %0 rows %1 cols  %2 height %3 width\n"
+		"         SCREEN MOUSE LOCATION  %0-3 screen row/col %4-7 cell row/col %8 loc\n"
+		"         SCREEN RESIZE          %0 rows %1 cols %2 height %3 width\n"
+		"         SCREEN SPLIT           %0 top row %1 top col %2 bot row %3 bot col\n"
+		"         SCREEN UNSPLIT         %0 top row %1 top col %2 bot row %3 bot col\n"
+		"         SCROLLED <VAR>         %0 row %1 col %2 -row %3 -col %4 word %5 line\n"
+		"         SECOND                 %6 second\n"
+		"         SEND OUTPUT            %0 raw text %1 size\n"
+		"         SENT OUTPUT            %0 raw text %1 size\n"
+		"         SESSION ACTIVATED      %0 name\n"
+		"         SESSION CONNECTED      %0 name %1 host %2 ip %3 port\n"
+		"         SESSION CREATED        %0 name %1 host %2 ip %3 port\n"
+		"         SESSION DEACTIVATED    %0 name\n"
+		"         SESSION DISCONNECTED   %0 name %1 host %2 ip %3 port\n"
+		"         SESSION TIMED OUT      %0 name %1 host %2 ip %3 port\n"
+		"         SHORT-CLICKED <VAR>    %0 row %1 col %2 -row %3 -col %4 word %5 line\n"
+		"         SWIPED <DIR>\n"
+		"           %0 dir %1 button %2 row %3 col %4 -row %5 -col %6 row %7 col %8 -row\n"
+		"           %9 -col %10 rows %11 cols\n"
+		"         SYSTEM ERROR           %0 name %1 system msg %2 error %3 error msg\n"
+		"         TIME                   %4 hour : %5 minute : %6 second\n"
+		"         TRIPLE-CLICKED <VAR>   %0 row %1 col %2 -row %3 -col %4 word %5 line\n"
+		"         UNKNOWN COMMAND        %0 raw text\n"
+		"         VARIABLE UPDATE <VAR>  %0 name %1 new value\n"
+		"         VARIABLE UPDATED <VAR> %0 name %1 new value\n"
+		"         VT100 SCROLL REGION    %0 top row %1 bot row %2 rows %3 cols %4 wrap\n"
+		"         WEEK <DAY>             %2 day of the week\n"
+		"         WRITE ERROR            %0 filename %1 error message\n"
+		"         YEAR                   %0 year\n"
+		"\n"
+		"         To see all events trigger use #event info on. Since this can quite\n"
+		"         spammy it's possible to gag event info messages.\n"
+		"\n"
+		"<178>Example<278>: #event {SESSION CONNECTED} {#read mychar.tin}\n"
+		"\n"
+		"<178>Comment<278>: You can remove an event with the #unevent command.\n",
+		
+		"button delay ticker"
+	},
+	{
+		"FORALL",
+
+		"<178>This command is obsolete, please use foreach instead.\n",
+
+		"foreach"
+	},
+	{
+		"FOREACH",
+
+		"<178>Command<278>: #foreach <178>{<278>list<178>} {<278>variable<178>} {<278>commands<178>}<278>\n"
+		"\n"
+		"         For each item in the provided list the foreach statement will update\n"
+		"         the given variable and execute the command part of the statement. List\n"
+		"         elements must be separated by braces or semicolons.\n"
+		"\n"
+		"<178>Example<278>: #foreach {bob;tim;kim} {name} {tell $name Hello}\n"
+		"<178>Example<278>: #foreach {{bob}{tim}{kim}} {name} {tell $name Hello}\n",
+		
+		"break continue list loop parse repeat return while"
+	},
+	{
+		"FORMAT",
+
+		"<178>Command<278>: #format <178>{<278>variable<178>} {<278>format<178>} {<278>argument1<178>} {<278>argument2<178>} {<278>etc<178>}<278>\n"
+		"\n"
+		"         Allows you to store a string into a variable in the exact same way\n"
+		"         C's sprintf works with a few enhancements and limitations such as\n"
+		"         no integer operations and a maximum of 30 arguments.\n"
+		"\n"
+		"         If you use #format inside an alias or action you must escape %1s as\n"
+		"         %+1s or %%1s or %\\1s so the %1 isn't substituted by the trigger.\n"
+		"\n"
+		"         #format {test} {%+9s} {string}  pad string with up to 9 spaces\n"
+		"         #format {test} {%-9s} {string}  post pad string with up to 9 spaces\n"
+		"         #format {test} {%.8s} {string}  copy at most 8 characters\n"
+		"         #format {test} {%a}   {number}  print corresponding charset character\n"
+		"         #format {test} {%c}   {string}  use a highlight color name\n"
+		"         #format {test} {%d}   {number}  print a number with integer formatting\n"
+		"         #format {test} {%f}   {string}  perform floating point math\n"
+		"         #format {test} {%g}   {number}  perform thousand grouping on {number}\n"
+		"         #format {test} {%h}   {string}  turn text into a header line\n"
+		"         #format {test} {%l}   {string}  lowercase text\n"
+		"         #format {test} {%m}   {string}  perform mathematical calculation\n"
+		"         #format {test} {%n}     {name}  capitalize the first letter\n"
+		"         #format {test} {%p}   {string}  strip leading and trailing spaces\n"
+		"         #format {test} {%r}   {string}  reverse text, hiya = ayih\n"
+		"         #format {test} {%s}   {string}  print given string\n"
+		"         #format {test} {%t}   {format}  display time with strftime format\n"
+		"                                         optional {{format}{time}} syntax\n"
+		"         #format {test} {%u}   {string}  uppercase text\n"
+		"         #format {list} {%w}   {string}  store word wrapped text in {list}\n"
+		"                                         optional {{string}{width}} syntax\n"
+		"         #format {test} {%x}      {hex}  print corresponding charset character\n"
+		"         #format {test} {%A}     {char}  store corresponding character value\n"
+		"         #format {test} {%C}   {number}  store number in chronological notation\n"
+		"         #format {test} {%D}      {hex}  convert hex to decimal in {test}\n"
+		"         #format {hash} {%H}   {string}  store a 64 bit string hash in {hash}\n"
+		"         #format {test} {%L}   {string}  store the string length in {test}\n"
+		"         #format {test} {%M}   {number}  convert number to metric in {test}\n"
+		"         #format {test} {%S}   {string}  store the number of spelling errors\n"
+		"         #format {time} {%T}         {}  store the epoch time in {time}\n"
+		"         #format {time} {%U}         {}  store the micro epoch time in {time}\n"
+		"         #format {test} {%X}      {dec}  convert dec to hexadecimal in {test}\n\n"
+		"         #format {test} {%%}             a literal % character\n"
+		"\n"
+		"<178>Comment<278>: See #help TIME for help on the %t argument.\n",
+		
+		"cat echo function local math replace script time variable"
+	},
+
+	{
+		"FUNCTION",
+
+		"<178>Command<278>: #function <178>{<278>name<178>} {<278>operation<178>}<278>\n"
+		"\n"
+		"         Functions allow you to execute a script within a line of text, and\n"
+		"         replace the function call with the line of text generated by the\n"
+		"         function.\n"
+		"\n"
+		"         Be aware that each function should set the $result variable at the\n"
+		"         end of the function, or call #return with the given result.\n"
+		"\n"
+		"         To use a function use the @ character before the function name.\n"
+		"         The function arguments should be placed between braces behind the\n"
+		"         function name with argument separated by semicolons.\n"
+		"\n"
+		"         The function itself can use the provided arguments which are stored\n"
+		"         in %1 to %9, with %0 holding all arguments.\n"
+		"\n"
+		"<178>Example<278>: #function {rnd} {#math {result} {1 d (%2 - %1 + 1) + %1 - 1}}\n"
+		"         #showme A random number between 100 and 200: @rnd{100;200}\n"
+		"\n"
+		"<178>Example<278>: #function gettime {#format result %t %H:%M}\n"
+		"         #showme The current time is @gettime{}\n"
+		"\n"
+		"<178>Comment<278>: You can remove a function with the #unfunction command.\n",
+		
+		"format local math replace script variable"
+	},
+	{
+		"GAG",
+
+		"<178>Command<278>: #gag <178>{<278>string<178>}<278>\n"
+		"\n"
+		"         Removes any line that contains the string.\n"
+		"\n"
+		"<178>Comment<278>: See '#help action', for more information about triggers.\n"
+		"\n"
+		"<178>Comment<278>: You can remove a gag with the #ungag command.\n",
+		
+		"action highlight prompt substitute"
+	},
+	{
+		"GREETING",
+
+		"<268>      #<268>##################################################################<268>#\n"
+		"<268>      #<278>                                                                  <268>#\n"
+		"<268>      #<278>                    T I N T I N + +   "CLIENT_VERSION"                    <268>#\n"
+		"<268>      #<278>                                                                  <268>#\n"
+//		"<268>      #<278>                 <268>T<278>he K<268>i<278>cki<268>n<278> <268>T<278>ickin D<268>i<278>kuMUD Clie<268>n<278>t <268>                #\n"
+//		"<268>      #<278>                                                                  <268>#\n"
+		"<268>      #<278>      Code by Peter Unold, Bill Reis, and Igor van den Hoven      <268>#\n"
+		"<268>      #<278>                                                                  <268>#\n"
+		"<268>      #<268>##################################################################<268>#<288>\n",
+		
+		""
+	},
+	{
+		"GREP",
+
+		"<178>Command<278>: #grep <178>[<278>page<178>] {<278>search string<178>}<278>\n"
+		"\n"
+		"         This command allows you to search for matching lines in your scroll\n"
+		"         back buffer. The amount of matches shown equals your screen size. If\n"
+		"         you want to search back further use the optional page number. You can\n"
+		"         use wildcards for better search results. Be aware the search string\n"
+		"         is case sensitive, which can be disabled by using %i.\n"
+		"\n"
+		"         By default grep searches from the end of the scrollback buffer to the\n"
+		"         beginning, this can be reversed by using a negative page number.\n"
+		"\n"
+		"<178>Example<278>: #grep Bubba tells you\n"
+		"         This will show all occasions where bubba tells you something.\n",
+		
+		"buffer echo showme"
+	},
+	{
+		"HELP",
+
+		"<178>Command<278>: #help <178>{<278>subject<178>}<278>\n"
+		"\n"
+		"         Without an argument #help will list all available help subjects.\n"
+		"\n"
+		"         Using #help %* will display all help entries.\n",
+		
+		"commands debug ignore info message statements"
+	},
+	{
+		"HIGHLIGHT",
+
+		"<178>Command<278>: #highlight <178>{<278>string<178>} {<278>color names<178>} {<278>priority<178>}<278>\n"
+		"\n"
+		"         The highlight command is used to allow you to highlight strings of text.\n"
+		"\n"
+		"         Available color options are:\n"
+		"\n"
+		"         reset      - resets the color state to default\n"
+		"         light      - turns the color light in 16 color mode.\n"
+		"         dark       - turns the color dark in 16 color mode.\n"
+		"         underscore - underscores the text.\n"
+		"         blink      - makes the text blink.\n"
+		"         reverse    - reverse foreground and background color.\n"
+		"         b          - makes next color the background color.\n"
+		"\n"
+		"         Available color names are:\n"
+		"\n"
+		"         <<888>F06B> - azure                 <<888>F08F> - Azure\n"
+		"         <<888>F00B> - blue                  <<888>F00F> - Blue\n"
+		"         <<888>F0BB> - cyan                  <<888>F0FF> - Cyan\n"
+		"         <<888>F000> - ebony                 <<888>F666> - Ebony\n"
+		"         <<888>F0B0> - green                 <<888>F0F0> - Green\n"
+		"         <<888>F0B6> - jade                  <<888>F0F8> - Jade\n"
+		"         <<888>F6B0> - lime                  <<888>F8F0> - Lime\n"
+		"         <<888>FB0B> - magenta               <<888>FF0F> - Magenta\n"
+		"         <<888>FB60> - orange                <<888>FF80> - Orange\n"
+		"         <<888>FB06> - pink                  <<888>FF08> - Pink\n"
+		"         <<888>FB00> - red                   <<888>FF00> - Red\n"
+		"         <<888>F888> - silver                <<888>FDDD> - Silver\n"
+		"         <<888>F860> - tan                   <<888>FDB0> - Tan\n"
+		"         <<888>F60B> - violet                <<888>F80F> - Violet\n"
+		"         <<888>FBBB> - white                 <<888>FFFF> - White\n"
+		"         <<888>FBB0> - yellow                <<888>FFF0> - Yellow\n"
+		"\n"
+
+		"         The %1-99 variables can be used as 'wildcards' that will match with any\n"
+		"         text. They are useful for highlighting a complete line. The %0 variable\n"
+		"         should never be used in highlights.\n"
+		"\n"
+		"         You may start the string to highlight with a ^ to only highlight text\n"
+		"         if it begins the line.\n"
+		"\n"
+		"         Besides color names also <<888>abc> color codes can be used.\n"
+		"\n"
+		"<178>Example<278>: #high {Valgar} {reverse blink}\n"
+		"         Prints every occurrence of 'Valgar' in blinking reverse video.\n"
+		"\n"
+		"<178>Example<278>: #high {^You %1} {bold cyan}\n"
+		"         Boldfaces any line that starts with 'You' in cyan.\n"
+		"\n"
+		"<178>Example<278>: #high {Bubba} {red underscore b green}\n"
+		"         Highlights the name Bubba as red underscored text on green background.\n"
+		"\n"
+		"<178>Comment<278>: See '#help action', for more information about triggers.\n"
+		"\n"
+		"<178>Comment<278>: See '#help substitute', for more advanced color substitution.\n"
+		"\n"
+		"<178>Comment<278>: This command only works with ANSI/VT100 terminals or emulators.\n"
+		"\n"
+		"<178>Comment<278>: You can remove a highlight with the #unhighlight command.\n",
+		
+		"action gag prompt substitute"
+	},
+	{
+		"HISTORY",
+
+		"<178>Command<278>: #history <178>{<278>delete<178>}<278>                 Delete the last command.\n"
+		"         #history <178>{<278>insert<178>}    {<278>command<178>}<278>    Insert a command.\n"
+		"         #history <178>{<278>list<178>}<278>                   Display the entire command history.\n"
+		"         #history <178>{<278>read<178>}      {<278>filename<178>}<278>   Read a command history from file.\n"
+		"         #history <178>{<278>write<178>}     {<278>filename<178>}<278>   Write a command history to file.\n"
+		"\n"
+		"         Without an argument all available options are shown.\n"
+		"\n"
+		"         By default all commands are saved to the history list and the history\n"
+		"         list is saved between sessions in the ~/.tintin/history.txt file.\n"
+		"\n"
+		"         You can set the character to repeat a command in the history with the\n"
+		"         #config {REPEAT CHAR} {<character>} configuration option, by default\n"
+		"         this is set to the exclamation mark.\n"
+		"\n"
+		"         You can use ! by itself to repeat the last command, or !<text> to\n"
+		"         repeat the last command starting with the given text.\n"
+		"\n"
+		"         You can use #config {REPEAT ENTER} {ON} to repeat the last command\n"
+		"         when you press enter on an empty line.\n"
+		"\n"
+		"         You can press ctrl-r to enter an interactive regex enabled history\n"
+		"         search mode, or by issuing #cursor {history search}.\n"
+		"\n"
+		"         TinTin++ tries to bind the arrow up and down keys to scroll through\n"
+		"         the history list by default. You can bind these with a macro yourself\n"
+		"         using #cursor {history next} and #cursor {history prev}. Many #cursor\n"
+		"         commands only work properly when bound with a macro.\n",
+		
+		"alias cursor keypad macro speedwalk tab"
+	},
+	{
+		"IF",
+
+		"<178>Command<278>: #if <178>{<278>conditional<178>} {<278>commands if true<178>} {<278>commands if false<178>}<278>\n"
+		"\n"
+		"         The 'if' command is one of the most powerful commands added since\n"
+		"         TINTIN III. It works similar to an 'if' statement in other languages,\n"
+		"         and is strictly based on the way C handles its conditional statements.\n"
+		"         When an 'if' command is encountered, the conditional statement is\n"
+		"         evaluated, and if TRUE (any non-zero result) the commands are executed.\n"
+		"\n"
+		"         The 'if' statement is only evaluated if it is read, so you must nest\n"
+		"         the 'if' statement inside another statement (most likely an 'action'\n"
+		"         command). The conditional is evaluated exactly the same as in the\n"
+		"         'math' command only instead of storing the result, the result is used\n"
+		"         to determine whether to execute the commands.\n"
+		"\n"
+		"<178>Example<278>: #action {%0 gives you %1 gold coins.} {#if {%1>5000} {thank %0}}\n"
+		"         If someone gives you more than 5000 coins, thank them.\n"
+		"\n"
+		"<178>Comment<278>: See '#help math', for more information.\n",
+		
+		"case default else elseif switch regex"
+	},
+	{
+		"IGNORE",
+
+		"<178>Command<278>: #ignore <178>{<278>listname<178>} {<278>on<178>|<278>off<178>}<278>\n"
+		"\n"
+		"         Toggles a list on or off. With no arguments it shows your current\n"
+		"         settings, as well as the list names that you can ignore.\n"
+		"\n"
+		"         If you for example set ACTIONS to OFF actions will no longer trigger.\n"
+		"         Not every list can be ignored.\n",
+
+		"class debug info kill message"
+	},
+	{
+		"INDEX",
+
+		"<278>"
+		"         On this page you'll find an introduction to using TinTin++. Additional\n"
+		"         information can be found in the individual help sections.\n"
+		"<128>\n"
+		"         Starting and Ending\n"
+		"<278>\n"
+		"         The syntax for starting TinTin++ is: ./tt++ [command file]\n"
+		"\n"
+		"         Read more about the command file in the files section below. Remember\n"
+		"         one thing though. All actions, aliases, substitutions, etc, defined\n"
+		"         when starting up TinTin++ are inherited by all sessions.\n"
+		"\n"
+		"         If you want to exit TinTin++ type '#end' or press ctrl-d on an empty\n"
+		"         line.\n"
+		"\n"
+		"         For the WinTin++ users, if you want to paste text use shift-insert,\n"
+		"         text is automatically copied upon selection. This is typical Linux\n"
+		"         behavior, but it can take some getting used to.\n"
+		"\n"
+		"<128>\n"
+		"         Basic features\n"
+		"<278>\n"
+		"         I'll start by explaining some of the very basic and important features:\n"
+		"\n"
+		"         All TinTin++ commands starts with a '#'.\n"
+		"\n"
+		"<178>Example<278>: #help -- #help is a client command, and isn't send to the\n"
+		"         server.\n"
+		"\n"
+		"         All TinTin++ commands can be abbreviated when typed.\n"
+		"\n"
+		"         #he -- Typing #he is the same as typing #help though it's suggested to\n"
+		"         use at least 3 letter abbreviations just in case another command is\n"
+		"         added that starts with 'he'.\n"
+		"\n"
+		"         All commands can be separated with a ';'.\n"
+		"\n"
+		"         n;l green;s;say Dan Dare is back! -- do these 4 commands\n"
+		"         There are 3 ways ';'s can be overruled.\n"
+		"\n"
+		"         \\say Hello ;) -- Lines starting with a '\\' aren't parsed by TinTin++.\n"
+		"         say Hello \\;) -- The escape character can escape 1 letter.\n"
+		"         #config verbatim on -- Everything is send as is except '#' commands.\n"
+		"<128>\n"
+		"         Connecting to a server\n"
+		"<178>\n"
+		"Command<278>: #session <178>{<278>session name<178>} {<278>server address<178>} {<278>port<178>}<278>\n"
+		"\n"
+		"         Example: #session someone tintin.sourceforge.net 4321\n"
+		"\n"
+		"         You can have more than one session, in which case you can switch\n"
+		"         between sessions typing #<session name>.\n"
+		"\n"
+		"         You can get a list of all sessions by typing: #session. The current\n"
+		"         active session is marked with (active). Snooped sessions with\n"
+		"         (snooped). MCCP sessions (compression) with (mccp 2) and (mccp 3).\n"
+		"\n"
+		"<128>\n"
+		"         Split\n"
+		"<178>\n"
+		"Command<278>: #split\n"
+		"\n"
+		"         The split command will create a separated input and output area.\n"
+		"\n"
+		"         Using the #prompt command you can capture the prompt and place it on\n"
+		"         the split line. To get rid of the split interface you can use #unsplit\n"
+		"         which will restore the terminal settings to default.\n"
+		"\n"
+		"<128>\n"
+		"         Alias\n"
+		"<178>\n"
+		"Command<278>: #alias <178>{<278>name<178>} {<278>commands<178>}<278>\n"
+		"\n"
+		"         The syntax of the #alias command is almost like alias in csh.\n"
+		"         Use this command to define aliases. The variables %0, %1.. %9 contain\n"
+		"         the arguments to the aliased command as follows:\n"
+		"         the %0 variable contains ALL the arguments.\n"
+		"         the %1 variable contains the 1st argument\n"
+		"         ....\n"
+		"         the %9 variable contains the 9th argument\n"
+		"\n"
+		"<178>Example<278>: #alias greet say Greetings, most honorable %1\n"
+		"\n"
+		"         If there are no variables on the right-side of the alias definition,\n"
+		"         any arguments following the aliases-command will be appended to the\n"
+		"         command string.\n"
+		"\n"
+		"<178>Example<278>: #alias ff cast 'fireball' -- 'ff bob' equals: cast 'fireball' bob\n"
+		"\n"
+		"         If you want an alias to execute more commands, you must use braces.\n"
+		"\n"
+		"<178>Example<278>: #alias ws <178>{<278>wake;stand<178>}<278>\n"
+		"\n"
+		"         To delete an alias use the #unalias command.\n"
+		"\n"
+		"         WARNING! TinTin++ doesn't baby sit, and hence does not check for\n"
+		"         recursive aliases! You can avoid recursion by escaping the entire\n"
+		"         line.\n"
+		"\n"
+		"<178>Example<278>: #alias put \\put %1 in %2\n"
+		"\n"
+		"         Or by using the send command.\n"
+		"\n"
+		"<178>Example<278>: #send put %1 in %2\n"
+		"\n"
+		"\n"
+		"<128>         Action\n"
+		"\n"
+		"<178>Command<278>: #action <178>{<278>action-text<178>} {<278>commands<178>}<278>\n"
+		"\n"
+		"         Use this command to define an action to take place when a particular\n"
+		"         text appears on your screen. There are 99 variables you can use as\n"
+		"         wildcards in the action-text.\n"
+		"\n"
+		"         These variables are %1, %2, %3 .... %9, %10, %11 ... %97, %98, %99.\n"
+		"\n"
+		"<178>Example<278>: #action <178>{<278>You are hungry<178>} {<278>get bread bag;eat bread<178>}<278>\n"
+		"\n"
+		"<178>Example<278>: #action <178>{<278>%1 has arrived.<178>}<278> shake %1 -- shake hands with people arriving.\n"
+		"\n"
+		"<178>Example<278>: #action <178>{<278>%1 tells you '%2'<178>}\n"
+		"                   {<278>tell bob %1 told me '%2'<178>}<278> -- forward tells.\n"
+		"\n"
+		"<178>Example<278>: #action <178>{<278>tells you<178>}<278> #bell -- beep on tell.\n"
+		"\n"
+		"         You can have TinTin++ ignore actions if you type '#ignore actions on'.\n"
+		"\n"
+		"         You can see what commands TinTin++ executes when an action triggers\n"
+		"         by typing '#debug actions on'.\n"
+		"\n"
+		"         You can remove actions with the #unaction command.\n"
+		"\n"
+		"<128>\n"
+		"         Highlight\n"
+		"\n"
+		"<178>Command<278>: #highlight <178>{<278>text<178>} {<278>color<178>}<278>\n"
+		"\n"
+		"         This command works a bit like #action. The purpose of this command is\n"
+		"         to substitute text from the server with color you provide. This command\n"
+		"         is a simplified version of the #substitute command.\n"
+		"\n"
+		"<178>Example<278>: #high <178>{<278>Snowy<178>} {<278>light yellow<178>}<278>\n"
+		"\n"
+		"<178>Example<278>: #high <178>{<278>%*Snowy%*<178>} {<278>light yellow<178>}<278>\n"
+		"\n"
+		"         Use #unhigh to delete highlights.\n"
+		"\n"
+		"\n"
+		"         Speedwalk\n"
+		"\n"
+		"         If you type a command consisting ONLY of letters and numbers n, e, s,\n"
+		"         w, u, d - then this command can be interpreted as a serie of movement\n"
+		"         commands.\n"
+		"\n"
+		"<178>Example<278>: ssw2n -- go south, south, west, north, north\n"
+		"\n"
+		"         If you have problems with typing some commands that actually ONLY\n"
+		"         consists of these letters, then type them in CAPS. For example when\n"
+		"         checking the NEWS or when asked to enter NEW as your name.\n"
+		"\n"
+		"         You must enable speedwalking with: #config speedwalk on/off.\n"
+		"\n"
+		"<128>\n"
+		"         Ticker\n"
+		"\n"
+		"<178>Command<278>: #ticker <178>{<278>name<178>} {<278>commands<178>} {<278>seconds<178>}<278>\n"
+		"\n"
+		"         The name can be whatever you want it to be, and is only required for\n"
+		"         the unticker command. The commands will be executed every x amount of\n"
+		"         seconds, which is specified in the interval part.\n"
+		"\n"
+		"<178>Example<278>: #tick <178>{<278>tick<178>} {<278>#delay 50 #show 10 SECONDS TO TICK!;#show TICK!!!<178>} {<278>60<178>}<278>\n"
+		"\n"
+		"         This creates a ticker with the name <178>{<278>tick<178>}<278> which will print TICK!!!,\n"
+		"         as well as print a warning when the next tick will occure.\n"
+		"\n"
+		"         You can remove tickers with #untick\n"
+		"\n"
+		"<128>\n"
+		"         Command files\n"
+		"<278>\n"
+		"         When you order TinTin++ to read a command file, it parses all the text\n"
+		"         in the file. You can use command files to keep aliases/actions in,\n"
+		"         login to a server (name, password etc..) and basically all kinds of\n"
+		"         commands.\n"
+		"\n"
+		"         You can make the command files with either a text editor (suggested),\n"
+		"         or use the #write command to write out a file.\n"
+		"\n"
+		"         Commands for files:\n"
+		"\n"
+		"         #read filename -- read and execute the file.\n"
+		"\n"
+		"         #write filename -- write all actions/aliases/substitutes/etc known for\n"
+		"         the current session to a file.\n"
+		"\n"
+		"<128>\n"
+		"         Repeating Commands\n"
+		"<278>\n"
+		"         You can repeat a command, the syntax is: #number command\n"
+		"\n"
+		"<178>Example<278>: #5 cackle -- if you just killed bob the wizard.\n"
+		"<178>Example<278>: #10 <178>{<278>buy bread;put bread bag<178>}<278> -- repeat these 2 commands 10 times.\n"
+		"<178>Example<278>: #100 ooc w00t w00t!!!!! -- nochannel yourself.\n"
+		"\n"
+		"<128>\n"
+		"         History\n"
+		"<278>\n"
+		"         TinTin++ has a limited subset of the csh history features.\n"
+		"\n"
+		"         ! -- repeat the last command\n"
+		"         !cast -- repeat the last command starting with cast\n"
+		"         ctrl-r -- enter the reverse history search mode.\n"
+		"\n"
+		"<128>\n"
+		"         Map commands\n"
+		"<278>\n"
+		"         TinTin++ has a powerful highly configurable automapper. Whenever\n"
+		"         you type n/ne/e/se/s/sw/w/nw/n/u/d tt++ tries to keep track of your\n"
+		"         movement.\n"
+		"\n"
+		"         Commands for map:\n"
+		"\n"
+		"         #map create -- create a map.\n"
+		"         #map goto 1 -- go to the first room in the map, created by default.\n"
+		"         #map map -- display the map.\n"
+		"         #map undo -- undo your last map alteration.\n"
+		"         #map write <filename> -- save the map to file.\n"
+		"         #map read <filename> -- load a map from file.\n"
+		"\n"
+		"         There are many other map options and it's beyond the scope of this\n"
+		"         help section to explain everything there is to know, but I'll give\n"
+		"         a set of commands that will get most people started.\n"
+		"\n"
+		"         #map create\n"
+		"         #split 12 1\n"
+		"         #map flag unicode on\n"
+		"         #map flag vt on\n"
+		"         #map goto 1\n"
+		"\n"
+		"         These commands will create a 12 row vt100 split section at the top of\n"
+		"         your screen where a map drawn using unicode characters is displayed.\n"
+		"\n"
+		"<178>Example<278>: #action <178>{<278>There is no exit in that direction.<178>} {<278>#map undo<178>}<278>\n"
+		"\n"
+		"         The map will be automatically created as you move around.\n"
+		"\n"
+		"<128>\n"
+		"         Help\n"
+		"\n"
+		"<178>Command<278>: #help <178>{<278>subject<178>}<278>\n"
+		"\n"
+		"         The help command is your friend and contains the same helpfiles\n"
+		"         inside TinTin++ as are available on the website. If you type #help\n"
+		"         without an argument you will see the various available help subjects\n"
+		"         which try to explain the TinTin++ commands and features in greater\n"
+		"         detail.\n"
+		"\n"
+		"<128>\n"
+		"         Enjoy<278>\n",
+
+		""
+	},
+	{
+		"INFO",
+
+		"<178>Command<278>: #info <178>{<278>listname<178>} {<278>LIST<178>|<278>ON<178>|<278>OFF<178>|<278>SAVE<178>}<278>\n"
+		"\n"
+		"         Without an argument info displays the settings of every tintin list.\n"
+		"\n"
+		"         By providing the name of a list and the LIST option it shows all\n"
+		"         triggers/variables associated with that list. With the SAVE option\n"
+		"         This data is written to the info variable.\n"
+		"\n"
+		"         #info cpu will show information about tintin's cpu usage.\n"
+		"         #info mccp will show information about data compression.\n"
+		"         #info stack will show the low level debugging stack.\n"
+		"         #info session will show some session information.\n"
+		"         #info system will show some system information.\n",
+
+		"class debug ignore kill message"
+	},
+	{
+		"KEYPAD",
+
+		"<278>When TinTin++ starts up it sends \\e= to the terminal to enable the terminal's\n"
+		"application keypad mode, which can be disabled using #showme {\\e>}\n"
+		"\n"
+		"<178>      Configuration A           Configuration B           Configuration C<268>\n"
+		" ╭─────┬─────┬─────┬─────╮ ╭─────┬─────┬─────┬─────╮ ╭─────┬─────┬─────┬─────╮\n"
+		" │<178>num<268>  │<178>/<268>    │<178>*<268>    │<178>-<268>    │ │<178>num<268>  │<178>/<268>    │<178>*<268>    │<178>-<268>    │ │<178>Num<268>  │<178>nkp/<268> │<178>nkp*<268> │<178>nkp-<268> │\n"
+		" ├─────┼─────┼─────┼─────┤ ├─────┼─────┼─────┼─────┤ ├─────┼─────┼─────┼─────┤\n"
+		" │<178>7<268>    │<178>8<268>    │<178>9<268>    │<178>+<268>    │ │<178>Home<268> │<178>Up<268>   │<178>PgUp<268> │<178>+<268>    │ │<178>nkp7<268> │<178>nkp8<268> │<178>nkp9<268> │<178>nkp+<268> │\n"
+		" ├─────┼─────┼─────┤     │ ├─────┼─────┼─────┤     │ ├─────┼─────┼─────┤     │\n"
+		" │<178>4<268>    │<178>5<268>    │<178>6<268>    │     │ │<178>Left<268> │<178>Cntr<268> │<178>Right<268>│     │ │<178>nkp4<268> │<178>nkp5<268> │<178>nkp6<268> │     │\n"
+		" ├─────┼─────┼─────┼─────┤ ├─────┼─────┼─────┼─────┤ ├─────┼─────┼─────┼─────┤\n"
+		" │<178>1<268>    │<178>2<268>    │<178>3<268>    │<178>Enter<268>│ │<178>End<268>  │<178>Down<268> │<178>PgDn<268> │<178>Enter<268>│ │<178>nkp1<268> │<178>nkp2<268> │<178>nkp3<268> │<178>nkpEn<268>│\n"
+		" ├─────┴─────┼─────┤     │ ├─────┴─────┼─────┤     │ ├─────┴─────┼─────┤     │\n"
+		" │<178>0<268>          │<178>.<268>    │     │ │<178>Ins<268>        │<178>Del<268><268>  │     │ │<178>nkp0<268>       │<178>nkp.<268> │     │\n"
+		" ╰───────────┴─────┴─────╯ ╰───────────┴─────┴─────╯ ╰───────────┴─────┴─────╯\n"
+		"<278>\n"
+		"With keypad mode disabled numlock on will give you configuration A, and numlock\n"
+		"off will give you configuration B. With keypad mode enabled you'll get\n"
+		"configuration C.\n"
+		"\n"
+		"<178>Terminals that support keypad mode"
+		"\n"
+		"<278>Linux Console, PuTTY, Eterm, aterm.\n"
+		"\n"
+		"<178>Terminals that do not support keypad mode\n"
+		"\n"
+		"<278>RXVT on Cygwin, Windows Console, Gnome Terminal, Konsole.\n"
+		"\n"
+		"<178>Peculiar Terminals\n"
+		"\n"
+		"<278>RXVT requires turning off numlock to enable configuration C.\n"
+		"\n"
+		"Xterm may require disabling Alt/NumLock Modifiers (num-lock) in the ctrl-left\n"
+		"click menu. Or edit ~/.Xresources and add XTerm*VT100.numLock:false\n"
+		"\n"
+		"Mac OS X Terminal requires enabling 'strict vt100 keypad behavior' in\n"
+		"Terminal -> Window Settings -> Emulation.\n",
+		
+		"colors coordinates escape mathematics pcre"
+	},
+	{
+		"KILL",
+
+		"<178>Command<278>: #kill <178>{<278>list<178><178>} {<278>pattern<178>}<278>\n"
+		"\n"
+		"         Without an argument, the kill command clears all lists.  Useful if\n"
+		"         you don't want to exit tintin to reload your command files.\n"
+		"\n"
+		"         With one argument a specific list can be cleared.\n"
+		"\n"
+		"         With two arguments the triggers in the chosen list that match the\n"
+		"         given pattern will be removed.\n"
+		"\n"
+		"<178>Example<278>: #kill alias %*test*\n",
+
+		"class debug ignore info message"
+	},
+	{
+		"LINE",
+
+		"<178>Command<278>: #line <178>{<278>option<178>} {<278>argument<178>}<278>\n"
+		"\n"
+		"         <178>#line background <argument>\n"
+		"         <278>  Prevent new session activation.\n"
+		"\n"
+		"         <178>#line capture <variable> <argument.\n"
+		"         <278>  Argument is executed and output stored in <variable>.\n"
+		"\n"
+		"         <178>#line debug <argument>\n"
+		"         <278>  Argument is executed in debug mode.\n"
+		"\n"
+		"         <178>#line gag\n"
+		"         <278>                Gag the next line.\n"
+		"\n"
+		"         <178>#line ignore {argument}\n"
+		"         <278>  Argument is executed without any triggers being checked.\n"
+		"\n"
+		"         <178>#line log <filename> [text]\n"
+		"         <278>  Log the next line to file unless the [text] argument is\n"
+		"         <278>  provided.\n"
+		"\n"
+		"         <178>#line logmode <option> <argument>\n"
+		"         <278>  Argument is executed using the provided logmode, available\n"
+		"         <278>  modes are: html, plain, and raw.\n"
+//		"\n"
+//		"         <178>#line logverbatim {filename} {[text]}  Log text without variable\n"
+//		"         <278>                                  substitution.\n"
+		"\n"
+		"         <178>#line oneshot <argument>\n"
+		"         <278>  Argument is executed in oneshot mode, all triggers created will\n"
+		"         <278>  only fire once.\n"
+		"\n"
+		"         <178>#line quiet <argument>\n"
+		"         <278>  Argument is executed with suppression of most system messages.\n"
+		"\n"
+		"         <178>#line strip <argument>\n"
+		"         <278>  Argument is executed with all color codes stripped.\n"
+		"\n"
+		"         <178>#line substitute <options> <argument>\n"
+		"         <278>  Argument is executed using the provided substitutions, available\n"
+		"         <278>  options are: arguments, colors, escapes, functions, secure, and\n"
+		"         <278>  variables.\n"
+		"\n"
+		"         <178>#line verbatim <argument>\n"
+		"         <278>  Argument is executed verbatim, prohibiting variable and function\n"
+		"         <278>  substitutions.\n"
+		"\n"
+		"         <178>#line verbose <argument>\n"
+		"         <278>  Argument is executed with most system messages enabled.\n"
+		"\n"
+		"         When using #line log and logging in html format use \\c< \\c> \\c& \\c\" to\n"
+		"         log a literal < > & and \".\n",
+
+		"class config"
+	},
+	{
+		"LIST",
+
+		"<178>Command<278>: #list <178>{<278>variable<178>} {<278>option<178>} {<278>argument<178>}<278>\n"
+		"\n"
+		"         #list {var} {add} {item}               Add {item} to the list\n"
+		"         #list {var} {clear}                    Empty the given list\n"
+		"         #list {var} {collapse}                 Turn list into a variable\n"
+		"         #list {var} {create} {item}            Create a list using {items}\n"
+		"         #list {var} {delete} {index} {number}  Delete the item at {index},\n"
+		"                                                the {number} is optional.\n"
+		"         #list {var} {explode}                  Turn list into a character list\n"
+		"         #list {var} {insert} {index} {string}  Insert {string} at given index\n"
+		"         #list {var} {find} {string} {variable} Return the found index\n"
+		"         #list {var} {get} {index} {variable}   Copy an item to {variable}\n"
+		"         #list {var} {shuffle}                  Shuffle the list\n"
+		"         #list {var} {set} {index} {string}     Change the item at {index}\n"
+		"         #list {var} {simplify} {variable}      Copy simple list to {variable}\n"
+		"         #list {var} {size} {variable}          Copy list size to {variable}\n"
+		"         #list {var} {sort} {string}            Insert item in alphabetic order\n"
+		"         #list {var} {tokenize} {string}        Create a character list\n"
+		"\n"
+		"         The index should be between 1 and the list's length. You can also give\n"
+		"         a negative value, in which case -1 equals the last item in the list, -2\n"
+		"         the second last, etc.\n"
+		"\n"
+		"         When inserting an item a positive index will prepend the item at the\n"
+		"         given index, while a negative index will append the item.\n"
+		"\n"
+		"         The add and create options allow using multiple items, as well\n"
+		"         as semicolon separated items.\n"
+		"\n"
+		"         A length of 0 is returned for an empty or non existant list.\n"
+		"\n"
+		"         You can directly access elements in a list variable using $var[1],\n"
+		"         $var[2], $var[-1], etc.\n",
+
+		"break continue foreach loop parse repeat return while"
+	},
+
+	{
+		"LOCAL",
+
+		"<178>Command<278>: #local <178>{<278>variable name<178>} {<278>text to fill variable<178>}<278>\n"
+		"\n"
+		"         The local command sets a local variable. Unlike a regular variable\n"
+		"         a local variable will only stay in memory for the duration of the\n"
+		"         event that created it. They are accessed in the same way as a\n"
+		"         regular variable.\n"
+		"\n"
+		"         Commands that store information to a variable will use a local variable\n"
+		"         if it exists.\n"
+		"\n"
+		"         Avoid setting the result variable as local in a function.\n"
+		"\n"
+		"<178>Example<278>: #alias {swap} {#local x %0;#replace x {e} {u};#showme $x}\n",
+
+		"format function math replace script variable"
+	},
+
+	{
+		"LOG",
+
+		"<178>Command<278>: #log <178>{<278>append<178>|<278>overwrite<178>|<278>off<178>} {<278>[filename]<178>}<278>\n"
+		"\n"
+		"         Logs session output to a file, you can set the data type to either\n"
+		"         plain, raw, or html with the config command.\n",
+		
+		"read scan textin write"
+	},
+
+	{
+		"LOOP",
+
+		"<178>Command<278>: #loop <178>{<278><start><178>} {<278><finish><178>} {<278><variable><178>} {<278>commands<178>}<278>\n"
+		"\n"
+		"         Like a for statement, loop will loop from start to finish incrementing\n"
+		"         or decrementing by 1 each time through.  The value of the loop counter\n"
+		"         is stored in the provided variable, which you can use in the commands.\n"
+		"\n"
+		"<178>Example<278>: #loop 1 3 loop {get all $loop\\.corpse}\n"
+		"         This equals 'get all 1.corpse;get all 2.corpse;get all 3.corpse'.\n"
+		"\n"
+		"         The . needs to be escaped so it's not treated as part of the variable.\n"
+		"\n"
+		"<178>Example<278>: #loop 3 1 cnt {drop $cnt\\.key}\n"
+		"         This equals 'drop 3.key;drop 2.key;drop 1.key'.\n",
+
+		"break continue foreach list parse repeat return while"
+	},
+	{
+		"MACRO",
+
+		"<178>Command<278>: #macro <178>{<278>key sequence<178>} {<278>commands<178>}<278>\n"
+		"\n"
+		"         Macros allow you to make tintin respond to function keys.\n"
+		"\n"
+		"         The key sequence send to the terminal when pressing a function key\n"
+		"         differs for every OS and terminal. To find out what sequence is send\n"
+		"         you can enable the CONVERT META config option.\n"
+		"\n"
+		"         Another option is pressing ctrl-v, which will enable CONVERT META for\n"
+		"         the next key pressed.\n"
+		"\n"
+		"         If you only want a key sequence to trigger at the start of an input\n"
+		"         line prefix the key sequence with ^.\n"
+		"\n"
+		"<178>Example<278>: #macro {(press ctrl-v)(press F1)} {#showme \\e[2J;#buffer lock}\n"
+		"         Clear the screen and lock the window when you press F1, useful when the\n"
+		"         boss is near.\n"
+		"\n"
+		"<178>Example<278>: #macro {\\eOM} {#cursor enter}\n"
+		"         Makes the keypad's enter key work as an enter in keypad mode.\n"
+		"\n"
+		"<178>Example<278>: #macro {^nn} {n}\n"
+		"         Makes pressing n twice on an empty line execute north.\n"
+		"\n"
+		"<178>Comment<278>: Not all terminals properly initialize the keypad key sequences.\n"
+		"         If this is the case you can still use the keypad, but instead of the\n"
+		"         arrow keys use ctrl b, f, p, and n.\n"
+		"\n"
+		"<178>Comment<278>: You can remove a macro with the #unmacro command.\n",
+
+		"alias cursor history keypad speedwalk tab"
+	},
+	{
+		"MAP",
+
+		"<178>Command<278>: #map\n"
+		"\n"
+		"         The map command is the backbone of the auto mapping feature.\n"
+		"\n"
+		"         <178>#map at <exit|vnum> <command>\n"
+		"         <278>  Execute the command at the given exit or vnum.\n"
+		"\n"
+		"         <178>#map center <x> <y> <z>\n"
+		"         <278>  Sets displaying center of the map viewer, default is 0 0 0.\n"
+		"\n"
+		"         <178>#map color <field> [value]\n"
+		"         <278>  Sets the map color for the given color field.\n"
+		"\n"
+		"         <178>#map create <size>\n"
+		"         <278>  Creates a new map and room 1. The default size is 50000 rooms.\n"
+		"\n"
+		"         <178>#map destroy {area|world} <name>\n"
+		"         <278>  Deletes the map or given area.\n"
+		"\n"
+		"         <178>#map delete <exit|vnum>\n"
+		"         <278>  Deletes the room for the given exit or vnum.\n"
+		"\n"
+		"         <178>#map dig <exit|vnum> [new|<vnum>]\n"
+		"         <278>  Creates an exit for the given exit name. If no valid exit name\n"
+		"         <278>  is given or no existing room is found a new room is created.\n"
+		"         <278>  Useful for portal links and other alternative forms of\n"
+		"         <278>  transportation. If the 'new' argument is provided all existing\n"
+		"         <278>  rooms are ignored and a new room is created. If a room vnum is\n"
+		"         <278>  given as the second argument an exit will be created leading\n"
+		"         <278>  to the given room vnum. If the room vnum doesn't exist a new\n"
+		"         <278>  room is created.\n"
+		"\n"
+		"         <178>#map entrance <exit> [option] [arg]\n"
+		"         <278>  Set the entrance data for the given exit. You must specify a\n"
+		"         <278>  valid two-way exit for this to work.\n"
+		"\n"
+		"         <178>#map exit <exit> <option> <arg>\n"
+		"         <278>  Set the exit data. Useful with a closed door where you can\n"
+		"         <278>  set the exit command: '#map exit e command {open east;e}'.\n"
+		"         <278>  Use #map exit <exit> for a list of available options.\n"
+		"\n"
+		"         <178>#map exitflag <exit> <AVOID|BLOCK|HIDE|INVIS> [on|off]\n"
+		"         <278>  Set exit flags. See #map roomflag for more info.\n"
+		"\n"
+		"         <178>#map explore <exit>\n"
+		"         <278>  Explores the given exit until a dead end or an\n"
+		"         <278>  intersection is found. The route is stored in #path and can\n"
+		"         <278>  subsequently be used with #walk. Useful for long roads.\n"
+		"\n"
+		"         <178>#map find <name> <exits> <desc> <area> <note> <terrain> <flag>\n"
+		"         <278>  searches for the given room name. If found the shortest path\n"
+		"         <278>  from your current location to the destination is calculated.\n"
+		"         <278>  The route is stored in #path and can subsequently be used with\n"
+		"         <278>  the various #path commands. If <exits> is provided all exits\n"
+		"         <278>  must be matched, if <roomdesc>, <roomarea> or <roomnote> or\n"
+		"         <278>  <roomterrain> or <roomflag> is provided these are matched as\n"
+		"         <278>  well against the room to be found.\n"
+		"         <278>  These options are also available to the at, delete, goto\n"
+		"         <278>  link, list and run commands.\n"
+		"\n"
+		"         <178>#map flag asciigraphics\n"
+		"         <278>  Takes up more space but draws a more detailed\n"
+		"         <278>  map that displays the ne se sw nw exits and room symbols.\n"
+		"\n"
+		"         <178>#map flag asciivnums\n"
+		"         <278>  Display room vnums if asciigraphics is enabled.\n"
+		"\n"
+		"         <178>#map flag nofollow\n"
+		"         <278>  When you enter movement commands the map will no longer\n"
+		"         <278>  automatically follow along. Useful for MSDP and GMCP\n"
+		"         <278>  automapping scripts.\n"
+		"\n"
+		"         <178>#map flag static\n"
+		"         <278>  Will make the map static so new rooms are no longer\n"
+		"         <278>  created when walking into an unmapped direction. Useful when\n"
+		"         <278>  you're done mapping and regularly bump into walls accidentally\n"
+		"         <278>  creating a new room. #map dig etc will still work.\n"
+		"\n"
+		"         <178>#map flag vtgraphics\n"
+		"         <278>  Enables vt line drawing on some terminals\n"
+		"\n"
+		"         <178>#map flag vtmap\n"
+		"         <278>  Will enable the vtmap which is shown in the top split\n"
+		"         <278>  screen if you have one. You can create a 16 rows high top\n"
+		"         <278>  screen by using '#split 16 1'.\n"
+		"\n"
+		"         <178>#map get <option> <variable> [vnum]\n"
+		"         <278>  Store a map value into a variable, if no vnum is given the\n"
+		"         <278>  current room is used. Use 'all' as the option to store all\n"
+		"         <278>  values as a table.\n"
+		"\n"
+		"         <178>#map get roomexits <variable>\n"
+		"         <278>  Store all room exits into variable.\n"
+		"\n"
+		"         <178>#map global <room vnum>\n"
+		"         <278>  Set the vnum of a room that contains global\n"
+		"         <278>  exits, for example an exit named 'recall' that leads to the\n"
+		"         <278>  recall location. The room can contain multiple exits, in case\n"
+		"         <278>  there are multiple commands that are similar to recall.\n"
+		"\n"
+		"         <178>#map goto <room vnum> [dig]\n"
+		"         <278>  Takes you to the given room vnum, with the\n"
+		"         <278>  dig argument a new room will be created if none exists.\n"
+		"\n"
+		"         <178>#map goto <name> <exits> <desc> <area> <note> <terrain>\n"
+		"         <278>  Takes you to the given room name, if you provide exits those\n"
+		"         <278>  must match.\n"
+		"\n"
+		"         <178>#map info\n"
+		"         <278>  Gives information about the map and room you are in.\n"
+		"\n"
+		"         <178>#map insert <direction> [roomflag]\n"
+		"         <278>  Insert a room in the given direction. Most useful for inserting\n"
+		"         <278>  void rooms.\n"
+		"\n"
+		"         <178>#map jump <x> <y> <z>\n"
+		"         <278>  Jump to the given coordinate, which is relative\n"
+		"         <278>  to your current room.\n"
+		"\n"
+		"         <178>#map landmark <name> <vnum> [description] [size]\n"
+		"         <278>  Creates an alias to target the provided room vnum. The\n"
+		"         <278>  description is optional and should be brief. The size\n"
+		"         <278>  determines from how many rooms away the landmark can be\n"
+		"         <278>  seen.\n"
+		"\n"
+		"         <178>#map leave\n"
+		"         <278>  Makes you leave the map. Useful when entering a maze. You\n"
+		"         <278>  can return to your last known room using #map return.\n"
+		"\n"
+		"         <178>#map legend <legend> [symbols|reset]\n"
+		"         <178>#map legend <legend> <index> [symbol]\n"
+		"         <278>  There are several legends and sub-legends available for\n"
+		"         <278>  drawing maps to suit personal preference and character sets.\n"
+		"         <278>  Use #map legend all to see the legend as currently defined.\n"
+		"         <278>  Use #map legend <legend> <reset> to set the default legend.\n"
+		"         <278>  Use #map legend <legend> <character list> to create a custom\n"
+		"         <278>  legend. Custom legends are automatically saved and loaded by\n"
+		"         <278>  using #map read and #map write.\n"
+		"\n"
+		"         <178>#map link <direction> <room name> [both]\n"
+		"         <278>  Links two rooms. If the both\n"
+		"         <278>  argument and a valid direction is given the link is two ways.\n"
+		"\n"
+		"         <178>#map list <name> <exits> <desc> <area> <note> <terrain>\n"
+		"         <278>  Lists all matching rooms and their distance. The following\n"
+		"         <278>  search keywords are supported.\n"
+		"\n"
+		"         <278>  {roomarea}    <arg> will list rooms with matching area name.\n"
+		"         <278>  {roomdesc}    <arg> will list rooms with matching room desc.\n"
+		"         <278>  {roomexits}   <arg> will list rooms with identical room exits.\n"
+		"         <278>  {roomflag}    <arg> will list rooms with matching room flags.\n"
+		"         <278>  {roomid}      <arg> will list rooms with identical id name.\n"
+		"         <278>  {roomname}    <arg> will list rooms with matching room name.\n"
+		"         <278>  {roomnote}    <arg> will list rooms with matching room note.\n"
+		"         <278>  {roomterrain} <arg> will list rooms with matching room terrain.\n"
+		"         <278>  {variable}    <arg> will save the output to given variable.\n"
+		"\n"
+		"         <178>#map map <rows> <cols> <append|overwrite|list|variable> <name>\n"
+		"         <278>  Display a drawing of the map of the given height and width.\n"
+		"         <278>  All arguments are optional. If {rows} or {cols} are set to {}\n"
+		"         <278>  or {0} they will use the scrolling window size as the default.\n"
+		"         <278>  If {rows} or {cols} are a negative number this number is\n"
+		"         <278>  subtracted from the scrolling window size.\n"
+		"\n"
+		"         <178>#map map <rows> <cols> draw <square>\n"
+		"         <278>  Display a drawing of the map of the given height and width.\n"
+		"         <278>  The square argument exists of 4 numbers formulating the top\n"
+		"         <278>  left corner and bottom right corner of a square.\n"
+		"\n"
+		"         <278>  If you use {append|overwrite} the map is written to the specified\n"
+		"         <278>  file name which must be given as the 4th argument.\n"
+		"         <278>  If you use {list|variable} the map is saved to the specified\n"
+		"         <278>  variable name.\n"
+		"\n"
+		"         <178>#map move <direction>\n"
+		"         <278>  This does the same as an actual movement\n"
+		"         <278>  command, updating your location on the map and creating new\n"
+		"         <278>  rooms. Useful when you are following someone and want the map\n"
+		"         <278>  to follow. You will need to create actions using '#map move',\n"
+		"         <278>  for this to work.\n"
+		"\n"
+		"         <178>#map offset <row> <col> <row> <col>\n"
+		"         <278>  Define the offset of the vtmap as a square. Without an argument\n"
+		"         <278>  it defaults to the entire top split region.\n"
+		"\n"
+		"         <178>#map read <filename>\n"
+		"         <278>  Will load the given map file.\n"
+		"\n"
+		"         <178>#map resize <size>\n"
+		"         <278>  Resize the map, setting the maximum number of rooms.\n"
+		"\n"
+		"         <178>#map return\n"
+		"         <278>  Returns you to your last known room after leaving the map\n"
+		"         <278>  or loading a map.\n"
+		"\n"
+		"         <178>#map roomflag <flags> <get|on|off>\n"
+		"         <278>\n"
+		"         <178>#map roomflag avoid\n"
+		"         <278>  When set, '#map find' will avoid a route leading\n"
+		"         <278>  through that room. Useful for locked doors, etc.\n"
+		"         <178>#map roomflag block\n"
+		"         <278>  When set the automapper will prevent movement into or through\n"
+		"         <278>  the room. Useful for death traps.\n"
+		"         <178>#map roomflag hide\n"
+		"         <278>  When set, '#map' will not display the map beyond\n"
+		"         <278>  this room. When mapping overlapping areas or areas that aren't\n"
+		"         <278>  build consistently you need this flag as well to stop\n"
+		"         <278>  auto-linking, unless you use void rooms.\n"
+		"         <178>#map roomflag invis\n"
+		"         <278>  When set the room will be colored with the INVIS color.\n"
+		"         <178>#map roomflag leave\n"
+		"         <278>  When entering a room with this flag, you will\n"
+		"         <278>  automatically leave the map. Useful when set at the entrance\n"
+		"         <278>  of an unmappable maze.\n"
+		"         <178>#map roomflag noglobal\n"
+		"         <278>  This marks a room as not allowing global\n"
+		"         <278>  transportation, like norecall rooms that block recall.\n"
+		"         <178>#map roomflag void\n"
+		"         <278>  When set the room becomes a spacing room that can\n"
+		"         <278>  be used to connect otherwise overlapping areas. A void room\n"
+		"         <278>  should only have two exits. When entering a void room you are\n"
+		"         <278>  moved to the connecting room until you enter a non void room.\n"
+		"         <178>#map roomflag static\n"
+		"         <278>  When set the room will no longer be autolinked\n"
+		"         <278>  when walking around. Useful for mapping mazes.\n"
+		"\n"
+		"         <178>#map run <room name> [delay]\n"
+		"         <278>  Calculates the shortest path to the destination and walks you\n"
+		"         <278>  there. The delay is optional and requires using braces. Besides\n"
+		"         <278>  the room name a list of exits can be provided for more precise\n"
+		"         <278>  matching.\n"
+		"\n"
+		"         <178>#map set <option> <value> [vnum]\n"
+		"         <278>  Set a map value for your current room, or given room if a room\n"
+		"         <278>  vnum is provided.\n"
+		"\n"
+		"         <178>#map sync <filename>\n"
+		"         <278>  Similar to #map read except the current map won't be unloaded\n"
+		"         <278>  or overwritten.\n"
+		"\n"
+		"         <178>#map terrain <name> <symbol> [flag]\n"
+		"         <278>  Set the terrain symbol and flag.\n"
+		"\n"
+		"         <178>#map terrain <name> <symbol> [DENSE|SPARSE|SCANT]\n"
+		"         <278>  Determine symbol density, omit for the default.\n"
+		"\n"
+		"         <178>#map terrain <name> <symbol> [NARROW|WIDE|VAST]\n"
+		"         <278>  Determine symbol spread range, omit for the default.\n"
+		"\n"
+		"         <178>#map terrain <name> <symbol> [FADEIN|FADEOUT]\n"
+		"         <278>  Determine symbol spread density, omit for the default.\n"
+		"\n"
+		"         <178>#map travel <direction> <delay>\n"
+		"         <278>  Follows the direction until a dead end or an intersection is\n"
+		"         <278>  found. Use braces around the direction if you use the delay,\n"
+		"         <278>  which will add the given delay between movements.\n"
+		"         <278>  Use #path stop to stop a delayed run.\n"
+		"\n"
+		"         <178>#map undo\n"
+		"         <278>  Will undo your last move. If this created a room or a link\n"
+		"         <278>  they will be deleted, otherwise you'll simply move back a\n"
+		"         <278>  room. Useful if you walked into a non existant direction.\n"
+		"\n"
+		"         <178>#map uninsert <direction>\n"
+		"         <278>  Exact opposite of the insert command.\n"
+		"\n"
+		"         <178>#map unlandmark <name>\n"
+		"         <278>  Removes a landmark.\n"
+		"\n"
+		"         <178>#map unlink <direction> [both]\n"
+		"         <278>  Will remove the exit, this isn't two way so you can have the\n"
+		"         <278>  properly display no exit rooms and mazes.\n"
+		"         <278>  If you use the both argument the exit is removed two-ways.\n"
+		"\n"
+		"         <178>#map unterrain <name>\n"
+		"         <278>  Removes a terrain.\n"
+		"\n"
+		"         <178>#map update\n"
+		"         <278>  Sets the vtmap to update within the next 0.1 seconds.\n"
+		"\n"
+		"         <178>#map vnum <low> [high]\n"
+		"         <278>  Change the room vnum to the given number, if a range is\n"
+		"         <278>  provided the first available room in that range is selected.\n"
+		"\n"
+		"         <178>#map write <filename> [force]\n"
+		"         <278>  Will save the map, if you want to save a map to a .tin file\n"
+		"         <278>  you must provide the {force} argument.\n",
+
+		"path pathdir"
+	},
+	{
+		"MATH",
+
+		"<178>Command<278>: #math <178>{<278>variable<178>} {<278>expression<178>}<278>\n"
+		"\n"
+		"         Performs math operations and stores the result in a variable.  The math\n"
+		"         follows a C-like precedence, as follows, with the top of the list\n"
+		"         having the highest priority.\n"
+		"\n"
+		"         Operators       Priority     Function\n"
+		"         ------------------------------------------------\n"
+		"         !               0            logical not\n"
+		"         ~               0            bitwise not\n"
+		"         *               1            integer multiply\n"
+		"         **              1            integer power\n"
+		"         /               1            integer divide\n"
+		"         //              1            integer sqrt // 2 or cbrt // 3\n"
+		"         %               1            integer modulo\n"
+		"         d               1            integer random dice roll\n"
+		"         +               2            integer addition\n"
+		"         -               2            integer subtraction\n"
+		"         <<              3            bitwise shift\n"
+		"         >>              3            bitwise shift\n"
+		"         ..              3            bitwise ellipsis\n"
+		"         >               4            logical greater than\n"
+		"         >=              4            logical greater than or equal\n"
+		"         <               4            logical less than\n"
+		"         <=              4            logical less than or equal\n"
+		"         ==              5            logical equal (can use regex)\n"
+		"         ===             5            logical equal (never regex)\n"
+		"         !=              5            logical not equal (can use regex)\n"
+		"         !==             5            logical not equal (never regex)\n"
+		"          &              6            bitwise and\n"
+		"          ^              7            bitwise xor\n"
+		"          |              8            bitwise or\n"
+		"         &&              9            logical and\n"
+		"         ^^             10            logical xor\n"
+		"         ||             11            logical or\n"
+		"\n"
+		"         True is any non-zero number, and False is zero.  Parentheses () have\n"
+		"         highest precedence, so inside the () is always evaluated first.\n"
+		"         Strings must be enclosed in { } and use regex with == and !=,\n"
+		"         in the case of <= and >= the alphabetic order is compared.\n"
+		"\n"
+		"         The #if and #switch commands use #math. Several commands accepting\n"
+		"         integer input allow math operations as well.\n"
+		"\n"
+		"         Floating point precision is added by using the decimal . operator.\n"
+		"         Time in seconds is calculated using [day]:[hour]:<minute>:<second>.\n"
+		"\n"
+		"<178>Example<278>: #math {heals} {$mana / 40}\n"
+		"         Assuming there is a variable $mana, divides its value by 40 and stores\n"
+		"         the result in $heals.\n"
+		"\n"
+		"<178>Example<278>: #action {^You receive %0 experience} {updatexp %0}\n"
+		"         #alias updatexp {#math {xpneed} {$xpneed - %0}\n"
+		"         Let's say you have a variable which stores xp needed for your next\n"
+		"         level.  The above will modify that variable after every kill, showing\n"
+		"         the amount still needed.\n"
+		"\n"
+		"<178>Example<278>: #action {%0 tells %1}\n"
+		"           {#if {{%0} == {Bubba} && $afk} {reply I'm away, my friend.}}\n"
+		"         When you are away from keyboard, it will only reply to your friend.\n",
+
+		"cat format function local mathematics replace script variable"
+	},
+
+	{
+		"MATHEMATICS",
+
+		"<178>Number operations\n"
+		"<278>"
+		"         Operators       Priority     Function\n"
+		"         ------------------------------------------------\n"
+		"         !               0            logical not\n"
+		"         ~               0            bitwise not\n"
+		"         *               1            integer multiply\n"
+		"         **              1            integer power\n"
+		"         /               1            integer divide\n"
+		"         //              1            integer sqrt // 2 or cbrt // 3\n"
+		"         %               1            integer modulo\n"
+		"         d               1            integer random dice roll\n"
+		"         +               2            integer addition\n"
+		"         -               2            integer subtraction\n"
+		"         <<              3            bitwise shift\n"
+		"         >>              3            bitwise shift\n"
+		"         >               4            logical greater than\n"
+		"         >=              4            logical greater than or equal\n"
+		"         <               4            logical less than\n"
+		"         <=              4            logical less than or equal\n"
+		"         ==              5            logical equal\n"
+		"         !=              5            logical not equal\n"
+		"          &              6            bitwise and\n"
+		"          ^              7            bitwise xor\n"
+		"          |              8            bitwise or\n"
+		"         &&              9            logical and\n"
+		"         ^^             10            logical xor\n"
+		"         ||             11            logical or\n"
+		"\n"
+		"Operator priority can be ignored by using parentheses, for example (1 + 1) * 2\n"
+		"equals 4, while 1 + 1 * 2 equals 3.\n"
+		"\n"
+		"<178>String operations<278>\n"
+		"<278>"
+		"         Operators       Priority     Function\n"
+		"         ------------------------------------------------\n"
+		"         >               4            alphabetical greater than\n"
+		"         >=              4            alphabetical greater than or equal\n"
+		"         <               4            alphabetical less than\n"
+		"         <=              4            alphabetical less than or equal\n"
+		"         ==              5            alphabetical equal (can use regex)\n"
+		"         !=              5            alphabetical not equal (can use regex)\n"
+		"\n"
+		"Strings must be encased in double quotes or braces. The > >= < <= operators\n"
+		"perform basic string comparisons. The == != operators perform regular\n"
+		"expressions, with the argument on the left being the string, and the argument\n"
+		"on the right being the regex. For example {bla} == {%*a} would evaluate as 1.\n",
+
+		"math"
+	},
+
+	{
+		"MESSAGE",
+
+		"<178>Command<278>: #message <178>{<278>listname<178>} {<278>on<178>|<278>off<178>}<278>\n"
+		"\n"
+		"         This will show the message status of all your lists if typed without an\n"
+		"         argument. If you set for example VARIABLES to OFF you will no longer be\n"
+		"         spammed when correctly using the #VARIABLE and #UNVARIABLE commands.\n",
+
+		"class debug ignore info kill"
+	},
+
+	{
+		"METRIC SYSTEM",
+
+		"  Name  Symbol                              Factor\n"
+		"--------------------------------------------------\n"
+//		" Yotta       Y   1 000 000 000 000 000 000 000 000\n"
+//		" Zetta       Z       1 000 000 000 000 000 000 000\n"
+//		"   Exa       E           1 000 000 000 000 000 000\n"
+//		"  Peta       P               1 000 000 000 000 000\n"
+//		"  Tera       T                   1 000 000 000 000\n"
+//		"  Giga       G                       1 000 000 000\n"
+		"  Mega       M                           1 000 000\n"
+		"  Kilo       K                               1 000\n"
+		"\n"
+		" milli       m                               0.001\n"
+		" micro       u                           0.000 001\n",
+//		"  nano       n                       0.000 000 001\n"
+//		"  pico       p                   0.000 000 000 001\n"
+//		" femto       f               0.000 000 000 000 001\n"
+//		"  atto       a           0.000 000 000 000 000 001\n"
+//		" zepto       z       0.000 000 000 000 000 000 001\n"
+//		" yocto       y   0.000 000 000 000 000 000 000 001\n",
+		
+		"echo format math"
+	},
+	
+	{
+		"MSDP",
+
+		"<278>\n"
+		"         MSDP is part of the #port functionality. See #help event for\n"
+		"         additional documentation as all MSDP events are available as\n"
+		"         regular events.\n"
+		"\n"
+		"         Available MSDP events can be queried using the MSDP protocol\n"
+		"         as described in the specification.\n"
+		"<178>\n"
+		"         https://tintin.sourceforge.io/protocols/msdp\n",
+
+		"event port"
+	},
+	{
+		"NOP",
+
+		"<178>Command<278>: #nop <178>{<278>whatever<178>}<278>\n"
+		"\n"
+		"         Short for 'no operation', and is ignored by the client.  It is useful\n"
+		"         for commenting in your coms file, any text after the nop and before a\n"
+		"         semicolon or end of line is ignored. You shouldn't put braces { } in it\n"
+		"         though, unless you close them properly.\n"
+		"\n"
+		"<178>Comment<278>: By using braces you can comment out multiple lines of code in a script\n"
+		"         file.\n"
+		"\n"
+		"         For commenting out an entire trigger and especially large sections of\n"
+		"         triggers you would want to use /* text */\n"
+		"\n"
+		"<178>Example<278>: #nop This is the start of my script file.\n",
+
+		"read"
+	},
+	{
+		"PARSE",
+
+		"<178>Command<278>: #parse <178>{<278>string<178>} {<278>variable<178>} {<278>commands<178>}<278>\n"
+		"\n"
+		"         Like the loop statement, parse will loop from start to finish through\n"
+		"         the given string.  The value of the current character is stored in the\n"
+		"         provided variable.\n"
+		"\n"
+		"<178>Example<278>: #parse {hello world} {char} {#showme $char}\n",
+
+		"break continue foreach list loop repeat return while"
+	},
+	{
+		"PATH",
+
+		"<178>Command<278>: #path <178>{<278>option<178>} {<278>argument<178>}<278>\n"
+		"\n"
+		"         create   Will clear the path and start path mapping.\n"
+		"         delete   Will delete the last move of the path.\n"
+		"         describe Describe the path and current position.\n"
+		"         destroy  Will clear the path and stop path mapping.\n"
+		"         goto     Go the the start, end, or given position index.\n"
+		"         insert   Add the given argument to the path.\n"
+		"         load     Load the given variable as the new path.\n"
+		"         map      Display the map and the current position.\n"
+		"         move     Move the position forward or backward. If a number is given\n"
+		"                  the position is changed by the given number of steps.\n"
+		"         run      Execute the current path, with an optional floating point\n"
+		"                  delay in seconds as the second argument.\n"
+		"         save     Save the path to a variable. You must specify whether you\n"
+		"                  want to save the path 'forward' or 'backward'. If you use\n"
+		"                  the 'length' or 'position' keywords the current length or\n"
+		"                  position is saved.\n"
+		"         swap     Switch the forward and backward path.\n"
+		"         unzip    Load the given speedwalk as the new path.\n"
+		"         walk     Take one step forward or backward.\n"
+		"         zip      Turn the path into a speedwalk.\n"
+		"\n"
+		"<178>Example<278>: #path ins {unlock n;open n} {unlock s;open s}\n",
+
+		"map pathdir"
+	},
+	{
+		"PATHDIR",
+
+		"<178>Command<278>: #pathdir <178>{<278>dir<178>} {<278>reversed dir<178>} {<278>coord<178>}<278>\n"
+		"\n"
+		"         By default tintin sets the most commonly used movement commands\n"
+		"         meaning you generally don't really have to bother with pathdirs.\n"
+		"         Pathdirs are used by the #path and #map commands.\n"
+		"\n"
+		"         The first argument is a direction, the second argument is the reversed\n"
+		"         direction.  The reverse direction of north is south, etc.\n"
+		"\n"
+		"         The third argument is a spatial coordinate which is a power of two.\n"
+		"         'n' is 1, 'e' is 2, 's' is 4, 'w' is '8', 'u' is 16, 'd' is 32. The\n"
+		"         exception is for compound directions, whose value should be the sum\n"
+		"         of the values of each cardinal direction it is composed of. For\n"
+		"         example, 'nw' is the sum of 'n' and 'w' which is 1 + 8, so 'nw'\n"
+		"         needs to be given the value of 9. This value is required for the\n"
+		"         #map functionality to work properly.\n"
+		"\n"
+		"<178>Example<278>: #pathdir {ue} {dw} {18}\n"
+		"         #pathdir {dw} {ue} {40}\n"
+		"\n"
+		"<178>Comment<278>: You can remove a pathdir with the #unpathdir command.\n",
+		
+		"map path"
+	},
+	{
+		"PCRE",
+
+		"<278>\n"
+		"         A regular expression, regex or regexp is a sequence of characters that\n"
+		"         defines a search pattern. Since the 1980s, different syntaxes for\n"
+		"         writing regular expressions exist, the two most widely used ones being\n"
+		"         the POSIX syntax and the similar but more advanced Perl standard.\n"
+		"         TinTin++ supports the Perl standard known as PCRE (Perl Compatible\n"
+		"         Regular Expressions).\n"
+		"\n"
+		"         Regular expressions are an integral part of TinTin++, but keep in mind\n"
+		"         that tintin doesn't allow you to use regular expressions directly,\n"
+		"         instead it uses a simpler intermediate syntax that still allows more\n"
+		"         complex expressions when needed.\n"
+		"\n"
+		"         Commands that utilize regular expressions are: action, alias, elseif,\n"
+		"         gag, grep, highlight, if, kill, local, math, prompt, regexp, replace,\n"
+		"         substitute, switch, variable and while. Several other commands use\n"
+		"         regular expressions in minor ways. Fortunately the basics are very\n"
+		"         easy to learn.\n"
+		"\n"
+		"<178>         TinTin++ Regular Expression<278>\n"
+		"\n"
+		"         The following support is available for regular expressions.\n"
+		"\n"
+		"       ^ match start of line.\n"
+		"       $ match of end of line.\n"
+		"       \\ escape one character.\n"
+		"\n"
+		"  %1-%99 match of any text, stored in the corresponding index.\n"
+		"      %0 should be avoided in the regex, contains all matched text.\n"
+		"     { } embed a perl compatible regular expression, matches are stored.\n"
+		"   %!{ } embed a perc compatible regular expression, matches are not stored.\n"
+		"\n"
+		"[ ] . + | ( ) ? * are treated as normal text unless used within braces. Keep in\n"
+		"mind that { } is replaced with ( ) automatically unless %!{ } is used.\n"
+		"<178>\n"
+		"         TinTin++  Description                            POSIX<278>\n"
+		"      %d Match zero to any number of digits               ([0-9]*?)\n"
+		"      %D Match zero to any number of non-digits           ([^0-9]*?)\n"
+		"      %i Matches become case insensitive                  (?i)\n"
+		"      %I Matches become case sensitive (default)          (?-i)\n"
+		"      %s Match zero to any number of spaces               ([\\r\\n\\t ]*?)\n"
+		"      %w Match zero to any number of word characters      ([A-Za-z0-9_]*?)\n"
+		"      %W Match zero to any number of non-word characters  ([^A-Za-z0-9_]*?)\n"
+		"      %? Match zero or one character                      (.\?\?)\n"
+		"      %. Match one character                              (.)\n"
+		"      %+ Match one to any number of characters            (.+?)\n"
+		"      %* Match zero to any number of characters           (.*?)\n"
+		"<178>\n"
+		"         Variables<278>\n"
+		"\n"
+		"         If you use %1 in an action to perform a match the matched string is\n"
+		"         stored in the %1 variable which can be used in the action body.\n"
+		"\n"
+		"Example: %1 says 'Tickle me'} {tickle %1}\n"
+		"\n"
+		"         If you use %2 the match is stored in %2, etc. If you use an unnumbered\n"
+		"         match like %* or %S the match is stored at the last used index\n"
+		"         incremented by one.\n"
+		"\n"
+		"Example: %3 says '%*'} {#if {\"%4\" == \"Tickle me\"} {tickle %3}}\n"
+		"\n"
+		"         The maximum variable index is 99. If you begin an action with %* the\n"
+		"         match is stored in %1. You should never use %0 in the trigger part of\n"
+		"         an action, when used in the body of an action %0 contains all the parts\n"
+		"         of the string that were matched.\n"
+		"\n"
+		"         To prevent a match from being stored use %!*, %!w, etc.\n"
+		"<178>\n"
+		"         Perl Compatible Regular Expressions<278>\n"
+		"\n"
+		"         You can embed a PCRE (Perl Compatible Regular Expression) using curley\n"
+		"         braces { }, these braces are replaced with parentheses ( ) unless you\n"
+		"         use %!{ }.\n"
+		"<178>\n"
+		"         Or<278>\n"
+		"\n"
+		"         You can separate alternatives within a PCRE using the | character.\n"
+		"\n"
+		"Example: #act {%* raises {his|her|its} eyebrows.} {say 42..}\n"
+		"<178>\n"
+		"         Brackets<278>\n"
+		"\n"
+		"         You can group alternatives and ranges within a PCRE using brackets.\n"
+		"\n"
+		"Example: #act {%* says 'Who is number {[1-9]}} {say $number[%2] is number %2}\n"
+		"\n"
+		"         The example only triggers if someone provides a number between 1 and\n"
+		"         9. Any other character will cause the action to not trigger.\n"
+		"\n"
+		"Example: #act {%* says 'Set password to {[^0-9]*}$} {say The password must\n"
+		"           contain at least one number, not for security reasons, but just to\n"
+		"           annoy you.} {4}\n"
+		"\n"
+		"         When the ^ character is used within brackets it creates an inverse\n"
+		"         search, [^0-9] matches every character except for a number between 0\n"
+		"         and 9.\n"
+		"<178>\n"
+		"         Quantification<278>\n"
+		"\n"
+		"         A quantifier placed after a match specifies how often the match is\n"
+		"         allowed to occur.\n"
+		"\n"
+		"       ? repeat zero or one time.\n"
+		"       * repeat zero or multiple times.\n"
+		"       + repeat once or multiple times.\n"
+		"     {n} repeat exactly n times, n must be a number.\n"
+		"    {n,} repeat at least n times, n must be a number.\n"
+		"   {n,o} repeat between n and o times, n and o must be a number.\n"
+		"\n"
+		"Example: #act {%* says 'Who is number {[1-9][0-9]{0,2}} {Say $number[%2] is\n"
+		"           number %2}\n"
+		"\n"
+		"         The example only triggers if someone provides a number between 1 and\n"
+		"         999.\n"
+		"\n"
+		"         <178>Parantheses<278>\n"
+		"\n"
+		"         TinTin Regular Expressions automatically add parenthesis, for example\n"
+		"         %* translates to (.*?) in PCRE unless the %* is found at the start or\n"
+		"         end of the line, in which cases it translates to (.*). Paranthesis in\n"
+		"         PCRE causes a change in execution priority similar to mathematical\n"
+		"         expressions, but parentheses also causes the match to be stored to a\n"
+		"         variable.\n"
+		"\n"
+		"         When nesting multiple sets of parentheses each nest is assigned its\n"
+		"         numerical variable in order of appearance.\n"
+		"\n"
+		"Example: #act {%* chats '{Mu(ha)+}'} {chat %2ha!}\n"
+		"\n"
+		"         If someone chats Muha you will chat Muhaha! If someone chats Muhaha\n"
+		"         you will chat Muhahaha!\n"
+		"\n"
+		"         <178>Lazy vs Greedy<278>\n"
+		"\n"
+		"         By default regex matches are greedy, meaning {.*} will capture as much\n"
+		"         text as possible.\n"
+		"\n"
+		"Example: #regex {bli bla blo} {^{.*} {.*}$} {#showme Arg1=(&1) Arg2=(&2)}\n"
+		"\n"
+		"         This will display: Arg1=(bli bla) Arg2=(blo)\n"
+		"\n"
+		"         By appending a ? behind a regex it becomes lazy, meaning {.*?} will\n"
+		"         capture as little text as possible.\n"
+		"\n"
+		"Example: #regex {bli bla blo} {^{.*?} {.*?}$} {#showme Arg1=(&1) Arg2=(&2)}\n"
+		"\n"
+		"         This will display: Arg1=(bli) Arg2=(bla blo).\n"
+		"\n"
+		"         <178>Escape Codes<278>\n"
+		"\n"
+		"         PCRE support the following escape codes.\n"
+		"<178>\n"
+		"    PCRE Description                                    POSIX<278>\n"
+		"      \\A Match start of string                          ^\n"
+		"      \\b Match word boundaries                          (^|\\r|\\n|\\t| |$)\n"
+		"      \\B Match non-word boundaries                      [^\\r\\n\\t ]\n"
+		"      \\c Insert control character                       \\c\n"
+		"      \\d Match digits                                   [0-9]\n"
+		"      \\D Match non-digits                               [^0-9]\n"
+		"      \\e Insert escape character                        \\e\n"
+		"      \\f Insert form feed character                     \\f\n"
+		"      \\n Insert line feed character                     \\n\n"
+		"      \\r Insert carriage return character               \\r\n"
+		"      \\s Match spaces                                   [\\r\\n\\t ]\n"
+		"      \\S Match non-spaces                               [^\\r\\n\\t ]\n"
+		"      \\t Insert tab character                           \\t\n"
+		"      \\w Match letters, numbers, and underscores        [A-Za-z0-9_]\n"
+		"      \\W Match non-letters, numbers, and underscores    [^A-Za-z0-9_]\n"
+		"      \\x Insert hex character                           \\x\n"
+		"      \\Z Match end of string                            $\n"
+		"\n"
+		"         \\s matches one space, \\s+ matches one or multiple spaces.\n"
+		"\n"
+		"         <178>Color triggers<278>\n"
+		"\n"
+		"         To make matching easier text triggers (Actions, Gags, Highlights,\n"
+		"         Prompts, and Substitutes) have their color codes stripped. If you\n"
+		"         want to create a color trigger you must start the triggers with a ~\n"
+		"         (tilda). To make escape codes visible use #config {convert meta} on.\n"
+		"\n"
+		"Example: #action {~\\e[1;37m%1} {#var roomname %1}\n"
+		"\n"
+		"         If the room name is the only line on the server in bright white\n"
+		"         white color trigger will save the roomname.\n"
+		"\n"
+		"\n"
+		"         This covers the basics. PCRE has more options, most of which are\n"
+		"         somewhat obscure, so you'll have to read a PCRE manual for additional\n"
+		"         information.\n",
+
+		"map path"
+	},
+
+	{
+		"PORT",
+
+		"<178>Command<278>: #port <178>{<278>option<178>} {<278>argument<178>}<278>\n"
+		"\n"
+		"         <178>#port {init} {name} {port} {file}\n"
+		"         <278>  Initilize a port session.\n"
+		"\n"
+		"         <178>#port {call} {address} {port}\n"
+		"         <278>  Connect to a remote socket.\n"
+		"\n"
+		"         <178>#port {color} {color names}\n"
+		"         <278>  Set the default color of port messages.\n"
+		"\n"
+		"         <178>#port {dnd}\n"
+		"         <278>  Do Not Disturb. Decline new connections\n"
+		"\n"
+		"         <178>#port {group} {name} {group}\n"
+		"         <278>  Assign a socket group.\n"
+		"\n"
+		"         <178>#port {ignore} {name}\n"
+		"         <278>  Ignore a socket\n"
+		"\n"
+		"         <178>#port {info}\n"
+		"         <278>  Display information about the port session.\n"
+		"\n"
+		"         <178>#port {name} {name}\n"
+		"         <278>  Change socket name.\n"
+		"\n"
+		"         <178>#port {prefix} {text}\n"
+		"         <278>  Set prefix before each message.\n"
+		"\n"
+		"         <178>#port {send} {name|all} {text}\n"
+		"         <278>  Send data to socket\n"
+		"\n"
+		"         <178>#port {uninitialize}\n"
+		"         <278>  Uninitialize the port session.\n"
+		"\n"
+		"         <178>#port {who}\n"
+		"         <278>  Show all connections\n"
+		"\n"
+		"         <178>#port {zap} {name}\n"
+		"         <278>  Close a connection\n"
+		"\n"
+		"         The port command is very similar to chat except that it creates a\n"
+		"         new session dedicated to receiving socket connections at the given\n"
+		"         port number without built-in support for a communication protocol.\n"
+		"\n"
+		"         You can init with 0 as the port number to create a dummy session.\n",
+
+		"all chat run session sessionname snoop ssl zap"
+	},
+
+	{
+		"PROMPT",
+
+		"<178>Command<278>: #prompt <178>{<278>text<178>} {<278>new text<178>} {<278>row #<178>} <178>{<278>col #<178>}<278>\n"
+		"\n"
+		"         Prompt is a feature for split window mode, which will capture a line\n"
+		"         received from the server and display it on the status bar of your\n"
+		"         split screen terminal. You would define <text> and <new text> the\n"
+		"         same way as with a substitution.\n"
+		"\n"
+		"         The row number is optional and useful if you use a non standard split\n"
+		"         mode. A positive row number draws #row lines from the top while a\n"
+		"         negative number draws #row lines from the bottom. Without an argument\n"
+		"         #prompt will write to the default split line, which is at row -2.\n"
+		"\n"
+		"         The col number is optional and can be used to set the column index.\n"
+		"         A positive col number draws the given number of columns from the left,\n"
+		"         while a negative col number draws from the right. If you leave the\n"
+		"         column argument empty tintin will clear the row before printing at\n"
+		"         the start of the row.\n"
+		"\n"
+		"         The #showme command takes a row and col argument as well so it's also\n"
+		"         possible to place text on your split lines using #showme.\n"
+		"\n"
+		"<178>Comment<278>: See <178>#help split<278> for more information on split mode.\n"
+		"\n"
+		"<178>Comment<278>: See <178>#help substitute<278> for more information on text\n"
+		"         substitutions.\n"
+		"\n"
+		"<178>Comment<278>: You can remove a prompt with the #unprompt command.\n",
+
+		"action gag highlight substitute"
+	},
+	{
+		"READ",
+
+		"<178>Command<278>: #read <178>{<278>filename<178>}<278>\n"
+		"\n"
+		"         Reads a commands file into memory.  The coms file is merged in with\n"
+		"         the currently loaded commands.  Duplicate commands are overwritten.\n"
+		"\n"
+		"         If you uses braces, { and } you can use several lines for 1 commands.\n"
+		"         This however means you must always match every { with a } for the read\n"
+		"         command to work.\n"
+		"\n"
+		"         You can comment out triggers using /* text */\n",
+
+		"log scan textin write"
+	},
+	{
+		"REGEXP",
+
+		"<178>Command<278>: #regexp <178>{<278>string<178>} {<278>expression<178>} {<278>true<178>} {<278>false<178>}<278>\n"
+		"\n"
+		"         Compares the string to the given regular expression.\n"
+		"\n"
+		"         Variables are stored in &1 to &99 with &0 holding the matched substring.\n"
+		"\n"
+		"       ^ force match of start of line.\n"
+		"       $ force match of end of line.\n"
+		"       \\ escape one character.\n"
+		"  %1-%99 lazy match of any text, available at %1-%99.\n"
+		"      %0 should be avoided in triggers, and if left alone lists all matches.\n"
+		"     { } embed a raw regular expression, matches are stored to %1-%99.\n"
+		"   %!{ } embed a raw regular expression, matches are not stored.\n"
+		"         [ ] . + | ( ) ? * are treated as normal text unlessed used within\n"
+		"         braces. Keep in mind that { } is replaced with ( ) automatically\n"
+		"         unless %!{ } is used.\n"
+		"\n"
+		"         Of the following the (lazy) match is available at %1-%99 + 1\n"
+		"\n"
+		"      %a match zero to any number of characters except newlines.\n"
+		"      %A match zero to any number of newlines.\n"
+		"      %d match zero to any number of digits.\n"
+		"      %D match zero to any number of non digits.\n"
+		"      %p match zero to any number of printable characters.\n"
+		"      %P match zero to any number of non printable characters.\n"
+		"      %s match zero to any number of spaces.\n"
+		"      %S match zero to any number of non spaces.\n"
+		"      %u match zero to any number of unicode characters.\n"
+		"      %U match zero to any number of non unicode characters.\n"
+		"      %w match zero to any number of word characters.\n"
+		"      %W match zero to any number of non word characters.\n"
+		"\n"
+		"      If you want to match 1 digit use %+1d, if you want to match between 3\n"
+		"      and 5 spaces use %+3..5s, if you want to match between 0 and 1 word\n"
+		"      characters use %+0..1w\n"
+		"\n"
+		"      %+ match one to any number of characters.\n"
+		"      %? match zero or one character.\n"
+		"      %. match one character.\n"
+		"      %* match zero to any number of characters.\n"
+		"\n"
+		"      %i matching becomes case insensitive.\n"
+		"      %I matching becomes case sensitive (default).\n"
+		"\n"
+		"         The match is automatically stored to a value between %1 and %99\n"
+		"         starting at %1 and incrementing by 1 for every regex. If you use\n"
+		"         %15 as a regular expression, the next unnumbered regular expression\n"
+		"         would be %16. To prevent a match from being stored use %!*, %!w, etc.\n"
+		"\n"
+		"<178>Example<278>: #regexp {bli bla blo} {bli {.*} blo} {#showme &1}\n",
+
+		"case default else elseif if switch"
+	},
+
+	{
+		"REPEAT",
+
+		"<178>Command<278>: #<178>[<078>number<178>] {<278>commands<178>}<278>\n"
+		"\n"
+		"Sometimes you want to repeat the same command multiple times. This is the\n"
+		"easiest way to accomplish that.\n"
+		"\n"
+		"<178>Example<278>: #10 {buy bread}\n",
+		
+		"break continue foreach list loop parse return while"
+	},
+	{
+		"REPLACE",
+
+		"<178>Command<278>: #replace <178>{<278>variable<178>} {<278>oldtext<178>} {<278>newtext<178>}<278>\n"
+		"\n"
+		"         Searches the variable text replacing each occurrence of 'oldtext' with\n"
+		"         'newtext'.\n",
+
+		"cat format function local math script variable"
+	},
+	{
+		"RETURN",
+
+		"<178>Command<278>: #return <178>{<278>text<178>}<278>\n"
+		"\n"
+		"         This command can be used to break out of a command string being\n"
+		"         executed.\n"
+		"\n"
+		"         If used inside a #function you can use #return with an argument to both\n"
+		"         break out of the function and set the result variable.\n",
+
+		"break continue foreach list loop parse repeat while"
+	},
+	{
+		"RUN",
+
+		"<178>Command<278>: #run <178>{<278>name<178>} {<278>shell command<178>} {<278>file<178>}<278>\n"
+		"\n"
+		"         The run command works much like the system command except that it\n"
+		"         runs the command in a pseudo terminal. The run command also creates\n"
+		"         a session that treats the given shell command as a server. This\n"
+		"         allows you to run ssh, as well as any other shell application, with\n"
+		"         full tintin scripting capabilities. If a file name is given the file\n"
+		"         is loaded prior to execution.\n"
+		"\n"
+		"<178>Example<278>: #run {somewhere} {ssh someone@somewhere.com}\n"
+		"<178>Example<278>: #run {something} {tail -f chats.log}\n",
+
+		"all port session sessionname snoop ssl zap"
+	},
+	{
+		"SCAN",
+
+		"<178>Command<278>: #scan <178>{<278>abort<178>|<278>csv<178><178>|<278>tsv<178><178>|<278>txt<178>} {<278>filename<178>}<278>\n"
+		"\n"
+		"         The scan command is a file loading utility.\n"
+		"\n"
+		"         <178>#scan {abort}\n"
+		"         <278>  This command must be called from with a SCAN event and will\n"
+		"         <278>  abort the scan if one is in progress.\n"
+		"\n"
+		"         <178>#scan {csv} <filename>\n"
+		"         <278>  The scan csv command reads in a comma separated value file\n"
+		"           without printing the content to the screen. Instead it triggers one\n"
+		"           of two events.\n"
+		"\n"
+		"           The SCAN CSV HEADER event is triggered on the first line of the csv\n"
+		"           file. The SCAN CSV LINE event is triggered on the second and each\n"
+		"           subsequent line of the csv file. The %0 argument contains the entire\n"
+		"           line, with  %1 containing the first value, %2 the second value, etc,\n"
+		"           all the way up to %99.\n"
+		"\n"
+		"           Values containing spaces must be surrounded with quotes, keep in mind\n"
+		"           newlines within quotes are not supported. Use two quotes to print one\n"
+		"           literal quote character.\n"
+		"\n"
+		"         <178>#scan {tsv} <filename>\n"
+		"\n"
+		"         <278>  The scan tsv <filename> command reads in a tab separated value file\n"
+		"           without printing the content to the screen. Instead it triggers the\n"
+		"           SCAN TSV HEADER event for the first line and SCAN TSV LINE for all\n"
+		"           subsequent lines.\n"
+		"\n"
+		"         <178>#scan {file} <filename> {commands}\n"
+		"\n"
+		"         <278>  The scan file command reads the given files and executes the\n"
+		"            commands argument. &0 contains the raw content of the file and\n"
+		"            &1 contains the plain content. &2 contains the raw byte size of the\n"
+		"            file and &3 the plain byte size. &5 contains the line count.\n"
+		"\n"
+		"         <178>#scan {txt} <filename>\n"
+		"\n"
+		"         <278>  The scan txt <filename> command reads in a file and sends its content\n"
+		"           to the screen as if it was send by a server. After using scan you can\n"
+		"           use page-up and down to view the file.\n"
+		"\n"
+		"           This command is useful to convert ansi color files to html or viewing\n"
+		"           raw log files.\n"
+		"\n"
+		"           Actions, highlights, and substitutions will trigger as normal, and it\n"
+		"           is possible to create an action to execute #scan abort to prematurely\n"
+		"           stop the scan.\n",
+
+		"read textin"
+	},
+
+	{
+		"SCREEN",
+
+		"<178>Command<278>: #screen <178>{<278>option<178>}<178> {<278>argument<178>}\n"
+		"\n"
+		"         <278>The screen command offers a variety of screen manipulation\n"
+		"         commands and utilities.\n"
+		"\n"
+		"         <178>#screen blur\n"
+		"         <278>  Move the terminal to the back of the stack.\n"
+		"\n"
+		"         <178>#screen clear [all|scroll region|square] <args>\n"
+		"         <278>  Provide 4 arguments defining the top left and bottom right corner\n"
+		"         <888>  when erasing a square.\n"
+		"\n"
+		"         <178>#screen focus\n"
+		"         <278>  Move the terminal to the front of the stack.\n"
+		"\n"
+		"         <178>#screen fullscreen [on|off]\n"
+		"         <278>  Toggles fullscreen mode when used without an argument.\n"
+		"\n"
+		"         <178>#screen get <rows|cols|height|width> <var>\n"
+		"         <278>  Get the rows/cols size in characters or height/width in pixels.\n"
+		"\n"
+		"         <178>#screen get <top_row|bot_row|top_split|bot_split> <var>\n"
+		"         <278>  Get the top and bot row of the scrolling region or the height\n"
+		"         <888>  of the top and bot split bars.\n"
+		"\n"
+		"         <178>#screen info\n"
+		"         <278>  Debugging information.\n"
+		"\n"
+		"         <178>#screen load <both|label|title>\n"
+		"         <278>  Reload the saved title, label, or both.\n"
+		"\n"
+		"         <178>#screen minimize <on|off>\n"
+		"         <278>  Minimize with on, restore with off.\n"
+		"\n"
+		"         <178>#screen maximize [on|off]\n"
+		"         <278>  Maximize with on, restore with off.\n"
+		"\n"
+		"         <178>#screen move <height> <width>\n"
+		"         <278>  Move the upper left corner of the terminal to pixel coordinate.\n"
+		"\n"
+		"         <178>#screen raise <event>\n"
+		"         <278>  This will raise several screen events with %1 and %2 arguments.\n"
+		"\n"
+		"         <178>#screen refresh\n"
+		"         <278>  Terminal dependant, may do nothing.\n"
+		"\n"
+		"         <178>#screen rescale <height> <width>\n"
+		"         <278>  Resize the screen to the given height and width in pixels.\n"
+		"\n"
+		"         <178>#screen resize <rows> <cols>\n"
+		"         <278>  Resize the screen to the given height and width in characters.\n"
+		"\n"
+		"         <178>#screen save <both|label|title>\n"
+		"         <278>  Save the title, label, or both.\n"
+		"\n"
+		"         <178>#screen set <both|label|title>\n"
+		"         <278>  Set the title, label, or both. Only title works on Windows.\n",
+
+		"bell"
+	},
+
+	{
+		"SCREEN READER",
+
+		"<178>Command<278>: #config <178>{<278>SCREEN READER<178>} {<278>ON|OFF<178>}<278>\n"
+		"\n"
+		"         Screen reader mode is enabled by using #config screen on.  The main\n"
+		"         purpose of the screen reader mode is to report to servers that a\n"
+		"         screen reader is being used by utilizing the MTTS standard.  The MTTS\n"
+		"         specification is available at:\n"
+		"\n"
+		"         http://tintin.sourceforge.net/protocols/mtts\n"
+		"\n"
+		"         With the screen reader mode enabled TinTin++ will try to remove visual\n"
+		"         elements where possible.\n",
+
+		"config"
+	},
+
+	{
+		"SCRIPT",
+
+		"<178>Command<278>: #script <178>{<278>variable<178>}<178> {<278>shell command<178>}<278>\n"
+		"\n"
+		"         The script command works much like the system command except that it\n"
+		"         treats the generated echos as commands if no variable is provided.\n"
+		"\n"
+		"         This is useful for running php, perl, ruby, and python scripts. You\n"
+		"         can run these scrips either from file or from within tintin if the\n"
+		"         scripting language allows this.\n"
+		"\n"
+		"         If you provide a variable the output of the script is stored as a list.\n"
+		"\n"
+		"<178>Example<278>: #script {ruby -e 'print \"#showme hello world\"'}\n"
+		"<178>Example<278>: #script {python -c 'print \"#showme hello world\"'}\n"
+		"<178>Example<278>: #script {php -r 'echo \"#showme hello world\"'}\n"
+		"<178>Example<278>: #script {path} {pwd};#showme The path is $path[1].\n",
+
+		"format function local math replace variable"
+	},
+
+	{
+		"SEND",
+
+		"<178>Command<278>: #send <178>{<278>text<178>}<278>\n"
+		"\n"
+		"         Sends the text directly to the server, useful if you want to start\n"
+		"         with an escape code.\n",
+
+		"textin"
+	},
+	{
+		"SESSION",
+
+		"<178>Command<278>: #session <178>{<278>name<178>} {<278>host<178>} {<278>port<178>} {<278>file<178>}<278>\n"
+		"\n"
+		"         Starts a telnet session with the given name, host, port, and optional\n"
+		"         file name. The name can be anything you want, except the name of an\n"
+		"         already existant session, a number, or the keywords '+', '-' and 'self'.\n"
+		"\n"
+		"         If a file name is given the file is only read if the session succesfully\n"
+		"         connects.\n"
+		"\n"
+		"         Without an argument #session shows the currently defined sessions.\n"
+		"\n"
+		"         If you have more than one session, you can use the following commands:\n"
+		"\n"
+		"         #session {-}        Switch to the previous session.\n"
+		"         #session {+}        Switch to the next session.\n"
+		"         #session {<number>} Switch to the given session. Session 0 is the\n"
+		"                             startup session, +1 the first, +2 the second, and\n"
+		"                             -1 is the last session. Sessions are (currently)\n"
+		"                             sorted in order of creation.\n"
+		"         #gts                Switch to the startup session. The name gts stands\n"
+		"                             for global tintin session.\n"
+		"         #ats                Switch to the active session. The name ats stands\n"
+		"                             for active tintin session.\n"
+		"                             not necessarily the calling session.\n"
+		"         #{name}             Activates to the session with the given name.\n"
+		"         #{name} {command}:  Executes a command with the given session without\n"
+		"                             changing the active session.\n"
+		"         @<name>{text}:      Parse text in the given session, substituting the\n"
+		"                             variables and functions, and print the result in\n"
+		"                             the current active session.\n"
+		"\n"
+		"         The startup session is named 'gts' and can be used for relog scripts. Do\n"
+		"         keep in mind that tickers do not work in the startup session.\n"
+		"\n"
+		"<178>Example<278>: #event {SESSION DISCONNECTED} {#gts #delay 10 #ses %0 tintin.net 4321}\n",
+
+		"all port run sessionname snoop ssl zap"
+	},
+
+	{
+		"SESSIONNAME",
+
+		"<178>Syntax<278>: #[sessionname] <178>{<278>commands<178>}<278>\n"
+		"\n"
+		"You can create multiple sessions with the #session command. By default only one\n"
+		"session is active, meaning commands you input are executed in the active\n"
+		"session. While all sessions receive output, only output sent to the active\n"
+		"session is displayed.\n"
+		"\n"
+		"When you create a session with the #session command you must specify a session\n"
+		"name, the session name, prepended with a hashtag, can be used to activate the\n"
+		"session when used without an argument. If an argument is given it will be\n"
+		"executed by that session as a command, the session will not be activated.\n"
+		"\n"
+		"<178>Example<278>: #ses one tintin.net 23;#ses two tintin.net 23;#one;#two grin\n"
+		"\n"
+		"This will create two sessions, the session that was created last (two in this\n"
+		"case) will be automatically activated upon creation. Using #one, session one is\n"
+		"activated. Using #two grin, the grin social will be executed by session two,\n"
+		"session one will remain the active session.\n",
+		
+		"all port run session snoop ssl zap"
+	},
+
+	{
+		"SHOWME",
+
+		"<178>Command<278>: #showme <178>{<278>string<178>} {<278>row<178>} <178>{<278>col<178>}<278>\n"
+		"\n"
+		"         Display the string to the terminal, do not send to the server.  Useful\n"
+		"         for status, warnings, etc.  The {row} and col number are optional and\n"
+		"         work the same way as the row number of the #prompt trigger.\n"
+		"\n"
+		"         Actions can be triggered by the showme command. If you want to avoid\n"
+		"         this from happening use: #line ignore #showme {<string>}.\n"
+		"\n"
+		"<178>Example<278>: #tick {TICK} {#delay 50 #showme 10 SECONDS TO TICK!!!} {60}\n"
+		"\n"
+		"<178>Comment<278>: The #prompt helpfile contains more information on using the\n"
+		"         option {row} and {col} arguments.\n",
+
+		"buffer draw echo grep"
+	},
+	{
+		"SNOOP",
+
+		"<178>Command<278>: #snoop <178>{<278>session name<178>} <178>{<278>on<178>|<278>off<178>}<278>\n"
+		"\n"
+		"         If there are multiple sessions active, this command allows you to monitor\n"
+		"         what is going on in the sessions that are not currently active.  The\n"
+		"         line of text from other sessions will be prefixed by the session's name.\n"
+		"\n"
+		"         You can toggle off snoop mode by executing #snoop a second time.\n",
+
+		"all port run session sessionname ssl zap"
+	},
+	{
+		"SPEEDWALK",
+
+		"         <278>Speedwalking allows you to enter multiple directions without using\n"
+		"         semicolons. Directions should be prefixed with a number and will be\n"
+		"         executed the given number of times.\n"
+		"\n"
+		"         You can enable speedwalking with #CONFIG {SPEEDWALK} {ON}.\n"
+		"\n"
+		"<178>Example<278>: Without speedwalk, you have to type:\n"
+		"         s;s;w;w;w;w;w;s;s;s;w;w;w;n;n;w\n"
+		"         With speedwalk, you only have to type:\n"
+		"         2s5w3s3w2n1w\n",
+
+		"alias cursor history keypad macro tab"
+	},
+	{
+		"SPLIT",
+
+		"<178>Command<278>: #split <178>{<278>top bar<178>} {<278>bottom bar<178>}\n"
+		"<178>Command<278>: #split <178>{<278><square><178>}\n"
+		"\n"
+		"         This option requires for your terminal to support VT100 emulation.\n"
+		"\n"
+		"         #split allows the creation of a top status bar, a scrolling region, a\n"
+		"         bottom status bar, and an input line.\n"
+		"\n"
+		"         <178>--top status bar--------\n"
+		"\n"
+		"         <278>  scrolling region\n"
+		"\n"
+		"         <178>--bottom status bar----------\n"
+		"         <278>  input line\n"
+		"\n"
+		"         By default the bottom status bar is filled with dashes --- and\n"
+		"         subsequently it is also known as the split line. The scrolling\n"
+		"         region is also known as the main screen and this is where all\n"
+		"         incoming text is displayed by default.\n"
+		"\n"
+		"         If you use #split without an argument it will set the height of the\n"
+		"         top status bar to 0 lines and the bottom status bar to 1 line.\n"
+		"\n"
+		"         If you use #split with one argument it will set the height of the top\n"
+		"         status bar to the given number of lines and the bottom status bar will\n"
+		"         be set to 1 line.\n"
+		"\n"
+		"         If you use two arguments the first argument is the height of the top\n"
+		"         status bar and the second argument the height of the bottom status bar.\n"
+		"\n"
+		"         The third and fourth argument are optional and tintin will interpret\n"
+		"         four arguments as a square argument existing of two coordinates\n"
+		"         defining the upper left corner and bottom right corner of the\n"
+		"         scrolling region.\n"
+		"\n"
+		"<178>Example<278>: #split 0 0\n"
+		"         This will create a split screen with just a scrolling regino and an\n"
+		"         input line. Great for the minimalist.\n"
+		"\n"
+		"<178>Comment<278>: You can display text on the split line(s) with the #prompt and\n"
+		"         #showme {line} {row} commands.\n"
+		"\n"
+		"<178>Comment<278>: You can remove split mode with the #unsplit command.\n",
+
+		"echo prompt showme"
+	},
+	{
+		"SSL",
+
+		"<178>Command<278>: #ssl <178>{<278>name<178>} {<278>host<178>} {<278>port<178>} {<278>file<178>}\n"
+		"\n"
+		"         Starts a secure socket telnet session with the given name, host, port,\n"
+		"         and optional file name.\n",
+
+		"all port run sessionname snoop ssl zap"
+	},
+	{
+		"STATEMENTS",
+
+		"         TinTin++ knows the following statements.\n"
+		"<278>\n"
+		"         #break\n"
+		"         #case {value} {true}\n"
+		"         #continue\n"
+		"         #default {commands}\n"
+		"         #else {commands}\n"
+		"         #elseif {expression} {true}\n"
+		"         #foreach {list} {variable} {commands}\n"
+		"         #if {expression} {true}\n"
+		"         #loop {min} {max} {variable} {commands}\n"
+		"         #parse {string} {variable} {commands}\n"
+		"         #return {value}\n"
+		"         #switch {expression} {commands}\n"
+		"         #while {expression} {commands}\n",
+
+		"commands help info"
+	},
+	{
+		"SUBSTITUTE",
+
+		"<178>Command<278>: #substitute <178>{<278>text<178>} {<278>new text<178>} {<278>priority<178>}<278>\n"
+		"\n"
+		"         Allows you to replace text from the server with the new text.\n"
+		"         This is helpful for complex coloring and making things more readable.\n"
+		"         The %1-%99 variables can be used to capture text and use it as part of\n"
+		"         the new output, and the ^ char is valid to only check the beginning of\n"
+		"         the line for the text specified.\n"
+		"\n"
+		"         If only one argument is given, all active substitutions that match the\n"
+		"         strings are displayed.  The '%*' char is valid in this instance.  See\n"
+		"         '#help regex', for advanced wildcard information.\n"
+		"\n"
+		"         If no argument is given, all subs are displayed.\n"
+		"\n"
+		"<178>Example<278>: #sub {Zoe} {ZOE}\n"
+		"         Any instance of Zoe will be replaced with ZOE.\n"
+		"\n"
+		"<178>Example<278>: #sub {~\\e[0;34m} {\\e[1;34m}\n"
+		"         Replace generic dark blue color codes with bright blue ones.\n"
+		"\n"
+		"<178>Example<278>: #sub {%1massacres%2} {<<888>018>%1<<888>118>MASSACRES<<888>018>%2}\n"
+		"         Replaces all occurrences of 'massacres' with 'MASSACRES' in red.\n"
+		"\n"
+		"<178>Comment<278>: See '#help action', for more information about triggers.\n"
+		"\n"
+		"<178>Comment<278>: See '#help colors', for more information.\n"
+		"\n"
+		"<178>Comment<278>: You can remove a substitution with the #unsubstitute command.\n",
+
+		"action gag highlight prompt"
+	},
+	{
+		"SUSPEND",
+
+		"<178>Command<278>: #cursor suspend\n"
+		"\n"
+		"         Temporarily suspends tintin and returns you to your shell.  To\n"
+		"         return to tintin, type 'fg' at the shell prompt.\n"
+		"\n"
+		"         While suspended your tintin sessions will freeze. To keep a\n"
+		"         suspended session running use the #detach command.\n",
+
+		"end"
+	},
+	{
+		"SWITCH",
+
+		"<178>Command<278>: #switch <178>{<278>conditional<178>} {<278>arguments<178>}<278>\n"
+		"\n"
+		"         The switch command works similar to the switch statement in other\n"
+		"         languages. When the 'switch' command is encountered its body is parsed\n"
+		"         and each 'case' command found will be compared to the conditional\n"
+		"         argument of the switch and executed if there is a match.\n"
+		"\n"
+		"         When comparing strings the switch and case arguments must be enclosed\n"
+		"         in quote characters.\n"
+		"\n"
+		"         If the 'default' command is found and no 'case' statement has been\n"
+		"         matched the default command's argument is executed.\n"
+		"\n"
+		"<178>Example<278>: #switch {1d4} {#case 1 cackle;#case 2 smile;#default giggle}\n",
+
+		"statements"
+	},
+	{
+		"SYSTEM",
+
+		"<178>Command<278>: #system <178>{<278>command<178>}<278>\n"
+		"\n"
+		"         Executes the command specified as a shell command.\n",
+
+		"detach script run"
+	},
+	{
+		"TAB",
+
+		"<178>Command<278>: #tab <178>{<278>word<178>}<278>\n"
+		"\n"
+		"         Adds a word to the tab completion list, alphabetically sorted.\n"
+		"\n"
+		"         If no tabs are defined tintin will use the scrollback buffer\n"
+		"         for auto tab completion.\n"
+		"\n"
+		"<178>Comment<278>: You can remove a tab with the #untab command.\n",
+
+		"alias cursor history keypad macro speedwalk"
+	},
+	{
+		"TEXTIN",
+
+		"<178>Command<278>: #textin <178>{<278>filename<178>} {<278>delay<178>}<278>\n"
+		"\n"
+		"         Textin allows the user to read in a file, and send its contents\n"
+		"         directly to the server.  Useful for doing online creation, or message\n"
+		"         writing.\n"
+		"\n"
+		"         The delay is in seconds and takes a floating point number which is\n"
+		"         cumulatively applied to each outgoing line.\n",
+
+		"scan send"
+	},
+	{
+		"TICKER",
+
+		"<178>Command<278>: #ticker <178>{<278>name<178>} {<278>commands<178>} {<278>interval in seconds<178>}<278>\n"
+		"\n"
+		"         Executes given command every # of seconds.\n"
+		"\n"
+		"<178>Comment<278>: Tickers don't work in the startup session.\n"
+		"\n"
+		"<178>Comment<278>: You can remove a ticker with the #unticker command.\n",
+
+		"delay event"
+	},
+	{
+		"TIME",
+
+		"<178>Command<278>: #format <178>{<278>variable<178>} {<278>%t<178>} {<278>argument<178>}<278>\n"
+		"\n"
+		"         The %t format specifier of the #format command allows printing dates\n"
+		"         using the strftime() format specifiers. By default the time stamp used\n"
+		"         is the current time, if you want to print a past or future date use:\n"
+		"\n"
+		"<178>Command<278>: #format <178>{<278>variable<178>} {<278>%t<178>} {{<278>argument<178>} <178>{{<278>epoch time<178>}}<278>\n"
+		"\n"
+		"         The current epoch time value is obtained using #format {time} {%T}.\n"
+		"\n"
+		"         When using %t the argument should contain strftime format specifiers.\n"
+		"         The output may differ depending on your locale.\n"
+		"\n"
+		"         %a  Abbreviated name of the day of the week (mon ... sun).\n"
+		"         %A  Full name of the day of the week. (Monday ... Sunday)\n"
+		"         %b  Abbreviated name of the month (Jan ... Dec)\n"
+		"         %B  Full name of the month. (January ... December)\n"
+		"         %C  2 digit numeric century. (19 ... 20)\n"
+		"         %d  2 digit numeric day of the month (01 ... 31)\n"
+		"         %H  2 digit numeric 24-hour clock hour. (00 ... 23)\n"
+		"         %I  2 digit numeric 12-hour clock hour. (01 ... 12)\n"
+		"         %j  3 digit numeric day of the year (001 ... 366)\n"
+		"         %m  2 digit numeric month of the year (01 ... 12)\n"
+		"         %M  2 digit numeric minute of the hour (00 ... 59)\n"
+		"         %p  Abbreviated 12 hour clock period (AM ... PM)\n"
+		"         %P  Abbreviated 12 hour clock period (am ... pm)\n"
+		"         %S  2 digit numeric second of the minute (00 ...59\n"
+		"         %u  1 digit numeric day of the week (1 ... 7)\n"
+		"         %U  2 digit numeric Sunday week of the year (00 ... 53\n"
+		"         %w  1 digit numeric day of the week (0 ... 6)\n"
+		"         %W  2 digit numeric Monday week of the year (00 ... 53\n"
+		"         %y  2 digit numeric year. (70 ... 38)\n"
+		"         %Y  4 digit numeric year. (1970 ... 2038)\n"
+		"         %z  5 digit timezone offset. (-1200 ... +1400)\n"
+		"         %Z  Abbreviated name of the time zone.\n",
+
+		"echo format"
+	},
+	{
+		"VARIABLE",
+
+		"<178>Command<278>: #variable <178>{<278>variable name<178>} {<278>text to fill variable<178>}<278>\n"
+		"\n"
+		"         Variables differ from the %0-99 arguments in the fact that you can\n"
+		"         specify a full word as a variable, and they stay in memory for the\n"
+		"         full session unless they are changed.  They can be saved in the\n"
+		"         coms file, and can be set to different values if you have two or\n"
+		"         more sessions running at the same time.  Variables are global for\n"
+		"         each session and can be accessed by adding a $ before the variable\n"
+		"         name.\n"
+		"\n"
+		"<178>Example<278>: #alias {target} {#var target %0}\n"
+		"         #alias {x}      {kick $target}\n"
+		"\n"
+		"         The name of a variable must exist of only letters, numbers and\n"
+		"         underscores in order to be substituted.  If you do not meet these\n"
+		"         requirements do not panic, simply encapsulate the variable in braces:\n"
+		"\n"
+		"<178>Example<278>: #variable {cool website} {http://tintin.sourceforge.net}\n"
+		"         #chat I was on ${cool website} yesterday!.\n"
+		"\n"
+		"         Variables can be nested using brackets or dots:\n"
+		"\n"
+		"<178>Example<278>: #var hp[self] 34;#var hp[target] 46\n"
+		"\n"
+		"         You can see the first nest of a variable using $variable[+1] and the\n"
+		"         last nest using $variable[-1]. Using $variable[-2] will report the\n"
+		"         second last variable, and so on. To show all indices use $variable[].\n"
+		"         To show all values use $variable[%*] or a less generic regex.\n"
+		"\n"
+		"         Nested variables are also known as tables, table generally being used\n"
+		"         to refer to several variables nested within one specific variable.\n"
+		"\n"
+		"<178>Example<278>: #showme {Targets starting with the letter A: $targets[A%*]\n"
+		"\n"
+		"         To see the internal index of a variable use &<variable name>. To see\n"
+		"         the size of a table you would use: &targets[] or &targets[%*]. A non\n"
+		"         existent nested variable will report itself as 0.\n"
+		"\n" 
+		"<178>Example<278>: #showme {Number of targets starting with A: &targets[A%*]\n"
+		"\n"
+		"         In some scripts you need to know the name of a nested variable. This\n"
+		"         is also known as the key, and you can get it using *variable. For\n"
+		"         example *target[+1]. To get the first variable's name use *{+1}.\n"
+		"\n"
+		"         It's also possible to declare a table using brace notation. Using\n"
+		"         #var hp[self] 34 is the equivalent of #var {hp} {{self}{34}}. This\n"
+		"         also allows merging tables. #var hp[self] 34;#var hp[target] 46 is\n"
+		"         the equivalent of #var {hp} {{self}{34} {target}{46}} as well as\n"
+		"         #var {hp} {{self}{34}} {{target}{46}} or if you want to get creative\n"
+		"         the equivalent of #var hp[self] 34;#var {hp} {$hp} {{target}{46}}.\n"
+		"\n"
+		"<178>Comment<278>: You can remove a variable with the #unvariable command.\n",
+
+		"cat format function local math replace script"
+	},
+	{
+		"WHILE",
+
+		"<178>Command<278>: #while <178>{<278>conditional<178>} {<278>commands<178>}<278>\n"
+		"\n"
+		"         This command works similar to a 'while' statement in other languages.\n"
+		"\n"
+		"         When a 'while' command is encourated, the conditional is evaluated,\n"
+		"         and if TRUE (any non-zero result) the commands are executed. The\n"
+		"         'while' loop will be repeated indefinitely until the conditional is\n"
+		"         FALSE or the #BREAK or #RETURN commands are found.\n"
+		"\n"
+		"         The 'while' statement is only evaluated if it is read, so you must\n"
+		"         nest it inside a trigger, like an alias or action.\n"
+		"\n"
+		"         The conditional is evaluated exactly the same as in the 'math' command.\n"
+                "\n"
+                "<178>Example<278>: #math cnt 0;#while {$cnt < 20} {#math cnt $cnt + 1;say $cnt}\n"
+		"\n"
+		"<178>Comment<278>: See '#help math', for more information.\n",
+
+		"statements"
+	},
+                                                                                                   
+	{
+		"WRITE",
+
+		"<178>Command<278>: #write <178>{<278><filename><178>} {<278>[FORCE]<178>}<278>\n"
+		"\n"
+		"         Writes all current actions, aliases, subs, highlights, and variables\n"
+		"         to a command file, specified by filename.\n"
+		"\n"
+		"         By default you cannot write to .map files to prevent accidentally\n"
+		"         overwriting a map file. Use the FORCE argument to ignore this\n"
+		"         protection.\n",
+
+		"log read scan textin"
+	},
+	{
+		"ZAP",
+
+		"<178>Command<278>: #zap {[session]}\n"
+		"\n"
+		"         Kill your current session.  If there is no current session, it will\n"
+		"         cause the program to terminate. If you provide an argument it'll zap\n"
+		"         the given session instead.\n",
+
+		"all port run session sessionname snoop ssl"
+	},
+	{
+		"",
+		"",
+		""
+	}
+};

+ 238 - 0
history.c

@@ -0,0 +1,238 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2006                       *
+******************************************************************************/
+
+#include "tintin.h"
+
+DO_COMMAND(do_history)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+	int cnt;
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		tintin_header(ses, " HISTORY COMMANDS ");
+
+		for (cnt = 0 ; *history_table[cnt].name != 0 ; cnt++)
+		{
+			tintin_printf2(ses, "  [%-13s] %s", history_table[cnt].name, history_table[cnt].desc);
+		}
+		tintin_header(ses, "");
+
+		return ses;
+	}
+
+	for (cnt = 0 ; *history_table[cnt].name ; cnt++)
+	{
+		if (!is_abbrev(arg1, history_table[cnt].name))
+		{
+			continue;
+		}
+
+		history_table[cnt].fun(ses, arg2);
+
+		return ses;
+	}
+
+	do_history(ses, "");
+
+	return ses;
+}
+
+
+void add_line_history(struct session *ses, char *line)
+{
+	struct listroot *root;
+
+	root = ses->list[LIST_HISTORY];
+
+	if (HAS_BIT(root->flags, LIST_FLAG_IGNORE) || gtd->level->ignore)
+	{
+		return;
+	}
+
+	if (*line == 0)
+	{
+		if (root->used && HAS_BIT(ses->flags, SES_FLAG_REPEATENTER))
+		{
+			strcpy(line, root->list[root->used - 1]->arg1);
+		}
+		return;
+	}
+
+	if (*line == gtd->repeat_char)
+	{
+		search_line_history(ses, line);
+	}
+
+	update_node_list(ses->list[LIST_HISTORY], line, "", "", "");
+
+	while (root->used > gtd->history_size)
+	{
+		delete_index_list(ses->list[LIST_HISTORY], 0);
+	}
+
+	return;
+}
+
+void search_line_history(struct session *ses, char *line)
+{
+	struct listroot *root;
+	int i;
+
+	root = ses->list[LIST_HISTORY];
+
+	for (i = root->used - 1 ; i >= 0 ; i--)
+	{
+		if (!strncmp(root->list[i]->arg1, &line[1], strlen(&line[1])))
+		{
+			strcpy(line, root->list[i]->arg1);
+
+			return;
+		}
+	}
+	tintin_printf2(ses, "#REPEAT: NO MATCH FOUND FOR '%s'", line);
+}
+
+DO_HISTORY(history_character)
+{
+	gtd->repeat_char = *arg;
+
+	show_message(ses, LIST_HISTORY, "#HISTORY CHARACTER SET TO {%c}.", gtd->repeat_char);
+}
+
+DO_HISTORY(history_delete)
+{
+	if (ses->list[LIST_HISTORY]->used)
+	{
+		delete_index_list(ses->list[LIST_HISTORY], ses->list[LIST_HISTORY]->used - 1);
+	}
+
+	return;
+}
+
+DO_HISTORY(history_insert)
+{
+	add_line_history(ses, arg);
+}
+
+DO_HISTORY(history_list)
+{
+	struct listroot *root;
+	int i, cnt = 1;
+
+	root = ses->list[LIST_HISTORY];
+
+	for (i = 0 ; i < root->used ; i++)
+	{
+		tintin_printf2(ses, "%6d - %s", cnt++, root->list[i]->arg1);
+	}
+	return;
+}
+
+DO_HISTORY(history_read)
+{
+	FILE *file;
+	char *cptr, buffer[BUFFER_SIZE];
+
+	file = fopen(arg, "r");
+
+	if (file == NULL)
+	{
+		show_message(ses, LIST_HISTORY, "#HISTORY: COULDN'T OPEN FILE {%s} TO READ.", arg);
+		return;
+	}
+
+	kill_list(ses->list[LIST_HISTORY]);
+
+	while (fgets(buffer, BUFFER_SIZE-1, file))
+	{
+		cptr = strchr(buffer, '\n');
+
+		if (cptr)
+		{
+			*cptr = 0;
+
+			if (*buffer)
+			{
+				insert_node_list(ses->list[LIST_HISTORY], buffer, "", "", "");
+			}
+		}
+	}
+	insert_node_list(ses->list[LIST_HISTORY], "", "", "", "");
+
+	fclose(file);
+
+	if (ses->list[LIST_HISTORY]->used > gtd->history_size) 
+	{
+		char buf[BUFFER_SIZE];
+
+		sprintf(buf, "{HISTORY SIZE} {%d}", UMIN(ses->list[LIST_HISTORY]->used, 9999));
+
+		do_configure(gts, buf);
+	}
+
+	return;
+}
+
+DO_HISTORY(history_size)
+{
+	if (atoi(arg) < 1 || atoi(arg) > 100000)
+	{
+		show_error(ses, LIST_COMMAND, "#ERROR: #HISTORY SIZE: PROVIDE A NUMBER BETWEEN 1 and 100,000");
+	}
+	else
+	{
+		gtd->history_size = atoi(arg);
+	}
+	return;
+}
+
+DO_HISTORY(history_write)
+{
+	struct listroot *root = ses->list[LIST_HISTORY];
+	FILE *file;
+	int i;
+
+	file = fopen(arg, "w");
+
+	if (file == NULL)
+	{
+		tintin_printf2(ses, "#HISTORY: COULDN'T OPEN FILE {%s} TO WRITE.", arg);
+
+		return;
+	}
+
+	for (i = 0 ; i < root->used ; i++)
+	{
+		fprintf(file, "%s\n", root->list[i]->arg1);
+	}
+
+	fclose(file);
+
+	return;
+}

+ 821 - 0
input.c

@@ -0,0 +1,821 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2006                       *
+******************************************************************************/
+
+#include "tintin.h"
+
+void process_input(void)
+{
+	char input[STRING_SIZE];
+	int len, out;
+
+	push_call("process_input(void)");
+
+	gtd->time_input = gtd->time;
+
+	if (gtd->detach_port)
+	{
+		if (gtd->detach_sock > 0)
+		{
+			len = read(gtd->detach_sock, input, 1);
+
+			if (len <= 0)
+			{
+				if (len == -1)
+				{
+					syserr_printf(gtd->ses, "process_input: read", gtd->detach_sock);
+				}
+				else
+				{
+					show_message(gtd->ses, LIST_COMMAND, "#DAEMON UPDATE: DETACHING FROM PID {%d} DUE TO READ FAILURE.", gtd->detach_pid);
+				}
+
+				gtd->detach_sock = close(gtd->detach_sock);
+
+				pop_call();
+				return;
+			}
+		}
+		else
+		{
+			printf("process_input: error?\n");
+		}
+	}
+	else
+	{
+		len = read(0, input, 1);
+	}
+
+	input[len] = 0;
+
+	if (gtd->attach_sock)
+	{
+		out = write(gtd->attach_sock, input, 1);
+
+		if (out >= 0)
+		{
+			pop_call();
+			return;
+		}
+
+		gtd->attach_sock = close(gtd->attach_sock);
+
+		show_message(gtd->ses, LIST_COMMAND, "#DAEMON ATTACH: WRITE ERROR: UNATTACHING.");
+	}
+
+	if (HAS_BIT(gtd->ses->telopts, TELOPT_FLAG_SGA) && !HAS_BIT(gtd->ses->telopts, TELOPT_FLAG_ECHO))
+	{
+		read_key(input, len);
+	}
+	else
+	{
+		read_line(input, len);
+	}
+
+	if (!HAS_BIT(gtd->flags, TINTIN_FLAG_PROCESSINPUT))
+	{
+		pop_call();
+		return;
+	}
+
+	DEL_BIT(gtd->flags, TINTIN_FLAG_PROCESSINPUT);
+
+	if (gtd->chat && gtd->chat->paste_time)
+	{
+		chat_paste(gtd->input_buf, NULL);
+
+		pop_call();
+		return;
+	}
+
+	if (HAS_BIT(gtd->ses->telopts, TELOPT_FLAG_ECHO))
+	{
+		add_line_history(gtd->ses, gtd->input_buf);
+	}
+
+	if (HAS_BIT(gtd->ses->telopts, TELOPT_FLAG_ECHO))
+	{
+		echo_command(gtd->ses, gtd->input_buf);
+	}
+	else
+	{
+		echo_command(gtd->ses, "");
+	}
+
+	if (gtd->ses->scroll->line != -1)
+	{
+		buffer_end(gtd->ses, "");
+	}
+
+	check_all_events(gtd->ses, SUB_ARG|SUB_SEC, 0, 1, "RECEIVED INPUT", gtd->input_buf);
+
+	if (check_all_events(gtd->ses, SUB_ARG|SUB_SEC, 0, 1, "CATCH RECEIVED INPUT", gtd->input_buf) == 1)
+	{
+		pop_call();
+		return;
+	}
+
+	if (HAS_BIT(gtd->flags, TINTIN_FLAG_CHILDLOCK))
+	{
+		write_mud(gtd->ses, gtd->input_buf, SUB_EOL);
+	}
+	else
+	{
+		gtd->ses = script_driver(gtd->ses, LIST_COMMAND, gtd->input_buf);
+	}
+
+	if (IS_SPLIT(gtd->ses))
+	{
+		erase_toeol();
+	}
+
+	gtd->input_buf[0] = 0;
+
+	fflush(NULL);
+
+	pop_call();
+	return;
+}
+
+void read_line(char *input, int len)
+{
+	int size, width, index;
+
+//	gtd->input_buf[gtd->input_len] = 0;
+
+	if (HAS_BIT(gtd->ses->flags, SES_FLAG_CONVERTMETA))
+	{
+		convert_meta(input, &gtd->macro_buf[strlen(gtd->macro_buf)], FALSE);
+	}
+	else if (HAS_BIT(gtd->flags, TINTIN_FLAG_CONVERTMETACHAR))
+	{
+		convert_meta(input, &gtd->macro_buf[strlen(gtd->macro_buf)], TRUE);
+	}
+	else
+	{
+		strcat(gtd->macro_buf, input);
+	}
+
+	if (check_key(input, len))
+	{
+		return;
+	}
+
+	if (HAS_BIT(gtd->ses->charset, CHARSET_FLAG_UTF8) && is_utf8_head(gtd->macro_buf))
+	{
+		if (get_utf8_size(gtd->macro_buf) == 1)
+		{
+			return;
+		}
+	}
+
+	if (gtd->macro_buf[0] == ASCII_ESC)
+	{
+		strcpy(input, gtd->macro_buf);
+
+		convert_meta(input, gtd->macro_buf, FALSE);
+	}
+
+	get_utf8_index(gtd->macro_buf, &index);
+
+	check_all_events(gtd->ses, SUB_ARG, 0, 2, "RECEIVED KEYPRESS", gtd->macro_buf, ntos(index));
+
+	if (check_all_events(gtd->ses, SUB_ARG, 0, 2, "CATCH RECEIVED KEYPRESS", gtd->macro_buf, ntos(index)) == 1)
+	{
+		gtd->macro_buf[0] = 0;
+		return;
+	}
+
+	while (gtd->macro_buf[0])
+	{
+		switch (gtd->macro_buf[0])
+		{
+			case ASCII_CR:
+			case ASCII_LF:
+				cursor_enter(gtd->ses, "");
+
+				memmove(gtd->macro_buf, &gtd->macro_buf[1], 1 + strlen(&gtd->macro_buf[1]));
+				break;
+
+			default:
+				if (HAS_BIT(gtd->ses->charset, CHARSET_FLAG_UTF8) && gtd->macro_buf[0] & 128 && gtd->macro_buf[1])
+				{
+					size = get_utf8_width(gtd->macro_buf, &width);
+
+					if (HAS_BIT(gtd->flags, TINTIN_FLAG_INSERTINPUT) && gtd->input_len != gtd->input_cur)
+					{
+						if (width)
+						{
+							cursor_delete(gtd->ses, "");
+						}
+					}
+
+					ins_sprintf(&gtd->input_buf[gtd->input_cur], "%.*s", size, gtd->macro_buf);
+
+					gtd->input_pos += width;
+					gtd->input_cur += size;
+					gtd->input_len += size;
+
+					if (width && gtd->input_len != gtd->input_cur)
+					{
+						input_printf("\e[%d@%.*s", width, size, gtd->macro_buf);
+					}
+					else
+					{
+						input_printf("%.*s", size, gtd->macro_buf);
+					}
+					memmove(gtd->macro_buf, &gtd->macro_buf[size], 1 + strlen(&gtd->macro_buf[size]));
+				}
+				else
+				{
+					if (HAS_BIT(gtd->flags, TINTIN_FLAG_INSERTINPUT) && gtd->input_len != gtd->input_cur)
+					{
+						cursor_delete(gtd->ses, "");
+					}
+
+					ins_sprintf(&gtd->input_buf[gtd->input_cur], "%c", gtd->macro_buf[0]);
+
+					gtd->input_len++;
+					gtd->input_cur++;
+					gtd->input_pos++;
+
+					if (gtd->input_len != gtd->input_cur)
+					{
+						input_printf("\e[1@%c", gtd->macro_buf[0]);
+					}
+					else
+					{
+						input_printf("%c", gtd->macro_buf[0]);
+					}
+					memmove(gtd->macro_buf, &gtd->macro_buf[1], 1 + strlen(&gtd->macro_buf[1]));
+				}
+
+//				gtd->macro_buf[0] = 0;
+				gtd->input_tmp[0] = 0;
+				gtd->input_buf[gtd->input_len] = 0;
+
+				cursor_check_line_modified(gtd->ses, "");
+
+				DEL_BIT(gtd->flags, TINTIN_FLAG_HISTORYBROWSE);
+
+				kill_list(gtd->ses->list[LIST_COMMAND]);
+
+				if (HAS_BIT(gtd->flags, TINTIN_FLAG_HISTORYSEARCH))
+				{
+					cursor_history_find(gtd->ses, "");
+				}
+				break;
+		}
+	}
+}
+
+void read_key(char *input, int len)
+{
+	int cnt;
+
+	if (gtd->input_buf[0] == gtd->tintin_char)
+	{
+		read_line(input, len);
+
+		return;
+	}
+
+	if (HAS_BIT(gtd->ses->flags, SES_FLAG_CONVERTMETA))
+	{
+		convert_meta(input, &gtd->macro_buf[strlen(gtd->macro_buf)], FALSE);
+	}
+	else if (HAS_BIT(gtd->flags, TINTIN_FLAG_CONVERTMETACHAR))
+	{
+		convert_meta(input, &gtd->macro_buf[strlen(gtd->macro_buf)], TRUE);
+	}
+	else
+	{
+		strcat(gtd->macro_buf, input);
+	}
+
+	if (check_key(input, len))
+	{
+		return;
+	}
+
+	for (cnt = 0 ; gtd->macro_buf[cnt] ; cnt++)
+	{
+		switch (gtd->macro_buf[cnt])
+		{
+			case ASCII_CR:
+			case ASCII_LF:
+				gtd->input_buf[0] = 0;
+				gtd->macro_buf[0] = 0;
+				gtd->input_len = 0;
+
+				if (HAS_BIT(gtd->ses->flags, SES_FLAG_RUN))
+				{
+					socket_printf(gtd->ses, 1, "%c", '\r');
+				}
+				else
+				{
+					socket_printf(gtd->ses, 2, "%c%c", '\r', '\n');
+				}
+				break;
+
+			default:
+				if (gtd->macro_buf[cnt] == gtd->tintin_char && gtd->input_buf[0] == 0)
+				{
+					if (gtd->input_len != gtd->input_cur)
+					{
+						print_stdout("\e[1@%c", gtd->macro_buf[cnt]);
+					}
+					else
+					{
+						print_stdout("%c", gtd->macro_buf[cnt]);
+					}
+					gtd->input_buf[0] = gtd->tintin_char;
+					gtd->input_buf[1] = 0;
+					gtd->macro_buf[0] = 0;
+					gtd->input_len = 1;
+					gtd->input_cur = 1;
+					gtd->input_pos = 1;
+				}
+				else
+				{
+					socket_printf(gtd->ses, 1, "%c", gtd->macro_buf[cnt]);
+					gtd->input_buf[0] = 127;
+					gtd->macro_buf[0] = 0;
+					gtd->input_len = 0;
+				}
+				break;
+		}
+	}
+}
+
+int check_key(char *input, int len)
+{
+	char buf[BUFFER_SIZE];
+	struct listroot *root;
+	struct listnode *node;
+	int cnt, val[5];
+
+	push_call("check_key(%p,%d)",input,len);
+
+//	tintin_printf2(gtd->ses, "check_key(%d,%s)",*gtd->macro_buf, gtd->macro_buf);
+
+	if (!HAS_BIT(gtd->ses->flags, SES_FLAG_CONVERTMETA))
+	{
+		root  = gtd->ses->list[LIST_MACRO];
+
+		if (!HAS_BIT(root->flags, LIST_FLAG_IGNORE))
+		{
+			for (root->update = 0 ; root->update < root->used ; root->update++)
+			{
+				node = root->list[root->update];
+
+				if (*node->arg1 == '^' && gtd->input_len)
+				{
+					continue;
+				}
+				else if (!strcmp(gtd->macro_buf, node->arg3))
+				{
+					strcpy(buf, node->arg2);
+
+					if (HAS_BIT(node->flags, NODE_FLAG_ONESHOT))
+					{
+						delete_node_list(gtd->ses, LIST_MACRO, node);
+					}
+
+					script_driver(gtd->ses, LIST_MACRO, buf);
+
+					gtd->macro_buf[0] = 0;
+					pop_call();
+					return TRUE;
+				}
+				else if (!strncmp(gtd->macro_buf, node->arg3, strlen(gtd->macro_buf)))
+				{
+					pop_call();
+					return TRUE;
+				}
+			}
+		}
+
+		if (!HAS_BIT(gtd->ses->telopts, TELOPT_FLAG_SGA) || HAS_BIT(gtd->ses->telopts, TELOPT_FLAG_ECHO) || gtd->input_buf[0] == gtd->tintin_char)
+		{
+			for (cnt = 0 ; *cursor_table[cnt].fun != NULL ; cnt++)
+			{
+				if (*cursor_table[cnt].code)
+				{
+					if (!strcmp(gtd->macro_buf, cursor_table[cnt].code))
+					{
+						cursor_table[cnt].fun(gtd->ses, "");
+						gtd->macro_buf[0] = 0;
+
+						pop_call();
+						return TRUE;
+					}
+					else if (!strncmp(gtd->macro_buf, cursor_table[cnt].code, strlen(gtd->macro_buf)))
+					{
+						pop_call();
+						return TRUE;
+					}
+				}
+			}
+		}
+
+		if (gtd->macro_buf[0] == ASCII_ESC)
+		{
+			if (gtd->macro_buf[1] == '[')
+			{
+				if (gtd->macro_buf[2] == '<' && !HAS_BIT(gtd->ses->list[LIST_BUTTON]->flags, LIST_FLAG_IGNORE))
+				{
+					val[0] = val[1] = val[2] = cnt = input[0] = 0;
+
+					for (len = 3 ; gtd->macro_buf[len] ; len++)
+					{
+						if (isdigit(gtd->macro_buf[len]))
+						{
+							cat_sprintf(input, "%c", gtd->macro_buf[len]);
+						}
+						else
+						{
+							switch (gtd->macro_buf[len])
+							{
+								case ';':
+									val[cnt++] = get_number(gtd->ses, input);
+									input[0] = 0;
+									break;
+
+								case 'm':
+								case 'M':
+									val[cnt++] = get_number(gtd->ses, input);
+									mouse_handler(gtd->ses, val[0], val[2], val[1], gtd->macro_buf[len]); // swap x y to row col
+									gtd->macro_buf[0] = 0;
+									pop_call();
+									return TRUE;
+
+								default:
+									print_stdout("unknownmouse input error (%s)\n", gtd->macro_buf);
+									gtd->macro_buf[0] = 0;
+									pop_call();
+									return TRUE;
+							}
+						}
+					}
+					pop_call();
+					return TRUE;
+				}
+				else if (gtd->macro_buf[2] >= '0' && gtd->macro_buf[2] <= '9')
+				{
+					cnt = input[0] = 0;
+					memset(val, 0, sizeof(val));
+
+//					tintin_printf2(gtd->ses, "debug: %d %d %d %d %d", val[0], val[1], val[2], val[3], val[4], val[5]);
+		
+					for (len = 2 ; gtd->macro_buf[len] ; len++)
+					{
+						if (cnt > 5)
+						{
+							pop_call();
+							return FALSE;
+						}
+
+						if (isdigit(gtd->macro_buf[len]))
+						{
+							cat_sprintf(input, "%c", gtd->macro_buf[len]);
+						}
+						else
+						{
+							switch (gtd->macro_buf[len])
+							{
+								case '-':
+									if (input[0] == 0)
+									{
+										strcat(input, "-");
+									}
+									else
+									{
+										tintin_printf2(NULL, "\e[1;31merror: bad csi input (%s)\n", &gtd->macro_buf[1]);
+										gtd->macro_buf[0] = 0;
+										continue;
+									}
+									break;
+								case ';':
+									val[cnt++] = get_number(gtd->ses, input);
+									input[0] = 0;
+									break;
+
+								case 't':
+									val[cnt++] = get_number(gtd->ses, input);
+									csit_handler(val[0], val[1], val[2]);
+									gtd->macro_buf[0] = 0;
+									pop_call();
+									return TRUE;
+
+								case '&':
+									val[cnt++] = get_number(gtd->ses, input);
+									input[0] = 0;
+									break;
+
+								case 'w':
+									rqlp_handler(val[0], val[1], val[2], val[3]);
+									gtd->macro_buf[0] = 0;
+									pop_call();
+									return TRUE;
+
+								default:
+									pop_call();
+									return FALSE;
+							}
+						}
+					}
+					pop_call();
+					return TRUE;
+				}
+				else if (gtd->macro_buf[2] == 0)
+				{
+					pop_call();
+					return TRUE;
+				}
+			}
+			else if (gtd->macro_buf[1] == ']')
+			{
+				switch (gtd->macro_buf[2])
+				{
+					case 'L':
+					case 'l':
+						for (len = 3 ; gtd->macro_buf[len] ; len++)
+						{
+							if (gtd->macro_buf[len] < 32)
+							{
+								input[len - 3] = 0;
+								osc_handler(gtd->macro_buf[2], input);
+								gtd->macro_buf[0] = 0;
+								pop_call();
+								return TRUE;
+							}
+							else
+							{
+								input[len - 3 ] = gtd->macro_buf[len];
+							}
+						}
+						break;
+
+					default:
+						print_stdout("\e[1;31merror: unknown osc input (%s)\n", gtd->macro_buf);
+						gtd->macro_buf[0] = 0;
+						pop_call();
+						return TRUE;
+				}
+			}
+			else if (gtd->macro_buf[1] == 0)
+			{
+				pop_call();
+				return TRUE;
+			}
+		}
+	}
+	pop_call();
+	return FALSE;
+}
+
+void convert_meta(char *input, char *output, int eol)
+{
+	char *pti, *pto;
+
+	push_call("convert_meta(%p,%p,%d)",input,output,eol);
+
+	pti = input;
+	pto = output;
+
+	while (*pti && pti - input < BUFFER_SIZE / 2)
+	{
+		switch (*pti)
+		{
+			case ASCII_ESC:
+				*pto++ = '\\';
+				*pto++ = 'e';
+				pti++;
+				break;
+
+			case ASCII_DEL:
+				*pto++ = '\\';
+				*pto++ = 'x';
+				*pto++ = '7';
+				*pto++ = 'F';
+				pti++;
+				break;
+
+			case ASCII_BEL:
+				*pto++ = '\\';
+				*pto++ = 'a';
+				pti++;
+				break;
+
+			case ASCII_BS:
+				*pto++ = '\\';
+				*pto++ = 'b';
+				pti++;
+				break;
+
+			case ASCII_FF:
+				*pto++ = '\\';
+				*pto++ = 'f';
+				pti++;
+				break;
+
+			case ASCII_HTAB:
+				*pto++ = '\\';
+				*pto++ = 't';
+				pti++;
+				break;
+
+			case ASCII_CR:
+				if (HAS_BIT(gtd->flags, TINTIN_FLAG_CONVERTMETACHAR))
+				{
+					*pto++ = '\\';
+					*pto++ = 'r';
+					*pto = 0;
+					DEL_BIT(gtd->flags, TINTIN_FLAG_CONVERTMETACHAR);
+					pop_call();
+					return;
+				}
+				if (eol)
+				{
+					*pto++ = '\\';
+					*pto++ = 'r';
+				}
+				*pto++ = *pti++;
+				break;
+
+			case ASCII_VTAB:
+				*pto++ = '\\';
+				*pto++ = 'v';
+				pti++;
+				break;
+
+			case ASCII_LF:
+				if (HAS_BIT(gtd->flags, TINTIN_FLAG_CONVERTMETACHAR))
+				{
+					*pto++ = '\\';
+					*pto++ = 'n';
+					*pto = 0;
+					DEL_BIT(gtd->flags, TINTIN_FLAG_CONVERTMETACHAR);
+					pop_call();
+					return;
+				}
+
+				if (eol)
+				{
+					*pto++ = '\\';
+					*pto++ = 'n';
+				}
+				*pto++ = *pti++;
+
+				break;
+
+			default:
+				if (*pti > 0 && *pti < 32)
+				{
+					*pto++ = '\\';
+					*pto++ = 'c';
+					if (*pti <= 26)
+					{
+						*pto++ = 'a' + *pti - 1;
+					}
+					else
+					{
+						*pto++ = 'A' + *pti - 1;
+					}
+					pti++;
+					break;
+				}
+				else
+				{
+					*pto++ = *pti++;
+				}
+				break;
+		}
+	}
+	*pto = 0;
+
+	DEL_BIT(gtd->flags, TINTIN_FLAG_CONVERTMETACHAR);
+
+	pop_call();
+	return;
+}
+
+char *str_convert_meta(char *input, int eol)
+{
+	static char buf[BUFFER_SIZE];
+
+	convert_meta(input, buf, eol);
+
+	return buf;
+}
+
+
+/*
+	Currenly only used in split mode.
+*/
+
+void echo_command(struct session *ses, char *line)
+{
+	char buffer[BUFFER_SIZE], output[BUFFER_SIZE];
+
+	DEL_BIT(ses->telopts, TELOPT_FLAG_PROMPT);
+
+	if (ses->check_output)
+	{
+		strcpy(output, ses->more_output);
+
+		process_mud_output(ses, buffer, FALSE);
+	}
+	else
+	{
+		strcpy(output, "");
+	}
+
+	if (!HAS_BIT(ses->flags, SES_FLAG_SPLIT))
+	{
+		add_line_buffer(ses, line, -1);
+
+		return;
+	}
+
+	if (HAS_BIT(ses->flags, SES_FLAG_ECHOCOMMAND))
+	{
+		sprintf(buffer, "%s%s\e[0m", ses->cmd_color, line);
+	}
+	else
+	{
+		if (strip_vt102_strlen(ses, output) == 0)
+		{
+			return;
+		}
+		sprintf(buffer, "\e[0m");
+	}
+
+	if (ses->wrap == gtd->screen->cols)
+	{
+		gtd->level->scroll++;
+
+		tintin_printf2(ses, "%s%s", ses->scroll->input, buffer);
+
+		gtd->level->scroll--;
+	}
+	add_line_buffer(ses, buffer, -1);
+}
+
+void input_printf(char *format, ...)
+{
+	char buf[STRING_SIZE];
+	va_list args;
+
+	if (!HAS_BIT(gtd->flags, TINTIN_FLAG_HISTORYSEARCH))
+	{
+		if (!HAS_BIT(gtd->ses->telopts, TELOPT_FLAG_ECHO) && gtd->input_buf[0] != gtd->tintin_char)
+		{
+			return;
+		}
+	}
+
+	va_start(args, format);
+	vsprintf(buf, format, args);
+	va_end(args);
+
+	print_stdout("%s", buf);
+}
+
+void modified_input(void)
+{
+	kill_list(gtd->ses->list[LIST_COMMAND]);
+
+	if (HAS_BIT(gtd->flags, TINTIN_FLAG_HISTORYSEARCH))
+	{
+		cursor_history_find(gtd->ses, "");
+	}
+
+	if (HAS_BIT(gtd->flags, TINTIN_FLAG_HISTORYBROWSE))
+	{
+		DEL_BIT(gtd->flags, TINTIN_FLAG_HISTORYBROWSE);
+	}
+
+}

+ 559 - 0
line.c

@@ -0,0 +1,559 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2008                       *
+******************************************************************************/
+
+#include "tintin.h"
+
+DO_COMMAND(do_line)
+{
+	char arg1[BUFFER_SIZE];
+	int cnt;
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+
+	if (*arg1 == 0)
+	{
+		tintin_header(ses, " LINE OPTIONS ");
+
+		for (cnt = 0 ; *line_table[cnt].fun != NULL ; cnt++)
+		{
+			if (*line_table[cnt].desc)
+			{
+				tintin_printf2(ses, "  [%-13s] %s", line_table[cnt].name, line_table[cnt].desc);
+			}
+		}
+		tintin_header(ses, "");
+
+		return ses;
+	}
+	else
+	{
+		for (cnt = 0 ; *line_table[cnt].name ; cnt++)
+		{
+			if (is_abbrev(arg1, line_table[cnt].name))
+			{
+				break;
+			}
+		}
+
+		if (*line_table[cnt].name == 0)
+		{
+			do_line(ses, "");
+		}
+		else
+		{
+			ses = line_table[cnt].fun(ses, arg);
+		}
+	}
+	return ses;
+}
+
+DO_LINE(line_background)
+{
+	char arg1[BUFFER_SIZE];
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ALL);
+
+	if (*arg1 == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #LINE {BACKGROUND} {command}.");
+
+		return ses;
+	}
+
+	gtd->level->background++;
+
+	ses = script_driver(ses, LIST_COMMAND, arg1);
+
+	gtd->level->background--;
+
+	return ses;
+}
+
+DO_LINE(line_benchmark)
+{
+	long long start, end;
+	char arg1[BUFFER_SIZE];
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ALL);
+
+	if (*arg1 == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #LINE {BENCHMARK} {command}.");
+
+		return ses;
+	}
+
+	start = utime();
+
+	ses = script_driver(ses, LIST_COMMAND, arg1);
+
+	end = utime();
+
+	tintin_printf2(ses, "#LINE BENCHMARK: %lld USEC.", end - start);
+
+	return ses;
+}
+
+DO_LINE(line_capture)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg1 && *arg2)
+	{
+		if (ses->line_capturefile)
+		{
+			free(ses->line_capturefile);
+		}
+		ses->line_capturefile  = strdup(arg1);
+		ses->line_captureindex = 1;
+
+		ses = script_driver(ses, LIST_COMMAND, arg2);
+
+		if (ses->line_capturefile)
+		{
+			free(ses->line_capturefile);
+			ses->line_capturefile = NULL;
+		}
+	}
+	else
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #LINE CAPTURE {VARIABLE} {command}.");
+	}
+	return ses;
+}
+
+DO_LINE(line_debug)
+{
+	char arg1[BUFFER_SIZE];
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ALL);
+
+	if (*arg1 == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #LINE {DEBUG} {command}.");
+
+		return ses;
+	}
+
+	gtd->level->debug++;
+
+	ses = script_driver(ses, LIST_COMMAND, arg1);
+
+	gtd->level->debug--;
+
+	return ses;
+}
+
+DO_LINE(line_gag)
+{
+	char arg1[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+
+	show_debug(ses, LIST_GAG, "#DEBUG LINE GAG");
+
+	SET_BIT(ses->flags, SES_FLAG_GAG);
+
+	return ses;
+}
+
+DO_LINE(line_ignore)
+{
+	char arg1[BUFFER_SIZE];
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ALL);
+
+	if (*arg1 == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #LINE {IGNORE} {command}.");
+		
+		return ses;
+	}
+
+	gtd->level->ignore++;
+
+	ses = script_driver(ses, LIST_COMMAND, arg1);
+
+	gtd->level->ignore--;
+
+	return ses;
+}
+
+// Without an argument mark next line to be logged, otherwise log the given line to file.
+
+DO_LINE(line_log)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+	FILE *logfile;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg1 && *arg2)
+	{
+		substitute(ses, arg2, arg2, SUB_ESC|SUB_COL|SUB_LNF);
+
+		if (ses->logline_time == gtd->time && !strcmp(ses->logline_name, arg1))
+		{
+			logit(ses, arg2, ses->logline_file, LOG_FLAG_NONE);
+		}
+		else
+		{
+			if ((logfile = fopen(arg1, "a")))
+			{
+				if (ses->logline_file)
+				{
+					fclose(ses->logline_file);
+				}
+				free(ses->logline_name);
+
+				ses->logline_name = strdup(arg1);
+				ses->logline_file = logfile;
+				ses->logline_time = gtd->time;
+
+				loginit(ses, ses->logline_file, LOG_FLAG_APPEND | HAS_BIT(ses->logmode, LOG_FLAG_HTML));
+
+				logit(ses, arg2, ses->logline_file, LOG_FLAG_NONE);
+			}
+			else
+			{
+				show_error(ses, LIST_COMMAND, "#ERROR: #LINE LOG {%s} - COULDN'T OPEN FILE.", arg1);
+			}
+		}
+	}
+	else
+	{
+		if (ses->lognext_time == gtd->time && !strcmp(ses->lognext_name, arg1))
+		{
+			SET_BIT(ses->logmode, LOG_FLAG_NEXT);
+		}
+		else if ((logfile = fopen(arg1, "a")))
+		{
+			if (ses->lognext_file)
+			{
+				fclose(ses->lognext_file);
+			}
+			free(ses->lognext_name);
+
+			ses->lognext_name = strdup(arg1);
+			ses->lognext_file = logfile;
+			ses->lognext_time = gtd->time;
+
+			SET_BIT(ses->logmode, LOG_FLAG_NEXT);
+		}
+		else
+		{
+			show_error(ses, LIST_COMMAND, "#ERROR: #LINE LOG {%s} - COULDN'T OPEN FILE.", arg1);
+		}
+	}
+	return ses;
+}
+
+DO_LINE(line_logmode)
+{
+	struct session *active_ses;
+
+	char arg1[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+
+	DEL_BIT(ses->logmode, LOG_FLAG_OLD_HTML|LOG_FLAG_OLD_PLAIN|LOG_FLAG_OLD_RAW);
+
+	switch (HAS_BIT(ses->logmode, LOG_FLAG_HTML|LOG_FLAG_PLAIN|LOG_FLAG_RAW))
+	{
+		case LOG_FLAG_HTML:
+			SET_BIT(ses->logmode, LOG_FLAG_OLD_HTML);
+			break;
+		case LOG_FLAG_PLAIN:
+			SET_BIT(ses->logmode, LOG_FLAG_OLD_PLAIN);
+			break;
+		case LOG_FLAG_RAW:
+			SET_BIT(ses->logmode, LOG_FLAG_OLD_RAW);
+			break;
+	}
+
+	if (is_abbrev(arg1, "HTML"))
+	{
+		SET_BIT(ses->logmode, LOG_FLAG_HTML);
+		DEL_BIT(ses->logmode, LOG_FLAG_PLAIN);
+		DEL_BIT(ses->logmode, LOG_FLAG_RAW);
+	}
+	else if (is_abbrev(arg1, "PLAIN"))
+	{
+		SET_BIT(ses->logmode, LOG_FLAG_PLAIN);
+		DEL_BIT(ses->logmode, LOG_FLAG_HTML);
+		DEL_BIT(ses->logmode, LOG_FLAG_RAW);
+	}
+	else if (is_abbrev(arg1, "RAW"))
+	{
+		SET_BIT(ses->logmode, LOG_FLAG_RAW);
+		DEL_BIT(ses->logmode, LOG_FLAG_HTML);
+		DEL_BIT(ses->logmode, LOG_FLAG_PLAIN);
+	}
+	else
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #LINE {LOGMODE} {HTML|PLAIN|RAW} {command}.");
+
+		return ses;
+	}
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ALL);
+
+	active_ses = script_driver(ses, LIST_COMMAND, arg1);
+
+	DEL_BIT(ses->logmode, LOG_FLAG_HTML|LOG_FLAG_PLAIN|LOG_FLAG_RAW);
+
+	switch (HAS_BIT(ses->logmode, LOG_FLAG_OLD_HTML|LOG_FLAG_OLD_PLAIN|LOG_FLAG_OLD_RAW))
+	{
+		case LOG_FLAG_OLD_HTML:
+			SET_BIT(ses->logmode, LOG_FLAG_HTML);
+			break;
+		case LOG_FLAG_OLD_PLAIN:
+			SET_BIT(ses->logmode, LOG_FLAG_PLAIN);
+			break;
+		case LOG_FLAG_OLD_RAW:
+			SET_BIT(ses->logmode, LOG_FLAG_RAW);
+			break;
+	}
+
+	return ses = active_ses;
+}
+
+DO_LINE(line_logverbatim)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+	FILE *logfile;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = get_arg_in_braces(ses, arg, arg2, GET_ALL);
+
+	if (*arg1 && *arg2)
+	{
+		if (!strcmp(ses->logline_name, arg1))
+		{
+			logit(ses, arg2, ses->logline_file, LOG_FLAG_LINEFEED);
+		}
+		else
+		{
+			if ((logfile = fopen(arg1, "a")))
+			{
+				if (ses->logline_file)
+				{
+					fclose(ses->logline_file);
+				}
+				free(ses->logline_name);
+				ses->logline_name = strdup(arg1);
+				ses->logline_file = logfile;
+
+				loginit(ses, ses->logline_file, LOG_FLAG_APPEND | HAS_BIT(ses->logmode, LOG_FLAG_HTML));
+
+				logit(ses, arg2, ses->logline_file, LOG_FLAG_LINEFEED);
+			}
+			else
+			{
+				show_error(ses, LIST_COMMAND, "#ERROR: #LINE LOGVERBATIM {%s} - COULDN'T OPEN FILE.", arg1);
+			}
+		}
+	}
+	else
+	{
+		if (!strcmp(ses->lognext_name, arg1))
+		{
+			SET_BIT(ses->logmode, LOG_FLAG_NEXT);
+		}
+		else if ((logfile = fopen(arg1, "a")))
+		{
+			if (ses->lognext_file)
+			{
+				fclose(ses->lognext_file);
+			}
+			free(ses->lognext_name);
+			ses->lognext_name = strdup(arg1);
+			ses->lognext_file = logfile;
+
+			SET_BIT(ses->logmode, LOG_FLAG_NEXT);
+		}
+		else
+		{
+			show_error(ses, LIST_COMMAND, "#ERROR: #LINE LOGVERBATIM {%s} - COULDN'T OPEN FILE.", arg1);
+		}
+	}
+	return ses;
+}
+
+DO_LINE(line_oneshot)
+{
+	char arg1[BUFFER_SIZE];
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ALL);
+
+	if (*arg1 == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #LINE {ONESHOT} {command}.");
+		
+		return ses;
+	}
+
+	gtd->level->oneshot++;
+
+	ses = script_driver(ses, LIST_COMMAND, arg1);
+
+	gtd->level->oneshot--;
+
+	return ses;
+}
+
+DO_LINE(line_quiet)
+{
+	char arg1[BUFFER_SIZE];
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ALL);
+
+	if (*arg1 == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #LINE {QUIET} {command}.");
+		
+		return ses;
+	}
+
+	gtd->level->quiet++;
+
+	ses = script_driver(ses, LIST_COMMAND, arg1);
+
+	gtd->level->quiet--;
+
+	return ses;
+}
+
+DO_LINE(line_strip)
+{
+	char arg1[BUFFER_SIZE], strip[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_ESC|SUB_COL);
+
+	if (*arg1 == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #LINE {STRIP} {command}.");
+
+		return ses;
+	}
+
+	strip_vt102_codes(arg1, strip);
+
+	ses = script_driver(ses, LIST_COMMAND, strip);
+
+	return ses;
+}
+
+DO_LINE(line_substitute)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], subs[BUFFER_SIZE];
+	int i, flags = 0;
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+	arg = get_arg_in_braces(ses, arg, arg2, GET_ALL);
+
+	if (*arg2 == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #LINE {SUBSTITUTE} {argument} {command}.");
+		
+		return ses;
+	}
+
+	arg = arg1;
+
+	while (*arg)
+	{
+		arg = get_arg_in_braces(ses, arg, subs, GET_ONE);
+
+		for (i = 0 ; *substitution_table[i].name ; i++)
+		{
+			if (is_abbrev(subs, substitution_table[i].name))
+			{
+				SET_BIT(flags, substitution_table[i].bitvector);
+			}
+		}
+
+		if (*arg == COMMAND_SEPARATOR)
+		{
+			arg++;
+		}
+	}
+
+	substitute(ses, arg2, subs, flags);
+
+	ses = script_driver(ses, LIST_COMMAND, subs);
+
+	return ses;
+}
+
+DO_LINE(line_verbatim)
+{
+	char arg1[BUFFER_SIZE];
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ALL);
+
+	if (*arg1 == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #LINE {VERBATIM} {command}.");
+		
+		return ses;
+	}
+
+	gtd->level->verbatim++;
+
+	ses = parse_input(ses, arg1);
+
+	gtd->level->verbatim--;
+
+	return ses;
+}
+
+DO_LINE(line_verbose)
+{
+	char arg1[BUFFER_SIZE];
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ALL);
+
+	if (*arg1 == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #LINE {VERBOSE} {command}.");
+		
+		return ses;
+	}
+
+	gtd->level->verbose++;
+
+	ses = script_driver(ses, LIST_COMMAND, arg1);
+
+	gtd->level->verbose--;
+
+	return ses;
+}
+
+	

+ 617 - 0
list.c

@@ -0,0 +1,617 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2019 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*               (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                  *
+*                                                                             *
+*                     coded by Igor van den Hoven 2004                        *
+******************************************************************************/
+
+
+#include "tintin.h"
+
+
+DO_COMMAND(do_list)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+	struct listnode *node;
+	int index, cnt;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_NST, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		tintin_header(ses, " LIST OPTIONS ");
+
+		for (index = 0 ; *array_table[index].fun ; index++)
+		{
+			if (array_table[index].desc && *array_table[index].name)
+			{
+				tintin_printf2(ses, "  [%-24s] %s", array_table[index].name, array_table[index].desc);
+			}
+		}
+		tintin_header(ses, "");
+	}
+	else if (*arg2 == 0)
+	{
+		show_error(ses, LIST_VARIABLE, "#SYNTAX: #LIST {variable} {option} {argument}");
+	}
+	else
+	{
+		for (cnt = 0 ; *array_table[cnt].name ; cnt++)
+		{
+			if (is_abbrev(arg2, array_table[cnt].name))
+			{
+				break;
+			}
+		}
+
+		if (*array_table[cnt].name == 0)
+		{
+			return do_list(ses, "");
+		}
+		else
+		{
+			if ((node = search_nest_node_ses(ses, arg1)) == NULL)
+			{
+				node = set_nest_node_ses(ses, arg1, "");
+			}
+			array_table[cnt].fun(ses, node, arg, arg1);
+		}
+	}
+	return ses;
+}
+
+DO_ARRAY(array_add)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], *str;
+	int index;
+
+	if (!list->root)
+	{
+		list->root = init_list(ses, LIST_VARIABLE, LIST_SIZE);
+	}
+
+	index = list->root->used + 1;
+
+	while (*arg)
+	{
+		arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+
+		str = arg1;
+
+		while (*str)
+		{
+			str = get_arg_in_braces(ses, str, arg2, GET_ALL);
+
+			set_nest_node(list->root, ntos(index++), "%s", arg2);
+
+			if (*str == COMMAND_SEPARATOR)
+			{
+				str++;
+			}
+		}
+
+		if (*arg == COMMAND_SEPARATOR)
+		{
+			arg++;
+		}
+	}
+	return ses;
+}
+
+DO_ARRAY(array_clear)
+{
+	if (list->root)
+	{
+		free_list(list->root);
+
+		list->root = NULL;
+	}
+
+	set_nest_node_ses(ses, var, "");
+
+	return ses;
+}
+
+DO_ARRAY(array_collapse)
+{
+	int index;
+
+	if (list->root)
+	{
+		str_cpy(&list->arg2, "");
+
+		for (index = 0 ; index < list->root->used ; index++)
+		{
+			str_cat(&list->arg2, list->root->list[index]->arg2);
+		}
+		free_list(list->root);
+
+		list->root = NULL;
+	}
+	return ses;
+}
+
+DO_ARRAY(array_create)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], buf[BUFFER_SIZE], *str;
+
+	int index = 1;
+
+	substitute(ses, arg, buf, SUB_VAR|SUB_FUN);
+
+	arg = buf;
+
+	if (list->root)
+	{
+		free_list(list->root);
+	}
+
+	list->root = init_list(ses, LIST_VARIABLE, LIST_SIZE);
+
+	while (*arg)
+	{
+		arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+
+		str = arg1;
+
+		while (*str)
+		{
+			str = get_arg_in_braces(ses, str, arg2, GET_ALL);
+
+			set_nest_node(list->root, ntos(index++), "%s", arg2);
+
+			if (*str == COMMAND_SEPARATOR)
+			{
+				str++;
+			}
+		}
+
+		if (*arg == COMMAND_SEPARATOR)
+		{
+			arg++;
+		}
+	}
+	return ses;
+}
+
+DO_ARRAY(array_delete)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+	int index, cnt, loop;
+
+	if (list->root)
+	{
+		arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+		arg = get_arg_in_braces(ses, arg, arg2, GET_ALL);
+
+		loop = *arg2 ? (int) get_number(ses, arg2) : 1;
+
+		while (loop--)
+		{
+			index = search_nest_index(list->root, arg1);
+
+			if (atoi(arg1) == 0 || index == -1)
+			{
+				show_error(ses, LIST_VARIABLE, "#LIST DEL: Invalid index: %s", arg1);
+
+				return ses;
+			}
+
+			for (cnt = index + 1 ; cnt < list->root->used ; cnt++)
+			{
+				str_cpy_printf(&list->root->list[cnt]->arg1, "%d", cnt);
+			}
+
+			delete_index_list(list->root, index);
+		}
+	}
+	else
+	{
+		show_error(ses, LIST_VARIABLE, "#LIST DEL: {%s} is not a list.", var);
+	}
+	return ses;
+}
+
+DO_ARRAY(array_explode)
+{
+	char buf[BUFFER_SIZE], tmp[BUFFER_SIZE], *pti;
+	int index = 1;
+
+	if (list->root)
+	{
+		array_collapse(ses, list, "", "");
+	}
+
+	list->root = init_list(ses, LIST_VARIABLE, LIST_SIZE);
+
+	pti = list->arg2;
+
+	while (*pti)
+	{
+		if (HAS_BIT(ses->charset, CHARSET_FLAG_EUC) && is_euc_head(ses, pti))
+		{
+			pti += sprintf(tmp, "%.*s", get_euc_size(ses, pti), pti);
+		}
+		else if (HAS_BIT(ses->charset, CHARSET_FLAG_UTF8) && is_utf8_head(pti))
+		{
+			pti += sprintf(tmp, "%.*s", get_utf8_size(pti), pti);
+		}
+		else
+		{
+			pti += sprintf(tmp, "%c", *pti);
+		}
+
+		set_nest_node(list->root, ntos(index++), "%s", tmp);
+	}
+	sub_arg_in_braces(ses, arg, buf, GET_ALL, SUB_VAR|SUB_FUN);
+
+	pti = buf;
+
+	while (*pti)
+	{
+		if (HAS_BIT(ses->charset, CHARSET_FLAG_EUC) && is_euc_head(ses, pti))
+		{
+			pti += sprintf(tmp, "%.*s", get_euc_size(ses, pti), pti);
+		}
+		else if (HAS_BIT(ses->charset, CHARSET_FLAG_UTF8) && is_utf8_head(pti))
+		{
+			pti += sprintf(tmp, "%.*s", get_utf8_size(pti), pti);
+		}
+		else
+		{
+			pti += sprintf(tmp, "%c", *pti);
+		}
+
+		set_nest_node(list->root, ntos(index++), "%s", tmp);
+	}
+	return ses;
+}
+
+DO_ARRAY(array_find)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+	int index;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg2 == 0)
+	{
+		show_error(ses, LIST_VARIABLE, "#SYNTAX: #LIST {variable} FIND {string} {variable}");
+		
+		return ses;
+	}
+
+	if (list->root)
+	{
+		for (index = 0 ; index < list->root->used ; index++)
+		{
+			if (match(ses, list->root->list[index]->arg2, arg1, SUB_NONE))
+			{
+				break;
+			}
+		}
+		if (index < list->root->used)
+		{
+			set_nest_node_ses(ses, arg2, "%d", index + 1);
+		}
+		else
+		{
+			set_nest_node_ses(ses, arg2, "0");
+		}
+		return ses;
+	}
+	else
+	{
+		set_nest_node_ses(ses, arg2, "0");
+	}
+
+	return ses;
+}
+
+
+DO_ARRAY(array_get)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg2 == 0)
+	{
+		show_error(ses, LIST_VARIABLE, "#SYNTAX: #LIST {variable} GET {index} {variable}");
+		
+		return ses;
+	}
+
+	if (list->root)
+	{
+		int index = search_nest_index(list->root, arg1);
+
+		if (atoi(arg1) == 0 || index == -1)
+		{
+			set_nest_node_ses(ses, arg2, "0");
+		}
+		else
+		{
+			set_nest_node_ses(ses, arg2, "%s", list->root->list[index]->arg2);
+		}
+		return ses;
+	}
+	else
+	{
+		set_nest_node_ses(ses, arg2, "0");
+	}
+
+	return ses;
+}
+
+DO_ARRAY(array_insert)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+	int cnt, index;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (!list->root)
+	{
+		list->root = init_list(ses, LIST_VARIABLE, LIST_SIZE);
+	}
+
+	index = search_nest_index(list->root, arg1);
+
+	if (atoi(arg1) == 0)
+	{
+		show_error(ses, LIST_VARIABLE, "#LIST INS: Invalid index: %s", arg1);
+		
+		return ses;
+	}
+
+	if (index == -1 || atoi(arg1) < 0)
+	{
+		index++;
+	}
+
+	for (cnt = index ; cnt < list->root->used ; cnt++)
+	{
+		str_cpy_printf(&list->root->list[cnt]->arg1, "%d", cnt + 2);
+	}
+
+	set_nest_node(list->root, ntos(index + 1), "%s", arg2);
+
+//	insert_node_list(list->root, ntos(index + 1), arg2, "");
+
+	return ses;
+}
+
+DO_ARRAY(array_simplify)
+{
+	char arg1[BUFFER_SIZE], *str;
+	int index;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+/*
+	if (*arg1 == 0)
+	{
+		show_error(ses, LIST_VARIABLE, "#SYNTAX: #LIST {variable} SIMPLIFY {variable}");
+		
+		return ses;
+	}
+*/
+	if (list->root)
+	{
+		for (index = 0 ; index < list->root->used ; index++)
+		{
+			if (index == 0)
+			{
+				str = str_dup(list->root->list[index]->arg2);
+			}
+			else
+			{
+				str_cat_printf(&str, ";%s", list->root->list[index]->arg2);
+			}
+		}
+		if (*arg1 == 0)
+		{
+			set_nest_node_ses(ses, list->arg1, "%s", str);
+		}
+		else
+		{
+			set_nest_node_ses(ses, arg1, "%s", str);
+		}
+
+		str_free(str);
+
+		return ses;
+	}
+	else
+	{
+		show_error(ses, LIST_VARIABLE, "#LIST SIMPLIFY: {%s} is not a list.", list->arg1);
+	}
+
+	return ses;
+}
+
+DO_ARRAY(array_size)
+{
+	char arg1[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		show_error(ses, LIST_VARIABLE, "#SYNTAX: #LIST {variable} SIZE {variable}");
+		
+		return ses;
+	}
+
+	if (list->root)
+	{
+		set_nest_node_ses(ses, arg1, "%d", list->root->used);
+	}
+	else
+	{
+		set_nest_node_ses(ses, arg1, "0");
+	}
+	return ses;
+}
+
+DO_ARRAY(array_set)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (list->root)
+	{
+		int index = search_nest_index(list->root, arg1);
+
+		if (atoi(arg1) == 0 || index == -1)
+		{
+			show_error(ses, LIST_VARIABLE, "#LIST SET: Invalid index: %s", arg1);
+			
+			return ses;
+		}
+
+		set_nest_node(list->root, ntos(index + 1), "%s", arg2);
+
+//		RESTRING(list->root->list[index]->arg2, arg2);
+
+		return ses;
+	}
+
+	show_error(ses, LIST_VARIABLE, "#LIST SET: {%s} is not a list.", var);
+
+	return ses;
+}
+
+DO_ARRAY(array_shuffle)
+{
+	char *swap;
+	int cnt, rnd;
+
+	if (!list->root)
+	{
+		list->root = init_list(ses, LIST_VARIABLE, LIST_SIZE);
+	}
+
+	for (cnt = 0 ; cnt < list->root->used ; cnt++)
+	{
+		rnd = generate_rand(ses) % list->root->used;
+
+		swap = list->root->list[cnt]->arg2;
+		list->root->list[cnt]->arg2 = list->root->list[rnd]->arg2;
+		list->root->list[rnd]->arg2 = swap;
+	}
+	return ses;
+}
+
+DO_ARRAY(array_sort)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], arg3[BUFFER_SIZE], *str;
+	int cnt;
+
+	if (!list->root)
+	{
+		list->root = init_list(ses, LIST_VARIABLE, LIST_SIZE);
+	}
+
+	while (*arg)
+	{
+		arg = sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+
+		str = arg1;
+
+		while (*str)
+		{
+			str = get_arg_in_braces(ses, str, arg2, GET_ALL);
+
+			for (cnt = 0 ; cnt < list->root->used ; cnt++)
+			{
+				if (strcmp(arg2, list->root->list[cnt]->arg2) <= 0)
+				{
+					break;
+				}
+			}
+
+			if (cnt == list->root->used)
+			{
+				sprintf(arg3, "{%d} {%s}", -1, arg2);
+			}
+			else
+			{
+				sprintf(arg3, "{%d} {%s}", cnt + 1, arg2);
+			}
+
+			array_insert(ses, list, arg3, var);
+
+			if (*str == COMMAND_SEPARATOR)
+			{
+				str++;
+			}
+		}
+		if (*arg == COMMAND_SEPARATOR)
+		{
+			arg++;
+		}
+	}
+	return ses;
+}
+
+DO_ARRAY(array_tokenize)
+{
+	char buf[BUFFER_SIZE], tmp[BUFFER_SIZE];
+	int index = 1, i;
+
+	sub_arg_in_braces(ses, arg, buf, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (list->root)
+	{
+		free_list(list->root);
+	}
+
+	list->root = init_list(ses, LIST_VARIABLE, LIST_SIZE);
+
+	i = 0;
+
+	while (buf[i] != 0)
+	{
+		if (HAS_BIT(ses->charset, CHARSET_FLAG_EUC) && is_euc_head(ses, &buf[i]))
+		{
+			i += sprintf(tmp, "%.*s", get_euc_size(ses, &buf[i]), &buf[i]);
+		}
+		else if (HAS_BIT(ses->charset, CHARSET_FLAG_UTF8) && is_utf8_head(&buf[i]))
+		{
+			i += sprintf(tmp, "%.*s", get_utf8_size(&buf[i]), &buf[i]);
+		}
+		else
+		{
+			i += sprintf(tmp, "%c", buf[i]);
+		}
+
+		set_nest_node(list->root, ntos(index++), "%s", tmp);
+	}
+	return ses;
+}

+ 543 - 0
log.c

@@ -0,0 +1,543 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2004                       *
+******************************************************************************/
+
+#include "tintin.h"
+
+
+void logit(struct session *ses, char *txt, FILE *file, int flags)
+{
+	char out[BUFFER_SIZE];
+
+	push_call("logit(%p,%p,%p,%d)",ses,txt,file,flags);
+
+	if (HAS_BIT(ses->logmode, LOG_FLAG_PLAIN))
+	{
+		strip_vt102_codes(txt, out);
+	}
+	else if (HAS_BIT(ses->logmode, LOG_FLAG_HTML))
+	{
+		vt102_to_html(ses, txt, out);
+	}
+	else
+	{
+		strcpy(out, txt);
+	}
+
+	if (HAS_BIT(flags, LOG_FLAG_LINEFEED))
+	{
+		strcat(out, "\n");
+	}
+	fputs(out, file);
+
+	fflush(file);
+
+	pop_call();
+	return;
+}
+
+void loginit(struct session *ses, FILE *file, int flags)
+{
+	push_call("loginit(%p,%p,%d)",ses,file,flags);
+
+	if (HAS_BIT(flags, LOG_FLAG_APPEND))
+	{
+		if (HAS_BIT(flags, LOG_FLAG_HTML))
+		{
+			fseek(file, 0, SEEK_END);
+
+			if (ftell(file) == 0)
+			{
+				write_html_header(ses, file);
+			}
+		}
+	}
+	else if (HAS_BIT(flags, LOG_FLAG_OVERWRITE) && HAS_BIT(flags, LOG_FLAG_HTML))
+	{
+		if (HAS_BIT(ses->logmode, LOG_FLAG_HTML))
+		{
+			write_html_header(ses, file);
+		}
+	}
+	pop_call();
+	return;
+}
+
+DO_COMMAND(do_log)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ALL, SUB_VAR|SUB_FUN|SUB_ESC);
+
+	if (*arg1 == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #LOG {APPEND|OVERWRITE|OFF} {<FILENAME>}");
+	}
+	else if (is_abbrev(arg1, "APPEND") && *arg2 != 0)
+	{
+		if (ses->logfile)
+		{
+			fclose(ses->logfile);
+		}
+
+		if ((ses->logfile = fopen(arg2, "a")))
+		{
+			SET_BIT(ses->logmode, LOG_FLAG_APPEND);
+
+			loginit(ses, ses->logfile, ses->logmode);
+
+			show_message(ses, LIST_COMMAND, "#LOG: LOGGING OUTPUT TO '%s' FILESIZE: %ld", arg2, ftell(ses->logfile));
+		}
+		else
+		{
+			show_error(ses, LIST_COMMAND, "#ERROR: #LOG {%s} {%s} - COULDN'T OPEN FILE.", arg1, arg2);
+		}
+	}
+	else if (is_abbrev(arg1, "OVERWRITE") && *arg2)
+	{
+		if (ses->logfile)
+		{
+			fclose(ses->logfile);
+		}
+
+		if ((ses->logfile = fopen(arg2, "w")))
+		{
+			SET_BIT(ses->logmode, LOG_FLAG_OVERWRITE);
+
+			loginit(ses, ses->logfile, ses->logmode);
+
+			show_message(ses, LIST_COMMAND, "#LOG: LOGGING OUTPUT TO '%s'", arg2);
+		}
+		else
+		{
+			show_error(ses, LIST_COMMAND, "#ERROR: #LOG {%s} {%s} - COULDN'T OPEN FILE.", arg1, arg2);
+		}
+	}
+	else if (is_abbrev(arg1, "OFF"))
+	{
+		if (ses->logfile)
+		{
+			DEL_BIT(ses->logmode, LOG_FLAG_APPEND|LOG_FLAG_OVERWRITE);
+
+			fclose(ses->logfile);
+			ses->logfile = NULL;
+			show_message(ses, LIST_COMMAND, "#LOG: LOGGING TURNED OFF.");
+		}
+		else
+		{
+			show_message(ses, LIST_COMMAND, "#LOG: LOGGING ALREADY TURNED OFF.");
+		}
+	}
+	else
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #LOG {APPEND|OVERWRITE|OFF} {<FILENAME>}");
+	}
+	return ses;
+}
+
+void write_html_header(struct session *ses, FILE *fp)
+{
+	char header[BUFFER_SIZE];
+	
+		sprintf(header, "<html>\n"
+		"<head>\n"
+		"<meta http-equiv='content-type' content='text/html; charset=%s'>\n"
+		"<meta name='viewport' content='width=device-width, initial-scale=1.0'>\n"
+		"<meta name='description' content='Generated by TinTin++ "CLIENT_VERSION" - http://tintin.sourceforge.net'>\n"
+		"<style type='text/css'>\n"
+		"body {font-family:Consolas;font-size:12pt;}\n"
+		"a {text-decoration:none;}\n"
+		"a:link {color:#06b;}\n"
+		"a:visited {color:#6b0;}\n"
+		"a:hover {text-decoration:underline;}\n"
+		"a:active {color:#b06;}\n"
+		".d30{ color: #000; } .l30{ color: #555; } .b40{ background-color: #000; } .b50{ background-color: #555 }\n"
+		".d31{ color: #B00; } .l31{ color: #F55; } .b41{ background-color: #B00; } .b51{ background-color: #F55 }\n"
+		".d32{ color: #0B0; } .l32{ color: #5F5; } .b42{ background-color: #0B0; } .b52{ background-color: #5F5 }\n"
+		".d33{ color: #BB0; } .l33{ color: #FF5; } .b43{ background-color: #BB0; } .b53{ background-color: #FF5 }\n"
+		".d34{ color: #00B; } .l34{ color: #55F; } .b44{ background-color: #00B; } .b54{ background-color: #55F }\n"
+		".d35{ color: #B0B; } .l35{ color: #F5F; } .b45{ background-color: #B0B; } .b55{ background-color: #F5F }\n"
+		".d36{ color: #0BB; } .l36{ color: #5FF; } .b46{ background-color: #0BB; } .b56{ background-color: #5FF }\n"
+		".d37{ color: #BBB; } .l37{ color: #FFF; } .b47{ background-color: #BBB; } .b57{ background-color: #FFF }\n"
+		".d38{ color: #FFF; } .l38{ color: #FFF; } .b48{ background-color: #000; } .b58{ background-color: #000 }\n"
+		".d39{ color: #FFF; } .l39{ color: #FFF; } .b49{ background-color: #000; } .b59{ background-color: #000 }\n"
+		"</style>\n"
+		"<body bgcolor='#000000'>\n"
+		"</head>\n"
+		"<pre>\n"
+		"<span class='b49'><span class='d39'>\n",
+		HAS_BIT(gtd->ses->charset, CHARSET_FLAG_UTF8) ? "utf-8" : 
+			HAS_BIT(ses->charset, CHARSET_FLAG_BIG5) ? "big5" : 
+				HAS_BIT(ses->charset, CHARSET_FLAG_GBK1) ? "gb18030" : "iso-8859-1");
+
+	fputs(header, fp);
+}
+
+
+void vt102_to_html(struct session *ses, char *txt, char *out)
+{
+	char tmp[BUFFER_SIZE], *pti, *pto;
+	char xtc[] = { '0', '6', '8', 'B', 'D', 'F' };
+	int vtc, fgc, bgc, cnt;
+	int rgb[6] = { 0, 0, 0, 0, 0, 0 };
+
+	vtc = ses->vtc;
+	fgc = ses->fgc;
+	bgc = ses->bgc;
+
+	pti = txt;
+	pto = out;
+
+	while (*pti)
+	{
+		while (skip_vt102_codes_non_graph(pti))
+		{
+			pti += skip_vt102_codes_non_graph(pti);
+		}
+
+		switch (*pti)
+		{
+			case 27:
+				pti += 2;
+
+				for (cnt = 0 ; pti[cnt] ; cnt++)
+				{
+					tmp[cnt] = pti[cnt];
+
+					if (pti[cnt] == ';' || pti[cnt] == 'm')
+					{
+						tmp[cnt] = 0;
+
+						cnt = -1;
+						pti += 1 + strlen(tmp);
+
+						if (HAS_BIT(vtc, COL_XTF_R))
+						{
+							fgc = URANGE(0, atoi(tmp), 255);
+							DEL_BIT(vtc, COL_XTF_R);
+							SET_BIT(vtc, COL_XTF);
+						}
+						else if (HAS_BIT(vtc, COL_XTB_R))
+						{
+							bgc = URANGE(0, atoi(tmp), 255);
+							DEL_BIT(vtc, COL_XTB_R);
+							SET_BIT(vtc, COL_XTB);
+						}
+						else if (HAS_BIT(vtc, COL_TCF_R))
+						{
+							if (rgb[0] == 256)
+							{
+								rgb[0] = URANGE(0, atoi(tmp), 255);
+							}
+							else if (rgb[1] == 256)
+							{
+								rgb[1] = URANGE(0, atoi(tmp), 255);
+							}
+							else if (rgb[2] == 256)
+							{
+								rgb[2] = URANGE(0, atoi(tmp), 255);
+
+								fgc = rgb[0] * 256 * 256 + rgb[1] * 256 + rgb[2];
+	
+								DEL_BIT(vtc, COL_TCF_R);
+								SET_BIT(vtc, COL_TCF);
+							}
+						}
+						else if (HAS_BIT(vtc, COL_TCB_R))
+						{
+							if (rgb[3] == 256)
+							{
+								rgb[3] = URANGE(0, atoi(tmp), 255);
+							}
+							else if (rgb[4] == 256)
+							{
+								rgb[4] = URANGE(0, atoi(tmp), 255);
+							}
+							else if (rgb[5] == 256)
+							{
+								rgb[5] = URANGE(0, atoi(tmp), 255);
+
+								bgc = rgb[3] * 256 * 256 + rgb[4] * 256 + rgb[5];
+
+								DEL_BIT(vtc, COL_TCB_R);
+								SET_BIT(vtc, COL_TCB);
+							}
+						}
+						else
+						{
+							switch (atoi(tmp))
+							{
+								case 0:
+									vtc = 0;
+									fgc = 39;
+									bgc = 49;
+									break;
+								case 1:
+									SET_BIT(vtc, COL_BLD);
+									break;
+								case 2:
+									if (HAS_BIT(vtc, COL_TCF_2))
+									{
+										DEL_BIT(vtc, COL_XTF_5|COL_TCF_2);
+										SET_BIT(vtc, COL_TCF_R);
+										rgb[0] = 256; rgb[1] = 256; rgb[2] = 256;
+									}
+									else if (HAS_BIT(vtc, COL_TCB_2))
+									{
+										DEL_BIT(vtc, COL_XTB_5|COL_TCF_2);
+										SET_BIT(vtc, COL_TCB_R);
+										rgb[3] = 256; rgb[4] = 256; rgb[5] = 256;
+									}
+									else
+									{
+										DEL_BIT(vtc, COL_BLD);
+									}
+									break;
+								case 5:
+									if (HAS_BIT(vtc, COL_XTF_5))
+									{
+										DEL_BIT(vtc, COL_XTF_5|COL_TCF_2);
+										SET_BIT(vtc, COL_XTF_R);
+									}
+									else if (HAS_BIT(vtc, COL_XTB_5))
+									{
+										DEL_BIT(vtc, COL_XTB_5|COL_TCF_2);
+										SET_BIT(vtc, COL_XTB_R);
+									}
+									break;
+								case 7:
+									SET_BIT(vtc, COL_REV);
+									break;
+								case 21:
+								case 22:
+									DEL_BIT(vtc, COL_BLD);
+									break;
+								case 27:
+									DEL_BIT(vtc, COL_REV);
+									break;
+								case 38:
+									SET_BIT(vtc, COL_XTF_5|COL_TCF_2);
+									fgc = 38;
+									break;
+								case 48:
+									SET_BIT(vtc, COL_XTB_5|COL_TCB_2);
+									bgc = 48;
+									break;
+								default:
+									switch (atoi(tmp) / 10)
+									{
+										case 3:
+										case 9:
+											DEL_BIT(vtc, COL_XTF|COL_TCF);
+											break;
+										case 4:
+										case 10:
+											DEL_BIT(vtc, COL_XTB|COL_TCB);
+											break;
+									}
+									if (atoi(tmp) / 10 == 4)
+									{
+										bgc = atoi(tmp);
+									}
+									if (atoi(tmp) / 10 == 10)
+									{
+										bgc = atoi(tmp) - 50;
+									}
+
+									if (atoi(tmp) / 10 == 3)
+									{
+										fgc = atoi(tmp);
+									}
+									if (atoi(tmp) / 10 == 9)
+									{
+										SET_BIT(vtc, COL_BLD);
+
+										fgc = atoi(tmp) - 60;
+									}
+									break;
+							}
+						}
+					}
+
+					if (pti[-1] == 'm')
+					{
+						break;
+					}
+				}
+
+				if (!HAS_BIT(vtc, COL_REV) && HAS_BIT(ses->vtc, COL_REV))
+				{
+					cnt = fgc;
+					fgc = ses->fgc = bgc - 10;
+					bgc = ses->bgc = cnt + 10;
+				}
+
+				if (bgc != ses->bgc || fgc != ses->fgc || vtc != ses->vtc)
+				{
+					sprintf(pto, "</span>");
+					pto += strlen(pto);
+
+					if (bgc != ses->bgc)
+					{
+						if (HAS_BIT(vtc, COL_XTB))
+						{
+							if (bgc < 8)
+							{
+								sprintf(pto, "</span><span class='b%d'>", 40+bgc);
+							}
+							else if (bgc < 16)
+							{
+								sprintf(pto, "</span><span class='b%d'>", 50+bgc-8);
+							}
+							else if (bgc < 232)
+							{
+								sprintf(pto, "</span><span style='background-color: #%c%c%c;'>", xtc[(bgc-16) / 36], xtc[(bgc-16) % 36 / 6], xtc[(bgc-16) % 6]);
+							}
+							else
+							{
+								sprintf(pto, "</span><span style='background-color: rgb(%d,%d,%d);'>", (bgc-232) * 10 + 8, (bgc-232) * 10 + 8,(bgc-232) * 10 + 8);
+							}
+						}
+						else if (HAS_BIT(vtc, COL_TCB))
+						{
+							sprintf(pto, "</span><span style='background-color: rgb(%d,%d,%d);'>", rgb[3], rgb[4], rgb[5]);
+						}
+						else
+						{
+							sprintf(pto, "</span><span class='b%d'>", bgc);
+						}
+						pto += strlen(pto);
+					}
+
+					if (HAS_BIT(vtc, COL_XTF))
+					{
+						if (fgc < 8)
+						{
+							sprintf(pto, "<span class='d%d'>", 30+fgc);
+						}
+						else if (fgc < 16)
+						{
+							sprintf(pto, "<span class='l%d'>", 30+fgc-8);
+						}
+						else if (fgc < 232)
+						{
+							sprintf(pto, "<span style='color: #%c%c%c;'>", xtc[(fgc-16) / 36], xtc[(fgc-16) % 36 / 6], xtc[(fgc-16) % 6]);
+						}
+						else
+						{
+							sprintf(pto, "<span style='color: rgb(%d,%d,%d);'>", (fgc-232) * 10 + 8, (fgc-232) * 10 + 8,(fgc-232) * 10 + 8);
+						}
+					}
+					else if (HAS_BIT(vtc, COL_TCF))
+					{
+						sprintf(pto, "<span style='color: rgb(%d,%d,%d);'>", fgc / 256 / 256, fgc / 256 % 256, fgc % 256);
+					}
+					else
+					{
+						if (HAS_BIT(vtc, COL_BLD))
+						{
+							sprintf(pto, "<span class='l%d'>", fgc);
+						}
+						else
+						{
+							sprintf(pto, "<span class='d%d'>", fgc);
+						}
+					}
+					pto += strlen(pto);
+				}
+
+				if (HAS_BIT(vtc, COL_REV) && !HAS_BIT(ses->vtc, COL_REV))
+				{
+					cnt = fgc;
+					fgc = ses->fgc = bgc - 10;
+					bgc = ses->bgc = cnt + 10;
+				}
+
+				ses->vtc = vtc;
+				ses->fgc = fgc;
+				ses->bgc = bgc;
+				break;
+
+			case  6:
+				*pto++ = '&';
+				pti++;
+				break;
+
+			case 28:
+				*pto++ = '<';
+				pti++;
+				break;
+
+			case 30:
+				*pto++ = '>';
+				pti++;
+				break;
+
+			case '>':
+				sprintf(pto, "&gt;");
+				pto += strlen(pto);
+				pti++;
+				break;
+
+			case '<':
+				sprintf(pto, "&lt;");
+				pto += strlen(pto);
+				pti++;
+				break;
+
+			case '"':
+				sprintf(pto, "&quot;");
+				pto += strlen(pto);
+				pti++;
+				break;
+
+			case '&':
+				sprintf(pto, "&amp;");
+				pto += strlen(pto);
+				pti++;
+				break;
+
+			case '$':
+				sprintf(pto, "&dollar;");
+				pto += strlen(pto);
+				pti++;
+				break;
+
+			case '\\':
+				sprintf(pto, "&bsol;");
+				pto += strlen(pto);
+				pti++;
+				break;
+			case 0:
+				break;
+
+			default:
+				*pto++ = *pti++;
+				break;
+		}
+	}
+	*pto = 0;
+}

+ 800 - 0
main.c

@@ -0,0 +1,800 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2019 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
+*                                                                             *
+*                         coded by Peter Unold 1992                           *
+*                    recoded by Igor van den Hoven 2004                       *
+******************************************************************************/
+
+#include "tintin.h"
+
+#include <signal.h>
+
+/*************** globals ******************/
+
+struct session *gts;
+struct tintin_data *gtd;
+
+void pipe_handler(int signal)
+{
+	syserr_printf(gtd->ses, "pipe_handler");
+}
+
+void xfsz_handler(int signal)
+{
+	syserr_printf(gtd->ses, "xfsz_handler");
+}
+
+void hub_handler(int signal)
+{
+	syserr_printf(gtd->ses, "hub_handler");
+}
+
+void ttin_handler(int signal)
+{
+	syserr_printf(gtd->ses, "ttin_handler");
+}
+
+void ttou_handler(int signal)
+{
+	syserr_printf(gtd->ses, "ttou_handler");
+}
+
+/*
+	when the screen size changes, take note of it
+*/
+
+void winch_handler(int signal)
+{
+	struct session *ses;
+
+	init_terminal_size(gts);
+
+	for (ses = gts->next ; ses ; ses = ses->next)
+	{
+		init_terminal_size(ses);
+
+		if (HAS_BIT(ses->telopts, TELOPT_FLAG_NAWS))
+		{
+			client_send_sb_naws(ses, 0, NULL);
+		}
+	}
+
+	winch_daemon();
+}
+
+
+void abort_handler(int signal)
+{
+	syserr_fatal(signal, "abort_handler");
+}
+
+void child_handler(int signal)
+{
+	return;
+	syserr_printf(gtd->ses, "child_handler");
+
+//	syserr_fatal(signal, "child_handler");
+}
+
+void interrupt_handler(int signal)
+{
+	if (gtd->ses->connect_retry > utime())
+	{
+		gtd->ses->connect_retry = 0;
+	}
+	else if (HAS_BIT(gtd->ses->telopts, TELOPT_FLAG_SGA) && !HAS_BIT(gtd->ses->telopts, TELOPT_FLAG_ECHO))
+	{
+		socket_printf(gtd->ses, 1, "%c", 4);
+	}
+	else if (gtd->attach_sock)
+	{
+		gtd->attach_sock = close(gtd->attach_sock);
+
+		show_message(gtd->ses, LIST_COMMAND, "#REDETACHING PROCESS TO {%s}", gtd->attach_file);
+	}
+	else
+	{
+		cursor_delete_or_exit(gtd->ses, "");
+	}
+}
+
+void suspend_handler(int signal)
+{
+	show_message(gtd->ses, LIST_COMMAND, "#SIGNAL: SIGTSTP");
+
+	if (gtd->attach_sock)
+	{
+		show_message(gtd->ses, LIST_COMMAND, "#DAEMON {%s} WANTS TO DETACH.", gtd->attach_file);
+
+		gtd->attach_sock = close(gtd->attach_sock);
+
+		return;
+	}
+}
+
+void trap_handler(int signal)
+{
+	syserr_fatal(signal, "trap_handler");
+}
+
+
+/****************************************************************************/
+/* main() - show title - setup signals - init lists - readcoms - mainloop() */
+/****************************************************************************/
+
+
+int main(int argc, char **argv)
+{
+	int c, i = 0, greeting = 0;
+	char filename[256];
+	char arg[BUFFER_SIZE];
+
+	#ifdef SOCKS
+		SOCKSinit(argv[0]);
+	#endif
+
+	if (signal(SIGTERM, trap_handler) == BADSIG)
+	{
+		syserr_fatal(-1, "signal SIGTERM");
+	}
+
+	if (signal(SIGSEGV, trap_handler) == BADSIG)
+	{
+		syserr_fatal(-1, "signal SIGSEGV");
+	}
+
+	if (signal(SIGHUP, trap_handler) == BADSIG)
+	{
+		syserr_fatal(-1, "signal SIGHUP");
+	}
+
+	if (signal(SIGABRT, abort_handler) == BADSIG)
+	{
+		syserr_fatal(-1, "signal SIGTERM");
+	}
+
+/*	if (signal(SIGCHLD, child_handler) == BADSIG)
+	{
+		syserr_fatal(-1, "signal SIGCHLD");
+	}
+*/
+/*
+	if (signal(SIGINT, interrupt_handler) == BADSIG)
+	{
+		syserr_fatal(-1, "signal SIGINT");
+	}
+*/
+
+	if (signal(SIGTSTP, suspend_handler) == BADSIG)
+	{
+		syserr_fatal(-1, "signal SIGTSTP");
+	}
+/*
+	if (signal(SIGPIPE, pipe_handler) == BADSIG)
+	{
+		syserr_fatal(-1, "signal SIGPIPE");
+	}
+
+	if (signal(SIGXFSZ, xfsz_handler) == BADSIG)
+	{
+		syserr_fatal(-1, "signal SIGXFSZ");
+	}
+
+	if (signal(SIGHUP, hub_handler) == BADSIG)
+	{
+		syserr_fatal(-1, "signal SIGHUP");
+	}
+
+	if (signal(SIGTTIN, hub_handler) == BADSIG)
+	{
+		syserr_fatal(-1, "signal SIGTTIN");
+	}
+
+	if (signal(SIGTTOU, hub_handler) == BADSIG)
+	{
+		syserr_fatal(-1, "signal SIGTTOU");
+	}
+*/
+	if (signal(SIGWINCH, winch_handler) == BADSIG)
+	{
+		syserr_fatal(-1, "signal SIGWINCH");
+	}
+
+	signal(SIGPIPE, SIG_IGN);
+
+	for (c = 0 ; c < argc ; c++)
+	{
+		if (c)
+		{
+			cat_sprintf(arg, " %s", argv[c]);
+		}
+		{
+			strcpy(arg, argv[0]);
+		}
+	}
+
+	if (argc > 1)
+	{
+		while ((c = getopt(argc, argv, "a: e: G h M:: r: R:: s t: T v V")) != EOF)
+		{
+			switch (c)
+			{
+				case 'h':
+					printf("Usage: %s [OPTION]... [FILE]...\n", argv[0]);
+					printf("\n");
+					printf("  -a  Set argument for PROGRAM START event.\n");
+					printf("  -e  Execute given command.\n");
+					printf("  -G  Don't show the greeting screen.\n");
+					printf("  -h  This help section.\n");
+					printf("  -M  Matrix Digital Rain.\n");
+					printf("  -r  Read given file.\n");
+					printf("  -s  Enable screen reader mode.\n");
+					printf("  -t  Set given title.\n");
+					printf("  -T  Don't set the default title.\n");
+					printf("  -v  Enable verbose mode.\n");
+					printf("  -V  Show version information.\n");
+
+					exit(1);
+
+				case 'M':
+				case 'G':
+					SET_BIT(greeting, STARTUP_FLAG_NOGREETING);
+					break;
+
+				case 's':
+					SET_BIT(greeting, STARTUP_FLAG_SCREENREADER);
+					break;
+
+				case 'V':
+					printf("\nTinTin++ " CLIENT_VERSION "\n");
+					printf("\n(C) 2004-2019 Igor van den Hoven\n");
+					printf("\nLicense GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.\n\n");
+					exit(1);
+			}
+		}
+	}
+
+	init_tintin(greeting);
+
+	sprintf(filename, "%s/%s", gtd->home, TINTIN_DIR);
+
+	if (mkdir(filename, 0777) || errno == EEXIST)
+	{
+		sprintf(filename, "%s/%s/%s", gtd->home, TINTIN_DIR, HISTORY_FILE);
+
+		if (access(filename, F_OK ) != -1)
+		{
+			history_read(gts, filename);
+		}
+	}
+
+	RESTRING(gtd->vars[1], argv[0]);
+
+	if (argc > 1)
+	{
+		optind = 1;
+
+		RESTRING(gtd->vars[2], argv[1]);
+
+		while ((c = getopt(argc, argv, "a: e: G h M:: r: R:: s t: T v")) != EOF)
+		{
+			switch (c)
+			{
+				case 'a':
+					RESTRING(gtd->vars[0], argv[2]);
+					SET_BIT(greeting, STARTUP_FLAG_ARGUMENT);
+					break;
+
+				case 'e':
+					gtd->level->input++;
+					gtd->ses = script_driver(gtd->ses, LIST_COMMAND, optarg);
+					gtd->level->input--;
+					break;
+
+				case 'G':
+					break;
+
+				case 'M':
+					do_test(gts, optarg ? optarg : "");
+					break;
+
+				case 'r':
+					gtd->level->input++;
+					gtd->ses = do_read(gtd->ses, optarg);
+					gtd->level->input--;
+					break;
+
+				case 'R':
+					SET_BIT(gtd->flags, TINTIN_FLAG_DAEMONIZE);
+					daemon_attach(gtd->ses, optarg ? optarg : "");
+					break;
+
+				case 's':
+					break;
+
+				case 't':
+					SET_BIT(greeting, STARTUP_FLAG_NOTITLE);
+					print_stdout("\e]0;%s\007", optarg);
+					break;
+
+				case 'T':
+					SET_BIT(greeting, STARTUP_FLAG_NOTITLE);
+					break;
+
+				case 'v':
+					do_configure(gtd->ses, "{VERBOSE} {ON}");
+					break;
+
+				default:
+//					tintin_printf2(NULL, "Unknown option '%c'.", c);
+					break;;
+			}
+		}
+	}
+
+	if (!HAS_BIT(greeting, STARTUP_FLAG_NOTITLE))
+	{
+		do_screen(gts, "LOAD BOTH");
+		do_screen(gts, "SAVE BOTH");
+		do_screen(gts, "SET BOTH TinTin++");
+	}
+
+	gtd->exec = strdup(argv[0]);
+
+	if (argc > 2)
+	{
+		RESTRING(gtd->vars[3], argv[2]);
+
+		for (i = 3 ; i <= optind ; i++)
+		{
+			RESTRING(gtd->vars[i+1], argv[i] ? argv[i] : "");
+		}
+
+		arg[0] = 0;
+
+		for (i = optind + 1 ; i < argc ; i++)
+		{
+			if (*arg)
+			{
+				strcat(arg, " ");
+			}
+			strcat(arg, argv[i]);
+
+			if (i < 100)
+			{
+				RESTRING(gtd->vars[i+1], argv[i]);
+			}
+		}
+
+		if (!HAS_BIT(greeting, STARTUP_FLAG_ARGUMENT))
+		{
+			RESTRING(gtd->vars[0], arg);
+		}
+	}
+
+	if (argv[optind] != NULL)
+	{
+		if (!strncasecmp(argv[optind], "telnet://", 9))
+		{
+			do_session(gts, argv[optind]);
+		}
+		else
+		{
+			gtd->level->input++;
+
+			gtd->ses = do_read(gtd->ses, argv[optind]);
+
+			gtd->level->input--;
+		}
+	}
+
+	check_all_events(gts, SUB_ARG, 0, 0, "PROGRAM START");
+
+	mainloop();
+
+	return 0;
+}
+
+void init_tintin(int greeting)
+{
+	int ref, index;
+
+	push_call("init_tintin(%d)",greeting);
+
+	gtd                 = (struct tintin_data *) calloc(1, sizeof(struct tintin_data));
+
+	gtd->level          = (struct level_data *) calloc(1, sizeof(struct level_data));
+
+	gtd->memory         = calloc(1, sizeof(struct str_data));
+
+	gtd->buf            = str_alloc(STRING_SIZE);
+	gtd->out            = str_alloc(STRING_SIZE);
+
+	gtd->flags          = TINTIN_FLAG_INHERITANCE;
+
+	gtd->mccp_len       = 10000;
+	gtd->mccp_buf       = (unsigned char *) calloc(1, gtd->mccp_len);
+
+	gtd->mud_output_max = 16384;
+	gtd->mud_output_buf = (char *) calloc(1, gtd->mud_output_max);
+
+	gtd->input_off      = 1;
+
+	gtd->os             = strdup(getenv("OS")   ? getenv("OS")   : "UNKNOWN");
+	gtd->home           = strdup(getenv("HOME") ? getenv("HOME") : "~/");
+	gtd->lang           = strdup(getenv("LANG") ? getenv("LANG") : "UNKNOWN");
+	gtd->term           = strdup(getenv("TERM") ? getenv("TERM") : "UNKNOWN");
+
+	gtd->detach_file    = strdup("");
+	gtd->attach_file    = strdup("");
+
+	gtd->time           = time(NULL);
+	gtd->calendar       = *localtime(&gtd->time);
+
+	for (index = 0 ; index < 100 ; index++)
+	{
+		gtd->vars[index] = strdup("");
+		gtd->cmds[index] = strdup("");
+	}
+
+	for (ref = 0 ; ref < 26 ; ref++)
+	{
+		for (index = 0 ; *command_table[index].name != 0 ; index++)
+		{
+			if (index && strcmp(command_table[index - 1].name, command_table[index].name) > 0)
+			{
+				print_stdout("\e[1;31minit_tintin() unsorted command table %s vs %s.", command_table[index - 1].name, command_table[index].name);
+			}
+
+			if (*command_table[index].name == 'a' + ref)
+			{
+				gtd->command_ref[ref] = index;
+				break;
+			}
+		}
+	}
+
+	for (index = 1 ; index ; index++)
+	{
+		if (*event_table[index].name == 0)
+		{
+			break;
+		}
+
+		if (strcmp(event_table[index - 1].name, event_table[index].name) > 0)
+		{
+			print_stdout("\e[1;31minit_tintin() unsorted event table %s vs %s.", event_table[index - 1].name, event_table[index].name);
+
+			break;
+		}
+	}
+
+	gtd->screen = calloc(1, sizeof(struct screen_data));
+
+	gtd->screen->rows   = SCREEN_HEIGHT;
+	gtd->screen->cols   = SCREEN_WIDTH;
+	gtd->screen->height = SCREEN_HEIGHT * 16;
+	gtd->screen->width  = SCREEN_WIDTH * 10;
+	gtd->screen->focus  = 1;
+
+	init_msdp_table();
+
+
+
+	// global tintin session
+
+	gts = (struct session *) calloc(1, sizeof(struct session));
+
+	gts->name           = strdup("gts");
+	gts->group          = strdup("");
+	gts->session_host   = strdup("");
+	gts->session_ip     = strdup("");
+	gts->session_port   = strdup("");
+	gts->cmd_color      = strdup("");
+	gts->telopts        = TELOPT_FLAG_ECHO;
+	gts->flags          = SES_FLAG_MCCP;
+	gts->socket         = 1;
+	gts->read_max       = 16384;
+	gts->lognext_name   = strdup("");
+	gts->logline_name   = strdup("");
+
+	gtd->ses = gts;
+
+	for (index = 0 ; index < LIST_MAX ; index++)
+	{
+		gts->list[index] = init_list(gts, index, 32);
+	}
+
+	gts->split  = calloc(1, sizeof(struct split_data));
+	gts->scroll = calloc(1, sizeof(struct scroll_data));
+
+	init_local(gts);
+
+	init_terminal_size(gts);
+
+	gtd->level->input++;
+
+	do_class(gts, "{CONFIG} {OPEN}");
+
+	do_configure(gts, "{AUTO TAB}         {5000}");
+	do_configure(gts, "{BUFFER SIZE}     {10000}");
+	do_configure(gts, "{COLOR MODE}         {ON}");
+	do_configure(gts, "{COLOR PATCH}       {OFF}");
+	do_configure(gts, "{COMMAND COLOR}   {<078>}");
+	do_configure(gts, "{COMMAND ECHO}       {ON}");
+	do_configure(gts, "{CONNECT RETRY}       {0}");
+	do_configure(gts, "{CHARSET}          {AUTO}");
+	do_configure(gts, "{HISTORY SIZE}     {1000}");
+	do_configure(gts, "{LOG MODE}          {RAW}");
+	do_configure(gts, "{MOUSE TRACKING}    {OFF}");
+	do_configure(gts, "{PACKET PATCH}     {AUTO}");
+	do_configure(gts, "{RANDOM SEED}      {AUTO}");
+	do_configure(gts, "{REPEAT CHAR}         {!}");
+	do_configure(gts, "{REPEAT ENTER}      {OFF}");
+	do_configure(gts, HAS_BIT(greeting, STARTUP_FLAG_SCREENREADER) ? "{SCREEN READER} {ON}" : "{SCREEN READER} {OFF}");
+	do_configure(gts, "{SCROLL LOCK}        {ON}");
+	do_configure(gts, "{SPEEDWALK}         {OFF}");
+	do_configure(gts, "{TAB WIDTH}        {AUTO}");
+	do_configure(gts, "{TELNET}             {ON}");
+	do_configure(gts, "{TINTIN CHAR}         {#}");
+	do_configure(gts, "{VERBATIM}          {OFF}");
+	do_configure(gts, "{VERBATIM CHAR}      {\\}");
+	do_configure(gts, "{VERBOSE}           {OFF}");
+	do_configure(gts, "{WORDWRAP}           {ON}");
+
+	do_class(gts, "{CONFIG} {CLOSE}");
+
+
+
+	do_class(gts, "{PATHDIR} {OPEN}");
+
+	insert_node_list(gts->list[LIST_PATHDIR],  "n",  "s",  "1", "");
+	insert_node_list(gts->list[LIST_PATHDIR],  "e",  "w",  "2", "");
+	insert_node_list(gts->list[LIST_PATHDIR],  "s",  "n",  "4", "");
+	insert_node_list(gts->list[LIST_PATHDIR],  "w",  "e",  "8", "");
+	insert_node_list(gts->list[LIST_PATHDIR],  "u",  "d", "16", "");
+	insert_node_list(gts->list[LIST_PATHDIR],  "d",  "u", "32", "");
+
+	insert_node_list(gts->list[LIST_PATHDIR], "ne", "sw",  "3", "");
+	insert_node_list(gts->list[LIST_PATHDIR], "nw", "se",  "9", "");
+	insert_node_list(gts->list[LIST_PATHDIR], "se", "nw",  "6", "");
+	insert_node_list(gts->list[LIST_PATHDIR], "sw", "ne", "12", "");
+
+	do_class(gts, "{PATHDIR} {CLOSE}");
+
+	gtd->level->input--;
+
+	init_terminal(gts);
+
+	reset_screen(gts);
+
+	if (!HAS_BIT(greeting, STARTUP_FLAG_NOGREETING))
+	{
+		if (HAS_BIT(greeting, STARTUP_FLAG_SCREENREADER))
+		{
+			tintin_printf2(gts, "Welcome to TinTin Plus Plus. Don't know which MUD to play? How about the following MUD.");
+
+			do_advertise(gts, "");
+
+			tintin_printf2(gts, "You're using TinTin Plus Plus written by Peter Unold, Bill Reis, and Igor van den Hoven.", CLIENT_VERSION);
+
+			tintin_printf2(gts, "For help and requests visit tintin.sourceforge.io/forum the captcha answer is 3671.");
+		}
+		else
+		{
+			do_advertise(gts, "");
+
+			if (gtd->screen->cols >= 80)
+			{
+				do_help(gts, "GREETING");
+			}
+			else
+			{
+				tintin_printf2(gts,
+					"\e[0;37mT I N T I N + +   %s"
+					"\n\n\e[0;36mT\e[0;37mhe K\e[0;36mi\e[0;37mcki\e[0;36mn\e[0;37m \e[0;36mT\e[0;37mickin D\e[0;36mi\e[0;37mkuMUD Clie\e[0;36mn\e[0;37mt\n\n"
+					"Code by Peter Unold, Bill Reis, and Igor van den Hoven\n",
+					CLIENT_VERSION);
+			}
+		}
+	}
+	pop_call();
+	return;
+}
+
+
+void quitmsg(char *message)
+{
+	struct session *ses;
+	static char crashed = FALSE;
+
+	if (crashed++)
+	{
+		print_stdout("quitmsg(crashed)\n");
+
+		fflush(NULL);
+
+		exit(-1);
+	}
+
+	SET_BIT(gtd->flags, TINTIN_FLAG_TERMINATE);
+
+	while ((ses = gts->next) != NULL)
+	{
+		cleanup_session(ses);
+	}
+
+	if (gtd->chat)
+	{
+		chat_uninitialize("", "");
+	}
+
+	check_all_events(gts, SUB_ARG, 0, 1, "PROGRAM TERMINATION", message ? message : "");
+
+	if (gtd->history_size)
+	{
+		char filename[BUFFER_SIZE];
+
+		sprintf(filename, "%s/%s/%s", gtd->home, TINTIN_DIR, HISTORY_FILE);
+
+		history_write(gts, filename);
+	}
+
+	reset_daemon();
+
+	reset_terminal(gts);
+
+	reset_screen(gts);
+
+	if (message == NULL || *message)
+	{
+		if (message)
+		{
+			print_stdout("\n\e[0m%s", message);
+		}
+		print_stdout("\nGoodbye from TinTin++\n\n");
+	}
+	fflush(NULL);
+
+	exit(0);
+}
+
+void syserr_printf(struct session *ses, char *fmt, ...)
+{
+	char buf[BUFFER_SIZE], name[BUFFER_SIZE], *errstr;
+
+	errstr = strerror(errno);
+	
+	va_list args;
+
+	va_start(args, fmt);
+	vsprintf(buf, fmt, args);
+	va_end(args);
+
+	if (ses)
+	{
+		sprintf(name, "(%s)", ses->name);
+	}
+	else
+	{
+		sprintf(name, "(null)");
+	}
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "SYSTEM ERROR", name, buf, ntos(errno), errstr);
+
+	if (!check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "CATCH SYSTEM ERROR", name, buf, ntos(errno), errstr))
+	{
+		if (gts)
+		{
+			tintin_printf2(gts, "#SYSTEM ERROR %s %s (%d: %s)\e[0m", name, buf, errno, errstr);
+		}
+
+		if (ses && ses != gts)
+		{
+			tintin_printf2(ses, "#SYSTEM ERROR: %s %s (%d: %s)\e[0m", name, buf, errno, errstr);		
+		}
+
+		if (ses && gtd->ses != ses && gtd->ses != gts)
+		{
+			tintin_printf2(gtd->ses, "#SYSTEM ERROR: %s %s (%d: %s)\e[0m", name, buf, errno, errstr);
+		}
+	}
+}
+
+void syserr_signal(int signal, char *msg)
+{
+	char buf[256];
+	static char crashed = FALSE;
+
+	if (crashed++)
+	{
+		print_stdout("\ecsyserr_signal(crashed)\n");
+
+		fflush(NULL);
+
+		exit(-1);
+	}
+
+	reset_terminal(gts);
+
+	reset_screen(gts);
+
+	fflush(NULL);
+
+	dump_stack_fatal();
+
+	sprintf(buf, "\e[1;31mFATAL SIGNAL FROM (%s): %s\e[0m\n", msg, strsignal(signal));
+
+	print_stdout("%s", buf);
+
+	fflush(NULL);
+
+	exit(0);
+}
+
+void syserr_fatal(int signal, char *msg)
+{
+	char buf[256], errstr[128];
+	static char crashed = FALSE;
+
+	if (crashed++)
+	{
+		print_stdout("\ecsyserr_fatal(crashed)");
+
+		fflush(NULL);
+
+		exit(-1);
+	}
+
+	if (signal <= 0)
+	{
+		sprintf(errstr, "(error %d: %s", errno, strerror(errno));
+	}
+	else
+	{
+		sprintf(errstr, "(signal %d: %s)", signal, strsignal(signal));
+	}
+
+	if (gtd->level->quiet)
+	{
+		gtd->level->quiet = 0;
+	}
+
+	reset_terminal(gts);
+
+	reset_screen(gts);
+
+	print_stdout("\e[r");
+
+	dump_stack_fatal();
+
+	sprintf(buf, "\n\e[1;31mFATAL ERROR \e[1;32m%s %s\e[0m\n", msg, errstr);
+
+	print_stdout("%s", buf);
+
+	reset_daemon();
+
+	fflush(NULL);
+
+	exit(0);
+}

+ 8175 - 0
mapper.c

@@ -0,0 +1,8175 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2004                       *
+******************************************************************************/
+
+#include "tintin.h"
+
+/*
+	todo:
+*/
+
+int                 map_grid_x;
+int                 map_grid_y;
+
+#define             MAP_SEARCH_DIST 1000
+#define             MAP_BF_SIZE 10000
+
+extern  int dir_flags(struct session *ses, int room, int dir);
+extern void create_map(struct session *ses, char *arg);
+extern struct room_data *create_room(struct session *ses, char *format, ...);
+extern void delete_room(struct session *ses, int room, int exits);
+extern struct exit_data *create_exit(struct session *ses, int room, char *format, ...);
+extern void delete_exit(struct session *ses, int room, struct exit_data *exit);
+extern void search_keywords(struct session *ses, char *arg, char *out, char *var);
+extern void map_search_compile(struct session *ses, char *arg, char *var);
+extern  int match_room(struct session *ses, int room, struct search_data *search);
+extern  int find_location(struct session *ses, char *arg);
+extern  int find_path(struct session *ses, char *arg);
+extern  int find_room(struct session *ses, char *arg);
+extern void goto_room(struct session *ses, int room);
+extern  int find_new_room(struct session *ses);
+extern struct exit_data *find_exit(struct session *ses, int room, char *arg);
+extern  int get_exit_dir(struct session *ses, char *arg);
+extern  int get_exit_length(struct session *ses, struct exit_data *exit);
+extern char *get_exit_color(struct session *ses, int room, struct exit_data *exit);
+extern  int dir_to_grid(int dir);
+extern  int revdir_to_grid(int dir);
+extern void set_room_exits(struct session *ses, int room);
+extern  int get_room_exits(struct session *ses, int room);
+extern  int get_terrain_index(struct session *ses, struct room_data *room, int x, int y);
+extern char *draw_terrain_symbol(struct session *ses, struct room_data *room, int line, int col, int x, int y, int flags);
+extern void displaygrid_build(struct session *ses, int room, int x, int y, int z);
+extern void add_undo(struct session *ses, char *format, ...);
+extern void del_undo(struct session *ses, struct link_data *link);
+extern char *draw_room(struct session *ses, struct room_data *room, int line, int x, int y);
+extern  int searchgrid_find(struct session *ses, int from, struct search_data *search);
+extern  int searchgrid_walk(struct session *ses, int offset, int from, int dest);
+extern void shortest_path(struct session *ses, int run, char *delay, char *arg);
+extern void explore_path(struct session *ses, int run, char *left, char *right);
+extern  int tunnel_void(struct session *ses, int from, int room, int dir);
+extern  int check_global(struct session *ses, int room);
+extern  int find_coord(struct session *ses, char *arg);
+extern  int spatialgrid_find(struct session *ses, int vnum, int x, int y, int z);
+extern void update_terrain(struct session *ses);
+
+DO_COMMAND(do_map)
+{
+	int cnt;
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+
+	push_call("do_map(%p,%p)",ses,arg);
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+
+	if (*arg1 == 0)
+	{
+		tintin_header(ses, " MAP OPTIONS ");
+
+		for (cnt = 0 ; *map_table[cnt].fun != NULL ; cnt++)
+		{
+			if (*map_table[cnt].desc)
+			{
+				tintin_printf2(ses, "  [%-13s] %s", map_table[cnt].name, map_table[cnt].desc);
+			}
+		}
+		tintin_header(ses, "");
+
+		pop_call();
+		return ses;
+	}
+	else
+	{
+		for (cnt = 0 ; *map_table[cnt].name ; cnt++)
+		{
+			if (is_abbrev(arg1, map_table[cnt].name))
+			{
+				if (map_table[cnt].check > 0 && ses->map == NULL)
+				{
+					show_error(ses, LIST_COMMAND, "#MAP: This session has no map data. Use #map create or #map read to create one.");
+					
+					pop_call();
+					return ses;
+				}
+				if (map_table[cnt].check > 1 && ses->map->room_list[ses->map->in_room] == NULL)
+				{
+					show_error(ses, LIST_COMMAND, "#MAP: You are not inside the map. Use #map goto to enter it.");
+
+					pop_call();
+					return ses;
+				}
+				*arg1 = 0;
+				*arg2 = 0;
+
+				if (gtd->level->ignore == 0)
+				{
+					if (HAS_BIT(map_table[cnt].flags, MAP_FLAG_VTMAP))
+					{
+						SET_BIT(ses->flags, SES_FLAG_UPDATEVTMAP);
+					}
+				}
+				pop_call();
+
+				push_call("MAP_%s(%p,%p,%p,%p,%p)",map_table[cnt].name,ses,arg,arg1,arg2);
+
+				map_table[cnt].fun (ses, arg, arg1, arg2);
+
+				pop_call();
+				return ses;
+			}
+		}
+
+		do_map(ses, "");
+	}
+	pop_call();
+	return ses;
+}
+
+/*
+	Utility functions
+*/
+
+void create_map(struct session *ses, char *arg)
+{
+	int group, legend;
+
+	push_call("create_map(%p,%p)",ses,arg);
+
+	if (ses->map)
+	{
+		delete_map(ses);
+	}
+
+	ses->map = (struct map_data *) calloc(1, sizeof(struct map_data));
+	ses->map->size = atoi(arg) > 0 ? atoi(arg) : 50000;
+
+	ses->map->room_list = (struct room_data **) calloc(ses->map->size, sizeof(struct room_data *));
+
+	ses->map->max_grid_x = 255;
+	ses->map->max_grid_y = 101;
+
+	ses->map->grid_rooms = (struct room_data **) calloc(ses->map->max_grid_x * ses->map->max_grid_y, sizeof(struct room_data *));
+
+	ses->map->search = calloc(1, sizeof(struct search_data));
+
+	ses->map->flags = MAP_FLAG_ASCIIGRAPHICS|MAP_FLAG_DIRECTION|MAP_FLAG_TERRAIN;
+
+	ses->map->global_exit         = (struct exit_data *) calloc(1, sizeof(struct exit_data));
+		ses->map->global_exit->vnum   = ses->map->global_vnum;
+		ses->map->global_exit->name   = restringf(ses->map->global_exit->name, "%cnop global", gtd->tintin_char);
+		ses->map->global_exit->cmd    = restringf(ses->map->global_exit->cmd, "%cnop global", gtd->tintin_char);
+		ses->map->global_exit->data   = strdup("");
+		ses->map->global_exit->weight = 1;
+		ses->map->global_exit->color  = strdup("");
+
+	do_map(ses, "{COLOR} {RESET}");
+
+	ses->map->display_stamp = 1;
+	ses->map->search->stamp = 1;
+
+	do_map(ses, "TERRAIN {} { }");
+/*
+	do_map(ses, "TERRAIN BEACH        <eea>~");
+	do_map(ses, "TERRAIN CITY         <ebf>-");
+	do_map(ses, "TERRAIN DESERT       <ffa>.");
+	do_map(ses, "TERRAIN FIELD        <228>.");
+	do_map(ses, "TERRAIN FOREST       <128>^");
+	do_map(ses, "TERRAIN HILL         <ddd>^");
+	do_map(ses, "TERRAIN LAKE         <248>~");
+	do_map(ses, "TERRAIN MOUNTAIN     <acf>^");
+	do_map(ses, "TERRAIN OCEAN        <148>@");
+	do_map(ses, "TERRAIN SWAMP        <bda>.");
+	do_map(ses, "TERRAIN UNDERGROUND  <baa>-");
+*/
+
+	create_room(ses, "%s", "{1} {0} {} {} { } {} {} {} {} {} {1.0} {}");
+
+	strcpy(arg, "");
+
+	for (group = 0 ; map_group_table[group].name ; group++)
+	{
+		for (legend = 0 ; map_legend_table[legend].group ; legend++)
+		{
+			if (*map_group_table[group].group == 0 || is_abbrev(map_group_table[group].group, map_legend_table[legend].group))
+			{
+				break;
+			}
+		}
+
+		if (map_legend_table[legend].group)
+		{
+			map_group_table[group].start = legend;
+		}
+		else
+		{
+			show_error(ses, LIST_COMMAND, "create_map: unknown legend group: %s, %s", map_group_table[group].name, map_group_table[group].group);
+
+			continue;
+		}
+
+		while (map_legend_table[++legend].group)
+		{
+			if (*map_group_table[group].group && !is_abbrev(map_group_table[group].group, map_legend_table[legend].group))
+			{
+				break;
+			}
+		}
+		map_group_table[group].end = legend;
+	}
+
+	gtd->level->quiet++;
+	do_map(ses, "LEGEND RESET");
+	gtd->level->quiet--;
+
+	pop_call();
+	return;
+}
+
+int delete_map(struct session *ses)
+{
+	int index, cnt;
+
+	for (index = cnt = 0 ; index < ses->map->size ; index++)
+	{
+		if (ses->map->room_list[index])
+		{
+			cnt++;
+
+			delete_room(ses, index, FALSE);
+		}
+	}
+	free(ses->map->room_list);
+
+	while (ses->map->undo_head)
+	{
+		del_undo(ses, ses->map->undo_head);
+	}
+
+	free(ses->map->global_exit->name);
+	free(ses->map->global_exit->cmd);
+	free(ses->map->global_exit->data);
+	free(ses->map->global_exit);
+
+	free(ses->map);
+
+	ses->map = NULL;
+
+	kill_list(ses->list[LIST_LANDMARK]);
+	kill_list(ses->list[LIST_TERRAIN]);
+
+	return cnt;
+}
+
+struct room_data *create_room(struct session *ses, char *format, ...)
+{
+	char *arg, buf[BUFFER_SIZE];
+	struct room_data *newroom;
+	va_list args;
+
+	va_start(args, format);
+	vsprintf(buf, format, args);
+	va_end(args);
+
+	newroom = (struct room_data *) calloc(1, sizeof(struct room_data));
+
+	arg = buf;
+
+	arg = get_arg_in_braces(ses, arg, buf, GET_ONE); newroom->vnum    = atoi(buf);
+
+	if (HAS_BIT(ses->map->flags, MAP_FLAG_SYNC) && ses->map->room_list[newroom->vnum] != NULL)
+	{
+		int vnum = newroom->vnum;
+
+		free(newroom);
+
+		return ses->map->room_list[vnum];
+	}
+
+	arg = get_arg_in_braces(ses, arg, buf, GET_ONE); newroom->flags   = atoi(buf);
+	arg = get_arg_in_braces(ses, arg, buf, GET_ONE); newroom->color   = strdup(buf);
+	arg = get_arg_in_braces(ses, arg, buf, GET_ONE); newroom->name    = strdup(buf);
+	arg = get_arg_in_braces(ses, arg, buf, GET_ONE); newroom->symbol  = strdup(buf);
+	arg = get_arg_in_braces(ses, arg, buf, GET_ONE); newroom->desc    = strdup(buf);
+	arg = get_arg_in_braces(ses, arg, buf, GET_ONE); newroom->area    = strdup(buf);
+	arg = get_arg_in_braces(ses, arg, buf, GET_ONE); newroom->note    = strdup(buf);
+	arg = get_arg_in_braces(ses, arg, buf, GET_ONE); newroom->terrain = strdup(buf);
+	arg = get_arg_in_braces(ses, arg, buf, GET_ONE); newroom->data    = strdup(buf);
+	arg = get_arg_in_braces(ses, arg, buf, GET_ONE); newroom->weight  = atof(buf);
+	arg = get_arg_in_braces(ses, arg, buf, GET_ONE); newroom->id      = strdup(buf);
+
+	if (HAS_BIT(newroom->flags, ROOM_FLAG_AVOID))
+	{
+		SET_BIT(newroom->flags, ROOM_FLAG_AVOID_TMP);
+	}
+	if (HAS_BIT(newroom->flags, ROOM_FLAG_HIDE))
+	{
+		SET_BIT(newroom->flags, ROOM_FLAG_HIDE_TMP);
+	}
+	if (HAS_BIT(newroom->flags, ROOM_FLAG_LEAVE))
+	{
+		SET_BIT(newroom->flags, ROOM_FLAG_LEAVE_TMP);
+	}
+	if (HAS_BIT(newroom->flags, ROOM_FLAG_VOID))
+	{
+		SET_BIT(newroom->flags, ROOM_FLAG_VOID_TMP);
+	}
+	if (HAS_BIT(newroom->flags, ROOM_FLAG_CURVED))
+	{
+		SET_BIT(newroom->flags, ROOM_FLAG_CURVED_TMP);
+	}
+
+	if (newroom->weight <= 0)
+	{
+		newroom->weight = 1;
+	}
+
+	if (newroom->vnum)
+	{
+		ses->map->room_list[newroom->vnum] = newroom;
+	}
+
+	show_message(ses, LIST_COMMAND, "#MAP CREATE ROOM %5d {%s}.", newroom->vnum, newroom->name);
+
+	return newroom;
+}
+
+void delete_room(struct session *ses, int room, int exits)
+{
+	struct exit_data *exit, *exit_next;
+	int cnt;
+
+	while (ses->map->room_list[room]->f_exit)
+	{
+		delete_exit(ses, room, ses->map->room_list[room]->f_exit);
+	}
+
+	free(ses->map->room_list[room]->area);
+	free(ses->map->room_list[room]->color);
+	free(ses->map->room_list[room]->id);
+	free(ses->map->room_list[room]->name);
+	free(ses->map->room_list[room]->symbol);
+	free(ses->map->room_list[room]->desc);
+	free(ses->map->room_list[room]->note);
+	free(ses->map->room_list[room]->terrain);
+	free(ses->map->room_list[room]->data); 
+
+	free(ses->map->room_list[room]);
+
+	ses->map->room_list[room] = NULL;
+
+	if (exits)
+	{
+		for (cnt = 0 ; cnt < ses->map->size ; cnt++)
+		{
+			if (ses->map->room_list[cnt])
+			{
+				for (exit = ses->map->room_list[cnt]->f_exit ; exit ; exit = exit_next)
+				{
+					exit_next = exit->next;
+
+					if (exit->vnum == room)
+					{
+						delete_exit(ses, cnt, exit);
+					}
+				}
+			}
+		}
+	}
+
+}
+
+struct exit_data *create_exit(struct session *ses, int vnum, char *format, ...)
+{
+	struct exit_data *newexit;
+	struct room_data *room;
+	va_list args;
+	char *arg, buf[BUFFER_SIZE];
+
+	push_call("create_exit(%p,%d,%p)",ses,vnum,format);
+
+	va_start(args, format);
+	vsprintf(buf, format, args);
+	va_end(args);
+
+	newexit = (struct exit_data *) calloc(1, sizeof(struct exit_data));
+
+	room = ses->map->room_list[vnum];
+
+	arg = buf;
+
+	arg = get_arg_in_braces(ses, arg, buf, GET_ONE);	newexit->vnum   = atoi(buf);
+	arg = get_arg_in_braces(ses, arg, buf, GET_ONE);
+
+	if (HAS_BIT(ses->map->flags, MAP_FLAG_SYNC) && find_exit(ses, vnum, buf))
+	{
+		free(newexit);
+
+		return find_exit(ses, vnum, buf);
+	}
+	newexit->name = strdup(buf);
+
+	arg = get_arg_in_braces(ses, arg, buf, GET_ALL);	newexit->cmd    = strdup(buf);
+	arg = get_arg_in_braces(ses, arg, buf, GET_ONE);	newexit->dir    = atoi(buf);
+	arg = get_arg_in_braces(ses, arg, buf, GET_ONE);	newexit->flags  = atoi(buf);
+	arg = get_arg_in_braces(ses, arg, buf, GET_ALL);	newexit->data   = strdup(buf);
+	arg = get_arg_in_braces(ses, arg, buf, GET_ONE);	newexit->weight = atof(buf);
+	arg = get_arg_in_braces(ses, arg, buf, GET_ONE);        newexit->color  = strdup(buf);
+
+	if (newexit->dir == 0)
+	{
+		newexit->dir = get_exit_dir(ses, newexit->name);
+	}
+
+	newexit->grid = dir_to_grid(newexit->dir);
+
+	if (room->exit_grid[newexit->grid] == NULL)
+	{
+		room->exit_grid[newexit->grid] = newexit;
+	}
+
+	if (newexit->weight <= 0)
+	{
+		newexit->weight = 1;
+	}
+
+	LINK(newexit, room->f_exit, room->l_exit);
+
+	room->exit_size++;
+
+	SET_BIT(room->exit_dirs, (1LL << newexit->dir));
+
+	show_message(ses, LIST_COMMAND, "#MAP CREATE EXIT {%s} {%s} TO ROOM %d.", newexit->name, newexit->cmd, newexit->vnum);
+
+	pop_call();
+	return newexit;
+}
+
+void delete_exit(struct session *ses, int room, struct exit_data *exit)
+{
+	free(exit->name);
+	free(exit->cmd);
+	free(exit->data);
+	free(exit->color);
+
+	UNLINK(exit, ses->map->room_list[room]->f_exit, ses->map->room_list[room]->l_exit)
+
+	set_room_exits(ses, room);
+
+	free(exit);
+}
+
+int get_exit_dir(struct session *ses, char *arg)
+{
+	struct listnode *node;
+
+	node = search_node_list(ses->list[LIST_PATHDIR], arg);
+
+	if (node)
+	{
+		return atoi(node->arg3);
+	}
+	else
+	{
+		return 0;
+	}
+}
+
+int get_exit_length(struct session *ses, struct exit_data *exit)
+{
+	return (int) exit->weight + ses->map->room_list[exit->vnum]->length;
+}
+
+char *get_exit_color(struct session *ses, int room, struct exit_data *exit)
+{
+	push_call("get_exit_color(%p,%d,%p)",ses,room,exit);
+
+	if (exit)
+	{
+		if (*exit->color)
+		{
+			pop_call();
+			return exit->color;
+		}
+
+		if (room)
+		{
+			struct exit_data *rev_exit = ses->map->room_list[exit->vnum]->exit_grid[revdir_to_grid(exit->dir)];
+
+			if (rev_exit && rev_exit->vnum == room)
+			{
+				if (ses->map->room_list[exit->vnum]->length < ses->map->room_list[rev_exit->vnum]->length)
+				{
+					exit = rev_exit;
+				}
+			}
+		}
+
+		if (HAS_BIT(exit->flags, EXIT_FLAG_AVOID) && *ses->map->color[MAP_COLOR_AVOID])
+		{
+			pop_call();
+			return ses->map->color[MAP_COLOR_AVOID];
+		}
+		if (HAS_BIT(exit->flags, EXIT_FLAG_BLOCK) && *ses->map->color[MAP_COLOR_BLOCK])
+		{
+			pop_call();
+			return ses->map->color[MAP_COLOR_BLOCK];
+		}
+		if (HAS_BIT(exit->flags, EXIT_FLAG_HIDE) && *ses->map->color[MAP_COLOR_HIDE])
+		{
+			pop_call();
+			return ses->map->color[MAP_COLOR_HIDE];
+		}
+		if (HAS_BIT(exit->flags, EXIT_FLAG_INVIS) && *ses->map->color[MAP_COLOR_INVIS])
+		{
+			pop_call();
+			return ses->map->color[MAP_COLOR_INVIS];
+		}
+		pop_call();
+		return ses->map->color[MAP_COLOR_EXIT];
+	}
+	else
+	{
+		pop_call();
+		return "";
+	}
+}
+
+int revdir_to_grid(int dir)
+{
+	switch (dir)
+	{
+		case 0:
+			return EXIT_GRID_0;
+		case MAP_EXIT_N:
+			return EXIT_GRID_S;
+		case MAP_EXIT_E:
+			return EXIT_GRID_W;
+		case MAP_EXIT_S:
+			return EXIT_GRID_N;
+		case MAP_EXIT_W:
+			return EXIT_GRID_E;
+		case MAP_EXIT_N|MAP_EXIT_E:
+			return EXIT_GRID_SW;
+		case MAP_EXIT_N|MAP_EXIT_W:
+			return EXIT_GRID_SE;
+		case MAP_EXIT_S|MAP_EXIT_E:
+			return EXIT_GRID_NW;
+		case MAP_EXIT_S|MAP_EXIT_W:
+			return EXIT_GRID_NE;
+	}
+
+	if (HAS_BIT(dir, MAP_EXIT_D))
+	{
+		return EXIT_GRID_U;
+	}
+
+	if (HAS_BIT(dir, MAP_EXIT_U))
+	{
+		return EXIT_GRID_D;
+	}
+
+	return EXIT_GRID_0;
+}
+
+int dir_to_grid(int dir)
+{
+	switch (dir)
+	{
+		case 0:
+			return EXIT_GRID_0;
+		case MAP_EXIT_N:
+			return EXIT_GRID_N;
+		case MAP_EXIT_E:
+			return EXIT_GRID_E;
+		case MAP_EXIT_S:
+			return EXIT_GRID_S;
+		case MAP_EXIT_W:
+			return EXIT_GRID_W;
+		case MAP_EXIT_N|MAP_EXIT_E:
+			return EXIT_GRID_NE;
+		case MAP_EXIT_N|MAP_EXIT_W:
+			return EXIT_GRID_NW;
+		case MAP_EXIT_S|MAP_EXIT_E:
+			return EXIT_GRID_SE;
+		case MAP_EXIT_S|MAP_EXIT_W:
+			return EXIT_GRID_SW;
+	}
+
+	if (HAS_BIT(dir, MAP_EXIT_D))
+	{
+		return EXIT_GRID_D;
+	}
+
+	if (HAS_BIT(dir, MAP_EXIT_U))
+	{
+		return EXIT_GRID_U;
+	}
+
+	return EXIT_GRID_0;
+}
+
+int get_room_exits(struct session *ses, int room)
+{
+	return ses->map->room_list[room]->exit_size;
+}
+
+void set_room_exits(struct session *ses, int vnum)
+{
+	struct exit_data *exit;
+	struct room_data *room;
+
+	room = ses->map->room_list[vnum];
+
+	room->exit_dirs = 0;
+	room->exit_size = 0;
+
+	memset(room->exit_grid, 0, sizeof(room->exit_grid));
+
+	for (exit = room->f_exit ; exit ; exit = exit->next)
+	{
+		SET_BIT(room->exit_dirs, 1LL << exit->dir);
+
+		if (room->exit_grid[exit->grid] == NULL)
+		{
+			room->exit_grid[exit->grid] = exit;
+		}
+		room->exit_size++;
+	}
+}
+
+int get_terrain_index(struct session *ses, struct room_data *room, int x, int y)
+{
+	struct listroot *root = ses->list[LIST_TERRAIN];
+	struct room_data *wide_grid[11], *vast_grid[11];
+	int terrain;
+
+	if (room)
+	{
+		return room->terrain_index;
+	}
+
+	wide_grid[EXIT_GRID_N]  = ses->map->grid_rooms[x     + map_grid_x * (y + 1)];
+	wide_grid[EXIT_GRID_NE] = ses->map->grid_rooms[x + 1 + map_grid_x * (y + 1)];
+	wide_grid[EXIT_GRID_E]  = ses->map->grid_rooms[x + 1 + map_grid_x * (y    )];
+	wide_grid[EXIT_GRID_SE] = ses->map->grid_rooms[x + 1 + map_grid_x * (y - 1)];
+	wide_grid[EXIT_GRID_S]  = ses->map->grid_rooms[x     + map_grid_x * (y - 1)];
+	wide_grid[EXIT_GRID_SW] = ses->map->grid_rooms[x - 1 + map_grid_x * (y - 1)];
+	wide_grid[EXIT_GRID_W]  = ses->map->grid_rooms[x - 1 + map_grid_x * (y    )];
+	wide_grid[EXIT_GRID_NW]	= ses->map->grid_rooms[x - 1 + map_grid_x * (y + 1)];
+
+	if (wide_grid[EXIT_GRID_N] && wide_grid[EXIT_GRID_N]->terrain_index != -1 && wide_grid[EXIT_GRID_N]->vnum && HAS_BIT(root->list[wide_grid[EXIT_GRID_N]->terrain_index]->room->terrain_flags, TERRAIN_FLAG_WIDE))
+	{
+		terrain = wide_grid[EXIT_GRID_N]->terrain_index;
+
+		if ((wide_grid[EXIT_GRID_E] == NULL || wide_grid[EXIT_GRID_E]->terrain_index == terrain) && (wide_grid[EXIT_GRID_S] == NULL || wide_grid[EXIT_GRID_S]->terrain_index == terrain) && (wide_grid[EXIT_GRID_W] == NULL || wide_grid[EXIT_GRID_W]->terrain_index == terrain))
+		{
+			return wide_grid[EXIT_GRID_N]->terrain_index;
+		}
+	}
+
+	if (wide_grid[EXIT_GRID_E] && wide_grid[EXIT_GRID_E]->terrain_index != -1 && wide_grid[EXIT_GRID_E]->vnum && HAS_BIT(root->list[wide_grid[EXIT_GRID_E]->terrain_index]->room->terrain_flags, TERRAIN_FLAG_WIDE))
+	{
+		terrain = wide_grid[EXIT_GRID_E]->terrain_index;
+
+		if ((wide_grid[EXIT_GRID_S] == NULL || wide_grid[EXIT_GRID_S]->terrain_index == terrain) && (wide_grid[EXIT_GRID_W] == NULL || wide_grid[EXIT_GRID_W]->terrain_index == terrain) && (wide_grid[EXIT_GRID_N] == NULL || wide_grid[EXIT_GRID_N]->terrain_index == terrain))
+		{
+			return wide_grid[EXIT_GRID_E]->terrain_index;
+		}
+	}
+
+	if (wide_grid[EXIT_GRID_S] && wide_grid[EXIT_GRID_S]->terrain_index != -1 && wide_grid[EXIT_GRID_S]->vnum && HAS_BIT(root->list[wide_grid[EXIT_GRID_S]->terrain_index]->room->terrain_flags, TERRAIN_FLAG_WIDE))
+	{
+		terrain = wide_grid[EXIT_GRID_S]->terrain_index;
+
+		if ((wide_grid[EXIT_GRID_W] == NULL || wide_grid[EXIT_GRID_W]->terrain_index == terrain) && (wide_grid[EXIT_GRID_N] == NULL || wide_grid[EXIT_GRID_N]->terrain_index == terrain) && (wide_grid[EXIT_GRID_E] == NULL || wide_grid[EXIT_GRID_E]->terrain_index == terrain))
+		{
+			return wide_grid[EXIT_GRID_S]->terrain_index;
+		}
+	}
+
+	if (wide_grid[EXIT_GRID_W] && wide_grid[EXIT_GRID_W]->terrain_index != -1 && wide_grid[EXIT_GRID_W]->vnum && HAS_BIT(root->list[wide_grid[EXIT_GRID_W]->terrain_index]->room->terrain_flags, TERRAIN_FLAG_WIDE))
+	{
+		terrain = wide_grid[EXIT_GRID_W]->terrain_index;
+
+		if ((wide_grid[EXIT_GRID_N] == NULL || wide_grid[EXIT_GRID_N]->terrain_index == terrain) && (wide_grid[EXIT_GRID_E] == NULL || wide_grid[EXIT_GRID_E]->terrain_index == terrain) && (wide_grid[EXIT_GRID_S] == NULL || wide_grid[EXIT_GRID_S]->terrain_index == terrain))
+		{
+			return wide_grid[EXIT_GRID_W]->terrain_index;
+		}
+	}
+
+	if (x < 2 || y < 2 || x > map_grid_x - 3 || y > map_grid_y - 3)
+	{
+		return -1;
+	}
+
+	vast_grid[EXIT_GRID_N]  = ses->map->grid_rooms[x     + map_grid_x * (y + 2)];
+	vast_grid[EXIT_GRID_NE] = ses->map->grid_rooms[x + 2 + map_grid_x * (y + 2)];
+	vast_grid[EXIT_GRID_E]  = ses->map->grid_rooms[x + 2 + map_grid_x * (y    )];
+	vast_grid[EXIT_GRID_SE] = ses->map->grid_rooms[x + 2 + map_grid_x * (y - 2)];
+	vast_grid[EXIT_GRID_S]  = ses->map->grid_rooms[x     + map_grid_x * (y - 2)];
+	vast_grid[EXIT_GRID_SW] = ses->map->grid_rooms[x - 2 + map_grid_x * (y - 2)];
+	vast_grid[EXIT_GRID_W]  = ses->map->grid_rooms[x - 2 + map_grid_x * (y    )];
+	vast_grid[EXIT_GRID_NW]	= ses->map->grid_rooms[x - 2 + map_grid_x * (y + 2)];
+
+	if (vast_grid[EXIT_GRID_N] && vast_grid[EXIT_GRID_N]->terrain_index != -1 && vast_grid[EXIT_GRID_N]->vnum && HAS_BIT(root->list[vast_grid[EXIT_GRID_N]->terrain_index]->room->terrain_flags, TERRAIN_FLAG_VAST))
+	{
+		terrain = vast_grid[EXIT_GRID_N]->terrain_index;
+
+		if ((wide_grid[EXIT_GRID_E] == NULL || wide_grid[EXIT_GRID_E]->terrain_index == terrain) && (wide_grid[EXIT_GRID_S] == NULL || wide_grid[EXIT_GRID_S]->terrain_index == terrain) && (wide_grid[EXIT_GRID_W] == NULL || wide_grid[EXIT_GRID_W]->terrain_index == terrain))
+		{
+			return vast_grid[EXIT_GRID_N]->terrain_index;
+		}
+	}
+
+	if (vast_grid[EXIT_GRID_E] && vast_grid[EXIT_GRID_E]->terrain_index != -1 && vast_grid[EXIT_GRID_E]->vnum && HAS_BIT(root->list[vast_grid[EXIT_GRID_E]->terrain_index]->room->terrain_flags, TERRAIN_FLAG_VAST))
+	{
+		terrain = vast_grid[EXIT_GRID_E]->terrain_index;
+
+		if ((wide_grid[EXIT_GRID_S] == NULL || wide_grid[EXIT_GRID_S]->terrain_index == terrain) && (wide_grid[EXIT_GRID_W] == NULL || wide_grid[EXIT_GRID_W]->terrain_index == terrain) && (wide_grid[EXIT_GRID_N] == NULL || wide_grid[EXIT_GRID_N]->terrain_index == terrain))
+		{
+			return vast_grid[EXIT_GRID_E]->terrain_index;
+		}
+	}
+
+	if (vast_grid[EXIT_GRID_S] && vast_grid[EXIT_GRID_S]->terrain_index != -1 && vast_grid[EXIT_GRID_S]->vnum && HAS_BIT(root->list[vast_grid[EXIT_GRID_S]->terrain_index]->room->terrain_flags, TERRAIN_FLAG_VAST))
+	{
+		terrain = vast_grid[EXIT_GRID_S]->terrain_index;
+
+		if ((wide_grid[EXIT_GRID_W] == NULL || wide_grid[EXIT_GRID_W]->terrain_index == terrain) && (wide_grid[EXIT_GRID_N] == NULL || wide_grid[EXIT_GRID_N]->terrain_index == terrain) && (wide_grid[EXIT_GRID_E] == NULL || wide_grid[EXIT_GRID_E]->terrain_index == terrain))
+		{
+			return vast_grid[EXIT_GRID_S]->terrain_index;
+		}
+	}
+
+	if (vast_grid[EXIT_GRID_W] && vast_grid[EXIT_GRID_W]->terrain_index != -1 && vast_grid[EXIT_GRID_W]->vnum && HAS_BIT(root->list[vast_grid[EXIT_GRID_W]->terrain_index]->room->terrain_flags, TERRAIN_FLAG_VAST))
+	{
+		terrain = vast_grid[EXIT_GRID_W]->terrain_index;
+
+		if ((wide_grid[EXIT_GRID_N] == NULL || wide_grid[EXIT_GRID_N]->terrain_index == terrain) && (wide_grid[EXIT_GRID_E] == NULL || wide_grid[EXIT_GRID_E]->terrain_index == terrain) && (wide_grid[EXIT_GRID_S] == NULL || wide_grid[EXIT_GRID_S]->terrain_index == terrain))
+		{
+			return vast_grid[EXIT_GRID_W]->terrain_index;
+		}
+	}
+
+	return -1;
+}
+
+int get_terrain_density(struct room_data *room, int width)
+{
+	int flag = 0;
+
+	switch (width)
+	{
+		case 0:
+			if (HAS_BIT(room->terrain_flags, TERRAIN_FLAG_FADEIN))
+			{
+				switch (HAS_BIT(room->terrain_flags, TERRAIN_FLAG_DENSE|TERRAIN_FLAG_AMPLE|TERRAIN_FLAG_SPARSE|TERRAIN_FLAG_SCANT))
+				{
+					case TERRAIN_FLAG_DENSE:
+						switch (HAS_BIT(room->terrain_flags, TERRAIN_FLAG_NARROW|TERRAIN_FLAG_STANDARD|TERRAIN_FLAG_WIDE|TERRAIN_FLAG_VAST))
+						{
+							case TERRAIN_FLAG_NARROW:
+								flag = TERRAIN_FLAG_DENSE;
+								break;
+							case TERRAIN_FLAG_STANDARD:
+								flag = TERRAIN_FLAG_AMPLE;
+								break;
+							case TERRAIN_FLAG_WIDE:
+								flag = TERRAIN_FLAG_SPARSE;
+								break;
+							case TERRAIN_FLAG_WIDE|TERRAIN_FLAG_VAST:
+								flag = TERRAIN_FLAG_SCANT;
+								break;
+						}
+						break;
+					case TERRAIN_FLAG_AMPLE:
+						switch (HAS_BIT(room->terrain_flags, TERRAIN_FLAG_NARROW|TERRAIN_FLAG_STANDARD|TERRAIN_FLAG_WIDE|TERRAIN_FLAG_VAST))
+						{
+							case TERRAIN_FLAG_NARROW:
+								flag = TERRAIN_FLAG_AMPLE;
+								break;
+							case TERRAIN_FLAG_STANDARD:
+								flag = TERRAIN_FLAG_SPARSE;
+								break;
+							case TERRAIN_FLAG_WIDE:
+								flag = TERRAIN_FLAG_SCANT;
+								break;
+							case TERRAIN_FLAG_WIDE|TERRAIN_FLAG_VAST:
+								flag = TERRAIN_FLAG_SCANT;
+								break;
+						}
+						break;
+
+					case TERRAIN_FLAG_SPARSE:
+						switch (HAS_BIT(room->terrain_flags, TERRAIN_FLAG_NARROW|TERRAIN_FLAG_WIDE|TERRAIN_FLAG_VAST))
+						{
+							case TERRAIN_FLAG_NARROW:
+								flag = TERRAIN_FLAG_SPARSE;
+								break;
+							case TERRAIN_FLAG_STANDARD:
+							case TERRAIN_FLAG_WIDE:
+							case TERRAIN_FLAG_WIDE|TERRAIN_FLAG_VAST:
+								flag = TERRAIN_FLAG_SCANT;
+								break;
+						}
+						break;
+
+					case TERRAIN_FLAG_SCANT:
+						flag = TERRAIN_FLAG_SCANT;
+						break;
+				}
+			}
+			else if (HAS_BIT(room->terrain_flags, TERRAIN_FLAG_FADEOUT))
+			{
+				switch (HAS_BIT(room->terrain_flags, TERRAIN_FLAG_DENSE|TERRAIN_FLAG_AMPLE|TERRAIN_FLAG_SPARSE|TERRAIN_FLAG_SCANT))
+				{
+					case TERRAIN_FLAG_DENSE:
+						flag = TERRAIN_FLAG_DENSE;
+						break;
+					case TERRAIN_FLAG_AMPLE:
+						flag = TERRAIN_FLAG_AMPLE;
+						break;
+					case TERRAIN_FLAG_SPARSE:
+						flag = TERRAIN_FLAG_SPARSE;
+						break;
+					case TERRAIN_FLAG_SCANT:
+						flag = TERRAIN_FLAG_SCANT;
+						break;
+				}
+			}
+			else
+			{
+				flag = room->terrain_flags;
+			}
+			break;
+
+		case 1:
+			if (HAS_BIT(room->terrain_flags, TERRAIN_FLAG_FADEIN))
+			{
+				switch (HAS_BIT(room->terrain_flags, TERRAIN_FLAG_DENSE|TERRAIN_FLAG_SPARSE|TERRAIN_FLAG_SCANT))
+				{
+					case TERRAIN_FLAG_DENSE:
+						switch (HAS_BIT(room->terrain_flags, TERRAIN_FLAG_NARROW|TERRAIN_FLAG_WIDE|TERRAIN_FLAG_VAST))
+						{
+							case TERRAIN_FLAG_NARROW:
+							case TERRAIN_FLAG_STANDARD:
+								flag = TERRAIN_FLAG_DENSE;
+								break;
+
+							case TERRAIN_FLAG_WIDE:
+								flag = TERRAIN_FLAG_AMPLE;
+								break;
+								
+							case TERRAIN_FLAG_WIDE|TERRAIN_FLAG_VAST:
+								flag = TERRAIN_FLAG_SPARSE;
+								break;
+						}
+						break;
+
+					case TERRAIN_FLAG_AMPLE:
+						switch (HAS_BIT(room->terrain_flags, TERRAIN_FLAG_NARROW|TERRAIN_FLAG_WIDE|TERRAIN_FLAG_VAST))
+						{
+							case TERRAIN_FLAG_NARROW:
+							case TERRAIN_FLAG_STANDARD:
+								flag = TERRAIN_FLAG_AMPLE;
+								break;
+							case TERRAIN_FLAG_WIDE:
+								flag = TERRAIN_FLAG_SPARSE;
+								break;
+							case TERRAIN_FLAG_WIDE|TERRAIN_FLAG_VAST:
+								flag = TERRAIN_FLAG_SCANT;
+								break;
+						}
+						break;
+
+					case TERRAIN_FLAG_SPARSE:
+						switch (HAS_BIT(room->terrain_flags, TERRAIN_FLAG_NARROW|TERRAIN_FLAG_WIDE|TERRAIN_FLAG_VAST))
+						{
+							case TERRAIN_FLAG_NARROW:
+							case TERRAIN_FLAG_STANDARD:
+							case TERRAIN_FLAG_WIDE:
+								flag = TERRAIN_FLAG_SPARSE;
+								break;
+							case TERRAIN_FLAG_WIDE|TERRAIN_FLAG_VAST:
+								flag = TERRAIN_FLAG_SCANT;
+								break;
+						}
+						break;
+
+					case TERRAIN_FLAG_SCANT:
+						flag = TERRAIN_FLAG_SCANT;
+						break;
+				}
+			}
+			else if (HAS_BIT(room->terrain_flags, TERRAIN_FLAG_FADEOUT))
+			{
+				switch (HAS_BIT(room->terrain_flags, TERRAIN_FLAG_DENSE|TERRAIN_FLAG_SPARSE|TERRAIN_FLAG_SCANT))
+				{
+					case TERRAIN_FLAG_DENSE:
+						flag = TERRAIN_FLAG_AMPLE;
+						break;
+
+					case TERRAIN_FLAG_AMPLE:
+						flag = TERRAIN_FLAG_SPARSE;
+						break;
+
+					case TERRAIN_FLAG_SPARSE:
+					case TERRAIN_FLAG_SCANT:
+						flag = TERRAIN_FLAG_SCANT;
+						break;
+				}
+			}
+			else
+			{
+				flag = room->terrain_flags;
+			}
+			break;
+
+		case 2:
+			if (HAS_BIT(room->terrain_flags, TERRAIN_FLAG_FADEIN))
+			{
+				switch (HAS_BIT(room->terrain_flags, TERRAIN_FLAG_DENSE|TERRAIN_FLAG_SPARSE|TERRAIN_FLAG_SCANT))
+				{
+					case TERRAIN_FLAG_DENSE:
+						switch (HAS_BIT(room->terrain_flags, TERRAIN_FLAG_NARROW|TERRAIN_FLAG_WIDE|TERRAIN_FLAG_VAST))
+						{
+							case TERRAIN_FLAG_NARROW:
+							case TERRAIN_FLAG_STANDARD:
+							case TERRAIN_FLAG_WIDE:
+								flag = TERRAIN_FLAG_DENSE;
+								break;
+
+							case TERRAIN_FLAG_WIDE|TERRAIN_FLAG_VAST:
+								flag = TERRAIN_FLAG_AMPLE;
+								break;
+						}
+						break;
+
+					case TERRAIN_FLAG_AMPLE:
+						switch (HAS_BIT(room->terrain_flags, TERRAIN_FLAG_NARROW|TERRAIN_FLAG_WIDE|TERRAIN_FLAG_VAST))
+						{
+							case TERRAIN_FLAG_NARROW:
+							case TERRAIN_FLAG_STANDARD:
+							case TERRAIN_FLAG_WIDE:
+								flag = TERRAIN_FLAG_AMPLE;
+								break;
+
+							case TERRAIN_FLAG_WIDE|TERRAIN_FLAG_VAST:
+								flag = TERRAIN_FLAG_SPARSE;
+								break;
+						}
+						break;
+
+					case TERRAIN_FLAG_SPARSE:
+						switch (HAS_BIT(room->terrain_flags, TERRAIN_FLAG_NARROW|TERRAIN_FLAG_WIDE|TERRAIN_FLAG_VAST))
+						{
+							case TERRAIN_FLAG_NARROW:
+							case TERRAIN_FLAG_STANDARD:
+							case TERRAIN_FLAG_WIDE:
+								flag = TERRAIN_FLAG_SPARSE;
+								break;
+							case TERRAIN_FLAG_WIDE|TERRAIN_FLAG_VAST:
+								flag = TERRAIN_FLAG_SCANT;
+								break;
+						}
+						break;
+
+					case TERRAIN_FLAG_SCANT:
+						flag = TERRAIN_FLAG_SCANT;
+						break;
+				}
+			}
+			else if (HAS_BIT(room->terrain_flags, TERRAIN_FLAG_FADEOUT))
+			{
+				switch (HAS_BIT(room->terrain_flags, TERRAIN_FLAG_DENSE|TERRAIN_FLAG_SPARSE|TERRAIN_FLAG_SCANT))
+				{
+					case TERRAIN_FLAG_DENSE:
+						flag = TERRAIN_FLAG_SPARSE;
+						break;
+
+					default:
+						flag = TERRAIN_FLAG_SCANT;
+						break;
+				}
+			}
+			else
+			{
+				flag = room->terrain_flags;
+			}
+			break;
+
+		default:
+			if (HAS_BIT(room->terrain_flags, TERRAIN_FLAG_FADEIN))
+			{
+				switch (HAS_BIT(room->terrain_flags, TERRAIN_FLAG_DENSE|TERRAIN_FLAG_SPARSE|TERRAIN_FLAG_SCANT))
+				{
+					case TERRAIN_FLAG_DENSE:
+						flag = TERRAIN_FLAG_DENSE;
+						break;
+					default:
+						flag = 0;
+						break;
+
+					case TERRAIN_FLAG_SPARSE:
+						flag = TERRAIN_FLAG_SPARSE;
+						break;
+
+					case TERRAIN_FLAG_SCANT:
+						flag = TERRAIN_FLAG_SCANT;
+						break;
+				}
+			}
+			else if (HAS_BIT(room->terrain_flags, TERRAIN_FLAG_FADEOUT))
+			{
+				flag = TERRAIN_FLAG_SCANT;
+			}
+			else
+			{
+				flag = room->terrain_flags;
+			}
+			break;
+	}
+
+	if (HAS_BIT(room->terrain_flags, TERRAIN_FLAG_DOUBLE))
+	{
+		SET_BIT(flag, TERRAIN_FLAG_DOUBLE);
+	}
+	return flag;
+}
+
+char *blank_terrain_symbol(struct session *ses, struct room_data *room, int index, int flags)
+{
+	if (HAS_BIT(flags, TERRAIN_FLAG_DOUBLE))
+	{
+		if (room && room->terrain_index != -1 && HAS_BIT(ses->list[LIST_TERRAIN]->list[room->terrain_index]->room->terrain_flags, TERRAIN_FLAG_DOUBLE))
+		{
+			if (HAS_BIT(ses->map->flags, MAP_FLAG_ASCIIGRAPHICS))
+			{
+				if (index % 2 == 1)
+				{
+					SET_BIT(ses->map->flags, MAP_FLAG_DOUBLED);
+
+					return "  ";
+				}
+				else
+				{
+					DEL_BIT(ses->map->flags, MAP_FLAG_DOUBLED);
+					return "";
+				}
+			}
+			else if (HAS_BIT(ses->map->flags, MAP_FLAG_UNICODEGRAPHICS))
+			{
+				switch (index)
+				{
+					case 1:
+					case 3:
+						SET_BIT(ses->map->flags, MAP_FLAG_DOUBLED);
+						return "  ";
+					case 2:
+					case 4:
+						DEL_BIT(ses->map->flags, MAP_FLAG_DOUBLED);
+						return "";
+					case 5:
+						return "\e[1;31m5";
+				}
+			}
+		}
+//		DEL_BIT(ses->map->flags, MAP_FLAG_DOUBLED);
+		return " ";
+	}
+	return " ";
+}
+
+char *draw_terrain_symbol(struct session *ses, struct room_data *room, int line, int index, int x, int y, int flags)
+{
+	struct room_data *room_grid[11], *terrain_room;
+	int terrain, width = 0, density, hash;
+
+	if (!HAS_BIT(ses->map->flags, MAP_FLAG_TERRAIN))
+	{
+		return " ";
+	}
+
+	if (HAS_BIT(ses->map->flags, MAP_FLAG_DOUBLED))
+	{
+		DEL_BIT(ses->map->flags, MAP_FLAG_DOUBLED);
+
+		if (HAS_BIT(flags, TERRAIN_FLAG_DOUBLE) && index != 1)
+		{
+			return "";
+		}
+//		return "\e[1;31m?";
+	}
+
+	room_grid[EXIT_GRID_0]  = ses->map->grid_rooms[x     + map_grid_x * (y    )];
+	room_grid[EXIT_GRID_N]  = ses->map->grid_rooms[x     + map_grid_x * (y + 1)];
+	room_grid[EXIT_GRID_NE] = ses->map->grid_rooms[x + 1 + map_grid_x * (y + 1)];
+	room_grid[EXIT_GRID_E]  = ses->map->grid_rooms[x + 1 + map_grid_x * (y    )];
+	room_grid[EXIT_GRID_SE] = ses->map->grid_rooms[x + 1 + map_grid_x * (y - 1)];
+	room_grid[EXIT_GRID_S]  = ses->map->grid_rooms[x     + map_grid_x * (y - 1)];
+	room_grid[EXIT_GRID_SW] = ses->map->grid_rooms[x - 1 + map_grid_x * (y - 1)];
+	room_grid[EXIT_GRID_W]  = ses->map->grid_rooms[x - 1 + map_grid_x * (y    )];
+	room_grid[EXIT_GRID_NW]	= ses->map->grid_rooms[x - 1 + map_grid_x * (y + 1)];
+
+	hash = index + (room && room->vnum ? room->vnum : x + y);
+
+	if (HAS_BIT(ses->map->flags, MAP_FLAG_ASCIIGRAPHICS))
+	{
+		if (room == NULL)
+		{
+			width++;
+
+			switch (line)
+			{
+				case 1:
+					switch (index)
+					{
+						case 1:
+						case 2:
+							room = room_grid[EXIT_GRID_N] ? room_grid[EXIT_GRID_N] : room_grid[EXIT_GRID_W] ? room_grid[EXIT_GRID_W] : room_grid[EXIT_GRID_NW] ? room_grid[EXIT_GRID_NW] : NULL;
+							break;
+						case 3:
+						case 4:
+							room = room_grid[EXIT_GRID_N] ? room_grid[EXIT_GRID_N] : NULL;
+							break;
+						case 5:
+						case 6:
+							room = room_grid[EXIT_GRID_N] ? room_grid[EXIT_GRID_N] : room_grid[EXIT_GRID_E] ? room_grid[EXIT_GRID_E] : room_grid[EXIT_GRID_NE] ? room_grid[EXIT_GRID_NE] : NULL;
+							break;
+					}
+					break;
+
+				case 2:
+					switch (index)
+					{
+						case 1:
+						case 2:
+							room = room_grid[EXIT_GRID_W] ? room_grid[EXIT_GRID_W] : room_grid[EXIT_GRID_NW] ? room_grid[EXIT_GRID_NW] : room_grid[EXIT_GRID_SW] ? room_grid[EXIT_GRID_SW] : NULL;
+							break;
+						case 3:
+						case 4:
+							room = room_grid[EXIT_GRID_N] ? room_grid[EXIT_GRID_N] : room_grid[EXIT_GRID_S] ? room_grid[EXIT_GRID_S] : NULL;
+							break;
+						case 5:
+						case 6:
+							room = room_grid[EXIT_GRID_E] ? room_grid[EXIT_GRID_E] : room_grid[EXIT_GRID_NE] ? room_grid[EXIT_GRID_NE] : room_grid[EXIT_GRID_SE] ? room_grid[EXIT_GRID_SE] : NULL;
+							break;
+					}
+					break;
+
+				case 3:
+					switch (index)
+					{
+						case 1:
+						case 2:
+							room = room_grid[EXIT_GRID_S] ? room_grid[EXIT_GRID_S] : room_grid[EXIT_GRID_W] ? room_grid[EXIT_GRID_W] : room_grid[EXIT_GRID_SW] ? room_grid[EXIT_GRID_SW] : NULL;
+							break;
+						case 3:
+						case 4:
+							room = room_grid[EXIT_GRID_S] ? room_grid[EXIT_GRID_S] : NULL;
+							break;
+						case 5:
+						case 6:
+							room = room_grid[EXIT_GRID_S] ? room_grid[EXIT_GRID_S] : room_grid[EXIT_GRID_E] ? room_grid[EXIT_GRID_E] : room_grid[EXIT_GRID_SE] ? room_grid[EXIT_GRID_SE] : NULL;
+							break;
+					}
+					break;
+			}
+
+			if (room == NULL || room->terrain_index == -1 || HAS_BIT(ses->list[LIST_TERRAIN]->list[room->terrain_index]->room->terrain_flags, TERRAIN_FLAG_NARROW))
+			{
+				return blank_terrain_symbol(ses, room, index, flags);
+			}
+		}
+
+		terrain = room->terrain_index;
+
+		if (terrain == -1)
+		{
+			return blank_terrain_symbol(ses, room, index, flags);
+		}
+
+		terrain_room = ses->list[LIST_TERRAIN]->list[terrain]->room;
+
+		if (HAS_BIT(ses->list[LIST_TERRAIN]->list[room->terrain_index]->room->terrain_flags, TERRAIN_FLAG_DOUBLE))
+		{
+			if (HAS_BIT(flags, TERRAIN_FLAG_DOUBLE))
+			{
+				if (index % 2 == 0)
+				{
+					return "\e[1;36m?";
+				}
+				SET_BIT(ses->map->flags, MAP_FLAG_DOUBLED);
+			}
+			else
+			{
+				return " ";
+			}
+		}
+
+		if (HAS_BIT(terrain_room->terrain_flags, TERRAIN_FLAG_NARROW) && room->exit_grid[EXIT_GRID_E] == NULL)
+		{
+			switch (line * 10 + index)
+			{
+				case 16:
+				case 26:
+				case 36:
+					return blank_terrain_symbol(ses, room, index, flags);
+			}
+		}
+
+		if (room->vnum == 0)
+		{
+			width++;
+
+			density = 0;
+
+			density += (room_grid[EXIT_GRID_N] && room_grid[EXIT_GRID_N]->vnum);
+			density += (room_grid[EXIT_GRID_NE] && room_grid[EXIT_GRID_NE]->vnum);
+			density += (room_grid[EXIT_GRID_E] && room_grid[EXIT_GRID_E]->vnum);
+			density += (room_grid[EXIT_GRID_SE] && room_grid[EXIT_GRID_SE]->vnum);
+			density += (room_grid[EXIT_GRID_S] && room_grid[EXIT_GRID_S]->vnum);
+			density += (room_grid[EXIT_GRID_SW] && room_grid[EXIT_GRID_SW]->vnum);
+			density += (room_grid[EXIT_GRID_W] && room_grid[EXIT_GRID_W]->vnum);
+			density += (room_grid[EXIT_GRID_NW] && room_grid[EXIT_GRID_NW]->vnum);
+
+			if (density == 0)
+			{
+				width++;
+			}
+		}
+
+		density = get_terrain_density(terrain_room, width);
+
+		if (HAS_BIT(density, TERRAIN_FLAG_DENSE))
+		{
+			return ses->list[LIST_TERRAIN]->list[terrain]->arg2;
+		}
+
+		if (HAS_BIT(density, TERRAIN_FLAG_SPARSE))
+		{
+			switch (line * 10 + index)
+			{
+				case 11:
+				case 23:
+				case 35:
+					return ses->list[LIST_TERRAIN]->list[terrain]->arg2;
+			}
+			return blank_terrain_symbol(ses, room, index, flags);
+		}
+
+		if (HAS_BIT(density, TERRAIN_FLAG_SCANT))
+		{
+			switch (line * 10 + index)
+			{
+				case 11:
+					return hash % 3 == 0 ? ses->list[LIST_TERRAIN]->list[terrain]->arg2 : blank_terrain_symbol(ses, room, index, flags);
+				case 23:
+					return hash % 3 == 1 ? ses->list[LIST_TERRAIN]->list[terrain]->arg2 : blank_terrain_symbol(ses, room, index, flags);
+				case 35:
+					return hash % 3 == 2 ? ses->list[LIST_TERRAIN]->list[terrain]->arg2 : blank_terrain_symbol(ses, room, index, flags);
+			}
+			return blank_terrain_symbol(ses, room, index, flags);
+		}
+
+		if (HAS_BIT(density, TERRAIN_FLAG_DOUBLE))
+		{
+			switch (y % 2 * 30 + line * 10 + index)
+			{
+				case 11:
+				case 15:
+				case 23:
+				case 31:
+				case 35:
+				case 43:
+				case 51:
+				case 55:
+				case 63:
+					return ses->list[LIST_TERRAIN]->list[terrain]->arg2;
+			}
+			return blank_terrain_symbol(ses, room, index, flags);
+		}
+
+		switch (y % 2 * 30 + line * 10 + index)
+		{
+			case 11:
+			case 13:
+			case 15:
+			case 22:
+			case 24:
+			case 26:
+			case 31:
+			case 33:
+			case 35:
+			case 42:
+			case 44:
+			case 46:
+			case 51:
+			case 53:
+			case 55:
+			case 62:
+			case 64:
+			case 66:
+				return ses->list[LIST_TERRAIN]->list[terrain]->arg2;
+		}
+		return blank_terrain_symbol(ses, room, index, flags);
+	}
+
+	// UNICODE
+
+	if (HAS_BIT(ses->map->flags, MAP_FLAG_UNICODEGRAPHICS))
+	{
+		if (room == NULL)
+		{
+			width++;
+
+			switch (line)
+			{
+				case 1:
+					switch (index)
+					{
+						case 1:
+						case 2:
+							room = room_grid[EXIT_GRID_W] ? room_grid[EXIT_GRID_W] : room_grid[EXIT_GRID_NW] ? room_grid[EXIT_GRID_NW] : NULL;
+							break;
+						case 3:
+						case 4:
+							room = room_grid[EXIT_GRID_N] ? room_grid[EXIT_GRID_N] : NULL;
+							break;
+						case 5:
+							room = room_grid[EXIT_GRID_N] ? room_grid[EXIT_GRID_N] : NULL;
+							break;
+					}
+					break;
+
+				case 2:
+					switch (index)
+					{
+						case 1:
+						case 2:
+							room = room_grid[EXIT_GRID_W] ? room_grid[EXIT_GRID_W] : NULL;
+							break;
+						case 3:
+						case 4:
+							if (room_grid[EXIT_GRID_S])
+							{
+								room = room_grid[EXIT_GRID_S];
+							}
+							else if (room_grid[EXIT_GRID_N])
+							{
+								room = room_grid[EXIT_GRID_N];
+							}
+							break;
+						case 5:
+							room = room_grid[EXIT_GRID_S] ? room_grid[EXIT_GRID_S] : NULL;
+							break;
+					}
+					break;
+			}
+
+			if (room == NULL || room->terrain_index == -1)
+			{
+				return blank_terrain_symbol(ses, room, index, flags);
+			}
+
+			if (HAS_BIT(ses->list[LIST_TERRAIN]->list[room->terrain_index]->room->terrain_flags, TERRAIN_FLAG_NARROW))
+			{
+				switch (line * 10 + index)
+				{
+					case 23:
+					case 24:
+					case 25:
+						return blank_terrain_symbol(ses, room, index, flags);
+				}
+
+				if (room_grid[EXIT_GRID_N] == NULL)
+				{
+					switch (line * 10 + index)
+					{
+						case 13:
+						case 14:
+						case 15:
+							return blank_terrain_symbol(ses, room, index, flags);
+					}
+				}
+
+				if (room_grid[EXIT_GRID_W] == NULL && room_grid[EXIT_GRID_NW] == NULL)
+				{
+					switch (line * 10 + index)
+					{
+						case 11:
+						case 12:
+							return blank_terrain_symbol(ses, room, index, flags);
+					}
+				}
+
+				if (room_grid[EXIT_GRID_W] == NULL)
+				{
+					switch (line * 10 + index)
+					{
+						case 21:
+						case 22:
+							return blank_terrain_symbol(ses, room, index, flags);
+					}
+				}
+			}
+		}
+		else
+		{
+			switch (line * 10 + index)
+			{
+				case 11:
+				case 12:
+					if (room_grid[EXIT_GRID_N] && hash % 4 == 0)
+					{
+						room = room_grid[EXIT_GRID_N];
+					}
+					else if (room_grid[EXIT_GRID_W] && hash % 4 == 1)
+					{
+						room = room_grid[EXIT_GRID_W];
+					}
+					else if (room_grid[EXIT_GRID_NW] && hash % 4 == 2)
+					{
+						room = room_grid[EXIT_GRID_NW];
+					}
+					break;
+				case 13:
+				case 14:
+					if (room_grid[EXIT_GRID_N] && hash % 2 == 0)
+					{
+						room = room_grid[EXIT_GRID_N];
+					}
+					break;
+				case 21:
+				case 22:
+					if (room_grid[EXIT_GRID_W] && hash % 2 == 0)
+					{
+						room = room_grid[EXIT_GRID_W];
+					}
+					break;
+			}
+
+			if (room == NULL)
+			{
+				return blank_terrain_symbol(ses, room, index, flags);
+			}
+		}
+
+		terrain = room->terrain_index;
+
+		if (terrain == -1)
+		{
+			return blank_terrain_symbol(ses, room, index, flags);
+		}
+
+		terrain_room = ses->list[LIST_TERRAIN]->list[terrain]->room;
+
+		if (HAS_BIT(terrain_room->terrain_flags, TERRAIN_FLAG_DOUBLE))
+		{
+			if (HAS_BIT(flags, TERRAIN_FLAG_DOUBLE))
+			{
+				if (index == 5)
+				{
+					return "\e[1;32m5";
+				}
+				if (index % 2 == 0)
+				{
+					DEL_BIT(ses->map->flags, MAP_FLAG_DOUBLED);
+					if (index == 2)
+					{
+						return " ";
+					}
+					if (index == 4)
+					{
+						return " ";
+					}
+					return "\e[1;35m?";
+				}
+				SET_BIT(ses->map->flags, MAP_FLAG_DOUBLED);
+			}
+			else
+			{
+				return blank_terrain_symbol(ses, room, index, flags);
+			}
+		}
+
+		if (room->vnum == 0)
+		{
+			width++;
+
+			density = 0;
+
+			density += (room_grid[EXIT_GRID_N] && room_grid[EXIT_GRID_N]->vnum);
+			density += (room_grid[EXIT_GRID_NE] && room_grid[EXIT_GRID_NE]->vnum);
+			density += (room_grid[EXIT_GRID_E] && room_grid[EXIT_GRID_E]->vnum);
+			density += (room_grid[EXIT_GRID_SE] && room_grid[EXIT_GRID_SE]->vnum);
+			density += (room_grid[EXIT_GRID_S] && room_grid[EXIT_GRID_S]->vnum);
+			density += (room_grid[EXIT_GRID_SW] && room_grid[EXIT_GRID_SW]->vnum);
+			density += (room_grid[EXIT_GRID_W] && room_grid[EXIT_GRID_W]->vnum);
+			density += (room_grid[EXIT_GRID_NW] && room_grid[EXIT_GRID_NW]->vnum);
+
+			if (density == 0)
+			{
+				width++;
+			}
+		}
+
+		density = get_terrain_density(terrain_room, width);
+
+		if (HAS_BIT(density, TERRAIN_FLAG_DENSE))
+		{
+			return ses->list[LIST_TERRAIN]->list[terrain]->arg2;
+		}
+
+		if (HAS_BIT(density, TERRAIN_FLAG_SPARSE))
+		{
+			if (HAS_BIT(flags, TERRAIN_FLAG_DOUBLE) && HAS_BIT(terrain_room->terrain_flags, TERRAIN_FLAG_DOUBLE))
+			{
+				if (room_grid[EXIT_GRID_0] && room_grid[EXIT_GRID_N] && room_grid[EXIT_GRID_0]->terrain_index != room_grid[EXIT_GRID_N]->terrain_index)
+				{
+					switch (line * 10 + index)
+					{
+						case 13:
+							return hash % 2 == 0 ? ses->list[LIST_TERRAIN]->list[terrain]->arg2 : blank_terrain_symbol(ses, room, index, flags);
+					}
+				}
+
+				switch (line * 10 + index)
+				{
+					case 11:
+						return hash % 2 == 1 ? ses->list[LIST_TERRAIN]->list[terrain]->arg2 : blank_terrain_symbol(ses, room, index, flags);
+
+					case 23:
+						return hash % 2 == 0 ? ses->list[LIST_TERRAIN]->list[terrain]->arg2 : blank_terrain_symbol(ses, room, index, flags);
+				}
+				return blank_terrain_symbol(ses, room, index, flags);
+			}
+
+			if (room_grid[EXIT_GRID_0] && room_grid[EXIT_GRID_N] && room_grid[EXIT_GRID_0]->terrain_index != room_grid[EXIT_GRID_N]->terrain_index)
+			{
+				switch (line * 10 + index)
+				{
+					case 12:
+						return hash % 2 == 0 ? ses->list[LIST_TERRAIN]->list[terrain]->arg2 : blank_terrain_symbol(ses, room, index, flags);
+
+					case 14:
+						return hash % 2 == 1 ? ses->list[LIST_TERRAIN]->list[terrain]->arg2 : blank_terrain_symbol(ses, room, index, flags);
+				}
+			}
+
+			switch (line * 10 + index)
+			{
+				case 11:
+					return hash % 5 == 0 ? ses->list[LIST_TERRAIN]->list[terrain]->arg2 : blank_terrain_symbol(ses, room, index, flags);
+				case 13:
+					return hash % 5 == 2 ? ses->list[LIST_TERRAIN]->list[terrain]->arg2 : blank_terrain_symbol(ses, room, index, flags);
+				case 15:
+					return hash % 5 == 4 ? ses->list[LIST_TERRAIN]->list[terrain]->arg2 : blank_terrain_symbol(ses, room, index, flags);
+
+				case 22:
+					return hash % 2 == 0 ? ses->list[LIST_TERRAIN]->list[terrain]->arg2 : blank_terrain_symbol(ses, room, index, flags);
+				case 24:
+					return hash % 2 == 1 ? ses->list[LIST_TERRAIN]->list[terrain]->arg2 : blank_terrain_symbol(ses, room, index, flags);
+			}
+			return blank_terrain_symbol(ses, room, index, flags);
+		}
+
+		if (HAS_BIT(density, TERRAIN_FLAG_SCANT))
+		{
+			if (HAS_BIT(flags, TERRAIN_FLAG_DOUBLE) && HAS_BIT(terrain_room->terrain_flags, TERRAIN_FLAG_DOUBLE))
+			{
+				if (room_grid[EXIT_GRID_0] && room_grid[EXIT_GRID_N] && room_grid[EXIT_GRID_0]->terrain_index != room_grid[EXIT_GRID_N]->terrain_index)
+				{
+					switch (line * 10 + index)
+					{
+						case 13:
+							return hash % 4 == 2 ? ses->list[LIST_TERRAIN]->list[terrain]->arg2 : blank_terrain_symbol(ses, room, index, flags);
+					}
+				}
+
+				switch (line * 10 + index)
+				{
+					case 11:
+						return hash % 4 == 0 ? ses->list[LIST_TERRAIN]->list[terrain]->arg2 : blank_terrain_symbol(ses, room, index, flags);
+//					case 13:
+//						return hash % 8 == 1 ? ses->list[LIST_TERRAIN]->list[terrain]->arg2 : blank_terrain_symbol(ses, room, index, flags);
+//					case 21:
+//						return hash % 8 == 2 ? ses->list[LIST_TERRAIN]->list[terrain]->arg2 : blank_terrain_symbol(ses, room, index, flags);
+					case 23:
+						return hash % 4 == 1 ? ses->list[LIST_TERRAIN]->list[terrain]->arg2 : blank_terrain_symbol(ses, room, index, flags);
+				}
+				return blank_terrain_symbol(ses, room, index, flags);
+			}
+
+			if (room_grid[EXIT_GRID_0] && room_grid[EXIT_GRID_N] && room_grid[EXIT_GRID_0]->terrain_index != room_grid[EXIT_GRID_N]->terrain_index)
+			{
+				switch (line * 10 + index)
+				{
+					case 12:
+						return hash % 7 == 5 ? ses->list[LIST_TERRAIN]->list[terrain]->arg2 : blank_terrain_symbol(ses, room, index, flags);
+					case 14:
+						return hash % 7 == 6 ? ses->list[LIST_TERRAIN]->list[terrain]->arg2 : blank_terrain_symbol(ses, room, index, flags);
+				}
+			}
+
+			switch (line * 10 + index)
+			{
+				case 11:
+					return hash % 7 == 0 ? ses->list[LIST_TERRAIN]->list[terrain]->arg2 : blank_terrain_symbol(ses, room, index, flags);
+				case 13:
+					return hash % 7 == 1 ? ses->list[LIST_TERRAIN]->list[terrain]->arg2 : blank_terrain_symbol(ses, room, index, flags);
+				case 15:
+					return hash % 7 == 2 ? ses->list[LIST_TERRAIN]->list[terrain]->arg2 : blank_terrain_symbol(ses, room, index, flags);
+				case 22:
+					return hash % 7 == 3 ? ses->list[LIST_TERRAIN]->list[terrain]->arg2 : blank_terrain_symbol(ses, room, index, flags);
+				case 24:
+					return hash % 7 == 4 ? ses->list[LIST_TERRAIN]->list[terrain]->arg2 : blank_terrain_symbol(ses, room, index, flags);
+			}
+			return blank_terrain_symbol(ses, room, index, flags);
+		}
+
+		if (HAS_BIT(flags, TERRAIN_FLAG_DOUBLE) && HAS_BIT(terrain_room->terrain_flags, TERRAIN_FLAG_DOUBLE))
+		{
+			switch (line * 10 + index)
+			{
+				case 13:
+					return room_grid[EXIT_GRID_0] && room_grid[EXIT_GRID_N] && room_grid[EXIT_GRID_0]->terrain_index != room_grid[EXIT_GRID_N]->terrain_index ? ses->list[LIST_TERRAIN]->list[terrain]->arg2 : blank_terrain_symbol(ses, room, index, flags);
+
+				case 11:
+//					return "\e[1;31m1 ";
+				case 23:
+//					return "\e[1;34m3 ";
+					return ses->list[LIST_TERRAIN]->list[terrain]->arg2;
+			}
+			return blank_terrain_symbol(ses, room, index, flags);
+		}
+
+		switch (line * 10 + index)
+		{
+			case 12:
+			case 14:
+				if (room_grid[EXIT_GRID_0] && room_grid[EXIT_GRID_N] && room_grid[EXIT_GRID_0]->terrain_index != -1 && room_grid[EXIT_GRID_0]->terrain_index != room_grid[EXIT_GRID_N]->terrain_index)
+				{
+					if (!HAS_BIT(ses->list[LIST_TERRAIN]->list[room_grid[EXIT_GRID_N]->terrain_index]->room->terrain_flags, TERRAIN_FLAG_DOUBLE))
+					{
+						return ses->list[LIST_TERRAIN]->list[room_grid[EXIT_GRID_N]->terrain_index]->arg2;
+					}
+				}
+				return blank_terrain_symbol(ses, room, index, flags);
+			case 11:
+			case 13:
+			case 15:
+			case 22:
+			case 24:
+				return ses->list[LIST_TERRAIN]->list[terrain]->arg2;
+		}
+		return blank_terrain_symbol(ses, room, index, flags);
+	}
+	return blank_terrain_symbol(ses, room, index, flags);
+}
+
+int follow_map(struct session *ses, char *argument)
+{
+	struct room_data *room;
+	struct exit_data *exit;;
+	int in_room, vnum;
+
+	push_call("follow_map(%p,%p)",ses,argument);
+
+	room = ses->map->room_list[ses->map->in_room];
+
+	if (HAS_BIT(ses->map->flags, MAP_FLAG_NOFOLLOW))
+	{
+		if (check_global(ses, room->vnum) && find_exit(ses, ses->map->global_vnum, argument))
+		{
+			in_room = ses->map->global_vnum;
+		}
+		else
+		{
+			in_room = ses->map->in_room;
+		}
+		exit = find_exit(ses, in_room, argument);
+
+		if (exit)
+		{
+			ses->map->dir = exit->dir;
+
+			vnum = tunnel_void(ses, in_room, exit->vnum, exit->dir);
+
+			check_all_events(ses, SUB_ARG, 0, 5, "MAP FOLLOW MAP", ntos(in_room), ntos(vnum), exit->name);
+		}
+		pop_call();
+		return 0;
+	}
+
+	if (check_global(ses, room->vnum))
+	{
+		if (find_exit(ses, ses->map->global_vnum, argument))
+		{
+			goto_room(ses, ses->map->global_vnum);
+		}
+	}
+
+	exit = find_exit(ses, ses->map->in_room, argument);
+
+	if (exit)
+	{
+		ses->map->dir = exit->dir;
+
+		in_room = ses->map->in_room;
+
+		vnum = tunnel_void(ses, in_room, exit->vnum, exit->dir);
+
+		if (ses->map->nofollow == 0)
+		{
+			if (HAS_BIT(exit->flags, EXIT_FLAG_BLOCK) || HAS_BIT(ses->map->room_list[vnum]->flags, ROOM_FLAG_BLOCK))
+			{
+				show_error(ses, LIST_COMMAND, "#MAP FOLLOW: %s {%d} HAS THE BLOCK FLAG SET.", HAS_BIT(exit->flags, EXIT_FLAG_BLOCK) ? "EXIT" : "ROOM", vnum);
+
+				pop_call();
+				return 0;
+			}
+			else
+			{
+				ses->map->nofollow++;
+				script_driver(ses, LIST_COMMAND, exit->cmd);
+				ses->map->nofollow--;
+			}
+		}
+
+		check_all_events(ses, SUB_ARG, 0, 5, "MAP FOLLOW MAP", ntos(in_room), ntos(vnum), exit->name);
+
+		add_undo(ses, "%d %d %d", vnum, in_room, MAP_UNDO_MOVE);
+
+		goto_room(ses, vnum);
+
+		if (HAS_BIT(ses->map->room_list[ses->map->in_room]->flags, ROOM_FLAG_LEAVE))
+		{
+			show_message(ses, LIST_COMMAND, "#MAP: LEAVE FLAG FOUND IN ROOM {%d}. LEAVING MAP.", ses->map->in_room);
+
+			map_leave(ses, "", "", "");
+		}
+
+		if (HAS_BIT(ses->map->flags, MAP_FLAG_VTMAP))
+		{
+			SET_BIT(ses->flags, SES_FLAG_UPDATEVTMAP);
+		}
+
+		pop_call();
+		return 1;
+	}
+
+	if (!HAS_BIT(ses->map->flags, MAP_FLAG_STATIC) && !HAS_BIT(ses->map->room_list[ses->map->in_room]->flags, ROOM_FLAG_STATIC))
+	{
+		struct listnode *node;
+
+		if ((node = search_node_list(ses->list[LIST_PATHDIR], argument)) == NULL)
+		{
+			pop_call();
+			return 0;
+		}
+
+		in_room = find_coord(ses, argument);
+
+		if (in_room)
+		{
+			show_message(ses, LIST_COMMAND, "#MAP CREATE LINK %5d {%s}.", in_room, ses->map->room_list[in_room]->name);
+
+			add_undo(ses, "%d %d %d", in_room, ses->map->in_room, MAP_UNDO_MOVE|MAP_UNDO_LINK);
+		}
+		else
+		{
+			for (in_room = 1 ; in_room < ses->map->size ; in_room++)
+			{
+				if (ses->map->room_list[in_room] == NULL)
+				{
+					break;
+				}
+			}
+
+			if (in_room == ses->map->size)
+			{
+				show_error(ses, LIST_COMMAND, "#MAP: Maximum amount of rooms of %d reached. Use #MAP RESIZE to increase the maximum.", ses->map->size);
+
+				pop_call();
+				return 1;
+			}
+			add_undo(ses, "%d %d %d", in_room, ses->map->in_room, MAP_UNDO_MOVE|MAP_UNDO_CREATE|MAP_UNDO_LINK);
+
+			create_room(ses, "{%d} {0} {} {} { } {} {} {} {} {} {1.0} {}", in_room);
+		}
+
+		exit = create_exit(ses, ses->map->in_room, "{%d} {%s} {%s}", in_room, node->arg1, node->arg1);
+
+		ses->map->dir = exit->dir;
+
+		if (find_exit(ses, in_room, node->arg2) == NULL)
+		{
+			create_exit(ses, in_room, "{%d} {%s} {%s}", ses->map->in_room, node->arg2, node->arg2);
+		}
+
+		if (ses->map->nofollow == 0)
+		{
+			ses->map->nofollow++;
+
+			script_driver(ses, LIST_COMMAND, argument);
+
+			ses->map->nofollow--;
+		}
+		goto_room(ses, in_room);
+
+		if (HAS_BIT(ses->map->flags, MAP_FLAG_VTMAP))
+		{
+			SET_BIT(ses->flags, SES_FLAG_UPDATEVTMAP);
+		}
+
+		pop_call();
+		return 1;
+	}
+	pop_call();
+	return 0;
+}
+
+void add_undo(struct session *ses, char *format, ...)
+{
+	struct link_data *link;
+	char buf[BUFFER_SIZE], *arg, dir[BUFFER_SIZE], rev[BUFFER_SIZE], flag[BUFFER_SIZE];
+	va_list args;
+
+	push_call("add_undo(%s,%s)",ses->name, format);
+
+	va_start(args, format);
+	vsprintf(buf, format, args);
+	va_end(args);
+
+	arg = get_arg_in_braces(ses, buf, dir, GET_ONE);
+	arg = get_arg_in_braces(ses, arg, rev, GET_ONE);
+	arg = get_arg_in_braces(ses, arg, flag, GET_ONE);
+
+	link = (struct link_data *) calloc(1, sizeof(struct link_data));
+
+	link->str1 = strdup(dir);
+	link->str2 = strdup(rev);
+	link->str3 = strdup(flag);
+
+	LINK(link, ses->map->undo_head, ses->map->undo_tail);
+
+	ses->map->undo_size++;
+
+	if (ses->map->undo_size > 100)
+	{
+		del_undo(ses, ses->map->undo_head);
+	}
+	pop_call();
+	return;
+}
+
+void del_undo(struct session *ses, struct link_data *link)
+{
+	UNLINK(link, ses->map->undo_head, ses->map->undo_tail);
+
+	free(link->str1);
+	free(link->str2);
+	free(link->str3);
+
+	free(link);
+
+	ses->map->undo_size--;
+}
+
+/*
+	Draws a map on a grid, original breadth first improvements by Bryan Turner
+*/
+
+
+
+struct grid_node
+{
+	int vnum;
+	int from;
+	int flags;
+	float length;
+	int dir;
+	int w;
+	int x;
+	int y;
+	int z;
+};
+
+void displaygrid_build(struct session *ses, int vnum, int x, int y, int z)
+{
+	int head, tail;
+	struct grid_node *node, *temp, list[MAP_BF_SIZE];
+	struct exit_data *exit;
+	struct room_data *room, *toroom;
+
+	push_call("displaygrid_build(%p,%d,%d,%d,%d)",ses,vnum,x,y,z);
+
+	map_grid_x = x;
+	map_grid_y = y;
+
+	head = 0;
+	tail = 1;
+
+	node = &list[head];
+
+	node->vnum   = vnum;
+	node->length = ses->map->room_list[vnum]->weight;
+	node->flags  = 0;
+
+	if (HAS_BIT(ses->map->flags, MAP_FLAG_UNICODEGRAPHICS))
+	{
+		node->x      = (x - 2) / 2 + ses->map->center_x;
+	}
+	else
+	{
+		node->x      = x / 2 + ses->map->center_x;
+	}
+	node->y      = y / 2 + ses->map->center_y;
+	node->z      = z / 2 + ses->map->center_z;
+
+
+
+	ses->map->display_stamp++;
+
+	for (vnum = 0 ; vnum < x * y ; vnum++)
+	{
+		ses->map->grid_rooms[vnum] = NULL;
+	}
+
+	while (head != tail)
+	{
+		node = &list[head];
+
+		head = (head + 1) % MAP_BF_SIZE;
+
+		room = ses->map->room_list[node->vnum];
+
+		if (ses->map->display_stamp != room->display_stamp)
+		{
+			room->display_stamp = ses->map->display_stamp;
+		}
+		else if (room->length <= node->length /*&& !HAS_BIT(room->flags, ROOM_FLAG_VOID)*/)
+		{
+			continue;
+		}
+
+		room->length = node->length;
+
+		if (node->x >= 0 && node->x < map_grid_x && node->y >= 0 && node->y < map_grid_y && node->z == 0)
+		{
+			if (ses->map->grid_rooms[node->x + map_grid_x * node->y] == NULL)
+			{
+				ses->map->grid_rooms[node->x + map_grid_x * node->y] = room;
+			}
+			else if (!HAS_BIT(room->flags, ROOM_FLAG_VOID))
+			{
+				continue;
+			}
+		}
+
+		for (exit = room->f_exit ; exit ; exit = exit->next)
+		{
+			if (exit->dir == 0)
+			{
+				continue;
+			}
+
+			toroom = ses->map->room_list[exit->vnum];
+
+			if (ses->map->display_stamp == toroom->display_stamp)
+			{
+				if (HAS_BIT(room->flags, ROOM_FLAG_VOID) || !HAS_BIT(toroom->flags, ROOM_FLAG_VOID))
+				{
+					if (room->length >= ses->map->room_list[exit->vnum]->length)
+					{
+						continue;
+					}
+				}
+			}
+
+			if (HAS_BIT(exit->flags, EXIT_FLAG_HIDE) || HAS_BIT(ses->map->room_list[exit->vnum]->flags, ROOM_FLAG_HIDE))
+			{
+				continue;
+			}
+
+			if (head == (tail + 1) % MAP_BF_SIZE)
+			{
+				break;
+			}
+
+			if (HAS_BIT(room->flags, ROOM_FLAG_VOID) && ses->map->in_room != room->vnum && get_room_exits(ses, room->vnum) % 2 == 0)
+			{
+				exit = room->exit_grid[dir_to_grid(node->dir)];
+			
+				if (exit == NULL)
+				{
+					if (ses->map->room_list[room->f_exit->vnum]->display_stamp != ses->map->display_stamp)
+					{
+						exit = room->f_exit;
+					}
+					else if (ses->map->room_list[room->l_exit->vnum]->display_stamp != ses->map->display_stamp)
+					{
+						exit = room->l_exit;
+					}
+					else if (ses->map->room_list[room->f_exit->vnum]->length <= ses->map->room_list[room->l_exit->vnum]->length)
+					{
+						exit = room->f_exit;
+					}
+					else
+					{
+						exit = room->l_exit;
+					}
+				}
+			}
+
+			temp = &list[tail];
+
+			temp->vnum   = exit->vnum;
+			temp->dir    = exit->dir;
+			temp->x      = node->x + (HAS_BIT(exit->dir, MAP_EXIT_E) ?  1 : HAS_BIT(exit->dir, MAP_EXIT_W) ? -1 : 0);
+			temp->y      = node->y + (HAS_BIT(exit->dir, MAP_EXIT_N) ?  1 : HAS_BIT(exit->dir, MAP_EXIT_S) ? -1 : 0);
+			temp->z      = node->z + (HAS_BIT(exit->dir, MAP_EXIT_U) ?  1 : HAS_BIT(exit->dir, MAP_EXIT_D) ? -1 : 0);
+			temp->length = node->length + exit->weight + ses->map->room_list[exit->vnum]->weight;
+
+			temp->flags  = 0;
+
+			tail = (tail + 1) % MAP_BF_SIZE;
+
+			if (HAS_BIT(room->flags, ROOM_FLAG_VOID) && ses->map->in_room != room->vnum && get_room_exits(ses, room->vnum) % 2 == 0)
+			{
+				temp->length = node->length + 0.000001;
+				break;
+			}
+		}
+	}
+
+	if (HAS_BIT(ses->map->flags, MAP_FLAG_UPDATETERRAIN))
+	{
+		update_terrain(ses);
+	}
+
+	{
+		int terrain;
+
+		for (y = map_grid_y - 2 ; y >= 1 ; y--)
+		{
+			for (x = 1 ; x < map_grid_x - 1 ; x++)
+			{
+				if (ses->map->grid_rooms[x + map_grid_x * y] == NULL)
+				{
+					terrain = get_terrain_index(ses, NULL, x, y);
+
+					if (terrain != -1)
+					{
+						ses->map->grid_rooms[x + map_grid_x * y] = ses->list[LIST_TERRAIN]->list[terrain]->room;
+					}
+				}
+			}
+		}
+	}
+
+	pop_call();
+	return;
+}
+
+
+void show_vtmap(struct session *ses)
+{
+	char buf[BUFFER_SIZE], out[BUFFER_SIZE], tmp[BUFFER_SIZE];
+	int x, y, line;
+	int top_row, top_col, bot_row, bot_col, rows, cols, row;
+
+	push_call("show_vtmap(%p)",ses);
+
+	if (ses->map == NULL || !HAS_BIT(ses->map->flags, MAP_FLAG_VTMAP))
+	{
+		pop_call();
+		return;
+	}
+
+	if (ses->map->room_list[ses->map->in_room] == NULL)
+	{
+		pop_call();
+		return;
+	}
+
+	if (ses != gtd->ses || HAS_BIT(gtd->ses->flags, SES_FLAG_READMUD))
+	{
+		pop_call();
+		return;
+	}
+
+	if (HAS_BIT(ses->map->flags, MAP_FLAG_RESIZE))
+	{
+		DEL_BIT(ses->map->flags, MAP_FLAG_RESIZE);
+
+		map_offset(ses, NULL, "", "");
+	}
+
+	if (ses->map->rows > 1 && ses->map->cols > 1)
+	{
+		top_row = ses->map->top_row;
+		top_col = ses->map->top_col;
+		bot_row = ses->map->bot_row;
+		bot_col = ses->map->bot_col;
+		rows    = ses->map->rows;
+		cols    = ses->map->cols;
+	}
+	else
+	{
+		top_row = 1;
+		top_col = 1;
+		bot_row = ses->split->top_row - 2;
+		bot_col = gtd->screen->cols;
+
+		rows    = ses->split->top_row - 2;
+		cols    = gtd->screen->cols;
+	}
+
+//	print_stdout("\e[%d;%d;%d;%d${", top_row, top_col, bot_row, bot_col);
+
+	if (HAS_BIT(ses->map->flags, MAP_FLAG_ASCIIGRAPHICS))
+	{
+		erase_square(ses, top_row, top_col, bot_row, bot_col);
+
+		map_grid_y = 2 + rows / 3;
+		map_grid_x = 2 + cols / 6;
+	}
+	else if (HAS_BIT(ses->map->flags, MAP_FLAG_UNICODEGRAPHICS))
+	{
+		erase_square(ses, top_row, top_col, bot_row - 1, bot_col);
+
+		map_grid_y = 2 + (rows + 2) / 2;
+		map_grid_x = 2 + (cols + 4) / 5;
+	}
+	else if (HAS_BIT(ses->map->flags, MAP_FLAG_BLOCKGRAPHICS))
+	{
+		erase_square(ses, top_row, top_col, bot_row, bot_col);
+
+		map_grid_y = 2 + rows / 2;
+		map_grid_x = 2 + cols / 5;
+	}
+	else if (HAS_BIT(ses->map->flags, MAP_FLAG_MUDFONT))
+	{
+		erase_square(ses, top_row, top_col, bot_row, bot_col);
+
+		map_grid_y = 2 + rows;
+		map_grid_x = 2 + cols / 2;
+	}
+	else
+	{
+		erase_square(ses, top_row, top_col, bot_row, bot_col);
+
+		map_grid_y = 2 + rows;
+		map_grid_x = 2 + cols;
+	}
+
+	displaygrid_build(ses, ses->map->in_room, map_grid_x, map_grid_y, 0);
+
+	save_pos(ses);
+
+	goto_pos(ses, top_row, 1);
+
+	row = top_row;
+
+	if (HAS_BIT(ses->map->flags, MAP_FLAG_ASCIIGRAPHICS))
+	{
+		for (y = map_grid_y - 2 ; y >= 1 ; y--)
+		{
+			for (line = 1 ; line <= 3 ; line++)
+			{
+				strcpy(buf, ses->map->color[MAP_COLOR_BACK]);
+
+				for (x = 1 ; x < map_grid_x - 1 ; x++)
+				{
+					strcat(buf, draw_room(ses, ses->map->grid_rooms[x + map_grid_x * y], line, x, y));
+				}
+				substitute(ses, buf, out, SUB_COL|SUB_CMP|SUB_LIT);
+
+				print_stdout("\e[%d;%dH%s\e[0m", row++, top_col, out);
+			}
+		}
+	}
+	else if (HAS_BIT(ses->map->flags, MAP_FLAG_UNICODEGRAPHICS))
+	{
+		for (y = map_grid_y - 2 ; y >= 1 ; y--)
+		{
+			for (line = 1 ; line <= 2 ; line++)
+			{
+				if (line == 2 && y == 1)
+				{
+					continue;
+				}
+				strcpy(buf, ses->map->color[MAP_COLOR_BACK]);
+
+				for (x = 1 ; x < map_grid_x - 1 ; x++)
+				{
+					if (x == map_grid_x - 2)
+					{
+						strcpy(tmp, draw_room(ses, ses->map->grid_rooms[x + map_grid_x * y], line, x, y));
+
+						cat_sprintf(buf, "%.*s", string_str_raw_len(ses, tmp, 0, 2), tmp);
+					}
+					else
+					{
+						strcat(buf, draw_room(ses, ses->map->grid_rooms[x + map_grid_x * y], line, x, y));
+					}
+				}
+				substitute(ses, buf, out, SUB_COL|SUB_CMP|SUB_LIT);
+
+				print_stdout("\e[%d;%dH%s\e[0m", row++, top_col, out);
+			}
+		}
+	}
+	else if (HAS_BIT(ses->map->flags, MAP_FLAG_BLOCKGRAPHICS))
+	{
+		for (y = map_grid_y - 2 ; y >= 1 ; y--)
+		{
+			for (line = 1 ; line <= 2 ; line++)
+			{
+				strcpy(buf, ses->map->color[MAP_COLOR_BACK]);
+
+				for (x = 1 ; x < map_grid_x - 1 ; x++)
+				{
+					strcat(buf, draw_room(ses, ses->map->grid_rooms[x + map_grid_x * y], line, x, y));
+				}
+				substitute(ses, buf, out, SUB_COL|SUB_CMP|SUB_LIT);
+
+				print_stdout("\e[%d;%dH%s\e[0m", row++, top_col, out);
+			}
+		}
+	}
+	else
+	{
+		for (y = map_grid_y - 2 ; y >= 1 ; y--)
+		{
+			strcpy(buf, ses->map->color[MAP_COLOR_BACK]);
+
+			for (x = 1 ; x < map_grid_x - 1 ; x++)
+			{
+				strcat(buf, draw_room(ses, ses->map->grid_rooms[x + map_grid_x * y], 0, x, y));
+			}
+			substitute(ses, buf, out, SUB_COL|SUB_CMP|SUB_LIT);
+
+			print_stdout("\e[%d;%dH%s\e[0m", row++, top_col, out);
+		}
+	}
+
+	restore_pos(ses);
+
+	pop_call();
+	return;
+}
+
+// http://shapecatcher.com/unicode/block/Mathematical_Operators diagonal #⊥⊤#
+
+char *draw_room(struct session *ses, struct room_data *room, int line, int x, int y)
+{
+	static char buf[201], *room_color, room_left[101], room_right[101];
+	int index, symsize = 1, exits, exit1, exit2, room1, room2, offset, flags = 0;
+//	struct room_data * room_grid[11];
+
+	push_call("draw_room(%p,%p,%d,%d,%d)",ses,room,line,x,y);
+
+	offset = HAS_BIT(ses->charset, CHARSET_FLAG_UTF8) ? LEGEND_UNICODE : LEGEND_ASCII;
+
+	room_color = ses->map->color[MAP_COLOR_ROOM];
+
+	if (room && room->vnum)
+	{
+		symsize = strip_color_strlen(ses, room->symbol);
+
+		if (HAS_BIT(room->flags, ROOM_FLAG_PATH) && room->search_stamp == ses->map->search->stamp)
+		{
+			room_color = ses->map->color[MAP_COLOR_PATH];
+		}
+		else if (*room->color)
+		{
+			room_color = room->color;
+		}
+		else
+		{
+			if (HAS_BIT(room->flags, ROOM_FLAG_INVIS))
+			{
+				room_color = ses->map->color[MAP_COLOR_INVIS];
+			}
+			else if (HAS_BIT(room->flags, ROOM_FLAG_HIDE))
+			{
+				room_color = ses->map->color[MAP_COLOR_HIDE];
+			}
+			else if (HAS_BIT(room->flags, ROOM_FLAG_AVOID))
+			{
+				room_color = ses->map->color[MAP_COLOR_AVOID];
+			}
+			else if (HAS_BIT(room->flags, ROOM_FLAG_BLOCK))
+			{
+				room_color = ses->map->color[MAP_COLOR_BLOCK];
+			}
+			else if (HAS_BIT(ses->map->flags, MAP_FLAG_SYMBOLGRAPHICS))
+			{
+				room_color = ses->map->color[MAP_COLOR_SYMBOL];
+			}
+		}
+
+		if (room->vnum == ses->map->in_room)
+		{
+			if (HAS_BIT(ses->map->flags, MAP_FLAG_DIRECTION))
+			{
+				exits = ses->map->dir;
+
+				DEL_BIT(exits, MAP_EXIT_U|MAP_EXIT_D);
+
+				switch (exits)
+				{
+					case MAP_EXIT_N:
+						index = LEGEND_ASCII_DIRS + 0;
+						break;
+					case MAP_EXIT_N+MAP_EXIT_E:
+						index = LEGEND_ASCII_DIRS + 1;
+						break;
+					case MAP_EXIT_E:
+						index = LEGEND_ASCII_DIRS + 2;
+						break;
+					case MAP_EXIT_S+MAP_EXIT_E:
+						index = LEGEND_ASCII_DIRS + 3;
+						break;
+					case MAP_EXIT_S:
+						index = LEGEND_ASCII_DIRS + 4;
+						break;
+					case MAP_EXIT_W+MAP_EXIT_S:
+						index = LEGEND_ASCII_DIRS + 5;
+						break;
+					case MAP_EXIT_W:
+						index = LEGEND_ASCII_DIRS + 6;
+						break;
+					case MAP_EXIT_W+MAP_EXIT_N:
+						index = LEGEND_ASCII_DIRS + 7;
+						break;
+					default:
+						index = LEGEND_ASCII_MISC + 1;
+						break;
+				}
+			}
+			else
+			{
+				index = LEGEND_ASCII_MISC + 0;
+			}
+		}
+	}
+
+	if (HAS_BIT(ses->map->flags, MAP_FLAG_UNICODEGRAPHICS))
+	{
+		struct room_data *room_n, *room_nw, *room_w;
+		long long exit_n, exit_nw, exit_w;
+
+		if (room)
+		{
+			if (HAS_BIT(room->flags, ROOM_FLAG_CURVED))
+			{
+				sprintf(room_left,  "%s%s", room_color, ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_RL_CURVED]);
+				sprintf(room_right, "%s%s", room_color, ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_RR_CURVED]);
+			}
+			else
+			{
+				
+				sprintf(room_left,  "%s%s", room_color, ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_RL]);
+				sprintf(room_right, "%s%s", room_color, ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_RR]);
+			}
+		}
+		room_n  = ses->map->grid_rooms[x + 0 + map_grid_x * (y + 1)];
+		room_nw = ses->map->grid_rooms[x - 1 + map_grid_x * (y + 1)];
+		room_w  = ses->map->grid_rooms[x - 1 + map_grid_x * (y + 0)];
+
+		exit_n = exit_nw = exit_w = 0;
+
+		if (room_nw && room_nw->exit_grid[EXIT_GRID_SE])
+		{
+			SET_BIT(exit_nw, UNICODE_DIR_SE);
+		}
+
+		if (room_w && room_w->exit_grid[EXIT_GRID_NE])
+		{
+			SET_BIT(exit_nw, UNICODE_DIR_NE);
+		}
+
+		if (room_n && room_n->exit_grid[EXIT_GRID_SW])
+		{
+			SET_BIT(exit_nw, UNICODE_DIR_SW);
+		}
+
+		if (room && HAS_BIT(room->exit_dirs, MAP_DIR_NW))
+		{
+			SET_BIT(exit_nw, UNICODE_DIR_NW);
+		}
+
+
+		if (room_n && HAS_BIT(room_n->exit_dirs, MAP_DIR_D))
+		{
+			SET_BIT(exit_n, MAP_DIR_D);
+		}
+
+		if (room && room->exit_grid[EXIT_GRID_N])
+		{
+			SET_BIT(exit_n, MAP_DIR_N);
+		}
+
+		if (room_n && room_n->exit_grid[EXIT_GRID_S])
+		{
+			SET_BIT(exit_n, MAP_DIR_S);
+		}
+
+		if (room && room->exit_grid[EXIT_GRID_W])
+		{
+			SET_BIT(exit_w, UNICODE_DIR_W);
+		}
+
+		if (room_w && room_w->exit_grid[EXIT_GRID_E])
+		{
+			SET_BIT(exit_w, UNICODE_DIR_E);
+		}
+
+		sprintf(buf, "%s", ses->map->color[MAP_COLOR_EXIT]);
+
+		switch (line)
+		{
+			case 1:
+				switch (exit_nw)
+				{
+					case 0:
+						strcat(buf, draw_terrain_symbol(ses, room, line, 1, x, y, TERRAIN_FLAG_DOUBLE));
+						strcat(buf, draw_terrain_symbol(ses, room, line, 2, x, y, TERRAIN_FLAG_DOUBLE));
+						break;
+
+					case UNICODE_DIR_SE:
+						cat_sprintf(buf, "%s%s", get_exit_color(ses, 0, room_nw->exit_grid[EXIT_GRID_SE]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_SE]);
+						strcat(buf, draw_terrain_symbol(ses, room, line, 2, x, y, flags));
+						break;
+					case UNICODE_DIR_NE:
+						cat_sprintf(buf, "%s%s", get_exit_color(ses, 0, room_w->exit_grid[EXIT_GRID_NE]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_NE]);
+						strcat(buf, draw_terrain_symbol(ses, room, line, 2, x, y, flags));
+						break;
+					case UNICODE_DIR_SE|UNICODE_DIR_NE:
+						cat_sprintf(buf, "%s%s", room_nw->length < room_w->length ? get_exit_color(ses, 0, room_nw->exit_grid[EXIT_GRID_SE]) : get_exit_color(ses, 0, room_w->exit_grid[EXIT_GRID_NE]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_SE + UNICODE_DIR_NE]);
+						strcat(buf, draw_terrain_symbol(ses, room, line, 2, x, y, flags));
+						break;
+
+					case UNICODE_DIR_SE|UNICODE_DIR_NW:
+						cat_sprintf(buf, "%s%s", room_nw->length < room->length ? get_exit_color(ses, 0, room_nw->exit_grid[EXIT_GRID_SE]) : get_exit_color(ses, 0, room->exit_grid[EXIT_GRID_NW]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_SE + UNICODE_DIR_NW]);
+						break;
+
+					case UNICODE_DIR_NE|UNICODE_DIR_SW:
+						cat_sprintf(buf, "%s%s", room_w->length < room_n->length ? get_exit_color(ses, 0, room_w->exit_grid[EXIT_GRID_NE]) : get_exit_color(ses, 0, room_n->exit_grid[EXIT_GRID_SW]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_NE + UNICODE_DIR_SW]);
+						break;
+//
+					case UNICODE_DIR_NE|UNICODE_DIR_NW:
+						cat_sprintf(buf, "%s%s%s%s", get_exit_color(ses, 0, room_w->exit_grid[EXIT_GRID_NE]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_NE], get_exit_color(ses, 0, room->exit_grid[EXIT_GRID_NW]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_NW]);
+						break;
+
+					case UNICODE_DIR_NE|UNICODE_DIR_SW|UNICODE_DIR_NW:
+						cat_sprintf(buf, "%s%s%s%s", get_exit_color(ses, 0, room_w->exit_grid[EXIT_GRID_NE]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_NE], room_n->length < room->length ? get_exit_color(ses, 0, room_n->exit_grid[EXIT_GRID_SW]) : get_exit_color(ses, 0, room->exit_grid[EXIT_GRID_NW]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_NW + UNICODE_DIR_SW]);
+						break;
+
+					case UNICODE_DIR_SE|UNICODE_DIR_SW:
+						cat_sprintf(buf, "%s%s%s%s", get_exit_color(ses, 0, room_nw->exit_grid[EXIT_GRID_SE]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_SE], get_exit_color(ses, 0, room_n->exit_grid[EXIT_GRID_SW]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_SW]);
+						break;
+
+					case UNICODE_DIR_SE|UNICODE_DIR_SW|UNICODE_DIR_NW:
+						cat_sprintf(buf, "%s%s%s%s", get_exit_color(ses, 0, room_nw->exit_grid[EXIT_GRID_SE]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_SE], room_n->length < room->length ? get_exit_color(ses, 0, room_n->exit_grid[EXIT_GRID_SW]) : get_exit_color(ses, 0, room->exit_grid[EXIT_GRID_NW]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_NW + UNICODE_DIR_SW]);
+						break;
+
+					case UNICODE_DIR_SE|UNICODE_DIR_NE|UNICODE_DIR_SW:
+						cat_sprintf(buf, "%s%s%s%s", room_nw->length < room_w->length ? get_exit_color(ses, 0, room_nw->exit_grid[EXIT_GRID_SE]) : get_exit_color(ses, 0, room_w->exit_grid[EXIT_GRID_NE]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_SE + UNICODE_DIR_NE], get_exit_color(ses, 0, room_n->exit_grid[EXIT_GRID_SW]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_SW]);
+						break;
+
+					case UNICODE_DIR_SE|UNICODE_DIR_NE|UNICODE_DIR_NW:
+						cat_sprintf(buf, "%s%s%s%s", room_nw->length < room_w->length ? get_exit_color(ses, 0, room_nw->exit_grid[EXIT_GRID_SE]) : get_exit_color(ses, 0, room_w->exit_grid[EXIT_GRID_NE]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_SE + UNICODE_DIR_NE], get_exit_color(ses, 0, room->exit_grid[EXIT_GRID_NW]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_NW]);
+						break;
+
+					case UNICODE_DIR_SW:
+						strcat(buf, draw_terrain_symbol(ses, room, line, 1, x, y, flags));
+						cat_sprintf(buf, "%s%s", get_exit_color(ses, 0, room_n->exit_grid[EXIT_GRID_SW]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_SW]);
+						break;
+
+					case UNICODE_DIR_NW:
+						strcat(buf, draw_terrain_symbol(ses, room, line, 1, x, y, flags));
+						cat_sprintf(buf, "%s%s", get_exit_color(ses, 0, room->exit_grid[EXIT_GRID_NW]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_NW]);
+						break;
+
+					case UNICODE_DIR_SW|UNICODE_DIR_NW:
+						strcat(buf, draw_terrain_symbol(ses, room, line, 1, x, y, flags));
+						cat_sprintf(buf, "%s%s", room_n->length < room->length ? get_exit_color(ses, 0, room_n->exit_grid[EXIT_GRID_SW]) : get_exit_color(ses, 0, room->exit_grid[EXIT_GRID_NW]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_SW + UNICODE_DIR_NW]);
+						break;
+
+					case UNICODE_DIR_NW|UNICODE_DIR_SE|UNICODE_DIR_NE|UNICODE_DIR_SW:
+//						cat_sprintf(buf, "ᐳᐸ");
+						cat_sprintf(buf, "%s%s%s%s", room_nw->length < room_w->length ? get_exit_color(ses, 0, room_nw->exit_grid[EXIT_GRID_SE]) : get_exit_color(ses, 0, room_w->exit_grid[EXIT_GRID_NE]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_SE + UNICODE_DIR_NE], room_n->length < room->length ? get_exit_color(ses, 0, room_n->exit_grid[EXIT_GRID_SW]) : get_exit_color(ses, 0, room->exit_grid[EXIT_GRID_NW]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_NW + UNICODE_DIR_SW]);
+						break;
+					default:
+						cat_sprintf(buf, "??");
+						break;
+				}
+
+				if (!HAS_BIT(exit_n, MAP_DIR_D) && !HAS_BIT(exit_n, MAP_DIR_N|MAP_DIR_S))
+				{
+					strcat(buf, draw_terrain_symbol(ses, room, line, 3, x, y, TERRAIN_FLAG_DOUBLE));
+					strcat(buf, draw_terrain_symbol(ses, room, line, 4, x, y, TERRAIN_FLAG_DOUBLE));
+				}
+				else
+				{
+					if (HAS_BIT(exit_n, MAP_DIR_D))
+					{
+						cat_sprintf(buf, "%s%s", get_exit_color(ses, 0, room_n->exit_grid[EXIT_GRID_D]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_D]);
+					}
+					else
+					{
+						strcat(buf, draw_terrain_symbol(ses, room, line, 3, x, y, flags));
+					}
+
+					switch (HAS_BIT(exit_n, MAP_DIR_N|MAP_DIR_S))
+					{
+						case MAP_DIR_N:
+							cat_sprintf(buf, "%s%s", get_exit_color(ses, 0, room->exit_grid[EXIT_GRID_N]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_N]);
+							break;
+						case MAP_DIR_S:
+							cat_sprintf(buf, "%s%s", get_exit_color(ses, 0, room_n->exit_grid[EXIT_GRID_S]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_S]);
+							break;
+						case MAP_DIR_N|MAP_DIR_S:
+							cat_sprintf(buf, "%s%s", get_exit_color(ses, room->vnum, room->exit_grid[EXIT_GRID_N]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_NS]);
+							break;
+						default:
+							strcat(buf, draw_terrain_symbol(ses, room, line, 4, x, y, flags));
+							break;
+					}
+				}
+
+				if (room && room->exit_grid[EXIT_GRID_U])
+				{
+					cat_sprintf(buf, "%s%s", get_exit_color(ses, 0, room->exit_grid[EXIT_GRID_U]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_U]);
+				}
+				else
+				{
+					strcat(buf, draw_terrain_symbol(ses, room, line, 5, x, y, flags));
+				}
+				break;
+
+			case 2:
+				buf[0] = 0;
+
+				if (room == NULL || room->vnum == 0)
+				{
+					if (HAS_BIT(exit_w, MAP_DIR_W))
+					{
+						sprintf(buf, "%s%s%s",
+							get_exit_color(ses, 0, room_w->exit_grid[EXIT_GRID_E]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_E],
+							draw_terrain_symbol(ses, room, line, 2, x, y, flags));
+					}
+					else
+					{
+						strcat(buf, draw_terrain_symbol(ses, room, line, 1, x, y, TERRAIN_FLAG_DOUBLE));
+						strcat(buf, draw_terrain_symbol(ses, room, line, 2, x, y, TERRAIN_FLAG_DOUBLE));
+					}
+					strcat(buf, draw_terrain_symbol(ses, room, line, 3, x, y, TERRAIN_FLAG_DOUBLE));
+					strcat(buf, draw_terrain_symbol(ses, room, line, 4, x, y, TERRAIN_FLAG_DOUBLE));
+
+					strcat(buf, draw_terrain_symbol(ses, room, line, 5, x, y, flags));
+
+					pop_call();
+					return buf;
+				}
+
+				switch (exit_w)
+				{
+					case 0:
+						strcat(buf, draw_terrain_symbol(ses, room, line, 1, x, y, TERRAIN_FLAG_DOUBLE));
+						strcat(buf, draw_terrain_symbol(ses, room, line, 2, x, y, TERRAIN_FLAG_DOUBLE));
+						break;
+					case UNICODE_DIR_E:
+						sprintf(buf, "%s%s%s", get_exit_color(ses, 0, room_w->exit_grid[EXIT_GRID_E]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_E], draw_terrain_symbol(ses, room, line, 2, x, y, flags));
+						break;
+					case UNICODE_DIR_W:
+						sprintf(buf, "%s%s%s", get_exit_color(ses, 0, room->exit_grid[EXIT_GRID_W]), draw_terrain_symbol(ses, room, line, 1, x, y, flags), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_W]);
+						break;
+					case UNICODE_DIR_E|UNICODE_DIR_W:
+						if (room->exit_grid[EXIT_GRID_W]->vnum == room_w->vnum && room_w->exit_grid[EXIT_GRID_E]->vnum == room->vnum)
+						{
+							// ‒‒
+							sprintf(buf, "%s%s%s%s", get_exit_color(ses, 0, room_w->exit_grid[EXIT_GRID_E]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_EW], get_exit_color(ses, 0, room->exit_grid[EXIT_GRID_W]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_EW]);
+						}
+						else
+						{
+							sprintf(buf, "%s%s%s%s", get_exit_color(ses, 0, room_w->exit_grid[EXIT_GRID_E]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_E], get_exit_color(ses, 0, room->exit_grid[EXIT_GRID_W]), ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_W]);
+						}
+						break;
+					default:
+						strcat(buf, "??");
+						break;
+				}
+
+				if (room->vnum == ses->map->in_room)
+				{
+					cat_sprintf(buf, "%s%s%s%s", room_left, ses->map->color[MAP_COLOR_USER], ses->map->legend[offset + index], room_right);
+				}
+				else if (symsize > 3)
+				{
+					cat_sprintf(buf, "%s%-3s", ses->map->color[MAP_COLOR_SYMBOL], room->symbol);
+				}
+				else
+				{
+					if (symsize > 1)
+					{
+						cat_sprintf(buf, "%s%-3s", ses->map->color[MAP_COLOR_SYMBOL], room->symbol);
+					}
+					else if (HAS_BIT(room->flags, ROOM_FLAG_VOID))
+					{
+						if (HAS_BIT(room->exit_dirs, MAP_DIR_W))
+						{
+							cat_sprintf(buf, "%s─", get_exit_color(ses, 0, room->exit_grid[EXIT_GRID_W]));
+						}
+						else
+						{
+							cat_sprintf(buf, " ");
+						}
+
+						if (*room->symbol != ' ' && symsize == 1)
+						{
+							cat_sprintf(buf, "%s%-1s", ses->map->color[MAP_COLOR_SYMBOL], room->symbol);
+						}
+						else
+						{
+							if (HAS_BIT(room->exit_dirs, MAP_DIR_N|MAP_DIR_E|MAP_DIR_S|MAP_DIR_W) == (MAP_DIR_N|MAP_DIR_E|MAP_DIR_S|MAP_DIR_W))
+							{
+								if (get_exit_length(ses, room->exit_grid[EXIT_GRID_N]) < get_exit_length(ses, room->exit_grid[EXIT_GRID_W]))
+								{
+									cat_sprintf(buf, "%s│", get_exit_color(ses, 0, room->exit_grid[EXIT_GRID_N]));
+								}
+								else
+								{
+									cat_sprintf(buf, "%s─", get_exit_color(ses, 0, room->exit_grid[EXIT_GRID_W]));
+								}
+							}
+							else if (HAS_BIT(room->exit_dirs, MAP_DIR_E|MAP_DIR_W) == (MAP_DIR_E|MAP_DIR_W))
+							{
+								cat_sprintf(buf, "%s─", get_exit_color(ses, 0, room->exit_grid[EXIT_GRID_W]));
+							}
+							else if (HAS_BIT(room->exit_dirs, MAP_DIR_N|MAP_DIR_S) == (MAP_DIR_N|MAP_DIR_S))
+							{
+								cat_sprintf(buf, "%s│", get_exit_color(ses, room->vnum, room->exit_grid[EXIT_GRID_N]));
+							}
+							else if (HAS_BIT(room->exit_dirs, MAP_DIR_N|MAP_DIR_E|MAP_DIR_S|MAP_DIR_W|MAP_DIR_NW|MAP_DIR_NE|MAP_DIR_SE|MAP_DIR_SW) == (MAP_DIR_S))
+							{
+								cat_sprintf(buf, "%s│", get_exit_color(ses, room->vnum, room->exit_grid[EXIT_GRID_S]));
+							}
+							else if (HAS_BIT(room->exit_dirs, MAP_DIR_N|MAP_DIR_E|MAP_DIR_S|MAP_DIR_W|MAP_DIR_NW|MAP_DIR_NE|MAP_DIR_SE|MAP_DIR_SW) == (MAP_DIR_N))
+							{
+								cat_sprintf(buf, "%s│", get_exit_color(ses, room->vnum, room->exit_grid[EXIT_GRID_N]));
+							}
+							else if (HAS_BIT(room->exit_dirs, MAP_DIR_N|MAP_DIR_E|MAP_DIR_S|MAP_DIR_W|MAP_DIR_NW|MAP_DIR_NE|MAP_DIR_SE|MAP_DIR_SW) == (MAP_DIR_E))
+							{
+								cat_sprintf(buf, "%s─", get_exit_color(ses, 0, room->exit_grid[EXIT_GRID_E]));
+							}
+							else if (HAS_BIT(room->exit_dirs, MAP_DIR_N|MAP_DIR_E|MAP_DIR_S|MAP_DIR_W|MAP_DIR_NW|MAP_DIR_NE|MAP_DIR_SE|MAP_DIR_SW) == (MAP_DIR_W))
+							{
+								cat_sprintf(buf, "%s─", get_exit_color(ses, 0, room->exit_grid[EXIT_GRID_W]));
+							}
+							else if (HAS_BIT(room->exit_dirs, MAP_DIR_N|MAP_DIR_E|MAP_DIR_S|MAP_DIR_W|MAP_DIR_NW|MAP_DIR_NE|MAP_DIR_SE|MAP_DIR_SW) == (MAP_DIR_N|MAP_DIR_E|MAP_DIR_W))
+							{
+								cat_sprintf(buf, "%s├", get_exit_color(ses, room->vnum, room->exit_grid[EXIT_GRID_N]));
+							}
+							else if (HAS_BIT(room->exit_dirs, MAP_DIR_N|MAP_DIR_E|MAP_DIR_S|MAP_DIR_W|MAP_DIR_NW|MAP_DIR_NE|MAP_DIR_SE|MAP_DIR_SW) == (MAP_DIR_S|MAP_DIR_E|MAP_DIR_W))
+							{
+								cat_sprintf(buf, "%s┬", get_exit_color(ses, 0, room->exit_grid[EXIT_GRID_E]));
+							}
+							else if (HAS_BIT(room->exit_dirs, MAP_DIR_NW|MAP_DIR_SW) && HAS_BIT(room->exit_dirs, MAP_DIR_E))
+							{
+								cat_sprintf(buf, "%s─", get_exit_color(ses, room->vnum, room->exit_grid[EXIT_GRID_E]));
+							}
+							else if (HAS_BIT(room->exit_dirs, MAP_DIR_NE))
+							{
+								cat_sprintf(buf, "%s╴", get_exit_color(ses, 0, room->exit_grid[EXIT_GRID_NE]));
+							}
+							else if (HAS_BIT(room->exit_dirs, MAP_DIR_SE))
+							{
+								cat_sprintf(buf, "%s╴", get_exit_color(ses, 0, room->exit_grid[EXIT_GRID_SE]));
+							}
+							else if (HAS_BIT(room->exit_dirs, MAP_DIR_SW))
+							{
+								cat_sprintf(buf, "%s╴", get_exit_color(ses, 0, room->exit_grid[EXIT_GRID_SW]));
+							}
+							else if (HAS_BIT(room->exit_dirs, MAP_DIR_NW))
+							{
+								cat_sprintf(buf, "%s╴", get_exit_color(ses, 0, room->exit_grid[EXIT_GRID_NW]));
+							}
+							else
+							{
+								cat_sprintf(buf, " ");
+							}
+						}
+
+						if (HAS_BIT(room->exit_dirs, MAP_DIR_E))
+						{
+							cat_sprintf(buf, "%s─", get_exit_color(ses, 0, room->exit_grid[EXIT_GRID_E]));
+						}
+						else
+						{
+							cat_sprintf(buf, " ");
+						}
+					}
+					else
+					{
+						cat_sprintf(buf, "%s%s%-1s%s", room_left, ses->map->color[MAP_COLOR_SYMBOL], room->symbol, room_right);
+					}
+				}
+				cat_sprintf(buf, "%s", ses->map->color[MAP_COLOR_BACK]);
+
+				break;
+		}
+		pop_call();
+		return buf;
+	}
+
+	if (HAS_BIT(ses->map->flags, MAP_FLAG_BLOCKGRAPHICS))
+	{
+		struct room_data *room_w;
+		long long exit_w;
+
+		room_w = ses->map->grid_rooms[x - 1 + map_grid_x * (y + 0)];
+		exit_w = 0;
+
+		if (room_w)
+		{
+			if (HAS_BIT(room_w->exit_dirs, MAP_DIR_E))
+			{
+				SET_BIT(exit_w, MAP_DIR_E);
+			}
+			if (HAS_BIT(room_w->exit_dirs, MAP_DIR_U))
+			{
+				SET_BIT(exit_w, MAP_DIR_U);
+			}
+		}
+
+		sprintf(buf, "%s", room_color);
+
+		switch (line)
+		{
+			case 1:
+				switch (exit_w)
+				{
+					case 0:
+						strcat(buf, " ");
+						break;
+					case MAP_DIR_E:
+						strcat(buf, "═");
+						break;
+					case MAP_DIR_E|MAP_DIR_U:
+						strcat(buf, "═̂");
+						break;
+					case MAP_DIR_U:
+						strcat(buf, " ̂");
+						break;
+					default:
+						strcat(buf, "?");
+						break;
+				}
+
+				if (room == NULL)
+				{
+					strcat(buf, "    ");
+				}
+				else
+				{
+					strcat(buf, HAS_BIT(room->exit_dirs, MAP_DIR_W) ? "═" : " ");
+
+					if (room->vnum == ses->map->in_room)
+					{
+						cat_sprintf(buf, "%s", ses->map->color[MAP_COLOR_USER]);
+					}
+
+					switch (HAS_BIT(room->exit_dirs, MAP_DIR_N|MAP_DIR_W))
+					{
+						case MAP_DIR_N:
+							strcat(buf, "║");
+							break;
+						case MAP_DIR_W:
+							strcat(buf, "═");
+							break;
+						case MAP_DIR_N|MAP_DIR_W:
+							strcat(buf, "╝");
+							break;
+						default:
+							strcat(buf, "╔");
+							break;
+					}
+
+
+					strcat(buf, HAS_BIT(room->exit_dirs, MAP_DIR_N) ? " " : "═");
+
+
+					switch (HAS_BIT(room->exit_dirs, MAP_DIR_N|MAP_DIR_E))
+					{
+						case MAP_DIR_N:
+							strcat(buf, "║");
+							break;
+						case MAP_DIR_E:
+							strcat(buf, "═");
+							break;
+						case MAP_DIR_N|MAP_DIR_E:
+							strcat(buf, "╚");
+							break;
+						default:
+							strcat(buf, "╗");
+							break;
+					}
+				}
+				break;
+
+			case 2:
+				switch (exit_w)
+				{
+					case 0:
+						strcat(buf, " ");
+						break;
+					case MAP_DIR_E:
+						strcat(buf, "═");
+						break;
+					case MAP_DIR_E|MAP_DIR_U:
+						strcat(buf, "═");
+						break;
+					default:
+						strcat(buf, " ");
+						break;
+				}
+
+				if (room == NULL)
+				{
+					strcat(buf, " ");
+				}
+				else
+				{
+					switch (HAS_BIT(room->exit_dirs, MAP_DIR_W|MAP_DIR_D))
+					{
+						case MAP_DIR_W:
+							strcat(buf, "═");
+							break;
+						case MAP_DIR_W|MAP_DIR_D:
+							strcat(buf, "═̬");
+							break;
+						case MAP_DIR_D:
+							strcat(buf, " ̬");
+							break;
+						default:
+							strcat(buf, " ");
+							break;
+					}
+				}
+
+				if (room == NULL)
+				{
+					strcat(buf, "   ");
+				}
+				else
+				{
+					if (room->vnum == ses->map->in_room)
+					{
+						cat_sprintf(buf, "%s", ses->map->color[MAP_COLOR_USER]);
+					}
+
+					switch (HAS_BIT(room->exit_dirs, MAP_DIR_S|MAP_DIR_W))
+					{
+						case MAP_DIR_S:
+							strcat(buf, "║");
+							break;
+						case MAP_DIR_W:
+							strcat(buf, "═");
+							break;
+						case MAP_DIR_S|MAP_DIR_W:
+							strcat(buf, "╗");
+								break;
+						default:
+							strcat(buf, "╚");
+							break;
+					}
+
+					strcat(buf, HAS_BIT(room->exit_dirs, MAP_DIR_S) ? " " : "═");
+
+					switch (HAS_BIT(room->exit_dirs, MAP_DIR_S|MAP_DIR_E))
+					{
+						case MAP_DIR_S:
+							strcat(buf, "║");
+							break;
+						case MAP_DIR_E:
+							strcat(buf, "═");
+							break;
+						case MAP_DIR_S|MAP_DIR_E:
+							strcat(buf, "╔");
+							break;
+						default:
+							strcat(buf, "╝");
+							break;
+					}
+				}
+				break;
+		}
+		pop_call();
+		return buf;
+	}
+
+	if (room == NULL || room->vnum == 0)
+	{
+		if (HAS_BIT(ses->map->flags, MAP_FLAG_ASCIIGRAPHICS))
+		{
+			strcpy(buf, "");
+
+			for (index = 1 ; index <= 6 ; index++)
+			{
+				strcat(buf, draw_terrain_symbol(ses, room, line, index, x, y, TERRAIN_FLAG_DOUBLE));
+			}
+		}
+		else if (HAS_BIT(ses->map->flags, MAP_FLAG_MUDFONT))
+		{
+			sprintf(buf, "  ");
+		}
+		else
+		{
+			sprintf(buf, " ");
+		}
+		pop_call();
+		return buf;
+	}
+
+
+	if (HAS_BIT(ses->map->flags, MAP_FLAG_ASCIIGRAPHICS))
+	{
+		strcpy(buf, "");
+
+		switch (line)
+		{
+			case 1:
+				if (room->exit_grid[EXIT_GRID_NW])
+				{
+					strcat(buf, get_exit_color(ses, room->vnum, room->exit_grid[EXIT_GRID_NW]));
+					strcat(buf, "\\");
+					strcat(buf, draw_terrain_symbol(ses, room, line, 2, x, y, flags));
+				}
+				else
+				{
+					strcat(buf, draw_terrain_symbol(ses, room, line, 1, x, y, TERRAIN_FLAG_DOUBLE));
+					strcat(buf, draw_terrain_symbol(ses, room, line, 2, x, y, TERRAIN_FLAG_DOUBLE));
+				}
+
+				if (room->exit_grid[EXIT_GRID_N] || room->exit_grid[EXIT_GRID_U])
+				{
+					cat_sprintf(buf, "%s%s", get_exit_color(ses, room->vnum, room->exit_grid[EXIT_GRID_N]), room->exit_grid[EXIT_GRID_N]  ? "|"  : draw_terrain_symbol(ses, room, line, 3, x, y, flags));
+					cat_sprintf(buf, "%s%s", get_exit_color(ses, room->vnum, room->exit_grid[EXIT_GRID_U]), room->exit_grid[EXIT_GRID_U]  ? "+"  : draw_terrain_symbol(ses, room, line, 4, x, y, flags));
+				}
+				else
+				{
+					strcat(buf, draw_terrain_symbol(ses, room, line, 3, x, y, TERRAIN_FLAG_DOUBLE));
+					strcat(buf, draw_terrain_symbol(ses, room, line, 4, x, y, TERRAIN_FLAG_DOUBLE));
+				}
+
+				if (room->exit_grid[EXIT_GRID_NE])
+				{
+					cat_sprintf(buf, "%s%s%s", get_exit_color(ses, room->vnum, room->exit_grid[EXIT_GRID_NE]), "/", draw_terrain_symbol(ses, room, line, 6, x, y, flags));
+				}
+				else
+				{
+					strcat(buf, draw_terrain_symbol(ses, room, line, 5, x, y, TERRAIN_FLAG_DOUBLE));
+					strcat(buf, draw_terrain_symbol(ses, room, line, 6, x, y, TERRAIN_FLAG_DOUBLE));
+				}
+				break;
+
+			case 2:
+				if (HAS_BIT(room->flags, ROOM_FLAG_CURVED))
+				{
+					sprintf(room_left,  "%s%s", room_color, ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_RL_CURVED]);
+					sprintf(room_right, "%s%s", room_color, ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_RR_CURVED]);
+				}
+				else
+				{
+					sprintf(room_left,  "%s%s", room_color, ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_RL]);
+					sprintf(room_right, "%s%s", room_color, ses->map->legend[LEGEND_UNICODE_GRAPHICS + UNICODE_DIR_RR]);
+				}
+
+				if (!HAS_BIT(ses->map->flags, MAP_FLAG_ASCIIVNUMS) && symsize <= 3)
+				{
+					cat_sprintf(buf, "%s%s",
+						get_exit_color(ses, room->vnum, room->exit_grid[EXIT_GRID_W]),
+						room->exit_grid[EXIT_GRID_W]  ? "-"  : draw_terrain_symbol(ses, room, line, 1, x, y, flags));
+				}
+
+				if (room->vnum == ses->map->in_room)
+				{
+					if (!HAS_BIT(ses->map->flags, MAP_FLAG_ASCIIVNUMS))
+					{
+						cat_sprintf(buf, "%s%s%s%s", room_left, ses->map->color[MAP_COLOR_USER], ses->map->legend[index], room_right);
+					}
+					else
+					{
+						if (HAS_BIT(ses->map->flags, MAP_FLAG_ASCIILENGTH))
+						{
+							cat_sprintf(buf, "%s%5.1f", ses->map->color[MAP_COLOR_USER], room->length, ses->map->color[MAP_COLOR_EXIT]);
+						}
+						else
+						{
+							cat_sprintf(buf, "%s%05d", ses->map->color[MAP_COLOR_USER], room->vnum);
+						}
+					}
+				}
+				else
+				{
+					if (HAS_BIT(ses->map->flags, MAP_FLAG_ASCIIVNUMS))
+					{
+						if (HAS_BIT(ses->map->flags, MAP_FLAG_ASCIILENGTH))
+						{
+							cat_sprintf(buf, "%s%5.1f%s", ses->map->color[MAP_COLOR_USER], room->length, ses->map->color[MAP_COLOR_EXIT]);
+						}
+						else
+						{
+							cat_sprintf(buf, "%s%05d%s", room_color, room->vnum, ses->map->color[MAP_COLOR_EXIT]);
+						}
+					}
+					else if (symsize > 3)
+					{
+						cat_sprintf(buf, "%s%-5s", ses->map->color[MAP_COLOR_SYMBOL], room->symbol);
+					}
+					else if (HAS_BIT(room->flags, ROOM_FLAG_VOID))
+					{
+						if (*room->symbol != ' ' && symsize == 1)
+						{
+							if (room->exit_dirs == (MAP_DIR_E|MAP_DIR_W))
+							{
+								cat_sprintf(buf, "%s-%s%s%s-", ses->map->color[MAP_COLOR_EXIT], ses->map->color[MAP_COLOR_SYMBOL], room->symbol, ses->map->color[MAP_COLOR_EXIT]);
+							}
+							else
+							{
+								cat_sprintf(buf, "%s %-2s", ses->map->color[MAP_COLOR_SYMBOL], room->symbol);
+							}
+						}
+						else if (*room->symbol != ' ' && strip_color_strlen(ses, room->symbol) > 1)
+						{
+							cat_sprintf(buf, "%s %-2s", ses->map->color[MAP_COLOR_SYMBOL], room->symbol);
+						}
+						else
+						{
+							strcat(buf, ses->map->color[MAP_COLOR_EXIT]);
+
+							switch (room->exit_dirs)
+							{
+								case MAP_DIR_N|MAP_DIR_S:
+									cat_sprintf(buf, " | ");
+									break;
+								case MAP_DIR_E|MAP_DIR_W:
+									cat_sprintf(buf, "---");
+									break;
+								case MAP_DIR_NE|MAP_DIR_SW:
+									cat_sprintf(buf, " / ");
+									break;
+								case MAP_DIR_NW|MAP_DIR_SE:
+									cat_sprintf(buf, " \\ ");
+									break;
+								default:
+									cat_sprintf(buf, " * ");
+									break;
+							}
+						}
+					}
+					else
+					{
+						if (strip_color_strlen(ses, room->symbol) <= 1)
+						{
+							cat_sprintf(buf, "%s%s%-1s%s", room_left, ses->map->color[MAP_COLOR_SYMBOL], room->symbol, room_right);
+						}
+						else
+						{
+							cat_sprintf(buf, "%s%s%-3s", room_color, ses->map->color[MAP_COLOR_SYMBOL], room->symbol);
+						}
+					}
+				}
+
+				if (HAS_BIT(ses->map->flags, MAP_FLAG_ASCIIVNUMS) || symsize > 3)
+				{
+					cat_sprintf(buf, "%s%s", get_exit_color(ses, room->vnum, room->exit_grid[EXIT_GRID_E]), room->exit_grid[EXIT_GRID_E] ? "-" : draw_terrain_symbol(ses, room, line, 6, x, y, flags));
+				}
+				else
+				{
+					if (room->exit_grid[EXIT_GRID_E])
+					{
+						cat_sprintf(buf, "%s--", get_exit_color(ses, room->vnum, room->exit_grid[EXIT_GRID_E]));
+					}
+					else
+					{
+						strcat(buf, draw_terrain_symbol(ses, room, line, 5, x, y, TERRAIN_FLAG_DOUBLE));
+						strcat(buf, draw_terrain_symbol(ses, room, line, 6, x, y, TERRAIN_FLAG_DOUBLE));
+					}
+				}
+				break;
+
+			case 3:
+				if (room->exit_grid[EXIT_GRID_SW] || room->exit_grid[EXIT_GRID_D])
+				{
+					cat_sprintf(buf, "%s%s", get_exit_color(ses, room->vnum, room->exit_grid[EXIT_GRID_SW]), room->exit_grid[EXIT_GRID_SW] ? "/"  : draw_terrain_symbol(ses, room, line, 1, x, y, flags));
+					cat_sprintf(buf, "%s%s", get_exit_color(ses, room->vnum, room->exit_grid[EXIT_GRID_D]), room->exit_grid[EXIT_GRID_D]  ? "-"  : draw_terrain_symbol(ses, room, line, 2, x, y, flags));
+				}
+				else
+				{
+					strcat(buf, draw_terrain_symbol(ses, room, line, 1, x, y, TERRAIN_FLAG_DOUBLE));
+					strcat(buf, draw_terrain_symbol(ses, room, line, 2, x, y, TERRAIN_FLAG_DOUBLE));
+				}
+
+				if (room->exit_grid[EXIT_GRID_S])
+				{
+					cat_sprintf(buf, "%s|%s", get_exit_color(ses, room->vnum, room->exit_grid[EXIT_GRID_S]), draw_terrain_symbol(ses, room, line, 4, x, y, flags));
+				}
+				else
+				{
+					strcat(buf, draw_terrain_symbol(ses, room, line, 3, x, y, TERRAIN_FLAG_DOUBLE));
+					strcat(buf, draw_terrain_symbol(ses, room, line, 4, x, y, TERRAIN_FLAG_DOUBLE));
+				}
+
+				if (room->exit_grid[EXIT_GRID_SE])
+				{
+					cat_sprintf(buf, "%s\\%s", get_exit_color(ses, room->vnum, room->exit_grid[EXIT_GRID_SE]), draw_terrain_symbol(ses, room, line, 6, x, y, flags));
+				}
+				else
+				{
+					strcat(buf, draw_terrain_symbol(ses, room, line, 5, x, y, TERRAIN_FLAG_DOUBLE));
+					strcat(buf, draw_terrain_symbol(ses, room, line, 6, x, y, TERRAIN_FLAG_DOUBLE));
+				}
+				break;
+		}
+		pop_call();
+		return buf;
+	}
+
+
+	if (room->vnum == ses->map->in_room)
+	{
+		exits = ses->map->dir;
+
+		DEL_BIT(exits, MAP_EXIT_U|MAP_EXIT_D);
+
+		if (HAS_BIT(ses->map->flags, MAP_FLAG_DIRECTION))
+		{
+			switch (exits)
+			{
+				case MAP_EXIT_N:
+					index = 24;
+					break;
+				case MAP_EXIT_N+MAP_EXIT_E:
+					index = 25;
+					break;
+				case MAP_EXIT_E:
+					index = 26;
+					break;
+				case MAP_EXIT_S+MAP_EXIT_E:
+					index = 27;
+					break;
+				case MAP_EXIT_S:
+					index = 28;
+					break;
+				case MAP_EXIT_S+MAP_EXIT_W:
+					index = 29;
+					break;
+				case MAP_EXIT_W:
+					index = 30;
+					break;
+				case MAP_EXIT_N+MAP_EXIT_W:
+					index = 31;
+					break;
+
+				default:
+					index = 17;
+					break;
+			}
+		}
+		else
+		{
+			index = 16;
+		}
+
+		if (HAS_BIT(ses->map->flags, MAP_FLAG_MUDFONT))
+		{
+			sprintf(buf, "%s%s%s", ses->map->color[MAP_COLOR_USER], ses->map->legend[offset + index], ses->map->legend[offset + index]);
+		}
+		else
+		{
+			sprintf(buf, "%s%s", ses->map->color[MAP_COLOR_USER], ses->map->legend[offset + index]);
+		}
+		pop_call();
+		return buf;
+	}
+
+	exit1 = 0;
+	exit2 = 0;
+	exits = 0;
+
+	if (HAS_BIT(room->exit_dirs, MAP_DIR_N))
+	{
+		SET_BIT(exit1, 1 << 0);
+		SET_BIT(exit2, 1 << 0);
+		SET_BIT(exits, MAP_EXIT_N);
+	}
+
+	if (HAS_BIT(room->exit_dirs, MAP_DIR_W))
+	{
+		SET_BIT(exit1, 1 << 2);
+		SET_BIT(exits, MAP_EXIT_W);
+	}
+
+	if (HAS_BIT(room->exit_dirs, MAP_DIR_E))
+	{
+		SET_BIT(exit2, 1 << 2);
+		SET_BIT(exits, MAP_EXIT_E);
+	}
+
+	if (HAS_BIT(room->exit_dirs, MAP_DIR_S))
+	{
+		SET_BIT(exit1, 1 << 4);
+		SET_BIT(exit2, 1 << 4);
+		SET_BIT(exits, MAP_EXIT_S);
+	}
+
+	if (HAS_BIT(ses->map->flags, MAP_FLAG_MUDFONT))
+	{
+		if (HAS_BIT(room->exit_dirs, MAP_DIR_NW))
+		{
+			SET_BIT(exit1, 1 << 1);
+		}
+		if (HAS_BIT(room->exit_dirs, MAP_DIR_NE))
+		{
+			SET_BIT(exit2, 1 << 1);
+		}
+		if (HAS_BIT(room->exit_dirs, MAP_DIR_SW))
+		{
+			SET_BIT(exit1, 1 << 3);
+		}
+		if (HAS_BIT(room->exit_dirs, MAP_DIR_SE))
+		{
+			SET_BIT(exit2, 1 << 3);
+		}
+
+		room1 = exit1 + LEGEND_MUDFONT_NWS;
+		room2 = exit2 + LEGEND_MUDFONT_NES;
+
+		if (HAS_BIT(room->flags, ROOM_FLAG_VOID))
+		{
+			room1 += 64;
+			room2 += 64;
+		}
+
+		if (HAS_BIT(room->flags, ROOM_FLAG_CURVED))
+		{
+			switch (room->exit_dirs)
+			{
+				case MAP_DIR_N|MAP_DIR_E:
+				case MAP_DIR_N|MAP_DIR_SE:
+					room1 = LEGEND_MUDFONT_CURVED + 0;
+					break;
+				case MAP_DIR_S|MAP_DIR_E:
+				case MAP_DIR_S|MAP_DIR_NE:
+					room1 = LEGEND_MUDFONT_CURVED + 1;
+					break;
+				case MAP_DIR_S|MAP_DIR_W:
+				case MAP_DIR_S|MAP_DIR_NW:
+					room2 = LEGEND_MUDFONT_CURVED + 2;
+					break;
+				case MAP_DIR_N|MAP_DIR_W:
+				case MAP_DIR_N|MAP_DIR_SW:
+					room2 = LEGEND_MUDFONT_CURVED + 3;
+					break;
+			}
+		}
+
+		sprintf(buf, "%s%s%s", room_color, ses->map->legend[room1], ses->map->legend[room2]);
+	}
+	else
+	{
+		if (HAS_BIT(ses->map->flags, MAP_FLAG_SYMBOLGRAPHICS) && room->symbol[0] && room->symbol[0] != ' ')
+		{
+			sprintf(buf, "%s%-1s", room_color, room->symbol);
+		}
+		else
+		{
+			if (HAS_BIT(room->flags, ROOM_FLAG_VOID) && (exits == MAP_EXIT_N+MAP_EXIT_S || exits == MAP_EXIT_E+MAP_EXIT_W))
+			{
+				sprintf(buf, "%s%s", room_color, exits == MAP_EXIT_N+MAP_EXIT_S ? ses->map->legend[offset+16+2] : ses->map->legend[offset+16+3]);
+			}
+			else
+			{
+				if (HAS_BIT(room->flags, ROOM_FLAG_CURVED))
+				{
+					switch (room->exit_dirs)
+					{
+						case MAP_DIR_N|MAP_DIR_E:
+							exits = 16 + 4;
+							break;
+						case MAP_DIR_S|MAP_DIR_E:
+							exits = 16 + 5;
+							break;
+						case MAP_DIR_S|MAP_DIR_W:
+							exits = 16 + 6;
+							break;
+						case MAP_DIR_N|MAP_DIR_W:
+							exits = 16 + 7;
+							break;
+					}
+				}
+				sprintf(buf, "%s%s", room_color, ses->map->legend[offset + exits]);
+			}
+		}
+	}
+	pop_call();
+	return buf;
+}
+
+void search_keywords(struct session *ses, char *arg, char *out, char *var)
+{
+	char buf[MAP_SEARCH_MAX][BUFFER_SIZE], arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+	int type, max;
+
+	push_call("search_keywords(%p,%p,%p,%p)",ses,arg,out,var);
+
+	for (type = 0 ; type < MAP_SEARCH_MAX ; type++)
+	{
+		buf[type][0] = 0;
+	}
+
+	var[0] = 0;
+
+	type = 0;
+
+	while (*arg && type < MAP_SEARCH_MAX)
+	{
+		arg = sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+
+		if (*arg1 == '{')
+		{
+			strcpy(arg2, arg1);
+
+			arg = get_arg_in_braces(ses, arg2, arg1, GET_ALL);
+		}
+
+		if (!strcasecmp(arg1, "roomid"))
+		{
+			arg = sub_arg_in_braces(ses, arg, buf[MAP_SEARCH_ID], GET_ALL, SUB_VAR|SUB_FUN);
+		}
+		else if (!strcasecmp(arg1, "roomname"))
+		{
+			arg = sub_arg_in_braces(ses, arg, buf[MAP_SEARCH_NAME], GET_ALL, SUB_VAR|SUB_FUN);
+		}
+		else if (!strcasecmp(arg1, "roomexits"))
+		{
+			arg = sub_arg_in_braces(ses, arg, buf[MAP_SEARCH_EXITS], GET_ALL, SUB_VAR|SUB_FUN);
+		}
+		else if (!strcasecmp(arg1, "roomdesc"))
+		{
+			arg = sub_arg_in_braces(ses, arg, buf[MAP_SEARCH_DESC], GET_ALL, SUB_VAR|SUB_FUN);
+		}
+		else if (!strcasecmp(arg1, "roomarea"))
+		{
+			arg = sub_arg_in_braces(ses, arg, buf[MAP_SEARCH_AREA], GET_ALL, SUB_VAR|SUB_FUN);
+		}
+		else if (!strcasecmp(arg1, "roomnote"))
+		{
+			arg = sub_arg_in_braces(ses, arg, buf[MAP_SEARCH_NOTE], GET_ALL, SUB_VAR|SUB_FUN);
+		}
+		else if (!strcasecmp(arg1, "roomterrain"))
+		{
+			arg = sub_arg_in_braces(ses, arg, buf[MAP_SEARCH_TERRAIN], GET_ALL, SUB_VAR|SUB_FUN);
+		}
+		else if (!strcasecmp(arg1, "roomflag"))
+		{
+			arg = sub_arg_in_braces(ses, arg, buf[MAP_SEARCH_FLAG], GET_ALL, SUB_VAR|SUB_FUN);
+		}
+		else if (!strcasecmp(arg1, "variable"))
+		{
+			arg = sub_arg_in_braces(ses, arg, var, GET_ALL, SUB_VAR|SUB_FUN);
+		}
+		else
+		{
+			strcpy(buf[type++], arg1);
+		}
+	}
+
+	for (max = MAP_SEARCH_MAX - 1 ; max >= 0 ; max--)
+	{
+		if (*buf[max])
+		{
+			break;
+		}
+	}
+
+	out[0] = 0;
+
+	for (type = 0 ; type <= max ; type++)
+	{
+		cat_sprintf(out, "{%s}", buf[type]);
+	}
+	pop_call();
+	return;
+}
+
+void map_search_compile(struct session *ses, char *arg, char *var)
+{
+	char tmp[BUFFER_SIZE], buf[BUFFER_SIZE], *ptb;
+	struct listnode *node;
+
+	push_call("map_search_compile(%p,%p,%p)",ses,arg,var);
+
+	search_keywords(ses, arg, tmp, var);
+
+	arg = sub_arg_in_braces(ses, tmp, buf, GET_ALL, SUB_VAR|SUB_FUN); // name
+
+	if (is_math(ses, buf))
+	{
+		ses->map->search->vnum = (int) get_number(ses, buf);
+	}
+	else
+	{
+		ses->map->search->vnum = 0;
+	}
+
+	if (ses->map->search->vnum)
+	{
+		pop_call();
+		return;
+	}
+
+	if (ses->map->search->arg)
+	{
+		free(ses->map->search->arg);
+	}
+
+	if (*buf)
+	{
+		ses->map->search->arg = strdup(buf);
+
+		node = search_node_list(ses->list[LIST_LANDMARK], ses->map->search->arg);
+
+		if (node)
+		{
+			ses->map->search->vnum = node->val32[0];
+
+			if (ses->map->search->vnum)
+			{
+				pop_call();
+				return;
+			}
+		}
+	}
+	else
+	{
+		ses->map->search->arg = NULL;
+	}
+
+	if (ses->map->search->name)
+	{
+		free(ses->map->search->name);
+	}
+
+	if (*buf)
+	{
+		strcat(buf, "$");
+
+		ses->map->search->name = tintin_regexp_compile(ses, NULL, buf, PCRE_ANCHORED);
+	}
+	else
+	{
+		ses->map->search->name = NULL;
+	}
+
+	arg = sub_arg_in_braces(ses, arg, buf, GET_ALL, SUB_VAR|SUB_FUN); // exits
+
+	ses->map->search->exit_dirs = 0;
+	ses->map->search->exit_size = 0;
+
+	if (ses->map->search->exit_list)
+	{
+		free(ses->map->search->exit_list);
+	}
+
+	if (*buf)
+	{
+		struct listnode *node;
+		char exit[BUFFER_SIZE];
+		ptb = buf;
+
+		tmp[0] = 0;
+
+		if (is_math(ses, buf))
+		{
+			ses->map->search->exit_dirs = get_number(ses, buf);
+
+			if (HAS_BIT(ses->map->search->exit_dirs, MAP_DIR_N))  ses->map->search->exit_size++;
+			if (HAS_BIT(ses->map->search->exit_dirs, MAP_DIR_E))  ses->map->search->exit_size++;
+			if (HAS_BIT(ses->map->search->exit_dirs, MAP_DIR_S))  ses->map->search->exit_size++;
+			if (HAS_BIT(ses->map->search->exit_dirs, MAP_DIR_W))  ses->map->search->exit_size++;
+			if (HAS_BIT(ses->map->search->exit_dirs, MAP_DIR_U))  ses->map->search->exit_size++;
+			if (HAS_BIT(ses->map->search->exit_dirs, MAP_DIR_D))  ses->map->search->exit_size++;
+			if (HAS_BIT(ses->map->search->exit_dirs, MAP_DIR_NE)) ses->map->search->exit_size++;
+			if (HAS_BIT(ses->map->search->exit_dirs, MAP_DIR_NW)) ses->map->search->exit_size++;
+			if (HAS_BIT(ses->map->search->exit_dirs, MAP_DIR_SE)) ses->map->search->exit_size++;
+			if (HAS_BIT(ses->map->search->exit_dirs, MAP_DIR_SW)) ses->map->search->exit_size++;
+		}
+		else
+		{
+			while (*ptb)
+			{
+				ptb = get_arg_in_braces(ses, ptb, exit, GET_ONE);
+
+				node = search_node_list(ses->list[LIST_PATHDIR], exit);
+
+				ses->map->search->exit_size++;
+
+				if (node)
+				{
+					SET_BIT(ses->map->search->exit_dirs, 1LL << atoi(node->arg3));
+				}
+				else
+				{
+					SET_BIT(ses->map->search->exit_dirs, 1); // flag indicates no exits
+
+					cat_sprintf(tmp, "{%s}", exit);
+				}
+
+				if (*ptb == COMMAND_SEPARATOR)
+				{
+					ptb++;
+				}
+			}
+		}
+		ses->map->search->exit_list = strdup(tmp);
+	}
+	else
+	{
+		ses->map->search->exit_list = strdup("");
+	}
+
+	arg = sub_arg_in_braces(ses, arg, buf, GET_ALL, SUB_VAR|SUB_FUN); // desc
+
+	if (ses->map->search->desc)
+	{
+		free(ses->map->search->desc);
+	}
+
+	if (*buf)
+	{
+		strcat(buf, "$");
+
+		ses->map->search->desc = tintin_regexp_compile(ses, NULL, buf, PCRE_ANCHORED);
+	}
+	else
+	{
+		ses->map->search->desc = NULL;
+	}
+
+	arg = sub_arg_in_braces(ses, arg, buf, GET_ALL, SUB_VAR|SUB_FUN); // area
+
+	if (ses->map->search->area)
+	{
+		free(ses->map->search->area);
+	}
+
+	if (*buf)
+	{
+		strcat(buf, "$");
+
+		ses->map->search->area = tintin_regexp_compile(ses, NULL, buf, PCRE_ANCHORED);
+	}
+	else
+	{
+		ses->map->search->area = NULL;
+	}
+
+	arg = sub_arg_in_braces(ses, arg, buf, GET_ALL, SUB_VAR|SUB_FUN); // note
+
+	if (ses->map->search->note)
+	{
+		free(ses->map->search->note);
+	}
+
+	if (*buf)
+	{
+		strcat(buf, "$");
+
+		ses->map->search->note = tintin_regexp_compile(ses, NULL, buf, PCRE_ANCHORED);
+	}
+	else
+	{
+		ses->map->search->note = NULL;
+	}
+
+	arg = sub_arg_in_braces(ses, arg, buf, GET_ALL, SUB_VAR|SUB_FUN); // terrain
+
+	if (ses->map->search->terrain)
+	{
+		free(ses->map->search->terrain);
+	}
+
+	if (*buf)
+	{
+		strcat(buf, "$");
+
+		ses->map->search->terrain = tintin_regexp_compile(ses, NULL, buf, PCRE_ANCHORED);
+	}
+	else
+	{
+		ses->map->search->terrain = NULL;
+	}
+
+	arg = sub_arg_in_braces(ses, arg, buf, GET_ALL, SUB_VAR|SUB_FUN); // flag
+
+	if (*buf)
+	{
+		char flags[BUFFER_SIZE];
+
+		ses->map->search->flag = get_number(ses, buf);
+
+		ptb = buf;
+
+		while (*buf)
+		{
+			ptb = sub_arg_in_braces(ses, ptb, flags, GET_ONE, SUB_NONE);
+
+			if (is_abbrev(buf, "avoid"))
+			{
+				SET_BIT(ses->map->search->flag, ROOM_FLAG_AVOID);
+			}
+			else if (is_abbrev(buf, "curved"))
+			{
+				SET_BIT(ses->map->search->flag, ROOM_FLAG_CURVED);
+			}
+			else if (is_abbrev(buf, "hide"))
+			{
+				SET_BIT(ses->map->search->flag, ROOM_FLAG_HIDE);
+			}
+			else if (is_abbrev(buf, "invis"))
+			{
+				SET_BIT(ses->map->search->flag, ROOM_FLAG_INVIS);
+			}
+			else if (is_abbrev(buf, "leave"))
+			{
+				SET_BIT(ses->map->search->flag, ROOM_FLAG_LEAVE);
+			}
+			else if (is_abbrev(buf, "void"))
+			{
+				SET_BIT(ses->map->search->flag, ROOM_FLAG_VOID);
+			}
+			else if (is_abbrev(buf, "static"))
+			{
+				SET_BIT(ses->map->search->flag, ROOM_FLAG_STATIC);
+			}
+
+			if (*ptb == COMMAND_SEPARATOR)
+			{
+				ptb++;
+			}
+		}
+	}
+	else
+	{
+		ses->map->search->flag = 0;
+	}
+
+	arg = sub_arg_in_braces(ses, arg, buf, GET_ALL, SUB_VAR|SUB_FUN); // id
+
+	if (ses->map->search->id)
+	{
+		free(ses->map->search->id);
+	}
+
+	if (*buf)
+	{
+		ses->map->search->id = strdup(buf);
+	}
+	else
+	{
+		ses->map->search->id = NULL;
+	}
+
+	pop_call();
+	return;
+}
+
+int match_room(struct session *ses, int vnum, struct search_data *search)
+{
+	struct room_data *room = ses->map->room_list[vnum];
+
+	if (room == NULL)
+	{
+		return 0;
+	}
+
+	if (search->vnum)
+	{
+		return room->vnum == search->vnum;
+	}
+
+	if (search->id)
+	{
+		return !strcmp(room->id, search->id);
+	}
+
+	if (search->name)
+	{
+		if (!regexp_compare(ses, search->name, room->name, "", 0, 0))
+		{
+			return 0;
+		}
+	}
+
+	if (search->exit_dirs)
+	{
+		char *arg, exit[BUFFER_SIZE];
+
+		if (search->exit_dirs != room->exit_dirs)
+		{
+			return 0;
+		}
+		if (search->exit_size != room->exit_size)
+		{
+			return 0;
+		}
+
+		arg = search->exit_list;
+
+		while (*arg)
+		{
+			arg = get_arg_in_braces(ses, arg, exit, GET_ONE);
+
+			if (!find_exit(ses, vnum, exit))
+			{
+				return 0;
+			}
+
+			if (*arg == COMMAND_SEPARATOR)
+			{
+				arg++;
+			}
+		}
+	}
+
+	if (search->desc)
+	{
+		if (!regexp_compare(ses, search->desc, room->desc, "", 0, 0))
+		{
+			return 0;
+		}
+	}
+
+	if (search->area)
+	{
+		if (!regexp_compare(ses, search->area, room->area, "", 0, 0))
+		{
+			return 0;
+		}
+	}
+
+	if (search->note)
+	{
+		if (!regexp_compare(ses, search->note, room->note, "", 0, 0))
+		{
+			return 0;
+		}
+	}
+
+	if (search->terrain)
+	{
+		if (!regexp_compare(ses, search->terrain, room->terrain, "", 0, 0))
+		{
+			return 0;
+		}
+	}
+
+	if (search->flag)
+	{
+		if ((room->flags & search->flag) != search->flag)
+		{
+			return 0;
+		}
+	}
+	return 1;
+}
+
+int find_path(struct session *ses, char *arg)
+{
+	struct exit_data *exit;
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+	int room;
+
+	push_call("find_path(%p,%p)",ses,arg);
+
+	arg = substitute_speedwalk(ses, arg, arg1);
+
+	room = ses->map->in_room;
+
+	while (*arg)
+	{
+		arg = get_arg_in_braces(ses, arg, arg2, GET_ALL);
+
+		exit = find_exit(ses, room, arg2);
+
+		if (exit == NULL)
+		{
+			pop_call();
+			return 0;
+		}
+
+		room = tunnel_void(ses, room, exit->vnum, exit->dir);
+
+		if (*arg == COMMAND_SEPARATOR)
+		{
+			arg++;
+		}
+	}
+
+	pop_call();
+	return room == ses->map->in_room ? 0 : room;
+}
+
+int find_location(struct session *ses, char *arg)
+{
+	struct listnode *node;
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], arg3[BUFFER_SIZE];
+	int x, y, z;
+	
+	push_call("find_location(%p,%p)",ses,arg);
+
+	if (find_exit(ses, ses->map->in_room, arg))
+	{
+		pop_call();
+		return find_exit(ses, ses->map->in_room, arg)->vnum;
+	}
+
+	if (is_math(ses, arg))
+	{
+		arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+		arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+		arg = sub_arg_in_braces(ses, arg, arg3, GET_ALL, SUB_VAR|SUB_FUN);
+
+		x = get_number(ses, arg1);
+		y = get_number(ses, arg2);
+		z = get_number(ses, arg3);
+	}
+	else
+	{
+		x = y = z = 0;
+
+		arg = substitute_speedwalk(ses, arg, arg2);
+
+		while (*arg)
+		{
+			arg = get_arg_in_braces(ses, arg, arg3, GET_ALL);
+
+			node = search_node_list(ses->list[LIST_PATHDIR], arg3);
+
+			if (node == NULL)
+			{
+//				show_error(ses, LIST_COMMAND, "#ERROR: #MAP FIND_LOCATION: {%s} IS AN INVALID PATHDIR.", arg3);
+
+				pop_call();
+				return 0;
+			}
+
+			x += (HAS_BIT(atoi(node->arg3), MAP_EXIT_E) ? 1 : HAS_BIT(atoi(node->arg3), MAP_EXIT_W) ? -1 : 0);
+			y += (HAS_BIT(atoi(node->arg3), MAP_EXIT_N) ? 1 : HAS_BIT(atoi(node->arg3), MAP_EXIT_S) ? -1 : 0);
+			z += (HAS_BIT(atoi(node->arg3), MAP_EXIT_U) ? 1 : HAS_BIT(atoi(node->arg3), MAP_EXIT_D) ? -1 : 0);
+
+			if (*arg == COMMAND_SEPARATOR)
+			{
+				arg++;
+			}
+		}
+	}
+
+	pop_call();
+	return spatialgrid_find(ses, ses->map->in_room, x, y, z);
+}
+
+int find_room(struct session *ses, char *arg)
+{
+	char var[BUFFER_SIZE];
+	struct listnode *node;
+	int room;
+
+	push_call("find_room(%p,%s)",ses,arg);
+
+	map_search_compile(ses, arg, var);
+
+	if (ses->map->search->vnum > 0 && ses->map->search->vnum < ses->map->size)
+	{
+		if (ses->map->room_list[ses->map->search->vnum])
+		{
+			pop_call();
+			return ses->map->search->vnum;
+		}
+		pop_call();
+		return 0;
+	}
+
+	if (ses->map->search->arg)
+	{
+		node = search_node_list(ses->list[LIST_LANDMARK], ses->map->search->arg);
+
+		if (node)
+		{
+			if (ses->map->room_list[node->val32[0]])
+			{
+				pop_call();
+				return node->val32[0];
+			}
+			pop_call();
+			return 0;
+		}
+	}
+
+	if (ses->map->in_room)
+	{
+		room = searchgrid_find(ses, ses->map->in_room, ses->map->search);
+
+		if (room)
+		{
+			pop_call();
+			return room;
+		}
+	}
+
+	for (room = 0 ; room < ses->map->size ; room++)
+	{
+		if (ses->map->room_list[room] == NULL)
+		{
+			continue;
+		}
+
+		if (!match_room(ses, room, ses->map->search))
+		{
+			continue;
+		}
+		pop_call();
+		return room;
+	}
+	pop_call();
+	return 0;
+}
+
+void goto_room(struct session *ses, int room)
+{
+	int last_room = ses->map->in_room;
+
+	push_call("goto_room(%p,%d)",ses,room);
+
+	if (ses->map->in_room)
+	{
+		check_all_events(ses, SUB_ARG|SUB_SEC, 0, 2, "MAP EXIT ROOM", ntos(last_room), ntos(room));
+		check_all_events(ses, SUB_ARG|SUB_SEC, 1, 2, "MAP EXIT ROOM %d", last_room, ntos(last_room), ntos(room));
+	}
+
+	ses->map->in_room = room;
+
+	DEL_BIT(ses->map->room_list[room]->flags, ROOM_FLAG_PATH);
+
+	if (last_room == 0)
+	{
+		check_all_events(ses, SUB_ARG|SUB_SEC, 0, 1, "MAP ENTER MAP", ntos(room));
+	}
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 0, 2, "MAP ENTER ROOM", ntos(room), ntos(last_room));
+	check_all_events(ses, SUB_ARG|SUB_SEC, 1, 2, "MAP ENTER ROOM %d", room, ntos(room), ntos(last_room));
+
+	pop_call();
+	return;
+}
+
+int find_new_room(struct session *ses)
+{
+	int room;
+
+	for (room = 1 ; room < ses->map->size ; room++)
+	{
+		if (ses->map->room_list[room] == NULL)
+		{
+			break;
+		}
+	}
+
+	if (room == ses->map->size)
+	{
+		show_error(ses, LIST_COMMAND, "#MAP CREATE ROOM: Maximum amount of rooms of %d reached. Use #map resize.", ses->map->size);
+
+		return 0;
+	}
+	return room;
+}
+
+int dir_flags(struct session *ses, int room, int dir)
+{
+	struct exit_data *exit;
+
+	for (exit = ses->map->room_list[room]->f_exit ; exit ; exit = exit->next)
+	{
+		if (exit->dir == dir)
+		{
+			return exit->flags; /* | HAS_BIT(ses->map->room_list[exit->vnum]->flags, EXIT_FLAG_ALL);*/
+		}
+	}
+	return 0;
+}
+
+struct exit_data *find_exit(struct session *ses, int room, char *arg)
+{
+	struct exit_data *exit;
+
+	for (exit = ses->map->room_list[room]->f_exit ; exit ; exit = exit->next)
+	{
+//		if (!strcmp(exit->name, arg) || exit->vnum == atoi(arg))
+		if (!strcmp(exit->name, arg))
+		{
+			return exit;
+		}
+	}
+	return NULL;
+}
+
+int check_global(struct session *ses, int room)
+{
+	if (HAS_BIT(ses->map->room_list[room]->flags, ROOM_FLAG_NOGLOBAL))
+	{
+		return FALSE;
+	}
+	
+	if (ses->map->room_list[ses->map->global_vnum] == NULL)
+	{
+		return FALSE;
+	}
+
+	if (room == ses->map->global_vnum)
+	{
+		return FALSE;
+	}
+	return TRUE;
+}
+
+int tunnel_void(struct session *ses, int from, int room, int dir)
+{
+	if (!HAS_BIT(ses->map->room_list[room]->flags, ROOM_FLAG_VOID))
+	{
+		return room;
+	}
+
+	if (get_room_exits(ses, room) != 2)
+	{
+		struct exit_data *exit = ses->map->room_list[room]->exit_grid[dir_to_grid(dir)];
+
+		if (exit)
+		{
+			return tunnel_void(ses, room, exit->vnum, exit->dir);
+		}
+		return room;
+	}
+
+	if (ses->map->room_list[room]->f_exit->vnum != from)
+	{
+		return tunnel_void(ses, room, ses->map->room_list[room]->f_exit->vnum, ses->map->room_list[room]->f_exit->dir);
+	}
+	else
+	{
+		return tunnel_void(ses, room, ses->map->room_list[room]->l_exit->vnum, ses->map->room_list[room]->l_exit->dir);
+	}
+}
+
+// shortest_path() utilities
+
+int searchgrid_find(struct session *ses, int from, struct search_data *search)
+{
+	int vnum, head, tail, index;
+	float length;
+	struct grid_node *node, *temp, list[MAP_BF_SIZE];
+	struct exit_data *exit;
+	struct room_data *room;
+
+	search->stamp++;
+
+	head = 0;
+	tail = 1;
+
+	node = &list[head];
+
+	node->vnum   = from;
+	node->length = ses->map->room_list[from]->weight;
+
+	// for map_list
+
+	node->w      = 0;
+	node->x      = 0;
+	node->y      = 0;
+	node->z      = 0;
+
+	while (head != tail)
+	{
+		node = &list[head];
+
+		room = ses->map->room_list[node->vnum];
+
+		length = node->length;
+
+		head = (head + 1) % MAP_BF_SIZE;
+
+		if (search->stamp != room->search_stamp)
+		{
+			room->search_stamp = search->stamp;
+
+			// first come first serve like with spatialgrid_find
+
+			room->w = node->w;
+			room->x = node->x;
+			room->y = node->y;
+			room->z = node->z;
+
+			DEL_BIT(room->flags, ROOM_FLAG_PATH);
+		}
+		else if (length >= room->length)
+		{
+			if (room->vnum != ses->map->global_vnum && room->w && node->w == 0)
+			{
+				room->w = node->w;
+				room->x = node->x;
+				room->y = node->y;
+				room->z = node->z;
+			}
+			continue;
+		}
+
+		room->length = length;
+
+		if (search->vnum)
+		{
+			if (room->vnum == search->vnum)
+			{
+				return room->vnum;
+			}
+		}
+		else
+		{
+			if (match_room(ses, room->vnum, search))
+			{
+				return room->vnum;
+			}
+		}
+
+		if (check_global(ses, room->vnum))
+		{
+			exit = ses->map->global_exit;
+		}
+		else
+		{
+			exit = room->f_exit;
+		}
+
+		for ( ; exit ; exit = exit->next)
+		{
+			vnum = tunnel_void(ses, room->vnum, exit->vnum, exit->dir);
+
+			if (HAS_BIT(exit->flags, EXIT_FLAG_AVOID|EXIT_FLAG_BLOCK) || HAS_BIT(ses->map->room_list[vnum]->flags, ROOM_FLAG_AVOID|ROOM_FLAG_BLOCK))
+			{
+				goto next_exit;
+			}
+
+			length = room->length + exit->weight + ses->map->room_list[vnum]->weight;
+
+			if (search->stamp == ses->map->room_list[vnum]->search_stamp)
+			{
+				if (length >= ses->map->room_list[vnum]->length)
+				{
+					goto next_exit;
+				}
+			}
+
+			temp = &list[tail];
+
+			temp->vnum   = vnum;
+			temp->length = length;
+			temp->w      = room->vnum == ses->map->global_vnum ? 1 : room->w;
+			temp->x      = room->x + (HAS_BIT(exit->dir, MAP_EXIT_E) ?  1 : HAS_BIT(exit->dir, MAP_EXIT_W) ? -1 : 0);
+			temp->y      = room->y + (HAS_BIT(exit->dir, MAP_EXIT_N) ?  1 : HAS_BIT(exit->dir, MAP_EXIT_S) ? -1 : 0);
+			temp->z      = room->z + (HAS_BIT(exit->dir, MAP_EXIT_U) ?  1 : HAS_BIT(exit->dir, MAP_EXIT_D) ? -1 : 0);
+
+			/*
+				list must remain ordered by length
+			*/
+
+			index = tail;
+
+			while (index != head)
+			{
+				temp = &list[index];
+
+				node = &list[index ? index - 1 : MAP_BF_SIZE - 1];
+
+				if (temp->length >= node->length)
+				{
+					break;
+				}
+
+				vnum         = temp->vnum;
+				length       = temp->length;
+
+				temp->vnum   = node->vnum;
+				temp->length = node->length;
+
+				node->vnum   = vnum;
+				node->length = length;
+
+				index = index ? index - 1 : MAP_BF_SIZE - 1;
+			}
+
+			tail = (tail + 1) % MAP_BF_SIZE;
+
+			if (tail == head)
+			{
+				show_error(ses, LIST_COMMAND, "#SHORTEST PATH: MAP TOO BIG FOR BF STACK OF %d", MAP_BF_SIZE);
+				break;
+			}
+
+			next_exit:
+
+			if (exit == ses->map->global_exit)
+			{
+				exit->next = room->f_exit;
+			}
+		}
+	}
+	return 0;
+}
+
+int searchgrid_walk(struct session *ses, int offset, int from, int dest)
+{
+	int vnum, trim, head, tail, index;
+	float length;
+	struct grid_node *node, *temp, list[MAP_BF_SIZE];
+	struct exit_data *exit;
+	struct room_data *room;
+
+	head = 0;
+	tail = 1;
+
+	list[head].vnum   = from;
+	list[head].length = ses->map->room_list[from]->weight;
+
+	while (head != tail)
+	{
+		node = &list[head];
+
+		room = ses->map->room_list[node->vnum];
+
+		length = node->length;
+
+		head = (head + 1) % MAP_BF_SIZE;
+
+
+		if (length >= room->length)
+		{
+			continue;
+		}
+
+		room->length = length;
+
+		if (room->vnum == dest)
+		{
+			return room->vnum;
+		}
+
+		trim = 1;
+
+		if (check_global(ses, room->vnum))
+		{
+			exit = ses->map->global_exit;
+		}
+		else
+		{
+			exit = room->f_exit;
+		}
+
+		for ( ; exit ; exit = exit->next)
+		{
+			vnum = tunnel_void(ses, room->vnum, exit->vnum, exit->dir);
+
+			if (HAS_BIT(exit->flags, EXIT_FLAG_AVOID|EXIT_FLAG_BLOCK) || HAS_BIT(ses->map->room_list[vnum]->flags, ROOM_FLAG_AVOID|ROOM_FLAG_BLOCK))
+			{
+				goto next_exit;
+			}
+
+			length = room->length + exit->weight + ses->map->room_list[vnum]->weight;
+
+			if (ses->map->search->stamp != ses->map->room_list[vnum]->search_stamp)
+			{
+				goto next_exit;
+			}
+
+			if (length >= ses->map->room_list[vnum]->length || length >= ses->map->room_list[dest]->length)
+			{
+				goto next_exit;
+			}
+
+			temp = &list[tail];
+
+			temp->vnum   = vnum;
+			temp->length = length;
+
+			/*
+				list must remain ordered by length
+			*/
+
+			index = tail;
+
+			while (index != head)
+			{
+				temp = &list[index];
+
+				node = &list[index ? index - 1 : MAP_BF_SIZE - 1];
+
+				if (temp->length >= node->length)
+				{
+					break;
+				}
+
+				vnum = temp->vnum;
+				length = temp->length;
+
+				temp->vnum = node->vnum;
+				temp->length = node->length;
+
+				node->vnum = vnum;
+				node->length = length;
+
+				index = index ? index - 1 : MAP_BF_SIZE - 1;
+			}
+
+			tail = (tail + 1) % MAP_BF_SIZE;
+
+			if (tail == head)
+			{
+				show_error(ses, LIST_COMMAND, "#SHORTEST PATH: MAP TOO BIG FOR BF STACK OF %d", MAP_BF_SIZE);
+				break;
+			}
+			trim = 0;
+
+			next_exit:
+
+			if (exit == ses->map->global_exit)
+			{
+				exit->next = room->f_exit;
+			}
+		}
+
+		if (trim)
+		{
+			room->length = 0;
+		}
+	}
+	return 0;
+}
+
+void shortest_path(struct session *ses, int run, char *delay, char *arg)
+{
+	char var[BUFFER_SIZE];
+	struct exit_data *exit;
+	struct room_data *room;
+	int vnum, dest;
+
+	if (HAS_BIT(ses->flags, SES_FLAG_PATHMAPPING))
+	{
+		show_error(ses, LIST_COMMAND, "#SHORTEST PATH: You have to use #PATH END first.");
+
+		return;
+	}
+
+	kill_list(ses->list[LIST_PATH]);
+
+	ses->list[LIST_PATH]->update = 0;
+
+	map_search_compile(ses, arg, var);
+
+	dest = searchgrid_find(ses, ses->map->in_room, ses->map->search);
+
+	if (dest == 0 || dest == ses->map->global_vnum)
+	{
+		show_error(ses, LIST_COMMAND, "#SHORTEST PATH: NO PATH FOUND TO %s.", arg);
+		return;
+	}
+
+	if (dest == ses->map->in_room)
+	{
+		show_error(ses, LIST_COMMAND, "#SHORTEST PATH: Already there.");
+		return;
+	}
+
+	vnum = ses->map->in_room;
+
+	// Slower than a backtrace, but works with mazes.
+
+	while (TRUE)
+	{
+		room = ses->map->room_list[vnum];
+
+		if (check_global(ses, room->vnum))
+		{
+			exit = ses->map->global_exit;
+		}
+		else
+		{
+			exit = room->f_exit;
+		}
+
+		for ( ; exit ; exit = exit->next)
+		{
+			if (HAS_BIT(exit->flags, EXIT_FLAG_AVOID|EXIT_FLAG_BLOCK) || HAS_BIT(ses->map->room_list[exit->vnum]->flags, ROOM_FLAG_AVOID|ROOM_FLAG_BLOCK))
+			{
+				goto exit_next;
+			}
+
+			vnum = tunnel_void(ses, room->vnum, exit->vnum, exit->dir);
+
+			if (searchgrid_walk(ses, room->length, vnum, dest))
+			{
+				break;
+			}
+
+			exit_next:
+
+			if (exit == ses->map->global_exit)
+			{
+				exit->next = room->f_exit;
+			}
+		}
+
+		if (exit == NULL)
+		{
+			show_error(ses, LIST_COMMAND, "#SHORTEST PATH: UNKNOWN ERROR.");
+			return;
+		}
+
+		if (exit != ses->map->global_exit)
+		{
+			if (HAS_BIT(ses->map->flags, MAP_FLAG_NOFOLLOW))
+			{
+				check_append_path(ses, exit->cmd, "", 0);
+			}
+			else
+			{
+				check_append_path(ses, exit->name, "", 0);
+			}
+		}
+
+		SET_BIT(ses->map->room_list[vnum]->flags, ROOM_FLAG_PATH);
+
+		if (ses->map->room_list[vnum]->search_stamp != ses->map->search->stamp)
+		{
+			show_error(ses, LIST_COMMAND, "%d bad search stamp %d vs %d", vnum, ses->map->room_list[vnum]->search_stamp, ses->map->search->stamp);
+		}
+
+		if (vnum == dest)
+		{
+			break;
+		}
+	}
+
+	if (run)
+	{
+		path_run(ses, delay);
+	}
+}
+
+/*
+	Virtual coordinate search for linkable rooms when creating a new room.
+*/
+
+int find_coord(struct session *ses, char *arg)
+{
+	int dir, x, y, z, room;
+
+	dir = get_exit_dir(ses, arg);
+
+	if (dir == 0)
+	{
+		return 0;
+	}
+
+	x = (HAS_BIT(dir, MAP_EXIT_E) ? 1 : HAS_BIT(dir, MAP_EXIT_W) ? -1 : 0);
+	y = (HAS_BIT(dir, MAP_EXIT_N) ? 1 : HAS_BIT(dir, MAP_EXIT_S) ? -1 : 0);
+	z = (HAS_BIT(dir, MAP_EXIT_U) ? 1 : HAS_BIT(dir, MAP_EXIT_D) ? -1 : 0);
+
+	room = spatialgrid_find(ses, ses->map->in_room, x, y, z);
+
+	if (ses->map->room_list[room])
+	{
+		if (HAS_BIT(ses->map->room_list[room]->flags, ROOM_FLAG_STATIC))
+		{
+			show_message(ses, LIST_COMMAND, "#MAP: Linkable room is marked static. Creating overlapping room instead.");
+
+			return 0;
+		}
+	}
+	return room;
+}
+
+// Used by #map jump and the auto linker
+
+int spatialgrid_find(struct session *ses, int from, int x, int y, int z)
+{
+	int head, tail;
+	struct grid_node *node, *temp, list[MAP_BF_SIZE];
+	struct exit_data *exit;
+	struct room_data *room;
+
+	push_call("spatialgrid_find(%s,%d,%d,%d,%d)",ses->name,from,x,y,z);
+
+	head = 0;
+	tail = 1;
+
+	node = &list[head];
+
+	node->vnum   = from;
+	node->x      = 0;
+	node->y      = 0;
+	node->z      = 0;
+	node->length = 0;
+	node->flags  = 0;
+
+	ses->map->display_stamp++;
+
+	while (head != tail)
+	{
+		node = &list[head];
+
+		head = (head + 1) % MAP_BF_SIZE;
+
+		room = ses->map->room_list[node->vnum];
+
+		if (ses->map->display_stamp != room->display_stamp)
+		{
+			room->display_stamp = ses->map->display_stamp;
+		}
+		else if (room->length <= node->length)
+		{
+			continue;
+		}
+
+		room->length = node->length;
+/*
+		if (HAS_BIT(node->flags, GRID_FLAG_HIDE))
+		{
+			continue;
+		}
+*/
+		if (node->x == x && node->y == y && node->z == z)
+		{
+			pop_call();
+			return node->vnum;
+		}
+
+		for (exit = room->f_exit ; exit ; exit = exit->next)
+		{
+			if (ses->map->display_stamp == ses->map->room_list[exit->vnum]->display_stamp)
+			{
+				if (room->length >= ses->map->room_list[exit->vnum]->length)
+				{
+					continue;
+				}
+			}
+
+			if (exit->dir == 0)
+			{
+				continue;
+			}
+
+			if (HAS_BIT(exit->flags, EXIT_FLAG_HIDE) || HAS_BIT(ses->map->room_list[exit->vnum]->flags, ROOM_FLAG_HIDE))
+			{
+				continue;
+			}
+
+			if (head == (tail + 1) % MAP_BF_SIZE)
+			{
+				break;
+			}
+
+			temp = &list[tail];
+
+			temp->vnum   = exit->vnum;
+			temp->w      = node->w;
+			temp->x      = node->x + (HAS_BIT(exit->dir, MAP_EXIT_E) ?  1 : HAS_BIT(exit->dir, MAP_EXIT_W) ? -1 : 0);
+			temp->y      = node->y + (HAS_BIT(exit->dir, MAP_EXIT_N) ?  1 : HAS_BIT(exit->dir, MAP_EXIT_S) ? -1 : 0);
+			temp->z      = node->z + (HAS_BIT(exit->dir, MAP_EXIT_U) ?  1 : HAS_BIT(exit->dir, MAP_EXIT_D) ? -1 : 0);
+			temp->length = node->length + 1;
+			temp->flags  = 0;
+/*
+			if (HAS_BIT(exit->flags, EXIT_FLAG_HIDE) || HAS_BIT(ses->map->room_list[exit->vnum]->flags, ROOM_FLAG_HIDE))
+			{
+				SET_BIT(temp->flags, GRID_FLAG_HIDE);
+
+				temp->length += 1000;
+			}
+*/
+			tail = (tail + 1) % MAP_BF_SIZE;
+		}
+	}
+	pop_call();
+	return 0;
+}
+
+void explore_path(struct session *ses, int run, char *arg1, char *arg2)
+{
+	struct exit_data *exit;
+	int room, vnum;
+
+	for (vnum = 0 ; vnum < ses->map->size ; vnum++)
+	{
+		if (ses->map->room_list[vnum])
+		{
+			DEL_BIT(ses->map->room_list[vnum]->flags, ROOM_FLAG_PATH);
+		}
+	}
+
+	if (HAS_BIT(ses->flags, SES_FLAG_PATHMAPPING))
+	{
+		show_error(ses, LIST_COMMAND, "#MAP EXPLORE: You have to use #PATH END first.");
+
+		return;
+	}
+
+	kill_list(ses->list[LIST_PATH]);
+
+	ses->list[LIST_PATH]->update = 0;
+
+	room = ses->map->in_room;
+
+	exit = find_exit(ses, room, arg1);
+
+	if (exit == NULL)
+	{
+		show_error(ses, LIST_COMMAND, "#MAP: There's no exit named '%s'.", arg1);
+		return;
+	}
+
+	vnum = exit->vnum;
+
+	if (HAS_BIT(ses->map->flags, MAP_FLAG_NOFOLLOW))
+	{
+		check_append_path(ses, exit->cmd, "", 0);
+	}
+	else
+	{
+		check_append_path(ses, exit->name, "", 0);
+	}
+
+	SET_BIT(ses->map->room_list[room]->flags, ROOM_FLAG_PATH);
+	SET_BIT(ses->map->room_list[vnum]->flags, ROOM_FLAG_PATH);
+
+	while (get_room_exits(ses, vnum) == 2)
+	{
+		exit = ses->map->room_list[vnum]->f_exit;
+
+		if (HAS_BIT(ses->map->room_list[exit->vnum]->flags, ROOM_FLAG_PATH))
+		{
+			exit = ses->map->room_list[vnum]->l_exit;
+
+			if (HAS_BIT(ses->map->room_list[exit->vnum]->flags, ROOM_FLAG_PATH))
+			{
+				break;
+			}
+		}
+
+		if (!HAS_BIT(ses->map->room_list[vnum]->flags, ROOM_FLAG_VOID))
+		{
+			if (HAS_BIT(ses->map->flags, MAP_FLAG_NOFOLLOW))
+			{
+				check_append_path(ses, exit->cmd, "", 0);
+			}
+			else
+			{
+				check_append_path(ses, exit->name, "", 0);
+			}
+		}
+
+		vnum = exit->vnum;
+
+		SET_BIT(ses->map->room_list[vnum]->flags, ROOM_FLAG_PATH);
+	}
+
+	DEL_BIT(ses->map->room_list[room]->flags, ROOM_FLAG_PATH);
+
+	if (run)
+	{
+		path_run(ses, arg2);
+	}
+}
+
+void map_mouse_handler(struct session *ses, char *arg1, char *arg2, int x, int y, int height, int width)
+{
+	char exit[10];
+	int max_x, max_y;
+	int top_row, top_col, bot_row, bot_col, rows, cols, char_height;
+
+	push_call("map_mouse_handler(%p,%p,%p,%d,%d)",ses,arg1,arg2,x,y);
+
+	if (ses->map == NULL || !HAS_BIT(ses->map->flags, MAP_FLAG_VTMAP) || ses->map->room_list[ses->map->in_room] == NULL)
+	{
+		pop_call();
+		return;
+	}
+
+	if (!HAS_BIT(ses->map->flags, MAP_FLAG_ASCIIGRAPHICS))
+	{
+		if (arg1 && arg2)
+		{
+//			do_screen(ses, "{RAISE} {SCREEN MOUSE LOCATION}");
+		}
+	}
+
+	exit[0] = 0;
+
+	char_height = 1 + height % UMAX(1, gtd->screen->char_height);
+
+	if (ses->map->rows > 1 && ses->map->cols > 1)
+	{
+		top_row = ses->map->top_row;
+		top_col = ses->map->top_col;
+		bot_row = ses->map->bot_row;
+		bot_col = ses->map->bot_col;
+		rows    = ses->map->rows;
+		cols    = ses->map->cols;
+	}
+	else
+	{
+		top_row = 1;
+		top_col = 1;
+		bot_row = ses->split->top_row - 2;
+		bot_col = gtd->screen->cols;
+		rows    = ses->split->top_row - 2;
+		cols    = gtd->screen->cols;
+	}
+
+	y = y - 1;
+	x = x - 1;
+
+	if (y > bot_row || y < top_row)
+	{
+		pop_call();
+		return;
+	}
+
+	if (x > bot_col || x < top_col)
+	{
+		pop_call();
+		return;
+	}
+
+	y = y + 1 - top_row;
+	x = x + 1 - top_col;
+
+	if (HAS_BIT(ses->map->flags, MAP_FLAG_ASCIIGRAPHICS))
+	{
+		char *grid[] = { "nw",  "2",  "n",  "u", "ne",  "6",
+				  "w", "RL", "RC", "RR",  "e", "e",
+				 "sw",  "d",  "s", "16", "se", "18" };
+
+//		tintin_printf2(ses, "\e[1;32mdebug: y=%d x=%d mod(y)=%d mod(x)=%d", y / 3, y / 6, y % 3, x % 6);
+
+		strcpy(exit, grid[URANGE(0, y % 3 * 6 + x % 6, 17)]);
+
+		y /= 3;
+		x /= 6;
+
+		max_y = 2 + rows / 3;
+		max_x = 2 + cols / 6;
+	}
+	else if (HAS_BIT(ses->map->flags, MAP_FLAG_UNICODEGRAPHICS))
+	{
+		char *grid[] = { "se", "sw",  "d",  "s",  "5",   "ne", "nw",  "8",  "n",  "u",   "e", "w", "RL", "RC", "RR",   "e",  "w", "RL", "RC", "RR" };
+		int x_mod, y_mod;
+
+		y_mod = y  % 2 * 2 + (char_height * 2 / gtd->screen->char_height ? 1 : 0);
+		x_mod = x  % 5;
+
+		y = y  / 2;
+		x = x  / 5;
+
+		strcpy(exit, grid[URANGE(0, 5 * y_mod + x_mod, 20)]);
+
+		switch (5 * y_mod + x_mod)
+		{
+			case 0:
+				y--;
+			case 5:
+			case 10:
+			case 15:
+				x--;
+				break;
+			case 1:
+			case 2:
+			case 3:
+			case 4:
+				y--;
+				break;
+		}
+
+//		tintin_printf2(ses, "\e[1;32mdebug: y=%d x=%d y_mod=%d x_mod=%d y+x=%d y=%d x=%d", y, x, y_mod, x_mod, 5*y_mod+x_mod, y, x);
+
+		max_y = 2 + (rows + 2) / 2;
+		max_x = 2 + (cols + 4) / 5;
+	}
+	else if (HAS_BIT(ses->map->flags, MAP_FLAG_BLOCKGRAPHICS))
+	{
+		y /= 2;
+		x /= 5;
+
+		max_y = 2 + rows / 2;
+		max_x = 2 + cols / 5;
+	}
+	else if (HAS_BIT(ses->map->flags, MAP_FLAG_MUDFONT))
+	{
+		x /= 2;
+
+		max_y = 2 + rows;
+		max_x = 2 + cols / 2;
+	}
+	else
+	{
+		max_y = 2 + rows;
+		max_x = 2 + cols;
+	}
+
+	y = max_y - 1 - y;
+
+	if (x < 0 || y < 0)
+	{
+		pop_call();
+		return;
+	}
+
+	if (max_x != map_grid_x || max_y != map_grid_y)
+	{
+		pop_call();
+		return;
+	}
+
+	if (ses->map->grid_rooms[x + 1 + max_x * (y - 1)])
+	{
+		if (arg1 && arg2)
+		{
+			check_all_events(ses, SUB_ARG, 2, 2, "MAP %s %s", arg1, arg2, ntos(ses->map->grid_rooms[x + 1 + max_x * (y - 1)]->vnum), exit);
+		}
+		else
+		{
+			check_all_events(ses, SUB_ARG, 0, 2, "MAP MOUSE LOCATION", ntos(ses->map->grid_rooms[x + 1 + max_x * (y - 1)]->vnum), exit);
+		}
+	}
+	pop_call();
+	return;
+}
+
+void update_terrain(struct session *ses)
+{
+	struct room_data *room;
+	int vnum;
+
+	DEL_BIT(ses->map->flags, MAP_FLAG_UPDATETERRAIN);
+
+	for (vnum = 1 ; vnum < ses->map->size ; vnum++)
+	{
+		room = ses->map->room_list[vnum];
+
+		if (room)
+		{
+			room->terrain_index = bsearch_alpha_list(ses->list[LIST_TERRAIN], room->terrain, 0);
+		}
+	}
+}
+
+/*
+	Map options
+*/
+
+
+DO_MAP(map_at)
+{
+	int new_room;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ALL, SUB_NONE);
+
+	new_room = find_room(ses, arg1);
+
+	ses->map->at_room = ses->map->in_room;
+
+	if (new_room == 0)
+	{
+		new_room = find_location(ses, arg1);
+
+		if (new_room == 0)
+		{
+			show_error(ses, LIST_COMMAND, "#MAP AT: Couldn't find room or exit {%s}.", arg1);
+
+			return;
+		}
+	}
+
+	ses->map->in_room = new_room;
+
+	script_driver(ses, LIST_COMMAND, arg2);
+
+	if (ses->map)
+	{
+		ses->map->in_room = ses->map->at_room;
+	}
+}
+
+DO_MAP(map_center)
+{
+	char arg3[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		ses->map->center_x = ses->map->center_y = ses->map->center_z = 0;
+	}
+	else
+	{
+		arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+		arg = sub_arg_in_braces(ses, arg, arg3, GET_ONE, SUB_VAR|SUB_FUN);
+
+		if (!is_math(ses, arg1) || !is_math(ses, arg2) || !is_math(ses, arg3))
+		{
+			show_error(ses, LIST_COMMAND, "#SYNTAX: #MAP CENTER {X} {Y} {Z}");
+
+			return;
+		}
+		else
+		{
+			ses->map->center_x = get_number(ses, arg1);
+			ses->map->center_y = get_number(ses, arg2);
+			ses->map->center_z = get_number(ses, arg3);
+
+			show_message(ses, LIST_COMMAND, "#MAP CENTER SET TO {%d} {%d} {%d}", ses->map->center_x, ses->map->center_y, ses->map->center_z);
+		}
+	}
+}
+
+DO_MAP(map_color)
+{
+	char buf[BUFFER_SIZE];
+	int index;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg1)
+	{
+		if (!strcasecmp(arg1, "RESET"))
+		{
+			for (index = 0 ; map_color_table[index].name ; index++)
+			{
+				strncpy(ses->map->color[index], map_color_table[index].code, COLOR_SIZE - 1);
+				strncpy(ses->map->color_raw[index], map_color_table[index].code, COLOR_SIZE - 1);
+			}
+
+			return;
+		}
+
+		for (index = 0 ; map_color_table[index].name ; index++)
+		{
+			if (is_abbrev(arg1, map_color_table[index].name))
+			{
+				if (is_abbrev(arg2, "RESET"))
+				{
+					translate_color_names(ses, map_color_table[index].code, ses->map->color[index]);
+					strncpy(ses->map->color_raw[index], map_color_table[index].code, COLOR_SIZE - 1);
+				}
+				else
+				{
+					translate_color_names(ses, arg2, ses->map->color[index]);
+					strncpy(ses->map->color_raw[index], arg2, COLOR_SIZE - 1);
+				}
+
+				get_color_names(ses, ses->map->color[index], buf);
+
+				show_message(ses, LIST_COMMAND, "#MAP COLOR %s%10s\e[0m SET TO {%s}", ses->map->color[index], buf, map_color_table[index].name, ses->map->color_raw[index]);
+
+				break;
+			}
+		}
+
+		if (map_color_table[index].name == NULL)
+		{
+			show_error(ses, LIST_COMMAND, "#SYNTAX: #MAP COLOR {AVOID|BACKGROUND|EXIT|HIDE|INVIS|PATH|ROOM|USER} {COLOR CODE}");
+
+			return;
+		}
+		show_message(ses, LIST_COMMAND, "#MAP: %s color set to: %s", arg1, arg2);
+	}
+	else
+	{
+		for (index = 0 ; map_color_table[index].name ; index++)
+		{
+			get_color_names(ses, ses->map->color[index], buf);
+
+			show_message(ses, LIST_COMMAND, "#MAP COLOR %s%10s\e[0m SET TO {%s}", buf, map_color_table[index].name, ses->map->color_raw[index]);
+		}
+	}
+}
+
+DO_MAP(map_create)
+{
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+
+	create_map(ses, arg1);
+
+	tintin_printf2(ses, "#MAP: %d room map created, use #map goto 1, to proceed", ses->map->size);
+}
+
+DO_MAP(map_debug)
+{
+	tintin_printf2(ses, "max spatial grid x: %d", ses->map->max_grid_x);
+	tintin_printf2(ses, "max spatial grid y: %d", ses->map->max_grid_y);
+	tintin_printf2(ses, "     max undo size: %d", ses->map->undo_size);
+	tintin_printf2(ses, "           in room: %d", ses->map->in_room);
+	tintin_printf2(ses, "           at room: %d", ses->map->at_room);
+	tintin_printf2(ses, "         last room: %d", ses->map->last_room);
+	tintin_printf2(ses, "             stamp: %d", ses->map->search->stamp);
+	tintin_printf2(ses, "            length: %f", ses->map->room_list[ses->map->in_room]->length);
+	tintin_printf2(ses, "          nofollow: %d", ses->map->nofollow);
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg1)
+	{
+		if (is_abbrev(arg1, "undo"))
+		{
+			struct link_data *link;
+
+			for (link = ses->map->undo_head ; link ; link = link->next)
+			{
+				tintin_printf2(ses, "%05s %05s %s", link->str1, link->str2, link->str3);
+			}
+		}
+	}
+}
+
+DO_MAP(map_delete)
+{
+	int room;
+	struct exit_data *exit;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (is_number(arg1))
+	{
+		room = find_room(ses, arg1);
+
+		if (room == 0)
+		{
+			show_error(ses, LIST_COMMAND, "#MAP DELETE {%s} - No room with that vnum found", arg1);
+
+			return;
+		}
+	}
+	else
+	{
+		exit = find_exit(ses, ses->map->in_room, arg1);
+
+		if (exit)
+		{
+			room = exit->vnum;
+		}
+
+		if (exit == NULL)
+		{
+			show_error(ses, LIST_COMMAND, "#MAP: No exit with that name found");
+			
+			return;
+		}
+
+		room = exit->vnum;
+	}
+
+	if (room == ses->map->in_room)
+	{
+		show_error(ses, LIST_COMMAND, "#MAP: You must first leave the room you're trying to delete");
+		
+		return;
+	}
+
+	delete_room(ses, room, TRUE);
+
+	show_message(ses, LIST_COMMAND, "#MAP: Room {%d} deleted", room);
+}
+
+DO_MAP(map_destroy)
+{
+	struct exit_data *exit;
+	int index, cnt;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (is_abbrev(arg1, "AREA"))
+	{
+		if (*arg2 == 0)
+		{
+			show_error(ses, LIST_COMMAND, "#SYNTAX: #MAP DESTROY AREA {<AREA NAME>}");
+
+			return;
+		}
+
+		if (ses->map->room_list[ses->map->in_room] && !strcmp(arg2, ses->map->room_list[ses->map->in_room]->area))
+		{
+			show_error(ses, LIST_COMMAND, "#MAP DESTROY AREA: YOU MUST FIRST LEAVE THE AREA YOU ARE TRYING TO DESTROY.");
+
+			return;
+		}
+
+		for (index = cnt = 0 ; index < ses->map->size ; index++)
+		{
+			if (ses->map->room_list[index])
+			{
+				if (!strcmp(arg2, ses->map->room_list[index]->area))
+				{
+					cnt++;
+
+					delete_room(ses, index, FALSE);
+				}
+			}
+		}
+
+		for (index = 0 ; index < ses->map->size ; index++)
+		{
+			if (ses->map->room_list[index])
+			{
+				for (exit = ses->map->room_list[index]->f_exit ; exit ; exit = exit->next)
+				{
+					if (ses->map->room_list[exit->vnum] == NULL)
+					{
+						delete_exit(ses, index, exit);
+
+						if (ses->map->room_list[index]->f_exit)
+						{
+							exit = ses->map->room_list[index]->f_exit;
+						}
+						else
+						{
+							break;
+						}
+					}
+				}
+			}
+		}
+		show_message(ses, LIST_COMMAND, "#MAP DESTROY AREA: DELETED %d ROOMS.", cnt);
+	}
+	else if (is_abbrev(arg1, "WORLD"))
+	{
+		cnt = delete_map(ses);
+
+		tintin_printf2(ses, "#MAP DESTROY WORLD: DELETED %d ROOMS.", cnt);
+	}
+	else
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #MAP DESTROY {AREA|WORLD} {<ARGUMENT>}");
+	}
+}
+
+DO_MAP(map_dig)
+{
+	char arg3[BUFFER_SIZE];
+	int room;
+	struct exit_data *exit;
+	struct listnode *node;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ALL, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg3, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #MAP DIG {<DIRECTION>|<VNUM>} {<LOCATION>|NEW}");
+		
+		return;
+	}
+
+	room = atoi(arg1);
+
+	if (room > 0 && room < ses->map->size)
+	{
+		if (ses->map->room_list[room] == NULL)
+		{
+			add_undo(ses, "%d %d %d", room, ses->map->in_room, MAP_UNDO_CREATE);
+
+			create_room(ses, "{%d} {0} {} {} { } {} {} {} {} {} {1.0} {}", room);
+		}
+		return;
+	}
+
+	exit = find_exit(ses, ses->map->in_room, arg1);
+
+	if (exit)
+	{
+		show_message(ses, LIST_COMMAND, "#MAP DIG: There is already a room in that direction.");
+		return;
+	}
+
+	if (*arg2 && strcasecmp(arg2, "new"))
+	{
+		if (is_number(arg2))
+		{
+			room = get_number(ses, arg2);
+		}
+		else
+		{
+			room = find_room(ses, arg2);
+		}
+
+		if (room == 0 && !strcasecmp(arg3, "new"))
+		{
+			room = find_new_room(ses);
+		}
+
+		if (room <= 0 || room >= ses->map->size)
+		{
+			show_error(ses, LIST_COMMAND, "#MAP DIG {%s}: Couldn't find room {%s}.", arg1, arg2);
+
+			return;
+		}
+
+		if (ses->map->room_list[room] == NULL)
+		{
+			add_undo(ses, "%d %d %d", room, ses->map->in_room, MAP_UNDO_CREATE|MAP_UNDO_LINK);
+
+			create_room(ses, "{%d} {0} {} {} { } {} {} {} {} {} {1.0} {%s}", room, ses->map->search->id ? ses->map->search->id : "");
+			create_exit(ses, ses->map->in_room, "{%d} {%s} {%s}", room, arg1, arg1);
+		}
+		else
+		{
+			add_undo(ses, "%d %d %d", room, ses->map->in_room, MAP_UNDO_LINK);
+
+			create_exit(ses, ses->map->in_room, "{%d} {%s} {%s}", room, arg1, arg1);
+		}
+		return;
+	}
+
+	room = find_coord(ses, arg1);
+
+	if (room && strcasecmp(arg2, "new"))
+	{
+		show_message(ses, LIST_COMMAND, "#MAP CREATE LINK %5d {%s}.", room, ses->map->room_list[room]->name);
+
+		add_undo(ses, "%d %d %d", room, ses->map->in_room, MAP_UNDO_LINK);
+
+		create_exit(ses, ses->map->in_room, "{%d} {%s} {%s}", room, arg1, arg1);
+	}
+	else
+	{
+		for (room = 1 ; room < ses->map->size ; room++)
+		{
+			if (ses->map->room_list[room] == NULL)
+			{
+				break;
+			}
+		}
+
+		if (room == ses->map->size)
+		{
+			show_error(ses, LIST_COMMAND, "#MAP DIG: Maximum amount of rooms of %d reached.", ses->map->size);
+			
+			return;
+		}
+		add_undo(ses, "%d %d %d", room, ses->map->in_room, MAP_UNDO_CREATE|MAP_UNDO_LINK);
+
+		create_room(ses, "{%d} {0} {} {} { } {} {} {} {} {} {1.0} {}", room);
+		create_exit(ses, ses->map->in_room, "{%d} {%s} {%s}", room, arg1, arg1);
+	}
+
+	if ((node = search_node_list(ses->list[LIST_PATHDIR], arg1)) != NULL)
+	{
+		if (find_exit(ses, room, node->arg2) == NULL)
+		{
+			create_exit(ses, room, "{%d} {%s} {%s}", ses->map->in_room, node->arg2, node->arg2);
+		}
+	}
+}
+
+void exit_edit(struct session *ses, struct exit_data *exit, char *arg, char *arg1, char *arg2, char *arg3)
+{
+	int room, dir;
+
+	if (*arg2 == 0)
+	{
+		tintin_printf2(ses, "    color: %s", str_convert_meta(exit->color, TRUE));
+		tintin_printf2(ses, "  command: %s", exit->cmd);
+		tintin_printf2(ses, "direction: %d", exit->dir);
+		tintin_printf2(ses, "    flags: %d", exit->flags);
+		tintin_printf2(ses, "  get/set: %s", exit->data);
+		tintin_printf2(ses, "     name: %s", exit->name);
+		tintin_printf2(ses, "     vnum: %d", exit->vnum);
+		tintin_printf2(ses, "   weight: %.3f", exit->weight);
+	}
+	else if (is_abbrev(arg2, "COLOR"))
+	{
+		RESTRING(exit->color, arg3);
+
+		show_message(ses, LIST_COMMAND, "#MAP %s {%s} : COLOR SET TO {%s}.", arg, arg1, exit->color);
+	}
+	else if (is_abbrev(arg2, "COMMAND"))
+	{
+		RESTRING(exit->cmd, arg3);
+
+		show_message(ses, LIST_COMMAND, "#MAP %s {%s} : COMMAND SET TO {%s}.", arg, arg1, exit->cmd);
+	}
+	else if (is_abbrev(arg2, "DIRECTION"))
+	{
+		if (is_math(ses, arg3))
+		{
+			dir = (int) get_number(ses, arg3);
+		}
+		else if ((dir = get_exit_dir(ses, arg3)) == 0)
+		{
+			show_error(ses, LIST_COMMAND, "#MAP %s {%s} : DIRECTION {%s} NOT FOUND.", arg, arg1, arg3);
+			
+			return;
+		}
+
+		exit->dir = dir;
+
+		exit->grid = dir_to_grid(exit->dir);
+
+		set_room_exits(ses, ses->map->in_room);
+
+		show_message(ses, LIST_COMMAND, "#MAP %s {%s} : DIRECTION {%s} SET TO {%d}.", arg, arg1, arg3, dir);
+	}
+	else if (is_abbrev(arg2, "FLAGS"))
+	{
+		exit->flags = (int) get_number(ses, arg3);
+
+		show_message(ses, LIST_COMMAND, "#MAP %s {%s} : FLAGS SET TO {%d}.", arg, arg1, exit->flags);
+	}
+	else if (is_abbrev(arg2, "GET"))
+	{
+		if (*arg3)
+		{
+			set_nest_node_ses(ses, arg3, "%s", exit->data);
+		}
+		else
+		{
+			tintin_printf2(ses, "#MAP %s GET: No destination variable.", arg);
+		}
+	}
+	else if (is_abbrev(arg2, "NAME"))
+	{
+		RESTRING(exit->name, arg3);
+
+		show_message(ses, LIST_COMMAND, "#MAP %s {%s} : NAME SET TO {%s}.", arg, arg1, exit->name);
+	}
+	else if (is_abbrev(arg2, "SAVE"))
+	{
+		if (*arg3)
+		{
+			set_nest_node_ses(ses, arg3, "{command}{%s}{destination}{%d}{dir}{%d}{flags}{%d}{name}{%s}{vnum}{%d}{weight}{%.3f}", exit->cmd, tunnel_void(ses, ses->map->in_room, exit->vnum, exit->dir), exit->dir, exit->flags, exit->name, exit->vnum, exit->weight);
+		}
+		else
+		{
+			show_error(ses, LIST_COMMAND, "#MAP %s SAVE: No destination variable.", arg);
+		}
+	}
+	else if (is_abbrev(arg2, "SET"))
+	{
+		RESTRING(exit->data, arg3);
+
+		show_message(ses, LIST_COMMAND, "#MAP %s {%s} : DATA SET TO {%s}.", arg, arg1, exit->data);
+	}
+	else if (is_abbrev(arg2, "VNUM"))
+	{
+		room = atoi(arg3);
+
+		if (room <= 0 || room >= ses->map->size)
+		{
+			show_error(ses, LIST_COMMAND, "#MAP %s VNUM: Invalid room vnum: %d.", arg, room);
+			return;
+		}
+
+		if (ses->map->room_list[room] == NULL)
+		{
+			show_error(ses, LIST_COMMAND, "#MAP %s VNUM: Non existant room vnum: %d.", arg, room);
+			return;
+		}
+		exit->vnum = room;
+
+		show_message(ses, LIST_COMMAND, "#MAP %s {%s} : VNUM SET TO {%s}.", arg, arg1, arg3);
+	}
+	else if (is_abbrev(arg2, "WEIGHT"))
+	{
+		if (get_number(ses, arg3) < 0.001)
+		{
+			show_message(ses, LIST_COMMAND, "#MAP %s {%s} : WEIGHT SHOULD BE AT LEAST 0.001", arg, arg1);
+		}
+		else
+		{
+			exit->weight = (float) get_number(ses, arg3);
+
+			show_message(ses, LIST_COMMAND, "#MAP %s {%s} : WEIGHT SET TO {%.3f}", arg, arg1, exit->weight);
+		}
+	}
+	else
+	{
+		show_error(ses, LIST_COMMAND, "Syntax: #MAP %s {<NAME>} {COMMAND|DIRECTION|GET|NAME|FLAGS|SAVE|SET|VNUM|WEIGHT} {<argument>}", arg);
+	}
+}
+
+DO_MAP(map_entrance)
+{
+	char arg3[BUFFER_SIZE];
+	struct exit_data *exit, *rev_exit;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg3, GET_ONE, SUB_VAR|SUB_FUN);
+
+	exit = find_exit(ses, ses->map->in_room, arg1);
+
+	if (exit == NULL)
+	{
+		show_message(ses, LIST_COMMAND, "#MAP ENTRANCE: Exit {%s} not found.", arg1);
+		
+		return;
+	}
+
+	rev_exit = ses->map->room_list[exit->vnum]->exit_grid[revdir_to_grid(exit->dir)];
+
+	if (rev_exit == NULL)
+	{
+		show_message(ses, LIST_COMMAND, "#MAP ENTRANCE {%s}: Exit {%s} has no matching entrance.");
+
+		return;
+	}
+
+	exit_edit(ses, rev_exit, "ENTRANCE", arg1, arg2, arg3);
+}
+
+DO_MAP(map_exit)
+{
+	char arg3[BUFFER_SIZE];
+	struct exit_data *exit;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg3, GET_ONE, SUB_VAR|SUB_FUN);
+
+	exit = find_exit(ses, ses->map->in_room, arg1);
+
+	if (exit == NULL)
+	{
+		show_error(ses, LIST_COMMAND, "#MAP EXIT: Exit {%s} not found.", arg1);
+		
+		return;
+	}
+
+	exit_edit(ses, exit, "EXIT", arg1, arg2, arg3);
+}
+
+DO_MAP(map_exitflag)
+{
+	struct exit_data *exit;
+	char arg3[BUFFER_SIZE];
+	int flag;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg3, GET_ONE, SUB_VAR|SUB_FUN);
+
+	exit = find_exit(ses, ses->map->in_room, arg1);
+
+	if (exit == NULL)
+	{
+		show_error(ses, LIST_COMMAND, "#MAP EXITFLAG: EXIT {%s} NOT FOUND.", arg1);
+
+		return;
+	}
+
+	if (*arg2 == 0)
+	{
+		tintin_printf2(ses, "#MAP: AVOID FLAG IS SET TO: %s.", HAS_BIT(exit->flags, EXIT_FLAG_AVOID) ? "ON" : "OFF");
+		tintin_printf2(ses, "#MAP: HIDE FLAG IS SET TO: %s.", HAS_BIT(exit->flags, EXIT_FLAG_HIDE) ? "ON" : "OFF");
+		tintin_printf2(ses, "#MAP: INVIS FLAG IS SET TO: %s.", HAS_BIT(exit->flags, EXIT_FLAG_INVIS) ? "ON" : "OFF");
+		tintin_printf2(ses, "#MAP: BLOCK FLAG IS SET TO: %s.", HAS_BIT(exit->flags, EXIT_FLAG_BLOCK) ? "ON" : "OFF");
+
+		return;
+	}
+
+	if (is_abbrev(arg2, "AVOID"))
+	{
+		flag = EXIT_FLAG_AVOID;
+	}
+	else if (is_abbrev(arg2, "BLOCK"))
+	{
+		flag = EXIT_FLAG_BLOCK;
+	}
+	else if (is_abbrev(arg2, "HIDE"))
+	{
+		flag = EXIT_FLAG_HIDE;
+	}
+	else if (is_abbrev(arg2, "INVISIBLE"))
+	{
+		flag = EXIT_FLAG_INVIS;
+	}
+	else
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #MAP EXITFLAG {%s} <AVOID|HIDE|INVIS> [ON|OFF]", arg1);
+
+		return;
+	}
+
+	if (*arg3 == 0)
+	{
+		TOG_BIT(exit->flags, flag);
+	}
+	else if (is_abbrev(arg3, "ON"))
+	{
+		SET_BIT(exit->flags, flag);
+	}
+	else if (is_abbrev(arg3, "OFF"))
+	{
+		DEL_BIT(exit->flags, flag);
+	}
+	else
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #MAP EXITFLAG {%s} {%s} [ON|OFF]", arg3);
+	}
+
+	if (is_abbrev(arg2, "AVOID"))
+	{
+		show_message(ses, LIST_COMMAND, "#MAP: AVOID FLAG SET TO %s.", HAS_BIT(exit->flags, EXIT_FLAG_AVOID) ? "ON" : "OFF");
+	}
+	else if (is_abbrev(arg2, "BLOCK"))
+	{
+		show_message(ses, LIST_COMMAND, "#MAP: BLOCK FLAG SET TO %s.", HAS_BIT(exit->flags, EXIT_FLAG_BLOCK) ? "ON" : "OFF");
+	}
+	else if (is_abbrev(arg2, "HIDE"))
+	{
+		show_message(ses, LIST_COMMAND, "#MAP: HIDE FLAG SET TO %s.", HAS_BIT(exit->flags, EXIT_FLAG_HIDE) ? "ON" : "OFF");
+	}
+	else if (is_abbrev(arg2, "INVISIBLE"))
+	{
+		show_message(ses, LIST_COMMAND, "#MAP: INVIS FLAG SET TO %s.", HAS_BIT(exit->flags, EXIT_FLAG_INVIS) ? "ON" : "OFF");
+	}
+}
+
+DO_MAP(map_explore)
+{
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+
+	explore_path(ses, FALSE, arg1, "");
+}
+
+DO_MAP(map_find)
+{
+	shortest_path(ses, FALSE, arg1, arg);
+}
+
+DO_MAP(map_flag)
+{
+	int flag = 0, unflag = 0;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg1)
+	{
+		if (is_abbrev(arg1, "static"))
+		{
+			flag   = MAP_FLAG_STATIC;
+		}
+		else if (is_abbrev(arg1, "vtmap"))
+		{
+			flag   = MAP_FLAG_VTMAP;
+		}
+		else if (is_abbrev(arg1, "asciigraphics"))
+		{
+			flag   = MAP_FLAG_ASCIIGRAPHICS;
+			unflag = MAP_FLAG_MUDFONT|MAP_FLAG_UNICODEGRAPHICS|MAP_FLAG_BLOCKGRAPHICS;
+		}
+		else if (is_abbrev(arg1, "asciilength"))
+		{
+			flag   = MAP_FLAG_ASCIIVNUMS|MAP_FLAG_ASCIILENGTH;
+		}
+		else if (is_abbrev(arg1, "asciivnums"))
+		{
+			flag   = MAP_FLAG_ASCIIVNUMS;
+			unflag = MAP_FLAG_ASCIILENGTH;
+		}
+		else if (is_abbrev(arg1, "blockgraphics"))
+		{
+			flag = MAP_FLAG_BLOCKGRAPHICS;
+			unflag = MAP_FLAG_MUDFONT|MAP_FLAG_UNICODEGRAPHICS|MAP_FLAG_ASCIIGRAPHICS;
+		}
+		else if (is_abbrev(arg1, "direction"))
+		{
+			flag = MAP_FLAG_DIRECTION;
+		}
+		else if (is_abbrev(arg1, "mudfont"))
+		{
+			flag = MAP_FLAG_MUDFONT;
+			unflag = MAP_FLAG_ASCIIGRAPHICS|MAP_FLAG_UNICODEGRAPHICS|MAP_FLAG_BLOCKGRAPHICS;
+		}
+		else if (is_abbrev(arg1, "nofollow"))
+		{
+			flag = MAP_FLAG_NOFOLLOW;
+		}
+		else if (is_abbrev(arg1, "simplegraphics"))
+		{
+			unflag = MAP_FLAG_ASCIIVNUMS|MAP_FLAG_SYMBOLGRAPHICS|MAP_FLAG_MUDFONT|MAP_FLAG_ASCIIGRAPHICS|MAP_FLAG_UNICODEGRAPHICS|MAP_FLAG_BLOCKGRAPHICS;
+		}
+		else if (is_abbrev(arg1, "symbolgraphics"))
+		{
+			flag = MAP_FLAG_SYMBOLGRAPHICS;
+			unflag = MAP_FLAG_ASCIIGRAPHICS|MAP_FLAG_UNICODEGRAPHICS|MAP_FLAG_BLOCKGRAPHICS;
+		}
+		else if (is_abbrev(arg1, "terrain"))
+		{
+			flag = MAP_FLAG_TERRAIN;
+		}
+		else if (is_abbrev(arg1, "unicodegraphics"))
+		{
+			flag = MAP_FLAG_UNICODEGRAPHICS;
+			unflag = MAP_FLAG_ASCIIGRAPHICS|MAP_FLAG_MUDFONT|MAP_FLAG_BLOCKGRAPHICS;
+		}
+		else
+		{
+			show_error(ses, LIST_COMMAND, "#MAP: Invalid flag {%s}.", arg1);
+
+			return;
+		}
+	}
+	else
+	{
+		tintin_printf2(ses, "#MAP: AsciiGraphics flag is set to %s.", HAS_BIT(ses->map->flags, MAP_FLAG_ASCIIGRAPHICS) ? "ON" : "OFF");
+		tintin_printf2(ses, "#MAP: AsciiVnums flag is set to %s.", HAS_BIT(ses->map->flags, MAP_FLAG_ASCIIVNUMS) ? "ON" : "OFF");
+		tintin_printf2(ses, "#MAP: BlockGraphics flag is set to %s.", HAS_BIT(ses->map->flags, MAP_FLAG_BLOCKGRAPHICS) ? "ON" : "OFF");
+		tintin_printf2(ses, "#MAP: Direction flag is set to %s.", HAS_BIT(ses->map->flags, MAP_FLAG_DIRECTION) ? "ON" : "OFF");
+		tintin_printf2(ses, "#MAP: MudFont flag is set to %s", HAS_BIT(ses->map->flags, MAP_FLAG_MUDFONT) ? "ON" : "OFF");
+		tintin_printf2(ses, "#MAP: NoFollow flag is set to %s.", HAS_BIT(ses->map->flags, MAP_FLAG_NOFOLLOW) ? "ON" : "OFF");
+		tintin_printf2(ses, "#MAP: SimpleGraphics flag is set to %s.", !HAS_BIT(ses->map->flags, MAP_FLAG_ASCIIVNUMS|MAP_FLAG_SYMBOLGRAPHICS|MAP_FLAG_MUDFONT|MAP_FLAG_ASCIIGRAPHICS|MAP_FLAG_UNICODEGRAPHICS|MAP_FLAG_BLOCKGRAPHICS) ? "ON" : "OFF");
+		tintin_printf2(ses, "#MAP: Static flag is set to %s.", HAS_BIT(ses->map->flags, MAP_FLAG_STATIC) ? "ON" : "OFF");
+		tintin_printf2(ses, "#MAP: SymbolGraphics flag is set to %s.", HAS_BIT(ses->map->flags, MAP_FLAG_SYMBOLGRAPHICS) ? "ON" : "OFF");
+		tintin_printf2(ses, "#MAP: Terrain flag is set to %s.", HAS_BIT(ses->map->flags, MAP_FLAG_TERRAIN) ? "ON" : "OFF");
+		tintin_printf2(ses, "#MAP: UnicodeGraphics flag is set to %s.", HAS_BIT(ses->map->flags, MAP_FLAG_UNICODEGRAPHICS) ? "ON" : "OFF");
+		tintin_printf2(ses, "#MAP: VTmap flag is set to %s.", HAS_BIT(ses->map->flags, MAP_FLAG_VTMAP) ? "ON" : "OFF");
+
+		return;
+	}
+
+	if (is_abbrev(arg2, "ON"))
+	{
+		SET_BIT(ses->map->flags, flag);
+	}
+	else if (is_abbrev(arg2, "OFF"))
+	{
+		DEL_BIT(ses->map->flags, flag);
+	}
+	else
+	{
+		TOG_BIT(ses->map->flags, flag);
+	}
+
+	if (unflag)
+	{
+		DEL_BIT(ses->map->flags, unflag);
+	}
+
+	if (is_abbrev(arg1, "asciigraphics"))
+	{
+		show_message(ses, LIST_COMMAND, "#MAP: AsciiGraphics flag set to %s.", HAS_BIT(ses->map->flags, MAP_FLAG_ASCIIGRAPHICS) ? "ON" : "OFF");
+	}
+	else if (is_abbrev(arg1, "asciivnums"))
+	{
+		show_message(ses, LIST_COMMAND, "#MAP: AsciiVnums flag set to %s.", HAS_BIT(ses->map->flags, MAP_FLAG_ASCIIVNUMS) ? "ON" : "OFF");
+	}
+	else if (is_abbrev(arg1, "direction"))
+	{
+		show_message(ses, LIST_COMMAND, "#MAP: Direction flag set to %s.", HAS_BIT(ses->map->flags, MAP_FLAG_DIRECTION) ? "ON" : "OFF");
+	}
+	else if (is_abbrev(arg1, "mudfont"))
+	{
+		show_message(ses, LIST_COMMAND, "#MAP: MudFont flag set to %s.", HAS_BIT(ses->map->flags, MAP_FLAG_MUDFONT) ? "ON" : "OFF");
+	}
+	else if (is_abbrev(arg1, "nofollow"))
+	{
+		show_message(ses, LIST_COMMAND, "#MAP: NoFollow flag set to %s.", HAS_BIT(ses->map->flags, MAP_FLAG_NOFOLLOW) ? "ON" : "OFF");
+	}
+	else if (is_abbrev(arg1, "static"))
+	{
+		show_message(ses, LIST_COMMAND, "#MAP: Static flag set to %s.", HAS_BIT(ses->map->flags, MAP_FLAG_STATIC) ? "ON" : "OFF");
+	}
+	else if (is_abbrev(arg1, "simplegraphics"))
+	{
+		show_message(ses, LIST_COMMAND, "#MAP: All graphic modes have been disabled.");
+	}
+	else if (is_abbrev(arg1, "symbolgraphics"))
+	{
+		show_message(ses, LIST_COMMAND, "#MAP: SymbolGraphics flag set to %s.", HAS_BIT(ses->map->flags, MAP_FLAG_SYMBOLGRAPHICS) ? "ON" : "OFF");
+	}
+	else if (is_abbrev(arg1, "terrain"))
+	{
+		show_message(ses, LIST_COMMAND, "#MAP: Terrain flag set to %s.", HAS_BIT(ses->map->flags, MAP_FLAG_TERRAIN) ? "ON" : "OFF");
+	}
+	else if (is_abbrev(arg1, "unicodegraphics"))
+	{
+		show_message(ses, LIST_COMMAND, "#MAP: UnicodeGraphics flag set to %s.", HAS_BIT(ses->map->flags, MAP_FLAG_UNICODEGRAPHICS) ? "ON" : "OFF");
+	}
+	else if (is_abbrev(arg1, "vtmap"))
+	{
+		show_message(ses, LIST_COMMAND, "#MAP: VTmap flag set to %s.", HAS_BIT(ses->map->flags, MAP_FLAG_VTMAP) ? "ON" : "OFF");
+	}
+
+
+}
+
+DO_MAP(map_get)
+{
+	struct room_data *room = ses->map->room_list[ses->map->in_room];
+	struct exit_data *exit;
+	char exits[BUFFER_SIZE], arg3[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ALL, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg3, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg3)
+	{
+		if (atoi(arg3) > 0 && atoi(arg3) < ses->map->size)
+		{
+			room = ses->map->room_list[atoi(arg3)];
+		}
+		else
+		{
+			room = NULL;
+		}
+	}
+
+	if (room == NULL)
+	{
+		set_nest_node_ses(ses, arg2, "0");
+	}
+	else if (*arg1 == 0 || *arg2 == 0)
+	{
+		tintin_printf2(ses, " worldflags: %d", ses->map->flags);
+		tintin_printf2(ses, "  worldsize: %d", ses->map->size);
+		tintin_printf2(ses, "");
+		tintin_printf2(ses, "   roomvnum: %d", room->vnum);
+		tintin_printf2(ses, "   roomarea: %s", room->area);
+		tintin_printf2(ses, "  roomcolor: %s", room->color);
+		tintin_printf2(ses, "   roomdata: %s", room->data);
+		tintin_printf2(ses, "   roomdesc: %s", room->desc);
+		tintin_printf2(ses, "  roomexits: %d", get_room_exits(ses, room->vnum));
+		tintin_printf2(ses, "  roomflags: %d", room->flags);
+		tintin_printf2(ses, "     roomid: %s", room->id);
+		tintin_printf2(ses, "   roomname: %s", room->name);
+		tintin_printf2(ses, "   roomnote: %s", room->note);
+		tintin_printf2(ses, " roomsymbol: %s", room->symbol);
+		tintin_printf2(ses, "roomterrain: %s", room->terrain);
+		tintin_printf2(ses, " roomweight: %.3f", room->weight);
+	}
+	else
+	{
+		if (is_abbrev(arg1, "all"))
+		{
+			exits[0] = 0;
+
+			for (exit = room->f_exit ; exit ; exit = exit->next)
+			{
+				cat_sprintf(exits, "{%s}{%d}", exit->name, exit->vnum);
+			}
+			set_nest_node_ses(ses, arg2, "{area}{%s}{color}{%s}{data}{%s}{desc}{%s}{exits}{%s}{flags}{%d}{id}{%d}{name}{%s}{note}{%s}{symbol}{%s}{terrain}{%s}{vnum}{%d}{weight}{%.3f}", room->area, room->color, room->data, room->desc, exits, room->flags, room->id, room->name, room->note, room->symbol, room->terrain, room->vnum, room->weight);
+		}
+		else if (is_abbrev(arg1, "roomarea"))
+		{
+			set_nest_node_ses(ses, arg2, "%s", room->area);
+		}
+		else if (is_abbrev(arg1, "roomcolor"))
+		{
+			set_nest_node_ses(ses, arg2, "%s", room->color);
+		}
+		else if (is_abbrev(arg1, "roomdata"))
+		{
+			set_nest_node_ses(ses, arg2, "%s", room->data);
+		}
+		else if (is_abbrev(arg1, "roomdesc"))
+		{
+			set_nest_node_ses(ses, arg2, "%s", room->desc);
+		}
+		else if (is_abbrev(arg1, "roomflags"))
+		{
+			set_nest_node_ses(ses, arg2, "%d", room->flags);
+		}
+		else if (is_abbrev(arg1, "roomid"))
+		{
+			set_nest_node_ses(ses, arg2, "%s", room->id);
+		}
+		else if (is_abbrev(arg1, "roomname"))
+		{
+			set_nest_node_ses(ses, arg2, "%s", room->name);
+		}
+		else if (is_abbrev(arg1, "roomnote"))
+		{
+			set_nest_node_ses(ses, arg2, "%s", room->note);
+		}
+		else if (is_abbrev(arg1, "roomsymbol"))
+		{
+			set_nest_node_ses(ses, arg2, "%s", room->symbol);
+		}
+		else if (is_abbrev(arg1, "roomterrain"))
+		{
+			set_nest_node_ses(ses, arg2, "%s", room->terrain);
+		}
+		else if (is_abbrev(arg1, "roomvnum"))
+		{
+			set_nest_node_ses(ses, arg2, "%d", room->vnum);
+		}
+		else if (is_abbrev(arg1, "roomweight"))
+		{
+			set_nest_node_ses(ses, arg2, "%.3f", room->weight);
+		}
+		else if (is_abbrev(arg1, "roomexits"))
+		{
+			exits[0] = 0;
+
+			for (exit = room->f_exit ; exit ; exit = exit->next)
+			{
+				cat_sprintf(exits, "{%s}{%d}", exit->name, exit->vnum);
+			}
+			set_nest_node_ses(ses, arg2, "%s", exits);
+		}
+		else if (is_abbrev(arg1, "worldflags"))
+		{
+			set_nest_node_ses(ses, arg2, "%d", ses->map->flags);
+		}
+		else if (is_abbrev(arg1, "worldsize"))
+		{
+			set_nest_node_ses(ses, arg2, "%d", ses->map->size);
+		}
+		else
+		{
+			show_message(ses, LIST_COMMAND, "#MAP GET: unknown option: %s.", arg1);
+		}
+	}
+}
+
+DO_MAP(map_global)
+{
+	int room;
+
+	sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		show_message(ses, LIST_COMMAND, "#MAP GLOBAL: GLOBAL ROOM SET TO VNUM %d.", ses->map->global_vnum);
+	}
+	else if (!strcmp(arg1, "0"))
+	{
+		ses->map->global_vnum = ses->map->global_exit->vnum = 0;
+
+		show_message(ses, LIST_COMMAND, "#MAP GLOBAL: GLOBAL ROOM SET TO VNUM %d.", 0);
+	}
+	else
+	{
+		room = find_room(ses, arg);
+
+		if (room)
+		{
+			ses->map->global_vnum = ses->map->global_exit->vnum = room;
+
+			show_message(ses, LIST_COMMAND, "#MAP GLOBAL: GLOBAL ROOM SET TO VNUM %d.", room);
+		}
+		else
+		{
+			show_message(ses, LIST_COMMAND, "#MAP GLOBAL: COULDN'T FIND ROOM %s.", arg1);
+		}
+	}
+}
+
+DO_MAP(map_goto)
+{
+	int room;
+
+	room = find_room(ses, arg);
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ALL, SUB_VAR|SUB_FUN); // look for dig argument
+
+	if (room == 0 && ses->map->search->vnum > 0 && ses->map->search->vnum < ses->map->size && !strcasecmp(arg2, "dig"))
+	{
+		room = ses->map->search->vnum;
+
+		create_room(ses, "{%d} {0} {} {} { } {} {} {} {} {} {1.0} {}", room);
+	}
+
+	if (room == 0)
+	{
+		room = find_room(ses, arg1);
+	}
+
+	if (room == 0 && ses->map->search->id && *ses->map->search->id && !strcasecmp(arg2, "dig"))
+	{
+		room = find_new_room(ses);
+
+		if (room)
+		{
+			create_room(ses, "{%d} {0} {} {} { } {} {} {} {} {} {1.0} {%s}", room, ses->map->search->id);
+		}
+	}
+
+	if (room == 0 && ses->map->in_room)
+	{
+		room = find_path(ses, arg1);
+
+		if (room == 0)
+		{
+			show_error(ses, LIST_COMMAND, "#MAP GOTO: COULDN'T FIND ROOM OR EXIT {%s}.", arg1);
+
+			return;
+		}
+	}
+
+	if (room == 0)
+	{
+		show_message(ses, LIST_COMMAND, "#MAP GOTO: COULDN'T FIND ROOM %s.", arg1);
+
+		return;
+	}
+	add_undo(ses, "%d %d %d", room, ses->map->in_room, MAP_UNDO_MOVE);
+
+	goto_room(ses, room);
+
+	show_message(ses, LIST_COMMAND, "#MAP GOTO: MOVED TO ROOM %d {%s}.", room, *ses->map->room_list[room]->name ? ses->map->room_list[room]->name : ses->map->room_list[room]->id);
+}
+
+DO_MAP(map_info)
+{
+	int room, cnt, exits;
+	struct exit_data *exit;
+	struct room_data *in_room = ses->map->room_list[ses->map->in_room];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (ses->map->in_room)
+	{
+		exit = find_exit(ses, ses->map->in_room, arg1);
+
+		if (exit)
+		{
+			tintin_printf2(ses, "    color: %s", str_convert_meta(exit->color, TRUE));
+			tintin_printf2(ses, "  command: %s", exit->cmd);
+			tintin_printf2(ses, "direction: %d", exit->dir);
+			tintin_printf2(ses, "    flags: %d", exit->flags);
+			tintin_printf2(ses, "  get/set: %s", exit->data);
+			tintin_printf2(ses, "   length: %d", get_exit_length(ses, exit));
+			tintin_printf2(ses, "     name: %s", exit->name);
+			tintin_printf2(ses, "     vnum: %d", exit->vnum);
+			tintin_printf2(ses, "   weight: %.3f", exit->weight);
+
+			return;
+		}
+	}
+
+	for (room = cnt = exits = 0 ; room < ses->map->size ; room++)
+	{
+		if (ses->map->room_list[room])
+		{
+			cnt++;
+
+			exits += get_room_exits(ses, room);
+		}
+	}
+
+	tintin_printf2(ses, " %+16s %-7d %+16s %-7d %+16s %-7d", "Total rooms:", cnt, "Total exits:", exits, "World size:", ses->map->size);
+	tintin_printf2(ses, " %+16s %-7d %+16s %-7d %+16s %-7d",  "Direction:", ses->map->dir, "Last room:", ses->map->last_room, "Undo size:", ses->map->undo_size);
+	tintin_printf2(ses, "");
+
+	strcpy(arg1, "");
+	cat_sprintf(arg1, " %+16s %-7s", "AsciiGraphics:", HAS_BIT(ses->map->flags, MAP_FLAG_ASCIIGRAPHICS) ? "on" : "off");
+	cat_sprintf(arg1, " %+16s %-7s", "AsciiVnums:", HAS_BIT(ses->map->flags, MAP_FLAG_ASCIIVNUMS) ? "on" : "off");
+	cat_sprintf(arg1, " %+16s %-7s", "BlockGraphics:", HAS_BIT(ses->map->flags, MAP_FLAG_BLOCKGRAPHICS) ? "on" : "off");
+	tintin_puts2(ses, arg1);
+
+	strcpy(arg1, "");
+	cat_sprintf(arg1, " %+16s %-7s", "Direction:", HAS_BIT(ses->map->flags, MAP_FLAG_DIRECTION) ? "on" : "off");
+	cat_sprintf(arg1, " %+16s %-7s", "MudFont:", HAS_BIT(ses->map->flags, MAP_FLAG_MUDFONT) ? "on" : "off");
+	cat_sprintf(arg1, " %+16s %-7s", "Nofollow:", HAS_BIT(ses->map->flags, MAP_FLAG_NOFOLLOW) ? "on" : "off");
+	tintin_puts2(ses, arg1);
+
+	strcpy(arg1, "");
+	cat_sprintf(arg1, " %+16s %-7s", "Static:", HAS_BIT(ses->map->flags, MAP_FLAG_STATIC) ? "on" : "off");
+	cat_sprintf(arg1, " %+16s %-7s", "SymbolGraphics:", HAS_BIT(ses->map->flags, MAP_FLAG_SYMBOLGRAPHICS) ? "on" : "off");
+	cat_sprintf(arg1, " %+16s %-7s", "UnicodeGraphics:", HAS_BIT(ses->map->flags, MAP_FLAG_UNICODEGRAPHICS) ? "on" : "off");
+	tintin_puts2(ses, arg1);
+
+	strcpy(arg1, "");
+	cat_sprintf(arg1, " %+16s %-7s", "Vtmap:", HAS_BIT(ses->map->flags, MAP_FLAG_VTMAP) ? "on" : "off");
+	tintin_puts2(ses, arg1);
+
+/*
+	strcpy(arg1, "");
+	cat_sprintf(arg1, " %+16s %-7s",
+	cat_sprintf(arg1, " %+16s %-7s",
+	cat_sprintf(arg1, " %+16s %-7s",
+	tintin_puts2(ses, arg1);
+*/
+
+	tintin_printf2(ses, "");
+	tintin_printf2(ses, " %+16s %4d %4d %4d %4d", "Map Offset:", ses->map->sav_top_row, ses->map->sav_top_col, ses->map->sav_bot_row, ses->map->sav_bot_col);
+	tintin_printf2(ses, " %+16s %4d %4d %4d %4d", "Current Offset:", ses->map->top_row, ses->map->top_col, ses->map->bot_row, ses->map->bot_col);
+
+	if (ses->map->in_room == 0)
+	{
+		return;
+	}
+
+	tintin_printf2(ses, "");
+
+	tintin_printf2(ses, "%+16s %s", "Room area:",    in_room->area);
+	tintin_printf2(ses, "%+16s %s", "Room data:",    in_room->data);
+	tintin_printf2(ses, "%+16s %s", "Room desc:",    in_room->desc);
+	tintin_printf2(ses, "%+16s %s", "Room id:",      in_room->id);
+	tintin_printf2(ses, "%+16s %s", "Room name:",    in_room->name);
+	tintin_printf2(ses, "%+16s %s", "Room note:",    in_room->note);
+	tintin_printf2(ses, "%+16s %s", "Room color:",   str_convert_meta(in_room->color, TRUE));
+	tintin_printf2(ses, "%+16s %s (%d)", "Room terrain:", in_room->terrain, in_room->terrain_index);
+
+	tintin_printf2(ses, "");
+	tintin_printf2(ses, " %+16s %-7d %+16s %-7.3f %+16s %-7s", "Room vnum:", ses->map->in_room, "Room weight:", in_room->weight, "Room symbol:", in_room->symbol);
+
+	tintin_printf2(ses, " %+16s %-7d %+16s %-7d %+16s %-7d",   "Center x:", ses->map->center_x, "Center y:", ses->map->center_y, "Center z:", ses->map->center_z);
+
+	tintin_printf2(ses, "");
+
+	strcpy(arg1, "");
+	cat_sprintf(arg1, " %+16s %-7s",    "Avoid:", HAS_BIT(in_room->flags, ROOM_FLAG_AVOID)    ? "on" : "off");
+	cat_sprintf(arg1, " %+16s %-7s",   "Curved:", HAS_BIT(in_room->flags, ROOM_FLAG_CURVED)   ? "on" : "off");
+	cat_sprintf(arg1, " %+16s %-7s",     "Hide:", HAS_BIT(in_room->flags, ROOM_FLAG_HIDE)     ? "on" : "off");
+
+	tintin_puts2(ses, arg1);
+
+	strcpy(arg1, "");
+	cat_sprintf(arg1, " %+16s %-7s",    "Invis:", HAS_BIT(in_room->flags, ROOM_FLAG_INVIS)    ? "on" : "off");
+	cat_sprintf(arg1, " %+16s %-7s",    "Leave:", HAS_BIT(in_room->flags, ROOM_FLAG_LEAVE)    ? "on" : "off");
+	cat_sprintf(arg1, " %+16s %-7s", "NoGlobal:", HAS_BIT(in_room->flags, ROOM_FLAG_NOGLOBAL) ? "on" : "off");
+	tintin_puts2(ses, arg1);
+
+	strcpy(arg1, "");
+	cat_sprintf(arg1, " %+16s %-7s",   "Static:", HAS_BIT(in_room->flags, ROOM_FLAG_STATIC)   ? "on" : "off");
+	cat_sprintf(arg1, " %+16s %-7s",     "Void:", HAS_BIT(in_room->flags, ROOM_FLAG_VOID)     ? "on" : "off");
+	tintin_puts2(ses, arg1);
+
+	tintin_printf2(ses, "");
+
+	for (exit = in_room->f_exit ; exit ; exit = exit->next)
+	{
+		tintin_printf2(ses, "%+16s %-3s (%3s)   to room: %-5d (%5s)", "Exit:", exit->name, exit->cmd, exit->vnum, ses->map->room_list[exit->vnum]->name);
+	}
+
+	tintin_printf2(ses, "");
+
+	for (room = 0 ; room < ses->map->size ; room++)
+	{
+		if (ses->map->room_list[room])
+		{
+			for (exit = ses->map->room_list[room]->f_exit ; exit ; exit = exit->next)
+			{
+				if (exit->vnum == ses->map->in_room)
+				{
+					tintin_printf2(ses, "%+16s %-3s (%3s) from room: %-5d (%5s)", "Entrance:", exit->name, exit->cmd, room, ses->map->room_list[room]->name);
+				}
+			}
+		}
+	}
+/*
+	for (exit = in_room->f_exit ; exit ; exit = exit->next)
+	{
+		tintin_printf2(ses, "%+14s %-4s %+14s %5d %+14s %5d %+14s %5s", "Exit name:", exit->name, "vnum:", exit->vnum, "flags:", exit->flags, "command:", exit->cmd);
+		tintin_printf2(ses, "%+14s %s", "Exit data:", exit->data);
+	}
+*/
+}
+
+DO_MAP(map_insert)
+{
+	int room, in_room, to_room;
+	struct exit_data *exit;
+	struct listnode *node;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+
+	for (room = 1 ; room < ses->map->size ; room++)
+	{
+		if (ses->map->room_list[room] == NULL)
+		{
+			break;
+		}
+	}
+
+	exit = find_exit(ses, ses->map->in_room, arg1);
+
+	node = search_node_list(ses->list[LIST_PATHDIR], arg1);
+
+	if (exit == NULL)
+	{
+		show_error(ses, LIST_COMMAND, "#MAP: There is no room in that direction.");
+
+		return;
+	}
+
+	if (room == ses->map->size)
+	{
+		show_error(ses, LIST_COMMAND, "#MAP: Maximum amount of rooms of %d reached.", ses->map->size);
+		return;
+	}
+
+	if (node == NULL)
+	{
+		show_error(ses, LIST_COMMAND, "#MAP: Given direction must be a pathdir.");
+		return;
+	}
+
+	in_room = ses->map->in_room;
+	to_room = exit->vnum;
+
+	add_undo(ses, "%d %d %d", room, ses->map->in_room, MAP_UNDO_INSERT);
+
+	create_room(ses, "{%d} {0} {} {} { } {} {} {} {} {} {1.0} {}", room);
+
+	create_exit(ses, room, "{%d} {%s} {%s}", to_room, node->arg1, node->arg1);
+
+	create_exit(ses, room, "{%d} {%s} {%s}", in_room, node->arg2, node->arg2);
+
+	exit->vnum = room;
+
+	if ((exit = find_exit(ses, to_room, node->arg2)) != NULL)
+	{
+		exit->vnum = room;
+	}
+
+	if (*arg)
+	{
+		ses->map->in_room = room;
+		map_roomflag(ses, arg, arg1, arg2);
+		ses->map->in_room = in_room;
+	}
+	show_message(ses, LIST_COMMAND, "#MAP: Inserted room {%d}.", room);
+}
+
+DO_MAP(map_jump)
+{
+	int room;
+
+	sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+
+	room = find_location(ses, arg1);
+
+	if (room)
+	{
+		add_undo(ses, "%d %d %d", room, ses->map->in_room, MAP_UNDO_MOVE);
+
+		goto_room(ses, room);
+
+		show_message(ses, LIST_COMMAND, "#MAP JUMP: JUMPED TO ROOM %d {%s}.", room, *ses->map->room_list[room]->name ? ses->map->room_list[room]->name : ses->map->room_list[room]->id);
+	}
+	else
+	{
+		show_message(ses, LIST_COMMAND, "#MAP JUMP: Couldn't find a room at {%s}.", arg1);
+	}
+}
+
+DO_MAP(map_landmark)
+{
+	struct listroot *root = ses->list[LIST_LANDMARK];
+	struct listnode *node;
+	char arg3[BUFFER_SIZE], arg4[BUFFER_SIZE];
+	int room, i, found;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg3, GET_ALL, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg4, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0 || *arg2 == 0)
+	{
+		i = bsearch_alpha_list(root, arg1, 0);
+
+		if (i > 0)
+		{
+			tintin_printf2(ses, "name: %-16s  vnum:%7d  size: %7s desc %s", root->list[i]->arg1, root->list[i]->val32[0], root->list[i]->arg4, root->list[i]->arg3);
+		}
+		else
+		{
+			for (found = i = 0 ; i < root->used ; i++)
+			{
+				if (*arg1 == 0 || match(ses, root->list[i]->arg1, arg1, SUB_NONE))
+				{
+					tintin_printf2(ses, "name: %-16s  vnum:%7d  size: %7s desc: %s", root->list[i]->arg1, root->list[i]->val32[0], root->list[i]->arg4, root->list[i]->arg3);
+
+					found = TRUE;
+				}
+			}
+
+			if (found == FALSE)
+			{
+				show_message(ses, LIST_COMMAND, "#MAP LANDMARK: NO MATCHES FOUND FOR {%s}.", arg1);
+			}
+		}
+	}
+	else
+	{
+		if (is_math(ses, arg2))
+		{
+			room = (int) get_number(ses, arg2);
+		}
+
+		if (room <= 0 || room >= ses->map->size)
+		{
+			show_error(ses, LIST_COMMAND, "#MAP LANDMARK: INVALID VNUM {%s}.", arg2);
+		}
+		else
+		{
+			node = update_node_list(root, arg1, arg2, arg3, arg4);
+
+			node->val32[0] = room;
+
+			show_message(ses, LIST_COMMAND, "#OK. LANDMARK {%s} HAS VNUM {%d} AND IS DESCRIBED AS {%s} WITH SIZE {%s}.", arg1, room, arg3, arg4);
+		}
+	} 
+}
+
+DO_MAP(map_unlandmark)
+{
+	delete_node_with_wild(ses, LIST_LANDMARK, arg);
+}
+
+DO_MAP(map_leave)
+{
+	if (ses->map->in_room == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#MAP: You're not currently inside the map.");
+	}
+	else
+	{
+		ses->map->last_room = ses->map->in_room;
+		ses->map->in_room = 0;
+
+		show_message(ses, LIST_COMMAND, "#MAP: Leaving the map. Use goto or return to return.");
+
+		check_all_events(ses, SUB_ARG|SUB_SEC, 0, 1, "MAP EXIT MAP", ntos(ses->map->in_room));
+	}
+}
+
+void map_legend_index(struct session *ses, char *arg, int head, int tail)
+{
+	char esc[BUFFER_SIZE], raw[BUFFER_SIZE];
+	int cnt;
+
+	for (cnt = head ; cnt < tail ; cnt++)
+	{
+		arg = sub_arg_in_braces(ses, arg, raw, GET_ONE, SUB_NONE);
+
+		substitute(ses, raw, esc, SUB_ESC);
+
+		if (is_number(esc))
+		{
+			numbertocharacter(ses, esc);
+		}
+		snprintf(ses->map->legend[cnt],     LEGEND_SIZE - 1, "%s", esc);
+		snprintf(ses->map->legend_raw[cnt], LEGEND_SIZE - 1, "%s", raw);
+
+		if (*arg == 0)
+		{
+			break;
+		}
+	}
+	return;
+}
+
+DO_MAP(map_legend)
+{
+	char buf[BUFFER_SIZE], arg3[BUFFER_SIZE];
+	int group, legend;
+
+	push_call("map_legend(%p,%p,%p,%p)",ses,arg,arg1,arg2);
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ALL, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg3, GET_ALL, SUB_VAR|SUB_FUN);
+
+	strcpy(buf, arg2);
+
+	if (*arg1 == 0)
+	{
+		for (group = 0 ; map_group_table[group].name ; group++)
+		{
+			tintin_printf2(ses, " [%-22s] [%-22s] [%3d] [%3d]",
+				map_group_table[group].group,
+				map_group_table[group].name,
+				map_group_table[group].start,
+				map_group_table[group].end);
+		}
+		pop_call();
+		return;
+	}
+
+	if (is_abbrev(arg1, "RESET"))
+	{
+		map_legend(ses, "{ASCII} {RESET}", arg1, arg2);
+		map_legend(ses, "{NESW} {RESET}", arg1, arg2);
+		map_legend(ses, "{MUDFONT BRAILLE TUBE} {RESET}", arg1, arg2);
+		map_legend(ses, "{UNICODE GRAPHICS} {RESET}", arg1, arg2);
+
+		pop_call();
+		return;
+	}
+
+	if (is_math(ses, arg1))
+	{
+		legend = (int) get_number(ses, arg1);
+
+		if (legend < map_group_table[0].start || legend >= map_group_table[0].end)
+		{
+			show_error(ses, LIST_COMMAND, "#SYNTAX: #MAP LEGEND {%d - %d} {[SYMBOL]}", map_group_table[0].start,  map_group_table[0].end);
+		}
+		else if (*arg2 == 0)
+		{
+			if (strip_vt102_strlen(ses, ses->map->legend[legend]) > 1)
+			{
+				tintin_printf2(ses, " [%-22s] [%-20s] [%3d] [ %12s ] [ %s]",
+					map_legend_table[legend].group,
+					map_legend_table[legend].name,
+					legend,
+					ses->map->legend_raw[legend],
+					ses->map->legend[legend]);
+			}
+			else
+			{
+				tintin_printf2(ses, " [%-22s] [%-20s] [%3d] [ %12s ] [ %s ]",
+					map_legend_table[legend].group,
+					map_legend_table[legend].name,
+					legend,
+					ses->map->legend_raw[legend],
+					ses->map->legend[legend]);
+			}
+		}
+		else
+		{
+			map_legend_index(ses, arg2, legend, legend + 1);
+		}
+		pop_call();
+		return;
+	}
+
+	for (group = 0 ; map_group_table[group].name ; group++)
+	{
+		if (is_abbrev(arg1, map_group_table[group].name))
+		{
+			break;
+		}
+	}
+
+
+	if (!map_group_table[group].name)
+	{
+		show_error(ses, LIST_COMMAND, "#MAP LEGEND: UNKNOWN LEGEND {%s} TRY:", arg1);
+
+		map_legend(ses, "", arg1, arg2);
+
+		pop_call();
+		return;
+	}
+
+	if (*arg2 == 0)
+	{
+		for (legend = map_group_table[group].start ; legend < map_group_table[group].end ; legend++)
+		{
+			if (strip_vt102_strlen(ses, ses->map->legend[legend]) > 1)
+			{
+				tintin_printf2(ses, " [%-22s] [%-20s] [%3d] [ %12s ] [ %s]",
+					map_legend_table[legend].group,
+					map_legend_table[legend].name,
+					legend,
+					ses->map->legend_raw[legend],
+					ses->map->legend[legend]);
+			}
+			else
+			{
+				tintin_printf2(ses, " [%-22s] [%-20s] [%3d] [ %12s ] [ %s ]",
+					map_legend_table[legend].group,
+					map_legend_table[legend].name,
+					legend,
+					ses->map->legend_raw[legend],
+					ses->map->legend[legend]);
+			}
+		}
+		pop_call();
+		return;
+	}
+
+	if (is_abbrev(arg2, "RESET"))
+	{
+		map_legend_index(ses, map_group_table[group].reset, map_group_table[group].start, map_group_table[group].end);
+
+		pop_call();
+		return;
+	}
+
+	for (legend = map_group_table[group].start ; legend < map_group_table[group].end ; legend++)
+	{
+		if (!strcasecmp(space_out(arg2), space_out(map_legend_table[legend].name)))
+		{
+			if (*arg3 == 0)
+			{
+				tintin_printf2(ses, "  [%-22s]  [%-20s]  [%3d]  [ %8s ]  [ %s ]",
+					map_group_table[group].name,
+					map_legend_table[legend].name,
+					legend,
+					ses->map->legend_raw[legend],
+					ses->map->legend[legend]);
+			}
+			else
+			{
+				map_legend_index(ses, arg3, legend, legend + 1);
+			}
+			break;
+		}
+	}
+
+	if (legend == map_group_table[group].end)
+	{
+		for (legend = map_group_table[group].start ; legend < map_group_table[group].end ; legend++)
+		{
+			if (is_abbrev(space_out(arg2), space_out(map_legend_table[legend].name)))
+			{
+				if (*arg3 == 0)
+				{
+					tintin_printf2(ses, "  [%-22s]  [%-20s]  [%3d]  [ %8s ]  [ %s ]",
+						map_group_table[group].name,
+						map_legend_table[legend].name,
+						legend,
+						ses->map->legend_raw[legend],
+						ses->map->legend[legend]);
+				}
+				else
+				{
+					map_legend_index(ses, arg3, legend, legend + 1);
+				}
+				break;
+			}
+		}
+	}
+
+
+	if (legend == map_group_table[group].end)
+	{
+		if (strlen(arg2) > (map_group_table[group].end - map_group_table[group].start) * 2)
+		{
+			map_legend_index(ses, arg2, map_group_table[group].start, map_group_table[group].end);
+		}
+		else
+		{
+			show_error(ses, LIST_COMMAND, "#SYNTAX: #MAP LEGEND {%s} {{arg %d} {arg %d} ... {arg %d} {arg %d}",
+				map_group_table[group].group,
+				map_group_table[group].start,
+				map_group_table[group].start - 1,
+				map_group_table[group].end - 1,
+				map_group_table[group].end);
+		}
+	}
+
+	pop_call();
+	return;
+}
+
+DO_MAP(map_link)
+{
+	char arg3[BUFFER_SIZE];
+	struct listnode *node;
+	struct exit_data *exit;
+	int room;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg3, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0 || *arg2 == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #MAP LINK {<DIRECTION>} {<LOCATION>} {BOTH}");
+		return;
+	}
+
+	room = find_room(ses, arg2);
+
+	if (room == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#MAP: Couldn't find room {%s}.", arg1);
+		return;
+	}
+
+	exit = find_exit(ses, ses->map->in_room, arg1);
+
+	if (exit)
+	{
+		delete_exit(ses, ses->map->in_room, exit);
+	}
+
+	create_exit(ses, ses->map->in_room, "{%d} {%s} {%s}", room, arg1, arg1);
+
+	if (is_abbrev(arg3, "both"))
+	{
+		if ((node = search_node_list(ses->list[LIST_PATHDIR], arg1)) != NULL)
+		{
+			if (find_exit(ses, room, node->arg2) == NULL)
+			{
+				create_exit(ses, room, "{%d} {%s} {%s}", ses->map->in_room, node->arg2, node->arg2);
+			}
+		}
+	}
+	show_message(ses, LIST_COMMAND, "#MAP LINK: Connected room {%s} to {%s}.", ses->map->room_list[ses->map->in_room]->name, ses->map->room_list[room]->name);
+}
+
+DO_MAP(map_list)
+{
+	struct room_data *room;
+	char var[BUFFER_SIZE];
+	int vnum;
+
+	map_search_compile(ses, "0", var);
+
+	searchgrid_find(ses, ses->map->in_room, ses->map->search);
+
+	map_search_compile(ses, arg, var);
+
+	set_nest_node_ses(ses, var, "");
+
+	for (vnum = 0 ; vnum < ses->map->size ; vnum++)
+	{
+		if (match_room(ses, vnum, ses->map->search))
+		{
+			room = ses->map->room_list[vnum];
+
+			if (*var)
+			{
+				add_nest_node_ses(ses, var, "{%d} {{distance}{%.3f}{x}{%d}{y}{%d}{z}{%d}}", room->vnum, ses->map->search->stamp == room->search_stamp ? room->length  : -1, room->x, room->y, room->z);
+			}
+			else
+			{
+				if (ses->map->search->stamp == room->search_stamp)
+				{
+					if (room->w == 0)
+					{
+						tintin_printf2(ses, "vnum: %5d  dist: %8.3f  x: %4d  y: %4d  z: %4d  name: %s", room->vnum, room->length, room->x, room->y, room->z, room->name);
+					}
+					else
+					{
+						tintin_printf2(ses, "vnum: %5d  dist: %8.3f  x: %4s  y: %4s  z: %4s  name: %s", room->vnum, room->length, "?", "?", "?", room->name);
+					}
+				}
+				else
+				{
+						tintin_printf2(ses, "vnum: %5d  dist: %8.8s  x:    ?  y:    ?  z:    ?  name: %s", room->vnum, "-1", room->name);
+				}
+			}
+		}
+	}
+}
+
+DO_MAP(map_map)
+{
+	char arg3[BUFFER_SIZE], arg4[BUFFER_SIZE];
+	FILE *logfile = NULL;
+	int x, y, line, row;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg3, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg4, GET_ALL, SUB_VAR|SUB_FUN);
+
+	push_call("map_map(%p,%p)",ses,arg);
+
+	if (is_math(ses, arg1))
+	{
+		map_grid_y = get_number(ses, arg1);
+
+		if (map_grid_y <= 0)
+		{
+			map_grid_y = UMAX(0, get_scroll_rows(ses) + map_grid_y);
+		}
+	}
+	else
+	{
+		map_grid_y = get_scroll_rows(ses);
+	}
+
+	if (is_math(ses, arg2))
+	{
+		map_grid_x = get_number(ses, arg2);
+
+		if (map_grid_x <= 0)
+		{
+			map_grid_x = UMAX(0, gtd->screen->cols + map_grid_x);
+		}
+	}
+	else
+	{
+		map_grid_x = get_scroll_cols(ses);
+	}
+
+	if (*arg3)
+	{
+		switch (*arg3)
+		{
+			case 'a':
+			case 'A':
+
+				strcpy(arg3, "APPEND");
+
+				logfile = fopen(arg4, "a");
+
+				loginit(ses, logfile, LOG_FLAG_APPEND | HAS_BIT(ses->logmode, LOG_FLAG_HTML));
+
+				break;
+
+			case 'd':
+			case 'D':
+				strcpy(arg3, "DRAW");
+
+				if (*arg4 == 0)
+				{
+					show_error(ses, LIST_COMMAND, "#SYNTAX: #MAP MAP {%s} {%s} {%s} {square}", arg1, arg2, arg3);
+					pop_call();
+					return;
+				}
+				break;
+
+			case 'o':
+			case 'O':
+				strcpy(arg3, "OVERWRITE");
+
+				logfile = fopen(arg4, "w");
+
+				loginit(ses, logfile, LOG_FLAG_OVERWRITE | HAS_BIT(ses->logmode, LOG_FLAG_HTML));
+
+				break;
+
+			case 'l':
+			case 'L':
+				strcpy(arg3, "LIST");
+				break;
+
+			case 'v':
+			case 'V':
+				strcpy(arg3, "VARIABLE");
+				break;
+
+			default:
+				show_error(ses, LIST_COMMAND, "#SYNTAX: #MAP MAP {rows} {cols} {append|overwrite|list|variable} {name}");
+				pop_call();
+				return;
+		}
+
+		if (*arg4 == 0)
+		{
+			show_error(ses, LIST_COMMAND, "#SYNTAX: #MAP MAP {%s} {%s} {%s} {name}", arg1, arg2, arg3);
+			pop_call();
+			return;
+		}
+	}
+
+	if (HAS_BIT(ses->map->flags, MAP_FLAG_ASCIIGRAPHICS))
+	{
+		map_grid_y = 2 + map_grid_y / 3;
+		map_grid_x = 2 + map_grid_x / 6;
+	}
+	else if (HAS_BIT(ses->map->flags, MAP_FLAG_UNICODEGRAPHICS))
+	{
+		map_grid_y = 2 + (map_grid_y + 1) / 2;
+		map_grid_x = 2 + (map_grid_x + 3) / 5;
+	}
+	else if (HAS_BIT(ses->map->flags, MAP_FLAG_BLOCKGRAPHICS))
+	{
+		map_grid_y = 2 + map_grid_y / 2;
+		map_grid_x = 2 + map_grid_x / 5;
+	}
+	else if (HAS_BIT(ses->map->flags, MAP_FLAG_MUDFONT))
+	{
+		map_grid_y = 2 + map_grid_y / 1;
+		map_grid_x = 2 + map_grid_x / 2;
+	}
+	else
+	{
+		map_grid_y = 2 + map_grid_y;
+		map_grid_x = 2 + map_grid_x;
+	}
+
+	if (map_grid_x > ses->map->max_grid_x || map_grid_y > ses->map->max_grid_y)
+	{
+		if (map_grid_x > ses->map->max_grid_x)
+		{
+			ses->map->max_grid_x = map_grid_x + 1;
+		}
+		if (map_grid_y > ses->map->max_grid_y)
+		{
+			ses->map->max_grid_y = map_grid_y + 1;
+		}
+
+		ses->map->grid_rooms = (struct room_data **) realloc(ses->map->grid_rooms, ses->map->max_grid_x * ses->map->max_grid_y * sizeof(struct room_data *));
+	}
+
+	displaygrid_build(ses, ses->map->in_room, map_grid_x, map_grid_y, 0);
+
+	*arg1 = row = 0;
+
+	if (HAS_BIT(ses->map->flags, MAP_FLAG_ASCIIGRAPHICS))
+	{
+		for (y = map_grid_y - 2 ; y >= 1 ; y--)
+		{
+			for (line = 1 ; line <= 3 ; line++)
+			{
+				str_cpy(&gtd->buf, ses->map->color[MAP_COLOR_BACK]);
+
+				for (x = 1 ; x < map_grid_x - 1 ; x++)
+				{
+					str_cat(&gtd->buf, draw_room(ses, ses->map->grid_rooms[x + map_grid_x * y], line, x, y));
+				}
+
+				str_clone(&gtd->out, gtd->buf);
+
+				substitute(ses, gtd->buf, gtd->out, SUB_COL|SUB_CMP|SUB_LIT);
+
+				if (logfile)
+				{
+					logit(ses, gtd->out, logfile, LOG_FLAG_LINEFEED);
+				}
+				else if (*arg3 == 'L')
+				{
+					cat_sprintf(arg1, "{%02d}{%s}", ++row, gtd->out);
+				}
+				else if (*arg3 == 'V' || *arg3 == 'D')
+				{
+					cat_sprintf(arg1, "%s\n", gtd->out);
+				}
+				else
+				{
+					tintin_puts2(ses, gtd->out);
+				}
+			}
+		}
+	}
+	else if (HAS_BIT(ses->map->flags, MAP_FLAG_UNICODEGRAPHICS) || HAS_BIT(ses->map->flags, MAP_FLAG_BLOCKGRAPHICS))
+	{
+		for (y = map_grid_y - 2 ; y >= 1 ; y--)
+		{
+			for (line = 1 ; line <= 2 ; line++)
+			{
+				str_cpy(&gtd->buf, ses->map->color[MAP_COLOR_BACK]);
+
+				for (x = 1 ; x < map_grid_x - 1 ; x++)
+				{
+					str_cat(&gtd->buf, draw_room(ses, ses->map->grid_rooms[x + map_grid_x * y], line, x, y));
+				}
+
+				str_clone(&gtd->out, gtd->buf);
+
+				substitute(ses, gtd->buf, gtd->out, SUB_COL|SUB_CMP|SUB_LIT);
+
+				if (logfile)
+				{
+					fprintf(logfile, "%s\n", gtd->out);
+				}
+				else if (*arg3 == 'L')
+				{
+					cat_sprintf(arg1, "{%02d}{%s\e[0m}", ++row, gtd->out);
+				}
+				else if (*arg3 == 'V' || *arg3 == 'D')
+				{
+					cat_sprintf(arg1, "%s\e[0m\n", gtd->out);
+				}
+				else
+				{
+					tintin_puts2(ses, gtd->out);
+				}
+			}
+		}
+	}
+	else
+	{
+		for (y = map_grid_y - 2 ; y >= 1 ; y--)
+		{
+			str_cpy(&gtd->buf, ses->map->color[MAP_COLOR_BACK]);
+
+			for (x = 1 ; x < map_grid_x - 1 ; x++)
+			{
+				str_cat(&gtd->buf, draw_room(ses, ses->map->grid_rooms[x + map_grid_x * y], 0, x, y));
+			}
+
+			substitute(ses, gtd->buf, gtd->out, SUB_COL|SUB_CMP|SUB_LIT);
+
+			if (logfile)
+			{
+				fprintf(logfile, "%s\n", gtd->out);
+			}
+			else if (*arg3 == 'L')
+			{
+				cat_sprintf(arg1, "{%02d}{%s}", ++row, gtd->out);
+			}
+			else if (*arg3 == 'V' || *arg3 == 'D')
+			{
+				cat_sprintf(arg1, "%s\n", gtd->out);
+			}
+			else
+			{
+				tintin_puts2(ses, gtd->out);
+			}
+		}
+	}
+
+	if (logfile)
+	{
+		fclose(logfile);
+	}
+	else if (*arg3 == 'D')
+	{
+		draw_map(ses, 1, 2, 3, 4, 5, 6, 7, "", arg4, arg2, arg1);
+	}
+	else if (*arg3 == 'L')
+	{
+		set_nest_node_ses(ses, arg4, "%s", arg1);
+	}
+	else if (*arg3 == 'V')
+	{
+		set_nest_node_ses(ses, arg4, "%s", arg1);
+	}
+
+	pop_call();
+	return;
+}
+
+DO_MAP(map_move)
+{
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+
+//	tintin_printf2(ses, "debug: %s vs %s", arg, arg1);
+
+	arg = substitute_speedwalk(ses, arg1, arg2);
+
+//	tintin_printf2(ses, "debug: %s vs %s", arg, arg2);
+
+	ses->map->nofollow++;
+
+	while (*arg)
+	{
+		arg = get_arg_in_braces(ses, arg, arg1, GET_ALL);
+
+		follow_map(ses, arg1);
+
+		if (*arg == COMMAND_SEPARATOR)
+		{
+			arg++;
+		}
+	}
+
+	ses->map->nofollow--;
+}
+
+DO_MAP(map_name)
+{
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+
+	RESTRING(ses->map->room_list[ses->map->in_room]->name, arg1);
+}
+
+DO_MAP(map_offset)
+{
+	char arg3[BUFFER_SIZE], arg4[BUFFER_SIZE];
+
+	if (arg)
+	{
+		arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+		arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+		arg = sub_arg_in_braces(ses, arg, arg3, GET_ONE, SUB_VAR|SUB_FUN);
+		arg = sub_arg_in_braces(ses, arg, arg4, GET_ONE, SUB_VAR|SUB_FUN);
+
+		ses->map->sav_top_row = get_number(ses, arg1);
+		ses->map->sav_top_col = get_number(ses, arg2);
+		ses->map->sav_bot_row = get_number(ses, arg3);
+		ses->map->sav_bot_col = get_number(ses, arg4);
+	}
+
+	if (ses->map->sav_top_row == 0)
+	{
+		ses->map->top_row = 1;
+	}
+	else if (ses->map->sav_top_row < 0)
+	{
+		ses->map->top_row = 1 + gtd->screen->rows + ses->map->sav_top_row;
+	}
+	else
+	{
+		ses->map->top_row = ses->map->sav_top_row;
+	}
+
+	if (ses->map->sav_top_col == 0)
+	{
+		ses->map->top_col = 1;
+	}
+	else if (ses->map->sav_top_col < 0)
+	{
+		ses->map->top_col = 1 + gtd->screen->cols + ses->map->sav_top_col;
+	}
+	else
+	{
+		ses->map->top_col = ses->map->sav_top_col;
+	}
+
+	if (ses->map->sav_bot_row == 0)
+	{
+		ses->map->bot_row = ses->split->top_row - 1;
+	}
+	else if (ses->map->sav_bot_row < 0)
+	{
+		ses->map->bot_row = 1 + gtd->screen->rows + ses->map->sav_bot_row;
+	}
+	else
+	{
+		ses->map->bot_row = ses->map->sav_bot_row;
+	}
+
+	if (ses->map->sav_bot_col == 0)
+	{
+		ses->map->bot_col = gtd->screen->cols;
+	}
+	else if (ses->map->sav_bot_col < 0)
+	{
+		ses->map->bot_col = 1 + gtd->screen->cols + ses->map->sav_bot_col;
+	}
+	else
+	{
+		ses->map->bot_col = ses->map->sav_bot_col;
+	}
+
+	ses->map->rows = ses->map->bot_row - ses->map->top_row;
+	ses->map->cols = ses->map->bot_col - ses->map->top_col;
+
+	if (arg)
+	{
+		show_message(ses, LIST_COMMAND, "#MAP OFFSET: SQUARE {%d, %d, %d, %d} ROWS {%d} COLS {%d}", ses->map->top_row, ses->map->top_col, ses->map->bot_row, ses->map->bot_col, ses->map->rows, ses->map->cols);
+	}
+}
+
+
+DO_MAP(map_read)
+{
+	FILE *myfile;
+	struct room_data *room;
+	struct exit_data *exit;
+	char buffer[BUFFER_SIZE], file[BUFFER_SIZE], *cptr;
+	int line = 1, vnum = 0;
+
+	arg = sub_arg_in_braces(ses, arg, file, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if ((myfile = fopen(file, "r")) == NULL)
+	{
+		show_error(ses, LIST_COMMAND, "#MAP: Map file {%s} not found.", file);
+
+		return;
+	}
+
+	gtd->level->quiet++;
+
+	if (fgets(buffer, BUFFER_SIZE - 1, myfile))
+	{
+		cptr = strchr(buffer, '\r'); /* For map files editor on Windows systems. */
+
+		if (cptr)
+		{
+			*cptr = 0;
+		}
+
+		cptr = strchr(buffer, '\n');
+
+		if (cptr)
+		{
+			*cptr = 0;
+		}
+
+		if (buffer[0] == 'C' && buffer[1] == ' ')
+		{
+			if (ses->map == NULL || !HAS_BIT(ses->map->flags, MAP_FLAG_SYNC))
+			{
+				create_map(ses, buffer + 2);
+			}
+		}
+		else
+		{
+			gtd->level->quiet--;
+
+			show_error(ses, LIST_COMMAND, "#MAP READ {%s}: INVALID START OF FILE. ABORTING READ..", file);
+
+			fclose(myfile);
+
+			return;
+		}
+	}
+	else
+	{
+		gtd->level->quiet--;
+
+		show_error(ses, LIST_COMMAND, "#MAP: INVALID READ ON LINE %d. ABORTING READ..", line);
+
+		fclose(myfile);
+		
+		return;
+	}
+
+	while (fgets(buffer, BUFFER_SIZE - 1, myfile))
+	{
+		line++;
+
+		cptr = strchr(buffer, '\r'); /* For map files editor on Windows systems. */
+
+		if (cptr)
+		{
+			*cptr = 0;
+		}
+
+		cptr = strchr(buffer, '\n');
+
+		if (cptr)
+		{
+			*cptr = 0;
+		}
+
+		switch (buffer[0])
+		{
+			case 'C':
+				switch (buffer[1])
+				{
+					case ' ':
+						gtd->level->quiet--;
+
+						show_error(ses, LIST_COMMAND, "#MAP: INVALID COMMAND {%d} {%s} ON LINE %d. ABORTING READ..", buffer[0], buffer, line);
+
+						fclose(myfile);
+
+						delete_map(ses);
+
+						return;
+
+					case 'A':
+					case 'B':
+					case 'E':
+					case 'H':
+					case 'I':
+					case 'P':
+					case 'R':
+					case 'S':
+					case 'U':
+						if (!HAS_BIT(ses->map->flags, MAP_FLAG_SYNC))
+						{
+							map_color(ses, buffer + 1, arg1, arg2);
+						}
+						break;
+
+					default:
+						show_error(ses, LIST_COMMAND, "#MAP READ: INVALID COMMAND {%d} {%s} ON LINE %d. ABORTING READ..", buffer[0], buffer, line);
+						break;
+				}
+				break;
+
+			case 'E':
+				create_exit(ses, vnum, "%s", buffer + 2);
+				break;
+
+			case 'F':
+				if (!HAS_BIT(ses->map->flags, MAP_FLAG_SYNC))
+				{
+					ses->map->flags = atoi(buffer + 2);
+				}
+				break;
+
+			case 'G':
+				if (ses->map->global_vnum == 0)
+				{
+					ses->map->global_vnum = ses->map->global_exit->vnum = atoi(buffer + 2);
+				}
+				break;
+
+			case 'I':
+				if (ses->map->last_room == 0)
+				{
+					ses->map->last_room = atoi(buffer + 2);
+				}
+				break;
+
+			case 'L':
+				switch (buffer[1])
+				{
+					case ' ':
+						if (!HAS_BIT(ses->map->flags, MAP_FLAG_SYNC))
+						{
+							map_legend(ses, buffer + 2, arg1, arg2);
+						}
+						break;
+
+					case 'M':
+						map_landmark(ses, buffer + 3, arg1, arg2);
+						break;
+				}
+				break;
+
+			case 'R':
+				room = create_room(ses, "%s", buffer + 2);
+				vnum = room->vnum;
+				break;
+
+			case 'T':
+				if (!HAS_BIT(ses->map->flags, MAP_FLAG_SYNC))
+				{
+					map_terrain(ses, buffer + 2, arg1, arg2);
+				}
+				break;
+
+			case 'V':
+				if (ses->map->version == 0)
+				{
+					ses->map->version = atoi(buffer + 2);
+				}
+				break;
+
+			case '#':
+				buffer[0] = gtd->tintin_char;
+				ses = script_driver(ses, LIST_COMMAND, buffer);
+				break;
+
+			case  0:
+			case 13:
+				break;
+
+			default:
+				gtd->level->quiet--;
+
+				show_error(ses, LIST_COMMAND, "#MAP: INVALID COMMAND {%d} {%s} ON LINE %d.", buffer[0], buffer, line);
+
+				gtd->level->quiet++;
+		}
+	}
+
+	gtd->level->quiet--;
+
+	fclose(myfile);
+
+	for (vnum = 0 ; vnum < ses->map->size ; vnum++)
+	{
+		if (ses->map->room_list[vnum] == NULL)
+		{
+			continue;
+		}
+
+		for (exit = ses->map->room_list[vnum]->f_exit ; exit ; exit = exit->next)
+		{
+			if (exit->vnum < 0 || exit->vnum >= ses->map->size || ses->map->room_list[exit->vnum] == NULL)
+			{
+				show_error(ses, LIST_COMMAND, "#MAP READ: Room %d - invalid exit '%s' to room %d.", vnum, exit->name, exit->vnum);
+
+				delete_exit(ses, vnum, exit);
+
+				if (ses->map->room_list[vnum]->f_exit)
+				{
+					exit = ses->map->room_list[vnum]->f_exit;
+				}
+				else
+				{
+					break;
+				}
+			}
+		}
+	}
+
+	show_message(ses, LIST_COMMAND, "#MAP READ: Map file {%s} loaded.", file);
+
+
+}
+
+DO_MAP(map_resize)
+{
+	int size, vnum, room;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+
+	size = atoi(arg1);
+
+	if (size <= ses->map->size)
+	{
+		for (room = vnum = 1 ; vnum < ses->map->size ; vnum++)
+		{
+			if (ses->map->room_list[vnum])
+			{
+				room = vnum;
+			}
+		}
+
+		if (room >= size)
+		{
+			show_error(ses, LIST_COMMAND, "#MAP RESIZE: YOU MUST DELETE ALL ROOMS WITH VNUMS ABOVE (%d) FIRST.", size);
+			return;
+		}
+	}
+
+	ses->map->room_list = (struct room_data **) realloc(ses->map->room_list, size * sizeof(struct room_data *));
+
+	if (ses->map->size < size)
+	{
+		while (ses->map->size < size)
+		{
+			ses->map->room_list[ses->map->size++] = NULL;
+		}
+	}
+	else
+	{
+		ses->map->size = size;
+	}
+
+	show_message(ses, LIST_COMMAND, "#MAP RESIZE: MAP RESIZED TO %d ROOMS.", ses->map->size);
+}
+
+DO_MAP(map_return)
+{
+	if (ses->map == NULL || ses->map->room_list[ses->map->last_room] == NULL)
+	{
+		show_error(ses, LIST_COMMAND, "#MAP RETURN: NO KNOWN LAST ROOM.");
+
+		return;
+	}
+
+	if (ses->map->in_room)
+	{
+		show_error(ses, LIST_COMMAND, "#MAP RETURN: ALREADY IN THE MAP.");
+	}
+	else
+	{
+		goto_room(ses, ses->map->last_room);
+
+		show_message(ses, LIST_COMMAND, "#MAP RETURN: RETURNED TO ROOM %d {%s}.", ses->map->in_room, ses->map->room_list[ses->map->in_room]->name);
+	}
+}
+
+DO_MAP(map_roomflag)
+{
+	char buf[BUFFER_SIZE], *str, arg3[BUFFER_SIZE];
+	int flag = 0;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg3, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		tintin_printf2(ses, "#MAP: Avoid flag is set to %s.", HAS_BIT(ses->map->room_list[ses->map->in_room]->flags, ROOM_FLAG_AVOID) ? "ON" : "OFF");
+		tintin_printf2(ses, "#MAP: Block flag is set to %s.", HAS_BIT(ses->map->room_list[ses->map->in_room]->flags, ROOM_FLAG_BLOCK) ? "ON" : "OFF");
+		tintin_printf2(ses, "#MAP: Hide flag is set to %s.", HAS_BIT(ses->map->room_list[ses->map->in_room]->flags, ROOM_FLAG_HIDE) ? "ON" : "OFF");
+		tintin_printf2(ses, "#MAP: Invis flag is set to %s.", HAS_BIT(ses->map->room_list[ses->map->in_room]->flags, ROOM_FLAG_INVIS) ? "ON" : "OFF");
+		tintin_printf2(ses, "#MAP: Leave flag is set to %s.", HAS_BIT(ses->map->room_list[ses->map->in_room]->flags, ROOM_FLAG_LEAVE) ? "ON" : "OFF");
+		tintin_printf2(ses, "#MAP: Void flag is set to %s.", HAS_BIT(ses->map->room_list[ses->map->in_room]->flags, ROOM_FLAG_VOID) ? "ON" : "OFF");
+		tintin_printf2(ses, "#MAP: Static flag is set to %s.", HAS_BIT(ses->map->room_list[ses->map->in_room]->flags, ROOM_FLAG_STATIC) ? "ON" : "OFF");
+		tintin_printf2(ses, "#MAP: Curved flag is set to %s.", HAS_BIT(ses->map->room_list[ses->map->in_room]->flags, ROOM_FLAG_CURVED) ? "ON" : "OFF");
+		tintin_printf2(ses, "#MAP: NoGlobal flag is set to %s.", HAS_BIT(ses->map->room_list[ses->map->in_room]->flags, ROOM_FLAG_NOGLOBAL) ? "ON" : "OFF");
+		return;
+	}
+
+	str = arg1;
+
+	while (*str)
+	{
+		str = get_arg_in_braces(ses, str, buf, GET_ONE);
+
+		if (is_abbrev(buf, "avoid"))
+		{
+			SET_BIT(flag, ROOM_FLAG_AVOID);
+		}
+		else if (is_abbrev(buf, "block"))
+		{
+			SET_BIT(flag, ROOM_FLAG_BLOCK);
+		}
+		else if (is_abbrev(buf, "curved"))
+		{
+			SET_BIT(flag, ROOM_FLAG_CURVED);
+		}
+		else if (is_abbrev(buf, "hide"))
+		{
+			SET_BIT(flag, ROOM_FLAG_HIDE);
+		}
+		else if (is_abbrev(buf, "invisible"))
+		{
+			SET_BIT(flag, ROOM_FLAG_INVIS);
+		}
+		else if (is_abbrev(buf, "leave"))
+		{
+			SET_BIT(flag, ROOM_FLAG_LEAVE);
+		}
+		else if (is_abbrev(buf, "noglobal"))
+		{
+			SET_BIT(flag, ROOM_FLAG_NOGLOBAL);
+		}
+		else if (is_abbrev(buf, "static"))
+		{
+			SET_BIT(flag, ROOM_FLAG_STATIC);
+		}
+		else if (is_abbrev(buf, "void"))
+		{
+			SET_BIT(flag, ROOM_FLAG_VOID);
+		}
+		else
+		{
+			show_error(ses, LIST_COMMAND, "#MAP: Invalid room flag {%s}.", buf);
+
+			return;
+		}
+
+		if (*str == COMMAND_SEPARATOR)
+		{
+			str++;
+		}
+	}
+
+	if (*arg2 == 0)
+	{
+		TOG_BIT(ses->map->room_list[ses->map->in_room]->flags, flag);	
+	}
+	else if (is_abbrev(arg2, "ON"))
+	{
+		SET_BIT(ses->map->room_list[ses->map->in_room]->flags, flag);
+	}
+	else if (is_abbrev(arg2, "OFF"))
+	{
+		DEL_BIT(ses->map->room_list[ses->map->in_room]->flags, flag);
+	}
+	else if (is_abbrev(arg2, "GET"))
+	{
+		if (*arg3 == 0)
+		{
+			show_error(ses, LIST_COMMAND, "#SYNTAX #MAP ROOMFLAG {%s} {GET} {<VARIABLE>}.", buf);
+		}
+		else
+		{
+			set_nest_node_ses(ses, arg3, "%d", HAS_BIT(ses->map->room_list[ses->map->in_room]->flags, flag));
+		}
+		return;
+	}
+	else
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX #MAP ROOMFLAG {%s} {[GET|ON|OFF]}.", buf);
+	}
+
+
+	if (HAS_BIT(flag, ROOM_FLAG_AVOID))
+	{
+		show_message(ses, LIST_COMMAND, "#MAP: Avoid flag set to %s.", HAS_BIT(ses->map->room_list[ses->map->in_room]->flags, ROOM_FLAG_AVOID) ? "ON" : "OFF");
+	}
+	if (HAS_BIT(flag, ROOM_FLAG_BLOCK))
+	{
+		show_message(ses, LIST_COMMAND, "#MAP: Block flag set to %s.", HAS_BIT(ses->map->room_list[ses->map->in_room]->flags, ROOM_FLAG_AVOID) ? "ON" : "OFF");
+	}
+	if (HAS_BIT(flag, ROOM_FLAG_CURVED))
+	{
+		show_message(ses, LIST_COMMAND, "#MAP: Curved flag set to %s.", HAS_BIT(ses->map->room_list[ses->map->in_room]->flags, ROOM_FLAG_CURVED) ? "ON" : "OFF");
+	}
+	if (HAS_BIT(flag, ROOM_FLAG_HIDE))
+	{
+		show_message(ses, LIST_COMMAND, "#MAP: Hide flag set to %s.", HAS_BIT(ses->map->room_list[ses->map->in_room]->flags, ROOM_FLAG_HIDE) ? "ON" : "OFF");
+	}
+	if (HAS_BIT(flag, ROOM_FLAG_INVIS))
+	{
+		show_message(ses, LIST_COMMAND, "#MAP: Invis flag set to %s.", HAS_BIT(ses->map->room_list[ses->map->in_room]->flags, ROOM_FLAG_INVIS) ? "ON" : "OFF");
+	}
+	if (HAS_BIT(flag, ROOM_FLAG_LEAVE))
+	{
+		show_message(ses, LIST_COMMAND, "#MAP: Leave flag set to %s.", HAS_BIT(ses->map->room_list[ses->map->in_room]->flags, ROOM_FLAG_LEAVE) ? "ON" : "OFF");
+	}
+	if (HAS_BIT(flag, ROOM_FLAG_NOGLOBAL))
+	{
+		show_message(ses, LIST_COMMAND, "#MAP: NoGlobal flag set to %s.", HAS_BIT(ses->map->room_list[ses->map->in_room]->flags, ROOM_FLAG_NOGLOBAL) ? "ON" : "OFF");
+	}
+	if (HAS_BIT(flag, ROOM_FLAG_VOID))
+	{
+		show_message(ses, LIST_COMMAND, "#MAP: Void flag set to %s.", HAS_BIT(ses->map->room_list[ses->map->in_room]->flags, ROOM_FLAG_VOID) ? "ON" : "OFF");
+	}
+	if (HAS_BIT(flag, ROOM_FLAG_STATIC))
+	{
+		show_message(ses, LIST_COMMAND, "#MAP: Static flag set to %s.", HAS_BIT(ses->map->room_list[ses->map->in_room]->flags, ROOM_FLAG_STATIC) ? "ON" : "OFF");
+	}
+
+}
+
+DO_MAP(map_set)
+{
+	struct room_data *room = ses->map->room_list[ses->map->in_room];
+	char arg3[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ALL, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg3, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg3)
+	{
+		if (atoi(arg3) > 0 && atoi(arg3) < ses->map->size)
+		{
+			room = ses->map->room_list[atoi(arg3)];
+		}
+		else
+		{
+			room = NULL;
+		}
+	}
+
+	if (room == NULL)
+	{
+		show_message(ses, LIST_COMMAND, "#MAP SET: invalid room vnum: %s", arg3);
+	}
+	else if (*arg1 == 0)
+	{
+		tintin_printf2(ses, "   roomarea: %s", room->area);
+		tintin_printf2(ses, "  roomcolor: %s", room->color);
+		tintin_printf2(ses, "   roomdata: %s", room->data);
+		tintin_printf2(ses, "   roomdesc: %s", room->desc);
+		tintin_printf2(ses, "  roomflags: %d", room->flags);
+		tintin_printf2(ses, "     roomid: %s", room->id);
+		tintin_printf2(ses, "   roomname: %s", room->name);
+		tintin_printf2(ses, "   roomnote: %s", room->note);
+		tintin_printf2(ses, " roomsymbol: %s", room->symbol);
+		tintin_printf2(ses, "roomterrain: %s", room->terrain);
+		tintin_printf2(ses, " roomweight: %.3f", room->weight);
+	}
+	else
+	{
+		if (is_abbrev(arg1, "roomarea"))
+		{
+			RESTRING(room->area, arg2);
+			show_message(ses, LIST_COMMAND, "#MAP SET: roomarea set to: %s", room->area);
+		}
+		else if (is_abbrev(arg1, "roomcolor"))
+		{
+			RESTRING(room->color, arg2);
+			show_message(ses, LIST_COMMAND, "#MAP SET: roomcolor set to: %s", arg2);
+		}
+		else if (is_abbrev(arg1, "roomdata"))
+		{
+			RESTRING(room->data, arg2);
+			show_message(ses, LIST_COMMAND, "#MAP SET: roomdata set to: %s", arg2);
+		}
+		else if (is_abbrev(arg1, "roomdesc"))
+		{
+			RESTRING(room->desc, arg2);
+			show_message(ses, LIST_COMMAND, "#MAP SET: roomdesc set to: %s", arg2);
+		}
+		else if (is_abbrev(arg1, "roomflags"))
+		{
+			room->flags = (int) get_number(ses, arg2);
+
+			show_message(ses, LIST_COMMAND, "#MAP SET: roomflags set to: %d", room->flags);
+		}
+		else if (is_abbrev(arg1, "roomid"))
+		{
+			RESTRING(room->id, arg2);
+
+			show_message(ses, LIST_COMMAND, "#MAP SET: roomid set to: %s", room->id);
+		}
+		else if (is_abbrev(arg1, "roomname"))
+		{
+			RESTRING(room->name, arg2);
+
+			show_message(ses, LIST_COMMAND, "#MAP SET: roomname set to: %s", room->name);
+		}
+		else if (is_abbrev(arg1, "roomnote"))
+		{
+			RESTRING(room->note, arg2);
+			show_message(ses, LIST_COMMAND, "#MAP SET: roomnote set to: %s", arg2);
+		}
+		else if (is_abbrev(arg1, "roomsymbol"))
+		{
+			RESTRING(room->symbol, arg2);
+
+			show_message(ses, LIST_COMMAND, "#MAP SET: roomsymbol set to: %s", room->symbol);
+		}
+		else if (is_abbrev(arg1, "roomterrain"))
+		{
+			RESTRING(room->terrain, arg2);
+			room->terrain_index = bsearch_alpha_list(ses->list[LIST_TERRAIN], room->terrain, 0);
+			show_message(ses, LIST_COMMAND, "#MAP SET: roomterrain set to: %s (%d)", arg2, room->terrain_index);
+		}
+		else if (is_abbrev(arg1, "roomweight"))
+		{
+			if (get_number(ses, arg2) < 0.001)
+			{
+				show_message(ses, LIST_COMMAND, "#MAP SET: roomweight should be at least 0.001");
+			}
+			else
+			{
+				room->weight = (float) get_number(ses, arg2);
+
+				show_message(ses, LIST_COMMAND, "#MAP SET: roomweight set to: %.3f", room->weight);
+			}
+		}
+		else
+		{
+			show_message(ses, LIST_COMMAND, "#MAP SET: unknown option: %s", arg1);
+		}
+	}
+}
+
+DO_MAP(map_sync)
+{
+	if (ses->map)
+	{
+		SET_BIT(ses->map->flags, MAP_FLAG_SYNC);
+	}
+
+	map_read(ses, arg, arg1, arg2);
+
+	if (ses->map)
+	{
+		DEL_BIT(ses->map->flags, MAP_FLAG_SYNC);
+	}
+}
+
+DO_MAP(map_terrain)
+{
+	struct listroot *root = ses->list[LIST_TERRAIN];
+	struct listnode *node;
+	struct room_data *room;
+	char arg3[BUFFER_SIZE], buf1[BUFFER_SIZE];
+	int i, found, flags, density, spread;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg3, GET_ALL, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, buf1, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg2 == 0 || (*arg1 == 0 && *arg2 == 0))
+	{
+		i = bsearch_alpha_list(root, arg1, 0);
+
+		if (i > 0)
+		{
+			tintin_printf2(ses, "name: %-16s  index %4d symbol: %-12s  flags: %s %s", root->list[i]->arg1, i, root->list[i]->arg2, root->list[i]->arg3, root->list[i]->arg4);
+		}
+		else
+		{
+			for (found = i = 0 ; i < root->used ; i++)
+			{
+				if (*arg1 == 0 || match(ses, root->list[i]->arg1, arg1, SUB_NONE))
+				{
+					room = root->list[i]->room;
+
+					if (!HAS_BIT(room->flags, ROOM_FLAG_TERRAIN))
+					{
+						tintin_printf2(ses, "error: no terrain flag set.");
+					}
+					tintin_printf2(ses, "name: %-16s  index %4d  symbol: %-16s  %s %s", room->name, room->terrain_index, room->symbol, root->list[i]->arg3, root->list[i]->arg4);
+
+					found = TRUE;
+				}
+			}
+
+			if (found == FALSE)
+			{
+				show_message(ses, LIST_COMMAND, "#MAP TERRAIN: NO MATCHES FOUND FOR {%s}.", arg1);
+			}
+		}
+	}
+	else
+	{
+		SET_BIT(ses->map->flags, MAP_FLAG_UPDATETERRAIN);
+
+		density = TERRAIN_FLAG_AMPLE;
+		spread = TERRAIN_FLAG_STANDARD;
+		flags = 0;
+
+		arg = arg3;
+
+		while (*arg)
+		{
+			arg = get_arg_in_braces(ses, arg, buf1, GET_ONE);
+
+			if (is_abbrev(buf1, "DENSE"))
+			{
+				density = TERRAIN_FLAG_DENSE;
+			}
+			else if (is_abbrev(buf1, "AMPLE"))
+			{
+				density = TERRAIN_FLAG_AMPLE;
+			}
+			else if (is_abbrev(buf1, "SPARSE"))
+			{
+				density = TERRAIN_FLAG_SPARSE;
+			}
+			else if (is_abbrev(buf1, "SCANT"))
+			{
+				density = TERRAIN_FLAG_SCANT;
+			}
+			else if (is_abbrev(buf1, "NARROW"))
+			{
+				spread = TERRAIN_FLAG_NARROW;
+			}
+			else if (is_abbrev(buf1, "WIDE"))
+			{
+				spread = TERRAIN_FLAG_WIDE;
+			}
+			else if (is_abbrev(buf1, "VAST"))
+			{
+				spread = TERRAIN_FLAG_WIDE|TERRAIN_FLAG_VAST;
+			}
+			else if (is_abbrev(buf1, "FADEIN"))
+			{
+				SET_BIT(flags, TERRAIN_FLAG_FADEIN);
+			}
+			else if (is_abbrev(buf1, "FADEOUT"))
+			{
+				SET_BIT(flags, TERRAIN_FLAG_FADEOUT);
+			}
+			else if (is_abbrev(buf1, "DOUBLE"))
+			{
+				SET_BIT(flags, TERRAIN_FLAG_DOUBLE);
+			}
+			else
+			{
+				show_error(ses, LIST_COMMAND, "#SYNTAX: #MAP TERRAIN {%s} [DENSE|DOUBLE|SPARSE|SCANT|NARROW|WIDE|VAST|FADEIN|FADEOUT]", arg1);
+			}
+
+			if (*arg == COMMAND_SEPARATOR)
+			{
+				arg++;
+			}
+		}
+
+		strcpy(buf1, "");
+
+		SET_BIT(flags, density);
+		SET_BIT(flags, spread);
+
+		if (flags)
+		{
+			if (HAS_BIT(flags, TERRAIN_FLAG_DENSE))
+			{
+				strcat(buf1, "DENSE ");
+			}
+			else if (HAS_BIT(flags, TERRAIN_FLAG_SPARSE))
+			{
+				strcat(buf1, "SPARSE ");
+			}
+			else if (HAS_BIT(flags, TERRAIN_FLAG_SCANT))
+			{
+				strcat(buf1, "SCANT ");
+			}
+
+			if (HAS_BIT(flags, TERRAIN_FLAG_NARROW))
+			{
+				strcat(buf1, "NARROW ");
+			}
+			else if (HAS_BIT(flags, TERRAIN_FLAG_VAST))
+			{
+				strcat(buf1, "VAST ");
+			}
+			else if (HAS_BIT(flags, TERRAIN_FLAG_WIDE))
+			{
+				strcat(buf1, "WIDE ");
+			}
+
+			if (HAS_BIT(flags, TERRAIN_FLAG_FADEIN))
+			{
+				strcat(buf1, "FADEIN ");
+			}
+			else if (HAS_BIT(flags, TERRAIN_FLAG_FADEOUT))
+			{
+				strcat(buf1, "FADEOUT ");
+			}
+
+			if (HAS_BIT(flags, TERRAIN_FLAG_DOUBLE))
+			{
+				strcat(buf1, "DOUBLE ");
+			}
+
+			buf1[strlen(buf1) - 1] = 0;
+		}
+
+		node = update_node_list(root, arg1, arg2, buf1, "");
+
+		if (node->room)
+		{
+			if (node->room->symbol)
+			{
+				RESTRING(node->room->terrain, arg1);
+				RESTRING(node->room->symbol, arg2);
+			}
+		}
+		else
+		{
+			node->room = create_room(ses, "{0} {%d} {} {%s} {%s} {} {} {} {} {} {1.0} {}", ROOM_FLAG_TERRAIN, node->arg1, node->arg2);
+		}
+
+		node->room->terrain_flags = flags;
+
+		for (i = 0 ; i < root->used ; i++)
+		{
+			root->list[i]->room->terrain_index = i;
+		}
+
+		show_message(ses, LIST_COMMAND, "#OK. TERRAIN {%s} HAS BEEN SET TO {%s} {%s}.", arg1, arg2, buf1);
+	}
+}
+
+DO_MAP(map_unterrain)
+{
+	if (delete_node_with_wild(ses, LIST_TERRAIN, arg))
+	{
+		SET_BIT(ses->map->flags, MAP_FLAG_UPDATETERRAIN);
+	}
+}
+
+DO_MAP(map_travel)
+{
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ALL, SUB_VAR|SUB_FUN);
+
+	explore_path(ses, TRUE, arg1, arg2);
+}
+
+
+DO_MAP(map_uninsert)
+{
+	int room1, room2, room3;
+	struct exit_data *exit1, *exit2, *exit3;
+	struct listnode *node;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+
+	room1 = ses->map->in_room;
+	exit1 = find_exit(ses, room1, arg1);
+
+	node = search_node_list(ses->list[LIST_PATHDIR], arg1);
+
+	if (exit1 == NULL)
+	{
+		show_error(ses, LIST_COMMAND, "#MAP UNINSERT: There is no room in that direction.");
+
+		return;
+	}
+
+	if (node == NULL)
+	{
+		show_error(ses, LIST_COMMAND, "#MAP UNINSERT: Given direction must be a pathdir.");
+		return;
+	}
+
+	room2 = exit1->vnum;
+	exit2 = find_exit(ses, room2, node->arg1);
+
+	if (exit2 == NULL)
+	{
+		show_error(ses, LIST_COMMAND, "#MAP UNINSERT: Unable to find backlink room.");
+		return;
+	}
+
+	room3 = exit2->vnum;
+	exit3 = find_exit(ses, room3, node->arg2);
+
+	if (exit3 == NULL)
+	{
+		show_error(ses, LIST_COMMAND, "#MAP UNINSERT: Unable to find backlink exit.");
+
+		return;
+	}
+
+	exit1->vnum = room3;
+	exit3->vnum = room1;
+
+	delete_room(ses, room2, TRUE);
+
+	show_message(ses, LIST_COMMAND, "#MAP UNINSERT: Uninserted room {%d}.", room2);
+}
+
+// 1) timestamp 2) type 3) data
+
+DO_MAP(map_undo)
+{
+	struct link_data *link;
+	struct room_data *room;
+	int undo_flag;
+	struct exit_data *exit1, *exit2, *exit3;
+
+	link = ses->map->undo_tail;
+
+	if (link == NULL)
+	{
+		show_error(ses, LIST_COMMAND, "#MAP UNDO: No known last move.");
+
+		return;
+	}
+
+	room = ses->map->room_list[atoi(link->str1)];
+
+	if (room == NULL)
+	{
+		show_error(ses, LIST_COMMAND, "#MAP UNDO: Room %s does not exist.", link->str2);
+		return;
+	}
+
+	if (ses->map->room_list[atoi(link->str2)] == NULL)
+	{
+		show_error(ses, LIST_COMMAND, "#MAP UNDO: Invalid last move.");
+		return;
+	}
+
+	undo_flag = atoi(link->str3);
+
+	if (HAS_BIT(undo_flag, MAP_UNDO_MOVE))
+	{
+ 		if (ses->map->in_room != room->vnum)
+		{
+			show_error(ses, LIST_COMMAND, "#MAP UNDO: Invalid last move.");
+			return;
+		}
+		show_message(ses, LIST_COMMAND, "#MAP UNDO: Moving to room %s.", link->str2);
+
+		goto_room(ses, atoi(link->str2));
+	}
+
+	if (HAS_BIT(undo_flag, MAP_UNDO_CREATE))
+	{
+		show_message(ses, LIST_COMMAND, "#MAP UNDO: Deleting room %d.", room->vnum);
+		delete_room(ses, room->vnum, TRUE);
+	}
+	else if (HAS_BIT(undo_flag, MAP_UNDO_LINK))
+	{
+		exit1 = find_exit(ses, room->vnum, link->str2);
+
+		if (exit1)
+		{
+			show_message(ses, LIST_COMMAND, "#MAP UNDO: Deleting exit leading %s.", exit1->name);
+			delete_exit(ses, room->vnum, exit1);
+		}
+
+		exit2 = find_exit(ses, atoi(link->str2), link->str1);
+
+		if (exit2)
+		{
+			show_message(ses, LIST_COMMAND, "#MAP UNDO: Deleting exit leading %s.", exit2->name);
+			delete_exit(ses, atoi(link->str2), exit2);
+		}
+	}
+	else if (HAS_BIT(undo_flag, MAP_UNDO_INSERT))
+	{
+		exit1 = find_exit(ses, atoi(link->str2), link->str1);
+
+		if (exit1 == NULL)
+		{
+			show_error(ses, LIST_COMMAND, "#MAP UNDO: Can't find exit between %s and %s.", link->str2, link->str1);
+			return;
+		}
+
+		exit2 = find_exit(ses, room->vnum, exit1->name);
+
+		if (exit2 == NULL)
+		{
+			show_error(ses, LIST_COMMAND, "#MAP UNDO: No valid exit found in room %d.", room->vnum);
+			return;
+		}
+
+		exit3 = find_exit(ses, exit2->vnum, ntos(room->vnum));
+
+		if (exit3 == NULL)
+		{
+			show_error(ses, LIST_COMMAND, "#MAP UNDO: Can't find exit between %d and %d.", room->vnum, exit2->vnum);
+			return;
+		}
+
+		exit1->vnum = exit2->vnum;
+		exit3->vnum = atoi(link->str2);
+
+		delete_room(ses, room->vnum, TRUE);
+
+		show_message(ses, LIST_COMMAND, "#MAP UNDO: Uninserting room %s.", link->str1);
+	}
+	del_undo(ses, link);
+}
+
+DO_MAP(map_unlink)
+{
+	struct exit_data *exit1;
+	struct exit_data *exit2;
+	struct listnode *node;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ALL, SUB_VAR|SUB_FUN);
+
+	node = search_node_list(ses->list[LIST_PATHDIR], arg1);
+
+	exit1 = find_exit(ses, ses->map->in_room, arg1);
+
+	if (exit1 == NULL)
+	{
+		show_error(ses, LIST_COMMAND, "#MAP UNLINK: No exit with that name found");
+
+		return;
+	}
+
+	if (*arg2 == 'b' || *arg == 'B')
+	{
+		if (node)
+		{
+			exit2 = find_exit(ses, exit1->vnum, node->arg2);
+
+			if (exit2)
+			{
+				delete_exit(ses, exit1->vnum, exit2);
+			}
+		}
+	}
+
+	delete_exit(ses, ses->map->in_room, exit1);
+
+	show_message(ses, LIST_COMMAND, "#MAP UNLINK: Exit deleted.");
+}
+
+DO_MAP(map_update)
+{
+	if (ses->map == NULL)
+	{
+		show_message(ses, LIST_COMMAND, "#MAP UPDATE: NO MAP DATA.");
+	}
+	else if (ses->map->room_list[ses->map->in_room] == NULL)
+	{
+		show_message(ses, LIST_COMMAND, "#MAP UPDATE: NOT INSIDE MAP.");
+	}
+	else if (!HAS_BIT(ses->map->flags, MAP_FLAG_VTMAP))
+	{
+		show_message(ses, LIST_COMMAND, "#MAP UPDATE: VTMAP FLAG NOT SET.");
+	}
+	else if (ses != gtd->ses)
+	{
+		show_message(ses, LIST_COMMAND, "#MAP UPDATE: NOT THE ACTIVE SESSION.");
+	}
+	else
+	{
+		show_message(ses, LIST_COMMAND, "#MAP UPDATE: OK.");
+		
+		SET_BIT(ses->flags, SES_FLAG_UPDATEVTMAP);
+	}
+}
+
+DO_MAP(map_run)
+{
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ALL, SUB_VAR|SUB_FUN);
+
+	shortest_path(ses, TRUE, arg2, arg1);
+}
+
+DO_MAP(map_vnum)
+{
+	int vnum, vnum1, vnum2, old_room, new_room;
+	struct exit_data *exit;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+
+	vnum1 = atoi(arg1);
+
+	if (*arg2)
+	{
+		vnum2 = atoi(arg2);
+	}
+	else
+	{
+		vnum2 = vnum1;
+	}
+	
+	if (vnum1 <= 0 || vnum1 >= ses->map->size || vnum2 <= 0 || vnum2 >= ses->map->size)
+	{
+		show_error(ses, LIST_COMMAND, "#MAP VNUM {%s} {%s} - VNUMS MUST BE BETWEEN {1} and {%d}", arg1, arg2, ses->map->size - 1);
+		return;
+	}
+
+	for (vnum = vnum1 ; vnum <= vnum2 ; vnum++)
+	{
+		if (ses->map->room_list[vnum] == NULL)
+		{
+			break;
+		}
+	}
+
+	if (vnum > vnum2)
+	{
+		show_error(ses, LIST_COMMAND, "#MAP VNUM {%s} {%s} - NO FREE VNUM FOUND.", arg1, arg2);
+		return;
+	}
+
+	old_room = ses->map->in_room;
+	new_room = vnum;
+
+	ses->map->room_list[new_room] = ses->map->room_list[old_room];
+	ses->map->room_list[new_room]->vnum = new_room;
+	ses->map->room_list[old_room] = NULL;
+	ses->map->in_room = new_room;
+
+	if (ses->map->at_room == old_room)
+	{
+		ses->map->at_room = new_room;
+	}
+
+	for (vnum = 1 ; vnum < ses->map->size ; vnum++)
+	{
+		if (ses->map->room_list[vnum] == NULL)
+		{
+			continue;
+		}
+
+		for (exit = ses->map->room_list[vnum]->f_exit ; exit ; exit = exit->next)
+		{
+			if (exit->vnum == old_room)
+			{
+				exit->vnum = new_room;
+			}
+		}
+	}
+
+	tintin_printf(ses, "#MAP VNUM: MOVED ROOM %d TO %d.", old_room, new_room);
+}
+
+DO_MAP(map_write)
+{
+	struct listroot *root;
+	FILE *file;
+	struct exit_data *exit;
+	int index;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #MAP WRITE {<filename>} {FORCE}");
+
+		return;
+	}
+
+	if (!str_suffix(arg1, ".tin") && !is_abbrev(arg2, "FORCE"))
+	{
+		show_error(ses, LIST_COMMAND, "#MAP WRITE {%s}: USE {FORCE} TO OVERWRITE .tin FILES.");
+
+		return;
+	}
+
+	if ((file = fopen(arg1, "w")) == NULL)
+	{
+		show_error(ses, LIST_COMMAND, "#MAP WRITE {%s} - COULDN'T OPEN FILE TO WRITE.", arg1);
+
+		return;
+	}
+
+	fprintf(file, "C %d\n\n", ses->map->size);
+
+	fprintf(file, "V 2020\n\n");
+
+	for (index = 0 ; map_color_table[index].name ; index++)
+	{
+		fprintf(file, "C%s %s\n", map_color_table[index].name, ses->map->color[index]);
+	}
+	fprintf(file, "\n");
+
+	fprintf(file, "F %d\n\n", ses->map->flags);
+
+	fprintf(file, "G %d\n\n", ses->map->global_vnum);
+
+	fprintf(file, "I %d\n\n", ses->map->in_room ? ses->map->in_room : ses->map->last_room);
+
+	for (index = 0 ; map_legend_table[index].name ; index++)
+	{
+		fprintf(file, "L {%s} {%s} {%s}\n", map_legend_table[index].group, map_legend_table[index].name, ses->map->legend_raw[index]);
+	}
+	fprintf(file, "\n\n");
+
+	root = ses->list[LIST_LANDMARK];
+
+	for (index = 0 ; index < root->used ; index++)
+	{
+		fprintf(file, "LM {%s} {%d} {%s} {%s}\n", root->list[index]->arg1, root->list[index]->val32[0], root->list[index]->arg3, root->list[index]->arg4);
+	}
+	fprintf(file, "\n\n");	
+
+	root = ses->list[LIST_TERRAIN];
+
+	for (index = 0 ; index < root->used ; index++)
+	{
+		fprintf(file, "T {%s} {%s} {%s}\n", root->list[index]->arg1, root->list[index]->arg2, root->list[index]->arg3);
+	}
+	fprintf(file, "\n\n");	
+
+	for (index = 0 ; index < ses->map->size ; index++)
+	{
+		if (ses->map->room_list[index])
+		{
+			DEL_BIT(ses->map->room_list[index]->flags, ROOM_FLAG_PATH);
+
+			fprintf(file, "\nR {%5d} {%d} {%s} {%s} {%s} {%s} {%s} {%s} {%s} {%s} {%.3f} {%s}\n",
+				ses->map->room_list[index]->vnum,
+				ses->map->room_list[index]->flags,
+				ses->map->room_list[index]->color,
+				ses->map->room_list[index]->name,
+				ses->map->room_list[index]->symbol,
+				ses->map->room_list[index]->desc,
+				ses->map->room_list[index]->area,
+				ses->map->room_list[index]->note,
+				ses->map->room_list[index]->terrain,
+				ses->map->room_list[index]->data,
+				ses->map->room_list[index]->weight,
+				ses->map->room_list[index]->id);
+
+			for (exit = ses->map->room_list[index]->f_exit ; exit ; exit = exit->next)
+			{
+				fprintf(file, "E {%5d} {%s} {%s} {%d} {%d} {%s} {%.3f} {%s}\n",
+					exit->vnum,
+					exit->name,
+					exit->cmd,
+					exit->dir,
+					exit->flags,
+					exit->data,
+					exit->weight,
+					exit->color);
+			}
+		}
+	}
+
+	fclose(file);
+
+	show_message(ses, LIST_COMMAND, "#MAP: Map file written to {%s}.", arg1);
+}

+ 1393 - 0
math.c

@@ -0,0 +1,1393 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2004                       *
+******************************************************************************/
+
+#include "tintin.h"
+
+#define EXP_VARIABLE         0
+#define EXP_STRING           1
+#define EXP_BRACE            2
+#define EXP_OPERATOR         3
+
+#define EXP_PR_DICE          0
+#define EXP_PR_INTMUL        1
+#define EXP_PR_INTADD        2
+#define EXP_PR_BITSHIFT      3
+#define EXP_PR_LOGLTGT       4
+#define EXP_PR_LOGCOMP       5
+#define EXP_PR_BITAND        6
+#define EXP_PR_BITXOR        7
+#define EXP_PR_BITOR         8
+#define EXP_PR_LOGAND        9
+#define EXP_PR_LOGXOR       10
+#define EXP_PR_LOGOR        11
+#define EXP_PR_VAR          12
+#define EXP_PR_LVL          13
+
+struct link_data *math_head;
+struct link_data *math_tail;
+struct link_data *mathnode_s;
+struct link_data *mathnode_e;
+
+int precision;
+
+DO_COMMAND(do_math)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+	struct listnode *node;
+	long double result;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_NST, SUB_VAR|SUB_FUN);
+
+	arg = get_arg_in_braces(ses, arg, arg2, GET_ALL);
+
+	if (*arg1 == 0 || *arg2 == 0)
+	{
+		show_error(ses, LIST_VARIABLE, "#SYNTAX: #MATH {variable} {expression}.");
+	}
+	else
+	{
+		result = get_number(ses, arg2);
+
+		node = set_nest_node_ses(ses, arg1, "%.*Lf", precision, result);
+
+		show_message(ses, LIST_VARIABLE, "#MATH: VARIABLE {%s} HAS BEEN SET TO {%s}.", arg1, node->arg2);
+	}
+
+	return ses;
+}
+
+int is_math(struct session *ses, char *str)
+{
+	return mathexp_tokenize(ses, str, 0, 0);
+}
+
+long double get_number(struct session *ses, char *str)
+{
+	long double val;
+	char result[BUFFER_SIZE];
+
+	mathexp(ses, str, result, 0);
+
+	val = tintoi(result);
+
+	return val;
+}
+
+unsigned long long get_integer(struct session *ses, char *str)
+{
+	unsigned long long val;
+	char result[BUFFER_SIZE];
+
+	mathexp(ses, str, result, 0);
+
+	val = tintou(result);
+
+	return val;
+}
+
+int get_ellipsis(struct listroot *root, char *name, int *min, int *max)
+{
+	unsigned long long range = get_integer(root->ses, name);
+
+	push_call("get_ellipsis(%p,%p,%p,%p)",root,name,min,max);
+
+
+	*min = (int) (HAS_BIT(range, 0x00000000FFFFFFFFLL));
+	*max = (int) (HAS_BIT(range, 0xFFFFFFFF00000000ULL) >> 32ULL);
+
+	*min = *min > 0 ? *min - 1 : root->used + *min;
+	*max = *max > 0 ? *max - 1 : root->used + *max;
+
+	*min = URANGE(0, *min, root->used - 1);
+	*max = URANGE(0, *max, root->used - 1);
+
+	pop_call();
+	return 1 + (*min < *max ? *max - *min : *min - *max);
+}
+
+long double get_double(struct session *ses, char *str)
+{
+	long double val;
+	char result[BUFFER_SIZE];
+
+	mathexp(ses, str, result, 1);
+
+	val = tintoi(result);
+
+	return val;
+}
+	
+void get_number_string(struct session *ses, char *str, char *result)
+{
+	long double val = get_number(ses, str);
+
+	sprintf(result, "%.*Lf", precision, val);
+}
+
+long double mathswitch(struct session *ses, char *left, char *right)
+{
+	char shift[BUFFER_SIZE];
+
+	sprintf(shift, "%s == %s", left, right);
+
+	return get_number(ses, shift);
+}
+
+/*
+	Flexible tokenized mathematical expression interpreter
+	
+	If seed is set it forces floating point math
+*/
+
+void mathexp(struct session *ses, char *str, char *result, int seed)
+{
+	struct link_data *node;
+
+	substitute(ses, str, result, SUB_VAR|SUB_FUN);
+
+	if (mathexp_tokenize(ses, result, seed, TRUE) == FALSE)
+	{
+		return;
+	}
+
+	node = math_head;
+
+	while (node->prev || node->next)
+	{
+		if (node->next == NULL || atoi(node->next->str1) < atoi(node->str1))
+		{
+			mathexp_level(ses, node);
+
+			node = math_head;
+		}
+		else
+		{
+			node = node->next;
+		}
+	}
+
+	strcpy(result, node->str3);
+}
+
+#define MATH_NODE(buffercheck, priority, newstatus)             \
+{                                                               \
+	if (buffercheck && pta == buf3)                         \
+	{                                                       \
+		return FALSE;                                   \
+	}                                                       \
+	if (badnumber && debug)                                 \
+	{                                                       \
+		badnumber = 0;                                  \
+		precision = 0;                                  \
+		show_debug(ses, LIST_VARIABLE, "#MATH EXP: INVALID NUMBER %s.", buf3); \
+	}                                                       \
+	*pta = 0;                                               \
+	sprintf(buf1, "%02d", level);                           \
+	sprintf(buf2, "%02d", priority);                        \
+	add_math_node(buf1, buf2, buf3);                        \
+	status = newstatus;                                     \
+	pta = buf3;                                             \
+	point = -1;                                             \
+	metric = 0;                                             \
+}
+
+void add_math_node(char *arg1, char *arg2, char *arg3)
+{
+	struct link_data *link;
+
+	link = (struct link_data *) calloc(1, sizeof(struct link_data));
+
+	link->str1 = strdup(arg1);
+	link->str2 = strdup(arg2);
+	link->str3 = strdup(arg3);
+
+	LINK(link, math_head, math_tail);
+}
+
+void del_math_node(struct link_data *node)
+{
+	UNLINK(node, math_head, math_tail);
+
+	free(node->str1);
+	free(node->str2);
+	free(node->str3);
+
+	free(node);
+}
+
+int mathexp_tokenize(struct session *ses, char *str, int seed, int debug)
+{
+	char buf1[BUFFER_SIZE], buf2[BUFFER_SIZE], buf3[STRING_SIZE], *pti, *pta;
+	int level, status, point, badnumber, metric, nest;
+
+	nest      = 0;
+	level     = 0;
+	metric    = 0;
+	point     = -1;
+	status    = EXP_VARIABLE;
+	precision = seed;
+	badnumber = 0;
+
+	pta = buf3;
+	pti = str;
+
+	while (math_head)
+	{
+		del_math_node(math_head);
+	}
+
+	while (*pti)
+	{
+		switch (status)
+		{
+			case EXP_VARIABLE:
+				switch (*pti)
+				{
+					case '0':
+					case '1':
+					case '2':
+					case '3':
+					case '4':
+					case '5':
+					case '6':
+					case '7':
+					case '8':
+					case '9':
+						*pta++ = *pti++;
+
+						if (metric)
+						{
+							badnumber = 1;
+
+							if (debug == 0)
+							{
+								return FALSE;
+							}
+						}
+
+						if (point >= 0)
+						{
+							point++;
+
+							if (precision < point)
+							{
+								precision = point;
+							}
+						}
+						break;
+
+					case '!':
+					case '~':
+					case '+':
+					case '-':
+						if (pta != buf3)
+						{
+							MATH_NODE(FALSE, EXP_PR_VAR, EXP_OPERATOR);
+						}
+						else
+						{
+							*pta++ = *pti++;
+						}
+						break;
+
+					case '{':
+						if (pta != buf3)
+						{
+							if (debug)
+							{
+								show_debug(ses, LIST_VARIABLE, "MATH EXP: { FOUND INSIDE A NUMBER");
+							}
+							return FALSE;
+						}
+						*pta++ = *pti++;
+						status = EXP_BRACE;
+						nest++;
+						break;
+
+					case '"':
+						if (pta != buf3)
+						{
+							if (debug)
+							{
+								show_debug(ses, LIST_VARIABLE, "MATH EXP: \" FOUND INSIDE A NUMBER");
+							}
+							return FALSE;
+						}
+						*pta++ = *pti++;
+						nest++;
+						status = EXP_STRING;
+						break;
+
+					case '(':
+						if (pta != buf3)
+						{
+							if (debug)
+							{
+								show_debug(ses, LIST_VARIABLE, "#MATH EXP: PARANTESES FOUND INSIDE A NUMBER");
+							}
+							return FALSE;
+						}
+						*pta++ = *pti++;
+						MATH_NODE(FALSE, EXP_PR_LVL, EXP_VARIABLE);
+						level++;
+						break;
+
+					case ',':
+						pti++;
+						break;
+
+					case ':':
+						*pta++ = *pti++;
+						break;
+						
+					case '.':
+						if (pta != buf3 && pti[1] == '.')
+						{
+							MATH_NODE(FALSE, EXP_PR_VAR, EXP_OPERATOR);
+						}
+						else
+						{
+							*pta++ = *pti++;
+							if (point >= 0)
+							{
+								if (debug)
+								{
+									show_debug(ses, LIST_VARIABLE, "#MATH EXP: MORE THAN ONE POINT FOUND INSIDE A NUMBER");
+								}
+								precision = 0;
+								return FALSE;
+							}
+							point++;
+						}
+						break;
+
+					case ' ':
+					case '\t':
+						pti++;
+						break;
+
+					case ')':
+					case 'd':
+					case '*':
+					case '/':
+					case '%':
+					case '<':
+					case '>':
+					case '&':
+					case '^':
+					case '|':
+					case '=':
+						if (pta != buf3)
+						{
+							MATH_NODE(FALSE, EXP_PR_VAR, EXP_OPERATOR);
+						}
+						else
+						{
+							*pta++ = *pti++;
+						}
+						break;
+
+					case 'K':
+					case 'M':
+//					case 'G':
+//					case 'T':
+//					case 'P':
+//					case 'E':
+//					case 'Z':
+//					case 'Y':
+						if (pta == buf3 || metric == 1)
+						{
+							badnumber = 1;
+
+							if (debug == 0)
+							{
+								return FALSE;
+							}
+							*pta++ = *pti++;
+							*pta = 0;
+						}
+						else
+						{
+							MATH_NODE(FALSE, EXP_PR_VAR, EXP_OPERATOR);
+
+							*pta++ = '*';
+							MATH_NODE(FALSE, EXP_PR_INTMUL, EXP_VARIABLE);
+
+							switch (*pti++)
+							{
+								case 'K': pta += sprintf(pta, "1000"); break;
+								case 'M': pta += sprintf(pta, "1000000"); break;
+								case 'G': pta += sprintf(pta, "1000000000"); break;
+								case 'T': pta += sprintf(pta, "1000000000000"); break;
+								case 'P': pta += sprintf(pta, "1000000000000000"); break;
+								case 'E': pta += sprintf(pta, "1000000000000000000"); break;
+								case 'Z': pta += sprintf(pta, "1000000000000000000000"); break;
+								case 'Y': pta += sprintf(pta, "1000000000000000000000000"); break;
+							}
+							metric = 1;
+						}
+						break;
+
+					case 'm':
+					case 'u':
+//					case 'n':
+//					case 'p':
+//					case 'f':
+//					case 'a':
+//					case 'z':
+//					case 'y':
+						if (pta == buf3 || metric == 1)
+						{
+							badnumber = 1;
+
+							if (debug == 0)
+							{
+								return FALSE;
+							}
+							*pta++ = *pti++;
+							*pta = 0;
+						}
+						else
+						{
+							MATH_NODE(FALSE, EXP_PR_VAR, EXP_OPERATOR);
+
+							*pta++ = '/';
+
+							MATH_NODE(FALSE, EXP_PR_INTMUL, EXP_VARIABLE);
+
+							switch (*pti++)
+							{
+								case 'm': pta += sprintf(pta, "1000"); break;
+								case 'u': pta += sprintf(pta, "1000000"); break;
+								case 'n': pta += sprintf(pta, "1000000000"); break;
+								case 'p': pta += sprintf(pta, "1000000000000"); break;
+								case 'f': pta += sprintf(pta, "1000000000000000"); break;
+								case 'a': pta += sprintf(pta, "1000000000000000000"); break;
+								case 'z': pta += sprintf(pta, "1000000000000000000000"); break;
+								case 'y': pta += sprintf(pta, "1000000000000000000000000"); break;
+							}
+							metric = 1;
+							precision = UMAX(precision, strlen(buf3) -1);
+						}
+						break;
+
+					default:
+						*pta++ = *pti++;
+						*pta = 0;
+
+						badnumber = 1;
+
+						if (debug == 0)
+						{
+							return FALSE;
+						}
+						break;
+				}
+				break;
+
+			case EXP_STRING:
+				switch (*pti)
+				{
+					case '"':
+						*pta++ = *pti++;
+						nest--;
+						if (nest == 0)
+						{
+							MATH_NODE(FALSE, EXP_PR_VAR, EXP_OPERATOR);
+						}
+						break;
+
+					default:
+						*pta++ = *pti++;
+						break;
+				}
+				break;
+
+			case EXP_BRACE:
+				switch (*pti)
+				{
+					case '{':
+						*pta++ = *pti++;
+						nest++;
+						break;
+
+					case '}':
+						*pta++ = *pti++;
+						nest--;
+						if (nest == 0)
+						{
+							MATH_NODE(FALSE, EXP_PR_VAR, EXP_OPERATOR);
+						}
+						break;
+
+					default:
+						*pta++ = *pti++;
+						break;
+				}
+				break;
+
+			case EXP_OPERATOR:
+				switch (*pti)
+				{
+					case ' ':
+						pti++;
+						break;
+
+					case '.':
+						if (pti[1] == '.')
+						{
+							*pta++ = *pti++;
+							*pta++ = *pti++;
+
+							MATH_NODE(FALSE, EXP_PR_LOGCOMP, EXP_VARIABLE);
+							break;
+						}
+						else
+						{
+							if (debug)
+							{
+								show_debug(ses, LIST_VARIABLE, "#MATH EXP: UNKNOWN OPERATOR: %c%c", pti[0], pti[1]);
+							}
+							return FALSE;
+						}
+						break;
+
+					case ')':
+						*pta++ = *pti++;
+						level--;
+						MATH_NODE(FALSE, EXP_PR_LVL, EXP_OPERATOR);
+						break;
+
+					case 'd':
+						*pta++ = *pti++;
+						MATH_NODE(FALSE, EXP_PR_DICE, EXP_VARIABLE);
+						break;
+
+					case '*':
+						*pta++ = *pti++;
+
+						switch (*pti)
+						{
+							case '*':
+								*pta++ = *pti++;
+								MATH_NODE(FALSE, EXP_PR_INTMUL, EXP_VARIABLE);
+								break;
+							
+							default:
+								MATH_NODE(FALSE, EXP_PR_INTMUL, EXP_VARIABLE);
+								break;
+						}
+						break;
+	
+					case '/':
+						*pta++ = *pti++;
+
+						switch (*pti)
+						{
+							case '/':
+								*pta++ = *pti++;
+								MATH_NODE(FALSE, EXP_PR_INTMUL, EXP_VARIABLE);
+								break;
+							default:
+								MATH_NODE(FALSE, EXP_PR_INTMUL, EXP_VARIABLE);
+								break;
+						}
+						break;
+
+					case '%':
+						*pta++ = *pti++;
+						MATH_NODE(FALSE, EXP_PR_INTMUL, EXP_VARIABLE);
+						break;
+
+					case '+':
+					case '-':
+						*pta++ = *pti++;
+						MATH_NODE(FALSE, EXP_PR_INTADD, EXP_VARIABLE);
+						break;
+
+					case '<':
+						*pta++ = *pti++;
+
+						switch (*pti)
+						{
+							case '<':
+								*pta++ = *pti++;
+								MATH_NODE(FALSE, EXP_PR_BITSHIFT, EXP_VARIABLE);
+								break;
+
+							case '=':
+								*pta++ = *pti++;
+								MATH_NODE(FALSE, EXP_PR_LOGLTGT, EXP_VARIABLE);
+								break;
+
+							default:
+								MATH_NODE(FALSE, EXP_PR_LOGLTGT, EXP_VARIABLE);
+								break;
+						}
+						break;
+
+					case '>':
+						*pta++ = *pti++;
+
+						switch (*pti)
+						{
+							case '>':
+								*pta++ = *pti++;
+								MATH_NODE(FALSE, EXP_PR_BITSHIFT, EXP_VARIABLE);
+								break;
+
+							case '=':
+								*pta++ = *pti++;
+								MATH_NODE(FALSE, EXP_PR_LOGLTGT, EXP_VARIABLE);
+								break;
+
+							default:
+								MATH_NODE(FALSE, EXP_PR_LOGLTGT, EXP_VARIABLE);
+								break;
+						}
+						break;
+
+					case '&':
+						*pta++ = *pti++;
+
+						switch (*pti)
+						{
+							case '&':
+								*pta++ = *pti++;
+								MATH_NODE(FALSE, EXP_PR_LOGAND, EXP_VARIABLE);
+								break;
+
+							default:
+								MATH_NODE(FALSE, EXP_PR_BITAND, EXP_VARIABLE);
+								break;
+						}
+						break;
+
+					case '^':
+						*pta++ = *pti++;
+
+						switch (*pti)
+						{
+							case '^':
+								*pta++ = *pti++;
+								MATH_NODE(FALSE, EXP_PR_LOGXOR, EXP_VARIABLE);
+								break;
+
+							default:
+								MATH_NODE(FALSE, EXP_PR_BITXOR, EXP_VARIABLE);
+								break;
+
+						}
+						break;
+
+					case '|':
+						*pta++ = *pti++;
+
+						switch (*pti)
+						{
+							case '|':
+								*pta++ = *pti++;
+								MATH_NODE(FALSE, EXP_PR_LOGOR, EXP_VARIABLE);
+								break;
+
+							default:
+								MATH_NODE(FALSE, EXP_PR_BITOR, EXP_VARIABLE);
+								break;
+						}
+						break;
+
+					case '=':
+					case '!':
+						*pta++ = *pti++;
+						switch (*pti)
+						{
+							case '=':
+								*pta++ = *pti++;
+								if (*pti == '=')
+								{
+									*pta++ = *pti++;
+								}
+								MATH_NODE(FALSE, EXP_PR_LOGCOMP, EXP_VARIABLE);
+								break;
+
+							default:
+								if (debug)
+								{
+									show_debug(ses, LIST_VARIABLE, "#MATH EXP: UNKNOWN OPERATOR: %c%c", pti[-1], pti[0]);
+								}
+								return FALSE;
+						}
+						break;
+
+					default:
+						if (debug)
+						{
+							show_debug(ses, LIST_VARIABLE, "#MATH EXP: UNKNOWN OPERATOR: %c", *pti);
+						}
+						return FALSE;
+				}
+				break;
+		}
+	}
+
+	if (level != 0)
+	{
+		if (debug)
+		{
+			show_debug(ses, LIST_VARIABLE, "#MATH EXP: UNMATCHED PARENTHESES, LEVEL: %d", level);
+		}
+		return FALSE;
+	}
+
+	if (status != EXP_OPERATOR)
+	{
+		MATH_NODE(TRUE, EXP_PR_VAR, EXP_OPERATOR);
+	}
+
+	return TRUE;
+}
+
+
+void mathexp_level(struct session *ses, struct link_data *node)
+{
+	int priority;
+
+	mathnode_e = node;
+
+	while (node->prev)
+	{
+		if (atoi(node->prev->str1) < atoi(node->str1))
+		{
+			break;
+		}
+		node = node->prev;
+	}
+
+	mathnode_s = node;
+
+	for (priority = 0 ; priority < EXP_PR_VAR ; priority++)
+	{
+		for (node = mathnode_s ; node ; node = node->next)
+		{
+			if (atoi(node->str2) == priority)
+			{
+				mathexp_compute(ses, node);
+			}
+			if (node == mathnode_e)
+			{
+				break;
+			}
+		}
+	}
+
+	node = mathnode_s;
+
+	while (node->prev && node->next)
+	{
+		if (atoi(node->prev->str2) == EXP_PR_LVL && atoi(node->next->str2) == EXP_PR_LVL)
+		{
+			free(node->str1);
+			node->str1 = strdup(node->next->str1);
+
+			del_math_node(node->next);
+			del_math_node(node->prev);
+		}
+		else
+		{
+			break;
+		}
+	}
+	return;
+}
+
+void mathexp_compute(struct session *ses, struct link_data *node)
+{
+	char temp[BUFFER_SIZE];
+	int integer64 = 0;
+	long double value = 0;
+	unsigned long long min = 0, max = 0;
+	unsigned long long value64 = 0;
+
+	switch (node->str3[0])
+	{
+		case '.':
+			integer64 = 1;
+
+                        SET_BIT(max, (unsigned int) tintoi(node->next->str3));
+                        max = max << 32ULL;
+                        SET_BIT(min, (unsigned int) tintoi(node->prev->str3));
+                        value64 = max | min;
+
+		case 'd':
+			if (tintoi(node->next->str3) <= 0)
+			{
+				show_debug(ses, LIST_VARIABLE, "#MATHEXP: INVALID DICE: %s", node->next->str3);
+				value = 0;
+			}
+			else
+			{
+				value = tindice(ses, node->prev->str3, node->next->str3);
+			}
+			break;
+		case '*':
+			switch (node->str3[1])
+			{
+				case '*':
+					value = pow(tintoi(node->prev->str3), tintoi(node->next->str3));
+					break;
+				default:
+					value = tintoi(node->prev->str3) * tintoi(node->next->str3);
+					break;
+			}
+			break;
+		case '/':
+			switch (node->str3[1])
+			{
+				case '/':
+					if (tintoi(node->next->str3) == 3)
+					{
+						value = cbrt(tintoi(node->prev->str3));
+					}
+					else
+					{
+						value = sqrt(tintoi(node->prev->str3));
+					}
+					break;
+				default:
+					if (tintoi(node->next->str3) == 0)
+					{
+						show_debug(ses, LIST_VARIABLE, "#MATH ERROR: DIVISION ZERO.");
+						value = 0;
+						precision = 0;
+//						value = tintoi(node->prev->str3);
+					}
+					else
+					{
+						if (precision)
+						{
+							value = tintoi(node->prev->str3) / tintoi(node->next->str3);
+						}
+						else
+						{
+							value = (long long) tintoi(node->prev->str3) / (long long) tintoi(node->next->str3);
+						}
+					}
+					break;
+			}
+			break;
+		case '%':
+			if (tintoi(node->next->str3) == 0)
+			{
+				show_debug(ses, LIST_VARIABLE, "#MATH ERROR: MODULO ZERO.");
+				value = tintoi(node->prev->str3);
+			}
+			else
+			{
+				integer64 = 1;
+
+				value64 = tintou(node->prev->str3) % tintou(node->next->str3);
+			}
+			break;
+		case '+':
+			value = tintoi(node->prev->str3) + tintoi(node->next->str3);
+			break;
+		case '-':
+			value = tintoi(node->prev->str3) - tintoi(node->next->str3);
+			break;
+		case '<':
+			switch (node->str3[1])
+			{
+				case '=':
+					value = tincmp(node->prev->str3, node->next->str3) <= 0;
+					break;
+				case '<':
+					value = atoll(node->prev->str3) << atoll(node->next->str3);
+					break;
+				default:
+					value = tincmp(node->prev->str3, node->next->str3) < 0;
+					break;
+			}
+			break;
+		case '>':
+			switch (node->str3[1])
+			{
+				case '=':
+					value = tincmp(node->prev->str3, node->next->str3) >= 0;
+					break;
+				case '>':
+					value = atoll(node->prev->str3) >> atoll(node->next->str3);
+					break;
+				default:
+					value = tincmp(node->prev->str3, node->next->str3) > 0;
+					break;
+			}
+			break;
+
+		case '&':
+			switch (node->str3[1])
+			{
+				case '&':
+					value = tintoi(node->prev->str3) && tintoi(node->next->str3);
+					break;
+				default:
+					value = atoll(node->prev->str3) & atoll(node->next->str3);
+					break;
+			}
+			break;
+		case '^':
+			switch (node->str3[1])
+			{
+				case '^':
+					value = ((tintoi(node->prev->str3) || tintoi(node->next->str3)) && (!tintoi(node->prev->str3) != !tintoi(node->next->str3)));
+					break;
+
+				default:
+					value = atoll(node->prev->str3) ^ atoll(node->next->str3);
+					break;
+			}
+			break;
+		case '|':
+			switch (node->str3[1])
+			{
+				case '|':
+					value = tintoi(node->prev->str3) || tintoi(node->next->str3);
+					break;
+
+				default:
+					value = atoll(node->prev->str3) | atoll(node->next->str3);
+					break;
+			}
+			break;
+		case '=':
+			if (node->str3[1] == '=' && node->str3[2] == '=')
+			{
+				
+				value = tincmp(node->prev->str3, node->next->str3) == 0;
+			}
+			else
+			{
+				value = tineval(ses, node->prev->str3, node->next->str3) != 0;
+			}
+			break;
+		case '!':
+			if (node->str3[1] == '=' && node->str3[2] == '=')
+			{
+				value = tincmp(node->prev->str3, node->next->str3) != 0;
+			}
+			else
+			{
+				value = tineval(ses, node->prev->str3, node->next->str3) == 0;
+			}
+			break;
+
+		default:
+			show_debug(ses, LIST_VARIABLE, "#COMPUTE EXP: UNKNOWN OPERATOR: %c%c", node->str3[0], node->str3[1]);
+			value = 0;
+			break;
+	}
+
+	if (node->prev == mathnode_s)
+	{
+		mathnode_s = node;
+	}
+
+	if (node->next == mathnode_e)
+	{
+		mathnode_e = node;
+	}
+
+	del_math_node(node->next);
+	del_math_node(node->prev);
+
+	sprintf(temp, "%d", EXP_PR_VAR);
+	free(node->str2);
+	node->str2 = strdup(temp);
+
+	if (integer64)
+	{
+		sprintf(temp, "%llu", value64);
+	}
+	else
+	{
+		sprintf(temp, "%.*Lf", precision, value);
+	}
+	free(node->str3);
+	node->str3 = strdup(temp);
+}
+
+/*
+	Keep synced with is_number()
+*/
+
+long double tintoi(char *str)
+{
+	char *ptr = str;
+	long double values[5] = {0, 0, 0, 0, 0}, m = 1;
+	int i = 1, d = 0;
+
+	if (*ptr == 0)
+	{
+		return 0;
+	}
+
+	switch (*ptr)
+	{
+		case '!':
+		case '~':
+		case '+':
+		case '-':
+			ptr++;
+			break;
+	}
+
+	ptr = str + strlen(str);
+
+	while (TRUE)
+	{
+		ptr--;
+
+		switch (*ptr)
+		{
+			case '0':
+			case '1':
+			case '2':
+			case '3':
+			case '4':
+			case '5':
+			case '6':
+			case '7':
+			case '8':
+			case '9':
+				values[i] += (*ptr - '0') * m;
+				m *= 10;
+				break;
+
+			case '.':
+				if (d)
+				{
+					return 0;
+				}
+				d = 1;
+
+				values[0] = values[1] / m;
+				values[1] = 0;
+				m = 1;
+				break;
+
+			case ':':
+				switch (i)
+				{
+					case 2:
+						values[i] *= 60;
+						break;
+					case 3:
+						values[i] *= 60 * 60;
+						break;
+					case 4:
+						return 0;
+				}
+				i++;
+				m = 1;
+				break;
+
+			case '!':
+			case '~':
+			case '+':
+			case '-':
+				if (ptr == str)
+				{
+					break;
+				}
+				return 0;
+
+			default:
+				return 0;
+		}
+
+		if (ptr == str)
+		{
+			switch (i)
+			{
+				case 2:
+					values[i] *= 60;
+					break;
+				case 3:
+					values[i] *= 60 * 60;
+					break;
+				case 4:
+					values[i] *= 60 * 60 * 24;
+					break;
+			}
+			break;
+		}
+	}
+
+	switch (*str)
+	{
+		case '!':
+			return !(values[0] + values[1] + values[2] + values[3] + values[4]);
+		case '~':
+			return ~ (long long) (values[0] + values[1] + values[2] + values[3] + values[4]);
+		case '+':
+			return +(values[0] + values[1] + values[2] + values[3] + values[4]);
+		case '-':
+			return -(values[0] + values[1] + values[2] + values[3] + values[4]);
+		default:
+			return (values[0] + values[1] + values[2] + values[3] + values[4]);
+	}
+}
+
+unsigned long long tintou(char *str)
+{
+	char *ptr = str;
+	unsigned long long value = 0, m = 1;
+
+	if (*ptr == 0)
+	{
+		return 0;
+	}
+
+	switch (*ptr)
+	{
+		case '!':
+		case '~':
+		case '+':
+		case '-':
+			ptr++;
+			break;
+	}
+
+	ptr = str + strlen(str);
+
+	while (ptr != str)
+	{
+		ptr--;
+
+		switch (*ptr)
+		{
+			case '0':
+			case '1':
+			case '2':
+			case '3':
+			case '4':
+			case '5':
+			case '6':
+			case '7':
+			case '8':
+			case '9':
+				value += (*ptr - '0') * m;
+				m *= 10;
+				break;
+
+			case '.':
+				*ptr = 0;
+				break;
+
+			case ':':
+				return 0;
+				break;
+
+			case '!':
+			case '~':
+			case '+':
+			case '-':
+				if (ptr == str)
+				{
+					break;
+				}
+				return 0;
+
+			default:
+				return 0;
+		}
+	}
+
+	switch (str[0])
+	{
+		case '!':
+			return !value;
+
+		case '~':
+			return ~value;
+
+		case '+':
+			return +value;
+
+		case '-':
+			return -value;
+
+		default:
+			return value;
+	}
+}
+
+/*
+	Keep synched with tintoi() and is_math()
+*/
+
+int is_number(char *str)
+{
+	char *ptr = str;
+	int i = 1, d = 0, valid = 0;
+
+	if (*ptr == 0)
+	{
+		return FALSE;
+	}
+
+	ptr = str + strlen(str);
+
+	while (TRUE)
+	{
+		ptr--;
+
+		switch (*ptr)
+		{
+			case '0':
+			case '1':
+			case '2':
+			case '3':
+			case '4':
+			case '5':
+			case '6':
+			case '7':
+			case '8':
+			case '9':
+				valid = TRUE;
+				break;
+
+			case '.':
+				if (d)
+				{
+					return FALSE;
+				}
+				d = 1;
+				valid = FALSE;
+				break;
+
+			case ':':
+				if (i == 4)
+				{
+					return FALSE;
+				}
+				i++;
+				valid = FALSE;
+				break;
+
+			case '!':
+			case '~':
+			case '+':
+			case '-':
+				if (ptr != str)
+				{
+					return FALSE;
+				}
+				break;
+
+			default:
+				return FALSE;
+		}
+
+		if (ptr == str)
+		{
+			break;
+		}
+	}
+	return valid;
+}
+
+long double tincmp(char *left, char *right)
+{
+	long double left_val, right_val;
+
+	switch (left[0])
+	{
+		case '{':
+		case '"':
+			return strcmp(left, right);
+
+		default:
+			left_val = tintoi(left);
+			right_val = tintoi(right);
+
+			return left_val - right_val;
+	}
+}
+
+long double tineval(struct session *ses, char *left, char *right)
+{
+	long double left_val, right_val;
+
+	switch (left[0])
+	{
+		case '{':
+			get_arg_in_braces(ses, left, left, GET_ALL);
+			get_arg_in_braces(ses, right, right, GET_ALL);
+
+			return match(ses, left, right, SUB_NONE);
+
+		case '"':
+			return match(ses, left, right, SUB_NONE);
+
+		default:
+			left_val = tintoi(left);
+			right_val = tintoi(right);
+
+			return left_val == right_val;
+	}
+}
+
+long double tindice(struct session *ses, char *left, char *right)
+{
+	unsigned long long cnt, numdice, sizedice, sum;
+	long double estimate;
+
+	numdice  = (unsigned long long) tintoi(left);
+	sizedice = (unsigned long long) tintoi(right);
+
+	if (sizedice == 0)
+	{
+		return 0;
+	}
+
+	if (numdice > 100)
+	{
+		estimate = numdice / 100.0;
+		numdice = 100;
+	}
+	else
+	{
+		estimate = 1;
+	}
+
+	for (cnt = sum = 0 ; cnt < numdice ; cnt++)
+	{
+		sum += generate_rand(ses) % sizedice + 1;
+	}
+
+	sum *= estimate;
+
+	return (long double) sum;
+}

+ 42 - 0
mccp.c

@@ -0,0 +1,42 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2004                       *
+******************************************************************************/
+
+#include "tintin.h"
+
+/*
+	client MCCP 2 + 3
+*/
+
+
+void *zlib_alloc( void *opaque, unsigned int items, unsigned int size )
+{
+	return calloc(items, size);
+}
+
+
+void zlib_free( void *opaque, void *address ) 
+{
+	free(address);
+}

+ 391 - 0
memory.c

@@ -0,0 +1,391 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2007                       *
+******************************************************************************/
+
+#include "tintin.h"
+
+char *restring(char *point, char *string)
+{
+	if (point)
+	{
+		free(point);
+	}
+
+	return strdup(string);
+}
+
+char *restringf(char *point, char *fmt, ...)
+{
+	char string[STRING_SIZE];
+	va_list args;
+
+	va_start(args, fmt);
+	vsprintf(string, fmt, args);
+	va_end(args);
+
+	if (point)
+	{
+		free(point);
+	}
+
+	return strdup(string);
+}
+
+
+/*
+	str_ functions
+*/
+
+char *str_alloc(int size)
+{
+	char *str;
+
+	struct str_data *str_ptr = (struct str_data *) calloc(1, sizeof(struct str_data) + size + 1);
+
+	LINK(str_ptr, gtd->memory->next, gtd->memory->prev);
+
+	gtd->memory->max++;
+
+	str_ptr->max = size + 1;
+	str_ptr->len = 0;
+
+	str = (char *) str_ptr + sizeof(struct str_data);
+
+	*str = 0;
+
+	return str;
+}
+
+struct str_data *str_realloc(struct str_data *str_ptr, int size)
+{
+	if (str_ptr->max <= size)
+	{
+		int len = str_ptr->len;
+
+		UNLINK(str_ptr, gtd->memory->next, gtd->memory->prev);
+
+		str_ptr = (struct str_data *) realloc(str_ptr, sizeof(struct str_data) + size + 1);
+
+		LINK(str_ptr, gtd->memory->next, gtd->memory->prev);
+
+		str_ptr->max = size + 1;
+		str_ptr->len = len;
+	}
+	return str_ptr;
+}
+
+struct str_data *str_resize(struct str_data *str_ptr, int add)
+{
+	int len = str_ptr->len;
+
+	if (str_ptr->max <= len + add)
+	{
+		str_ptr = str_realloc(str_ptr, len * 2 + add);
+	}
+	return str_ptr;
+}
+
+// like str_dup but return an empty string
+
+char *str_mim(char *original)
+{
+	char *string = str_alloc(strlen(original));
+
+	str_cpy(&string, "");
+
+	return string;
+}
+
+// give **clone the same max length as *original.
+
+void str_clone(char **clone, char *original)
+{
+	struct str_data *clo_ptr = (struct str_data *) (*clone - sizeof(struct str_data));
+	int len = str_len(original);
+
+	if (clo_ptr->max < len)
+	{
+		clo_ptr = str_realloc(clo_ptr, len * 2);
+
+		*clone = (char *) clo_ptr + sizeof(struct str_data);
+	}
+}
+
+char *str_dup_clone(char *original)
+{
+	char *dup;
+	int len;
+
+	len = str_len(original);
+	dup = str_alloc(len);
+
+	memcpy(dup, original, len + 1);
+
+	return dup;
+}
+
+// call after a non str_ function alters *str to set the correct length.
+
+void str_fix(char *original)
+{
+	struct str_data *str_ptr = (struct str_data *) (original - sizeof(struct str_data));
+
+	str_ptr->len = strlen(original);
+}
+
+int str_len(char *str)
+{
+	struct str_data *str_ptr = (struct str_data *) (str - sizeof(struct str_data));
+
+	return str_ptr->len;
+}
+
+char *str_dup(char *original)
+{
+	char *dup = str_alloc(strlen(original));
+
+	str_cpy(&dup, original);
+
+	return dup;
+}
+
+char *str_dup_printf(char *fmt, ...)
+{
+	char *str, *ptr;
+	va_list args;
+
+	push_call("str_dup_printf(%s)", fmt);
+
+	va_start(args, fmt);
+	vasprintf(&str, fmt, args);
+	va_end(args);
+
+	ptr = str_dup(str);
+
+	free(str);
+
+	pop_call();
+	return ptr;
+}
+
+char *str_cpy(char **str, char *buf)
+{
+	int buf_len;
+	struct str_data *str_ptr;
+
+	buf_len = strlen(buf);
+
+	str_ptr = (struct str_data *) (*str - sizeof(struct str_data));
+
+	if (str_ptr->max <= buf_len)
+	{
+		str_ptr = str_realloc(str_ptr, buf_len);
+
+		*str = (char *) str_ptr + sizeof(struct str_data);
+	}
+	str_ptr->len = buf_len;
+
+	strcpy(*str, buf);
+
+	return *str;
+}
+
+char *str_cpy_printf(char **ptr, char *fmt, ...)
+{
+	char *str;
+	va_list args;
+
+	va_start(args, fmt);
+	vasprintf(&str, fmt, args);
+	va_end(args);
+
+	str_cpy(ptr, str);
+
+	free(str);
+
+	return *ptr;
+}
+
+// unused
+
+char *str_ndup(char *buf, int len)
+{
+	char *dup = str_alloc(len + 1);
+
+	str_ncpy(&dup, buf, len);
+
+	return dup;
+}
+
+// Like strncpy but handles the string terminator properly
+
+char *str_ncpy(char **str, char *buf, int len)
+{
+	int buf_len;
+	struct str_data *str_ptr;
+
+	buf_len = strlen(buf);
+
+	if (buf_len > len)
+	{
+		buf_len = len;
+	}
+
+	str_ptr = (struct str_data *) (*str - sizeof(struct str_data));
+
+	if (str_ptr->max <= buf_len)
+	{
+		str_ptr = str_realloc(str_ptr, len);
+
+		*str = (char *) str_ptr + sizeof(struct str_data);
+	}
+
+	str_ptr->len = UMIN(buf_len, len);
+
+	strncpy(*str, buf, len);
+
+	(*str)[len] = 0;
+
+	return *str;
+}
+
+char *str_cat(char **str, char *buf)
+{
+	int buf_len;
+	struct str_data *str_ptr;
+
+	buf_len = strlen(buf);
+
+	str_ptr = (struct str_data *) (*str - sizeof(struct str_data));
+
+	if (str_ptr->max <= str_ptr->len + buf_len)
+	{
+		str_ptr = str_resize(str_ptr, buf_len);
+
+		*str = (char *) str_ptr + sizeof(struct str_data);
+	}
+
+	strcpy(&(*str)[str_ptr->len], buf);
+
+	str_ptr->len += buf_len;
+
+	return *str;
+}
+
+// Unused
+
+char *str_cat_chr(char **ptr, char chr)
+{
+	struct str_data *str_ptr;
+
+	str_ptr = (struct str_data *) (*ptr - sizeof(struct str_data));
+
+	if (str_ptr->max <= str_ptr->len + 1)
+	{
+		str_ptr = str_resize(str_ptr, 1);
+
+		*ptr = (char *) str_ptr + sizeof(struct str_data);
+	}
+
+	(*ptr)[str_ptr->len++] = chr;
+
+	(*ptr)[str_ptr->len] = 0;
+
+	return *ptr;
+}
+	
+
+char *str_cat_printf(char **ptr, char *fmt, ...)
+{
+	char *str;
+	va_list args;
+
+	va_start(args, fmt);
+	vasprintf(&str, fmt, args);
+	va_end(args);
+
+	str_cat(ptr, str);
+
+	free(str);
+
+	return *ptr;
+}
+
+char *str_ins(char **str, int index, char *buf)
+{
+	int buf_len;
+	struct str_data *str_ptr;
+
+	buf_len = strlen(buf);
+
+	str_ptr = (struct str_data *) (*str - sizeof(struct str_data));
+
+	if (str_ptr->max <= str_ptr->len + buf_len)
+	{
+		str_ptr = str_resize(str_ptr, buf_len);
+
+		*str = (char *) str_ptr + sizeof(struct str_data);
+	}
+
+	if (index >= str_ptr->len)
+	{
+		strcpy(&(*str)[str_ptr->len], buf);
+	}
+	else
+	{
+		int cnt;
+		char *pta, *ptz;
+
+		pta = &(*str)[str_ptr->len + 1];
+		ptz = &(*str)[str_ptr->len + 1 + buf_len];
+
+		for (cnt = 0 ; cnt < buf_len + 1 ; cnt++)
+		{
+			*ptz-- = *pta--;
+		}
+
+		pta = &(*str)[index];
+		ptz = buf;
+
+		for (cnt = 0 ; cnt < buf_len ; cnt++)
+		{
+			*pta++ = *ptz++;
+		}
+	}
+
+	str_ptr->len += buf_len;
+
+	return *str;
+}
+
+void str_free(char *ptr)
+{
+	struct str_data *str_ptr = (struct str_data *) (ptr - sizeof(struct str_data));
+
+	UNLINK(str_ptr, gtd->memory->next, gtd->memory->prev);
+
+	gtd->memory->max--;
+
+	free(str_ptr);
+}

+ 286 - 0
misc.c

@@ -0,0 +1,286 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2019 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
+*                                                                             *
+*                         coded by Peter Unold 1992                           *
+******************************************************************************/
+
+#include "tintin.h"
+
+
+DO_COMMAND(do_bell)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		print_stdout("\007");
+
+		return ses;
+	}
+
+	if (is_abbrev(arg1, "FLASH"))
+	{
+		if (is_abbrev(arg2, "ON"))
+		{
+			print_stdout("\e[?1042h");
+		}
+		else if (is_abbrev(arg2, "OFF"))
+		{
+			print_stdout("\e[?1042l");
+		}
+		else
+		{
+			show_error(ses, LIST_COMMAND, "#SYNTAX: #BELL FLASH {ON|OFF}");
+		}
+	}
+	else if (is_abbrev(arg1, "FOCUS"))
+	{
+		if (is_abbrev(arg2, "ON"))
+		{
+			print_stdout("\e[?1043h");
+		}
+		else if (is_abbrev(arg2, "OFF"))
+		{
+			print_stdout("\e[?1043l");
+		}
+		else
+		{
+			show_error(ses, LIST_COMMAND, "#SYNTAX: #BELL POP {ON|OFF}");
+		}
+	}
+	else if (is_abbrev(arg1, "MARGIN"))
+	{
+		if (is_abbrev(arg2, "ON"))
+		{
+			print_stdout("\e[?44h");
+		}
+		else if (is_abbrev(arg2, "OFF"))
+		{
+			print_stdout("\e[?44l");
+		}
+		else
+		{
+			show_error(ses, LIST_COMMAND, "#SYNTAX: #BELL MARGIN {ON|OFF}");
+		}
+	}
+	else if (is_abbrev(arg1, "RING"))
+	{
+		print_stdout("\007");
+	}
+	else if (is_abbrev(arg1, "VOLUME"))
+	{
+		if (is_math(ses, arg2))
+		{
+			print_stdout("\e[ %dt", (int) get_number(ses, arg2));
+		}
+		else
+		{
+			show_error(ses, LIST_COMMAND, "#SYNTAX: #BELL VOLUME {1-8}");
+		}
+	}
+	else
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #BELL {FLASH|FOCUS|MARGIN|RING|VOLUME} {ARGUMENT}");
+	}
+	return ses;
+}
+
+
+DO_COMMAND(do_commands)
+{
+	char buf[BUFFER_SIZE] = { 0 }, add[BUFFER_SIZE];
+	int cmd;
+
+	tintin_header(ses, " %s ", "COMMANDS");
+
+	for (cmd = 0 ; *command_table[cmd].name != 0 ; cmd++)
+	{
+		if (*arg && !is_abbrev(arg, command_table[cmd].name))
+		{
+			continue;
+		}
+		if ((int) strlen(buf) + 20 > gtd->screen->cols)
+		{
+			tintin_puts2(ses, buf);
+			buf[0] = 0;
+		}
+		sprintf(add, "%20s", command_table[cmd].name);
+		strcat(buf, add);
+	}
+	if (buf[0])
+	{
+		tintin_puts2(ses, buf);
+	}
+	tintin_header(ses, "");
+
+	return ses;
+}
+
+
+DO_COMMAND(do_cr)
+{
+	write_mud(ses, "", SUB_EOL);
+
+	return ses;
+}
+
+
+DO_COMMAND(do_echo)
+{
+	char format[BUFFER_SIZE], result[BUFFER_SIZE], temp[BUFFER_SIZE], *output, left[BUFFER_SIZE];
+	int lnf;
+
+	arg = sub_arg_in_braces(ses, arg, format, GET_ONE, SUB_VAR|SUB_FUN);
+
+	format_string(ses, format, arg, result);
+
+	arg = result;
+
+	if (*arg == DEFAULT_OPEN)
+	{
+		arg = get_arg_in_braces(ses, arg, left, GET_ALL);
+		      get_arg_in_braces(ses, arg, temp, GET_ALL);
+
+		if (*temp)
+		{
+			int row = (int) get_number(ses, temp);
+
+			substitute(ses, left, temp, SUB_COL|SUB_ESC);
+
+			split_show(ses, temp, row, 0);
+
+			return ses;
+		}
+	}
+
+	lnf = !str_suffix(arg, "\\");
+
+	substitute(ses, arg, temp, SUB_COL|SUB_ESC);
+
+	if (strip_vt102_strlen(ses, ses->more_output) != 0)
+	{
+		output = str_dup_printf("\n\e[0m%s\e[0m", temp);
+	}
+	else
+	{
+		output = str_dup_printf("\e[0m%s\e[0m", temp);
+	}
+
+	add_line_buffer(ses, output, lnf);
+
+	if (ses == gtd->ses)
+	{
+		if (!HAS_BIT(ses->flags, SES_FLAG_READMUD) && IS_SPLIT(ses))
+		{
+			save_pos(ses);
+
+			goto_pos(ses, ses->split->bot_row, 1);
+
+			print_line(ses, &output, lnf);
+			
+			restore_pos(ses);
+		}
+		else
+		{
+			print_line(ses, &output, lnf);
+		}
+	}
+	str_free(output);
+
+	return ses;
+}
+
+
+DO_COMMAND(do_end)
+{
+	char arg1[BUFFER_SIZE];
+
+	if (*arg)
+	{
+		sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN|SUB_COL|SUB_ESC|SUB_LNF);
+
+		quitmsg(arg1);
+	}
+	else
+	{
+		quitmsg(NULL);
+	}
+	return NULL;
+}
+
+
+DO_COMMAND(do_nop)
+{
+	return ses;
+}
+
+
+DO_COMMAND(do_send)
+{
+	char arg1[BUFFER_SIZE];
+
+	push_call("do_send(%p,%p)",ses,arg);
+
+	get_arg_in_braces(ses, arg, arg1, GET_ALL);
+
+	write_mud(ses, arg1, SUB_VAR|SUB_FUN|SUB_ESC|SUB_EOL);
+
+	pop_call();
+	return ses;
+}
+
+
+
+
+DO_COMMAND(do_test)
+{
+	char arg1[BUFFER_SIZE], arg2[100], arg3[100], arg4[100];
+
+	strcpy(arg2, "9");
+	strcpy(arg3, "<f0b8>");
+	strcpy(arg4, "3 9");
+
+	if (isdigit(arg[0]))
+	{
+		sprintf(arg2, "%d", (arg[0] - '0') * (arg[0] - '0'));
+
+		if (isxdigit(arg[1]) && isxdigit(arg[2]) && isxdigit(arg[3]))
+		{
+			sprintf(arg3, "<f%c%c%c>", arg[1], arg[2], arg[3]);
+
+			if (isdigit(arg[4]) && isdigit(arg[5]))
+			{
+				sprintf(arg4, "%d %d %s", (arg[4] - '0') * (arg[4] - '0') / 10, (arg[5] - '0') * (arg[5] - '0'), &arg[6]);
+			}
+		}
+	}
+	sprintf(arg1, "#line quiet {#event {RECEIVED KEYPRESS} {#end \\};#screen cursor hide;#screen clear all;#event {SECOND} #loop 0 %s cnt #delay {$cnt / (1.0+%s)} #draw %s rain 1 1 -1 -1 rain %s}", arg2, arg2, arg3, arg4);
+
+	script_driver(gtd->ses, LIST_COMMAND, arg1);
+
+	return ses;
+}
+

+ 171 - 2
mods/igr.mods

@@ -1,3 +1,174 @@
+Nov 2019        2.01.93
+------------------------------------------------------------------------------
+
+regex.c         Added support for using %+4s to match 4 spaces, or %+0..2d
+                to match between 0 and 2 digits. Only works with letter
+                arguments.
+
+regex.c         Added %a regex as an alternative for %* because %+4* is
+                not valid.
+
+regex.c         Added %p regex which will match printable characters.
+
+regex.c         Added %u regex which should match whole unicode characters.
+
+utf8.c          Added BIG5toUTF8, KOI8toUTF8, ISO1toUTF8, ISO2toUTF8 charset
+                config options.
+
+utf8.c          Added GBK1TOUTF8 charset config option. When enabled 2 byte
+                GBK codes are translated to unicode.
+
+utf8.c          Added GBK-1 charset config option which should be able to
+                handle GB18030.
+
+mapper.c        Added a UNICODE GRAPHICS group to #map legend, this will
+                allow you to customize most of the unicodegraphics symbols.
+
+variable.c      Added #format <variable> %S option to do a spell check.
+                Returns the number of errors found. Still a work in
+                progress.
+
+                People using %S to get the session name should switch to
+                using #info session save.
+
+data.c          Added #info session option, this data can be saved.
+
+draw.c          Added #draw RAIN {<VARIABLE>} {[SPAWN]} {[FADE]} {[LEGEND]}
+
+                Use tt++ -M for a default example.
+
+class.c         Added #class clear, load, and save. See #help class.
+
+main.c          Configurations and pathdirs are automatically assigned to
+                the CONFIG and PATHDIR class on startup. Somewhat experimental
+                as there may be unforseen complications.
+
+list.c          Added #list <list> collapse and explode
+
+mapper.c        Updated #map {} output.
+
+nest.c          Added better #local support.
+
+variable.c      Added #cat command. See #help cat.
+
+event.c         Added SWIPED N/NE/E/SE/S/SW/W/NW events. These do not work
+                on Termux yet, but I've asked the Termux dev to enable
+                advanced mouse mode by default.
+
+draw.c          Added #draw TABLE type which comes with the GRID option to
+                change the drawing behavior. Requires a nested string
+                argument. Under construction.
+
+mapper.c        Added #map flag terrain on/off option to enable or disable
+                terrain displaying. Default for new maps is on.
+
+mapper.c        Added #map terrain <name> <symbol> [flag] and #map unterrain
+                Creates a symbol, which can be colored, for the given
+                terrain type. Density flags are DENSE, AMPLE, SPARSE, SCANT,
+                omit for default density. Range flags are NARROW, STANDARD,
+                WIDE, and VAST, omit for default.
+
+                Decay flags are FADEIN and FADEOUT which speak for
+                themselves. DOUBLE will expect the terrain symbol to be
+                2 characters or a double-width unicode character.
+
+variable.c      Added %C format option to store the given number using
+                chronological notation.
+
+                People using %C to get the screen width should switch to
+                using #screen get cols <var> and #screen get rows <var>
+
+mapper.c        Added a 4th argument to landmarks which will indicate the
+                distance from which the landmark will be visible in a future
+                #map describe command.
+
+draw.c          Added HUGE option to #draw. Only supports uppercase letters,
+                numbers, and a few punctuations for now.
+
+cursor.c        Added #cursor tab <list|scrollback> <backward|forward>
+                option. Allows people with custom tab settings to upgrade
+                to the new format before I start messing with it.
+
+                list goes exclusively through the tab list, scrollback goes
+                exclusively through the scrollback buffer, use both list and
+                scrollback to go through both.
+
+line.c          Added #line capture <variable> <command> option.
+
+mapper.c        Added the MAP MOUSE LOCATION event to go along with the 
+                SCREEN MOUSE LOCATION event. MAP MOUSE LOCATION is more
+                accurate than regular MAP click events. It needs to be
+                trigged with #screen raise mouse location, which is not
+                supported by all terminals.
+
+                You'll need to use ./configure --enable-dec-locator for xterm
+                to enable the MOUSE LOCATION events.
+
+
+list.c          Added #list <var> shuffle
+
+mapper.c        I'm working on a new speedwalk standard that will work with
+                diagonal exits. Initial support is added to the #map at,
+                #map goto, and the #map jump commands. Old speedwalk support
+                is going to be left unchanged.
+
+                The format is similar to old speed walks, except that
+                speedwalks like 2ene need to be changed to 2e1n1e.
+
+                #map jump and #map at will use virtual traversal, meaning you
+                can move through walls as long as the destination is an actual
+                location.
+
+                #map move and #map goto will use physical traversal, meaning
+                #map goto 10w only works if there is a straight line of 10
+                rooms leading west, while #map move 10w will create 10 rooms
+                if static mode is disabled. Keep in mind that #map move will
+                trigger map movements events while #map goto will not.
+
+                Regular #config SPEEDWALK speedwalks are unaffected.
+
+mapper.c        Added MAP MOUSE LOCATION event which allows more accurate
+                mouse clicking reports in unicode mode on terminals which
+                support it. Appears to be some obscure compile time option
+                on xterm.
+
+mapper.c        AsciiGraphics and UnicodeGraphics mapping modes now report
+                the exit that was clicked on MAP MOUSE click events in the
+                %2 argument.
+
+screen.c        Added #screen raise SCREEN MOUSE LOCATION, the SCREEN MOUSE
+                LOCATION in turn will report row,col,-row,-col, as well as
+                the row,col,-row,-col for the pixel position of the mouse
+                within the character cell. %8 will report the pixel position
+                as a 3x3 cell using values 1 through 9.
+
+mapper.c        Added the BLOCK exit flag and room flag. Works like AVOID, but
+                also prevents movement into the room.
+
+mapper.c        Added #map landmark <name> <vnum> [desc] and #map unlandmark.
+                Landmarks work as fast lookup aliases and to give tintin an
+                idea of locations of interest to eventually describe the map
+                in words for screen readers.
+
+mapper.c        Added #map exit <dir> color <code> option.
+
+mapper.c        Added exit coloring for avoid/hide/invis flagged exits.
+
+main.c          Added -V startup option to show version number.
+
+daemon.c        Added better link lost handling to daemons.
+
+cursor.c        Fixed #help event section on {SCREEN FOCUS}.
+
+math.c          Added metric suffix support to #math, see #help metric.
+
+variable.c      Added #echo %M to print numbers using the metric system,
+                supported suffixes are K M, m, and u.
+
+mapper.c        Added #map sync <filename> which will try to synchronize the
+                currently loaded map with the file being read. Should be a
+                valid alternative to #map read at all times.
+
 Sep 2019        2.01.92
 ------------------------------------------------------------------------------
 
@@ -836,8 +1007,6 @@ tinexp.c       @ $ * and & characters send from the server are only escaped if
                the key matches an existing variable. Keep in mind that ${ *{
                and &{ are always escaped.
 
-variable.c     Added #format <variable> %S option to store the session name to
-               the given variable.
 
 tinexp.c       Added support for <Fxxx> and <Bxxx> 12 bit color codes. F
                stands for foreground, B for background, xxx is hexadecimal RGB

+ 976 - 0
msdp.c

@@ -0,0 +1,976 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2011                       *
+******************************************************************************/
+
+#include "tintin.h"
+#include "telnet.h"
+
+// Set table size and check for errors. Call once at startup.
+
+void init_msdp_table(void)
+{
+	int index;
+
+	for (index = 0 ; *msdp_table[index].name ; index++)
+	{
+		if (strcmp(msdp_table[index].name, msdp_table[index+1].name) > 0)
+		{
+			if (*msdp_table[index+1].name)
+			{
+				print_stdout("\e[31minit_msdp_table: Improperly sorted variable: %s.\e0m", msdp_table[index+1].name);
+			}
+		}
+	}
+	gtd->msdp_table_size = index;
+}
+
+// Binary search on the msdp_table.
+
+int msdp_find(char *var)
+{
+	int val, bot, top, srt;
+
+	bot = 0;
+	top = gtd->msdp_table_size - 1;
+	val = top / 2;
+
+	while (bot <= top)
+	{
+		srt = strcmp(var, msdp_table[val].name);
+
+		if (srt < 0)
+		{
+			top = val - 1;
+		}
+		else if (srt > 0)
+		{
+			bot = val + 1;
+		}
+		else
+		{
+			return val;
+		}
+		val = bot + (top - bot) / 2;
+	}
+        tintin_printf2(NULL, "msdp_find: %s (-1)", var, val);
+	return -1;
+}
+
+void arachnos_devel(struct session *ses, char *fmt, ...)
+{
+	char buf[STRING_SIZE];
+
+	va_list args;
+
+	va_start(args, fmt);
+	vsprintf(buf, fmt, args);
+	va_end(args);
+
+	port_printf(ses, "ARACHNOS: %s", buf);
+}
+
+void arachnos_mudlist(struct session *ses, char *fmt, ...)
+{
+	char buf[STRING_SIZE];
+
+	va_list args;
+
+	va_start(args, fmt);
+	vsprintf(buf, fmt, args);
+	va_end(args);
+
+	port_printf(ses, "ARACHNOS: %s", buf);
+}
+
+void msdp_update_all(char *var, char *fmt, ...)
+{
+	struct session *ses;
+	struct port_data *buddy;
+	char buf[STRING_SIZE];
+	va_list args;
+
+	va_start(args, fmt);
+	vsprintf(buf, fmt, args);
+	va_end(args);
+
+	for (ses = gts->next ; ses ; ses = ses->next)
+	{
+		if (ses->port)
+		{
+			for (buddy = ses->port->next ; buddy ; buddy = buddy->next)
+			{
+				if (buddy->msdp_data)
+				{
+					msdp_update_var(ses, buddy, var, buf);
+				}
+			}
+		}
+	}
+}
+
+// Update a variable and queue it if it's being reported.
+
+void msdp_update_index(struct session *ses, struct port_data *buddy, int index, char *str)
+{
+	if (strcmp(buddy->msdp_data[index]->value, str))
+	{
+		if (HAS_BIT(buddy->msdp_data[index]->flags, MSDP_FLAG_REPORTED))
+		{
+			SET_BIT(buddy->msdp_data[index]->flags, MSDP_FLAG_UPDATED);
+			SET_BIT(buddy->comm_flags, COMM_FLAG_MSDPUPDATE);
+		}
+		RESTRING(buddy->msdp_data[index]->value, str);
+	}
+}
+
+void msdp_update_var(struct session *ses, struct port_data *buddy, char *var, char *str)
+{
+	int index;
+
+	index = msdp_find(var);
+
+	if (index == -1)
+	{
+		port_printf(ses, "msdp_update_var: Unknown variable: %s.", var);
+
+		return;
+	}
+
+	msdp_update_index(ses, buddy, index, str);
+}
+
+void msdp_update_varf(struct session *ses, struct port_data *buddy, char *var, char *fmt, ...)
+{
+	char buf[STRING_SIZE];
+	va_list args;
+
+	va_start(args, fmt);
+	vsprintf(buf, fmt, args);
+	va_end(args);
+
+	msdp_update_var(ses, buddy, var, buf);
+}
+
+// Update a variable and send it instantly.
+
+void msdp_update_var_instant(struct session *ses, struct port_data *buddy, char *var, char *fmt, ...)
+{
+	char buf[STRING_SIZE];
+	int index, length;
+	va_list args;
+
+	index = msdp_find(var);
+
+	if (index == -1)
+	{
+		port_printf(ses, "msdp_update_var_instant: Unknown variable: %s.", var);
+
+		return;
+	}
+
+	va_start(args, fmt);
+	vsprintf(buf, fmt, args);
+	va_end(args);
+
+	if (strcmp(buddy->msdp_data[index]->value, buf))
+	{
+		RESTRING(buddy->msdp_data[index]->value, buf);
+	}
+
+	if (HAS_BIT(buddy->msdp_data[index]->flags, MSDP_FLAG_REPORTED))
+	{
+		length = sprintf(buf, "%c%c%c%c%s%c%s%c%c", IAC, SB, TELOPT_MSDP, MSDP_VAR, msdp_table[index].name, MSDP_VAL, buf, IAC, SE);
+
+		write_msdp_to_descriptor(ses, buddy, buf, length);
+	}
+}
+
+// Send all reported variables that have been updated.
+
+void msdp_send_update(struct session *ses, struct port_data *buddy)
+{
+	char *ptr, buf[STRING_SIZE];
+	int index;
+
+	if (buddy->msdp_data == NULL)
+	{
+		return;
+	}
+
+	ptr = buf;
+
+	ptr += sprintf(ptr, "%c%c%c", IAC, SB, TELOPT_MSDP);
+
+	for (index = 0 ; index < gtd->msdp_table_size ; index++)
+	{
+		if (HAS_BIT(buddy->msdp_data[index]->flags, MSDP_FLAG_UPDATED))
+		{
+			ptr += sprintf(ptr, "%c%s%c%s", MSDP_VAR, msdp_table[index].name, MSDP_VAL, buddy->msdp_data[index]->value);
+
+			DEL_BIT(buddy->msdp_data[index]->flags, MSDP_FLAG_UPDATED);
+		}
+
+		if (ptr - buf > STRING_SIZE - BUFFER_SIZE)
+		{
+			port_printf(ses, "msdp_send_update: MSDP BUFFER OVERFLOW");
+			break;
+		}
+	}
+
+	ptr += sprintf(ptr, "%c%c", IAC, SE);
+
+	write_msdp_to_descriptor(ses, buddy, buf, ptr - buf);
+
+	DEL_BIT(buddy->comm_flags, COMM_FLAG_MSDPUPDATE);
+}
+
+
+char *msdp_get_var(struct session *ses, struct port_data *buddy, char *var)
+{
+	int index;
+
+	index = msdp_find(var);
+
+	if (index == -1)
+	{
+		port_printf(ses, "msdp_get_var: Unknown variable: %s.", var);
+
+		return NULL;
+	}
+
+	return buddy->msdp_data[index]->value;
+}
+
+// 1d array support for commands
+
+void process_msdp_index_val(struct session *ses, struct port_data *buddy, int var_index, char *val )
+{
+	int val_index;
+
+	val_index = msdp_find(val);
+
+	if (val_index >= 0)
+	{
+		if (msdp_table[var_index].fun)
+		{
+			msdp_table[var_index].fun(ses, buddy, val_index);
+		}
+	}
+} 
+
+// 1d array support for commands
+
+void process_msdp_array(struct session *ses, struct port_data *buddy, int var_index, char *val)
+{
+	char buf[BUFFER_SIZE], *pto, *pti;
+
+	pti = val;
+	pto = buf;
+
+	while (*pti)
+	{
+		switch (*pti)
+		{
+			case MSDP_ARRAY_OPEN:
+				break;
+
+			case MSDP_VAL:
+				*pto = 0;
+
+				if (*buf)
+				{
+					process_msdp_index_val(ses, buddy, var_index, buf);
+				}
+				pto = buf;
+				break;
+
+			case MSDP_ARRAY_CLOSE:
+				*pto = 0;
+
+				if (*buf)
+				{
+					process_msdp_index_val(ses, buddy, var_index, buf);
+				}
+				return;
+
+			default:
+				*pto++ = *pti;
+				break;
+		}
+		pti++;
+	}
+	*pto = 0;
+
+	if (*buf)
+	{
+		process_msdp_index_val(ses, buddy, var_index, buf);
+	}
+	return;
+}
+
+void process_msdp_varval(struct session *ses, struct port_data *buddy, char *var, char *val )
+{
+	int var_index, val_index;
+
+	var_index = msdp_find(var);
+
+	if (var_index == -1)
+	{
+		return;
+	}
+
+	if (HAS_BIT(msdp_table[var_index].flags, MSDP_FLAG_CONFIGURABLE))
+	{
+		RESTRING(buddy->msdp_data[var_index]->value, val);
+
+		if (msdp_table[var_index].fun)
+		{
+			msdp_table[var_index].fun(ses, buddy, var_index);
+		}
+		return;
+	}
+
+	// Commands only take variables as arguments.
+
+	if (HAS_BIT(msdp_table[var_index].flags, MSDP_FLAG_COMMAND))
+	{
+		if (*val == MSDP_ARRAY_OPEN)
+		{
+			port_printf(ses, "process_msdp_varval: array");
+
+			process_msdp_array(ses, buddy, var_index, val);
+		}
+		else
+		{
+			val_index = msdp_find(val);
+
+			if (val_index == -1)
+			{
+				return;
+			}
+
+			if (msdp_table[var_index].fun)
+			{
+				msdp_table[var_index].fun(ses, buddy, val_index);
+			}
+		}
+		return;
+	}
+} 
+
+void msdp_command_list(struct session *ses, struct port_data *buddy, int index)
+{
+	char *ptr, buf[STRING_SIZE];
+	int flag;
+
+	if (!HAS_BIT(msdp_table[index].flags, MSDP_FLAG_LIST))
+	{
+		return;
+	}
+
+	ptr = buf;
+
+	flag = msdp_table[index].flags;
+
+	ptr += sprintf(ptr, "%c%c%c%c%s%c%c", IAC, SB, TELOPT_MSDP, MSDP_VAR, msdp_table[index].name, MSDP_VAL, MSDP_ARRAY_OPEN);
+
+	for (index = 0 ; index < gtd->msdp_table_size ; index++)
+	{
+		if (flag != MSDP_FLAG_LIST)
+		{
+			if (HAS_BIT(buddy->msdp_data[index]->flags, flag) && !HAS_BIT(buddy->msdp_data[index]->flags, MSDP_FLAG_LIST))
+			{
+				ptr += sprintf(ptr, "%c%s", MSDP_VAL, msdp_table[index].name);
+			}
+		}
+		else
+		{
+			if (HAS_BIT(buddy->msdp_data[index]->flags, MSDP_FLAG_LIST))
+			{
+				ptr += sprintf(ptr, "%c%s", MSDP_VAL, msdp_table[index].name);
+			}
+		}
+	}
+
+	ptr += sprintf(ptr, "%c%c%c", MSDP_ARRAY_CLOSE, IAC, SE);
+
+	write_msdp_to_descriptor(ses, buddy, buf, ptr - buf);
+}
+
+void msdp_command_report(struct session *ses, struct port_data *buddy, int index)
+{
+	port_printf(ses, "msdp_command_report(%s,%s,%d)", ses->name, buddy->name, index);
+
+	if (!HAS_BIT(msdp_table[index].flags, MSDP_FLAG_REPORTABLE))
+	{
+		return;
+	}
+
+	SET_BIT(buddy->msdp_data[index]->flags, MSDP_FLAG_REPORTED);
+
+	if (!HAS_BIT(msdp_table[index].flags, MSDP_FLAG_SENDABLE))
+	{
+		return;
+	}
+
+	SET_BIT(buddy->msdp_data[index]->flags, MSDP_FLAG_UPDATED);
+	SET_BIT(buddy->comm_flags, COMM_FLAG_MSDPUPDATE);
+}
+
+void msdp_command_reset(struct session *ses, struct port_data *buddy, int index)
+{
+	int flag;
+
+	if (!HAS_BIT(msdp_table[index].flags, MSDP_FLAG_LIST))
+	{
+		return;
+	}
+
+	flag = msdp_table[index].flags &= ~MSDP_FLAG_LIST;
+
+	for (index = 0 ; index < gtd->msdp_table_size ; index++)
+	{
+		if (HAS_BIT(buddy->msdp_data[index]->flags, flag))
+		{
+			buddy->msdp_data[index]->flags = msdp_table[index].flags;
+		}
+	}
+}
+
+void msdp_command_send(struct session *ses, struct port_data *buddy, int index)
+{
+	if (HAS_BIT(buddy->msdp_data[index]->flags, MSDP_FLAG_SENDABLE))
+	{
+		SET_BIT(buddy->msdp_data[index]->flags, MSDP_FLAG_UPDATED);	
+		SET_BIT(buddy->comm_flags, COMM_FLAG_MSDPUPDATE);
+	}
+}
+
+void msdp_command_unreport(struct session *ses, struct port_data *buddy, int index)
+{
+	if (!HAS_BIT(msdp_table[index].flags, MSDP_FLAG_REPORTABLE))
+	{
+		return;
+	}
+
+	DEL_BIT(buddy->msdp_data[index]->flags, MSDP_FLAG_REPORTED);
+}
+
+// Arachnos Map support
+
+void msdp_configure_map(struct session *ses, struct port_data *buddy, int index)
+{
+	char val[BUFFER_SIZE];
+	struct room_data *room;
+	struct exit_data *exit;
+	int vnum;
+
+	if (ses->map == NULL || *buddy->msdp_data[index]->value == 0)
+	{
+		return;
+	}
+
+	if (!strcmp(msdp_table[index].name, "MAP_ROOM_INFO"))
+	{
+		vnum = atoi(buddy->msdp_data[index]->value);
+
+		port_printf(ses, "MAP_ROOM_INFO: %d", vnum);
+
+		if (vnum <= 0 || vnum >= ses->map->size)
+		{
+			return;
+		}
+
+		room = ses->map->room_list[vnum];
+
+		if (room == NULL)
+		{
+			return;
+		}
+
+		sprintf(val, "ROOM\002%c", MSDP_TABLE_OPEN);
+
+		cat_sprintf(val, "\001AREA\002%s",    room->area);
+		cat_sprintf(val, "\001COLOR\002%s",   room->color);
+		cat_sprintf(val, "\001DESC\002%s",    room->desc);
+
+		cat_sprintf(val, "\001EXITS\002%c", MSDP_TABLE_OPEN);
+
+		for (exit = room->f_exit ; exit ; exit = exit->next)
+		{
+			cat_sprintf(val, "\001%s\002", exit->name);
+			cat_sprintf(val, "%c", MSDP_TABLE_OPEN);
+				cat_sprintf(val, "\001COMMAND\002%s",  exit->cmd);
+				cat_sprintf(val, "\001FLAGS\002%d",    exit->flags);
+				cat_sprintf(val, "\001NAME\002%s",     exit->name);
+				cat_sprintf(val, "\001VNUM\002%d",     exit->vnum);
+				cat_sprintf(val, "\001WEIGHT\002%.3f", exit->weight);
+			cat_sprintf(val, "%c", MSDP_TABLE_CLOSE);
+		}
+		cat_sprintf(val, "%c", MSDP_TABLE_CLOSE);
+
+		cat_sprintf(val, "\001FLAGS\002%d",     room->flags);
+		cat_sprintf(val, "\001NAME\002%s",      room->name);
+		cat_sprintf(val, "\001NOTE\002%s",      room->note);
+		cat_sprintf(val, "\001SYMBOL\002%s",    room->symbol);
+		cat_sprintf(val, "\001TERRAIN\002%s",   room->terrain);
+		cat_sprintf(val, "\001VNUM\002%d",      room->vnum);
+		cat_sprintf(val, "\001WEIGHT\002%.3f",  room->weight);
+
+		cat_sprintf(val, "%c", MSDP_TABLE_CLOSE);
+
+		msdp_update_index(ses, buddy, index, val);
+
+		SET_BIT(buddy->msdp_data[index]->flags, MSDP_FLAG_UPDATED);
+		SET_BIT(buddy->comm_flags, COMM_FLAG_MSDPUPDATE);
+	}
+	else if (!strcmp(msdp_table[index].name, "MAP_ROOM_GOTO"))
+	{
+		// to be imped
+	}
+}
+
+// Arachnos Intermud support
+
+void msdp_configure_arachnos(struct session *ses, struct port_data *buddy, int index)
+{
+	char var[BUFFER_SIZE], val[BUFFER_SIZE];
+	char mud_name[BUFFER_SIZE], mud_host[BUFFER_SIZE], mud_port[BUFFER_SIZE];
+	char msg_user[BUFFER_SIZE], msg_time[BUFFER_SIZE], msg_body[BUFFER_SIZE];
+	char mud_players[BUFFER_SIZE], mud_uptime[BUFFER_SIZE], mud_update[BUFFER_SIZE];
+	char *pti, *pto;
+
+	struct tm timeval_tm;
+	time_t timeval_t;
+
+	var[0] = val[0] = mud_name[0] = mud_host[0] = mud_port[0] = msg_user[0] = msg_time[0] = msg_body[0] = mud_players[0] = mud_uptime[0] = mud_update[0] = 0;
+
+	pti = buddy->msdp_data[index]->value;
+
+	while (*pti)
+	{
+		switch (*pti)
+		{
+			case MSDP_VAR:
+				pti++;
+				pto = var;
+
+				while (*pti > MSDP_ARRAY_CLOSE)
+				{
+					*pto++ = *pti++;
+				}
+				*pto = 0;
+				break;
+
+			case MSDP_VAL:
+				pti++;
+				pto = val;
+
+				while (*pti > MSDP_ARRAY_CLOSE)
+				{
+					*pto++ = *pti++;
+				}
+				*pto = 0;
+
+				if (!strcmp(var, "MUD_NAME"))
+				{
+					strcpy(mud_name, val);
+				}
+				else if (!strcmp(var, "MUD_HOST"))
+				{
+					strcpy(mud_host, val);
+				}
+				else if (!strcmp(var, "MUD_PORT"))
+				{
+					strcpy(mud_port, val);
+				}
+				else if (!strcmp(var, "MSG_USER"))
+				{
+					strcpy(msg_user, val);
+				}
+				else if (!strcmp(var, "MSG_TIME"))
+				{
+					timeval_t = (time_t) atoll(val);
+					timeval_tm = *localtime(&timeval_t);
+					
+					strftime(msg_time, 20, "%T %D", &timeval_tm);
+				}
+				else if (!strcmp(var, "MSG_BODY"))
+				{
+					strcpy(msg_body, val);
+				}
+				else if (!strcmp(var, "MUD_UPTIME"))
+				{
+					timeval_t = (time_t) atoll(val);
+					timeval_tm = *localtime(&timeval_t);
+					
+					strftime(mud_uptime, 20, "%T %D", &timeval_tm);
+				}
+				else if (!strcmp(var, "MUD_UPDATE"))
+				{
+					timeval_t = (time_t) atoll(val);
+					timeval_tm = *localtime(&timeval_t);
+					
+					strftime(mud_update, 20, "%T %D", &timeval_tm);
+				}
+				else if (!strcmp(var, "MUD_PLAYERS"))
+				{
+					strcpy(mud_players, val);
+				}
+				break;
+
+			default:
+				pti++;
+				break;
+		}
+	}
+
+	if (*mud_name && *mud_host && *mud_port)
+	{
+		if (!strcmp(msdp_table[index].name, "ARACHNOS_DEVEL"))
+		{
+			if (*msg_user && *msg_time && *msg_body)
+			{
+				arachnos_devel(ses, "%s %s@%s:%s devtalks: %s", msg_time, msg_user, mud_host, mud_port, msg_body);
+			}
+		}
+		else if (!strcmp(msdp_table[index].name, "ARACHNOS_MUDLIST"))
+		{
+			if (*mud_uptime && *mud_update && *mud_players)
+			{
+				arachnos_mudlist(ses, "%18.18s %14.14s %5.5s %17.17s %17.17s %4.4s", mud_name, mud_host, mud_port, mud_update, mud_uptime, mud_players);
+			}
+		}
+	}
+}
+
+struct msdp_type msdp_table[] =
+{
+	{    "ARACHNOS_DEVEL",            MSDP_FLAG_CONFIGURABLE|MSDP_FLAG_REPORTABLE,  PORT_RANK_SCOUT, msdp_configure_arachnos },
+	{    "ARACHNOS_MUDLIST",                               MSDP_FLAG_CONFIGURABLE,  PORT_RANK_SCOUT, msdp_configure_arachnos },
+	{    "COMMANDS",                             MSDP_FLAG_COMMAND|MSDP_FLAG_LIST,  PORT_RANK_SPY,   NULL },
+	{    "CONFIGURABLE_VARIABLES",          MSDP_FLAG_CONFIGURABLE|MSDP_FLAG_LIST,  PORT_RANK_SPY,   NULL },
+	{    "LIST",                                                MSDP_FLAG_COMMAND,  PORT_RANK_SPY,   msdp_command_list },
+	{    "LISTS",                                                  MSDP_FLAG_LIST,  PORT_RANK_SPY,   NULL },
+	{    "MAP_ROOM_INFO",             MSDP_FLAG_CONFIGURABLE|MSDP_FLAG_REPORTABLE,  PORT_RANK_SCOUT, msdp_configure_map },
+        {    "REPORT",                                              MSDP_FLAG_COMMAND,  PORT_RANK_SPY,   msdp_command_report },
+	{    "REPORTABLE_VARIABLES",              MSDP_FLAG_REPORTABLE|MSDP_FLAG_LIST,  PORT_RANK_SPY,   NULL },
+	{    "REPORTED_VARIABLES",                  MSDP_FLAG_REPORTED|MSDP_FLAG_LIST,  PORT_RANK_SPY,   NULL },
+	{    "RESET",                                               MSDP_FLAG_COMMAND,  PORT_RANK_SPY,   msdp_command_reset },
+	{    "SCREEN_CHARACTER_HEIGHT",       MSDP_FLAG_SENDABLE|MSDP_FLAG_REPORTABLE,  PORT_RANK_SPY,   NULL },
+	{    "SCREEN_CHARACTER_WIDTH",        MSDP_FLAG_SENDABLE|MSDP_FLAG_REPORTABLE,  PORT_RANK_SPY,   NULL },
+	{    "SCREEN_COLS",                   MSDP_FLAG_SENDABLE|MSDP_FLAG_REPORTABLE,  PORT_RANK_SPY,   NULL },
+	{    "SCREEN_FOCUS",                  MSDP_FLAG_SENDABLE|MSDP_FLAG_REPORTABLE,  PORT_RANK_SPY,   NULL },
+	{    "SCREEN_HEIGHT",                 MSDP_FLAG_SENDABLE|MSDP_FLAG_REPORTABLE,  PORT_RANK_SPY,   NULL },
+	{    "SCREEN_MINIMIZED",              MSDP_FLAG_SENDABLE|MSDP_FLAG_REPORTABLE,  PORT_RANK_SPY,   NULL },
+	{    "SCREEN_POSITION_HEIGHT",        MSDP_FLAG_SENDABLE|MSDP_FLAG_REPORTABLE,  PORT_RANK_SPY,   NULL },
+	{    "SCREEN_POSITION_WIDTH",         MSDP_FLAG_SENDABLE|MSDP_FLAG_REPORTABLE,  PORT_RANK_SPY,   NULL },
+	{    "SCREEN_ROWS",                   MSDP_FLAG_SENDABLE|MSDP_FLAG_REPORTABLE,  PORT_RANK_SPY,   NULL },
+	{    "SCREEN_WIDTH",                  MSDP_FLAG_SENDABLE|MSDP_FLAG_REPORTABLE,  PORT_RANK_SPY,   NULL },
+	{    "SEND",                                                MSDP_FLAG_COMMAND,  PORT_RANK_SPY,   msdp_command_send },
+	{    "SENDABLE_VARIABLES",                  MSDP_FLAG_SENDABLE|MSDP_FLAG_LIST,  PORT_RANK_SPY,   NULL },
+	{    "SPECIFICATION",                                      MSDP_FLAG_SENDABLE,  PORT_RANK_SPY,   NULL },
+	{    "UNREPORT",                                            MSDP_FLAG_COMMAND,  PORT_RANK_SPY,   msdp_command_unreport },
+	{    "",                                                                    0,              0,   NULL }
+};
+
+void write_msdp_to_descriptor(struct session *ses, struct port_data *buddy, char *src, int length)
+{
+	char out[STRING_SIZE];
+
+	if (!HAS_BIT(buddy->comm_flags, COMM_FLAG_GMCP))
+	{
+		port_telnet_printf(ses, buddy, length, "%s", src);
+	}
+	else
+	{
+		length = msdp2json((unsigned char *) src, length, out);
+
+		port_telnet_printf(ses, buddy, length, "%s", out);
+	}
+}
+
+int msdp2json(unsigned char *src, int srclen, char *out)
+{
+	char *pto;
+	int i, nest, last;
+
+	nest = last = 0;
+
+	pto = out;
+
+	if (src[2] == TELOPT_MSDP)
+	{
+		pto += sprintf(pto, "%c%c%cMSDP {", IAC, SB, TELOPT_GMCP);
+	}
+
+	i = 3;
+
+	while (i < srclen)
+	{
+		if (src[i] == IAC && src[i+1] == SE)
+		{
+			break;
+		}
+
+		switch (src[i])
+		{
+			case MSDP_TABLE_OPEN:
+				*pto++ = '{';
+				nest++;
+				last = MSDP_TABLE_OPEN;
+				break;
+
+			case MSDP_TABLE_CLOSE:
+				if (last == MSDP_VAL || last == MSDP_VAR)
+				{
+					*pto++ = '"';
+				}
+				if (nest)
+				{
+					nest--;
+				}
+				*pto++ = '}';
+				last = MSDP_TABLE_CLOSE;
+				break;
+
+			case MSDP_ARRAY_OPEN:
+				*pto++ = '[';
+				nest++;
+				last = MSDP_ARRAY_OPEN;
+				break;
+
+			case MSDP_ARRAY_CLOSE:
+				if (last == MSDP_VAL || last == MSDP_VAR)
+				{
+					*pto++ = '"';
+				}
+				if (nest)
+				{
+					nest--;
+				}
+				*pto++ = ']';
+				last = MSDP_ARRAY_CLOSE;
+				break;
+
+			case MSDP_VAR:
+				if (last == MSDP_VAL || last == MSDP_VAR)
+				{
+					*pto++ = '"';
+				}
+				if (last == MSDP_VAL || last == MSDP_VAR || last == MSDP_TABLE_CLOSE || last == MSDP_ARRAY_CLOSE)
+				{
+					*pto++ = ',';
+				}
+				*pto++ = '"';
+				last = MSDP_VAR;
+				break;
+
+			case MSDP_VAL:
+				if (last == MSDP_VAR)
+				{
+					*pto++ = '"';
+					*pto++ = ':';
+				}
+				if (last == MSDP_VAL)
+				{
+					*pto++ = '"';
+					*pto++ = ',';
+				}
+
+				if (src[i+1] != MSDP_TABLE_OPEN && src[i+1] != MSDP_ARRAY_OPEN)
+				{
+					*pto++ = '"';
+				}
+				last = MSDP_VAL;
+				break;
+
+			case '\\':
+				*pto++ = '\\';
+				*pto++ = '\\';
+				break;
+
+			case '"':
+				*pto++ = '\\';
+				*pto++ = '"';
+				break;
+
+			default:
+				*pto++ = src[i];
+				break;
+		}
+		i++;
+	}
+
+	pto += sprintf(pto, "}%c%c", IAC, SE);
+
+	return pto - out;
+}
+
+int json2msdp(unsigned char *src, int srclen, char *out)
+{
+	char *pto;
+	int i, nest, last, type, state[100];
+
+	nest = last = 0;
+
+	pto = out;
+
+	if (src[2] == TELOPT_GMCP)
+	{
+		pto += sprintf(pto, "%c%c%c", IAC, SB, TELOPT_MSDP);
+	}
+
+	i = 3;
+
+	if (!strncmp((char *) &src[3], "MSDP {", 6))
+	{
+		i += 6;
+	}
+
+	state[0] = nest = type = 0;
+
+	while (i < srclen && src[i] != IAC && nest < 99)
+	{
+		switch (src[i])
+		{
+			case ' ':
+				i++;
+				break;
+
+			case '{':
+				*pto++ = MSDP_TABLE_OPEN;
+				i++;
+				state[++nest] = 0;
+				break;
+
+			case '}':
+				nest--;
+				i++;
+				if (nest < 0)
+				{
+					pto += sprintf(pto, "%c%c", IAC, SE);
+					return pto - out;
+				}
+				*pto++ = MSDP_TABLE_CLOSE;
+				break;
+
+			case '[':
+				i++;
+				state[++nest] = 1;
+				*pto++ = MSDP_ARRAY_OPEN;
+				break;
+
+			case ']':
+				nest--;
+				i++;
+				*pto++ = MSDP_ARRAY_CLOSE;
+				break;
+
+			case ':':
+				*pto++ = MSDP_VAL;
+				i++;
+				break;
+
+			case ',':
+				i++;
+				if (state[nest])
+				{
+					*pto++ = MSDP_VAL;
+				}
+				else
+				{
+					*pto++ = MSDP_VAR;
+				}
+				break;
+
+			case '"':
+				i++;
+				if (last == 0)
+				{
+					last = MSDP_VAR;
+					*pto++ = MSDP_VAR;
+				}
+				type = 1;
+
+				while (i < srclen && src[i] != IAC && type)
+				{
+					switch (src[i])
+					{
+						case '\\':
+							i++;
+
+							if (i < srclen && src[i] == '"')
+							{
+								*pto++ = src[i++];
+							}
+							else
+							{
+								*pto++ = '\\';
+							}
+							break;
+
+						case '"':
+							i++;
+							type = 0;
+							break;
+
+						default:
+							*pto++ = src[i++];
+							break;
+					}
+				}
+				break;
+
+			default:
+				type = 1;
+
+				while (i < srclen && src[i] != IAC && type)
+				{
+					switch (src[i])
+					{
+						case '}':
+						case ']':
+						case ',':
+						case ':':
+							type = 0;
+							break;
+
+						case ' ':
+							i++;
+							break;
+
+						default:
+							*pto++ = src[i++];
+							break;
+					}
+				}
+				break;
+		}
+	}
+	pto += sprintf(pto, "%c%c", IAC, SE);
+
+	return pto - out;
+}
+

+ 1197 - 0
nest.c

@@ -0,0 +1,1197 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2009                       *
+******************************************************************************/
+
+#include "tintin.h"
+
+struct listroot *search_nest_root(struct listroot *root, char *arg)
+{
+	struct listnode *node;
+
+	node = search_node_list(root, arg);
+
+	if (node == NULL || node->root == NULL)
+	{
+		return NULL;
+	}
+	return node->root;
+}
+
+struct listroot *search_nest_base_ses(struct session *ses, char *arg)
+{
+	struct listnode *node;
+	struct listroot *root;
+
+	int index;
+
+	if (HAS_BIT(gtd->flags, TINTIN_FLAG_LOCAL))
+	{
+		for (index = gtd->script_index ; index ; index--)
+		{
+			root = gtd->script_stack[index]->local;
+
+			if (root->used)
+			{
+				node = search_node_list(root, arg);
+
+				if (node)
+				{
+					return root;
+				}
+			}
+		}
+	}
+
+	node = search_node_list(ses->list[LIST_VARIABLE], arg);
+
+	if (node == NULL)
+	{
+		return NULL;
+	}
+	return ses->list[LIST_VARIABLE];
+}
+
+struct listnode *search_base_node(struct listroot *root, char *variable)
+{
+	char name[BUFFER_SIZE];
+
+	get_arg_to_brackets(root->ses, variable, name);
+
+	return search_node_list(root, name);
+}
+
+struct listnode *search_nest_node_ses(struct session *ses, char *variable)
+{
+	char name[BUFFER_SIZE], *arg;
+	struct listroot *root;
+	struct listnode *node;
+	int index;
+
+	if (HAS_BIT(gtd->flags, TINTIN_FLAG_LOCAL))
+	{
+		for (index = gtd->script_index ; index ; index--)
+		{
+			root = gtd->script_stack[index]->local;
+
+			if (root->used)
+			{
+				arg = get_arg_to_brackets(root->ses, variable, name);
+
+				while (root && *arg)
+				{
+					root = search_nest_root(root, name);
+
+					if (root)
+					{
+						arg = get_arg_in_brackets(root->ses, arg, name);
+					}
+				}
+
+				if (root)
+				{
+					node = search_node_list(root, name);
+
+					if (node)
+					{
+						return node;
+					}
+				}
+			}
+		}
+	}
+
+	root = ses->list[LIST_VARIABLE];
+
+	arg = get_arg_to_brackets(ses, variable, name);
+
+	while (root && *arg)
+	{
+		root = search_nest_root(root, name);
+
+		if (root)
+		{
+			arg = get_arg_in_brackets(root->ses, arg, name);
+		}
+	}
+
+	if (root)
+	{
+		return search_node_list(root, name);
+	}
+
+	return NULL;
+}
+
+	
+struct listnode *search_nest_node(struct listroot *root, char *variable)
+{
+	char name[BUFFER_SIZE], *arg;
+
+	arg = get_arg_to_brackets(root->ses, variable, name);
+
+	while (root && *arg)
+	{
+		root = search_nest_root(root, name);
+
+		if (root)
+		{
+			arg = get_arg_in_brackets(root->ses, arg, name);
+		}
+	}
+
+	if (root)
+	{
+		return search_node_list(root, name);
+	}
+
+	return NULL;
+}
+
+int search_nest_index(struct listroot *root, char *variable)
+{
+	char name[BUFFER_SIZE], *arg;
+
+	arg = get_arg_to_brackets(root->ses, variable, name);
+
+	while (root && *arg)
+	{
+		root = search_nest_root(root, name);
+
+		if (root)
+		{
+			arg = get_arg_in_brackets(root->ses, arg, name);
+		}
+	}
+
+	if (root)
+	{
+		return search_index_list(root, name, NULL);
+	}
+
+	return -1;
+}
+
+struct listroot *update_nest_root(struct listroot *root, char *arg)
+{
+	struct listnode *node;
+
+	node = search_node_list(root, arg);
+
+	if (node == NULL)
+	{
+		node = update_node_list(root, arg, "", "", "");
+	}
+
+	if (node->root == NULL)
+	{
+		node->root = init_list(root->ses, root->type, LIST_SIZE);
+	}
+
+	return node->root;
+}
+
+void update_nest_node(struct listroot *root, char *arg)
+{
+//	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+
+	char *arg1, *arg2;
+
+	arg1 = str_mim(arg);
+	arg2 = str_mim(arg);
+
+	while (*arg)
+	{
+		arg = get_arg_in_braces(root->ses, arg, arg1, GET_ONE);
+		arg = get_arg_in_braces(root->ses, arg, arg2, GET_ONE);
+
+		if (*arg2 == DEFAULT_OPEN)
+		{
+			update_nest_node(update_nest_root(root, arg1), arg2);
+		}
+		else if (*arg1)
+		{
+			update_node_list(root, arg1, arg2, "", "");
+		}
+
+		if (*arg == COMMAND_SEPARATOR)
+		{
+			arg++;
+		}
+	}
+	str_free(arg1);
+	str_free(arg2);
+
+	return;
+}
+
+int delete_nest_node(struct listroot *root, char *variable)
+{
+	char name[BUFFER_SIZE], *arg;
+	int index;
+
+	arg = get_arg_to_brackets(root->ses, variable, name);
+
+	while (root && *arg)
+	{
+		root = search_nest_root(root, name);
+
+		if (root)
+		{
+			arg = get_arg_in_brackets(root->ses, arg, name);
+		}
+	}
+
+	if (root)
+	{
+		index = search_index_list(root, name, NULL);
+
+		if (index != -1)
+		{
+			delete_index_list(root, index);
+
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+
+// Return the number of indices of a node.
+
+int get_nest_size(struct listroot *root, char *variable)
+{
+	char name[BUFFER_SIZE], *arg;
+	int index, count;
+	arg = get_arg_to_brackets(root->ses, variable, name);
+
+	if (!strcmp(arg, "[]"))
+	{
+		if (*name == 0)
+		{
+			return root->used + 1;
+		}
+
+		if (search_nest_root(root, name) == NULL)
+		{
+			if (search_node_list(root, name))
+			{
+				return 1;
+			}
+		}
+	}
+
+	while (root && *name)
+	{
+		// Handle regex queries
+
+		if (search_nest_root(root, name) == NULL)
+		{
+			if (search_node_list(root, name) == NULL)
+			{
+				if (tintin_regexp_check(root->ses, name))
+				{
+					for (index = count = 0 ; index < root->used ; index++)
+					{
+						if (match(root->ses, root->list[index]->arg1, name, SUB_NONE))
+						{
+							count++;
+						}
+					}
+					return count + 1;
+				}
+				else if (strstr(name, "..") && is_math(root->ses, name))
+				{
+					int min, max, range;
+
+					if (root->used)
+					{
+						range = get_ellipsis(root, name, &min, &max);
+
+						return range + 1;
+					}
+					else
+					{
+						return 1;
+					}
+				}
+				else
+				{
+					return 0;
+				}
+			}
+		}
+
+		root = search_nest_root(root, name);
+
+		if (root)
+		{
+			if (!strcmp(arg, "[]"))
+			{
+				return root->used + 1;
+			}
+			arg = get_arg_in_brackets(root->ses, arg, name);
+		}
+	}
+
+	return 0;
+}
+
+int get_nest_size_index(struct listroot *root, char *variable, char **result)
+{
+	char name[BUFFER_SIZE], *arg;
+	int index, count;
+
+	arg = get_arg_to_brackets(root->ses, variable, name);
+
+	str_cpy(result, "");
+
+	if (!strcmp(arg, "[]"))
+	{
+		if (*name == 0)
+		{
+			return root->used + 1;
+		}
+
+		if (search_nest_root(root, name) == NULL)
+		{
+			if (search_node_list(root, name))
+			{
+				return 1;
+			}
+		}
+	}
+
+	while (root && *name)
+	{
+		// Handle regex queries
+
+		if (search_nest_root(root, name) == NULL)
+		{
+			if (search_node_list(root, name) == NULL)
+			{
+				if (tintin_regexp_check(root->ses, name))
+				{
+					for (index = count = 0 ; index < root->used ; index++)
+					{
+						if (match(root->ses, root->list[index]->arg1, name, SUB_NONE))
+						{
+							count++;
+						}
+					}
+					return count + 1;
+				}
+				else if (strstr(name, "..") && is_math(root->ses, name))
+				{
+					int min, max, range;
+
+					if (root->used)
+					{
+						range = get_ellipsis(root, name, &min, &max);
+
+						return range + 1;
+					}
+					else
+					{
+						return 1;
+					}
+				}
+				else
+				{
+					return 0;
+				}
+			}
+		}
+
+		root = search_nest_root(root, name);
+
+		if (root)
+		{
+			if (!strcmp(arg, "[]"))
+			{
+				return root->used + 1;
+			}
+			arg = get_arg_in_brackets(root->ses, arg, name);
+		}
+	}
+
+	return 0;
+}
+
+int get_nest_size_key(struct listroot *root, char *variable, char **result)
+{
+	char name[BUFFER_SIZE], *arg;
+	int index, count;
+
+	arg = get_arg_to_brackets(root->ses, variable, name);
+
+	str_cpy(result, "");
+
+	if (!strcmp(arg, "[]"))
+	{
+		if (*name == 0)
+		{
+			for (index = 0 ; index < root->used ; index++)
+			{
+				str_cat_printf(result, "{%s}", root->list[index]->arg1);
+			}
+			return root->used + 1;
+		}
+
+		if (search_nest_root(root, name) == NULL)
+		{
+			if (search_node_list(root, name))
+			{
+				return 1;
+			}
+		}
+	}
+
+	while (root && *name)
+	{
+		// Handle regex queries
+
+		if (search_nest_root(root, name) == NULL)
+		{
+			if (search_node_list(root, name) == NULL)
+			{
+				if (tintin_regexp_check(root->ses, name))
+				{
+					for (index = count = 0 ; index < root->used ; index++)
+					{
+						if (match(root->ses, root->list[index]->arg1, name, SUB_NONE))
+						{
+							str_cat_printf(result, "{%s}", root->list[index]->arg1);
+							count++;
+						}
+					}
+					return count + 1;
+				}
+				else if (strstr(name, "..") && is_math(root->ses, name))
+				{
+					int min, max, range;
+
+					if (root->used)
+					{
+						range = get_ellipsis(root, name, &min, &max);
+
+						if (min < max)
+						{
+							while (min <= max)
+							{
+								str_cat_printf(result, "{%s}", root->list[min++]->arg1);
+							}
+						}
+						else
+						{
+							while (min >= max)
+							{
+								str_cat_printf(result, "{%s}", root->list[min--]->arg1);
+							}
+						}
+						return range + 1;
+					}
+					else
+					{
+						return 1;
+					}
+				}
+				else
+				{
+					return 0;
+				}
+			}
+		}
+
+		root = search_nest_root(root, name);
+
+		if (root)
+		{
+			if (!strcmp(arg, "[]"))
+			{
+				for (index = 0 ; index < root->used ; index++)
+				{
+					str_cat_printf(result, "{%s}", root->list[index]->arg1);
+				}
+				return root->used + 1;
+			}
+			arg = get_arg_in_brackets(root->ses, arg, name);
+		}
+	}
+
+	return 0;
+}
+
+int get_nest_size_val(struct listroot *root, char *variable, char **result)
+{
+	char name[BUFFER_SIZE], *arg;
+	int index, count;
+	static int warning;
+
+	arg = get_arg_to_brackets(root->ses, variable, name);
+
+	str_cpy(result, "");
+
+	if (!strcmp(arg, "[]"))
+	{
+		if (*name == 0)
+		{
+			for (index = 0 ; index < root->used ; index++)
+			{
+				str_cat_printf(result, "{%s}", root->list[index]->arg1);
+			}
+			return root->used + 1;
+		}
+
+		if (search_nest_root(root, name) == NULL)
+		{
+			if (search_node_list(root, name))
+			{
+				return 1;
+			}
+		}
+	}
+
+	while (root && *name)
+	{
+		// Handle regex queries
+
+		if (search_nest_root(root, name) == NULL)
+		{
+			if (search_node_list(root, name) == NULL)
+			{
+				if (tintin_regexp_check(root->ses, name))
+				{
+					for (index = count = 0 ; index < root->used ; index++)
+					{
+						if (match(root->ses, root->list[index]->arg1, name, SUB_NONE))
+						{
+							show_nest_node(root->list[index], result, FALSE); // behaves like strcat
+							count++;
+						}
+					}
+					return count + 1;
+				}
+				else if (strstr(name, "..") && is_math(root->ses, name))
+				{
+					int min, max, range;
+
+					if (root->used)
+					{
+						range = get_ellipsis(root, name, &min, &max);
+
+						if (min < max)
+						{
+							while (min <= max)
+							{
+								show_nest_node(root->list[min++], result, FALSE);
+							}
+						}
+						else
+						{
+							while (min >= max)
+							{
+								show_nest_node(root->list[min--], result, FALSE);
+							}
+						}
+						return range + 1;
+					}
+					else
+					{
+						return 1;
+					}
+				}
+				else
+				{
+					return 0;
+				}
+			}
+		}
+
+		root = search_nest_root(root, name);
+
+		if (root)
+		{
+			if (!strcmp(arg, "[]"))
+			{
+				if (++warning < 100)
+				{
+					tintin_printf2(root->ses, "\n\e[1;5;31mdebug: please use *%s instead of $%s.\n", variable, variable);
+				}
+
+				for (index = 0 ; index < root->used ; index++)
+				{
+					str_cat_printf(result, "{%s}", root->list[index]->arg1);
+				}
+				return root->used + 1;
+			}
+			arg = get_arg_in_brackets(root->ses, arg, name);
+		}
+	}
+
+	return 0;
+}
+
+
+struct listnode *get_nest_node_key(struct listroot *root, char *variable, char **result, int def)
+{
+	struct listnode *node;
+	int size;
+
+	size = get_nest_size_key(root, variable, result); // will copy keys to result
+
+	if (size)
+	{
+		return NULL;
+	}
+
+	node = search_nest_node(root, variable);
+
+	if (node)
+	{
+		str_cpy_printf(result, "%s", node->arg1);
+
+		if (HAS_BIT(node->flags, NODE_FLAG_ONESHOT))
+		{
+			delete_nest_node(root, variable);
+		}
+		return node;
+	}
+
+	node = search_base_node(root, variable);
+
+	if (node || def)
+	{
+		str_cpy(result, "");
+	}
+	else
+	{
+		str_cpy_printf(result, "*%s", variable);
+	}
+	return NULL;
+}
+
+// Has ONESHOT check
+
+struct listnode *get_nest_node_val(struct listroot *root, char *variable, char **result, int def)
+{
+	struct listnode *node;
+	int size;
+
+	size = get_nest_size_val(root, variable, result);
+
+	if (size)
+	{
+		return NULL;
+	}
+
+	node = search_nest_node(root, variable);
+
+	if (node)
+	{
+		show_nest_node(node, result, TRUE);
+
+		if (HAS_BIT(node->flags, NODE_FLAG_ONESHOT))
+		{
+			delete_nest_node(root, variable);
+		}
+		return node;
+	}
+
+	node = search_base_node(root, variable);
+
+	if (node || def)
+	{
+		str_cpy(result, "");
+	}
+	else
+	{
+		str_cpy_printf(result, "$%s", variable);
+	}
+	return NULL;
+}
+
+
+int get_nest_index(struct listroot *root, char *variable, char **result, int def)
+{
+	struct listnode *node;
+	int index, size;
+
+	size = get_nest_size_index(root, variable, result);
+
+	if (size)
+	{
+		str_cpy_printf(result, "%d", size - 1);
+
+		return -1;
+	}
+
+	node = search_nest_node(root, variable);
+	index = search_nest_index(root, variable);
+
+	if (node && index >= 0)
+	{
+		str_cpy_printf(result, "%d", index + 1);
+
+		if (HAS_BIT(node->flags, NODE_FLAG_ONESHOT))
+		{
+			delete_index_list(root, index);
+		}
+		return index;
+	}
+
+	node = search_base_node(root, variable);
+
+	if (node || def)
+	{
+		str_cpy(result, "0");
+	}
+	else
+	{
+		str_cpy_printf(result, "&%s", variable);
+	}
+	return -1;
+}
+
+// cats to result when initialize is 0
+
+void show_nest_node(struct listnode *node, char **str_result, int initialize)
+{
+	if (initialize)
+	{
+		str_cpy(str_result, "");
+	}
+
+	if (node->root == NULL)
+	{
+		if (initialize)
+		{
+			str_cat(str_result, node->arg2);
+		}
+		else
+		{
+			str_cat_printf(str_result, "{%s}", node->arg2);
+		}
+	}
+	else
+	{
+		struct listroot *root = node->root;
+		int i;
+
+		if (!initialize)
+		{
+			str_cat(str_result, "{");
+		}
+
+		for (i = 0 ; i < root->used ; i++)
+		{
+			str_cat_printf(str_result, "{%s}", root->list[i]->arg1);
+
+			show_nest_node(root->list[i], str_result, FALSE);
+		}
+
+		if (!initialize)
+		{
+			str_cat(str_result, "}");
+		}
+	}
+}
+
+void view_nest_node(struct listnode *node, char **str_result, int nest, int initialize)
+{
+	if (initialize == TRUE)
+	{
+		str_cpy(str_result, "");
+	}
+
+	if (node->root == NULL)
+	{
+		if (initialize)
+		{
+			str_cat(str_result, node->arg2);
+		}
+		else
+		{
+			str_cat_printf(str_result, COLOR_BRACE "{" COLOR_STRING "%s" COLOR_BRACE "}\n", node->arg2);
+		}
+	}
+	else
+	{
+		struct listroot *root = node->root;
+		int i;
+
+		if (initialize == FALSE)
+		{
+			str_cat_printf(str_result, "\n" COLOR_BRACE "%s{\n", indent(nest));
+		}
+
+		nest++;
+
+		for (i = 0 ; i < root->used ; i++)
+		{
+			str_cat_printf(str_result, COLOR_BRACE "%s{" COLOR_STRING "%s" COLOR_BRACE "} ", indent(nest), root->list[i]->arg1);
+
+			view_nest_node(root->list[i], str_result, nest, FALSE);
+		}
+
+		nest--;
+
+		if (initialize == FALSE)
+		{
+			str_cat_printf(str_result, COLOR_BRACE "%s}\n", indent(nest), "");
+		}
+	}
+}
+
+struct listnode *set_nest_node_ses(struct session *ses, char *arg1, char *format, ...)
+{
+	struct listnode *node;
+	struct listroot *root;
+	char *arg, *arg2, name[BUFFER_SIZE];
+	va_list args;
+
+	push_call("set_nest_node_ses(%p,%s,%p,...)",ses,arg1,format);
+
+	va_start(args, format);
+	vasprintf(&arg2, format, args);
+	va_end(args);
+
+	arg = get_arg_to_brackets(ses, arg1, name);
+
+	check_all_events(ses, SUB_ARG, 1, 2, "VARIABLE UPDATE %s", name, name, arg2);
+
+	root = search_nest_base_ses(ses, name);
+
+	if (root == NULL)
+	{
+		root = ses->list[LIST_VARIABLE];
+		node = NULL;
+	}
+	else
+	{
+		node = search_nest_node(root, arg1);
+	}
+
+	while (*arg)
+	{
+		root = update_nest_root(root, name);
+
+		if (root)
+		{
+			arg = get_arg_in_brackets(root->ses, arg, name);
+		}
+	}
+
+	node = search_node_list(root, name);
+
+	if (node && node->root)
+	{
+		free_list(node->root);
+
+		node->root = NULL;
+	}
+
+	if (*space_out(arg2) == DEFAULT_OPEN)
+	{
+		update_nest_node(update_nest_root(root, name), arg2);
+
+		node = search_node_list(root, name);
+	}
+	else if (node)
+	{
+		str_cpy(&node->arg2, arg2);
+	}
+	else
+	{
+		node = update_node_list(root, name, arg2, "", "");
+	}
+
+	if (gtd->level->oneshot)
+	{
+		SET_BIT(node->flags, NODE_FLAG_ONESHOT);
+	}
+
+	check_all_events(root->ses, SUB_ARG, 1, 1, "VARIABLE UPDATED %s", name, name, arg2);
+
+	free(arg2);
+
+	pop_call();
+	return node;
+}
+
+// like set, but we're adding here.
+
+struct listnode *add_nest_node_ses(struct session *ses, char *arg1, char *format, ...)
+{
+	struct listnode *node;
+	struct listroot *root;
+	char *arg, *arg2, name[BUFFER_SIZE];
+	va_list args;
+
+	push_call("add_nest_node_ses(%p,%s,%p,...)",ses,arg1,format);
+
+	va_start(args, format);
+	vasprintf(&arg2, format, args);
+	va_end(args);
+
+	arg = get_arg_to_brackets(ses, arg1, name);
+
+	check_all_events(ses, SUB_ARG, 1, 2, "VARIABLE UPDATE %s", name, name, arg2);
+
+	root = search_nest_base_ses(ses, name);
+
+	if (root == NULL)
+	{
+		root = ses->list[LIST_VARIABLE];
+		node = NULL;
+	}
+	else
+	{
+		node = search_nest_node(root, arg1);
+	}
+
+	while (*arg)
+	{
+		root = update_nest_root(root, name);
+
+		if (root)
+		{
+			arg = get_arg_in_brackets(root->ses, arg, name);
+		}
+	}
+
+	node = search_node_list(root, name);
+
+/*
+	if (node && node->root)
+	{
+		free_list(node->root);
+
+		node->root = NULL;
+	}
+*/
+
+	if (*space_out(arg2) == DEFAULT_OPEN)
+	{
+		update_nest_node(update_nest_root(root, name), arg2);
+
+		node = search_node_list(root, name);
+	}
+	else if (node)
+	{
+		str_cpy(&node->arg2, arg2);
+	}
+	else
+	{
+		node = update_node_list(root, name, arg2, "", "");
+	}
+
+	if (gtd->level->oneshot)
+	{
+		SET_BIT(node->flags, NODE_FLAG_ONESHOT);
+	}
+
+	check_all_events(root->ses, SUB_ARG, 1, 1, "VARIABLE UPDATED %s", name, name, arg2);
+
+	free(arg2);
+
+	pop_call();
+	return node;
+}
+
+
+struct listnode *set_nest_node(struct listroot *root, char *arg1, char *format, ...)
+{
+	struct listroot *base;
+	struct listnode *node;
+	char *arg, *arg2, name[BUFFER_SIZE];
+	va_list args;
+
+	push_call("set_nest_node(%p,%s,%p,...)",root,arg1,format);
+
+	va_start(args, format);
+	vasprintf(&arg2, format, args);
+	va_end(args);
+
+	arg = get_arg_to_brackets(root->ses, arg1, name);
+
+	check_all_events(root->ses, SUB_ARG, 1, 2, "VARIABLE UPDATE %s", name, name, arg2);
+
+	if (HAS_BIT(gtd->flags, TINTIN_FLAG_LOCAL))
+	{
+		base = search_nest_base_ses(root->ses, name);
+
+		if (base)
+		{
+			root = base;
+		}
+	}
+
+	while (*arg)
+	{
+		root = update_nest_root(root, name);
+
+		if (root)
+		{
+			arg = get_arg_in_brackets(root->ses, arg, name);
+		}
+	}
+
+	node = search_node_list(root, name);
+
+	if (node && node->root)
+	{
+		free_list(node->root);
+
+		node->root = NULL;
+	}
+
+	if (*space_out(arg2) == DEFAULT_OPEN)
+	{
+		update_nest_node(update_nest_root(root, name), arg2);
+
+		node = search_node_list(root, name);
+	}
+	else if (node)
+	{
+		str_cpy(&node->arg2, arg2);
+	}
+	else
+	{
+		node = update_node_list(root, name, arg2, "", "");
+	}
+
+	if (gtd->level->oneshot)
+	{
+		SET_BIT(node->flags, NODE_FLAG_ONESHOT);
+	}
+
+	check_all_events(root->ses, SUB_ARG, 1, 1, "VARIABLE UPDATED %s", name, name, arg2);
+
+	free(arg2);
+
+	pop_call();
+	return node;
+}
+
+// Like set, but don't erase old data.
+
+struct listnode *add_nest_node(struct listroot *root, char *arg1, char *format, ...)
+{
+	struct listroot *base;
+	struct listnode *node;
+	char *arg, *arg2, name[BUFFER_SIZE];
+	va_list args;
+
+	push_call("add_nest_node(%p,%s,%p,...)",root,arg1,format);
+
+	va_start(args, format);
+	vasprintf(&arg2, format, args);
+	va_end(args);
+
+	arg = get_arg_to_brackets(root->ses, arg1, name);
+
+	check_all_events(root->ses, SUB_ARG, 1, 2, "VARIABLE UPDATE %s", name, name, arg2);
+
+	if (HAS_BIT(gtd->flags, TINTIN_FLAG_LOCAL))
+	{
+		base = search_nest_base_ses(root->ses, name);
+
+		if (base)
+		{
+			root = base;
+		}
+	}
+
+	while (*arg)
+	{
+		root = update_nest_root(root, name);
+
+		if (root)
+		{
+			arg = get_arg_in_brackets(root->ses, arg, name);
+		}
+	}
+
+	node = search_node_list(root, name);
+/*
+	if (node && node->root)
+	{
+		free_list(node->root);
+
+		node->root = NULL;
+	}
+*/
+
+	if (*space_out(arg2) == DEFAULT_OPEN)
+	{
+		root = update_nest_root(root, name);
+
+		update_nest_node(root, arg2);
+
+		node = search_node_list(root, name);
+	}
+	else if (node)
+	{
+		str_cat(&node->arg2, arg2);
+	}
+	else
+	{
+		node = update_node_list(root, name, arg2, "", "");
+	}
+
+	if (gtd->level->oneshot)
+	{
+		SET_BIT(node->flags, NODE_FLAG_ONESHOT);
+	}
+
+	check_all_events(root->ses, SUB_ARG, 1, 1, "VARIABLE UPDATED %s", name, name, arg2);
+
+	free(arg2);
+
+	pop_call();
+	return node;
+}
+
+
+void copy_nest_node(struct listroot *dst_root, struct listnode *dst, struct listnode *src)
+{
+	int index;
+
+	if (src->root == NULL)
+	{
+		return;
+	}
+
+	dst_root = dst->root = init_list(dst_root->ses, dst_root->type, src->root->size);
+
+	for (index = 0 ; index < src->root->used ; index++)
+	{
+		dst = insert_node_list(dst_root, src->root->list[index]->arg1, src->root->list[index]->arg2, src->root->list[index]->arg3, src->root->list[index]->arg4);
+
+		if (src->root->list[index]->root)
+		{
+			copy_nest_node(dst_root, dst, src->root->list[index]);
+		}
+	}
+}

+ 527 - 0
net.c

@@ -0,0 +1,527 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2019 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
+*                                                                             *
+*                         coded by Peter Unold 1992                           *
+******************************************************************************/
+
+
+#include "tintin.h"
+
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <signal.h>
+#include <sys/types.h>
+
+/*
+	IPv6 compatible connect code.
+*/
+
+#ifdef HAVE_GETADDRINFO
+
+int connect_mud(struct session *ses, char *host, char *port)
+{
+	int sock, error;
+	struct addrinfo *address;
+	static struct addrinfo hints;
+	char ip[100];
+
+	if (!is_number(port))
+	{
+		tintin_puts(ses, "#THE PORT SHOULD BE A NUMBER.");
+		return -1;
+	}
+
+//	hints.ai_family   = AF_UNSPEC;
+	hints.ai_family   = AF_INET;
+	hints.ai_protocol = IPPROTO_TCP;
+	hints.ai_socktype = SOCK_STREAM;
+
+	error = getaddrinfo(host, port, &hints, &address);
+
+	if (error)
+	{
+		hints.ai_family = AF_INET6;
+
+		error = getaddrinfo(host, port, &hints, &address);
+
+		if (error)
+		{
+			tintin_printf2(ses, "#SESSION '%s' COULD NOT CONNECT - UNKNOWN HOST.", ses->name);
+
+			return -1;
+		}
+	}
+
+	sock = socket(address->ai_family, address->ai_socktype, address->ai_protocol);
+
+	if (sock < 0)
+	{
+		syserr_printf(ses, "connect_mud: socket");
+
+		freeaddrinfo(address);
+
+		return -1;
+	}
+
+//	fcntl(sock, F_SETFL, O_NDELAY);
+
+        ses->connect_error = connect(sock, address->ai_addr, address->ai_addrlen);
+
+        if (ses->connect_error)
+        {
+                syserr_printf(ses, "connect_mud: connect");
+
+                close(sock);
+
+                freeaddrinfo(address);
+
+                return -1;
+        }
+
+	if (fcntl(sock, F_SETFL, O_NDELAY|O_NONBLOCK) == -1)
+	{
+		syserr_printf(ses, "connect_mud: fcntl O_NDELAY|O_NONBLOCK");
+
+		close(sock);
+
+		freeaddrinfo(address);
+
+		return -1;
+	}
+
+	error = getnameinfo(address->ai_addr, address->ai_addrlen, ip, 100, NULL, 0, NI_NUMERICHOST);
+
+	if (error)
+	{
+		syserr_printf(ses, "connect_mud: getnameinfo:");
+	}
+	else
+	{
+		RESTRING(ses->session_ip, ip);
+	}
+
+	freeaddrinfo(address);
+
+	return sock;
+}
+
+#else
+
+int connect_mud(struct session *ses, char *host, char *port)
+{
+	int sock, d;
+	struct sockaddr_in sockaddr;
+
+	print_stdout("debug: NO ADDRESS INFO?\n");
+
+	if (sscanf(host, "%d.%d.%d.%d", &d, &d, &d, &d) == 4)
+	{
+		sockaddr.sin_addr.s_addr = inet_addr(host);
+	}
+	else
+	{
+		struct hostent *hp;
+
+		if (!(hp = gethostbyname(host)))
+		{
+			tintin_puts2(ses, "#ERROR - UNKNOWN HOST.");
+
+			return -1;
+		}
+		memcpy((char *)&sockaddr.sin_addr, hp->h_addr, sizeof(sockaddr.sin_addr));
+	}
+
+	if (is_number(port))
+	{
+		sockaddr.sin_port = htons(atoi(port));
+	}
+	else
+	{
+		tintin_puts(ses, "#THE PORT SHOULD BE A NUMBER.");
+		return -1;
+	}
+
+	if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
+	{
+		syserr_printf(ses, "old_connect_mud: socket()");
+
+		return -1;
+	}
+
+	sockaddr.sin_family = AF_INET;
+
+	ses->connect_error = connect(sock, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
+
+	if (ses->connect_error)
+	{
+		syserr_printf(ses, "connect_mud: connect");
+
+		close(sock);
+
+		return 0;
+	}
+
+	if (fcntl(sock, F_SETFL, O_NDELAY|O_NONBLOCK) == -1)
+	{
+		syserr_printf(ses, "connect_mud: fcntl O_NDELAY|O_NONBLOCK");
+	}
+
+	RESTRING(ses->session_ip, inet_ntoa(sockaddr.sin_addr));
+
+	return sock;
+}
+
+#endif
+
+void write_line_mud(struct session *ses, char *line, int size)
+{
+	int result;
+
+	push_call("write_line_mud(%p,%p)",line,ses);
+
+	if (ses == gts)
+	{
+		if (HAS_BIT(gtd->flags, TINTIN_FLAG_CHILDLOCK))
+		{
+			tintin_printf2(ses, "#THIS SESSION IS CHILD LOCKED, PRESS CTRL-D TO EXIT.");
+		}
+		else
+		{
+			tintin_printf2(ses, "#NO SESSION ACTIVE. USE: %csession {name} {host} {port} TO START ONE.", gtd->tintin_char);
+		}
+		pop_call();
+		return;
+	}
+
+	if (!HAS_BIT(ses->flags, SES_FLAG_CONNECTED))
+	{
+		tintin_printf2(ses, "#THIS SESSION IS NOT CONNECTED, CANNOT SEND: %s", line);
+
+		pop_call();
+		return;
+	}
+
+	if (!HAS_BIT(ses->telopts, TELOPT_FLAG_TELNET) && HAS_BIT(ses->charset, CHARSET_FLAG_ALL_TOUTF8))
+	{
+		char buf[BUFFER_SIZE];
+
+		size = utf8_to_all(ses, line, buf);
+
+		strcpy(line, buf);
+	}
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 0, 2, "SEND OUTPUT", line, ntos(size));
+
+	if (!check_all_events(ses, SUB_ARG|SUB_SEC, 0, 2, "CATCH SEND OUTPUT", line, ntos(size)))
+	{
+		if (ses->mccp3)
+		{
+			result = client_write_compressed(ses, line, size);
+		}
+#ifdef HAVE_GNUTLS_H
+		else if (ses->ssl)
+		{
+			result = gnutls_record_send(ses->ssl, line, size);
+
+			while (result == GNUTLS_E_INTERRUPTED || result == GNUTLS_E_AGAIN)
+			{
+				result = gnutls_record_send(ses->ssl, 0, 0);
+			}
+		}
+#endif
+		else
+		{
+			result = write(ses->socket, line, size);
+
+			if (result == -1)
+			{
+				syserr_printf(ses, "write_line_mud: write");
+			}
+		}
+		
+		if (result == -1)
+		{
+			cleanup_session(ses);
+
+			pop_call();
+			return;
+		}
+	}
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 0, 2, "SENT OUTPUT", line, ntos(size));
+
+	pop_call();
+	return;
+}
+
+
+int read_buffer_mud(struct session *ses)
+{
+	unsigned char buffer[BUFFER_SIZE];
+	int size;
+
+	push_call("read_buffer_mud(%p)",ses);
+
+#ifdef HAVE_GNUTLS_H
+
+	if (ses->ssl)
+	{
+		do
+		{
+			size = gnutls_record_recv(ses->ssl, buffer, BUFFER_SIZE - 1);
+		}
+		while (size == GNUTLS_E_INTERRUPTED || size == GNUTLS_E_AGAIN);
+
+		if (size < 0)
+		{
+			tintin_printf2(ses, "#SSL ERROR: %s", gnutls_strerror(size));
+		}
+	}
+	else
+#endif
+	size = read(ses->socket, buffer, BUFFER_SIZE - 1);
+	
+	if (size <= 0)
+	{
+		pop_call();
+		return FALSE;
+	}
+	ses->read_len = client_translate_telopts(ses, buffer, size);
+
+	pop_call();
+	return TRUE;
+}
+
+
+void readmud(struct session *ses)
+{
+	char *line, *next_line;
+	char linebuf[BUFFER_SIZE];
+	struct session *cts;
+
+	push_call("readmud(%p)", ses);
+
+	if (gtd->mud_output_len < BUFFER_SIZE)
+	{
+		check_all_events(ses, SUB_ARG|SUB_SEC, 0, 1, "RECEIVED OUTPUT", gtd->mud_output_buf);
+	}
+
+	gtd->mud_output_len = 0;
+
+	/* separate into lines and print away */
+
+	// cts = current tintin session, may have to make this global to avoid glitches
+
+	cts = gtd->ses;
+
+	if (HAS_BIT(gtd->ses->flags, SES_FLAG_SPLIT))
+	{
+		save_pos(gtd->ses);
+
+		goto_pos(gtd->ses, gtd->ses->split->bot_row, 1);
+	}
+
+	SET_BIT(cts->flags, SES_FLAG_READMUD);
+
+	for (line = gtd->mud_output_buf ; line && *line ; line = next_line)
+	{
+		next_line = strchr(line, '\n');
+
+		if (next_line)
+		{
+			if (next_line - line >= BUFFER_SIZE / 3)
+			{
+				// This is not ideal, but being a rare case it'll suffice for now
+
+				next_line = &line[BUFFER_SIZE / 3];
+			}
+			*next_line++ = 0;
+		}
+		else
+		{
+			if (*line == 0)
+			{
+				break;
+			}
+
+			if (strlen(line) > BUFFER_SIZE / 3)
+			{
+				next_line = &line[BUFFER_SIZE / 3];
+
+				*next_line++ = 0;
+			}
+
+			if (strlen(ses->more_output) < BUFFER_SIZE / 3)
+			{
+				if (!HAS_BIT(ses->telopts, TELOPT_FLAG_PROMPT))
+				{
+					if (ses->packet_patch)
+					{
+						strcat(ses->more_output, line);
+						ses->check_output = utime() + ses->packet_patch;
+
+						break;
+					}
+					else if (HAS_BIT(ses->flags, SES_FLAG_AUTOPATCH))
+					{
+						if (HAS_BIT(ses->flags, SES_FLAG_SPLIT))
+						{
+							ses->check_output = utime() + 100000ULL;
+						}
+					}
+				}
+			}
+		}
+
+		if (ses->more_output[0])
+		{
+			if (ses->check_output)
+			{
+				strcat(ses->more_output, line);
+				strcpy(linebuf, ses->more_output);
+
+				ses->more_output[0] = 0;
+			}
+			else
+			{
+				strcpy(linebuf, line);
+			}
+		}
+		else
+		{
+			strcpy(linebuf, line);
+		}
+
+		if (HAS_BIT(ses->charset, CHARSET_FLAG_ALL_TOUTF8))
+		{
+			char tempbuf[BUFFER_SIZE];
+
+			all_to_utf8(ses, linebuf, tempbuf);
+
+			process_mud_output(ses, tempbuf, next_line == NULL);
+		}
+		else
+		{
+			process_mud_output(ses, linebuf, next_line == NULL);
+		}
+	}
+	DEL_BIT(cts->flags, SES_FLAG_READMUD);
+
+	if (HAS_BIT(gtd->ses->flags, SES_FLAG_SPLIT))
+	{
+		restore_pos(gtd->ses);
+	}
+
+	pop_call();
+	return;
+}
+
+
+void process_mud_output(struct session *ses, char *linebuf, int prompt)
+{
+	char line[STRING_SIZE];
+
+	push_call("process_mud_output(%p,%p,%d)",ses,linebuf,prompt);
+
+	ses->check_output = 0;
+
+	strip_vt102_codes(linebuf, line);
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 0, 2, "RECEIVED LINE", linebuf, line);
+
+	if (check_all_events(ses, SUB_ARG|SUB_SEC, 0, 2, "CATCH RECEIVED LINE", linebuf, line))
+	{
+		pop_call();
+		return;
+	}
+
+	if (prompt)
+	{
+		check_all_events(ses, SUB_ARG|SUB_SEC, 0, 2, "RECEIVED PROMPT", linebuf, line);
+
+		if (check_all_events(ses, SUB_ARG|SUB_SEC, 0, 2, "CATCH RECEIVED PROMPT", linebuf, line))
+		{
+			pop_call();
+			return;
+		}
+	}
+
+	if (HAS_BIT(ses->flags, SES_FLAG_COLORPATCH))
+	{
+		sprintf(line, "%s%s%s", ses->color_patch, linebuf, "\e[0m");
+
+		get_color_codes(ses->color_patch, linebuf, ses->color_patch, GET_ALL);
+
+		linebuf = line;
+	}
+
+	do_one_line(linebuf, ses);   /* changes linebuf */
+
+	/*
+		Take care of gags, vt102 support still goes
+	*/
+
+	if (HAS_BIT(ses->flags, SES_FLAG_GAG))
+	{
+		DEL_BIT(ses->flags, SES_FLAG_GAG);
+
+		strip_non_vt102_codes(linebuf, line);
+
+		print_stdout("%s", line);
+
+		strip_vt102_codes(linebuf, line);
+
+		show_info(ses, LIST_GAG, "#INFO GAG {%s}", line);
+
+		pop_call();
+		return;
+	}
+
+	if (!check_all_events(ses, SUB_ARG|SUB_SEC, 0, 2, "CATCH BUFFERED LINE", linebuf, line))
+	{
+		add_line_buffer(ses, linebuf, prompt);
+	}
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 0, 2, "BUFFERED LINE", linebuf, line);
+
+	if (ses == gtd->ses)
+	{
+		char *output = str_dup(linebuf);
+
+		print_line(ses, &output, prompt);
+
+		str_free(output);
+
+		if (prompt)
+		{
+			if (!IS_SPLIT(ses))
+			{
+				gtd->input_off = 1 + strip_vt102_strlen(ses, linebuf);
+			}
+		}
+	}
+	pop_call();
+	return;
+}

+ 1078 - 0
parse.c

@@ -0,0 +1,1078 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2019 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
+*                                                                             *
+*                         coded by Peter Unold 1992                           *
+*                     recoded by Igor van den Hoven 2005                      *
+******************************************************************************/
+
+#include "tintin.h"
+
+// whether str1 is an abbreviation of str2
+
+int case_table[256] =
+{
+	  0,   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,
+	 95,  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,
+	 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,
+	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
+};
+
+int is_abbrev(char *str1, char *str2)
+{
+	char *str3 = gtd->is_result;
+
+	if (*str1 == 0)
+	{
+		return false;
+	}
+
+	if (*str2 == 0)
+	{
+		tintin_printf2(gtd->ses, "\e[1;31mis_abbrev(%s,%s)", str1, str2);
+
+		dump_stack();
+
+		return false;
+	}
+
+	while (true)
+	{
+		if (*str1 == 0)
+		{
+			strcpy(str3, str2);
+
+			return true;
+		}
+
+		if (case_table[(int) *str1] != case_table[(int) *str2])
+		{
+			return false;
+		}
+		str1++;
+		*str3++ = *str2++;
+	}
+}
+
+void filename_string(char *input, char *output)
+{
+	while (*input)
+	{
+		*output++ = (char) case_table[(int) *input++];
+	}
+	*output = 0;
+}
+
+int is_suffix(char *str1, char *str2)
+{
+	int len1, len2;
+
+	len1 = strlen(str1);
+	len2 = strlen(str2);
+
+	if (len1 >= len2)
+	{
+		if (is_abbrev(str1 + len1 - len2, str2))
+		{
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+
+struct session *parse_input(struct session *ses, char *input)
+{
+	char *line;
+
+	push_call("parse_input(%s,%s)",ses->name,input);
+
+	if (*input == 0)
+	{
+		write_mud(ses, input, SUB_EOL);
+
+		pop_call();
+		return ses;
+	}
+
+	if (VERBATIM(ses))
+	{
+		line = (char *) malloc(BUFFER_SIZE);
+
+		strcpy(line, input);
+
+		if (check_all_aliases(ses, line))
+		{
+			ses = script_driver(ses, LIST_ALIAS, line);
+		}
+		else
+		{
+			write_mud(ses, line, SUB_EOL);
+		}
+
+		free(line);
+
+		pop_call();
+		return ses;
+	}
+
+	if (*input == gtd->verbatim_char)
+	{
+		write_mud(ses, input+1, SUB_EOL);
+
+		pop_call();
+		return ses;
+	}
+
+	line = (char *) malloc(BUFFER_SIZE);
+
+	while (*input)
+	{
+		input = space_out(input);
+
+		input = get_arg_all(ses, input, line, GET_ONE);
+
+		if (parse_command(ses, line))
+		{
+			ses = script_driver(ses, LIST_COMMAND, line);
+		}
+		else if (check_all_aliases(ses, line))
+		{
+			ses = script_driver(ses, LIST_ALIAS, line);
+		}
+		else if (HAS_BIT(ses->flags, SES_FLAG_SPEEDWALK) && is_speedwalk(ses, line))
+		{
+			process_speedwalk(ses, line);
+		}
+		else
+		{
+			write_mud(ses, line, SUB_VAR|SUB_FUN|SUB_ESC|SUB_EOL);
+		}
+
+		if (*input == COMMAND_SEPARATOR)
+		{
+			input++;
+		}
+
+	}
+
+	free(line);
+
+	pop_call();
+	return ses;
+}
+
+/*
+	Deal with variables and functions used as commands.
+*/
+
+struct session *parse_command(struct session *ses, char *input)
+{
+	char *arg, line[BUFFER_SIZE], cmd1[BUFFER_SIZE], cmd2[BUFFER_SIZE];
+
+	push_call("parse_command(%p,%p)",ses,input);
+
+	arg = get_arg_stop_spaces(ses, input, cmd1, GET_ONE);
+
+	substitute(ses, cmd1, cmd2, SUB_VAR|SUB_FUN);
+
+	if (!strcmp(cmd1, cmd2))
+	{
+		pop_call();
+		return NULL;
+	}
+
+	sprintf(line, "%s%s%s", cmd2, *arg ? " " : "", arg);
+
+	strcpy(input, line);
+
+	pop_call();
+	return ses;
+}
+
+char *substitute_speedwalk(struct session *ses, char *input, char *output)
+{
+	char num[NUMBER_SIZE], name[BUFFER_SIZE], *pti, *ptn, *pto;
+	int cnt, max;
+
+	pti = input;
+	pto = output;
+
+	while (*pti && pto - output < INPUT_SIZE)
+	{
+		while (isspace(*pti))
+		{
+			pti++;
+		}
+
+		if (isdigit(*pti))
+		{
+			ptn = num;
+
+			while (isdigit(*pti))
+			{
+				if (ptn - num < 4)
+				{
+					*ptn++ = *pti++;
+				}
+				else
+				{
+					pti++;
+				}
+			}
+			*ptn = 0;
+
+			max = atoi(num);
+		}
+		else
+		{
+			max = 1;
+		}
+
+		pti = get_arg_stop_digits(ses, pti, name, GET_ONE);
+
+		if (*name == 0)
+		{
+			break;
+		}
+
+		for (cnt = 0 ; cnt < max ; cnt++)
+		{
+			if (output != pto)
+			{
+				*pto++ = COMMAND_SEPARATOR;
+			}
+			pto += sprintf(pto, "%s", name);
+		}
+
+		if (*pti == COMMAND_SEPARATOR)
+		{
+			pti++;
+		}
+	}
+	*pto = 0;
+
+	return output;
+}
+	
+int is_speedwalk(struct session *ses, char *input)
+{
+	int digit = 0, flag = FALSE;
+
+	while (*input)
+	{
+		switch (*input)
+		{
+			case 'n':
+			case 'e':
+			case 's':
+			case 'w':
+			case 'u':
+			case 'd':
+				if (digit > 3)
+				{
+					return FALSE;
+				}
+				digit = 0;
+				flag  = TRUE;
+				break;
+
+			case '0':
+			case '1':
+			case '2':
+			case '3':
+			case '4':
+			case '5':
+			case '6':
+			case '7':
+			case '8':
+			case '9':
+				digit++;
+				flag = FALSE;
+				break;
+
+			default:
+				return FALSE;
+		}
+		input++;
+	}
+	return flag;
+}
+
+
+void process_speedwalk(struct session *ses, char *input)
+{
+	char dir[2];
+	int cnt, i;
+
+	for (dir[1] = 0 ; *input ; input++)
+	{
+		if (isdigit((int) *input))
+		{
+			sscanf(input, "%d%c", &cnt, dir);
+
+			for (i = 0 ; i < cnt ; i++)
+			{
+				write_mud(ses, dir, SUB_EOL);
+			}
+
+			while (*input != dir[0])
+			{
+				input++;
+			}
+		}
+		else
+		{
+			dir[0] = *input;
+
+			write_mud(ses, dir, SUB_EOL);
+		}
+	}
+	return;
+}
+
+/*
+	Deals with all # stuff
+*/
+
+struct session *parse_tintin_command(struct session *ses, char *input)
+{
+	char line[BUFFER_SIZE];
+	struct session *sesptr;
+
+	input = get_arg_stop_spaces(ses, input, line, GET_ONE);
+
+	substitute(ses, line, line, SUB_VAR|SUB_FUN);
+
+	if (is_number(line))
+	{
+		int cnt = atoi(line);
+
+		input = get_arg_in_braces(ses, input, line, GET_ALL);
+
+		while (cnt-- > 0)
+		{
+			ses = script_driver(ses, LIST_COMMAND, line);
+		}
+		return ses;
+	}
+
+	sesptr = find_session(line);
+
+	if (sesptr)
+	{
+		if (*input)
+		{
+			input = get_arg_in_braces(ses, input, line, GET_ALL);
+
+			substitute(ses, line, line, SUB_VAR|SUB_FUN);
+
+			script_driver(sesptr, LIST_COMMAND, line);
+
+			return ses;
+		}
+		else
+		{
+			return activate_session(sesptr);
+		}
+	}
+
+	tintin_printf2(ses, "#ERROR: #UNKNOWN TINTIN-COMMAND '%s'.", line);
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 0, 1, "UNKNOWN COMMAND", line);
+
+	return ses;
+}
+
+
+int cnt_arg_all(struct session *ses, char *string, int flag)
+{
+	char *arg, tmp[BUFFER_SIZE];
+	int cnt;
+
+	arg = string;
+	cnt = 0;
+
+	while (*arg)
+	{
+		cnt++;
+
+		arg = get_arg_in_braces(ses, arg, tmp, flag);
+
+		if (*arg == COMMAND_SEPARATOR)
+		{
+			arg++;
+		}
+	}
+	return cnt;
+}
+
+/*
+	get all arguments - only check for unescaped command separators
+*/
+
+
+char *get_arg_all(struct session *ses, char *string, char *result, int verbatim)
+{
+	char *pto, *pti;
+	int skip, nest = 0;
+
+	pti = string;
+	pto = result;
+
+	if (*pti == gtd->verbatim_char)
+	{
+		while (*pti)
+		{
+			*pto++ = *pti++;
+		}
+		*pto = 0;
+
+		return pti;
+	}
+
+	while (*pti)
+	{
+		if (HAS_BIT(ses->charset, CHARSET_FLAG_EUC) && is_euc_head(ses, pti))
+		{
+			*pto++ = *pti++;
+			*pto++ = *pti++;
+			continue;
+		}
+
+		skip = find_secure_color_code(pti);
+
+		if (skip)
+		{
+			while (skip--)
+			{
+				*pto++ = *pti++;
+			}
+			continue;
+		}
+
+		if (*pti == '\\' && pti[1] == COMMAND_SEPARATOR)
+		{
+			*pto++ = *pti++;
+		}
+		else if (*pti == COMMAND_SEPARATOR && nest == 0 && !verbatim)
+		{
+			break;
+		}
+		else if (*pti == DEFAULT_OPEN)
+		{
+			nest++;
+		}
+		else if (*pti == DEFAULT_CLOSE)
+		{
+			nest--;
+		}
+		*pto++ = *pti++;
+
+		if (pto - result >= BUFFER_SIZE - 3)
+		{
+			tintin_printf2(ses, "#ERROR: INPUT BUFFER OVERFLOW.");
+
+			pto--;
+
+			break;
+		}
+	}
+	*pto = '\0'; 
+
+	return pti;
+}
+
+
+/*
+	Braces are stripped in braced arguments leaving all else as is.
+*/
+
+char *get_arg_in_braces(struct session *ses, char *string, char *result, int flag)
+{
+	char *pti, *pto;
+	int skip, nest = 1;
+
+	pti = space_out(string);
+	pto = result;
+
+	if (*pti != DEFAULT_OPEN)
+	{
+		if (!HAS_BIT(flag, GET_ALL))
+		{
+			pti = get_arg_stop_spaces(ses, pti, result, flag);
+		}
+		else
+		{
+			pti = get_arg_with_spaces(ses, pti, result, flag);
+		}
+		return pti;
+	}
+
+	pti++;
+
+	while (*pti)
+	{
+		
+		if (HAS_BIT(ses->charset, CHARSET_FLAG_EUC) && is_euc_head(ses, pti))
+		{
+			*pto++ = *pti++;
+			*pto++ = *pti++;
+			continue;
+		}
+
+		skip = find_secure_color_code(pti);
+
+		if (skip)
+		{
+			while (skip--)
+			{
+				*pto++ = *pti++;
+			}
+			continue;
+		}
+
+		if (*pti == DEFAULT_OPEN)
+		{
+			nest++;
+		}
+		else if (*pti == DEFAULT_CLOSE)
+		{
+			nest--;
+
+			if (nest == 0)
+			{
+				break;
+			}
+		}
+		*pto++ = *pti++;
+	}
+
+	if (*pti == 0)
+	{
+		tintin_printf2(ses, "#ERROR: GET BRACED ARGUMENT: UNMATCHED BRACE.");
+	}
+	else
+	{
+		pti++;
+	}
+	*pto = '\0';
+
+	return pti;
+}
+
+char *sub_arg_in_braces(struct session *ses, char *string, char *result, int flag, int sub)
+{
+	char *buffer = str_alloc(UMAX(strlen(string), BUFFER_SIZE));
+
+	string = get_arg_in_braces(ses, string, buffer, flag);
+
+	substitute(ses, buffer, result, sub);
+
+	str_free(buffer);
+
+	return string;
+}
+
+/*
+	get all arguments
+*/
+
+char *get_arg_with_spaces(struct session *ses, char *string, char *result, int flag)
+{
+	char *pto, *pti;
+	int skip, nest = 0;
+
+	pti = space_out(string);
+	pto = result;
+
+	while (*pti)
+	{
+		if (HAS_BIT(ses->charset, CHARSET_FLAG_EUC) && is_euc_head(ses, pti))
+		{
+			*pto++ = *pti++;
+			*pto++ = *pti++;
+			continue;
+		}
+
+		skip = find_secure_color_code(pti);
+
+		if (skip)
+		{
+			while (skip--)
+			{
+				*pto++ = *pti++;
+			}
+			continue;
+		}
+
+		if (*pti == '\\' && pti[1] == COMMAND_SEPARATOR)
+		{
+			*pto++ = *pti++;
+		}
+		else if (*pti == COMMAND_SEPARATOR && nest == 0)
+		{
+			break;
+		}
+		else if (*pti == DEFAULT_OPEN)
+		{
+			nest++;
+		}
+		else if (*pti == DEFAULT_CLOSE)
+		{
+			nest--;
+		}
+		*pto++ = *pti++;
+	}
+	*pto = '\0'; 
+
+	return pti;
+}
+
+/*
+	get one arg, stop at spaces
+*/
+
+char *get_arg_stop_spaces(struct session *ses, char *string, char *result, int flag)
+{
+	char *pto, *pti;
+	int skip, nest = 0;
+
+	pti = space_out(string);
+	pto = result;
+
+	while (*pti)
+	{
+		if (HAS_BIT(ses->charset, CHARSET_FLAG_EUC) && is_euc_head(ses, pti))
+		{
+			*pto++ = *pti++;
+			*pto++ = *pti++;
+			continue;
+		}
+
+		skip = find_secure_color_code(pti);
+
+		if (skip)
+		{
+			while (skip--)
+			{
+				*pto++ = *pti++;
+			}
+			continue;
+		}
+
+
+		if (*pti == '\\' && pti[1] == COMMAND_SEPARATOR)
+		{
+			*pto++ = *pti++;
+		}
+		else if (*pti == COMMAND_SEPARATOR && nest == 0)
+		{
+			break;
+		}
+		else if (isspace((int) *pti) && nest == 0)
+		{
+			pti++;
+			break;
+		}
+		else if (*pti == DEFAULT_OPEN)
+		{
+			nest++;
+		}
+		else if (*pti == '[' && HAS_BIT(flag, GET_NST))
+		{
+			nest++;
+		}
+		else if (*pti == DEFAULT_CLOSE)
+		{
+			nest--;
+		}
+		else if (*pti == ']' && HAS_BIT(flag, GET_NST))
+		{
+			nest--;
+		}
+		*pto++ = *pti++;
+	}
+	*pto = '\0';
+
+	return pti;
+}
+
+// Get one arg, stop at numbers, used for speedwalks
+
+char *get_arg_stop_digits(struct session *ses, char *string, char *result, int flag)
+{
+	char *pto, *pti;
+	int nest = 0;
+
+	pti = space_out(string);
+	pto = result;
+
+	while (*pti)
+	{
+		if (HAS_BIT(ses->charset, CHARSET_FLAG_EUC) && is_euc_head(ses, pti))
+		{
+			*pto++ = *pti++;
+			*pto++ = *pti++;
+			continue;
+		}
+
+		if (*pti == '\\' && pti[1] == COMMAND_SEPARATOR)
+		{
+			*pto++ = *pti++;
+		}
+		else if (*pti == COMMAND_SEPARATOR && nest == 0)
+		{
+			break;
+		}
+		else if (isdigit((int) *pti) && nest == 0)
+		{
+			pti++;
+			break;
+		}
+		else if (*pti == DEFAULT_OPEN)
+		{
+			nest++;
+		}
+		else if (*pti == '[' && HAS_BIT(flag, GET_NST))
+		{
+			nest++;
+		}
+		else if (*pti == DEFAULT_CLOSE)
+		{
+			nest--;
+		}
+		else if (*pti == ']' && HAS_BIT(flag, GET_NST))
+		{
+			nest--;
+		}
+		*pto++ = *pti++;
+	}
+	*pto = '\0';
+
+	return pti;
+}
+
+/*
+	advance ptr to next none-space
+*/
+
+char *space_out(char *string)
+{
+	while (isspace((int) *string))
+	{
+		string++;
+	}
+	return string;
+}
+
+/*
+	For variable handling
+*/
+
+char *get_arg_to_brackets(struct session *ses, char *string, char *result)
+{
+	char *pti, *pto, *ptii, *ptoo;
+	int nest1 = 0, nest2 = 0, nest3 = 0;
+
+	pti = space_out(string);
+	pto = result;
+	ptii = ptoo = NULL;
+
+	while (*pti)
+	{
+		if (HAS_BIT(ses->charset, CHARSET_FLAG_EUC) && is_euc_head(ses, pti))
+		{
+			*pto++ = *pti++;
+			*pto++ = *pti++;
+			continue;
+		}
+
+		if (*pti == '[')
+		{
+			nest2++;
+
+			if (nest1 == 0 && ptii == NULL)
+			{
+				ptii = pti;
+				ptoo = pto;
+			}
+		}
+		else if (*pti == ']')
+		{
+			if (nest2)
+			{
+				nest2--;
+			}
+			else
+			{
+				nest3 = 1;
+			}
+
+			if (*(pti+1) == 0 && ptii && nest1 == 0 && nest2 == 0 && nest3 == 0)
+			{
+				*ptoo = 0;
+
+				return ptii;
+			}
+		}
+		else if (*pti == DEFAULT_OPEN)
+		{
+			nest1++;
+		}
+		else if (*pti == DEFAULT_CLOSE)
+		{
+			nest1--;
+		}
+		*pto++ = *pti++;
+	}
+	*pto = 0;
+
+	return pti;
+}
+
+char *get_arg_at_brackets(struct session *ses, char *string, char *result)
+{
+	char *pti, *pto;
+	int nest = 0;
+
+	pti = string;
+	pto = result;
+
+	if (*pti != '[')
+	{
+		*pto = 0;
+
+		return pti;
+	}
+
+	while (*pti)
+	{
+		if (HAS_BIT(ses->charset, CHARSET_FLAG_EUC) && is_euc_head(ses, pti))
+		{
+			*pto++ = *pti++;
+			*pto++ = *pti++;
+			continue;
+		}
+
+		if (*pti == '[')
+		{
+			nest++;
+		}
+		else if (*pti == ']')
+		{
+			if (nest)
+			{
+				nest--;
+			}
+			else
+			{
+				break;
+			}
+		}
+		else if (nest == 0)
+		{
+			break;
+		}
+		*pto++ = *pti++;
+	}
+
+	if (nest)
+	{
+		tintin_printf2(NULL, "#ERROR: GET BRACKETED VARIABLE: UNMATCHED BRACKET.");
+	}
+	*pto = 0;
+
+	return pti;
+}
+
+char *get_arg_in_brackets(struct session *ses, char *string, char *result)
+{
+	char *pti, *pto;
+	int nest = 1;
+
+	pti = string;
+	pto = result;
+
+	if (*pti != '[')
+	{
+		*pto = 0;
+
+		return pti;
+	}
+
+	pti++;
+
+	while (*pti)
+	{
+		if (HAS_BIT(ses->charset, CHARSET_FLAG_EUC) && is_euc_head(ses, pti))
+		{
+			*pto++ = *pti++;
+			*pto++ = *pti++;
+			continue;
+		}
+
+		if (*pti == '[')
+		{
+			nest++;
+		}
+		else if (*pti == ']')
+		{
+			nest--;
+
+			if (nest == 0)
+			{
+				break;
+			}
+		}
+		*pto++ = *pti++;
+	}
+
+	if (*pti == 0)
+	{
+		tintin_printf2(NULL, "#ERROR: GET BRACKETED ARGUMENT: UNMATCHED BRACKET.");
+	}
+	else
+	{
+		pti++;
+	}
+	*pto = 0;
+
+	return pti;
+}
+
+char *get_char(struct session *ses, char *string, char *result)
+{
+	char *pti = string;
+
+	if (HAS_BIT(ses->charset, CHARSET_FLAG_EUC) && is_euc_head(ses, pti))
+	{
+		pti += sprintf(result, "%c%c", pti[0], pti[1]);
+	}
+	else if (HAS_BIT(ses->charset, CHARSET_FLAG_UTF8) && is_utf8_head(pti))
+	{
+		pti += sprintf(result, "%.*s", get_utf8_size(pti), pti);
+	}
+	else
+	{
+		pti += sprintf(result, "%c", pti[0]);
+	}
+
+	return pti;
+}
+
+/*
+	send command to the mud
+*/
+
+void write_mud(struct session *ses, char *command, int flags)
+{
+	char output[BUFFER_SIZE];
+	int size;
+
+	size = substitute(ses, command, output, flags);
+
+	if (HAS_BIT(ses->flags, SES_FLAG_PATHMAPPING))
+	{
+		if (ses->map == NULL || ses->map->nofollow == 0)
+		{
+			check_append_path(ses, command, NULL, 1);
+		}
+	}
+
+	if (ses->map && ses->map->in_room && ses->map->nofollow == 0)
+	{
+		if (follow_map(ses, command))
+		{
+			return;
+		}
+	}
+
+	write_line_mud(ses, output, size);
+}
+
+
+/*
+	Check line for triggers
+*/
+
+void do_one_line(char *line, struct session *ses)
+{
+	char strip[BUFFER_SIZE];
+
+	push_call("[%s] do_one_line(%s)",ses->name,line);
+
+	if (gtd->level->ignore)
+	{
+		pop_call();
+		return;
+	}
+
+	strip_vt102_codes(line, strip);
+
+	if (!HAS_BIT(ses->list[LIST_ACTION]->flags, LIST_FLAG_IGNORE))
+	{
+		check_all_actions(ses, line, strip);
+	}
+
+	if (!HAS_BIT(ses->list[LIST_PROMPT]->flags, LIST_FLAG_IGNORE))
+	{
+		check_all_prompts(ses, line, strip);
+	}
+
+	if (!HAS_BIT(ses->list[LIST_GAG]->flags, LIST_FLAG_IGNORE))
+	{
+		check_all_gags(ses, line, strip);
+	}
+
+	if (!HAS_BIT(ses->list[LIST_SUBSTITUTE]->flags, LIST_FLAG_IGNORE))
+	{
+		check_all_substitutions(ses, line, strip);
+	}
+
+	if (!HAS_BIT(ses->list[LIST_HIGHLIGHT]->flags, LIST_FLAG_IGNORE))
+	{
+		check_all_highlights(ses, line, strip);
+	}
+
+	if (HAS_BIT(ses->logmode, LOG_FLAG_NEXT))
+	{
+		logit(ses, line, ses->lognext_file, LOG_FLAG_LINEFEED);
+
+		DEL_BIT(ses->logmode, LOG_FLAG_NEXT);
+	}
+	pop_call();
+	return;
+}

+ 980 - 0
path.c

@@ -0,0 +1,980 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2019 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
+*                                                                             *
+*                         coded by Peter Unold 1992                           *
+*                     recoded by Igor van den Hoven 2004                      *
+******************************************************************************/
+
+#include "tintin.h"
+
+
+DO_COMMAND(do_path)
+{
+	char arg1[BUFFER_SIZE];
+	int cnt;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		tintin_header(ses, " PATH OPTIONS ");
+
+		for (cnt = 0 ; *path_table[cnt].fun != NULL ; cnt++)
+		{
+			if (*path_table[cnt].desc)
+			{
+				tintin_printf2(ses, "  [%-13s] %s", path_table[cnt].name, path_table[cnt].desc);
+			}
+		}
+		tintin_header(ses, "");
+
+		return ses;
+	}
+	else
+	{
+		for (cnt = 0 ; *path_table[cnt].name ; cnt++)
+		{
+			if (is_abbrev(arg1, path_table[cnt].name))
+			{
+				break;
+			}
+		}
+
+		if (*path_table[cnt].name == 0)
+		{
+			do_path(ses, "");
+		}
+		else
+		{
+			path_table[cnt].fun(ses, arg);
+		}
+	}
+	return ses;
+}
+
+
+DO_PATH(path_create)
+{
+	struct listroot *root = ses->list[LIST_PATH];
+
+	kill_list(root);
+
+	root->update = 0;
+
+	show_message(ses, LIST_COMMAND, "#PATH CREATE: YOU START MAPPING A NEW PATH.");
+
+	SET_BIT(ses->flags, SES_FLAG_PATHMAPPING);
+}
+
+
+DO_PATH(path_destroy)
+{
+	struct listroot *root = ses->list[LIST_PATH];
+
+	kill_list(root);
+
+	root->update = 0;
+
+	DEL_BIT(ses->flags, SES_FLAG_PATHMAPPING);
+
+	show_message(ses, LIST_COMMAND, "#PATH DESTROY: PATH DESTROYED.");
+}
+
+
+DO_PATH(path_start)
+{
+	if (HAS_BIT(ses->flags, SES_FLAG_PATHMAPPING))
+	{
+		show_message(ses, LIST_COMMAND, "#PATH START: ERROR: YOU ARE ALREADY MAPPING A PATH.");
+	}
+	else
+	{
+		SET_BIT(ses->flags, SES_FLAG_PATHMAPPING);
+
+		show_message(ses, LIST_COMMAND, "#PATH START: YOU START MAPPING A PATH.");
+	}
+}
+
+DO_PATH(path_stop)
+{
+	int index;
+	struct listroot *root = ses->list[LIST_PATH];
+
+	if (HAS_BIT(ses->flags, SES_FLAG_PATHMAPPING))
+	{
+		show_message(ses, LIST_COMMAND, "#PATH STOP: YOU STOP MAPPING A PATH.");
+
+		DEL_BIT(ses->flags, SES_FLAG_PATHMAPPING);
+	}
+	else
+	{
+		if (root->list[root->update]->val64)
+		{
+			for (index = 0 ; index < root->used ; index++)
+			{
+				root->list[index]->val64 = 0;
+			}
+			show_message(ses, LIST_COMMAND, "#PATH STOP: YOU STOP RUNNING A PATH.");
+		}
+		else
+		{
+			show_message(ses, LIST_COMMAND, "#PATH STOP: YOU ARE NOT MAPPING OR RUNNING A PATH.");
+		}
+	}
+}
+
+
+DO_PATH(path_describe)
+{
+	char *dirs[] = {
+		"west", "west-southwest", "southwest", "south-southwest",
+		"south", "south-southeast", "southeast", "east-southeast",
+		"east", "east-northeast", "northeast", "north-northeast",
+		"north", "north-northwest", "northwest", "west-northwest",
+		"west" };
+
+	char *slopes[] = {
+		"a steep upward slope", "a steady upward slope", "a slight upward slope",
+		"the same level",
+		"a slight downward slope", "a steady downward slope", "a steep downward slope" };
+
+	struct listroot *root = ses->list[LIST_PATH];
+	struct listnode *node;
+	int a, d, i, s, x, y, z;
+
+	i = x = y = z = 0;
+
+	i = root->update;
+
+	while (i < root->used)
+	{
+		node = search_node_list(ses->list[LIST_PATHDIR], root->list[i++]->arg1);
+
+		if (node)
+		{
+			x += (HAS_BIT(atoi(node->arg3), MAP_EXIT_E) ? 1 : HAS_BIT(atoi(node->arg3), MAP_EXIT_W) ? -1 : 0);
+			y += (HAS_BIT(atoi(node->arg3), MAP_EXIT_N) ? 1 : HAS_BIT(atoi(node->arg3), MAP_EXIT_S) ? -1 : 0);
+			z += (HAS_BIT(atoi(node->arg3), MAP_EXIT_U) ? 1 : HAS_BIT(atoi(node->arg3), MAP_EXIT_D) ? -1 : 0);
+		}
+	}
+
+	a = sqrt(x * x + y * y);
+
+	a = sqrt(a * a + z * z);
+
+	d = round(16 * (atan2(y, x) + M_PI) / (M_PI * 2));
+
+	s = round(12 * (atan2(a, z) - M_PI / 4) / M_PI);
+
+	if (x == 0 && y == 0)
+	{
+		if (z == 0)
+		{
+			if (root->used > 2)
+			{
+				tintin_printf2(ses, "The path is %d rooms long and leads back to where you are at.", root->used);
+			}
+			else
+			{
+				tintin_printf2(ses, "The path is %d rooms long and the destination is right where you are at.", root->used);
+			}
+		}
+		else
+		{
+			tintin_printf2(ses, "The path is %d rooms long and the destination lies %d rooms %s of you.", root->used, abs(z), z < 0 ? "below" : "above", s);
+		}
+	}
+	else
+	{
+		tintin_printf2(ses, "The path is %d rooms long and the destination lies %d rooms to the %s of you at %s.", root->used, a, dirs[d], slopes[s]);
+	}
+
+	if (root->update == 0)
+	{
+		tintin_printf2(ses, "You are at the start of the path.");
+	}
+	else if (root->update == root->used)
+	{
+		tintin_printf2(ses, "You are at the end of the path.");
+	}
+	else
+	{
+		tintin_printf2(ses, "You've traversed %d out of %d steps of the path.", root->update, root->used);
+	}
+}
+
+DO_PATH(path_map)
+{
+	struct listroot *root = ses->list[LIST_PATH];
+	char buf[BUFFER_SIZE];
+	int i = 0;
+
+	if (root->used == 0)
+	{
+		show_message(ses, LIST_COMMAND, "#PATH MAP: EMPTY PATH.");
+	}
+	else
+	{
+		sprintf(buf, "%-7s", "#PATH:");
+
+		for (i = 0 ; i < root->update ; i++)
+		{
+			cat_sprintf(buf, " %s", root->list[i]->arg1);
+		}
+
+		if (i != root->used)
+		{
+			cat_sprintf(buf, " [%s]", root->list[i++]->arg1);
+
+			for (i = root->update + 1 ; i < root->used ; i++)
+			{
+				cat_sprintf(buf, " %s", root->list[i]->arg1);
+			}
+		}
+
+		if (root->update == root->used)
+		{
+			cat_sprintf(buf, " [ ]");
+		}
+
+		tintin_puts2(ses, buf);
+	}
+}
+
+
+DO_PATH(path_save)
+{
+	struct listroot *root = ses->list[LIST_PATH];
+	char result[STRING_SIZE], arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+	int i;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if (root->used == 0)
+	{
+		tintin_puts2(ses, "#PATH SAVE: LOAD OR CREATE A PATH FIRST.");
+	}
+	else if (*arg2 == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #PATH SAVE <BACKWARD|FORWARD|LENGTH|POSITION> <VARIABLE NAME>");
+	}
+	else if (is_abbrev(arg1, "BACKWARDS"))
+	{
+		result[0] = 0;
+
+		for (i = root->used - 1 ; i >= 0 ; i--)
+		{
+			strcat(result, root->list[i]->arg2);
+
+			if (i != 0)
+			{
+				cat_sprintf(result, "%c", COMMAND_SEPARATOR);
+			}
+		}
+		set_nest_node_ses(ses, arg2, "%s", result);
+
+		show_message(ses, LIST_COMMAND, "#PATH SAVE: BACKWARD PATH SAVED TO {%s}", arg2);
+	}
+	else if (is_abbrev(arg1, "FORWARDS"))
+	{
+		result[0] = 0;
+
+		for (i = 0 ; i < root->used ; i++)
+		{
+			strcat(result, root->list[i]->arg1);
+
+			if (i != root->used - 1)
+			{
+				cat_sprintf(result, "%c", COMMAND_SEPARATOR);
+			}
+		}
+		set_nest_node_ses(ses, arg2, "%s", result);
+
+		show_message(ses, LIST_COMMAND, "#PATH SAVE: FORWARD PATH SAVED TO {%s}", arg2);
+	}
+	else if (is_abbrev(arg1, "LENGTH"))
+	{
+		sprintf(result, "%d", root->used);
+
+		set_nest_node_ses(ses, arg2, "%s", result);
+
+		show_message(ses, LIST_COMMAND, "#PATH SAVE: PATH LENGTH SAVED TO {%s}", arg2);
+	}
+	else if (is_abbrev(arg1, "POSITION"))
+	{
+		sprintf(result, "%d", root->update + 1);
+
+		set_nest_node_ses(ses, arg2, "%s", result);
+
+		show_message(ses, LIST_COMMAND, "#PATH SAVE: PATH POSITION SAVED TO {%s}", arg2);
+	}
+	else
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #PATH SAVE <BACKWARD|FORWARD|LENGTH|POSITION> <VARIABLE NAME>");
+	}
+}
+
+
+DO_PATH(path_load)
+{
+	struct listroot *root = ses->list[LIST_PATH];
+	char arg1[BUFFER_SIZE], temp[BUFFER_SIZE];
+	struct listnode *node;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if ((node = search_nest_node_ses(ses, arg1)) == NULL)
+	{
+		arg = arg1;
+	}
+	else
+	{
+		arg = node->arg2;
+	}
+
+	kill_list(root);
+
+	root->update = 0;
+
+	while (*arg)
+	{
+		if (*arg == ';')
+		{
+			arg++;
+		}
+
+		arg = get_arg_in_braces(ses, arg, temp, GET_ALL);
+
+		if ((node = search_node_list(root, temp)))
+		{
+			insert_node_list(root, node->arg1, node->arg2, "0", "");
+		}
+		else
+		{
+			insert_node_list(root, temp, temp, "0", "");
+		}
+	}
+	show_message(ses, LIST_COMMAND, "#PATH LOAD: PATH WITH %d NODES LOADED.", root->used);
+}
+
+DO_PATH(path_delete)
+{
+	struct listroot *root = ses->list[LIST_PATH];
+
+	if (root->used)
+	{
+		show_message(ses, LIST_COMMAND, "#PATH DELETE: DELETED MOVE {%s}.", root->list[root->used - 1]->arg1);
+
+		delete_index_list(root, root->used - 1);
+
+		if (root->update >= root->used)
+		{
+			root->update--;
+		}
+	}
+	else
+	{
+		tintin_puts(ses, "#PATH DELETE: NO MOVES LEFT.");
+	}
+
+}
+
+DO_PATH(path_insert)
+{
+	struct listroot *root = ses->list[LIST_PATH];
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0 && *arg2 == 0)
+	{
+		show_message(ses, LIST_COMMAND, "#PATH INSERT: ERROR: YOU MUST GIVE A COMMAND TO INSERT");
+	}
+	else
+	{
+		insert_node_list(root, arg1, arg2, "0", "");
+
+		show_message(ses, LIST_COMMAND, "#PATH INSERT: FORWARD {%s} BACKWARD {%s}.", arg1, arg2);
+
+		if (HAS_BIT(ses->flags, SES_FLAG_PATHMAPPING))
+		{
+			root->update = root->used;
+		}
+	}
+}
+
+
+DO_PATH(path_run)
+{
+	int index;
+	struct listroot *root = ses->list[LIST_PATH];
+	char arg1[BUFFER_SIZE];
+	long long total, delay;
+
+	push_call("path_run(%p,%p)",ses,arg);
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if (root->update == root->used)
+	{
+		tintin_puts(ses, "#PATH RUN: #END OF PATH.");
+	}
+	else
+	{
+		if (*arg1)
+		{
+		
+			delay = (long long) get_number(ses, arg1) * 1000000LL;
+			total = 0;
+
+			DEL_BIT(ses->flags, SES_FLAG_PATHMAPPING);
+
+			for (index = root->update ; index < root->used ; index++)
+			{
+				root->list[index]->val64 = gtd->utime + total;
+
+				total += delay;
+			}
+		}
+		else
+		{
+			while (root->update < root->used)
+			{
+				script_driver(ses, LIST_COMMAND, root->list[root->update++]->arg1);
+			}
+		}
+	}
+
+	pop_call();
+	return;
+}
+
+
+DO_PATH(path_walk)
+{
+	struct listroot *root = ses->list[LIST_PATH];
+	char arg1[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+
+	DEL_BIT(ses->flags, SES_FLAG_PATHMAPPING);
+
+	if (is_abbrev(arg1, "BACKWARDS"))
+	{
+		if (root->update == 0)
+		{
+			tintin_puts(ses, "#PATH WALK: #START OF PATH.");
+		}
+		else
+		{
+			script_driver(ses, LIST_COMMAND, root->list[--root->update]->arg2);
+
+			if (root->update == 0)
+			{
+				check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "START OF PATH");
+			}
+		}
+	}
+	else if (*arg1 == 0 || is_abbrev(arg1, "FORWARDS"))
+	{
+		if (root->update == root->used)
+		{
+			tintin_puts(ses, "#PATH WALK: #END OF PATH.");
+		}
+		else
+		{
+			script_driver(ses, LIST_COMMAND, root->list[root->update++]->arg1);
+
+			if (root->update == root->used)
+			{
+				check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "END OF PATH");
+			}
+		}
+	}
+	else
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #PATH WALK {FORWARD|BACKWARD}.");
+	}
+}
+
+
+DO_PATH(path_swap)
+{
+	struct listroot *root = ses->list[LIST_PATH];
+	struct listnode *node;
+	int a, z;
+
+	if (root->used == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#PATH SWAP: ERROR: PATH IS EMPTY.");
+
+		return;
+	}
+
+	a = 0;
+	z = root->used - 1;
+
+	if (root->update)
+	{
+		root->update = root->used - root->update;
+	}
+
+	while (z > a)
+	{
+		arg = root->list[z]->arg1;
+		root->list[z]->arg1 = root->list[z]->arg2;
+		root->list[z]->arg2 = arg;
+
+		arg = root->list[a]->arg1;
+		root->list[a]->arg1 = root->list[a]->arg2;
+		root->list[a]->arg2 = arg;
+
+		node = root->list[z];
+		root->list[z--] = root->list[a];
+		root->list[a++] = node;
+	}
+
+	if (z == a)
+	{
+		arg = root->list[z]->arg1;
+		root->list[z]->arg1 = root->list[z]->arg2;
+		root->list[z]->arg2 = arg;
+	}
+
+	show_message(ses, LIST_COMMAND, "#PATH SWAP: PATH HAS BEEN SWAPPED.");
+}
+
+
+DO_PATH(path_zip)
+{
+	struct listroot *root = ses->list[LIST_PATH];
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+	int i, cnt;
+
+	cnt   =  1;
+
+	*arg1 =  0;
+	*arg2 = 0;
+
+	for (i = 0 ; i < root->used ; i++)
+	{
+		if (search_node_list(root, root->list[i]->arg1) == NULL || strlen(root->list[i]->arg1) != 1)
+		{
+			if (i && search_node_list(root, root->list[i - 1]->arg1) != NULL && strlen(root->list[i - 1]->arg1) == 1)
+			{
+				cat_sprintf(arg1, "%c", COMMAND_SEPARATOR);
+			}
+			cat_sprintf(arg1, "%s", root->list[i]->arg1);
+
+			if (i < root->used - 1)
+			{
+				cat_sprintf(arg1, "%c", COMMAND_SEPARATOR);
+			}
+			continue;
+		}
+
+		if (i < root->used - 1 && !strcmp(root->list[i]->arg1, root->list[i + 1]->arg1))
+		{
+			cnt++;
+		}
+		else
+		{
+			cat_sprintf(arg1, "%d%s", cnt, root->list[i]->arg1);
+
+			cnt = 1;
+		}
+	}
+
+	for (i = root->used - 1 ; i >= 0 ; i--)
+	{
+		if (search_node_list(root, root->list[i]->arg2) == NULL || strlen(root->list[i]->arg2) != 1)
+		{
+			if (i != root->used - 1 && search_node_list(root, root->list[i + 1]->arg2) != NULL && strlen(root->list[i + 1]->arg2) == 1)
+			{
+				cat_sprintf(arg2, "%c", COMMAND_SEPARATOR);
+			}
+			cat_sprintf(arg2, "%s", root->list[i]->arg2);
+
+			if (i > 0)
+			{
+				cat_sprintf(arg2, "%c", COMMAND_SEPARATOR);
+			}
+			continue;
+		}
+
+		if (i > 0 && !strcmp(root->list[i]->arg2, root->list[i - 1]->arg2))
+		{
+			cnt++;
+		}
+		else
+		{
+			cat_sprintf(arg2, "%d%s", cnt, root->list[i]->arg2);
+			cnt = 1;
+		}
+	}
+
+	root->update = root->used;
+
+	kill_list(root);
+
+	insert_node_list(root, arg1, arg2, "0", "");
+
+	show_message(ses, LIST_COMMAND, "#PATH ZIP: THE PATH HAS BEEN ZIPPED TO {%s} {%s}.", arg1, arg2);
+}
+
+DO_PATH(path_unzip)
+{
+	struct listroot *root = ses->list[LIST_PATH];
+	char arg1[BUFFER_SIZE], temp[BUFFER_SIZE], *str;
+	struct listnode *node;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if ((node = search_nest_node_ses(ses, arg1)) == NULL)
+	{
+		arg = arg1;
+	}
+	else
+	{
+		arg = node->arg2;
+	}
+
+	kill_list(root);
+
+	root->update = 0;
+
+	while (*arg)
+	{
+		if (*arg == ';')
+		{
+			arg++;
+		}
+
+		arg = get_arg_in_braces(ses, arg, temp, GET_ALL);
+
+		if (is_speedwalk(ses, temp))
+		{
+			char dir[2];
+			int cnt, i;
+
+			str = temp;
+
+			for (dir[1] = 0 ; *str ; str++)
+			{
+				if (isdigit((int) *str))
+				{
+					sscanf(str, "%d%c", &cnt, dir);
+
+					while (*str != dir[0])
+					{
+						str++;
+					}
+				}
+				else
+				{
+					cnt = 1;
+					dir[0] = *str;
+				}
+
+				for (i = 0 ; i < cnt ; i++)
+				{
+					if ((node = search_node_list(ses->list[LIST_PATHDIR], dir)))
+					{
+						insert_node_list(root, node->arg1, node->arg2, "0", "");
+					}
+					else
+					{
+						insert_node_list(root, dir, dir, "0", "");
+					}
+				}
+			}
+		}
+		else
+		{
+			if ((node = search_node_list(ses->list[LIST_PATHDIR], temp)))
+			{
+				insert_node_list(root, node->arg1, node->arg2, "0", "");
+			}
+			else
+			{
+				insert_node_list(root, temp, temp, "0", "");
+			}
+		}
+	}
+	show_message(ses, LIST_COMMAND, "#PATH UNZIP: PATH WITH %d NODES UNZIPPED.", root->used);
+}
+
+
+DO_PATH(path_goto)
+{
+	struct listroot *root = ses->list[LIST_PATH];
+	char arg1[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if (is_abbrev(arg1, "END"))
+	{
+		root->update = root->used;
+
+		show_message(ses, LIST_COMMAND, "#PATH GOTO: POSITION SET TO {%d}.", root->update + 1);
+	}
+	else if (is_abbrev(arg1, "START"))
+	{
+		root->update = 0;
+
+		show_message(ses, LIST_COMMAND, "#PATH GOTO: POSITION SET TO %d.", root->update + 1);
+	}
+	else if (is_math(ses, arg1))
+	{
+		if (get_number(ses, arg1) < 1 || get_number(ses, arg1) > root->used + 1)
+		{
+			show_message(ses, LIST_COMMAND, "#PATH GOTO: POSITION MUST BE BETWEEN 1 AND %d.", root->used + 1);
+		}
+		else
+		{
+			root->update = get_number(ses, arg1) - 1;
+
+			show_message(ses, LIST_COMMAND, "#PATH GOTO: POSITION SET TO %d.", root->update + 1);
+		}
+	}
+}
+
+
+DO_PATH(path_move)
+{
+	struct listroot *root = ses->list[LIST_PATH];
+	char arg1[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if (is_abbrev(arg1, "BACKWARD"))
+	{
+		if (root->update == 0)
+		{
+			show_message(ses, LIST_COMMAND, "#PATH GOTO: ALREADY AT START OF PATH.", root->update);
+		}
+		else
+		{
+			root->update--;
+
+			show_message(ses, LIST_COMMAND, "#PATH MOVE: POSITION SET TO %d.", root->update);
+		}
+	}
+	else if (is_abbrev(arg1, "FORWARD"))
+	{
+		if (root->update == root->used)
+		{
+			show_message(ses, LIST_COMMAND, "#PATH MOVE: ALREADY AT END OF PATH.", root->update);
+		}
+		else
+		{
+			root->update++;
+
+			show_message(ses, LIST_COMMAND, "#PATH MOVE: POSITION SET TO %d.", root->update);
+		}
+	}
+	else if (is_math(ses, arg1))
+	{
+		root->update = URANGE(0, root->update + get_number(ses, arg1), root->used);
+
+		show_message(ses, LIST_COMMAND, "#PATH MOVE: POSITION SET TO %d.", root->update + 1);
+	}
+}
+
+DO_PATH(path_undo)
+{
+	struct listroot *root = ses->list[LIST_PATH];
+
+	if (root->used == 0)
+	{
+		show_message(ses, LIST_COMMAND, "#PATH UNDO: ERROR: PATH IS EMPTY.");
+
+		return;
+	}
+
+	if (root->update != root->used)
+	{
+		show_message(ses, LIST_COMMAND, "#PATH UNDO: ERROR: YOUR POSITION IS NOT AT END OF PATH.");
+	
+		return;
+	}
+
+	if (!HAS_BIT(ses->flags, SES_FLAG_PATHMAPPING))
+	{
+		show_message(ses, LIST_COMMAND, "#PATH UNDO: ERROR: YOU ARE NOT CURRENTLY MAPPING A PATH.");
+
+		return;
+	}
+
+	DEL_BIT(ses->flags, SES_FLAG_PATHMAPPING);
+
+	script_driver(ses, LIST_COMMAND, root->list[root->used - 1]->arg2);
+
+	SET_BIT(ses->flags, SES_FLAG_PATHMAPPING);
+
+	delete_index_list(root, root->used - 1);
+
+	root->update = root->used;
+
+
+	show_message(ses, LIST_COMMAND, "#PATH MOVE: POSITION SET TO %d.", root->update);
+}
+
+void check_append_path(struct session *ses, char *forward, char *backward, int follow)
+{
+	struct listroot *root = ses->list[LIST_PATH];
+	struct listnode *node;
+
+	if (follow)
+	{
+		if ((node = search_node_list(ses->list[LIST_PATHDIR], forward)))
+		{
+			insert_node_list(root, node->arg1, node->arg2, "0", "");
+
+			root->update = root->used;
+		}
+	}
+	else
+	{
+		if ((node = search_node_list(ses->list[LIST_PATHDIR], forward)))
+		{
+			insert_node_list(root, node->arg1, node->arg2, "0", "");
+		}
+		else
+		{
+			insert_node_list(root, forward, backward, "0", "");
+		}
+	}
+}
+
+
+DO_COMMAND(do_pathdir)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], arg3[BUFFER_SIZE];
+	struct listnode *node;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg3, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		show_list(ses->list[LIST_PATHDIR], 0);
+	}
+	else if (*arg2 == 0)
+	{
+		if (show_node_with_wild(ses, arg1, ses->list[LIST_PATHDIR]) == FALSE)
+		{
+			show_message(ses, LIST_PATHDIR, "#NO MATCH(ES) FOUND FOR {%s}.", arg1);
+		}
+	}
+	else
+	{
+		if (*arg3 == 0)
+		{
+			if ((node = search_node_list(ses->list[LIST_PATHDIR], arg1)) != NULL)
+			{
+				strcpy(arg3, node->arg3);
+			}
+			else
+			{
+				strcpy(arg3, "0");
+			}
+		}
+		else
+		{
+			if (!is_math(ses, arg3) || get_number(ses, arg3) < 0 || get_number(ses, arg3) >= 64)
+			{
+				show_message(ses, LIST_PATHDIR, "#PATHDIR: THE THIRD ARGUMENT MUST BE A NUMBER BETWEEN 0 and 63.");
+				return ses;
+			}
+			get_number_string(ses, arg3, arg3);
+		}
+		update_node_list(ses->list[LIST_PATHDIR], arg1, arg2, arg3, "");
+
+		show_message(ses, LIST_PATHDIR, "#OK: DIRECTION {%s} WILL BE REVERSED AS {%s} @ {%s}.", arg1, arg2, arg3);
+	}
+	return ses;
+}
+
+DO_COMMAND(do_unpathdir)
+{
+	char arg1[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+
+	do
+	{
+		if (delete_nest_node(ses->list[LIST_PATHDIR], arg1))
+		{
+			show_message(ses, LIST_PATHDIR, "#OK. {%s} IS NO LONGER A PATHDIR.", arg1);
+		}
+		else
+		{
+			delete_node_with_wild(ses, LIST_PATHDIR, arg1);
+		}
+		arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	}
+	while (*arg1);
+
+	return ses;
+}
+
+// Old commands, left for backward compatibility
+
+DO_PATH(path_new)
+{
+	struct listroot *root = ses->list[LIST_PATH];
+
+	if (HAS_BIT(ses->flags, SES_FLAG_PATHMAPPING))
+	{
+		show_message(ses, LIST_COMMAND, "#PATH NEW: YOU ARE ALREADY MAPPING A PATH.");
+	}
+	else
+	{
+		kill_list(root);
+
+		root->update = 0;
+
+		show_message(ses, LIST_COMMAND, "#PATH NEW: YOU ARE NOW MAPPING A PATH.");
+
+		SET_BIT(ses->flags, SES_FLAG_PATHMAPPING);
+	}
+}
+
+DO_PATH(path_end)
+{
+	if (HAS_BIT(ses->flags, SES_FLAG_PATHMAPPING))
+	{
+		show_message(ses, LIST_COMMAND, "#PATH END: YOU ARE NO LONGER MAPPING A PATH.");
+
+		DEL_BIT(ses->flags, SES_FLAG_PATHMAPPING);
+	}
+	else
+	{
+		show_message(ses, LIST_COMMAND, "#PATH: YOU ARE NOT MAPPING A PATH.");
+	}
+}
+	

+ 1222 - 0
port.c

@@ -0,0 +1,1222 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2017                       *
+******************************************************************************/
+
+#include "tintin.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+
+#define CALL_TIMEOUT 5
+
+DO_COMMAND(do_port)
+{
+	char cmd[BUFFER_SIZE], arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+	int cnt;
+
+	arg = get_arg_in_braces(ses, arg, cmd, GET_ONE);
+
+	if (*cmd == 0)
+	{
+		tintin_header(ses, " PORT OPTIONS ");
+
+		for (cnt = 0 ; *port_table[cnt].name != 0 ; cnt++)
+		{
+			tintin_printf2(ses, "  [%-13s] %s", port_table[cnt].name, port_table[cnt].desc);
+		}
+		tintin_header(ses, "");
+
+		return ses;
+	}
+
+	for (cnt = 0 ; *port_table[cnt].name != 0 ; cnt++)
+	{
+		if (!is_abbrev(cmd, port_table[cnt].name))
+		{
+			continue;
+		}
+
+		if (port_table[cnt].fun != port_initialize && ses->port == NULL)
+		{
+			tintin_printf(ses, "#PORT: You must initialize a port first.");
+
+			return ses;
+		}
+
+		arg = sub_arg_in_braces(ses, arg, arg1,  port_table[cnt].lval, SUB_VAR|SUB_FUN);
+
+		arg = sub_arg_in_braces(ses, arg, arg2, port_table[cnt].rval, SUB_VAR|SUB_FUN);
+
+		ses = port_table[cnt].fun(ses, arg1, arg2, arg);
+
+		return ses;
+	}
+
+	do_port(ses, "");
+
+	return ses;
+}
+
+
+DO_PORT(port_initialize)
+{
+	char temp[BUFFER_SIZE], file[BUFFER_SIZE];
+	struct sockaddr_in sa;
+	struct linger ld;
+	int sock, port, reuse = 1;
+
+	arg = sub_arg_in_braces(ses, arg, file,  GET_ONE, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0 || *arg2 == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #PORT INITIALIZE {NAME} {PORT} {FILE}");
+		
+		return ses;
+	}
+
+	if (find_session(arg1))
+	{
+		tintin_printf(ses, "#PORT INITIALIZE: THERE'S A SESSION NAMED {%s} ALREADY.", arg1);
+
+		return ses;
+	}
+
+	if (!is_number(arg2))
+	{
+		tintin_printf(ses, "#PORT INITIALIZE: {%s} IS NOT A VALID PORT NUMBER.", arg2);
+
+		return ses;
+	}
+
+	tintin_printf(ses, "#TRYING TO LAUNCH '%s' ON PORT '%s'.", arg1, arg2);
+
+	sprintf(temp, "{localport} {%d} {%s}", atoi(arg2), file);
+
+	port = atoi(arg2);
+
+	sa.sin_family      = AF_INET;
+	sa.sin_addr.s_addr = INADDR_ANY;
+	sa.sin_port        = htons(port);
+
+	sock = socket(AF_INET, SOCK_STREAM, 0);
+
+	if (sock < 0)
+	{
+		syserr_printf(ses, "port_initialize: socket");
+
+		return ses;
+	}
+
+	if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int)) == -1)
+	{
+		syserr_printf(ses, "port_initialize: setsockopt");
+
+		return ses;
+	}
+
+	ld.l_onoff  = 0; 
+	ld.l_linger = 100;
+
+	setsockopt(sock, SOL_SOCKET, SO_LINGER, (char *) &ld, sizeof(ld));
+
+	if (fcntl(sock, F_SETFL, O_NDELAY|O_NONBLOCK) == -1)
+	{
+		syserr_printf(ses, "port_initialize: fcntl O_NDELAY|O_NONBLOCK");
+
+		return ses;
+	}
+
+	if (bind(sock, (struct sockaddr *) &sa, sizeof(sa)) < 0)
+	{
+		tintin_printf(NULL, "#PORT INITIALIZE: PORT %d IS ALREADY IN USE.", port);
+
+		close(sock);
+
+		return ses;
+	}
+
+	if (listen(sock, 32) == -1)
+	{
+		syserr_printf(ses, "port_initialize: listen");
+
+		close(sock);
+
+		return ses;
+	}
+
+	// kind of dodgy, but should be a mere formality.
+
+	ses = new_session(ses, arg1, temp, -1, 0);
+
+	ses->port = (struct port_data *) calloc(1, sizeof(struct port_data));
+
+	ses->port->fd       = sock;
+	ses->port->port     = port;
+
+	ses->port->name     = strdup(arg1);
+	ses->port->group    = strdup("");
+	ses->port->color    = strdup("\e[0;1;36m");
+	ses->port->ip       = strdup("<Unknown>");
+	ses->port->prefix   = strdup("<PORT> ");
+
+	tintin_printf(ses, "#PORT INITIALIZE: SESSION {%s} IS LISTENING ON PORT %d.", ses->name, ses->port->port);
+
+	return ses;
+}
+
+
+DO_PORT(port_uninitialize)
+{
+	int port = ses->port->port;
+
+	while (ses->port->next)
+	{
+		close_port(ses, ses->port->next, TRUE);
+	}
+	close_port(ses, ses->port, FALSE);
+
+	ses->port = NULL;
+
+	tintin_printf(ses, "#PORT UNINITIALIZE: CLOSED PORT {%d}.", port);
+
+	return ses;
+}
+
+
+int port_new(struct session *ses, int sock)
+{
+	struct port_data *new_buddy;
+	struct sockaddr_in sock_addr;
+	socklen_t len;
+	int fd;
+
+	push_call("port_new(%p,%d)",ses,sock);
+
+	len = sizeof(sock);
+
+	getsockname(sock, (struct sockaddr *) &sock_addr, &len);
+
+	if ((fd = accept(sock, (struct sockaddr *) &sock_addr, &len)) < 0)
+	{
+		syserr_printf(ses, "port_new: accept");
+
+		pop_call();
+		return -1;
+	}
+
+	if (fcntl(fd, F_SETFL, O_NDELAY|O_NONBLOCK) == -1)
+	{
+		syserr_printf(ses, "port_new: fcntl O_NDELAY|O_NONBLOCK");
+	}
+
+	if (HAS_BIT(ses->port->flags, PORT_FLAG_DND))
+	{
+		close(fd);
+
+		pop_call();
+		return -1;
+	}
+
+	if (HAS_BIT(ses->port->flags, PORT_FLAG_PRIVATE))
+	{
+		if (strcmp(inet_ntoa(sock_addr.sin_addr), "127.0.0.1"))
+		{
+			port_printf(ses, "%s D%d Refusing remote connection, private flag set.", inet_ntoa(sock_addr.sin_addr), fd);
+
+			close(fd);
+
+			pop_call();
+			return -1;
+		}
+	}
+
+	new_buddy = (struct port_data *) calloc(1, sizeof(struct port_data));
+
+	new_buddy->fd       = fd;
+
+	new_buddy->name     = strdup(ntos(fd));
+	new_buddy->group    = strdup("");
+	new_buddy->ip       = strdup(inet_ntoa(sock_addr.sin_addr));
+	new_buddy->prefix   = strdup("");
+	new_buddy->color    = strdup("");
+	new_buddy->proxy    = strdup("");
+	new_buddy->ttype    = strdup("");
+
+	new_buddy->port     = 0;
+
+	ses->port->total++;
+
+	LINK(new_buddy, ses->port->next, ses->port->prev);
+
+	port_printf(ses, "New connection: %s D%d.", new_buddy->ip, new_buddy->fd);
+
+	if (HAS_BIT(ses->flags, SES_FLAG_TELNET))
+	{
+		announce_support(ses, new_buddy);
+	}
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 0, 3, "PORT CONNECTION", new_buddy->name, new_buddy->ip, ntos(new_buddy->port));
+
+	pop_call();
+	return 0;
+}
+
+
+
+void close_port(struct session *ses, struct port_data *buddy, int unlink)
+{
+	buddy->flags = 0;
+
+	push_call("close_port(%p,%p,%d)",ses,buddy,unlink);
+
+	if (unlink)
+	{
+		ses->port->total--;
+
+		if (buddy == ses->port->update)
+		{
+			ses->port->update = buddy->next;
+		}
+		UNLINK(buddy, ses->port->next, ses->port->prev);
+	}
+
+	SET_BIT(buddy->flags, PORT_FLAG_LINKLOST);
+
+	if (buddy != ses->port)
+	{
+		if (*buddy->name == 0)
+		{
+			port_printf(ses, "Closing connection to %s D%d", buddy->ip, buddy->fd);
+		}
+		else
+		{
+			port_printf(ses, "Closing connection to %s@%s D%d.", buddy->name, buddy->ip, buddy->fd);
+		}
+	}
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 0, 3, "PORT DISCONNECTION", buddy->name, buddy->ip, ntos(buddy->port));
+
+	if (close(buddy->fd) == -1)
+	{
+		syserr_printf(ses, "close_port: close");
+	}
+
+	end_mccp2(ses, buddy);
+	end_mccp3(ses, buddy);
+
+	free(buddy->group);
+	free(buddy->ip);
+	free(buddy->name);
+	free(buddy->prefix);
+	free(buddy->color);
+
+	free(buddy);
+
+	pop_call();
+	return;
+}
+
+
+void process_port_connections(struct session *ses, fd_set *read_set, fd_set *write_set, fd_set *exc_set)
+{
+	struct port_data *buddy;
+
+	push_call("process_port_connections(%p,%p,%p)",read_set,write_set,exc_set);
+
+	if (FD_ISSET(ses->port->fd, read_set))
+	{
+		port_new(ses, ses->port->fd);
+	}
+
+	for (buddy = ses->port->next ; buddy ; buddy = ses->port->update)
+	{
+		ses->port->update = buddy->next;
+
+		if (HAS_BIT(buddy->flags, PORT_FLAG_LINKLOST) || FD_ISSET(buddy->fd, exc_set))
+		{
+			FD_CLR(buddy->fd, write_set);
+			FD_CLR(buddy->fd, read_set);
+
+			close_port(ses, buddy, TRUE);
+		}
+		else if (FD_ISSET(buddy->fd, read_set))
+		{
+			if (process_port_input(ses, buddy) < 0)
+			{
+				FD_CLR(buddy->fd, write_set);
+				FD_CLR(buddy->fd, read_set);
+
+				close_port(ses, buddy, TRUE);
+			}
+		}
+
+		if (HAS_BIT(buddy->comm_flags, COMM_FLAG_MSDPUPDATE))
+		{
+			msdp_send_update(ses, buddy);
+		}
+	}
+	pop_call();
+	return;
+}
+
+void port_socket_write(struct session *ses, struct port_data *buddy, char *str, int len)
+{
+	if (!HAS_BIT(buddy->flags, PORT_FLAG_LINKLOST))
+	{
+		if (buddy->mccp2)
+		{
+			write_mccp2(ses, buddy, str, len);
+		}
+		else
+		{
+			if (write(buddy->fd, str, len) < 0)
+			{
+				port_printf(ses, "Lost link to socket '%s'.", buddy->name);
+
+				SET_BIT(buddy->flags, PORT_FLAG_LINKLOST);
+			}
+		}
+	}
+}
+
+void port_socket_printf(struct session *ses, struct port_data *buddy, char *format, ...)
+{
+	char buf[BUFFER_SIZE];
+	va_list args;
+
+	va_start(args, format);
+	vsnprintf(buf, BUFFER_SIZE / 3, format, args);
+	va_end(args);
+
+	port_socket_write(ses, buddy, buf, strlen(buf));
+}
+
+void port_telnet_printf(struct session *ses, struct port_data *buddy, size_t length, char *format, ...)
+{
+	size_t size;
+
+	char buf[BUFFER_SIZE];
+	va_list args;
+
+	va_start(args, format);
+	size = vsprintf(buf, format, args);
+	va_end(args);
+
+	if (size != length && HAS_BIT(ses->telopts, TELOPT_FLAG_DEBUG))
+	{
+		tintin_printf(ses, "DEBUG TELNET: port_telnet_printf size difference: %d vs %d", size, length);
+	}
+
+	port_socket_write(ses, buddy, buf, length);
+}
+
+void port_printf(struct session *ses, char *format, ...)
+{
+	char buf[STRING_SIZE], tmp[STRING_SIZE];
+	va_list args;
+
+	va_start(args, format);
+	vsnprintf(buf, BUFFER_SIZE / 3, format, args);
+	va_end(args);
+
+	sprintf(tmp, "%s%s", ses->port->prefix, buf);
+
+	strip_vt102_codes_non_graph(tmp, buf);
+
+	sprintf(tmp, "%s%s%s", ses->port->color, buf, "\e[0m");
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 0, 2, "PORT MESSAGE", tmp, buf);
+
+	if (!check_all_events(ses, SUB_ARG|SUB_SEC, 0, 2, "CATCH PORT MESSAGE", tmp, buf))
+	{
+		tintin_puts(ses, tmp);
+	}
+}
+
+void port_log_printf(struct session *ses, struct port_data *buddy, char *format, ...)
+{
+	char buf[STRING_SIZE], tmp[STRING_SIZE];
+	va_list args;
+
+	va_start(args, format);
+	vsnprintf(buf, BUFFER_SIZE / 3, format, args);
+	va_end(args);
+
+	sprintf(tmp, "%s%s@%s %s", ses->port->prefix, buddy->name, buddy->ip, buf);
+
+	strip_vt102_codes_non_graph(tmp, buf);
+
+	sprintf(tmp, "%s%s\e[0m", ses->port->color, buf);
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 0, 5, "PORT LOG MESSAGE", buddy->name, buddy->ip, ntos(buddy->fd), tmp, buf);
+
+	if (!check_all_events(ses, SUB_ARG|SUB_SEC, 0, 5, "CATCH PORT LOG MESSAGE", buddy->name, buddy->ip, ntos(buddy->fd), tmp, buf))
+	{
+		tintin_puts(ses, tmp);
+	}
+}
+
+int process_port_input(struct session *ses, struct port_data *buddy)
+{
+	char input[BUFFER_SIZE], *pt1, *pt2;
+	int size, echo;
+
+	push_call("process_port_input(%p)",buddy);
+
+//	while (TRUE)
+	{
+		size = read(buddy->fd, input, BUFFER_SIZE / 4);
+
+		if (size < 0)
+		{
+			if (errno == EWOULDBLOCK)
+			{
+				pop_call();
+				return 0;
+			}
+
+			syserr_printf(ses, "process_port_input: read:");
+
+			pop_call();
+			return -1;
+		}
+
+		if (size == 0)
+		{
+			pop_call();
+			return -1;
+		}
+
+		input[size] = 0;
+
+		echo = buddy->intop;
+
+		buddy->intop += server_translate_telopts(ses, buddy, (unsigned char *) input, size, (unsigned char *) buddy->inbuf, buddy->intop);
+
+		// Handle local echo for Windows telnet
+
+		if (HAS_BIT(buddy->comm_flags, COMM_FLAG_REMOTEECHO))
+		{
+			while (echo < buddy->intop)
+			{
+				switch (input[echo++])
+				{
+					case   8:
+					case 127:
+						input[echo] = '\b';
+						port_socket_printf(ses, buddy, "\b \b");
+						break;
+
+					case '\n':
+						port_socket_printf(ses, buddy, "\r\n");
+						break;
+
+					default:
+						if (HAS_BIT(buddy->comm_flags, COMM_FLAG_PASSWORD))
+						{
+							port_socket_printf(ses, buddy, "*");
+						}
+						else
+						{
+							port_socket_printf(ses, buddy, "%c", input[echo]);
+						}
+						break;
+				}
+			}
+		}
+
+		if (buddy->intop > BUFFER_SIZE / 4)
+		{
+			port_socket_printf(ses, buddy, "\e[1;31mYou overflowed your input buffer, you must reconnect.\n");
+			port_log_printf(ses, buddy, "Buffer overflow, closing connection.");
+			
+			pop_call();
+			return -1;
+		}
+
+		if (buddy->intop)
+		{
+			pt2 = buddy->inbuf;
+
+			while (pt2)
+			{
+				pt1 = pt2;
+				pt2 = strchr(pt1, '\n');
+
+				if (pt2)
+				{
+					*pt2++ = 0;
+
+					get_port_commands(ses, buddy, pt1, pt2 - pt1);
+				}
+			}
+			buddy->intop = strlen(pt1);
+
+			memmove(buddy->inbuf, pt1, buddy->intop);
+		}
+	}
+	pop_call();
+	return 0;
+}
+
+void get_port_commands(struct session *ses, struct port_data *buddy, char *buf, int len)
+{
+	char txt[STRING_SIZE];
+
+	push_call("get_port_commands(%s,%d,%s)",buddy->name,len,buf);
+
+	strip_vt102_codes(buf, txt);
+
+	if (!check_all_events(ses, SUB_ARG|SUB_SEC, 0, 5, "CATCH PORT RECEIVED MESSAGE", buddy->name, buddy->ip, ntos(buddy->port), buf, txt))
+	{
+		port_receive_message(ses, buddy, buf);
+	}
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 0, 5, "PORT RECEIVED MESSAGE", buddy->name, buddy->ip, ntos(buddy->port), buf, txt);
+
+	pop_call();
+	return;
+}
+
+
+void port_receive_message(struct session *ses, struct port_data *buddy, char *txt)
+{
+	if (HAS_BIT(buddy->flags, PORT_FLAG_IGNORE))
+	{
+		return;
+	}
+
+	port_printf(ses, "%s", txt);
+}
+
+DO_PORT(port_call)
+{
+	int sock, error;
+	char host[BUFFER_SIZE], port[BUFFER_SIZE];
+	struct addrinfo *address;
+	static struct addrinfo hints;
+	struct port_data *new_buddy;
+	struct timeval to;
+	fd_set wds, rds;
+
+	to.tv_sec = CALL_TIMEOUT;
+	to.tv_usec = 0;
+
+	strcpy(host, arg1);
+	strcpy(port, arg2);
+
+	port_printf(ses, "Attempting to call {%s} {%s} ...", host, port);
+
+	hints.ai_family   = AF_INET;
+	hints.ai_protocol = IPPROTO_TCP;
+	hints.ai_socktype = SOCK_STREAM;
+
+	error = getaddrinfo(host, port, &hints, &address);
+
+	if (error)
+	{
+		hints.ai_family = AF_INET6;
+
+		error = getaddrinfo(host, port, &hints, &address);
+
+		if (error)
+		{
+			port_printf(ses, "Failed to call %s, unknown host.", host);
+			
+			return ses;
+		}
+	}
+
+	sock = socket(address->ai_family, address->ai_socktype, address->ai_protocol);
+
+	if (sock < 0)
+	{
+		syserr_printf(ses, "port_call: socket");
+
+		freeaddrinfo(address);
+
+		return ses;
+	}
+
+	switch (address->ai_family)
+	{
+		case AF_INET:
+			inet_ntop(address->ai_family, &((struct sockaddr_in *)address->ai_addr)->sin_addr, host, address->ai_addrlen);
+			break;
+
+		case AF_INET6:
+			inet_ntop(address->ai_family, &((struct sockaddr_in6 *)address->ai_addr)->sin6_addr, host, address->ai_addrlen);
+			break;
+	}
+
+	error = connect(sock, address->ai_addr, address->ai_addrlen);
+
+	if (error)
+	{
+		syserr_printf(ses, "port_call: connect");
+
+		port_printf(ses, "Failed to connect to %s:%s", host, port);
+
+		close(sock);
+
+		freeaddrinfo(address);
+
+		return ses;
+	}
+
+	freeaddrinfo(address);
+
+	FD_ZERO(&wds);
+
+	FD_SET(sock, &wds);
+
+	error = select(FD_SETSIZE, NULL, &wds, NULL, &to);
+
+	if (error < 0)
+	{
+		syserr_printf(ses, "port_call: select wds:");
+
+		port_printf(ses, "Failed to connect to %s %s", host, port);
+
+		close(sock);
+
+		return ses;
+	}
+
+	if (!FD_ISSET(sock, &wds))
+	{
+		port_printf(ses, "Connection timed out.");
+
+		close(sock);
+
+		return ses;
+	}
+
+	new_buddy = calloc(1, sizeof(struct port_data));
+
+	new_buddy->fd       = sock;
+	new_buddy->port     = atoi(port);
+
+	new_buddy->group    = strdup("");
+	new_buddy->ip       = strdup(host);
+	new_buddy->name     = strdup(ntos(sock));
+	new_buddy->color    = strdup("");
+	new_buddy->prefix   = strdup("");
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 0, 3, "PORT CONNECTION", new_buddy->name, new_buddy->ip, ntos(new_buddy->port));
+
+	FD_ZERO(&rds);
+	FD_SET(sock, &rds);
+
+	to.tv_sec  = CALL_TIMEOUT;
+	to.tv_usec = 0;
+
+	error = select(FD_SETSIZE, &rds, NULL, NULL, &to);
+
+	if (error < 0)
+	{
+		syserr_printf(ses, "port_call: select rds:");
+
+		close_port(ses, new_buddy, FALSE);
+
+		return ses;
+	}
+
+	if (process_port_input(ses, new_buddy) == -1)
+	{
+		FD_CLR(new_buddy->fd, &rds);
+
+		close_port(ses, new_buddy, FALSE);
+
+		return ses;
+	}
+
+	// NULL check because of threading.
+
+	if (ses->port == NULL || *new_buddy->name == 0)
+	{
+		close_port(ses, new_buddy, FALSE);
+
+		return ses;
+	}
+
+	if (fcntl(sock, F_SETFL, O_NDELAY|O_NONBLOCK) == -1)
+	{
+		syserr_printf(ses, "port_new: fcntl O_NDELAY|O_NONBLOCK");
+	}
+
+	ses->port->total++;
+
+	LINK(new_buddy, ses->port->next, ses->port->prev);
+
+	port_printf(ses, "Connection made to %s.", new_buddy->name);
+
+	return ses;
+}
+
+DO_PORT(port_info)
+{
+	tintin_printf2(ses, "Port                 : %d", ses->port->port);
+	tintin_printf2(ses, "Prefix               : %s", ses->port->prefix);
+	tintin_printf2(ses, "Color                : %s", str_convert_meta(ses->port->color, TRUE));
+	tintin_printf2(ses, "DND                  : %s", HAS_BIT(ses->port->flags, PORT_FLAG_DND) ? "On" : "Off");
+
+	return ses;
+}
+
+
+DO_PORT(port_name)
+{
+	struct port_data *buddy;
+
+	buddy = find_port_buddy(ses, arg1);
+
+	substitute(ses, arg2, arg2, SUB_COL|SUB_ESC);
+
+	if (buddy == NULL)
+	{
+		port_printf(ses, "There is no socket named '%s'.", arg1);
+
+		return ses;
+	}
+
+	RESTRING(buddy->name, arg2);
+
+	port_printf(ses, "Name of socket '%s' changed to '%s'.", arg1, buddy->name);
+
+	return ses;
+}
+
+
+DO_PORT(port_prefix)
+{
+	RESTRING(ses->port->prefix, arg1);
+
+	port_printf(ses, "Prefix set to '%s'", ses->port->prefix);
+
+	return ses;
+}
+
+DO_PORT(port_send)
+{
+	struct port_data *buddy;
+
+	substitute(gtd->ses, arg2, arg2, SUB_COL|SUB_ESC|SUB_LNF);
+
+	if (!strcasecmp(arg1, "ALL"))
+	{
+		for (buddy = ses->port->next ; buddy ; buddy = buddy->next)
+		{
+			port_socket_printf(ses, buddy, "%s", arg2);
+		}
+	}
+	else
+	{
+		if ((buddy = find_port_buddy(ses, arg1)) != NULL)
+		{
+			port_socket_printf(ses, buddy, "%s", arg2);
+		}
+		else if (find_port_group(ses, arg1) != NULL)
+		{
+			for (buddy = ses->port->next ; buddy ; buddy = buddy->next)
+			{
+				if (!strcmp(buddy->group, arg1))
+				{
+					port_socket_printf(ses, buddy, "%s", arg2);
+				}
+			}
+		}
+		else
+		{
+			port_printf(ses, "There is no socket named '%s'.", arg1);
+		}
+	}
+
+	return ses;
+}
+
+DO_PORT(port_who)
+{
+	struct port_data *buddy;
+	int cnt = 1;
+
+	tintin_printf(ses, "     %-15s  %-5s  %-20s  %-5s  ", "Name", "Flags", "Address", "Port");
+	tintin_printf(ses, "     ===============  =====  ====================  =====  ");
+
+	for (buddy = ses->port->next ; buddy ; buddy = buddy->next)
+	{
+		tintin_printf(ses, " %03d %+15s  %s%s%s%s%s  %+20s  %+5u",
+			cnt++,
+			buddy->name,
+			HAS_BIT(buddy->flags, PORT_FLAG_PRIVATE)   ? "P" : " ",
+			HAS_BIT(buddy->flags, PORT_FLAG_IGNORE)    ? "I" : " ",
+			HAS_BIT(buddy->flags, PORT_FLAG_SERVE)     ? "S" : " ",
+			HAS_BIT(buddy->flags, PORT_FLAG_FORWARD)   ? "F" :
+			HAS_BIT(buddy->flags, PORT_FLAG_FORWARDBY) ? "f" : " ",
+			" ",
+			buddy->ip,
+			buddy->port);
+	}
+	tintin_printf(ses, "     ===============  =====  ====================  ===== ");
+
+	return ses;
+}
+
+
+DO_PORT(port_zap)
+{
+	struct port_data *buddy;
+
+	if (!strcasecmp(arg1, "ALL"))
+	{
+		while (ses->port->next)
+		{
+			close_port(ses, ses->port->next, TRUE);
+		}
+	}
+	else
+	{
+		if ((buddy = find_port_buddy(ses, arg1)))
+		{
+			close_port(ses, buddy, TRUE);
+		}
+		else
+		{
+			port_printf(ses, "There is no socket named '%s'.", arg1);
+		}
+	}
+
+	return ses;
+}
+
+
+DO_PORT(port_color)
+{
+	if (*arg1 == 0 || get_color_names(gtd->ses, arg1, arg2) == FALSE)
+	{
+		port_printf(ses, "Valid colors are:\n\nreset, bold, dim, light, dark, underscore, blink, reverse, black, red, green, yellow, blue, magenta, cyan, white, b black, b red, b green, b yellow, b blue, b magenta, b cyan, b white");
+
+		return ses;
+	}
+	RESTRING(ses->port->color, arg2);
+
+	port_printf(ses, "Color has been set to %s", arg1);
+
+	return ses;
+}
+
+DO_PORT(port_dnd)
+{
+	TOG_BIT(ses->port->flags, PORT_FLAG_DND);
+
+	if (HAS_BIT(ses->port->flags, PORT_FLAG_DND))
+	{
+		port_printf(ses, "New connections are no longer accepted.");
+	}
+	else
+	{
+		port_printf(ses, "New connections are accepted.");
+	}
+
+	return ses;
+}
+
+DO_PORT(port_flag)
+{
+	if (*arg1 == 0)
+	{
+		tintin_printf2(ses, "#SYNTAX: #PORT FLAG <DND|PRIVATE> [ON|OFF]");
+	}
+	else if (is_abbrev(arg1, "DND"))
+	{
+		if (!strcasecmp(arg2, "ON"))
+		{
+			SET_BIT(ses->port->flags, PORT_FLAG_DND);
+		}
+		else if (!strcasecmp(arg2, "OFF"))
+		{
+			DEL_BIT(ses->port->flags, PORT_FLAG_DND);
+		}
+		else if (*arg2 == 0)
+		{
+			TOG_BIT(ses->port->flags, PORT_FLAG_DND);
+		}
+		else
+		{
+			tintin_printf2(ses, "#SYNTAX: #PORT FLAG DND [ON|OFF]");
+			
+			return ses;
+		}
+
+		if (HAS_BIT(ses->port->flags, PORT_FLAG_DND))
+		{
+			port_printf(ses, "New connections are no longer accepted.");
+		}
+		else
+		{
+			port_printf(ses, "New connections are accepted.");
+		}
+	}
+	else if (is_abbrev(arg1, "PRIVATE"))
+	{
+		if (!strcasecmp(arg2, "ON"))
+		{
+			SET_BIT(ses->port->flags, PORT_FLAG_PRIVATE);
+		}
+		else if (!strcasecmp(arg2, "OFF"))
+		{
+			DEL_BIT(ses->port->flags, PORT_FLAG_PRIVATE);
+		}
+		else if (*arg2 == 0)
+		{
+			TOG_BIT(ses->port->flags, PORT_FLAG_PRIVATE);
+		}
+		else
+		{
+			tintin_printf2(ses, "#SYNTAX: #PORT FLAG PRIVATE [ON|OFF]");
+			
+			return ses;
+		}
+
+		if (HAS_BIT(ses->port->flags, PORT_FLAG_PRIVATE))
+		{
+			port_printf(ses, "Remote connections are no longer accepted.");
+		}
+		else
+		{
+			port_printf(ses, "Remote connections are accepted.");
+		}
+	}
+	return ses;
+}
+
+DO_PORT(port_group)
+{
+	struct port_data *buddy;
+	int cnt = 0;
+
+	if (*arg1 == 0)
+	{
+		tintin_printf(NULL, "     %-15s  %-20s  %-5s  %-15s", "Name", "Address", "Port", "Group");
+		tintin_printf(NULL, "     ===============  ====================  =====  ==================== ");
+
+		for (buddy = ses->port->next ; buddy ; buddy = buddy->next)
+		{
+			tintin_printf(NULL, " %03d %-15s  %-20s  %-5u  %-20s",
+				cnt++,
+				buddy->name,
+				buddy->ip,
+				buddy->port,
+				buddy->group);
+		}
+		tintin_printf(NULL, "     ===============  ====================  =====  ==================== ");
+	}
+	else if (!strcasecmp(arg1, "ALL"))
+	{
+		port_printf(ses, "You set everyone's group to '%s'", arg2);
+
+		for (buddy = ses->port->next ; buddy ; buddy = buddy->next)
+		{
+			RESTRING(buddy->group, arg2);
+		}
+	}
+	else
+	{
+		if ((buddy = find_port_buddy(ses, arg1)) != NULL)
+		{
+			RESTRING(buddy->group, arg2);
+
+			port_printf(ses, "You set %s's group to '%s'", buddy->name, arg2);
+		}
+		else
+		{
+			port_printf(ses, "You are not connected to anyone named '%s'.", arg1);
+		}
+	}
+
+	return ses;
+}
+
+
+DO_PORT(port_ignore)
+{
+	struct port_data *buddy;
+
+	if ((buddy = find_port_buddy(ses, arg1)) == NULL)
+	{
+		port_printf(ses, "You are not connected to anyone named '%s'.", arg1);
+
+		return ses;
+	}
+
+	TOG_BIT(buddy->flags, PORT_FLAG_IGNORE);
+
+	if (HAS_BIT(buddy->flags, PORT_FLAG_IGNORE))
+	{
+		port_printf(ses, "You are now ignoring %s.", buddy->name);
+	}
+	else
+	{
+		port_printf(ses, "You are no longer ignoring %s.", buddy->name);
+	}
+	
+	return ses;
+}
+
+
+
+struct port_data *find_port_buddy(struct session *ses, char *arg)
+{
+	struct port_data *buddy;
+
+	if (*arg == 0)
+	{
+		return NULL;
+	}
+
+	if (is_number(arg))
+	{
+		for (buddy = ses->port->next ; buddy ; buddy = buddy->next)
+		{
+			if (atoi(arg) == buddy->fd)
+			{
+				return buddy;
+			}
+		}
+	}
+
+	for (buddy = ses->port->next ; buddy ; buddy = buddy->next)
+	{
+		if (!strcmp(arg, buddy->ip))
+		{
+			return buddy;
+		}
+	}
+
+	for (buddy = ses->port->next ; buddy ; buddy = buddy->next)
+	{
+		if (is_abbrev(arg, buddy->name))
+		{
+			return buddy;
+		}
+	}
+
+	return NULL;
+}
+
+
+struct port_data *find_port_group(struct session *ses, char *arg)
+{
+	struct port_data *buddy;
+
+	if (*arg == 0)
+	{
+		return NULL;
+	}
+
+	for (buddy = ses->port->next ; buddy ; buddy = buddy->next)
+	{
+		if (!strcmp(arg, buddy->group))
+		{
+			return buddy;
+		}
+	}
+	return NULL;
+}
+
+DO_PORT(port_rank)
+{
+	struct port_data *buddy;
+	int cnt, rank;
+
+	if (*arg1 == 0)
+	{
+		tintin_printf(NULL, "     %-15s  %-20s  %-5s  %-15s", "Name", "Address", "Port", "Rank");
+		tintin_printf(NULL, "     ===============  ====================  =====  ==================== ");
+
+		cnt = 0;
+
+		for (buddy = ses->port->next ; buddy ; buddy = buddy->next)
+		{
+			tintin_printf(NULL, " %03d %-15s  %-20s  %-5u  %-20s",
+				cnt++,
+				buddy->name,
+				buddy->ip,
+				buddy->port,
+				rank_table[buddy->rank].name);
+		}
+		tintin_printf(NULL, "     ===============  ====================  =====  ==================== ");
+
+		return ses;
+	}
+
+	if (*arg2 == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #PORT RANK <NAME> <SPY|SCOUT>");
+
+		return ses;
+	}
+	else if (is_abbrev(arg2, "SPY"))
+	{
+		rank = PORT_RANK_SPY;
+	}
+	else if (is_abbrev(arg2, "SCOUT"))
+	{
+		rank = PORT_RANK_SCOUT;
+	}
+	else
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #PORT RANK <NAME> <SPY|SCOUT>");
+		
+		return ses;
+	}
+
+	if (!strcasecmp(arg1, "ALL"))
+	{
+		port_printf(ses, "You set everyone's rank to '%s'", rank_table[rank].name);
+
+		for (buddy = ses->port->next ; buddy ; buddy = buddy->next)
+		{
+			buddy->rank = rank;
+		}
+	}
+	else
+	{
+		if ((buddy = find_port_buddy(ses, arg1)) != NULL)
+		{
+			buddy->rank = rank;
+
+			port_printf(ses, "YOU SET %s'S RANK TO '%s'", buddy->name, rank_table[buddy->rank]);
+		}
+		else
+		{
+			port_printf(ses, "YOU ARE NOT CONNECTED TO ANYONE NAMED '%s'.", arg1);
+		}
+	}
+	return ses;
+}

+ 1200 - 0
regex.c

@@ -0,0 +1,1200 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2004                       *
+******************************************************************************/
+
+#include <sys/types.h>
+#include <pcre.h>
+
+#include "tintin.h"
+
+
+int match(struct session *ses, char *str, char *exp, int sub)
+{
+	char expbuf[BUFFER_SIZE];
+
+	sprintf(expbuf, "\\A%s\\Z", exp);
+
+	substitute(ses, expbuf, expbuf, sub);
+
+	return tintin_regexp(ses, NULL, str, expbuf, 0, 0);
+}
+
+int find(struct session *ses, char *str, char *exp, int sub, int flag)
+{
+	if (HAS_BIT(sub, SUB_VAR|SUB_FUN))
+	{
+		char expbuf[BUFFER_SIZE], strbuf[BUFFER_SIZE];
+
+		substitute(ses, str, strbuf, SUB_VAR|SUB_FUN);
+		substitute(ses, exp, expbuf, SUB_VAR|SUB_FUN);
+
+		return tintin_regexp(ses, NULL, strbuf, expbuf, 0, flag);
+	}
+	else
+	{
+		return tintin_regexp(ses, NULL, str, exp, 0, flag);
+	}
+}
+
+DO_COMMAND(do_regexp)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], is_t[BUFFER_SIZE], is_f[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = get_arg_in_braces(ses, arg, is_t, GET_ALL);
+	arg = get_arg_in_braces(ses, arg, is_f, GET_ALL);
+
+	if (*is_t == 0)
+	{
+		show_error(ses, LIST_COMMAND, "SYNTAX: #REGEXP {string} {expression} {true} {false}.");
+	}
+	else
+	{
+		if (tintin_regexp(ses, NULL, arg1, arg2, 0, REGEX_FLAG_CMD))
+		{
+			substitute(ses, is_t, is_t, SUB_CMD);
+
+			ses = script_driver(ses, LIST_COMMAND, is_t);
+		}
+		else if (*is_f)
+		{
+			ses = script_driver(ses, LIST_COMMAND, is_f);
+		}
+	}
+	return ses;
+}
+
+int regexp_compare(struct session *ses, pcre *nodepcre, char *str, char *exp, int option, int flag)
+{
+	pcre *regex;
+	int i, j, matches, match[303];
+
+	if (nodepcre == NULL)
+	{
+		regex = regexp_compile(ses, exp, option);
+	}
+	else
+	{
+		regex = nodepcre;
+	}
+
+	if (regex == NULL)
+	{
+		return FALSE;
+	}
+
+	matches = pcre_exec(regex, NULL, str, strlen(str), 0, 0, match, 303);
+
+	if (matches <= 0)
+	{
+		if (nodepcre == NULL)
+		{
+			free(regex);
+		}
+		return FALSE;
+	}
+
+	// REGEX_FLAG_FIX handles %1 to %99 usage. Backward compatibility.
+
+	switch (flag)
+	{
+		case REGEX_FLAG_CMD:
+			for (i = 0 ; i < matches ; i++)
+			{
+				gtd->cmds[i] = restringf(gtd->cmds[i], "%.*s", match[i*2+1] - match[i*2], &str[match[i*2]]);
+			}
+			break;
+
+		case REGEX_FLAG_CMD + REGEX_FLAG_FIX:
+			for (i = 0 ; i < matches ; i++)
+			{
+				j = gtd->args[i];
+
+				gtd->cmds[j] = restringf(gtd->cmds[j], "%.*s", match[i*2+1] - match[i*2], &str[match[i*2]]);
+			}
+			break;
+
+		case REGEX_FLAG_ARG:
+			for (i = 0 ; i < matches ; i++)
+			{
+				gtd->vars[i] = restringf(gtd->vars[i], "%.*s", match[i*2+1] - match[i*2], &str[match[i*2]]);
+			}
+			break;
+
+		case REGEX_FLAG_ARG + REGEX_FLAG_FIX:
+			for (i = 0 ; i < matches ; i++)
+			{
+				j = gtd->args[i];
+
+				gtd->vars[j] = restringf(gtd->vars[j], "%.*s", match[i*2+1] - match[i*2], &str[match[i*2]]);
+			}
+			break;
+	}
+
+	if (nodepcre == NULL)
+	{
+		free(regex);
+	}
+
+	return TRUE;
+}
+
+pcre *regexp_compile(struct session *ses, char *exp, int option)
+{
+	const char *error;
+	int i;
+/*
+	if (HAS_BIT(ses->charset, CHARSET_FLAG_UTF8))
+	{
+		option |= PCRE_UTF8|PCRE_NO_UTF8_CHECK;
+	}
+*/
+	return pcre_compile(exp, option, &error, &i, NULL);
+}
+
+
+
+
+/******************************************************************************
+* Calls tintin_regexp checking if the string matches, and automatically fills *
+* in the text represented by the wildcards on success.                        *
+******************************************************************************/
+
+int check_one_regexp(struct session *ses, struct listnode *node, char *line, char *original, int option)
+{
+	char *exp, *str;
+
+	if (node->regex == NULL)
+	{
+		char result[BUFFER_SIZE];
+
+		substitute(ses, node->arg1, result, SUB_VAR|SUB_FUN);
+
+		exp = result;
+	}
+	else
+	{
+		exp = node->arg1;
+	}
+
+	if (*node->arg1 == '~')
+	{
+		exp++;
+		str = original;
+	}
+	else
+	{
+		str = line;
+	}	
+
+	return tintin_regexp(ses, node->regex, str, exp, option, REGEX_FLAG_ARG);
+}
+
+/*
+	Keep synched with tintin_regexp and tintin_regexp_compile
+*/
+
+int get_regex_range(char *in, char *out, int *var, int *arg)
+{
+	char *pti, *pto, *ptr, range[BUFFER_SIZE];
+
+	pto = out;
+	pti = in;
+	ptr = range;
+
+	while (*pti)
+	{
+		switch (*pti)
+		{
+			case '0':
+			case '1':
+			case '2':
+			case '3':
+			case '4':
+			case '5':
+			case '6':
+			case '7':
+			case '8':
+			case '9':
+				*ptr++ = *pti++;
+				continue;
+
+			case '.':
+				if (pti[1] != '.')
+				{
+					goto end;
+				}
+				if (ptr == range)
+				{
+					*ptr++ = '0';
+				}
+				*ptr++ = ',';
+				pti += 2;
+				continue;
+
+			case 'a':
+				pto += sprintf(pto, "(.");
+				break;
+			case 'A':
+				pto += sprintf(pto, "(\\n");
+				break;
+			case 'd':
+				pto += sprintf(pto, "([0-9]");
+				break;
+			case 'D':
+				pto += sprintf(pto, "([^0-9]");
+				break;
+			case 'p':
+				pto += sprintf(pto, "([\\x20-\\xfe]");
+				break;
+			case 'P':
+				pto += sprintf(pto, "([^\\x20-\\xfe]");
+				break;
+			case 's':
+				pto += sprintf(pto, "(\\s");
+				break;
+			case 'S':
+				pto += sprintf(pto, "(\\S");
+				break;
+			case 'u':
+				pto += sprintf(pto, "((?:[\\xC0-\\xFE][\\x80-\\xC0]{1,3})");
+				break;
+			case 'U':
+				pto += sprintf(pto, "([\\x00-\\x7F\\xFF]");
+				break;
+			case 'w':
+				pto += sprintf(pto, "([a-zA-Z]");
+				break;
+			case 'W':
+				pto += sprintf(pto, "([^a-zA-Z]");
+				break;
+
+			default:
+				goto end;
+		}
+		*ptr = 0;
+		pti++;
+
+		pto += sprintf(pto, "{%s}%s", range, *pti ? "?)" : ")");
+
+		return pti - in;
+	}
+	end:
+
+	if (var)
+	{
+		gtd->args[next_arg(*var)] = next_arg(*arg);
+	}
+	strcpy(out, in[2] == 0 ? "(.+)" : "(.+?)");
+
+	return 0;
+}
+
+
+int tintin_regexp_check(struct session *ses, char *exp)
+{
+	if (*exp == '^')
+	{
+		return TRUE;
+	}
+
+	while (*exp)
+	{
+
+		if (HAS_BIT(ses->charset, CHARSET_FLAG_EUC) && is_euc_head(ses, exp))
+		{
+			exp += 2;
+			continue;
+		}
+
+		switch (exp[0])
+		{
+			case '\\':
+			case '{':
+				return TRUE;
+
+			case '$':
+				if (exp[1] == 0)
+				{
+					return TRUE;
+				}
+				break;
+
+			case '%':
+				switch (exp[1])
+				{
+					case '0':
+					case '1':
+					case '2':
+					case '3':
+					case '4':
+					case '5':
+					case '6':
+					case '7':
+					case '8':
+					case '9':
+
+					case 'a':
+					case 'A':
+					case 'd':
+					case 'D':
+					case 'i':
+					case 'I':
+					case 'p':
+					case 'P':
+					case 's':
+					case 'S':
+					case 'u':
+					case 'U':
+					case 'w':
+					case 'W':
+					case '?':
+					case '*':
+					case '+':
+					case '.':
+					case '%':
+						return TRUE;
+
+					case '!':
+						switch (exp[2])
+						{
+							case 'a':
+							case 'A':
+							case 'd':
+							case 'D':
+							case 'p':
+							case 'P':
+							case 's':
+							case 'S':
+							case 'u':
+							case 'U':
+							case 'w':
+							case 'W':
+							case '?':
+							case '*':
+							case '+':
+							case '.':
+							case '{':
+								return TRUE;
+						}
+						break;
+				}
+				break;
+		}
+		exp++;
+	}
+	return FALSE;
+}
+
+int tintin_regexp(struct session *ses, pcre *nodepcre, char *str, char *exp, int option, int flag)
+{
+	char out[BUFFER_SIZE], *pti, *pto;
+	int arg = 1, var = 1, fix = 0;
+
+	pti = exp;
+	pto = out;
+
+	while (*pti == '^')
+	{
+		*pto++ = *pti++;
+	}
+
+	while (*pti)
+	{
+		if (HAS_BIT(ses->charset, CHARSET_FLAG_EUC) && is_euc_head(ses, pti))
+		{
+			*pto++ = *pti++;
+
+			switch (*pti)
+			{
+				case '\\':
+				case '[':
+				case ']':
+				case '(':
+				case ')':
+				case '|':
+				case '.':
+				case '?':
+				case '+':
+				case '*':
+				case '$':
+				case '^':
+					*pto++ = '\\';
+					break;
+			}
+			*pto++ = *pti++;
+			continue;
+		}
+
+		switch (pti[0])
+		{
+			case '\\':
+				*pto++ = *pti++;
+				*pto++ = *pti++;
+				break;
+
+			case '{':
+				gtd->args[next_arg(var)] = next_arg(arg);
+				*pto++ = '(';
+				pti = get_arg_in_braces(ses, pti, pto, GET_ALL);
+				pto += strlen(pto);
+				*pto++ = ')';
+				break;
+
+			case '[':
+			case ']':
+			case '(':
+			case ')':
+			case '|':
+			case '.':
+			case '?':
+			case '+':
+			case '*':
+			case '^':
+				*pto++ = '\\';
+				*pto++ = *pti++;
+				break;
+
+			case '$':
+				if (pti[1] != DEFAULT_OPEN && !isalnum((int) pti[1]))
+				{
+					int i = 0;
+
+					while (pti[++i] == '$')
+					{
+						continue;
+					}
+
+					if (pti[i])
+					{
+						*pto++ = '\\';
+					}
+				}
+				*pto++ = *pti++;
+				break;
+
+			case '%':
+				switch (pti[1])
+				{
+					case '0':
+					case '1':
+					case '2':
+					case '3':
+					case '4':
+					case '5':
+					case '6':
+					case '7':
+					case '8':
+					case '9':
+						fix = REGEX_FLAG_FIX;
+						arg = isdigit((int) pti[2]) ? (pti[1] - '0') * 10 + (pti[2] - '0') : pti[1] - '0';
+						gtd->args[next_arg(var)] = next_arg(arg);
+						pti += isdigit((int) pti[2]) ? 3 : 2;
+						strcpy(pto, *pti == 0 ? "(.*)" : "(.*?)");
+						pto += strlen(pto);
+						break;
+
+					case 'a':
+						gtd->args[next_arg(var)] = next_arg(arg);
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "([^\\n]*)" : "([^\\n]*?)");
+						pto += strlen(pto);
+						break;
+
+					case 'A':
+						gtd->args[next_arg(var)] = next_arg(arg);
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "(\\n*)" : "(\\n*?)");
+						pto += strlen(pto);
+						break;
+
+					case 'd':
+						gtd->args[next_arg(var)] = next_arg(arg);
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "([0-9]*)" : "([0-9]*?)");
+						pto += strlen(pto);
+						break;
+
+					case 'D':
+						gtd->args[next_arg(var)] = next_arg(arg);
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "([^0-9]*)" : "([^0-9]*?)");
+						pto += strlen(pto);
+						break;
+
+					case 'i':
+						pti += 2;
+						strcpy(pto, "(?i)");
+						pto += strlen(pto);
+						break;
+
+					case 'I':
+						pti += 2;
+						strcpy(pto, "(?-i)");
+						pto += strlen(pto);
+						break;
+
+					case 'p':
+						gtd->args[next_arg(var)] = next_arg(arg);
+						pti += 2;
+						pto += sprintf(pto, "%s", *pti == 0 ? "([\\x20-\\xfe]*)" : "([\\x20-\\xfe]*?)");
+						break;
+
+					case 'P':
+						gtd->args[next_arg(var)] = next_arg(arg);
+						pti += 2;
+						pto += sprintf(pto, "%s", *pti == 0 ? "([^\\x20-\\xfe]*)" : "([^\\x20-\\xfe]*?)");
+						break;
+
+					case 's':
+						gtd->args[next_arg(var)] = next_arg(arg);
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "(\\s*)" : "(\\s*?)");
+						pto += strlen(pto);
+						break;
+
+					case 'S':
+						gtd->args[next_arg(var)] = next_arg(arg);
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "(\\S*)" : "(\\S*?)");
+						pto += strlen(pto);
+						break;
+
+					case 'u':
+						gtd->args[next_arg(var)] = next_arg(arg);
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "((?:[\\x00-\\x7F|\\xC0-\\xFE][\\x80-\\xC0]{1,3})*)" : "((?:[\\xC0-\\xFE][\\x80-\\xC0]{1,3})*?)");
+						pto += strlen(pto);
+						break;
+
+					case 'U':
+						gtd->args[next_arg(var)] = next_arg(arg);
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "(^[\xFF]*)" : "([\\x00-\\x7F\\xFF]*?)");
+						pto += strlen(pto);
+						break;
+
+					case 'w':
+						gtd->args[next_arg(var)] = next_arg(arg);
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "([a-zA-Z]*)" : "([a-zA-Z]*?)");
+						pto += strlen(pto);
+						break;
+
+					case 'W':
+						gtd->args[next_arg(var)] = next_arg(arg);
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "([^a-zA-Z]*)" : "([^a-zA-Z]*?)");
+						pto += strlen(pto);
+						break;
+
+					case '*':
+						gtd->args[next_arg(var)] = next_arg(arg);
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "(.*)" : "(.*?)");
+						pto += strlen(pto);
+						break;
+
+					case '+':
+						pti += 2 + get_regex_range(&pti[2], pto, &var, &arg);
+						pto += strlen(pto);
+						break;
+
+					case '%':
+						*pto++ = *pti++;
+						pti++;
+						break;
+
+					case '.':
+						gtd->args[next_arg(var)] = next_arg(arg);
+						pti += 2;
+						strcpy(pto, "(.)");
+						pto += strlen(pto);
+						break;
+
+					case '?':
+						gtd->args[next_arg(var)] = next_arg(arg);
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "(.?)" : "(.?" "?)");
+						pto += strlen(pto);
+						break;
+
+					case '!':
+						switch (pti[2])
+						{
+							case 'a':
+								gtd->args[next_arg(var)] = next_arg(arg);
+								pti += 2;
+								strcpy(pto, *pti == 0 ? "[^\\n]*" : "[^\\n]*?");
+								pto += strlen(pto);
+								break;
+
+							case 'A':
+								gtd->args[next_arg(var)] = next_arg(arg);
+								pti += 2;
+								strcpy(pto, *pti == 0 ? "\\n*" : "\\n*?");
+								pto += strlen(pto);
+								break;
+
+							case 'd':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? "[0-9]*" : "[0-9]*?");
+								pto += strlen(pto);
+								break;
+
+							case 'D':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? "[^0-9]*" : "[^0-9]*?");
+									pto += strlen(pto);
+								break;
+
+							case 'p':
+								pti += 3;
+								pto += sprintf(pto, "%s", *pti == 0 ? "[\\x20-\\xfe]*" : "[\\x20-\\xfe]*?");
+								break;
+
+							case 'P':
+								pti += 3;
+								pto += sprintf(pto, "%s", *pti == 0 ? "[^\\x20-\\xfe]*" : "[^\\x20-\\xfe]*?");
+								break;
+
+							case 's':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? "\\s*" : "\\s*?");
+								pto += strlen(pto);
+								break;
+
+							case 'S':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? "\\S*" : "\\S*?");
+								pto += strlen(pto);
+								break;
+
+							case 'u':
+								gtd->args[next_arg(var)] = next_arg(arg);
+								pti += 3;
+								strcpy(pto, *pti == 0 ? "(?:[\\xC0-\\xFE][\\x80-\\xC0]{1,3})*" : "(?:[\\xC0-\\xFE][\\x80-\\xC0]{1,3})*?");
+								pto += strlen(pto);
+								break;
+
+							case 'U':
+								gtd->args[next_arg(var)] = next_arg(arg);
+								pti += 3;
+								strcpy(pto, *pti == 0 ? "[\\x00-\\x7F\\xFF]*" : "[\\x00-\\x7F\\xFF]*?");
+								pto += strlen(pto);
+								break;
+
+							case 'w':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? "[a-zA-Z]*" : "[a-zA-Z]*?");
+								pto += strlen(pto);
+								break;
+
+							case 'W':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? "[^a-zA-Z]*" : "[^a-zA-Z]*?");
+								pto += strlen(pto);
+								break;
+
+							case '?':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? ".?" : ".?" "?");
+								pto += strlen(pto);
+								break;
+
+							case '*':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? ".*" : ".*?");
+								pto += strlen(pto);
+								break;
+
+							case '+':
+								pti += 3 + get_regex_range(&pti[3], pto, NULL, NULL);
+								pto += strlen(pto);
+								break;
+
+							case '.':
+								pti += 3;
+								strcpy(pto, ".");
+								pto += strlen(pto);
+								break;
+
+							case '{':
+								pti = get_arg_in_braces(ses, pti+2, pto, GET_ALL);
+								pto += strlen(pto);
+								break;
+
+							default:
+								*pto++ = *pti++;
+								break;
+						}
+						break;
+
+					default:
+						*pto++ = *pti++;
+						break;
+				}
+				break;
+
+			default:
+				*pto++ = *pti++;
+				break;
+		}
+	}
+	*pto = 0;
+
+	return regexp_compare(ses, nodepcre, str, out, option, flag + fix);
+}
+
+pcre *tintin_regexp_compile(struct session *ses, struct listnode *node, char *exp, int option)
+{
+	char out[BUFFER_SIZE], *pti, *pto;
+
+	pti = exp;
+	pto = out;
+
+	if (*pti == '~')
+	{
+		pti++;
+	}
+
+	while (*pti == '^')
+	{
+		*pto++ = *pti++;
+	}
+
+	while (*pti)
+	{
+		if (HAS_BIT(ses->charset, CHARSET_FLAG_EUC) && is_euc_head(ses, pti))
+		{
+			*pto++ = *pti++;
+
+			switch (*pti)
+			{
+				case '\\':
+				case '[':
+				case ']':
+				case '(':
+				case ')':
+				case '|':
+				case '.':
+				case '?':
+				case '+':
+				case '*':
+				case '$':
+				case '^':
+					*pto++ = '\\';
+					break;
+			}
+			*pto++ = *pti++;
+			continue;
+		}
+
+		switch (pti[0])
+		{
+			case '\\':
+				*pto++ = *pti++;
+				*pto++ = *pti++;
+				break;
+
+			case '{':
+				*pto++ = '(';
+				pti = get_arg_in_braces(ses, pti, pto, GET_ALL);
+				while (*pto)
+				{
+					if (pto[0] == '$' || pto[0] == '@')
+					{
+						if (pto[1])
+						{
+							return NULL;
+						}
+					}
+					pto++;
+				}
+				*pto++ = ')';
+				break;
+
+			case '&':
+				if (pti[1] == DEFAULT_OPEN || isalnum((int) pti[1]) || pti[1] == '&')
+				{
+					return NULL;
+				}
+				*pto++ = *pti++;
+				break;
+
+			case '@':
+				if (pti[1] == DEFAULT_OPEN || isalnum((int) pti[1]) || pti[1] == '@')
+				{
+					return NULL;
+				}
+				*pto++ = *pti++;
+				break;
+
+			case '$':
+				if (pti[1] == DEFAULT_OPEN || isalnum((int) pti[1]))
+				{
+					return NULL;
+				}
+				{
+					int i = 0;
+
+					while (pti[++i] == '$')
+					{
+						continue;
+					}
+
+					if (pti[i])
+					{
+						*pto++ = '\\';
+					}
+				}
+				*pto++ = *pti++;
+				break;
+
+			case '[':
+			case ']':
+			case '(':
+			case ')':
+			case '|':
+			case '.':
+			case '?':
+			case '+':
+			case '*':
+			case '^':
+				*pto++ = '\\';
+				*pto++ = *pti++;
+				break;
+
+			case '%':
+				switch (pti[1])
+				{
+					case '0':
+					case '1':
+					case '2':
+					case '3':
+					case '4':
+					case '5':
+					case '6':
+					case '7':
+					case '8':
+					case '9':
+						pti += isdigit((int) pti[2]) ? 3 : 2;
+						strcpy(pto, *pti == 0 ? "(.*)" : "(.*?)");
+						pto += strlen(pto);
+						break;
+
+					case 'd':
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "([0-9]*)" : "([0-9]*?)");
+						pto += strlen(pto);
+						break;
+
+					case 'D':
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "([^0-9]*)" : "([^0-9]*?)");
+						pto += strlen(pto);
+						break;
+
+					case 'i':
+						pti += 2;
+						strcpy(pto, "(?i)");
+						pto += strlen(pto);
+						break;
+
+					case 'I':
+						pti += 2;
+						strcpy(pto, "(?-i)");
+						pto += strlen(pto);
+						break;
+
+					case 'p':
+						pti += 2;
+						pto += sprintf(pto, "%s", *pti == 0 ? "([\\x20-\\xfe]*)" : "([\\x20-\\xfe]*?)");
+						break;
+
+					case 'P':
+						pti += 2;
+						pto += sprintf(pto, "%s", *pti == 0 ? "([^\\x20-\\xfe]*)" : "([^\\x20-\\xfe]*?)");
+						break;
+						
+					case 's':
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "(\\s*)" : "(\\s*?)");
+						pto += strlen(pto);
+						break;
+
+					case 'S':
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "(\\S*)" : "(\\S*?)");
+						pto += strlen(pto);
+						break;
+
+					case 'u':
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "((?:[\\xC0-\\xFE][\\x80-\\xC0]{1,3})*)" : "((?:[\\xC0-\\xFE][\\x80-\\xC0]{1,3})*?)");
+						pto += strlen(pto);
+						break;
+
+					case 'U':
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "([\\x00-\\x7F\\xFF]*)" : "([\\x00-\\x7F\\xFF]*?)");
+						pto += strlen(pto);
+						break;
+
+					case 'w':
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "([a-zA-Z]*)" : "([a-zA-Z]*?)");
+						pto += strlen(pto);
+						break;
+
+					case 'W':
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "([^a-zA-Z]*)" : "([^a-zA-Z]*?)");
+						pto += strlen(pto);
+						break;
+
+					case '?':
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "(.?)" : "(.?" "?)");
+						pto += strlen(pto);
+						break;
+
+					case '*':
+						pti += 2;
+						strcpy(pto, *pti == 0 ? "(.*)" : "(.*?)");
+						pto += strlen(pto);
+						break;
+
+					case '+':
+						pti += 2 + get_regex_range(&pti[2], pto, NULL, NULL);
+						pto += strlen(pto);
+						break;
+
+					case '.':
+						pti += 2;
+						strcpy(pto, "(.)");
+						pto += strlen(pto);
+						break;
+
+					case '%':
+						*pto++ = *pti++;
+						pti++;
+						break;
+
+					case '!':
+						switch (pti[2])
+						{
+							case 'd':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? "[0-9]*" : "[0-9]*?");
+								pto += strlen(pto);
+								break;
+
+							case 'D':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? "[^0-9]*" : "[^0-9]*?");
+								pto += strlen(pto);
+								break;
+
+							case 'p':
+								pti += 3;
+								pto += sprintf(pto, "%s", *pti == 0 ? "[\\x21-\\x7E]*" : "[\\x21-\\x7E]?*");
+								break;
+
+							case 'P':
+								pti += 3;
+								pto += sprintf(pto, "%s", *pti == 0 ? "[^\\x20-\\xfe]*" : "[^\\x20-\\xfe]*?");
+								break;
+
+							case 's':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? "\\s*" : "\\s*?");
+								pto += strlen(pto);
+								break;
+
+							case 'S':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? "\\S*" : "\\S*?");
+								pto += strlen(pto);
+								break;
+
+							case 'w':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? "[a-zA-Z]*" : "[a-zA-Z]*?");
+								pto += strlen(pto);
+								break;
+
+							case 'W':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? "[^a-zA-Z]*" : "[^a-zA-Z]*?");
+								pto += strlen(pto);
+								break;
+
+							case '?':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? ".?" : ".?" "?");
+								pto += strlen(pto);
+								break;
+
+							case '*':
+								pti += 3;
+								strcpy(pto, *pti == 0 ? ".*" : ".*?");
+								pto += strlen(pto);
+								break;
+
+							case '+':
+								pti += 3 + get_regex_range(&pti[3], pto, NULL, NULL);
+								pto += strlen(pto);
+								break;
+
+							case '.':
+								pti += 3;
+								strcpy(pto, ".");
+								pto += strlen(pto);
+								break;
+
+							case '{':
+								pti = get_arg_in_braces(ses, pti+2, pto, GET_ALL);
+
+								while (*pto)
+								{
+									if (pto[0] == '$' || pto[0] == '@')
+									{
+										if (pto[1])
+										{
+											return NULL;
+										}
+									}
+									pto++;
+								}
+								break;
+
+							default:
+								*pto++ = *pti++;
+								break;
+						}
+						break;
+
+					default:
+						*pto++ = *pti++;
+						break;
+				}
+				break;
+
+			default:
+				*pto++ = *pti++;
+				break;
+		}
+	}
+	*pto = 0;
+
+	return regexp_compile(ses, out, option);
+}
+
+void tintin_macro_compile(char *input, char *output)
+{
+	char *pti, *pto;
+
+	pti = input;
+	pto = output;
+
+	if (*pti == '^')
+	{
+		pti++;
+	}
+
+	while (*pti)
+	{
+		switch (pti[0])
+		{
+			case '\\':
+				switch (pti[1])
+				{
+					case 'C':
+						if (pti[2] == '-' && pti[3])
+						{
+							*pto++  = pti[3] - 'a' + 1;
+							pti    += 4;
+						}
+						else
+						{
+							*pto++ = *pti++;
+						}
+						break;
+
+					case 'c':
+						*pto++ = pti[2] % 32;
+						pti += 3;
+						break;
+
+					case 'a':
+						*pto++  = ASCII_BEL;
+						pti += 2;
+						break;
+
+					case 'b':
+						*pto++  = 127;
+						pti    += 2;
+						break;
+
+					case 'e':
+						*pto++  = ASCII_ESC;
+						pti    += 2;
+						break;
+
+					case 'r':
+						*pto++ = ASCII_CR;
+						pti   += 2;
+						break;
+
+					case 't':
+						*pto++  = ASCII_HTAB;
+						pti    += 2;
+						break;
+
+					case 'x':
+						if (pti[2] && pti[3])
+						{
+							*pto++ = hex_number_8bit(&pti[2]);
+							pti += 4;
+						}
+						else
+						{
+							*pto++ = *pti++;
+						}
+						break;
+					default:
+						*pto++ = *pti++;
+						break;
+				}
+				break;
+
+			default:
+				*pto++ = *pti++;
+				break;
+		}
+	}
+	*pto = 0;
+}

+ 447 - 0
scan.c

@@ -0,0 +1,447 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2005                       *
+******************************************************************************/
+
+#include "tintin.h"
+
+#ifdef HAVE_PTY_H
+#include <pty.h>
+#else
+#ifdef HAVE_UTIL_H
+#include <util.h>
+#endif
+#endif
+
+/* support routines for comma separated value files */
+
+char *get_arg_in_quotes(struct session *ses, char *string, char *result, int flag)
+{
+	char *pti, *pto;
+	int nest = 0;
+
+	pti = space_out(string);
+	pto = result;
+
+	if (*pti == '"')
+	{
+		nest = TRUE;
+		pti++;
+	}
+
+	while (*pti)
+	{
+		if (HAS_BIT(ses->charset, CHARSET_FLAG_EUC) && is_euc_head(ses, pti))
+		{
+			*pto++ = *pti++;
+			*pto++ = *pti++;
+			continue;
+		}
+
+		if (*pti == '"')
+		{
+			if (pti[1] == '"')
+			{
+				*pto++ = *pti++;
+			}
+			else if (nest)
+			{
+				nest = FALSE;
+			}
+			pti++;
+			continue;
+		}
+		else if (nest == TRUE)
+		{
+			*pto++ = *pti++;
+		}
+		else if (*pti == ' ' || *pti == '\t')
+		{
+			pti++;
+		}
+		else if (*pti == ',')
+		{
+			pti++;
+			break;
+		}
+		else
+		{
+			*pto++ = *pti++;
+		}
+	}
+
+	if (nest)
+	{
+		tintin_printf2(ses, "#SCAN CSV: GET QUOTED ARGUMENT: UNMATCHED QUOTE.");
+	}
+	*pto = 0;
+
+	return pti;
+}
+
+struct session *scan_bulk_file(struct session *ses, FILE *fp, char *filename, char *arg)
+{
+	char line[STRING_SIZE], *str_out, *str_rip, *str_sub;
+	int cnt = 0;
+
+	str_out = str_dup("");
+
+	while (fgets(line, BUFFER_SIZE - 1, fp))
+	{
+		cnt++;
+		str_cat(&str_out, line);
+	}
+
+	str_rip = str_alloc(str_len(str_out));
+
+	strip_vt102_codes(str_out, str_rip);
+
+	RESTRING(gtd->cmds[0], str_out);
+	RESTRING(gtd->cmds[1], str_rip);
+	RESTRING(gtd->cmds[2], ntos(str_len(str_out)));
+	RESTRING(gtd->cmds[3], ntos(strlen(str_rip)));
+	RESTRING(gtd->cmds[4], ntos(cnt));
+
+	str_sub = str_alloc(strlen(arg) + STRING_SIZE);
+
+	substitute(ses, arg, str_sub, SUB_CMD);
+
+	show_message(ses, LIST_COMMAND, "#SCAN BULK: FILE {%s} SCANNED.", filename);
+
+	DEL_BIT(ses->flags, SES_FLAG_SCAN);
+
+	ses = script_driver(ses, LIST_COMMAND, str_sub);
+
+	return ses;
+}
+
+struct session *scan_csv_file(struct session *ses, FILE *fp, char *filename)
+{
+	char line[STRING_SIZE], temp[BUFFER_SIZE], *arg;
+	int i, header = FALSE;
+
+	SET_BIT(ses->flags, SES_FLAG_SCAN);
+
+	while (fgets(line, BUFFER_SIZE, fp))
+	{
+		arg = strchr(line, '\r');
+
+		if (arg)
+		{
+			*arg = 0;
+		}
+		else
+		{
+		
+			arg = strchr(line, '\n');
+
+			if (arg)
+			{
+				*arg = 0;
+			}
+		}
+
+		RESTRING(gtd->vars[0], line);
+
+		arg = line;
+
+		for (i = 1 ; i < 100 ; i++)
+		{
+			arg = get_arg_in_quotes(ses, arg, temp, FALSE);
+
+			RESTRING(gtd->vars[i], temp);
+
+			if (*arg == 0)
+			{
+				while (++i < 100)
+				{
+					if (*gtd->vars[i])
+					{
+						RESTRING(gtd->vars[i], "");
+					}
+				}
+				break;
+			}
+		}
+
+		if (header == FALSE)
+		{
+			header = TRUE;
+
+			check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "SCAN CSV HEADER");
+		}
+		else
+		{
+			check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "SCAN CSV LINE");
+		}
+
+		if (HAS_BIT(ses->flags, SES_FLAG_SCANABORT))
+		{
+			break;
+		}
+	}
+
+	DEL_BIT(ses->flags, SES_FLAG_SCAN);
+
+	if (HAS_BIT(ses->flags, SES_FLAG_SCANABORT))
+	{
+		DEL_BIT(ses->flags, SES_FLAG_SCANABORT);
+
+		show_message(ses, LIST_COMMAND, "#SCAN CSV: FILE {%s} PARTIALLY SCANNED.", filename);
+	}
+	else
+	{
+		show_message(ses, LIST_COMMAND, "#SCAN CSV: FILE {%s} SCANNED.", filename);
+	}
+	fclose(fp);
+
+	return ses;
+}
+
+/* support routines for tab separated value files */
+
+char *get_arg_stop_tabs(struct session *ses, char *string, char *result, int flag)
+{
+	char *pti, *pto;
+
+	pti = string;
+	pto = result;
+
+	while (*pti)
+	{
+		if (*pti == '\t')
+		{
+			pti++;
+			break;
+		}
+		*pto++ = *pti++;
+	}
+	*pto = 0;
+
+	return pti;
+}
+
+struct session *scan_tsv_file(struct session *ses, FILE *fp, char *filename)
+{
+	char line[STRING_SIZE], temp[BUFFER_SIZE], *arg;
+	int i, header = FALSE;
+
+	SET_BIT(ses->flags, SES_FLAG_SCAN);
+
+	while (fgets(line, BUFFER_SIZE, fp))
+	{
+		arg = strchr(line, '\r');
+
+		if (arg)
+		{
+			*arg = 0;
+		}
+		else
+		{
+		
+			arg = strchr(line, '\n');
+
+			if (arg)
+			{
+				*arg = 0;
+			}
+		}
+
+		RESTRING(gtd->vars[0], line);
+
+		arg = line;
+
+		for (i = 1 ; i < 100 ; i++)
+		{
+			arg = get_arg_stop_tabs(ses, arg, temp, FALSE);
+
+			RESTRING(gtd->vars[i], temp);
+
+			if (*arg == 0)
+			{
+				while (++i < 100)
+				{
+					if (*gtd->vars[i])
+					{
+						RESTRING(gtd->vars[i], "");
+					}
+				}
+				break;
+			}
+		}
+
+		if (header == FALSE)
+		{
+			header = TRUE;
+
+			check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "SCAN TSV HEADER");
+		}
+		else
+		{
+			check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "SCAN TSV LINE");
+		}
+
+		if (HAS_BIT(ses->flags, SES_FLAG_SCANABORT))
+		{
+			break;
+		}
+	}
+
+	DEL_BIT(ses->flags, SES_FLAG_SCAN);
+
+	if (HAS_BIT(ses->flags, SES_FLAG_SCANABORT))
+	{
+		DEL_BIT(ses->flags, SES_FLAG_SCANABORT);
+
+		show_message(ses, LIST_COMMAND, "#SCAN TSV: FILE {%s} PARTIALLY SCANNED.", filename);
+	}
+	else
+	{
+		show_message(ses, LIST_COMMAND, "#SCAN TSV: FILE {%s} SCANNED.", filename);
+	}
+	return ses;
+}
+
+/* support routines for text files */
+
+struct session *scan_txt_file(struct session *ses, FILE *fp, char *filename)
+{
+	char line[STRING_SIZE], *arg;
+
+	SET_BIT(ses->flags, SES_FLAG_SCAN);
+
+	while (fgets(line, BUFFER_SIZE - 1, fp))
+	{
+		arg = strchr(line, '\r');
+
+		if (arg)
+		{
+			*arg = 0;
+		}
+		else
+		{
+		
+			arg = strchr(line, '\n');
+
+			if (arg)
+			{
+				*arg = 0;
+			}
+		}
+
+		process_mud_output(ses, line, FALSE);
+
+		if (HAS_BIT(ses->flags, SES_FLAG_SCANABORT))
+		{
+			break;
+		}
+	}
+
+	DEL_BIT(ses->flags, SES_FLAG_SCAN);
+
+	if (HAS_BIT(ses->flags, SES_FLAG_SCANABORT))
+	{
+		DEL_BIT(ses->flags, SES_FLAG_SCANABORT);
+
+		show_message(ses, LIST_COMMAND, "#SCAN TXT: FILE {%s} PARTIALLY SCANNED.", filename);
+	}
+	else
+	{
+		show_message(ses, LIST_COMMAND, "#SCAN TXT: FILE {%s} SCANNED.", filename);
+	}
+	return ses;
+}
+
+DO_COMMAND(do_scan)
+{
+	FILE *fp;
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCAN {ABORT|CSV|TXT} {<FILENAME>}");
+
+		return ses;
+	}
+
+	if (is_abbrev(arg1, "ABORT"))
+	{
+		if (!HAS_BIT(ses->flags, SES_FLAG_SCAN))
+		{
+			show_error(ses, LIST_COMMAND, "#SCAN ABORT: NOT CURRENTLY SCANNING.");
+		}
+		else
+		{
+			SET_BIT(ses->flags, SES_FLAG_SCANABORT);
+		}
+		return ses;
+	}
+
+	if (*arg2 == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCAN {ABORT|CSV|TXT} {<FILENAME>}");
+
+		return ses;
+	}
+
+	if ((fp = fopen(arg2, "r")) == NULL)
+	{
+		show_error(ses, LIST_COMMAND, "#ERROR: #SCAN - FILE {%s} NOT FOUND.", arg2);
+
+		return ses;
+	}
+
+	SET_BIT(ses->flags, SES_FLAG_SCAN);
+
+	if (is_abbrev(arg1, "FILE"))
+	{
+		ses = scan_bulk_file(ses, fp, arg2, arg);
+	}
+	else if (is_abbrev(arg1, "CSV"))
+	{
+		ses = scan_csv_file(ses, fp, arg2);
+	}
+	else if (is_abbrev(arg1, "TSV"))
+	{
+		ses = scan_tsv_file(ses, fp, arg2);
+	}
+	else if (is_abbrev(arg1, "TXT"))
+	{
+		ses = scan_txt_file(ses, fp, arg2);
+	}
+	else
+	{
+		DEL_BIT(ses->flags, SES_FLAG_SCAN);
+
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCAN {ABORT|CSV|FILE|TSV|TXT} {<FILENAME>}");
+	}
+
+	DEL_BIT(ses->flags, SES_FLAG_SCAN);
+
+	fclose(fp);
+
+	return ses;
+}

+ 1520 - 0
screen.c

@@ -0,0 +1,1520 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2019                       *
+******************************************************************************/
+
+#include "tintin.h"
+
+void screen_osc(char *arg1, char *arg2);
+void screen_csi(char *cmd, char *arg1, char *arg2, char *arg3, char *tc);
+void screen_csit(struct session *ses, char *arg1, char *arg2, char *tc);
+
+DO_COMMAND(do_screen)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+	int cnt;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		tintin_header(ses, " SCREEN OPTIONS ");
+
+		for (cnt = 0 ; *screen_table[cnt].fun ; cnt++)
+		{
+			if (*screen_table[cnt].name)
+			{
+				tintin_printf2(ses, "  [%-18s] [%-6s] %s", screen_table[cnt].name, "", screen_table[cnt].desc);
+			}
+		}
+		tintin_header(ses, "");
+	}
+	else
+	{
+		for (cnt = 0 ; *screen_table[cnt].name ; cnt++)
+		{
+			if (is_abbrev(arg1, screen_table[cnt].name))
+			{
+				if (!HAS_BIT(screen_table[cnt].get1, SCREEN_FLAG_GET_NONE))
+				{
+					if (HAS_BIT(screen_table[cnt].get1, SCREEN_FLAG_GET_ONE))
+					{
+						arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+					}
+					else
+					{
+						arg = sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+					}
+				}
+
+				if (!HAS_BIT(screen_table[cnt].get2, SCREEN_FLAG_GET_NONE))
+				{
+					if (HAS_BIT(screen_table[cnt].get2, SCREEN_FLAG_GET_ONE))
+					{
+						arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+					}
+					else
+					{
+						arg = sub_arg_in_braces(ses, arg, arg2, GET_ALL, SUB_VAR|SUB_FUN);
+					}
+				}
+				screen_table[cnt].fun(ses, cnt, arg, arg1, arg2);
+
+				return ses;
+			}
+		}
+		tintin_printf(ses, "#ERROR: #SCREEN {%s} IS NOT A VALID OPTION.", capitalize(arg1));
+	}
+	return ses;
+}
+
+
+
+DO_SCREEN(screen_blur)
+{
+	screen_csit(ses, "6", "", "");
+}
+
+
+DO_SCREEN(screen_cursor)
+{
+	if (is_abbrev(arg1, "HIDE"))
+	{
+		SET_BIT(gtd->flags, TINTIN_FLAG_HIDDENCURSOR);
+		screen_csi("?", "25", "", "", "l");
+	}
+	else if (is_abbrev(arg1, "SHOW"))
+	{
+		DEL_BIT(gtd->flags, TINTIN_FLAG_HIDDENCURSOR);
+		screen_csi("?", "25", "", "", "h");
+	}
+	else if (is_abbrev(arg1, "BAR"))
+	{
+		screen_csi("", "6", "", " ", "q");
+	}
+	else if (is_abbrev(arg1, "BLOCK"))
+	{
+		screen_csi("", "2", "", " ", "q");
+	}
+	else if (is_abbrev(arg1, "UNDERLINE"))
+	{
+		screen_csi("", "4", "", " ", "q");
+	}
+	else if (is_abbrev(arg1, "BLINK"))
+	{
+		screen_csi("?", "12", "", "", "h");
+	}
+	else if (is_abbrev(arg1, "STEADY"))
+	{
+		screen_csi("?", "12", "", "", "l");
+	}
+	else
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {CURSOR} {HIDE|SHOW|BLINK|STEADY}");
+	}	
+}
+
+DO_SCREEN(screen_clear)
+{
+	int top_row, top_col, bot_row, bot_col;
+
+	if (is_abbrev(arg1, "ALL"))
+	{
+		print_stdout("\e[2J");
+	}
+	else if (is_abbrev(arg1, "BOTTOM SPLIT"))
+	{
+		erase_bot_region(ses);
+	}
+	else if (is_abbrev(arg1, "TOP SPLIT"))
+	{
+		erase_top_region(ses);
+	}
+	else if (is_abbrev(arg1, "LEFT SPLIT"))
+	{
+		erase_left_region(ses);
+	}
+	else if (is_abbrev(arg1, "RIGHT SPLIT"))
+	{
+		erase_right_region(ses);
+	}
+	else if (is_abbrev(arg1, "SCROLL REGION"))
+	{
+		erase_scroll_region(ses);
+	}
+	else if (is_abbrev(arg1, "SPLIT REGION"))
+	{
+		erase_split_region(ses);
+	}
+	else if (is_abbrev(arg1, "SQUARE"))
+	{
+		arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+		arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+
+		top_row = get_row_index(ses, arg1);
+		top_col = get_col_index(ses, arg2);
+
+//		tintin_printf2(ses, "debug: (%s) (%s) %d %d", arg1, arg2, top_row, top_col);
+
+		arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+		arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+
+		bot_row = get_row_index(ses, arg1);
+		bot_col = get_col_index(ses, arg2);
+
+//		tintin_printf2(ses, "debug: (%s) (%s) %d %d", arg1, arg2, bot_row, bot_col);
+
+		if (bot_col == 0)
+		{
+			show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN CLEAR SQUARE {<ROW>} {<COL>} {<ROW>} {<COL>}");
+		}
+		else
+		{
+			erase_square(ses, top_row, top_col, bot_row, bot_col);
+
+//			print_stdout("\e[%d;%d;%d;%d${", top_row, top_col, bot_row, bot_col); VT400 not widely supported
+		}
+	}
+	else
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN CLEAR {ALL|SCROLL|SPLIT|SQUARE}");
+	}
+}
+
+DO_SCREEN(screen_fill)
+{
+	char buf[BUFFER_SIZE];
+
+	if (is_abbrev(arg1, "DEFAULT"))
+	{
+		if (ses->split->sav_top_col || ses->split->sav_bot_col)
+		{
+			sprintf(buf, "CLEAR SPLIT");
+
+			do_screen(ses, buf);
+		}
+
+		if (ses->split->sav_top_row > 0)
+		{
+			if (ses->split->sav_top_row == 1)
+			{
+				sprintf(buf, "LINE %d %d %d %d", 1, 1, ses->split->top_row - 1, gtd->screen->cols);
+			}
+			else
+			{
+				sprintf(buf, "BOX %d %d %d %d {}", 1, 1, ses->split->top_row - 1, gtd->screen->cols);
+			}
+			do_draw(ses, buf);
+		}
+
+		if (ses->split->sav_bot_row)
+		{
+			if (ses->split->sav_bot_row == 1)
+			{
+				sprintf(buf, "LINE %d %d %d %d", ses->split->bot_row + 1, 1, gtd->screen->rows - 1, gtd->screen->cols);
+			}
+			else
+			{
+				sprintf(buf, "BOX %d %d %d %d {}", ses->split->bot_row + 1, 1, gtd->screen->rows - 1, gtd->screen->cols);
+			}
+			do_draw(ses, buf);
+		}
+
+		if (ses->split->sav_top_row > 0)
+		{
+			if (ses->split->sav_top_col)
+			{
+				sprintf(buf, "TEED VERTICAL LINE %d %d %d %d", ses->split->top_row - 1, ses->split->top_col - 1, ses->split->bot_row + 1, ses->split->top_col - 1);
+				do_draw(ses, buf);
+			}
+
+			if (ses->split->sav_bot_col)
+			{
+				sprintf(buf, "TEED VERTICAL LINE %d %d %d %d", ses->split->top_row - 1, ses->split->bot_col + 1, ses->split->bot_row + 1, ses->split->bot_col + 1);
+				do_draw(ses, buf);
+			}
+		}
+		else
+		{
+			if (ses->split->sav_top_col)
+			{
+				sprintf(buf, "VERTICAL LINE %d %d %d %d", ses->split->top_row, ses->split->top_col - 1, ses->split->bot_row, ses->split->top_col - 1);
+				do_draw(ses, buf);
+			}
+
+			if (ses->split->sav_bot_col)
+			{
+				sprintf(buf, "VERTICAL LINE %d %d %d %d", ses->split->top_row, ses->split->bot_col + 1, ses->split->bot_row, ses->split->bot_col + 1);
+				do_draw(ses, buf);
+			}
+		}
+	}
+	else if (is_abbrev(arg1, "SPLIT"))
+	{
+		fill_split_region(ses, arg2);
+	}
+	else if (is_abbrev(arg1, "BOTTOM SPLIT"))
+	{
+		fill_bot_region(ses, arg2);
+	}
+	else if (is_abbrev(arg1, "TOP SPLIT"))
+	{
+		fill_top_region(ses, arg2);
+	}
+	else if (is_abbrev(arg1, "LEFT SPLIT"))
+	{
+		fill_left_region(ses, arg2);
+	}
+	else if (is_abbrev(arg1, "RIGHT SPLIT"))
+	{
+		fill_right_region(ses, arg2);
+	}
+	else
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN FILL {TOP|BOT|LEFT|RIGHT|SCROLL|SPLIT|DEFAULT} {arg}");
+	}
+}
+
+DO_SCREEN(screen_focus)
+{
+	screen_csit(ses, "5", "", "");
+}
+
+DO_SCREEN(screen_fullscreen)
+{
+	if (*arg1 == 0)
+	{
+		screen_csit(ses, "10", "2", "");
+	}
+	else if (is_abbrev(arg1, "ON"))
+	{
+		screen_csit(ses, "10", "1", "");
+	}
+	else if (is_abbrev(arg1, "OFF"))
+	{
+		screen_csit(ses, "10", "0", "");
+	}
+}
+
+DO_SCREEN(screen_get)
+{
+	if (*arg1 == 0 || *arg2 == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {GET} {FOCUS} {<VAR>}");
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {GET} {ROWS|COLS|HEIGHT|WIDTH} {<VAR>}");
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {GET} {CHAR_HEIGHT|CHAR_WIDTH}");
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {GET} {SPLIT_TOP_BAR|SPLIT_BOT_BAR|SPLIT_LEFT_BAR|SPLIT_RIGHT_BAR} {<VAR>}");
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {GET} {SCROLL_TOP_ROW|SCROLL_TOP_COL|SCROLL_BOT_ROW|SCROLL_BOT_COL} {<VAR>}");
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {GET} {CUR_ROW|CUR_COL} {<VAR>}");
+
+		return;
+	}
+
+	if (is_abbrev(arg1, "FOCUS"))
+	{
+		set_nest_node_ses(ses, arg2, "%d", gtd->screen->focus);
+	}
+	else if (is_abbrev(arg1, "CHAR_HEIGHT"))
+	{
+		set_nest_node_ses(ses, arg2, "%d", gtd->screen->char_height);
+	}
+	else if (is_abbrev(arg1, "CHAR_WIDTH"))
+	{
+		set_nest_node_ses(ses, arg2, "%d", gtd->screen->char_width);
+	}
+	else if (is_abbrev(arg1, "COLS"))
+	{
+		set_nest_node_ses(ses, arg2, "%d", gtd->screen->cols);
+	}
+	else if (is_abbrev(arg1, "CUR_COL"))
+	{
+		set_nest_node_ses(ses, arg2, "%d", ses->cur_col);
+	}
+	else if (is_abbrev(arg1, "CUR_ROW"))
+	{
+		set_nest_node_ses(ses, arg2, "%d", ses->cur_row);
+	}
+	else if (is_abbrev(arg1, "HEIGHT"))
+	{
+		set_nest_node_ses(ses, arg2, "%d", gtd->screen->height);
+	}
+	else if (is_abbrev(arg1, "WIDTH"))
+	{
+		set_nest_node_ses(ses, arg2, "%d", gtd->screen->width);
+	}
+
+	else if (is_abbrev(arg1, "ROWS"))
+	{
+		set_nest_node_ses(ses, arg2, "%d", gtd->screen->rows);
+	}
+
+	else if (is_abbrev(arg1, "SPLIT_TOP_BAR"))
+	{
+		set_nest_node_ses(ses, arg2, "%d", ses->split->sav_top_row);
+	}
+	else if (is_abbrev(arg1, "SPLIT_LEFT_BAR"))
+	{
+		set_nest_node_ses(ses, arg2, "%d", ses->split->sav_top_col);
+	}
+	else if (is_abbrev(arg1, "SPLIT_BOT_BAR"))
+	{
+		set_nest_node_ses(ses, arg2, "%d", ses->split->sav_bot_row);
+	}
+	else if (is_abbrev(arg1, "SPLIT_RIGHT_BAR"))
+	{
+		set_nest_node_ses(ses, arg2, "%d", ses->split->sav_bot_col);
+	}
+
+	else if (is_abbrev(arg1, "SCROLL_ROWS"))
+	{
+		set_nest_node_ses(ses, arg2, "%d", ses->split->bot_row - ses->split->top_row);
+	}
+	else if (is_abbrev(arg1, "SCROLL_COLS"))
+	{
+		set_nest_node_ses(ses, arg2, "%d", ses->wrap);
+	}
+
+	else if (is_abbrev(arg1, "SCROLL_TOP_ROW"))
+	{
+		set_nest_node_ses(ses, arg2, "%d", ses->split->top_row);
+	}
+	else if (is_abbrev(arg1, "SCROLL_TOP_COL"))
+	{
+		set_nest_node_ses(ses, arg2, "%d", ses->split->top_col);
+	}
+	else if (is_abbrev(arg1, "SCROLL_BOT_ROW"))
+	{
+		set_nest_node_ses(ses, arg2, "%d", ses->split->bot_row);
+	}
+	else if (is_abbrev(arg1, "SCROLL_BOT_COL"))
+	{
+		set_nest_node_ses(ses, arg2, "%d", ses->split->bot_col);
+	}
+	else
+	{
+		screen_get(ses, 0, "", "", "");
+	}
+}
+
+DO_SCREEN(screen_maximize)
+{
+	if (*arg1 == 0 || is_abbrev(arg1, "ON"))
+	{
+		screen_csit(ses, "9", "1", "");
+	}
+	else if (is_abbrev(arg1, "OFF"))
+	{
+		screen_csit(ses, "9", "0", "");
+	}
+	else if (is_abbrev(arg1, "HORIZONTALLY"))
+	{
+		screen_csit(ses, "9", "3", "");
+	}
+	else if (is_abbrev(arg1, "VERTICALLY"))
+	{
+		screen_csit(ses, "9", "2", "");
+	}
+	else
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {MAXIMIZE} {ON|OFF|VERTICAL|HORIZONTAL}");
+	}
+}
+
+DO_SCREEN(screen_minimize)
+{
+	if (*arg1 == 0 || is_abbrev(arg1, "ON"))
+	{
+		screen_csit(ses, "2", arg2, arg2);
+	}
+	else if (is_abbrev(arg1, "OFF"))
+	{
+		screen_csit(ses, "1", arg2, arg2);
+	}
+	else
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {MINIMIZE} {ON|OFF}");
+	}
+}
+
+DO_SCREEN(screen_move)
+{
+	int height, width;
+
+	if (!is_math(ses, arg1) || !is_math(ses, arg2))
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {MOVE} {HEIGHT} {WIDTH}");
+
+		return;
+	}
+
+	height = (int) get_number(ses, arg2);
+	width  = (int) get_number(ses, arg1);
+
+	if (height < 0)
+	{
+		strcpy(arg2, "0");
+	}
+
+	if (width < 0)
+	{
+		strcpy(arg1, "0");
+	}
+
+	screen_csit(ses, "3", arg2, arg1); // reverse x,y to row,col
+}
+
+DO_SCREEN(screen_rescale)
+{
+	if (is_abbrev(arg1, "HORIZONTALLY"))
+	{
+		if (*arg2 == 0)
+		{
+			screen_csit(ses, "4", "", arg2);
+		}
+		else
+		{
+			show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {RESCALE} {HORIZONTALLY} {[WIDTH]}");
+		}
+	}
+	else if (is_abbrev(arg1, "VERTICALLY"))
+	{
+		if (*arg2 == 0)
+		{
+			screen_csit(ses, "4", arg2, "");
+		}
+		else
+		{
+			show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {RESCALE} {VERTICALLY} {[HEIGHT]}");
+		}
+	}
+	else if (*arg1 == 0 || is_math(ses, arg1))
+	{
+		if (*arg2 == 0 || is_math(ses, arg2))
+		{
+			screen_csit(ses, "4", arg1, arg2);
+		}
+		else
+		{
+			screen_rescale(ses, 0, arg, "SYNTAX", "");
+		}
+	}
+	else
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {RESCALE} {[HEIGHT]} {[WIDTH]}");
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {RESCALE} {VERTICALLY} {<HEIGHT>}");
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {RESCALE} {HORIZONTALLY} {<WIDTH>}");
+	}
+}
+
+DO_SCREEN(screen_pixel_resize)
+{
+	screen_csit(ses, "4", arg1, arg2);
+}
+
+DO_SCREEN(screen_restore)
+{
+	screen_csit(ses, "10", "2", "");
+}
+
+DO_SCREEN(screen_scrollbar)
+{
+	if (is_abbrev(arg1, "HIDE"))
+	{
+		screen_csi("?", "30", "", "", "l");
+	}
+	else if(is_abbrev(arg1, "SHOW"))
+	{
+		screen_csi("?", "30", "", "", "h");
+	}
+	else
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {SCROLLBAR} {HIDE|SHOW}");
+	}
+}
+
+DO_SCREEN(screen_scrollregion)
+{
+	int top_row, top_col, bot_row, bot_col;
+
+	if ((*arg1 && !is_math(ses, arg1)) || (*arg2 && !is_math(ses, arg2)))
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN SCROLLREGION {TOP ROW} {TOP COL} {BOT ROW} {BOT COL}");
+
+		return;
+	}
+
+	top_row = get_number(ses, arg1);
+	top_col = get_number(ses, arg2);
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if ((*arg1 && !is_math(ses, arg1)) || (*arg2 && !is_math(ses, arg2)))
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN SCROLL {TOP ROW} {TOP COL} {BOT ROW} {BOT COL}");
+
+		return;
+	}
+
+	bot_row = get_number(ses, arg1);
+	bot_col = get_number(ses, arg2);
+
+	if ((top_row|top_col|bot_row|bot_col) == 0)
+	{
+		do_unsplit(ses, "");
+
+		return;
+	}
+
+	ses->split->sav_top_row = top_row;
+	ses->split->sav_top_col = top_col;
+	ses->split->sav_bot_row = bot_row;
+	ses->split->sav_bot_col = bot_col;
+
+	SET_BIT(ses->flags, SES_FLAG_SPLIT);
+	SET_BIT(ses->flags, SES_FLAG_SCROLLSPLIT);
+
+	init_split(ses, ses->split->sav_top_row, ses->split->sav_top_col, ses->split->sav_bot_row, ses->split->sav_bot_col);
+
+	return;
+}
+
+DO_SCREEN(screen_load)
+{
+	if (is_abbrev(arg1, "LABEL"))
+	{
+		screen_csit(ses, "23", "1", "");
+	}
+	else if (is_abbrev(arg1, "BOTH") || is_abbrev(arg1, "NAME"))
+	{
+		screen_csit(ses, "23", "0", "");
+	}
+	else if (is_abbrev(arg1, "TITLE"))
+	{
+		screen_csit(ses, "23", "2", "");
+	}
+	else
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {LOAD} {LABEL|NAME|TITLE}");
+	}
+}
+
+DO_SCREEN(screen_refresh)
+{
+	SET_BIT(ses->flags, SES_FLAG_PRINTBUFFER);
+
+	buffer_end(ses, "");
+
+	DEL_BIT(ses->flags, SES_FLAG_PRINTBUFFER);
+}
+
+DO_SCREEN(screen_resize)
+{
+	if (is_abbrev(arg1, "HORIZONTALLY"))
+	{
+		if (*arg2 == 0 || is_math(ses,arg2))
+		{
+			screen_csit(ses, "8", " ", arg2);
+		}
+		else
+		{
+			show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {SIZE} {HORIZONTALLY} {[COLS]}");
+		}
+	}
+	else if (is_abbrev(arg1, "VERTICALLY"))
+	{
+		if (*arg2 == 0 || is_math(ses, arg2))
+		{
+			screen_csit(ses, "8", arg2, " ");
+		}
+		else
+		{
+			show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {SIZE} {VERTICALLY} {[ROWS]}");
+		}
+	}
+	else if (*arg1 == 0 || is_math(ses, arg1))
+	{
+		if (*arg2 == 0 || is_math(ses, arg2))
+		{
+			screen_csit(ses, "8", arg1, arg2);
+		}
+		else
+		{
+			screen_resize(ses, 0, arg, "SYNTAX", "");
+		}
+	}
+	else
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {SIZE} {[ROWS]} {[COLS]}");
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {SIZE} {VERTICALLY} {<ROWS>}");
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {SIZE} {HORIZONTALLY} {<COLS>}");
+	}
+}
+
+DO_SCREEN(screen_save)
+{
+	if (is_abbrev(arg1, "LABEL"))
+	{
+		screen_csit(ses, "22", "1", "");
+	}
+	else if (is_abbrev(arg1, "BOTH"))
+	{
+		screen_csit(ses, "22", "0", "");
+	}
+	else if (is_abbrev(arg1, "TITLE"))
+	{
+		screen_csit(ses, "22", "2", "");
+	}
+	else
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {SAVE} {BOTH|LABEL|TITLE}");
+	}
+}
+
+DO_SCREEN(screen_set)
+{
+	if (is_abbrev(arg1, "BOTH") || is_abbrev(arg1, "NAME"))
+	{
+		screen_osc("0", arg2);
+	}
+	else if (is_abbrev(arg1, "COLS"))
+	{
+		gtd->screen->cols = get_number(ses, arg2);
+	}
+	else if (is_abbrev(arg1, "LABEL"))
+	{
+		screen_osc("1", arg2);
+	}
+	else if (is_abbrev(arg1, "ROWS"))
+	{
+		gtd->screen->rows = get_number(ses, arg2);
+	}
+	else if (is_abbrev(arg1, "TITLE"))
+	{
+		screen_osc("2", arg2);
+	}
+	else
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {SET} {COLS|ROWS|LABEL|NAME|TITLE}");
+	}
+}
+
+DO_SCREEN(screen_raise)
+{
+	if (*arg1 == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {RAISE} {SCREEN CHARACTER DIMENSIONS}");
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {RAISE} {SCREEN DESKTOP DIMENSIONS}");
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {RAISE} {SCREEN DIMENSIONS}");
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {RAISE} {SCREEN MINIMIZED}");
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {RAISE} {SCREEN MOUSE LOCATION}");
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {RAISE} {SCREEN LOCATION}");
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {RAISE} {SCREEN RESIZE}");
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SCREEN {RAISE} {SCREEN SIZE}");
+
+		return;
+	}
+
+	if (is_abbrev("SCREEN ", arg1))
+	{
+		arg1 += 7;
+	}
+
+	if (is_abbrev(arg1, "CHARACTER DIMENSIONS"))
+	{
+		screen_csit(ses, "16", "", "");
+	}
+	else if (is_abbrev(arg1, "DESKTOP DIMENSIONS"))
+	{
+		screen_csit(ses, "15", "", "");
+	}
+	else if (is_abbrev(arg1, "MINIMIZED"))
+	{
+		screen_csit(ses, "11", "", "");
+	}
+	else if (is_abbrev(arg1, "MOUSE LOCATION"))
+	{
+		print_stdout("%s", "\e[2;1'z\e['|");
+	}
+	else if (is_abbrev(arg1, "LOCATION") || is_abbrev(arg1, "POSITION"))
+	{
+		screen_csit(ses, "13", "", "");
+	}
+	else if (is_abbrev(arg1, "RESIZE"))
+	{
+		check_all_events(NULL, SUB_ARG, 0, 4, "SCREEN RESIZE", ntos(gtd->screen->rows), ntos(gtd->screen->cols), ntos(gtd->screen->height), ntos(gtd->screen->width));
+	}
+	else if (is_abbrev(arg1, "SIZE"))
+	{
+		screen_csit(ses, "18", "", "");
+	}
+	else if (is_abbrev(arg1, "DIMENSIONS"))
+	{
+		screen_csit(ses, "14", "2", "");
+	}
+	else
+	{
+		screen_raise(ses, 0, "", "", "");
+	}
+}
+
+int get_row_index(struct session *ses, char *arg)
+{
+	int val;
+
+	if (*arg && is_math(ses, arg))
+	{
+		val = get_number(ses, arg);
+	}
+	else
+	{
+		val = 0;
+	}
+
+	if (val < 0)
+	{
+		val = 1 + gtd->screen->rows + val;
+	}
+
+	if (val > gtd->screen->rows)
+	{
+		val = gtd->screen->rows;
+	}
+
+	return val;
+}
+
+int get_col_index(struct session *ses, char *arg)
+{
+	int val;
+
+	if (*arg && is_math(ses, arg))
+	{
+		val = get_number(ses, arg);
+	}
+	else
+	{
+		val = 0;
+	}
+
+	if (val < 0)
+	{
+		val = 1 + gtd->screen->cols + val;
+	}
+
+	if (val > gtd->screen->cols)
+	{
+		val = gtd->screen->cols;
+	}
+
+	return val;
+}
+
+
+
+void csit_handler(int ind, int var1, int var2)
+{
+//	tintin_printf2(gtd->ses, "csit_handler(%d,%d,%d)",ind,var1,var2);
+
+	switch (ind)
+	{
+		case 1:
+			gtd->screen->minimized = 1;
+			check_all_events(NULL, SUB_ARG, 0, 1, "SCREEN MINIMIZED", "1");
+			msdp_update_all("SCREEN_MINIMIZED", "1");
+			break;
+
+		case 2:
+			gtd->screen->minimized = 0;
+			check_all_events(NULL, SUB_ARG, 0, 1, "SCREEN MINIMIZED", "0");
+			msdp_update_all("SCREEN_MINIMIZED", "0");
+			break;
+
+		case 3:
+			gtd->screen->pos_height = UMAX(0, var2);
+			gtd->screen->pos_width  = UMAX(0, var1);
+			check_all_events(NULL, SUB_ARG, 0, 4, "SCREEN LOCATION", ntos(var2 / gtd->screen->char_height), ntos(var1 / gtd->screen->char_width), ntos(var2), ntos(var1)); // swap x y
+			msdp_update_all("SCREEN_LOCATION_HEIGHT", "%d", gtd->screen->pos_height);
+			msdp_update_all("SCREEN_LOCATION_WIDTH", "%d", gtd->screen->pos_width);
+			break;
+
+		case 4:
+			check_all_events(NULL, SUB_ARG, 0, 2, "SCREEN DIMENSIONS", ntos(var1), ntos(var2));
+			break;
+
+		case 5:
+			gtd->screen->desk_height = var1;
+			gtd->screen->desk_width  = var2;
+			check_all_events(NULL, SUB_ARG, 0, 2, "SCREEN DESKTOP DIMENSIONS", ntos(var1), ntos(var2));
+			break;
+
+		case 6:
+			gtd->screen->char_height = var1;
+			gtd->screen->char_width  = var2;
+			check_all_events(NULL, SUB_ARG, 0, 2, "SCREEN CHARACTER DIMENSIONS", ntos(var1), ntos(var2));
+			msdp_update_all("SCREEN_CHARACTER_HEIGHT", "%d", gtd->screen->char_height);
+			msdp_update_all("SCREEN_CHARACTER_WIDTH", "%d", gtd->screen->char_width);
+			break;
+
+		case 7:
+			init_screen(gtd->screen->rows, gtd->screen->cols, gtd->screen->height, gtd->screen->width);
+			check_all_events(NULL, SUB_ARG, 0, 4, "SCREEN REFRESH", ntos(gtd->screen->rows), ntos(gtd->screen->cols), ntos(gtd->screen->height), ntos(gtd->screen->width));
+			break;
+
+		case 8:
+			gtd->screen->rows = var1;
+			gtd->screen->cols = var2;
+
+			check_all_events(NULL, SUB_ARG, 0, 2, "SCREEN SIZE", ntos(var1), ntos(var2));
+			msdp_update_all("SCREEN_ROWS", "%d", gtd->screen->rows);
+			msdp_update_all("SCREEN_COLS", "%d", gtd->screen->cols);
+			break;
+
+		case 9:
+			gtd->screen->desk_rows = var1;
+			gtd->screen->desk_cols = var2;
+
+			check_all_events(NULL, SUB_ARG, 0, 2, "SCREEN DESKTOP SIZE", ntos(var1), ntos(var2));
+			msdp_update_all("SCREEN_DESKTOP_ROWS", "%d", gtd->screen->desk_rows);
+			msdp_update_all("SCREEN_DESKTOP_COLS", "%d", gtd->screen->desk_cols);
+			break;
+	}
+}
+
+void rqlp_handler(int event, int button, int height, int width)
+{
+	int row, col, rev_row, rev_col, char_height, char_width, rev_char_height, rev_char_width, debug, info, grid_val;
+	char *grid[] = {"1", "2", "3", "4", "5", "6", "7", "8", "9"};
+
+	row = 1 + height / UMAX(1, gtd->screen->char_height);
+	col = 1 + width / UMAX(1, gtd->screen->char_width);
+
+	char_height = 1 + height % UMAX(1, gtd->screen->char_height);
+	char_width  = 1 + width % UMAX(1, gtd->screen->char_width);
+
+	rev_row = -1 - (gtd->screen->rows - row);
+	rev_col = -1 - (gtd->screen->cols - col);
+
+	rev_char_height = -1 - (gtd->screen->char_height - char_height);
+	rev_char_width  = -1 - (gtd->screen->char_width - char_width);
+
+	grid_val = URANGE(0, char_height * 3 / gtd->screen->char_height, 2) * 3 + URANGE(0, char_width * 3 / gtd->screen->char_width, 2);
+
+	debug = HAS_BIT(gtd->ses->flags, SES_FLAG_MOUSEDEBUG) ? 1 : 0;
+	info  = HAS_BIT(gtd->ses->flags, SES_FLAG_MOUSEINFO) ? 1 : 0;
+
+	gtd->level->debug += debug;
+	gtd->level->info  += info;
+
+	check_all_events(gtd->ses, SUB_ARG, 0, 9, "SCREEN MOUSE LOCATION", ntos(row), ntos(col), ntos(rev_row), ntos(rev_col), ntos(char_height), ntos(char_width), ntos(rev_char_height), ntos(rev_char_width), grid[grid_val]);
+
+	map_mouse_handler(gtd->ses, NULL, NULL, col, row, char_height, char_width);
+
+	gtd->level->debug -= debug;
+	gtd->level->info  -= info;
+
+//	tintin_printf2(gtd->ses, "rqlp_handler(%d,%d,%d,%d) (%d,%d,%d,%d) (%d,%d,%d,%d)", event,button,height,width, row,col,rev_row,rev_col, char_height,char_width,rev_char_height,rev_char_width);
+}
+
+void screen_osc(char *arg1, char *arg2)
+{
+	print_stdout("\e]%s;%s\a", arg1, arg2);
+}
+
+void osc_handler(char ind, char *arg)
+{
+	tintin_printf2(gtd->ses, "osc debug: [%c] (%s)", ind, arg);
+}
+
+void screen_csi(char *cmd, char *num1, char *num2, char *num3, char *tc)
+{
+
+	print_stdout("\e[%s%s%s%s%s%s%s",
+		cmd,
+		*num1 ? XT_S : XT_V, *num1 && *num1 != ' ' ? num1 : "",
+		*num2 ? XT_S : XT_V, *num2 && *num2 != ' ' ? num2 : "",
+		num3,
+		tc);
+
+	SET_BIT(gtd->flags, TINTIN_FLAG_FLUSH);
+}
+
+void screen_csit(struct session *ses, char *arg1, char *arg2, char *arg3)
+{
+	char num1[NUMBER_SIZE], num2[NUMBER_SIZE];
+
+	if (*arg2 && *arg2 != ' ' && is_math(ses, arg2))
+	{
+		get_number_string(ses, arg2, num1);
+	}
+	else
+	{
+		strcpy(num1, arg2);
+	}
+
+	if (*arg3 && *arg3 != ' ' && is_math(ses, arg3))
+	{
+		get_number_string(ses, arg3, num2);
+	}
+	else
+	{
+		strcpy(num2, arg3);
+	}
+
+	print_stdout("\e[%s%s%s%s%st", arg1, *num1 ? XT_S : XT_V, *num1 && *num1 != ' ' ? num1 : "", *num2 ? XT_S : XT_V, *num2 && *num2 != ' ' ? num2 : "");
+
+//	convert_meta(buf, debug, FALSE);
+
+//	tintin_printf2(gtd->ses, "\e[1;32m[%s] num1 (%s) num2 (%s) %s %s", num1, num2, debug, buf);
+
+
+	SET_BIT(gtd->flags, TINTIN_FLAG_FLUSH);
+}
+
+
+DO_SCREEN(screen_info)
+{
+	int lvl;
+
+	tintin_printf2(ses, "gtd->ses->split->sav_top_row: %4d", gtd->ses->split->sav_top_row);
+	tintin_printf2(ses, "gtd->ses->split->sav_top_col: %4d", gtd->ses->split->sav_top_col);
+	tintin_printf2(ses, "gtd->ses->split->sav_bot_row: %4d", gtd->ses->split->sav_bot_row);
+	tintin_printf2(ses, "gtd->ses->split->sav_bot_col: %4d", gtd->ses->split->sav_bot_col);
+
+	tintin_printf2(ses, "gtd->ses->split->top_row:     %4d", gtd->ses->split->top_row);
+	tintin_printf2(ses, "gtd->ses->split->top_col:     %4d", gtd->ses->split->top_col);
+	tintin_printf2(ses, "gtd->ses->split->bot_row:     %4d", gtd->ses->split->bot_row);
+	tintin_printf2(ses, "gtd->ses->split->bot_col:     %4d", gtd->ses->split->bot_col);
+
+	tintin_printf2(ses, "");
+
+	tintin_printf2(ses, "gtd->ses->wrap:           %4d", gtd->ses->wrap);
+	tintin_printf2(ses, "gtd->ses->cur_row:        %4d", gtd->ses->cur_row);
+	tintin_printf2(ses, "gtd->ses->cur_col:        %4d", gtd->ses->cur_col);
+
+	for (lvl = 0 ; lvl < gtd->screen->sav_lev ; lvl++)
+	{
+	tintin_printf2(ses, "gtd->screen->sav_row[%2d]: %4d", lvl, gtd->screen->sav_row[lvl]);
+	tintin_printf2(ses, "gtd->screen->sav_col[%2d]: %4d", lvl, gtd->screen->sav_col[lvl]);
+	}
+
+	tintin_printf2(ses, "");
+
+	tintin_printf2(ses, "gtd->screen->rows:        %4d", gtd->screen->rows);
+	tintin_printf2(ses, "gtd->screen->cols:        %4d", gtd->screen->cols);
+	tintin_printf2(ses, "gtd->screen->height:      %4d", gtd->screen->height);
+	tintin_printf2(ses, "gtd->screen->pix_cows:    %4d", gtd->screen->width);
+
+	return;
+
+	tintin_printf2(ses, "");
+
+	tintin_printf2(ses, "gtd->screen->top_row:     %4d", gtd->screen->top_row);
+	tintin_printf2(ses, "gtd->screen->bot_row:     %4d", gtd->screen->bot_row);
+	tintin_printf2(ses, "gtd->screen->cur_row:     %4d", gtd->screen->cur_row);
+	tintin_printf2(ses, "gtd->screen->cur_col:     %4d", gtd->screen->cur_col);
+	tintin_printf2(ses, "gtd->screen->max_row:     %4d", gtd->screen->max_row);
+
+	tintin_printf2(ses, "");
+
+
+	if (!HAS_BIT(ses->flags, SES_FLAG_READMUD) && IS_SPLIT(ses))
+	{
+		tintin_printf2(ses, "SPLIT mode detected.");
+	}
+}
+
+/*
+
+	if (gtd->screen == NULL)
+	{
+		tintin_printf2(ses, "gtd->screen: NULL");
+
+		return ses;
+	}
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+
+
+	print_screen(gts);
+
+	return ses;
+}
+*/
+
+void add_row_screen(int index)
+{
+	gtd->screen->lines[index] = (struct row_data *) calloc(1, sizeof(struct row_data));
+	gtd->screen->lines[index]->str = strdup("");
+}
+
+void del_row_screen(int index)
+{
+	free(gtd->screen->lines[index]->str);
+	free(gtd->screen->lines[index]);
+}
+
+void init_screen(int rows, int cols, int height, int width)
+{
+	int cnt;
+
+	gtd->screen->rows        = UMAX(1, rows);
+	gtd->screen->cols        = UMAX(1, cols);
+	gtd->screen->height      = UMAX(1, height);
+	gtd->screen->width       = UMAX(1, width);
+	gtd->screen->char_height = UMAX(1, gtd->screen->height / gtd->screen->rows);
+	gtd->screen->char_width  = UMAX(1, gtd->screen->width / gtd->screen->cols);
+
+	gtd->screen->focus  = 1;
+
+	return;
+
+//	disabled for now
+
+	push_call("init_screen(%d,%d)",rows,cols);
+
+	if (gtd->screen)
+	{
+		if (gtd->screen->max_row < rows)
+		{
+			gtd->screen->lines = (struct row_data **) realloc(gtd->screen->lines, rows * sizeof(struct row_data *));
+			
+			memset(gtd->screen->lines + gtd->screen->max_row * sizeof(struct row_data *), 0, (rows - gtd->screen->max_row) * sizeof(struct row_data *));
+
+			gtd->screen->max_row = rows;
+		}
+	}
+	else
+	{
+		gtd->screen = calloc(1, sizeof(struct screen_data));
+		gtd->screen->lines = (struct row_data **) calloc(rows, sizeof(struct row_data *));
+
+		gtd->screen->top_row = 1;
+		gtd->screen->bot_row = rows;
+
+		gtd->screen->max_row = rows;
+	}
+
+	gtd->screen->rows = rows;
+	gtd->screen->cols = cols;
+
+	gtd->screen->sav_lev = 0;
+	gtd->screen->sav_row[0] = gtd->screen->cur_row = rows;
+	gtd->screen->sav_col[0] = gtd->screen->cur_col = 0;
+
+	for (cnt = 0 ; cnt < gtd->screen->max_row ; cnt++)
+	{
+		if (gtd->screen->lines[cnt] == NULL)
+		{
+			add_row_screen(cnt);
+		}
+	}
+	pop_call();
+	return;
+}
+
+void destroy_screen()
+{
+	int cnt;
+
+//	disabled for now
+
+	return;
+
+	for (cnt = 0 ; cnt < gtd->screen->max_row ; cnt++)
+	{
+		del_row_screen(cnt);
+	}
+	free(gtd->screen->lines);
+	free(gtd->screen);
+
+	gtd->screen = NULL;
+}
+
+void print_screen()
+{
+	int cnt;
+
+	for (cnt = 0 ; cnt < gtd->screen->rows ; cnt++)
+	{
+		print_stdout("%2d %s\n", cnt, gtd->screen->lines[cnt]->str);
+	}
+}
+
+
+void add_line_screen(char *str)
+{
+	char *ptr;
+	int cnt;
+
+	// disabled for now
+
+	return;
+
+	push_call("add_line_screen(%p)",str);
+
+	if (gtd->screen == NULL)
+	{
+		print_stdout("screen == NULL!\n");
+
+		pop_call();
+		return;
+	}
+
+	while (str)
+	{
+		cnt = gtd->ses->split->top_row - 1;
+
+		free(gtd->screen->lines[cnt]->str);
+
+		while (cnt < gtd->ses->split->bot_row - 2)
+		{
+			gtd->screen->lines[cnt]->str = gtd->screen->lines[cnt + 1]->str;
+
+			cnt++;
+		}
+
+		ptr = strchr(str, '\n');
+
+		if (ptr)
+		{
+			gtd->screen->lines[cnt]->str = strndup(str, ptr - str);
+
+			str = ptr + 1;
+		}
+		else
+		{
+			gtd->screen->lines[cnt]->str = strdup(str);
+
+			str = NULL;
+		}
+//		gtd->screen->lines[cnt]->raw_len = strlen(gtd->screen->lines[cnt]->str);
+//		gtd->screen->lines[cnt]->str_len = strip_vt102_strlen(gts, gtd->screen->lines[cnt]->str);
+	}
+
+	pop_call();
+	return;
+}
+
+
+
+void set_line_screen(char *str, int row, int col)
+{
+	char buf[BUFFER_SIZE];
+
+	// disabled for now
+	
+	return;
+
+	push_call("set_line_screen(%p,%d)",str,row,col);
+
+	strcpy(buf, gtd->screen->lines[row]->str);
+
+	free(gtd->screen->lines[row]->str);
+
+	strcpy(&buf[col], str);
+
+	gtd->screen->lines[row]->str = strdup(buf);
+
+	pop_call();
+	return;
+}
+
+/*
+	save cursor, goto top row, delete (bot - top) rows, restore cursor
+*/
+
+void erase_scroll_region(struct session *ses)
+{
+	int row;
+
+	push_call("erase_scroll_region(%p) [%d,%d]",ses,ses->split->top_row,ses->split->bot_row);
+
+	save_pos(ses);
+
+	for (row = ses->split->top_row ; row <= ses->split->bot_row ; row++)
+	{
+		print_stdout("\e[%d;%dH\e[%dX", row, ses->split->top_col, ses->wrap);
+	}
+	restore_pos(ses);
+
+	pop_call();
+	return;
+}
+
+void erase_split_region(struct session *ses)
+{
+	erase_top_region(ses);
+
+	erase_bot_region(ses);
+
+	erase_left_region(ses);
+
+	erase_right_region(ses);
+}
+
+void erase_top_region(struct session *ses)
+{
+	int row;
+
+	if (ses->split->top_row > 1)
+	{
+		save_pos(ses);
+		goto_pos(ses, 1, 1);
+
+		for (row = 1 ; row < ses->split->top_row ; row++)
+		{
+			print_stdout("\e[K\n");
+		}
+		restore_pos(ses);
+	}
+}
+
+void erase_bot_region(struct session *ses)
+{
+	int row;
+
+	if (ses->split->bot_row < gtd->screen->rows)
+	{
+		save_pos(ses);
+		goto_pos(ses, ses->split->bot_row + 1, 1);
+
+		for (row = ses->split->bot_row + 1 ; row < gtd->screen->rows ; row++)
+		{
+			print_stdout("\e[K\n");
+		}
+		restore_pos(ses);
+	}
+}
+
+void erase_left_region(struct session *ses)
+{
+	int row;
+
+	if (ses->split->top_col > 1)
+	{
+		save_pos(ses);
+
+		for (row = ses->split->top_row ; row <= ses->split->bot_row ; row++)
+		{
+			print_stdout("\e[%d;1H\e[%dX", row, ses->split->top_col - 1);
+		}
+		restore_pos(ses);
+	}
+}
+
+void erase_right_region(struct session *ses)
+{
+	int row;
+
+	if (ses->split->bot_col < gtd->screen->cols)
+	{
+		save_pos(ses);
+
+		for (row = ses->split->top_row ; row <= ses->split->bot_row ; row++)
+		{
+			print_stdout("\e[%d;%dH\e[K", row, ses->split->bot_col + 1);
+		}
+		restore_pos(ses);
+	}
+}
+
+
+void erase_square(struct session *ses, int top_row, int top_col, int bot_row, int bot_col)
+{
+	int row;
+
+	push_call("erase_square(%p,%d,%d,%d,%d)",ses,top_row,top_col,bot_row,bot_col);
+
+	save_pos(ses);
+
+	for (row = top_row ; row <= bot_row ; row++)
+	{
+		goto_pos(ses, row, top_col);
+		print_stdout("\e[%dX", bot_col - top_col + 1);
+	}
+	restore_pos(ses);
+
+	pop_call();
+	return;
+}
+
+void fill_scroll_region(struct session *ses, char *arg)
+{
+	int row, col;
+
+	save_pos(ses);
+
+	for (row = ses->split->top_row ; row < ses->split->bot_row ; row++)
+	{
+		goto_pos(ses, row, ses->split->top_col);
+
+		for (col = ses->split->top_col ; col < ses->split->bot_col ; col++)
+		{
+			print_stdout("%s", arg);
+		}
+		print_stdout("\n");
+	}
+	restore_pos(ses);
+}
+
+void fill_top_region(struct session *ses, char *arg)
+{
+	int row, col;
+
+	if (ses->split->top_row > 1)
+	{
+		save_pos(ses);
+		goto_pos(ses, 1, 1);
+
+		for (row = 1 ; row < ses->split->top_row ; row++)
+		{
+			print_stdout("\e[0m");
+
+			for (col = 0 ; col < gtd->screen->cols ; col++)
+			{
+				print_stdout("%s", arg);
+			}
+			print_stdout("\n");
+		}
+		restore_pos(ses);
+	}
+}
+
+void fill_bot_region(struct session *ses, char *arg)
+{
+	int row, col;
+
+	if (ses->split->bot_row < gtd->screen->rows)
+	{
+		save_pos(ses);
+		goto_pos(ses, ses->split->bot_row + 1, 1);
+
+		for (row = ses->split->bot_row + 1 ; row < gtd->screen->rows ; row++)
+		{
+			print_stdout("\e[0m");
+			for (col = 0 ; col < gtd->screen->cols ; col++)
+			{
+				print_stdout("%s", arg);
+			}
+			print_stdout("\n");
+		}
+		restore_pos(ses);
+	}
+}
+
+void fill_left_region(struct session *ses, char *arg)
+{
+	int row, col;
+
+	if (ses->split->top_col > 1)
+	{
+		save_pos(ses);
+
+		for (row = ses->split->top_row ; row <= ses->split->bot_row ; row++)
+		{
+			print_stdout("\e[%d;1H\e[0m", row);
+
+			for (col = 0 ; col < ses->split->top_col - 1 ; col++)
+			{
+				print_stdout("%s", arg);
+			}
+		}
+		restore_pos(ses);
+	}
+}
+
+void fill_right_region(struct session *ses, char *arg)
+{
+	int row, col;
+
+	if (ses->split->bot_col < gtd->screen->cols)
+	{
+		save_pos(ses);
+
+		for (row = ses->split->top_row ; row <= ses->split->bot_row ; row++)
+		{
+			print_stdout("\e[%d;%dH\e[0m", row, ses->split->bot_col + 1);
+
+			for (col = ses->split->bot_col + 1 ; col <= gtd->screen->cols ; col++)
+			{
+				print_stdout("%s", arg);
+			}
+		}
+		restore_pos(ses);
+	}
+}
+
+void fill_split_region(struct session *ses, char *arg)
+{
+	fill_top_region(ses, arg);
+
+	fill_bot_region(ses, arg);
+
+	fill_left_region(ses, arg);
+
+	fill_right_region(ses, arg);
+}
+
+void get_line_screen(char *result, int row)
+{
+	strcpy(result, gtd->screen->lines[row]->str);
+}
+
+void get_word_screen(char *result, int row, int col)
+{
+	char *ptr;
+	int i, j;
+
+	strip_vt102_codes(gtd->screen->lines[row]->str, result);
+
+	ptr = result;
+
+	if (!isalnum((int) ptr[col]) && ptr[col] != '_')
+	{
+		sprintf(result, "%c", ptr[col]);
+
+		return;
+	}
+
+	for (i = col ; i >= 0 ; i--)
+	{
+		if (!isalnum((int) ptr[i]) && ptr[i] != '_')
+		{
+			break;
+		}
+	}
+	i++;
+
+	for (j = col ; ptr[j] ; j++)
+	{
+		if (!isalnum((int) ptr[j]) && ptr[j] != '_')
+		{
+			break;
+		}
+	}
+
+	strncpy(result, &ptr[i], j - i);
+
+	result[j - i] = 0;
+}

+ 733 - 0
session.c

@@ -0,0 +1,733 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2019 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
+*                                                                             *
+*                         coded by Peter Unold 1992                           *
+******************************************************************************/
+
+#include "tintin.h"
+
+DO_COMMAND(do_all)
+{
+	char arg1[BUFFER_SIZE];
+	struct session *sesptr;
+
+	if (gts->next)
+	{
+		sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+
+		for (sesptr = gts->next ; sesptr ; sesptr = gtd->all)
+		{
+			gtd->all = sesptr->next;
+
+			if (!HAS_BIT(sesptr->flags, SES_FLAG_CLOSED))
+			{
+				script_driver(sesptr, LIST_COMMAND, arg1);
+			}
+		}
+	}
+	else
+	{
+		show_error(ses, LIST_COMMAND, "#ALL: THERE AREN'T ANY SESSIONS.");
+	}
+	return ses;
+}
+
+
+DO_COMMAND(do_session)
+{
+	char temp[BUFFER_SIZE], arg1[BUFFER_SIZE];
+	struct session *sesptr;
+	int cnt;
+
+	substitute(ses, arg, temp, SUB_VAR|SUB_FUN);
+
+	arg = temp;
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+
+	if (*arg1 == 0)
+	{
+		tintin_puts(ses, "#THESE SESSIONS HAVE BEEN DEFINED:");
+
+		for (sesptr = gts->next ; sesptr ; sesptr = sesptr->next)
+		{
+			show_session(ses, sesptr);
+		}
+	}
+	else if (*arg1 && *arg == 0)
+	{
+		if (!strncasecmp(arg1, "telnet://", 9))
+		{
+			char *pti, *pto;
+
+			pto = temp;
+			pti = arg1 + 9;
+
+			while (*pti)
+			{
+				if (*pti == '/')
+				{
+					break;
+				}
+				else if (*pti == ':')
+				{
+					pti++;
+					*pto++ = ' ';
+				}
+				else
+				{
+					*pto++ = *pti++;
+				}
+			}
+			*pto = 0;
+
+			ses = new_session(ses, "telnet", temp, 0, 0);
+		}
+
+		if (*arg1 == '+')
+		{
+			return activate_session(ses->next ? ses->next : gts->next ? gts->next : ses);
+		}
+
+		if (*arg1 == '-')
+		{
+			return activate_session(ses->prev ? ses->prev : gts->prev ? gts->prev : ses);
+		}
+
+		if (is_number(arg1))
+		{
+			for (cnt = 0, sesptr = gts ; sesptr ; cnt++, sesptr = sesptr->next)
+			{
+				if (cnt == atoi(arg1))
+				{
+					return activate_session(sesptr);
+				}
+			}
+		}
+
+		tintin_puts(ses, "#THAT SESSION IS NOT DEFINED.");
+	}
+	else
+	{
+		ses = new_session(ses, arg1, arg, 0, 0);
+	}
+	return ses;
+}
+
+
+DO_COMMAND(do_snoop)
+{
+	struct session *sesptr = ses;
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg1)
+	{
+		sesptr = find_session(arg1);
+
+		if (sesptr == NULL)
+		{
+			show_error(ses, LIST_COMMAND, "#SNOOP: THERE'S NO SESSION NAMED {%s}", arg1);
+			
+			return ses;
+		}
+	}
+	else
+	{
+		sesptr = ses;
+	}
+
+	if (*arg2 == 0)
+	{
+		if (HAS_BIT(sesptr->flags, SES_FLAG_SNOOP))
+		{
+			show_message(ses, LIST_COMMAND, "#SNOOP: NO LONGER SNOOPING SESSION '%s'", sesptr->name);
+		}
+		else
+		{
+			show_message(ses, LIST_COMMAND, "#SNOOP: SNOOPING SESSION '%s'", sesptr->name);
+		}
+		TOG_BIT(sesptr->flags, SES_FLAG_SNOOP);
+	}
+	else if (is_abbrev(arg2, "ON"))
+	{
+		show_message(ses, LIST_COMMAND, "#SNOOP: SNOOPING SESSION '%s'", sesptr->name);
+
+		SET_BIT(sesptr->flags, SES_FLAG_SNOOP);
+	}
+	else if (is_abbrev(arg2, "OFF"))
+	{
+		show_message(ses, LIST_COMMAND, "#SNOOP: NO LONGER SNOOPING SESSION '%s'", sesptr->name);
+
+		DEL_BIT(sesptr->flags, SES_FLAG_SNOOP);
+	}
+	else
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SNOOP {session} {ON|OFF}");
+	}
+	return ses;
+}
+
+
+DO_COMMAND(do_zap)
+{
+	struct session *sesptr;
+	char arg1[BUFFER_SIZE];
+
+	push_call("do_zap(%p,%p)",ses,arg);
+
+	sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg1)
+	{
+		sesptr = find_session(arg1);
+
+		if (sesptr == NULL)
+		{
+			show_error(ses, LIST_COMMAND, "#ZAP: THERE'S NO SESSION WITH THAT NAME!");
+
+			pop_call();
+			return ses;
+		}
+	}
+	else
+	{
+		sesptr = ses;
+	}
+
+	tintin_puts(sesptr, "");
+
+	tintin_puts(sesptr, "#ZZZZZZZAAAAAAAAPPPP!!!!!!!!! LET'S GET OUTTA HERE!!!!!!!!");
+
+	if (sesptr == gts)
+	{
+		pop_call();
+		return do_end(NULL, "");
+	}
+
+	if (ses == sesptr)
+	{
+		cleanup_session(sesptr);
+
+		pop_call();
+		return gtd->ses;
+	}
+	cleanup_session(sesptr);
+
+	pop_call();
+	return ses;
+}
+
+
+void show_session(struct session *ses, struct session *ptr)
+{
+	char temp[BUFFER_SIZE];
+
+	sprintf(temp, "%-10s %18s:%-5s", ptr->name, ptr->session_host, ptr->session_port);
+
+	cat_sprintf(temp, " %8s", ptr == gtd->ses ? "(active)" :  "");
+
+	cat_sprintf(temp, " %10s", ptr->mccp2 ? (ptr->mccp3 ? "(mccp 2+3)" : "(mccp 2)  ") : ptr->mccp3 ? "(mccp 3)" : "");
+
+	cat_sprintf(temp, " %7s", HAS_BIT(ptr->flags, SES_FLAG_SNOOP) ? "(snoop)" : "");
+
+	cat_sprintf(temp, " %5s", ptr->logfile ? "(log)" : "");
+
+	cat_sprintf(temp, " %5s", ptr->ssl ? "(ssl)" : "");
+
+	tintin_puts2(ses, temp);
+}
+
+struct session *find_session(char *name)
+{
+	struct session *ses;
+
+	for (ses = gts ; ses ; ses = ses->next)
+	{
+		if (!strcmp(ses->name, name))
+		{
+			return ses;
+		}
+	}
+
+	if (!strcmp("ats", name))
+	{
+		return gtd->ses;
+	}
+
+	return NULL;
+}
+
+// find a session to activate when current session is closed
+
+struct session *newactive_session(void)
+{
+	push_call("newactive_session(void)");
+
+	if (gts->next)
+	{
+		activate_session(gts->next);
+	}
+	else
+	{
+		activate_session(gts);
+	}
+	pop_call();
+	return gtd->ses;
+}
+
+struct session *activate_session(struct session *ses)
+{
+	check_all_events(gtd->ses, SUB_ARG, 0, 1, "SESSION DEACTIVATED", gtd->ses->name);
+
+	gtd->ses = ses;
+
+	dirty_screen(ses);
+
+	show_message(ses, LIST_COMMAND, "#SESSION '%s' ACTIVATED.", ses->name);
+
+	check_all_events(ses, SUB_ARG, 0, 1, "SESSION ACTIVATED", ses->name);
+
+	return ses;
+}
+
+/**********************/
+/* open a new session */
+/**********************/
+
+struct session *new_session(struct session *ses, char *name, char *arg, int desc, int ssl)
+{
+	int cnt = 0;
+	char host[BUFFER_SIZE], port[BUFFER_SIZE], file[BUFFER_SIZE];
+	struct session *newses;
+
+	push_call("new_session(%p,%p,%p,%d,%d)",ses,name,arg,desc,ssl);
+
+	if (HAS_BIT(gtd->flags, TINTIN_FLAG_TERMINATE))
+	{
+		pop_call();
+		return ses;
+	}
+
+	arg = sub_arg_in_braces(ses, arg, host, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, port, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, file, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if (desc == 0)
+	{
+		if (*host == 0)
+		{
+			tintin_puts(ses, "#HEY! SPECIFY AN ADDRESS WILL YOU?");
+
+			pop_call();
+			return ses;
+		}
+
+		if (*port == 0)
+		{
+			tintin_puts(ses, "#HEY! SPECIFY A PORT NUMBER WILL YOU?");
+
+			pop_call();
+			return ses;
+		}
+	}
+
+	if (find_session(name))
+	{
+		tintin_puts(ses, "#THERE'S A SESSION WITH THAT NAME ALREADY.");
+
+		pop_call();
+		return ses;
+	}
+
+	newses                = (struct session *) calloc(1, sizeof(struct session));
+
+	newses->name          = strdup(name);
+	newses->session_host  = strdup(host);
+	newses->session_ip    = strdup("");
+	newses->session_port  = strdup(port);
+	newses->created       = gtd->time;
+
+	newses->group         = strdup(gts->group);
+	newses->flags         = gts->flags;
+	newses->color         = gts->color;
+	newses->logmode       = gts->logmode;
+	newses->charset       = gts->charset;
+
+	newses->telopts       = gts->telopts;
+	newses->auto_tab      = gts->auto_tab;
+	newses->packet_patch  = gts->packet_patch;
+	newses->tab_width     = gts->tab_width;
+	newses->cmd_color     = strdup(gts->cmd_color);
+
+	newses->read_max      = gts->read_max;
+	newses->read_buf      = (unsigned char *) calloc(1, gts->read_max);
+
+	newses->lognext_name  = strdup("");
+	newses->logline_name  = strdup("");
+	newses->rand          = utime();
+
+
+	LINK(newses, gts->next, gts->prev);
+
+	if (HAS_BIT(gtd->flags, TINTIN_FLAG_INHERITANCE))
+	{
+		for (cnt = 0 ; cnt < LIST_MAX ; cnt++)
+		{
+			newses->list[cnt] = copy_list(newses, gts->list[cnt], cnt);
+		}
+	}
+	else
+	{
+		for (cnt = 0 ; cnt < LIST_MAX ; cnt++)
+		{
+			if (cnt == LIST_CONFIG)
+			{
+				newses->list[cnt] = copy_list(newses, gts->list[cnt], cnt);
+			}
+			else
+			{
+				newses->list[cnt] = init_list(newses, cnt, 32);
+			}
+		}
+	}
+
+	newses->event_flags = gts->event_flags;
+
+	newses->split   = calloc(1, sizeof(struct split_data));
+
+	memcpy(newses->split, gts->split, sizeof(struct split_data));
+
+	newses->cur_row = gts->cur_row;
+	newses->cur_col = gts->cur_col;
+
+	newses->wrap    = gts->wrap;
+
+        newses->scroll = calloc(1, sizeof(struct scroll_data));
+	init_buffer(newses, gts->scroll->size);
+
+	memcpy(&newses->cur_terminal, &gts->cur_terminal, sizeof(gts->cur_terminal));
+
+	if (desc == 0)
+	{
+		tintin_printf(ses, "#TRYING TO CONNECT '%s' TO '%s' PORT '%s'.", newses->name, newses->session_host, newses->session_port);
+	}
+	else if (desc == -1)
+	{
+		// #PORT INITIALIZE {NAME} {PORT} {FILE}
+	}
+	else
+	{
+		tintin_printf(ses, "#TRYING TO LAUNCH '%s' RUNNING '%s'.", newses->name, newses->session_host);
+	}
+
+	dirty_screen(newses);
+
+	if (gtd->level->background == 0)
+	{
+		gtd->ses = newses;
+	}
+
+	if (desc == 0)
+	{
+		newses = connect_session(newses);
+	}
+	else if (desc == -1)
+	{
+		SET_BIT(newses->flags, SES_FLAG_PORT);
+	}
+	else
+	{
+		SET_BIT(newses->flags, SES_FLAG_CONNECTED|SES_FLAG_RUN);
+
+		SET_BIT(newses->telopts, TELOPT_FLAG_SGA);
+		DEL_BIT(newses->telopts, TELOPT_FLAG_ECHO);
+
+		newses->socket = desc;
+	}
+
+	if (newses == NULL)
+	{
+		pop_call();
+		return ses;
+	}
+
+#ifdef HAVE_GNUTLS_H
+	if (ssl)
+	{
+		newses->ssl = ssl_negotiate(newses);
+
+		if (newses->ssl == 0)
+		{
+			cleanup_session(newses);
+
+			pop_call();
+			return ses;
+		}
+	}
+#endif
+
+	if (*file)
+	{
+		newses = do_read(newses, file);
+	}
+	check_all_events(newses, SUB_ARG, 0, 4, "SESSION CREATED", newses->name, newses->session_host, newses->session_ip, newses->session_port);
+
+	if (gtd->level->background == 0)
+	{
+		pop_call();
+		return newses;
+	}
+	pop_call();
+	return ses;
+}
+
+struct session *connect_session(struct session *ses)
+{
+	int sock;
+	static struct timeval to;
+
+	push_call("connect_session(%p)",ses);
+
+	ses->connect_retry = utime() + gts->connect_retry;
+
+	reconnect:
+
+	sock = connect_mud(ses, ses->session_host, ses->session_port);
+
+	if (sock == -1)
+	{
+//		syserr_printf(ses, "connect_session: connect");
+
+		cleanup_session(ses);
+
+		pop_call();
+		return NULL;
+	}
+
+	if (sock)
+	{
+/*
+		if (fcntl(sock, F_SETFL, O_NDELAY|O_NONBLOCK) == -1)
+		{
+			syserr_printf(ses, "connect_session: fcntl O_NDELAY|O_NONBLOCK");
+		}
+*/
+		ses->socket = sock;
+
+		ses->connect_retry = 0;
+
+		SET_BIT(ses->flags, SES_FLAG_CONNECTED);
+
+		tintin_printf2(ses, "");
+
+		tintin_printf(ses, "#SESSION '%s' CONNECTED TO '%s' PORT '%s'", ses->name, ses->session_host, ses->session_port);
+
+		check_all_events(ses, SUB_ARG, 0, 4, "SESSION CONNECTED", ses->name, ses->session_host, ses->session_ip, ses->session_port);
+
+		pop_call();
+		return ses;
+	}
+
+	if (ses->connect_retry > utime())
+	{
+		fd_set readfds;
+
+		FD_ZERO(&readfds);
+		FD_SET(0, &readfds);
+
+		if (select(FD_SETSIZE, &readfds, NULL, NULL, &to) <= 0)
+		{
+			if (to.tv_sec == 0)
+			{
+				to.tv_sec = 1;
+
+				tintin_printf(ses, "#SESSION '%s' FAILED TO CONNECT. RETRYING FOR %d SECONDS.", ses->name, (ses->connect_retry - utime()) / 1000000);
+			}
+
+			goto reconnect;
+		}
+	}
+
+	if (ses->connect_error)
+	{
+		to.tv_sec = 0;
+
+		tintin_printf(ses, "#SESSION '%s' FAILED TO CONNECT.", ses->name);
+	}
+
+	cleanup_session(ses);
+
+	pop_call();
+	return NULL;
+}
+
+/*****************************************************************************/
+/* cleanup after session died. if session=gtd->ses, try find new active      */
+/*****************************************************************************/
+
+void cleanup_session(struct session *ses)
+{
+	push_call("cleanup_session(%p)",ses);
+
+	if (HAS_BIT(ses->flags, SES_FLAG_CLOSED))
+	{
+		tintin_printf2(NULL, "\n#SESSION '%s' IS ALREADY CLOSED.", ses->name);
+		dump_stack();
+
+		pop_call();
+		return;
+	}
+
+	if (ses == gtd->update)
+	{
+		gtd->update = ses->next;
+	}
+
+	if (ses == gtd->all)
+	{
+		gtd->all = ses->next;
+	}
+
+	UNLINK(ses, gts->next, gts->prev);
+
+	if (ses->socket)
+	{
+		if (close(ses->socket) == -1)
+		{
+			syserr_printf(ses, "cleanup_session: close");
+		}
+//		else
+//		{
+//			int status;
+
+//			wait(&status);
+//		}
+
+		// the PID is stored in the session's port.
+/*
+		if (HAS_BIT(ses->flags, SES_FLAG_RUN))
+		{
+			kill(atoi(ses->session_port), SIGTERM);
+		}
+*/
+	}
+
+	if (ses->port)
+	{
+		port_uninitialize(ses, "", "", "");
+	}
+
+	SET_BIT(ses->flags, SES_FLAG_CLOSED);
+
+	if (HAS_BIT(ses->flags, SES_FLAG_PORT) || HAS_BIT(ses->flags, SES_FLAG_CONNECTED))
+	{
+		DEL_BIT(ses->flags, SES_FLAG_CONNECTED);
+
+		check_all_events(ses, SUB_ARG, 0, 4, "SESSION DISCONNECTED", ses->name, ses->session_host, ses->session_ip, ses->session_port);
+
+		tintin_printf(gtd->ses, "#SESSION '%s' DIED.", ses->name);
+	}
+	else
+	{
+		check_all_events(ses, SUB_ARG, 0, 4, "SESSION TIMED OUT", ses->name, ses->session_host, ses->session_ip, ses->session_port);
+
+		tintin_printf(gtd->ses, "#SESSION '%s' TIMED OUT.", ses->name);
+	}
+
+	if (ses == gtd->ses)
+	{
+		gtd->ses = newactive_session();
+	}
+
+#ifdef HAVE_GNUTLS_H
+
+	if (ses->ssl)
+	{
+		gnutls_deinit(ses->ssl);
+	}
+
+#endif
+
+	LINK(ses, gtd->dispose_next, gtd->dispose_prev);
+
+	pop_call();
+	return;
+}
+
+void dispose_session(struct session *ses)
+{
+	int index;
+
+	push_call("dispose_session(%p)", ses);
+
+	UNLINK(ses, gtd->dispose_next, gtd->dispose_prev);
+
+	if (ses->logfile)
+	{
+		fclose(ses->logfile);
+	}
+
+	if (ses->lognext_file)
+	{
+		fclose(ses->lognext_file);
+	}
+
+	if (ses->logline_file)
+	{
+		fclose(ses->logline_file);
+	}
+
+	if (ses->map)
+	{
+		delete_map(ses);
+	}
+
+	for (index = 0 ; index < LIST_MAX ; index++)
+	{
+		free_list(ses->list[index]);
+	}
+
+	client_end_mccp2(ses);
+	client_end_mccp3(ses);
+
+	init_buffer(ses, 0);
+
+	free(ses->name);
+	free(ses->session_host);
+	free(ses->session_ip);
+	free(ses->session_port);
+	free(ses->group);
+	free(ses->read_buf);
+	free(ses->cmd_color);
+	free(ses->lognext_name);
+	free(ses->logline_name);
+	free(ses->split);
+
+	free(ses);
+
+	pop_call();
+	return;
+}

+ 547 - 0
show.c

@@ -0,0 +1,547 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2019 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*               (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                  *
+*                                                                             *
+*                       coded by Peter Unold 1992                             *
+*                  recoded by Igor van den Hoven 2005                         *
+******************************************************************************/
+
+#include "tintin.h"
+
+
+DO_COMMAND(do_showme)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], arg3[BUFFER_SIZE], temp[STRING_SIZE], *output;
+	int lnf;
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ALL);
+
+	lnf = !str_suffix(arg1, "\\");
+
+	substitute(ses, arg1, temp, SUB_VAR|SUB_FUN);
+	substitute(ses, temp, arg1, SUB_COL|SUB_ESC);
+
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg3, GET_ONE, SUB_VAR|SUB_FUN);
+
+	do_one_line(arg1, ses);
+
+	if (HAS_BIT(ses->flags, SES_FLAG_GAG))
+	{
+		DEL_BIT(ses->flags, SES_FLAG_GAG);
+
+		gtd->level->ignore++;
+
+		show_info(ses, LIST_GAG, "#INFO GAG {%s}", arg1);
+
+		gtd->level->ignore--;
+
+		return ses;
+	}
+
+	if (*arg2)
+	{
+		split_show(ses, arg1, (int) get_number(ses, arg2), (int) get_number(ses, arg3));
+
+		return ses;
+	}
+
+	if (strip_vt102_strlen(ses, ses->more_output) != 0)
+	{
+		output = str_dup_printf("\n%s%s%s", COLOR_TEXT, arg1, COLOR_TEXT);
+	}
+	else
+	{
+		output = str_dup_printf("%s%s%s", COLOR_TEXT, arg1, COLOR_TEXT);
+	}
+
+	add_line_buffer(ses, output, lnf);
+
+	if (ses == gtd->ses)
+	{
+		if (!HAS_BIT(ses->flags, SES_FLAG_READMUD) && IS_SPLIT(ses))
+		{
+			save_pos(ses);
+
+			goto_pos(ses, ses->split->bot_row, ses->split->top_col);
+		}
+
+		print_line(ses, &output, lnf);
+
+		if (!HAS_BIT(ses->flags, SES_FLAG_READMUD) && IS_SPLIT(ses))
+		{
+			restore_pos(ses);
+		}
+	}
+
+	str_free(output);
+
+	return ses;
+}
+
+void show_message(struct session *ses, int index, char *format, ...)
+{
+	struct listroot *root;
+	char *buffer;
+	va_list args;
+
+	push_call("show_message(%p,%p,%p)",ses,index,format);
+
+	root = ses->list[index];
+
+	if (gtd->level->verbose || gtd->level->debug )
+	{
+		goto display;
+	}
+
+	if (HAS_BIT(root->flags, LIST_FLAG_DEBUG))
+	{
+		goto display;
+	}
+
+	if (!HAS_BIT(root->flags, LIST_FLAG_MESSAGE))
+	{
+		goto end;
+	}
+
+	if (gtd->level->input)
+	{
+		goto end;
+	}
+
+	display:
+
+	va_start(args, format);
+	vasprintf(&buffer, format, args);
+	va_end(args);
+
+	tintin_puts2(ses, buffer);
+
+	free(buffer);
+
+	pop_call();
+	return;
+
+	end:
+
+
+	if (HAS_BIT(root->flags, LIST_FLAG_LOG))
+	{
+		if (ses->logfile)
+		{
+			va_start(args, format);
+			vasprintf(&buffer, format, args);
+			va_end(args);
+
+			logit(ses, buffer, ses->logfile, LOG_FLAG_LINEFEED);
+
+			free(buffer);
+		}
+	}
+
+	pop_call();
+	return;
+}
+
+void show_error(struct session *ses, int index, char *format, ...)
+{
+	struct listroot *root;
+	char *buffer;
+	va_list args;
+
+	push_call("show_error(%p,%p,%p)",ses,index,format);
+
+	va_start(args, format);
+	vasprintf(&buffer, format, args);
+	va_end(args);
+
+	if (gtd->level->verbose || gtd->level->debug)
+	{
+		tintin_puts2(ses, buffer);
+
+		goto end;
+	}
+
+	root = ses->list[index];
+
+	if (HAS_BIT(root->flags, LIST_FLAG_DEBUG))
+	{
+		tintin_puts2(ses, buffer);
+
+		goto end;
+	}
+
+	if (HAS_BIT(root->flags, LIST_FLAG_MESSAGE))
+	{
+		tintin_puts2(ses, buffer);
+
+		goto end;
+	}
+
+	if (HAS_BIT(root->flags, LIST_FLAG_LOG))
+	{
+		if (ses->logfile)
+		{
+			logit(ses, buffer, ses->logfile, LOG_FLAG_LINEFEED);
+		}
+	}
+
+	end:
+
+	free(buffer);
+
+	pop_call();
+	return;
+}
+
+void show_debug(struct session *ses, int index, char *format, ...)
+{
+	struct listroot *root;
+	char buf[STRING_SIZE];
+	va_list args;
+
+	push_call("show_debug(%p,%p,%p)",ses,index,format);
+
+	root = ses->list[index];
+
+	if (gtd->level->debug == 0 && !HAS_BIT(root->flags, LIST_FLAG_DEBUG) && !HAS_BIT(root->flags, LIST_FLAG_LOG))
+	{
+		pop_call();
+		return;
+	}
+
+	va_start(args, format);
+
+	vsprintf(buf, format, args);
+
+	va_end(args);
+
+	if (gtd->level->debug || HAS_BIT(root->flags, LIST_FLAG_DEBUG))
+	{
+		gtd->level->verbose++;
+
+		tintin_puts2(ses, buf);
+
+		gtd->level->verbose--;
+
+		pop_call();
+		return;
+	}
+
+	if (HAS_BIT(root->flags, LIST_FLAG_LOG))
+	{
+		if (ses->logfile)
+		{
+			logit(ses, buf, ses->logfile, LOG_FLAG_LINEFEED);
+		}
+	}
+	pop_call();
+	return;
+}
+
+void show_info(struct session *ses, int index, char *format, ...)
+{
+	struct listroot *root;
+	char buf[STRING_SIZE];
+	va_list args;
+
+	push_call("show_info(%p,%p,%p)",ses,index,format);
+
+	root = ses->list[index];
+
+	if (gtd->level->info == 0 && !HAS_BIT(root->flags, LIST_FLAG_INFO))
+	{
+		pop_call();
+		return;
+	}
+
+	va_start(args, format);
+
+	vsprintf(buf, format, args);
+
+	va_end(args);
+
+	gtd->level->verbose++;
+
+	tintin_puts(ses, buf);
+
+	gtd->level->verbose--;
+
+	pop_call();
+	return;
+}
+
+void print_lines(struct session *ses, int flags, char *format, ...)
+{
+	char *buffer, *str_buf;
+	va_list args;
+
+	push_call("print_lines(%p,%d,%p,...)",ses,flags,format);
+
+	va_start(args, format);
+	vasprintf(&buffer, format, args);
+	va_end(args);
+
+	if (flags)
+	{
+		str_buf = str_alloc(BUFFER_SIZE + strlen(buffer) * 2);
+
+		substitute(ses, buffer, str_buf, flags);
+
+		show_lines(ses, str_buf);
+
+		str_free(str_buf);
+	}
+	else
+	{
+		show_lines(ses, buffer);
+	}
+
+	free(buffer);
+
+	pop_call();
+	return;
+}
+
+void show_lines(struct session *ses, char *str)
+{
+	char *ptf;
+
+	push_call("show_lines(%p,%p,...)",ses,str);
+
+	while (*str)
+	{
+		ptf = strchr(str, '\n');
+
+		if (ptf == NULL)
+		{
+			break;
+		}
+		*ptf++ = 0;
+
+		tintin_puts3(ses, str);
+
+		str = ptf;
+	}
+	pop_call();
+	return;
+}
+
+
+void tintin_header(struct session *ses, char *format, ...)
+{
+	char arg[BUFFER_SIZE], buf[BUFFER_SIZE];
+	va_list args;
+	int cols;
+
+	push_call("tintin_header(%p,%p)",ses,format);
+
+	va_start(args, format);
+	vsprintf(arg, format, args);
+	va_end(args);
+
+	cols = get_scroll_cols(ses);
+
+	if (cols < 2)
+	{
+		pop_call();
+		return;
+	}
+
+	if ((int) strlen(arg) > cols - 2)
+	{
+		arg[cols - 2] = 0;
+	}
+
+	if (HAS_BIT(ses->flags, SES_FLAG_SCREENREADER))
+	{
+		memset(buf, ' ', cols);
+	}
+	else
+	{
+		memset(buf, '#', cols);
+	}
+
+	memcpy(&buf[(cols - strlen(arg)) / 2], arg, strlen(arg));
+
+	buf[cols] = 0;
+
+	tintin_puts2(ses, buf);
+
+	pop_call();
+	return;
+}
+
+void tintin_printf2(struct session *ses, char *format, ...)
+{
+	char *buffer;
+	va_list args;
+
+	push_call("tintin_printf2(%p,%p,...)",ses,format);
+
+	va_start(args, format);
+	vasprintf(&buffer, format, args);
+	va_end(args);
+
+	tintin_puts2(ses, buffer);
+
+	free(buffer);
+
+	pop_call();
+	return;
+}
+
+void tintin_printf(struct session *ses, char *format, ...)
+{
+	char buffer[BUFFER_SIZE];
+	va_list args;
+
+	va_start(args, format);
+	vsprintf(buffer, format, args);
+	va_end(args);
+
+	tintin_puts(ses, buffer);
+}
+
+/*
+	Show string and fire triggers
+*/
+
+void tintin_puts(struct session *ses, char *string)
+{
+	if (ses == NULL)
+	{
+		ses = gtd->ses;
+	}
+
+	do_one_line(string, ses);
+
+	if (HAS_BIT(ses->flags, SES_FLAG_GAG))
+	{
+		DEL_BIT(ses->flags, SES_FLAG_GAG);
+
+		gtd->level->ignore++;
+
+		if (HAS_BIT(ses->list[LIST_GAG]->flags, LIST_FLAG_INFO))
+		{
+			show_info(ses, LIST_GAG, "#INFO GAG {%s}", string);
+		}
+
+		gtd->level->ignore--;
+	}
+	else
+	{
+		tintin_puts2(ses, string);
+	}
+}
+
+/*
+	show string and don't fire triggers
+*/
+
+void tintin_puts2(struct session *ses, char *string)
+{
+	char *output;
+
+	push_call("tintin_puts2(%p,%p)",ses,string);
+
+	output = str_dup_printf("%s%s%s", COLOR_TEXT, string, COLOR_TEXT);
+
+	tintin_puts3(ses, output);
+
+	str_free(output);
+
+	pop_call();
+	return;
+}
+
+
+
+/*
+	show string, no triggers, no color reset
+*/
+
+void tintin_puts3(struct session *ses, char *string)
+{
+	char *output, temp[STRING_SIZE];
+
+	push_call("tintin_puts3(%p,%p)",ses,string);
+
+	if (ses == NULL)
+	{
+		ses = gtd->ses;
+	}
+
+	if (ses->line_capturefile)
+	{
+		sprintf(temp, "{%d}{%s}", ses->line_captureindex++, string);
+
+		if (ses->line_captureindex == 1)
+		{
+			set_nest_node_ses(ses, ses->line_capturefile, "%s", temp);
+		}
+		else
+		{
+			add_nest_node_ses(ses, ses->line_capturefile, "%s", temp);
+		}
+	}
+
+	if (!HAS_BIT(gtd->ses->flags, SES_FLAG_VERBOSE) && gtd->level->quiet && gtd->level->verbose == 0)
+	{
+		pop_call();
+		return;
+	}
+
+	if (strip_vt102_strlen(ses, ses->more_output) != 0)
+	{
+		output = str_dup_printf("\n%s", string);
+	}
+	else
+	{
+		output = str_dup_printf("%s", string);
+	}
+
+	add_line_buffer(ses, output, FALSE);
+
+	if (ses == gtd->ses)
+	{
+		if (!HAS_BIT(ses->flags, SES_FLAG_READMUD) && IS_SPLIT(ses))
+		{
+			save_pos(ses);
+
+			goto_pos(ses, ses->split->bot_row, ses->split->top_col);
+		}
+
+		print_line(ses, &output, FALSE);
+
+		if (!HAS_BIT(ses->flags, SES_FLAG_READMUD) && IS_SPLIT(ses))
+		{
+			restore_pos(ses);
+		}
+	}
+
+	str_free(output);
+
+	pop_call();
+	return;
+}
+

+ 342 - 0
split.c

@@ -0,0 +1,342 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2019 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
+*                                                                             *
+*                          coded by Bill Reiss 1993                           *
+*                     recoded by Igor van den Hoven 2004                      *
+******************************************************************************/
+
+#include "tintin.h"
+
+DO_COMMAND(do_split)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if ((*arg1 && !is_math(ses, arg1)) || (*arg2 && !is_math(ses, arg2)))
+	{
+		if (*arg == 0)
+		{
+			show_error(ses, LIST_COMMAND, "#SYNTAX: #SPLIT {TOP BAR} {BOTTOM BAR}");
+		}
+		else
+		{
+			show_error(ses, LIST_COMMAND, "#SYNTAX: #SPLIT {TOP BAR} {BOT BAR} {LEFT BAR} {RIGHT BAR}");
+		}
+		return ses;
+	}
+
+	ses->split->sav_top_row = *arg1 ? get_number(ses, arg1) : 0;
+	ses->split->sav_bot_row = *arg2 ? get_number(ses, arg2) : 1;
+
+	if (*arg == 0)
+	{
+		ses->split->sav_top_col = 0;
+		ses->split->sav_bot_col = 0;
+	}
+	else
+	{
+		arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+		arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+
+		if ((*arg1 && !is_math(ses, arg1)) || (*arg2 && !is_math(ses, arg2)))
+		{
+			show_error(ses, LIST_COMMAND, "#SYNTAX: #SPLIT {TOP BAR} {BOT BAR} {LEFT BAR} {RIGHT BAR}");
+
+			return ses;
+		}
+
+		ses->split->sav_top_col = *arg1 ? get_number(ses, arg1) : 1;
+		ses->split->sav_bot_col = *arg2 ? get_number(ses, arg2) : 0;
+	}
+
+	DEL_BIT(ses->flags, SES_FLAG_SCROLLSPLIT);
+	SET_BIT(ses->flags, SES_FLAG_SPLIT);
+
+	init_split(ses, ses->split->sav_top_row, ses->split->sav_top_col, ses->split->sav_bot_row,  ses->split->sav_bot_col);
+
+	return ses;
+}
+
+
+DO_COMMAND(do_unsplit)
+{
+	memset(ses->split, 0, sizeof(struct split_data));
+
+	ses->wrap = gtd->screen->cols;
+
+	reset_screen(ses);
+
+	SET_BIT(ses->scroll->flags, SCROLL_FLAG_RESIZE);
+
+	if (HAS_BIT(ses->flags, SES_FLAG_SPLIT))
+	{
+		if (HAS_BIT(ses->telopts, TELOPT_FLAG_NAWS))
+		{
+			client_send_sb_naws(ses, 0, NULL);
+		}
+		DEL_BIT(ses->flags, SES_FLAG_SPLIT);
+		DEL_BIT(ses->flags, SES_FLAG_SCROLLSPLIT);
+	}
+	check_all_events(ses, SUB_ARG, 0, 4, "SCREEN UNSPLIT", ntos(ses->split->top_row), ntos(ses->split->top_col), ntos(ses->split->bot_row), ntos(ses->split->bot_col));
+	return ses;
+}
+
+void init_split(struct session *ses, int top_row, int top_col, int bot_row, int bot_col)
+{
+	push_call("init_split(%p,%d,%d,%d,%d)",ses,top_row,top_col,bot_row,bot_col);
+
+	SET_BIT(ses->scroll->flags, SCROLL_FLAG_RESIZE);
+
+	if (!HAS_BIT(ses->flags, SES_FLAG_SPLIT))
+	{
+		ses->split->top_row = 1;
+		ses->split->top_col = 1;
+		ses->split->bot_row = gtd->screen->rows;
+		ses->split->bot_col = gtd->screen->cols;
+		ses->wrap = gtd->screen->cols;
+
+		init_pos(ses, gtd->screen->rows, 1);
+
+		if (ses->map && HAS_BIT(ses->map->flags, MAP_FLAG_VTMAP))
+		{
+			SET_BIT(ses->flags, SES_FLAG_UPDATEVTMAP);
+		}
+
+		pop_call();
+		return;
+	}
+
+	if (HAS_BIT(ses->flags, SES_FLAG_SCROLLSPLIT))
+	{
+		ses->split->top_row = top_row > 0 ? top_row : top_row < 0 ? gtd->screen->rows + top_row + 1 : 1;
+		ses->split->top_col = top_col > 0 ? top_col : top_col < 0 ? gtd->screen->cols + top_col + 1 : 1;
+		ses->split->bot_row = bot_row > 0 ? bot_row : bot_row < 0 ? gtd->screen->rows + bot_row + 1 : gtd->screen->rows - 2;
+		ses->split->bot_col = bot_col > 0 ? bot_col : bot_col < 0 ? gtd->screen->cols + bot_col + 1 : gtd->screen->cols;
+	}
+	else
+	{
+		ses->split->top_row = top_row > 0 ? top_row + 1 : top_row < 0 ? gtd->screen->rows + top_row + 1 : 1;
+		ses->split->top_col = top_col > 0 ? top_col + 1 : top_col < 0 ? gtd->screen->cols + top_col + 1 : 1;
+
+		ses->split->bot_row = bot_row > 0 ? gtd->screen->rows - bot_row - 1 : bot_row < 0 ? bot_row * -1 : gtd->screen->rows - 1;
+		ses->split->bot_col = bot_col > 0 ? gtd->screen->cols - bot_col : bot_col < 0 ? bot_col * -1 : gtd->screen->cols;
+	}
+
+	ses->split->top_row = URANGE(1, ses->split->top_row, gtd->screen->rows -3);
+	ses->split->bot_row = URANGE(ses->split->top_row + 1,  ses->split->bot_row, gtd->screen->rows - 1);
+
+	ses->split->top_col = URANGE(1, ses->split->top_col, gtd->screen->cols - 2);
+	ses->split->bot_col = URANGE(ses->split->top_col + 1, ses->split->bot_col, gtd->screen->cols);
+
+	ses->wrap = ses->split->bot_col - (ses->split->top_col - 1);
+
+	scroll_region(ses, ses->split->top_row, ses->split->bot_row);
+
+	init_pos(ses, gtd->screen->rows, 1);
+
+	if (HAS_BIT(ses->telopts, TELOPT_FLAG_NAWS))
+	{
+		client_send_sb_naws(ses, 0, NULL);
+	}
+
+	if (ses->map && HAS_BIT(ses->map->flags, MAP_FLAG_VTMAP))
+	{
+		SET_BIT(ses->flags, SES_FLAG_UPDATEVTMAP);
+	}
+
+	if (!HAS_BIT(ses->flags, SES_FLAG_SCROLLSPLIT))
+	{
+		if (gtd->level->quiet == 0)
+		{
+//			if (HAS_BIT(ses->charset, CHARSET_FLAG_UTF8))
+			{
+				do_screen(ses, "FILL DEFAULT");
+			}
+/*			else
+			{
+				fill_split_region(ses, "-");
+
+				erase_scroll_region(ses);
+				fill_split_region(ses, "-");
+				buffer_end(ses, "");
+
+			}*/
+		}
+	}
+	check_all_events(ses, SUB_ARG, 0, 4, "SCREEN SPLIT", ntos(ses->split->top_row), ntos(ses->split->top_col), ntos(ses->split->bot_row), ntos(ses->split->bot_col));
+
+	pop_call();
+	return;
+}
+
+
+/*
+	unsplit
+*/
+
+void reset_screen(struct session *ses)
+{
+	reset_scroll_region(ses);
+
+	init_pos(ses, gtd->screen->rows, 1);
+}
+
+
+/*
+	refresh
+*/
+
+void dirty_screen(struct session *ses)
+{
+	push_call("dirty_screen(%p)",ses);
+
+	refresh_session_terminal(ses);
+
+	print_stdout("\e=");
+
+	if (HAS_BIT(ses->flags, SES_FLAG_SPLIT))
+	{
+		init_split(ses, ses->split->sav_top_row, ses->split->sav_top_col, ses->split->sav_bot_row, ses->split->sav_bot_col);
+	}
+	else if (IS_SPLIT(ses))
+	{
+		scroll_region(ses, ses->split->top_row, ses->split->bot_row);
+	}
+	else
+	{
+		reset_screen(ses);
+	}
+
+	if (IS_SPLIT(ses) && ses == gtd->ses)
+	{
+		init_pos(ses, gtd->screen->rows, 1);
+	}
+
+	pop_call();
+	return;
+}
+
+
+void split_show(struct session *ses, char *prompt, int row, int col)
+{
+	char buf1[BUFFER_SIZE];
+	int original_row, original_col, len, clear;
+
+	original_row = row;
+	original_col = col;
+
+	if (row < 0)
+	{
+		row = 1 + gtd->screen->rows + row;
+	}
+	else if (row == 0)
+	{
+		row = gtd->screen->rows - 1;
+	}
+
+	clear = 0;
+
+	if (col < 0)
+	{
+		col = 1 + gtd->screen->cols + col;
+	}
+	else if (col == 0)
+	{
+		col = 1;
+		clear = 1;
+	}
+
+	if (row < 1 || row > gtd->screen->rows)
+	{
+		show_error(ses, LIST_PROMPT, "#ERROR: PROMPT ROW IS OUTSIDE THE SCREEN: {%s} {%d} {%d}.", prompt, original_row, original_col);
+
+		return;
+	}
+
+	if (col < 0 || col > gtd->screen->cols)
+	{
+		show_error(ses, LIST_PROMPT, "#ERROR: PROMPT COLUMN IS OUTSIDE THE SCREEN: {%s} {%d} {%d}.", prompt, original_row, original_col);
+
+		return;
+	}
+
+	if (row > ses->split->top_row && row <= ses->split->bot_row)
+	{
+		if (col >= ses->split->top_col && col <= ses->split->bot_col)
+		{
+			show_error(ses, LIST_PROMPT, "#ERROR: PROMPT ROW IS INSIDE THE SCROLLING REGION: {%s} {%d}.", prompt, original_row);
+			return;
+		}
+	}
+
+	if (ses != gtd->ses)
+	{
+		return;
+	}
+
+	len = strip_vt102_strlen(ses, prompt);
+
+	if (len == 0)
+	{
+		sprintf(buf1, "%.*s", gtd->screen->cols + 4, "\e[0m--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------");
+	}
+	else if (col - 1 + len <= gtd->screen->cols)
+	{
+		sprintf(buf1, "%s", prompt);
+	}
+	else
+	{
+		show_debug(ses, LIST_PROMPT, "#DEBUG PROMPT {%s}", prompt);
+
+		sprintf(buf1, "#PROMPT SIZE (%d) LONGER THAN ROW SIZE (%d)", len, gtd->screen->cols);
+	}
+
+	save_pos(ses);
+
+	if (row == gtd->screen->rows)
+	{
+		gtd->input_off = len + 1;
+
+		goto_pos(ses, row, col);
+
+		print_stdout("%s%s", buf1, gtd->input_buf);
+
+		// bit of a hack
+
+		gtd->screen->sav_col[0] = inputline_cur_pos();
+	}
+	else
+	{
+		goto_pos(ses, row, col);
+
+		print_stdout("%s%s", clear ? "\e[2K" : "", buf1);
+	}
+
+//	set_line_screen(buf1, row - 1, col - 1);
+
+	restore_pos(ses);
+}
+
+

+ 408 - 0
ssl.c

@@ -0,0 +1,408 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2008-2019 Adam Borowski and Igor van den Hoven                  *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
+*                                                                             *
+*                         coded by Adam Borowski 2008                         *
+*                   modifications by Igor van den Hoven 2014                  *
+******************************************************************************/
+
+#include "tintin.h"
+
+#ifdef HAVE_GNUTLS_H
+
+static gnutls_certificate_credentials_t ssl_cred = 0;
+
+static int ssl_check_cert(struct session *ses, gnutls_session_t sslses);
+
+DO_COMMAND(do_ssl)
+{
+	char temp[BUFFER_SIZE], arg1[BUFFER_SIZE];
+
+	substitute(ses, arg, temp, SUB_VAR|SUB_FUN);
+
+	arg = temp;
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+
+	if (*arg1 == 0 || *arg == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SSL {name} {host} {port}");
+	}
+	else
+	{
+		ses = new_session(ses, arg1, arg, 0, 1);
+	}
+	return ses;
+}
+
+gnutls_session_t ssl_negotiate(struct session *ses)
+{
+	gnutls_session_t ssl_ses;
+
+	int ret;
+	
+	if (!ssl_cred)
+	{
+		gnutls_global_init();
+		gnutls_certificate_allocate_credentials(&ssl_cred);
+	}
+
+	gnutls_init(&ssl_ses, GNUTLS_CLIENT);
+	gnutls_set_default_priority(ssl_ses);
+	gnutls_credentials_set(ssl_ses, GNUTLS_CRD_CERTIFICATE, ssl_cred);
+	gnutls_transport_set_ptr(ssl_ses, (gnutls_transport_ptr_t) (long int) ses->socket);
+
+	do 
+	{
+		ret = gnutls_handshake(ssl_ses);
+	}
+	while (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED);
+
+	if (ret)
+	{
+		tintin_printf2(ses, "#SSL: handshake failed error: %s", gnutls_strerror(ret));
+		gnutls_deinit(ssl_ses);
+		return 0;
+	}
+/*
+	{
+		char *debug = gnutls_session_get_desc(ssl_ses);
+
+		tintin_printf2(ses, "#SSL: %s", debug);
+
+		gnutls_free(debug);
+	}
+*/
+	if (!ssl_check_cert(ses, ssl_ses))
+	{
+		gnutls_deinit(ssl_ses);
+		return 0;
+	}
+	return ssl_ses;
+}
+
+
+static int get_cert_file(struct session *ses, char *result)
+{
+	char name[BUFFER_SIZE], *ptr;
+
+	sprintf(name, "%s_%s", ses->session_host, ses->session_port);
+
+	ptr = name;
+
+	while (*ptr)
+	{
+		if (*ptr == ':')
+		{
+			*ptr++ = '.';
+		}
+		else if (isalnum((int) *ptr) || *ptr == '-' || *ptr == '.' || *ptr == '_')
+		{
+			ptr++;
+		}
+		else
+		{
+			return 0;
+		}
+	}
+
+	sprintf(result, "%s/%s/ssl/%s.crt", gtd->home, TINTIN_DIR, name);
+
+	return 1;
+}
+
+
+static void load_cert(struct session *ses, gnutls_x509_crt_t *cert)
+{
+	char cert_file[STRING_SIZE];
+	FILE *fp;
+	gnutls_datum_t bptr;
+	
+	if (!get_cert_file(ses, cert_file))
+	{
+		return;
+	}
+
+	if ((fp = fopen(cert_file, "r")) == NULL)
+	{
+		return;
+	}
+
+	bptr.size = fread(cert_file, 1, STRING_SIZE, fp);
+	bptr.data = (unsigned char *) cert_file;
+
+	fclose(fp);
+	
+	gnutls_x509_crt_init(cert);
+
+	if (gnutls_x509_crt_import(*cert, &bptr, GNUTLS_X509_FMT_PEM))
+	{
+		gnutls_x509_crt_deinit(*cert);
+
+		*cert = 0;
+	}
+}
+
+static void save_cert(struct session *ses, gnutls_x509_crt_t cert, int new)
+{
+	char filename[BUFFER_SIZE], buf[STRING_SIZE];
+	FILE *fp;
+	size_t len;
+	
+	len = STRING_SIZE;
+
+	if (gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_PEM, buf, &len))
+	{
+		return;
+	}
+
+	sprintf(filename, "%s/%s", gtd->home, TINTIN_DIR);
+
+	if (mkdir(filename, 0777) && errno != EEXIST)
+	{
+		tintin_printf(ses, "#SSL: FAILED TO CREATE TINTIN DIR %s (%s)", filename, strerror(errno));
+
+		return;
+	}
+
+	sprintf(filename, "%s/%s/ssl", gtd->home, TINTIN_DIR);
+
+	mkdir(filename, 0755);
+
+	if (mkdir(filename, 0755) && errno != EEXIST)
+	{
+		tintin_printf(ses, "#SSL: CANNOT CREATE CERTS DIR %s (%s)", filename, strerror(errno));
+
+		return;
+	}
+
+	if (!get_cert_file(ses, filename))
+	{
+		return;
+	}
+
+	if (new)
+	{
+		tintin_printf(ses, "#SSL: THIS IS THE FIRST TIME YOU CONNECT TO THIS SERVER.");
+	}
+
+	tintin_printf(ses, "#SSL: SAVING SERVER CERTIFICATE TO %s", filename);
+
+	if ((fp = fopen(filename, "w")) == NULL)
+	{
+		tintin_printf(ses, "#SSL: SAVE FAILED (%s)", strerror(errno));
+
+		return;
+	}
+
+	if (fwrite(buf, 1, len, fp) != len)
+	{
+		tintin_printf(ses, "#SSL: SAVE FAILED (%s)", strerror(errno));
+		fclose(fp);
+		unlink(filename);
+
+		return;
+	}
+
+	if (fclose(fp))
+	{
+		tintin_printf(ses, "#SSL: SAVE FAILED (%s)", strerror(errno));
+	}
+}
+
+
+static int diff_certs(gnutls_x509_crt_t c1, gnutls_x509_crt_t c2)
+{
+	char buf1[STRING_SIZE], buf2[STRING_SIZE];
+	size_t len1, len2;
+	
+	len1 = len2 = STRING_SIZE;
+
+	if (gnutls_x509_crt_export(c1, GNUTLS_X509_FMT_DER, buf1, &len1))
+	{
+		return 1;
+	}
+
+	if (gnutls_x509_crt_export(c2, GNUTLS_X509_FMT_DER, buf2, &len2))
+	{
+		return 1;
+	}
+
+	if (len1 != len2)
+	{
+		return 1;
+	}
+
+	return memcmp(buf1, buf2, len1);
+}
+
+
+static int ssl_check_cert(struct session *ses, gnutls_session_t ssl_ses)
+{
+	char filename[BUFFER_SIZE], buf2[BUFFER_SIZE], *bptr;
+	time_t t;
+	gnutls_x509_crt_t cert, oldcert;
+	const gnutls_datum_t *cert_list;
+	unsigned int cert_list_size;
+	char *err = 0;
+
+	oldcert = 0;
+
+	load_cert(ses, &oldcert);
+
+	if (gnutls_certificate_type_get(ssl_ses) != GNUTLS_CRT_X509)
+	{
+		err = "#SSL: SERVER DOES NOT USE x509 -> NO KEY RETENTION.";
+		goto nocert;
+	}
+	
+	if ((cert_list = gnutls_certificate_get_peers(ssl_ses, &cert_list_size)) == NULL)
+	{
+		err = "#SSL: SERVER HAS NO x509 CERTIFICATE -> NO KEY RETENTION.";
+		goto nocert;
+	}
+	
+	gnutls_x509_crt_init(&cert);
+
+	if (gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER) < 0)
+	{
+		err = "#SSL: SERVER'S CERTIFICATE IS INVALID.";
+		goto badcert;
+	}
+	
+	t = time(0);
+
+	if (gnutls_x509_crt_get_activation_time(cert) > t)
+	{
+		sprintf(buf2, "%s", ctime(&t));
+
+		if ((bptr = strchr(buf2, '\n')))
+		{
+			*bptr = 0;
+		}
+		sprintf(filename, "CERTIFICATE ACTIVATION TIME IS IN THE FUTURE (%s)", buf2);
+
+		err = filename;
+	}
+	
+	if (gnutls_x509_crt_get_expiration_time(cert)<t)
+	{
+		sprintf(buf2, "%s", ctime(&t));
+
+		if ((bptr = strchr(buf2, '\n')))
+		{
+			*bptr = 0;
+		}
+		sprintf(filename, "CERTIFICATE HAS EXPIRED (%s)", buf2);
+		err = filename;
+	}
+	
+	if (!oldcert)
+	{
+		save_cert(ses, cert, 1);
+	}
+	else if (diff_certs(cert, oldcert))
+	{
+		t -= gnutls_x509_crt_get_expiration_time(oldcert);
+
+		if (err || t < -7*24*3600)
+		{
+			if (err)
+			{
+				sprintf(buf2, "CERTIFICATE MISMATCH, AND NEW %s", err);
+			}
+			else
+			{
+				sprintf(buf2, "SERVER CERTIFICATE IS DIFFERENT FROM THE SAVED ONE.");
+			}
+			err = buf2;
+		}
+		else
+		{
+			if (t > 0)
+			{
+				tintin_printf(ses, "#SSL: SERVER CERTIFICATE HAS CHANGED, BUT THE OLD ONE WAS EXPIRED.");
+			}
+			else
+			{
+				tintin_printf(ses, "#SSL: SERVER CERTIFICATE HAS CHANGED, BUT THE OLD ONE WAS ABOUT TO EXPIRE.");
+			}
+
+			/* Replace the old cert */
+
+			save_cert(ses, cert, 0);
+			gnutls_x509_crt_deinit(oldcert);
+			oldcert = 0;
+		}
+	}
+	else
+	{
+		/* All is well */
+		gnutls_x509_crt_deinit(oldcert);
+		oldcert = 0;
+	}
+
+badcert:
+	gnutls_x509_crt_deinit(cert);
+	
+nocert:
+	if (oldcert)
+	{
+		gnutls_x509_crt_deinit(oldcert);
+	}
+
+	if (err)
+	{
+		if (oldcert)
+		{
+			tintin_printf(ses, "#SSL ERROR: %s", err);
+
+			tintin_printf(ses, "#SSL ALERT: THE SERVER'S SETTINGS WERE CHANGED IN AN UNEXPECTED WAY.");
+			tintin_printf(ses, "#SSL ALERT: YOU MAY BE VULNERABLE TO MAN-IN-THE-MIDDLE ATTACKS.");
+			tintin_printf(ses, "#SSL ALERT: TO CONTINUE, PLEASE DELETE THE FILE:");
+			tintin_printf(ses, "#SSL ALERT: %s", filename);
+			tintin_printf(ses, "#SSL ERROR: ABORTING CONNECTION.");
+			return 0;
+		}
+		else
+		{
+			tintin_printf(ses, "#SSL ALERT: %s", err);
+			tintin_printf(ses, "#SSL ALERT: YOU MAY BE VULNERABLE TO MAN-IN-THE-MIDDLE ATTACKS.");
+			return 2;
+		}
+	}
+	else
+	{
+		return 1;
+	}
+}
+
+#else
+
+DO_COMMAND(do_ssl)
+{
+	tintin_printf2(ses, "The GnuTLS library wasn't found. Install GnuTLS, run ./configure, and recompile for SSL support.");
+
+	return ses;
+}
+
+#endif

+ 1947 - 0
substitute.c

@@ -0,0 +1,1947 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2004                       *
+******************************************************************************/
+
+#include "tintin.h"
+
+// color subs
+
+char *c256to16_fg[256] =
+{
+	"\e[22;30m", "\e[22;31m", "\e[22;32m", "\e[22;33m", "\e[22;34m", "\e[22;35m", "\e[22;36m", "\e[22;37m",
+	 "\e[1;30m",  "\e[1;31m",  "\e[1;32m",  "\e[1;33m",  "\e[1;34m",  "\e[1;35m",  "\e[1;36m",  "\e[1;37m",
+
+	"\e[22;30m", "\e[22;34m", "\e[22;34m", "\e[22;34m",  "\e[1;34m",  "\e[1;34m",
+	"\e[22;32m", "\e[22;36m", "\e[22;36m", "\e[22;34m",  "\e[1;34m",  "\e[1;34m",
+	"\e[22;32m", "\e[22;36m", "\e[22;36m", "\e[22;36m",  "\e[1;34m",  "\e[1;34m",
+	"\e[22;32m", "\e[22;32m", "\e[22;36m", "\e[22;36m", "\e[22;36m",  "\e[1;36m",
+	 "\e[1;32m",  "\e[1;32m",  "\e[1;32m", "\e[22;36m",  "\e[1;36m",  "\e[1;36m",
+	 "\e[1;32m",  "\e[1;32m",  "\e[1;32m",  "\e[1;36m",  "\e[1;36m",  "\e[1;36m",
+
+	"\e[22;31m", "\e[22;35m", "\e[22;35m", "\e[22;34m",  "\e[1;34m",  "\e[1;34m",
+	"\e[22;33m",  "\e[1;30m", "\e[22;34m", "\e[22;34m",  "\e[1;34m",  "\e[1;34m",
+	"\e[22;33m", "\e[22;32m", "\e[22;36m", "\e[22;36m",  "\e[1;34m",  "\e[1;34m",
+	"\e[22;32m", "\e[22;32m", "\e[22;36m", "\e[22;36m", "\e[22;36m",  "\e[1;36m",
+	 "\e[1;32m",  "\e[1;32m",  "\e[1;32m", "\e[22;36m",  "\e[1;36m",  "\e[1;36m",
+	 "\e[1;32m",  "\e[1;32m",  "\e[1;32m",  "\e[1;36m",  "\e[1;36m",  "\e[1;36m",
+
+	"\e[22;31m", "\e[22;35m", "\e[22;35m", "\e[22;35m",  "\e[1;34m",  "\e[1;34m",
+	"\e[22;33m", "\e[22;31m", "\e[22;35m", "\e[22;35m",  "\e[1;34m",  "\e[1;34m",
+	"\e[22;33m", "\e[22;33m", "\e[22;37m", "\e[22;34m",  "\e[1;34m",  "\e[1;34m",
+	"\e[22;33m", "\e[22;33m", "\e[22;32m", "\e[22;36m", "\e[22;36m",  "\e[1;34m",
+	 "\e[1;32m",  "\e[1;32m",  "\e[1;32m", "\e[22;36m",  "\e[1;36m",  "\e[1;36m",
+	 "\e[1;32m",  "\e[1;32m",  "\e[1;32m",  "\e[1;32m",  "\e[1;36m",  "\e[1;36m",
+
+	"\e[22;31m", "\e[22;31m", "\e[22;35m", "\e[22;35m", "\e[22;35m",  "\e[1;35m",
+	"\e[22;31m", "\e[22;31m", "\e[22;35m", "\e[22;35m", "\e[22;35m",  "\e[1;35m",
+	"\e[22;33m", "\e[22;33m", "\e[22;31m", "\e[22;35m", "\e[22;35m",  "\e[1;34m",
+	"\e[22;33m", "\e[22;33m", "\e[22;33m", "\e[22;37m",  "\e[1;34m",  "\e[1;34m",
+	"\e[22;33m", "\e[22;33m", "\e[22;33m",  "\e[1;32m",  "\e[1;36m",  "\e[1;36m",
+	 "\e[1;33m",  "\e[1;33m",  "\e[1;32m",  "\e[1;32m",  "\e[1;36m",  "\e[1;36m",
+
+	 "\e[1;31m",  "\e[1;31m",  "\e[1;31m", "\e[22;35m",  "\e[1;35m",  "\e[1;35m",
+	 "\e[1;31m",  "\e[1;31m",  "\e[1;31m", "\e[22;35m",  "\e[1;35m",  "\e[1;35m",
+	 "\e[1;31m",  "\e[1;31m",  "\e[1;31m", "\e[22;35m",  "\e[1;35m",  "\e[1;35m",
+	"\e[22;33m", "\e[22;33m", "\e[22;33m",  "\e[1;31m",  "\e[1;35m",  "\e[1;35m",
+	 "\e[1;33m",  "\e[1;33m",  "\e[1;33m",  "\e[1;33m",  "\e[1;37m",  "\e[1;37m",
+	 "\e[1;33m",  "\e[1;33m",  "\e[1;33m",  "\e[1;33m",  "\e[1;37m",  "\e[1;37m",
+
+	 "\e[1;31m",  "\e[1;31m",  "\e[1;31m",  "\e[1;35m",  "\e[1;35m",  "\e[1;35m",
+	 "\e[1;31m",  "\e[1;31m",  "\e[1;31m",  "\e[1;35m",  "\e[1;35m",  "\e[1;35m",
+	 "\e[1;31m",  "\e[1;31m",  "\e[1;31m",  "\e[1;31m",  "\e[1;35m",  "\e[1;35m",
+	 "\e[1;33m",  "\e[1;33m",  "\e[1;31m",  "\e[1;31m",  "\e[1;35m",  "\e[1;35m",
+	 "\e[1;33m",  "\e[1;33m",  "\e[1;33m",  "\e[1;33m",  "\e[1;37m",  "\e[1;37m",
+	 "\e[1;33m",  "\e[1;33m",  "\e[1;33m",  "\e[1;33m",  "\e[1;37m",  "\e[1;37m",
+
+	 "\e[1;30m",  "\e[1;30m",  "\e[1;30m",  "\e[1;30m",  "\e[1;30m",  "\e[1;30m",
+	 "\e[1;30m",  "\e[1;30m",  "\e[1;30m",  "\e[1;30m",  "\e[1;30m",  "\e[1;30m",
+	"\e[22;37m", "\e[22;37m", "\e[22;37m", "\e[22;37m", "\e[22;37m", "\e[22;37m",
+	 "\e[1;37m",  "\e[1;37m",  "\e[1;37m",  "\e[1;37m",  "\e[1;37m",  "\e[1;37m"
+};
+
+char *c256to16_bg[256] =
+{
+	"\e[22;40m", "\e[22;41m", "\e[22;42m", "\e[22;43m", "\e[22;44m", "\e[22;45m", "\e[22;46m", "\e[22;47m",
+	 "\e[1;40m",  "\e[1;41m",  "\e[1;42m",  "\e[1;43m",  "\e[1;44m",  "\e[1;45m",  "\e[1;46m",  "\e[1;47m",
+
+	"\e[22;40m", "\e[22;44m", "\e[22;44m", "\e[22;44m",  "\e[1;44m",  "\e[1;44m",
+	"\e[22;42m", "\e[22;46m", "\e[22;46m", "\e[22;44m",  "\e[1;44m",  "\e[1;44m",
+	"\e[22;42m", "\e[22;46m", "\e[22;46m", "\e[22;46m",  "\e[1;44m",  "\e[1;44m",
+	"\e[22;42m", "\e[22;42m", "\e[22;46m", "\e[22;46m", "\e[22;46m",  "\e[1;46m",
+	 "\e[1;42m",  "\e[1;42m",  "\e[1;42m", "\e[22;46m",  "\e[1;46m",  "\e[1;46m",
+	 "\e[1;42m",  "\e[1;42m",  "\e[1;42m",  "\e[1;46m",  "\e[1;46m",  "\e[1;46m",
+
+	"\e[22;41m", "\e[22;45m", "\e[22;45m", "\e[22;44m",  "\e[1;44m",  "\e[1;44m",
+	"\e[22;43m",  "\e[1;40m", "\e[22;44m", "\e[22;44m",  "\e[1;44m",  "\e[1;44m",
+	"\e[22;43m", "\e[22;42m", "\e[22;46m", "\e[22;46m",  "\e[1;44m",  "\e[1;44m",
+	"\e[22;42m", "\e[22;42m", "\e[22;46m", "\e[22;46m", "\e[22;46m",  "\e[1;46m",
+	 "\e[1;42m",  "\e[1;42m",  "\e[1;42m", "\e[22;46m",  "\e[1;46m",  "\e[1;46m",
+	 "\e[1;42m",  "\e[1;42m",  "\e[1;42m",  "\e[1;46m",  "\e[1;46m",  "\e[1;46m",
+
+	"\e[22;41m", "\e[22;45m", "\e[22;45m", "\e[22;45m",  "\e[1;44m",  "\e[1;44m",
+	"\e[22;43m", "\e[22;41m", "\e[22;45m", "\e[22;45m",  "\e[1;44m",  "\e[1;44m",
+	"\e[22;43m", "\e[22;43m", "\e[22;47m", "\e[22;44m",  "\e[1;44m",  "\e[1;44m",
+	"\e[22;43m", "\e[22;43m", "\e[22;42m", "\e[22;46m", "\e[22;46m",  "\e[1;44m",
+	 "\e[1;42m",  "\e[1;42m",  "\e[1;42m", "\e[22;46m",  "\e[1;46m",  "\e[1;46m",
+	 "\e[1;42m",  "\e[1;42m",  "\e[1;42m",  "\e[1;42m",  "\e[1;46m",  "\e[1;46m",
+
+	"\e[22;41m", "\e[22;41m", "\e[22;45m", "\e[22;45m", "\e[22;45m",  "\e[1;45m",
+	"\e[22;41m", "\e[22;41m", "\e[22;45m", "\e[22;45m", "\e[22;45m",  "\e[1;45m",
+	"\e[22;43m", "\e[22;43m", "\e[22;41m", "\e[22;45m", "\e[22;45m",  "\e[1;44m",
+	"\e[22;43m", "\e[22;43m", "\e[22;43m", "\e[22;47m",  "\e[1;44m",  "\e[1;44m",
+	"\e[22;43m", "\e[22;43m", "\e[22;43m",  "\e[1;42m",  "\e[1;46m",  "\e[1;46m",
+	 "\e[1;43m",  "\e[1;43m",  "\e[1;42m",  "\e[1;42m",  "\e[1;46m",  "\e[1;46m",
+
+	 "\e[1;41m",  "\e[1;41m",  "\e[1;41m", "\e[22;45m",  "\e[1;45m",  "\e[1;45m",
+	 "\e[1;41m",  "\e[1;41m",  "\e[1;41m", "\e[22;45m",  "\e[1;45m",  "\e[1;45m",
+	 "\e[1;41m",  "\e[1;41m",  "\e[1;41m", "\e[22;45m",  "\e[1;45m",  "\e[1;45m",
+	"\e[22;43m", "\e[22;43m", "\e[22;43m",  "\e[1;41m",  "\e[1;45m",  "\e[1;45m",
+	 "\e[1;43m",  "\e[1;43m",  "\e[1;43m",  "\e[1;43m",  "\e[1;47m",  "\e[1;47m",
+	 "\e[1;43m",  "\e[1;43m",  "\e[1;43m",  "\e[1;43m",  "\e[1;47m",  "\e[1;47m",
+
+	 "\e[1;41m",  "\e[1;41m",  "\e[1;41m",  "\e[1;45m",  "\e[1;45m",  "\e[1;45m",
+	 "\e[1;41m",  "\e[1;41m",  "\e[1;41m",  "\e[1;45m",  "\e[1;45m",  "\e[1;45m",
+	 "\e[1;41m",  "\e[1;41m",  "\e[1;41m",  "\e[1;41m",  "\e[1;45m",  "\e[1;45m",
+	 "\e[1;43m",  "\e[1;43m",  "\e[1;41m",  "\e[1;41m",  "\e[1;45m",  "\e[1;45m",
+	 "\e[1;43m",  "\e[1;43m",  "\e[1;43m",  "\e[1;43m",  "\e[1;47m",  "\e[1;47m",
+	 "\e[1;43m",  "\e[1;43m",  "\e[1;43m",  "\e[1;43m",  "\e[1;47m",  "\e[1;47m",
+
+	 "\e[1;40m",  "\e[1;40m",  "\e[1;40m",  "\e[1;40m",  "\e[1;40m",  "\e[1;40m",
+	 "\e[1;40m",  "\e[1;40m",  "\e[1;40m",  "\e[1;40m",  "\e[1;40m",  "\e[1;40m",
+	"\e[22;47m", "\e[22;47m", "\e[22;47m", "\e[22;47m", "\e[22;47m", "\e[22;47m",
+	 "\e[1;47m",  "\e[1;47m",  "\e[1;47m",  "\e[1;47m",  "\e[1;47m",  "\e[1;47m"
+};
+
+
+// input A to F
+
+int c256_val(char chr)
+{
+	static unsigned char c256_val[256] =
+	{
+		  0,   0,   0,   0,    0,   0,   0,   0,   0,   0,    0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,    0,   0,   0,   0,   0,   0,    0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,    0,   0,   0,   0,   0,   0,    0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,    0,   0,   0,   0,   0,   0,    0,   0,   0,   0,   0,   0,
+		  0,   0,   1,   2,    3,   4,   5,   0,   0,   0,    0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,    0,   0,   0,   0,   0,   0,    0,   0,   0,   0,   0,   0,
+		  0,   0,   1,   2,    3,   4,   5,   0,   0,   0,    0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,    0,   0,   0,   0,   0,   0,    0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,    0,   0,   0,   0,   0,   0,    0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,    0,   0,   0,   0,   0,   0,    0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,    0,   0,   0,   0,   0,   0,    0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,    0,   0,   0,   0,   0,   0,    0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,    0,   0,   0,   0,   0,   0,    0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,    0,   0,   0,   0,   0,   0,    0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,    0,   0,   0,   0,   0,   0,    0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,    0,   0,   0,   0,   0,   0,    0,   0,   0,   0,   0,   0
+	};
+
+	return (int) c256_val[(unsigned char) chr];
+}
+
+// input 00 to FF
+
+int c4096_val(char chr1, char chr2)
+{
+	static unsigned char c4096_val[256] =
+	{
+		  0,   0,   0,   0,    0,   0,   0,   0,   0,   0,    0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,    0,   0,   0,   0,   0,   0,    0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,    0,   0,   0,   0,   0,   0,    0,   0,   0,   0,   0,   0,
+		  0,   1,   2,   3,    4,   5,   6,   7,   8,   9,    0,   0,   0,   0,   0,   0,
+		  0,  10,  11,  12,   13,  14,  15,   0,   0,   0,    0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,    0,   0,   0,   0,   0,   0,    0,   0,   0,   0,   0,   0,
+		  0,  10,  11,  12,   13,  14,  15,   0,   0,   0,    0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,    0,   0,   0,   0,   0,   0,    0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,    0,   0,   0,   0,   0,   0,    0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,    0,   0,   0,   0,   0,   0,    0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,    0,   0,   0,   0,   0,   0,    0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,    0,   0,   0,   0,   0,   0,    0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,    0,   0,   0,   0,   0,   0,    0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,    0,   0,   0,   0,   0,   0,    0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,    0,   0,   0,   0,   0,   0,    0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,    0,   0,   0,   0,   0,   0,    0,   0,   0,   0,   0,   0
+	};
+
+	return (int) c4096_val[(unsigned char) chr1] * 16 + c4096_val[(unsigned char) chr2];
+}
+
+// input 00 to FF
+
+int c4096_to_256_val(char chr1, char chr2)
+{
+	static unsigned char c4096_to_256[256] =
+	{
+		  0,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
+		  1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
+		  1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
+		  1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
+		  1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
+		  1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
+		  1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
+		  2,   2,   2,   2,   2,   2,   2,   2,   2,   2,   2,   2,   2,   2,   2,   2,
+		  2,   2,   2,   2,   2,   2,   2,   2,   2,   2,   2,   2,   2,   2,   2,   2,
+		  3,   3,   3,   3,   3,   3,   3,   3,   3,   3,   3,   3,   3,   3,   3,   3,
+		  3,   3,   3,   3,   3,   3,   3,   3,   3,   3,   3,   3,   3,   3,   3,   3,
+		  3,   3,   3,   3,   3,   3,   3,   3,   3,   3,   3,   3,   3,   3,   3,   3,
+		  4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,
+		  4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,
+		  4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,   4,
+		  5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,
+	};
+
+	return (int) c4096_to_256[c4096_val(chr1, chr2)];
+}
+
+char int_to_hex[16] =
+{
+	'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+};
+
+char int_to_256[6] =
+{
+	'A', 'B', 'C', 'D', 'E', 'F'
+};
+
+void c4096_rnd(struct session *ses, char *str)
+{
+	sprintf(str, "%c%c%c", int_to_hex[generate_rand(ses) % 16], int_to_hex[generate_rand(ses) % 16], int_to_hex[generate_rand(ses) % 16]);
+}
+
+int is_c32(char chr)
+{
+	static unsigned char c32_lookup[256] =
+	{
+		  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  '?',
+		  0,  'A',  1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
+		  1,   1,   1,   1,   1,   1,   1,   1,   1,   1,  'Z',  0,   0,   0,   0,   0,
+		  0,  'a',  1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
+		  1,   1,   1,   1,   1,   1,   1,   1,   1,   1,  'z',  0,   0,   0,   0,   0,
+		  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+		  0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0
+	};
+
+	return (int) c32_lookup[(unsigned char) chr];
+}
+
+char *c32_fg_dark[26] =
+{
+	"<f06b>", "<f00b>", "<f0bb>", "", "<f000>", "", "<f0b0>", "", "", "<f0b6>", "", "<f6b0>", "<fb0b>",
+	"", "<fb60>", "<fb06>", "", "<fb00>", "<f888>", "<f860>", "", "<f60b>", "<fbbb>", "", "<fbb0>", ""
+};
+
+char *c32_fg_bold[26] =
+{
+	"<f08f>", "<f00f>", "<f0ff>", "", "<f666>", "", "<f0f0>", "", "", "<f0f8>", "", "<f8f0>", "<ff0f>",
+	"", "<ff80>", "<ff08>", "", "<ff00>", "<fddd>", "<fdb0>", "", "<f80f>", "<ffff>", "", "<fff0>", ""
+};
+
+int is_variable(struct session *ses, char *str)
+{
+	struct listroot *root;
+	char temp[BUFFER_SIZE], *ptt;
+	int i = 1;
+
+	while (str[i] == str[0])
+	{
+		i++;
+	}
+
+	if (str[i] == DEFAULT_OPEN)
+	{
+		return TRUE;
+	}
+
+	if (str[i] != '_' && isalpha((int) str[i]) == 0)
+	{
+		return FALSE;
+	}
+
+	ptt = temp;
+
+	while (isalnum((int) str[i]) || str[i] == '_')
+	{
+		*ptt++ = str[i];
+
+		i++;
+	}
+	*ptt = 0;
+
+	root = local_list(ses);
+
+	if (search_node_list(root, temp) == NULL)
+	{
+		root = ses->list[LIST_VARIABLE];
+
+		if (search_node_list(root, temp) == NULL)
+		{
+			return FALSE;
+		}
+	}
+
+	return TRUE;
+}
+
+int is_function(struct session *ses, char *str)
+{
+	char temp[BUFFER_SIZE], *ptt;
+	int i = 1;
+
+	while (str[i] == str[0])
+	{
+		i++;
+	}
+
+	if (str[i] != '_' && isalpha((int) str[i]) == 0)
+	{
+		return FALSE;
+	}
+
+	ptt = temp;
+
+	while (isalnum((int) str[i]) || str[i] == '_')
+	{
+		*ptt++ = str[i];
+
+		i++;
+	}
+	*ptt = 0;
+
+	if (str[i] != DEFAULT_OPEN)
+	{
+		return FALSE;
+	}
+
+	if (search_node_list(ses->list[LIST_FUNCTION], temp) == NULL)
+	{
+		if (find_session(temp) == NULL)
+		{
+			return FALSE;
+		}
+	}
+
+	return TRUE;
+}
+
+int is_color_code(char *pti)
+{
+	if (pti[0] == '<')
+	{
+		if (pti[1] == 0 || pti[2] == 0 || pti[3] == 0 || pti[4] == 0)
+		{
+			return 0;
+		}
+
+		if (pti[4] == '>')
+		{
+
+			if (isdigit(pti[1]) && isdigit(pti[2]) && isdigit(pti[3]))
+			{
+				return 5;
+			}
+			if (pti[1] >= 'a' && pti[1] <= 'f' && pti[2] >= 'a' && pti[2] <= 'f' && pti[3] >= 'a' && pti[3] <= 'f')
+			{
+				return 5;
+			}
+			if (pti[1] >= 'A' && pti[1] <= 'F' && pti[2] >= 'A' && pti[2] <= 'F' && pti[3] >= 'A' && pti[3] <= 'F')
+			{
+				return 5;
+			}
+
+			if (pti[1] == 'g' || pti[1] == 'G')
+			{
+				if (isdigit((int) pti[2]) && isdigit((int) pti[3]))
+				{
+					return 5;
+				}
+				return 0;
+			}
+
+			return 0;
+		}
+
+		if (pti[5] == 0)
+		{
+			return 0;
+		}
+
+		if (toupper((int) pti[1]) == 'F')
+		{
+			if (isxdigit(pti[2]) && isxdigit(pti[3]) && isxdigit(pti[4]) && pti[5] == '>')
+			{
+				return 6;
+			}
+			else if (pti[2] == '?' && pti[3] == '?' && pti[4] == '?' && pti[5] == '>')
+			{
+				return 6;
+			}
+			else if (isxdigit(pti[2]) && isxdigit(pti[3]) && isxdigit(pti[4]) && isxdigit(pti[5]) && isxdigit(pti[6]) && isxdigit(pti[7]) && pti[8] == '>')
+			{
+				return 9;
+			}
+			return 0;
+		}
+
+		if (toupper(pti[1]) == 'B')
+		{
+			if (isxdigit(pti[2]) && isxdigit(pti[3]) && isxdigit(pti[4]) && pti[5] == '>')
+			{
+				return 6;
+			}
+			if (toupper(pti[1]) == 'B' && pti[2] == '?' && pti[3] == '?' && pti[4] == '?' && pti[5] == '>')
+			{
+				return 6;
+			}
+			if (toupper(pti[1]) == 'B' && isxdigit(pti[2]) && isxdigit(pti[3]) && isxdigit(pti[4]) && isxdigit(pti[5]) && isxdigit(pti[6]) && isxdigit(pti[7]) && pti[8] == '>')
+			{
+				return 9;
+			}
+			return 0;
+		}
+	}
+	return 0;
+}
+
+
+char *fuzzy_char(struct session *ses, char val1, char val2, int mode)
+{
+	static char out[10][3];
+	static int cnt;
+	int tmp;
+
+	cnt = (cnt + 1) % 10;
+
+	switch (mode)
+	{
+		case 8:
+			tmp = c256_val(val2) - 1 + generate_rand(ses) % 3;
+
+			tmp = URANGE(0, tmp, 5);
+
+			if (isupper(val2))
+			{
+				sprintf(out[cnt], "%c", toupper(int_to_256[tmp]));
+			}
+			else
+			{
+				sprintf(out[cnt], "%c", tolower(int_to_256[tmp]));
+			}
+			break;
+
+		case 12:
+			tmp = c4096_val(0, val2);
+
+			tmp = tmp - 2 + generate_rand(ses) % 4;
+
+			tmp = URANGE(0, tmp, 15);
+
+			sprintf(out[cnt], "%c", int_to_hex[tmp]);
+			break;
+
+		case 24:
+			tmp = c4096_val(val1, val2);
+
+			tmp = (tmp - 12 + generate_rand(ses) % 24);
+
+			tmp = URANGE(0, tmp, 255);
+
+			sprintf(out[cnt], "%c%c", int_to_hex[tmp / 16], int_to_hex[tmp % 16]);
+			break;
+
+		default:
+			tintin_printf2(ses, "dim_char: invalid mode");
+			break;
+	}
+	return out[cnt];
+}
+
+char *fuzzy_color_code(struct session *ses, char *in)
+{
+	static char *pto, out[10][100];
+	static int cnt;
+	char *pti, buf[100];
+	int tmp;
+
+	if (*in == 0 || strlen(in) > 50)
+	{
+		return "";
+	}
+
+	strcpy(buf, in);
+
+	pti = buf;
+	cnt = (cnt + 1) % 10;
+	pto = out[cnt];
+
+	while (*pti)
+	{
+		if (pti[0] == '<')
+		{
+			if (pti[1] == 0 || pti[2] == 0 || pti[3] == 0 || pti[4] == 0)
+			{
+				return out[cnt];
+			}
+
+			if (pti[4] == '>')
+			{
+				if (isdigit(pti[1]) && isdigit(pti[2]) && isdigit(pti[3]))
+				{
+					pto += sprintf(pto, "<%c%c%c>", pti[1], pti[2], pti[3]);
+				}
+				else if (pti[1] >= 'a' && pti[1] <= 'f' && pti[2] >= 'a' && pti[2] <= 'f' && pti[3] >= 'a' && pti[3] <= 'f')
+				{
+					pto += sprintf(pto, "<%s%s%s>", fuzzy_char(ses, 0, pti[1], 8), fuzzy_char(ses, 0, pti[2], 8), fuzzy_char(ses, 0, pti[3], 8));
+				}
+				else if (pti[1] >= 'A' && pti[1] <= 'F' && pti[2] >= 'A' && pti[2] <= 'F' && pti[3] >= 'A' && pti[3] <= 'F')
+				{
+					pto += sprintf(pto, "<%s%s%s>", fuzzy_char(ses, 0, pti[1], 8), fuzzy_char(ses, 0, pti[2], 8), fuzzy_char(ses, 0, pti[3], 8));
+				}
+				else if ((pti[1] == 'g' || pti[1] == 'G') && isdigit((int) pti[2]) && isdigit((int) pti[3]))
+				{
+					tmp = (pti[2] - '0') * 10 + (pti[3] - '0') - 3 + generate_rand(ses) % 7;
+
+					pto += sprintf(pto, "<%c%02d>", pti[1], URANGE(0, tmp, 23));
+				}
+				else
+				{
+					return out[cnt];
+				}
+				pti += 5;
+			}
+			else if (toupper((int) pti[1]) == 'F')
+			{
+				if (isxdigit(pti[2]) && isxdigit(pti[3]) && isxdigit(pti[4]) && pti[5] == '>')
+				{
+					pto += sprintf(pto, "<F%s%s%s>", fuzzy_char(ses, 0, pti[2], 12), fuzzy_char(ses, 0, pti[3], 12), fuzzy_char(ses, 0, pti[4], 12));
+
+					pti += 6;
+				}
+				else if (pti[2] == '?' && pti[3] == '?' && pti[4] == '?' && pti[5] == '>')
+				{
+					c4096_rnd(ses, &pti[2]);
+
+					pto += sprintf(pto, "<F%c%c%c>", pti[2], pti[3], pti[4]);
+
+					pti += 6;
+				}
+				else if (isxdigit(pti[2]) && isxdigit(pti[3]) && isxdigit(pti[4]) && isxdigit(pti[5]) && isxdigit(pti[6]) && isxdigit(pti[7]) && pti[8] == '>')
+				{
+					pto += sprintf(pto, "<F%s%s%s>", fuzzy_char(ses, pti[3], pti[4], 24), fuzzy_char(ses, pti[4], pti[5], 24), fuzzy_char(ses, pti[7], pti[7], 24));
+
+					pti += 9;
+				}
+				else
+				{
+					return out[cnt];
+				}
+			}
+			else if (toupper(pti[1]) == 'B')
+			{
+				if (isxdigit(pti[2]) && isxdigit(pti[3]) && isxdigit(pti[4]) && pti[5] == '>')
+				{
+					pto += sprintf(pto, "<B%s%s%s>", fuzzy_char(ses, 0, pti[2], 12), fuzzy_char(ses, 0, pti[3], 12), fuzzy_char(ses, 0, pti[4], 12));
+
+					pti += 6;
+				}
+				else if (pti[2] == '?' && pti[3] == '?' && pti[4] == '?' && pti[5] == '>')
+				{
+					c4096_rnd(ses, &pti[2]);
+
+					pto += sprintf(pto, "<B%c%c%c>", pti[2], pti[3], pti[4]);
+
+					pti += 6;
+				}
+				else if (isxdigit(pti[2]) && isxdigit(pti[3]) && isxdigit(pti[4]) && isxdigit(pti[5]) && isxdigit(pti[6]) && isxdigit(pti[7]) && pti[8] == '>')
+				{
+					pto += sprintf(pto, "<B%s%s%s>", fuzzy_char(ses, pti[2], pti[3], 24), fuzzy_char(ses, pti[4], pti[5], 24), fuzzy_char(ses, pti[6], pti[7], 24));
+
+					pti += 9;
+				}
+				else
+				{
+					return out[cnt];
+				}
+			}
+			else
+			{
+				return out[cnt];
+			}
+		}
+		else
+		{
+			return out[cnt];
+		}
+	}
+	return out[cnt];
+}
+
+char *dim_char(struct session *ses, char val1, char val2, int mod, int mode)
+{
+	static char out[10][3];
+	static int cnt;
+	int tmp;
+
+	cnt = (cnt + 1) % 10;
+
+	switch (mode)
+	{
+		case 8:
+			tmp = c256_val(val2) - mod;
+
+			tmp = URANGE(0, tmp, 5);
+
+			if (isupper(val2))
+			{
+				sprintf(out[cnt], "%c", toupper(int_to_256[tmp]));
+			}
+			else
+			{
+				sprintf(out[cnt], "%c", tolower(int_to_256[tmp]));
+			}
+			break;
+
+		case 12:
+			tmp = c4096_val(0, val2);
+
+			tmp = tmp - mod - generate_rand(ses) % 3;
+
+			tmp = URANGE(0, tmp, 15);
+
+			sprintf(out[cnt], "%c", int_to_hex[tmp]);
+			break;
+
+		case 24:
+			tmp = c4096_val(val1, val2);
+
+			tmp = tmp - mod - generate_rand(ses) % 2;
+
+			tmp = URANGE(0, tmp, 255);
+
+			sprintf(out[cnt], "%c%c", int_to_hex[tmp / 16], int_to_hex[tmp % 16]);
+			break;
+
+		default:
+			tintin_printf2(ses, "dim_char: invalid mode");
+			break;
+	}
+	return out[cnt];
+}
+
+char *dim_color_code(struct session *ses, char *in, int mod)
+{
+	static char *pto, out[10][60], buf[60];
+	char *pti;
+	static int cnt;
+	int tmp;
+
+	if (*in == 0 || strlen(in) > 40)
+	{
+		return "";
+	}
+
+	strcpy(buf, in);
+
+	pti = buf;
+	cnt = (cnt + 1) % 10;
+	pto = out[cnt];
+
+	if (mod < 0)
+	{
+		strcpy(pto, buf);
+
+		return out[cnt];
+	}
+
+	*pto = 0;
+
+	while (*pti)
+	{
+		if (pti[0] == '<')
+		{
+			if (pti[1] == 0 || pti[2] == 0 || pti[3] == 0 || pti[4] == 0)
+			{
+				return out[cnt];
+			}
+
+			if (pti[4] == '>')
+			{
+				if (isdigit(pti[1]) && isdigit(pti[2]) && isdigit(pti[3]))
+				{
+					pto += sprintf(pto, "<%c%c%c>", pti[1], pti[2], pti[3]);
+				}
+				else if (pti[1] >= 'a' && pti[1] <= 'f' && pti[2] >= 'a' && pti[2] <= 'f' && pti[3] >= 'a' && pti[3] <= 'f')
+				{
+					pto += sprintf(pto, "<%s%s%s>", dim_char(ses, 0, pti[1], mod, 8), dim_char(ses, 0, pti[2], mod, 8), dim_char(ses, 0, pti[3], mod, 8));
+				}
+				else if (pti[1] >= 'A' && pti[1] <= 'F' && pti[2] >= 'A' && pti[2] <= 'F' && pti[3] >= 'A' && pti[3] <= 'F')
+				{
+					pto += sprintf(pto, "<%s%s%s>", dim_char(ses, 0, pti[1], mod, 8), dim_char(ses, 0, pti[2], mod, 8), dim_char(ses, 0, pti[3], mod, 8));
+				}
+				else if ((pti[1] == 'g' || pti[1] == 'G') && isdigit((int) pti[2]) && isdigit((int) pti[3]))
+				{
+					tmp = (pti[2] - '0') * 10 + (pti[3] - '0') - mod;
+
+					pto += sprintf(pto, "<%c%02d>", pti[1], URANGE(0, tmp, 23));
+				}
+				else
+				{
+					return out[cnt];
+				}
+				pti += 5;
+			}
+			else if (toupper((int) pti[1]) == 'F')
+			{
+				if (pti[2] == '?' && pti[3] == '?' && pti[4] == '?' && pti[5] == '>')
+				{
+					c4096_rnd(ses, &pti[2]);
+				}
+
+				if (isxdigit(pti[2]) && isxdigit(pti[3]) && isxdigit(pti[4]) && pti[5] == '>')
+				{
+					pto += sprintf(pto, "<F%s%s%s>", dim_char(ses, 0, pti[2], mod, 12), dim_char(ses, 0, pti[3], mod, 12), dim_char(ses, 0, pti[4], mod, 12));
+
+					pti += 6;
+				}
+				else if (isxdigit(pti[2]) && isxdigit(pti[3]) && isxdigit(pti[4]) && isxdigit(pti[5]) && isxdigit(pti[6]) && isxdigit(pti[7]) && pti[8] == '>')
+				{
+					pto += sprintf(pto, "<F%s%s%s>", dim_char(ses, pti[2], pti[3], mod, 24), dim_char(ses, pti[4], pti[5], mod, 24), dim_char(ses, mod, pti[6], pti[7], 24));
+
+					pti += 9;
+				}
+				else
+				{
+					return out[cnt];
+				}
+			}
+			else if (toupper(pti[1]) == 'B')
+			{
+				if (pti[2] == '?' && pti[3] == '?' && pti[4] == '?' && pti[5] == '>')
+				{
+					c4096_rnd(ses, &pti[2]);
+				}
+
+				if (isxdigit(pti[2]) && isxdigit(pti[3]) && isxdigit(pti[4]) && pti[5] == '>')
+				{
+					pto += sprintf(pto, "<B%s%s%s>", dim_char(ses, 0, pti[2], mod, 12), dim_char(ses, 0, pti[3], mod, 12), dim_char(ses, 0, pti[4], mod, 12));
+
+					pti += 6;
+				}
+				else if (toupper(pti[1]) == 'B' && isxdigit(pti[2]) && isxdigit(pti[3]) && isxdigit(pti[4]) && isxdigit(pti[5]) && isxdigit(pti[6]) && isxdigit(pti[7]) && pti[8] == '>')
+				{
+					pto += sprintf(pto, "<B%s%s%s>", dim_char(ses, pti[2], pti[3], mod, 24), dim_char(ses, pti[4], pti[5], mod, 24), dim_char(ses, pti[6], pti[7], mod, 24));
+
+					pti += 9;
+				}
+				else
+				{
+					return out[cnt];
+				}
+			}
+			else
+			{
+				return out[cnt];
+			}
+		}
+	}
+	return out[cnt];
+
+}
+
+char lit_char(struct session *ses, char max, char val, int mod, int mode)
+{
+	int tmp;
+
+	switch (mode)
+	{
+		case 8:
+			tmp = val + mod;
+
+			return UMIN(max, tmp);
+
+		case 12:
+			tmp = c4096_val(0, val);
+
+			tmp = URANGE(0, tmp + mod, 15);
+
+			return int_to_hex[tmp];
+	}
+	return val;
+}
+
+char *lit_color_code(struct session *ses, char *pti, int mod)
+{
+	static char fuzzy[10][20];
+	static int cnt;
+	int tmp;
+
+	cnt = (cnt + 1) % 10;
+
+	if (pti[0] == '<')
+	{
+		if (pti[1] == 0 || pti[2] == 0 || pti[3] == 0 || pti[4] == 0)
+		{
+			return "";
+		}
+
+		if (pti[4] == '>')
+		{
+			if (isdigit(pti[1]) && isdigit(pti[2]) && isdigit(pti[3]))
+			{
+				sprintf(fuzzy[cnt], "<%c%c%c>%.9s", pti[1], pti[2], pti[3], &pti[5]);
+
+				return fuzzy[cnt];
+			}
+
+			if (pti[1] >= 'a' && pti[1] <= 'f' && pti[2] >= 'a' && pti[2] <= 'f' && pti[3] >= 'a' && pti[3] <= 'f')
+			{
+				sprintf(fuzzy[cnt], "<%c%c%c>%.9s", lit_char(ses, 'f', pti[1], mod, 8), lit_char(ses, 'f', pti[2], mod, 8), lit_char(ses, 'f', pti[3], mod, 8), &pti[5]);
+
+				return fuzzy[cnt];
+			}
+
+			if (pti[1] >= 'A' && pti[1] <= 'F' && pti[2] >= 'A' && pti[2] <= 'F' && pti[3] >= 'A' && pti[3] <= 'F')
+			{
+				sprintf(fuzzy[cnt], "<%c%c%c>%.9s", lit_char(ses, 'F', pti[1], mod, 8), lit_char(ses, 'F', pti[2], mod, 8), lit_char(ses, 'F', pti[3], mod, 8), &pti[5]);
+
+				return fuzzy[cnt];
+			}
+		}
+
+		{
+			if (pti[1] == 'g' || pti[1] == 'G')
+			{
+				if (isdigit((int) pti[2]) && isdigit((int) pti[3]))
+				{
+					tmp = (pti[2] - '0') * 10 + (pti[3] - '0');
+
+					tmp -= mod;
+
+					sprintf(fuzzy[cnt], "<%c%02d>%.9s", pti[1], URANGE(0, tmp, 23), &pti[5]);
+
+					return fuzzy[cnt];
+				}
+				return "";
+			}
+		}
+
+		if (pti[5] == 0)
+		{
+			return "";
+		}
+
+		if (toupper((int) pti[1]) == 'F')
+		{
+			if (isxdigit(pti[2]) && isxdigit(pti[3]) && isxdigit(pti[4]) && pti[5] == '>')
+			{
+				sprintf(fuzzy[cnt], "<F%c%c%c>%.9s", lit_char(ses, 'F', pti[2], mod, 12), lit_char(ses, 'F', pti[3], mod, 12), lit_char(ses, 'F', pti[4], mod, 12), &pti[6]);
+
+				return fuzzy[cnt];
+			}
+			else if (pti[2] == '?' && pti[3] == '?' && pti[4] == '?' && pti[5] == '>')
+			{
+				sprintf(fuzzy[cnt], "<F??%c>%.9s", '?', &pti[6]);
+
+				c4096_rnd(ses, &fuzzy[cnt][2]);
+
+				return dim_color_code(ses, fuzzy[cnt], mod);
+			}
+			else if (isxdigit(pti[2]) && isxdigit(pti[3]) && isxdigit(pti[4]) && isxdigit(pti[5]) && isxdigit(pti[6]) && isxdigit(pti[7]) && pti[8] == '>')
+			{
+				sprintf(fuzzy[cnt], "<F%c%c%c>%.9s", lit_char(ses, 'F', pti[2], mod, 12), lit_char(ses, 'F', pti[4], mod, 12), lit_char(ses, mod, pti[6], 'F', 12), &pti[9]);
+
+				return fuzzy[cnt];
+			}
+			return "";
+		}
+
+		if (toupper(pti[1]) == 'B')
+		{
+			if (isxdigit(pti[2]) && isxdigit(pti[3]) && isxdigit(pti[4]) && pti[5] == '>')
+			{
+				sprintf(fuzzy[cnt], "<B%c%c%c>%.9s", lit_char(ses, 'F', pti[2], mod, 12), lit_char(ses, 'F', pti[3], mod, 12), lit_char(ses, 'F', pti[4], mod, 12), &pti[6]);
+
+				return fuzzy[cnt];
+			}
+			if (toupper(pti[1]) == 'B' && pti[2] == '?' && pti[3] == '?' && pti[4] == '?' && pti[5] == '>')
+			{
+				sprintf(fuzzy[cnt], "<B??%c>%.9s", '?', &pti[6]);
+
+				c4096_rnd(ses, &fuzzy[cnt][2]);
+
+				return dim_color_code(ses, fuzzy[cnt], mod);
+			}
+			if (toupper(pti[1]) == 'B' && isxdigit(pti[2]) && isxdigit(pti[3]) && isxdigit(pti[4]) && isxdigit(pti[5]) && isxdigit(pti[6]) && isxdigit(pti[7]) && pti[8] == '>')
+			{
+				sprintf(fuzzy[cnt], "<B%c%c%c>%.9s", lit_char(ses, 'F', pti[2], mod, 12), lit_char(ses, 'F', pti[4], mod, 12), lit_char(ses, mod, pti[6], 'F', 12), &pti[9]);
+
+				return fuzzy[cnt];
+			}
+			return "";
+		}
+	}
+	return "";
+}
+
+int substitute(struct session *ses, char *string, char *result, int flags)
+{
+	struct listnode *node;
+	struct listroot *root;
+	struct session *sesptr;
+	char temp[BUFFER_SIZE], buf[BUFFER_SIZE], buffer[BUFFER_SIZE], *pti, *pto, *ptt, *str;
+	char *pte, old[10] = { 0 };
+	int i, skip, cnt, escape = FALSE, flags_neol = flags;
+
+	push_call("substitute(%p,%p,%p,%d)",ses,string,result,flags);
+
+	pti = string;
+	pto = (string == result) ? buffer : result;
+
+	DEL_BIT(flags_neol, SUB_EOL|SUB_LNF);
+
+	while (TRUE)
+	{
+		if (HAS_BIT(ses->charset, CHARSET_FLAG_EUC) && is_euc_head(ses, pti))
+		{
+			*pto++ = *pti++;
+			*pto++ = *pti++;
+			continue;
+		}
+
+		switch (*pti)
+		{
+			case '\0':
+				if (HAS_BIT(flags, SUB_EOL))
+				{
+					if (HAS_BIT(ses->flags, SES_FLAG_RUN))
+					{
+						*pto++ = '\r';
+					}
+					else
+					{
+						*pto++ = '\r';
+						*pto++ = '\n';
+					}
+				}
+
+				if (HAS_BIT(flags, SUB_LNF))
+				{
+					*pto++ = '\n';
+				}
+
+				*pto = 0;
+
+				if (string == result)
+				{
+					strcpy(result, buffer);
+
+					pop_call();
+					return pto - buffer;
+				}
+				else
+				{
+					pop_call();
+					return pto - result;
+				}
+				break;
+
+			case '@':
+				if (HAS_BIT(flags, SUB_FUN) && !HAS_BIT(ses->list[LIST_FUNCTION]->flags, LIST_FLAG_IGNORE))
+				{
+					i = 1;
+					escape = FALSE;
+					sesptr = NULL;
+
+					while (pti[i] == '@')
+					{
+						escape = TRUE;
+
+						i++;
+					}
+
+					for (ptt = temp ; isalnum((int) pti[i]) || pti[i] == '_' ; i++)
+					{
+						*ptt++ = pti[i];
+					}
+					*ptt = 0;
+
+					if (pti[i] != DEFAULT_OPEN)
+					{
+						while (*pti == '@')
+						{
+							*pto++ = *pti++;
+						}
+						continue;
+					}
+
+					node = search_node_list(ses->list[LIST_FUNCTION], temp);
+
+					if (node == NULL)
+					{
+						sesptr = find_session(temp);
+					}
+
+					if (sesptr == NULL && node == NULL)
+					{
+						while (*pti == '@')
+						{
+							*pto++ = *pti++;
+						}
+						continue;
+					}
+
+					if (escape)
+					{
+						pti++;
+
+						while (*pti == '@')
+						{
+							*pto++ = *pti++;
+						}
+						continue;
+					}
+
+					pti = get_arg_in_braces(ses, &pti[i], temp, GET_ONE);
+
+					if (sesptr)
+					{
+						substitute(sesptr, temp, pto, flags_neol);
+
+						pto += strlen(pto);
+
+						continue;
+					}
+					else
+					{
+						substitute(ses, temp, buf, flags_neol);
+					}
+
+					show_debug(ses, LIST_FUNCTION, "#DEBUG FUNCTION {%s}", node->arg1);
+
+					RESTRING(gtd->vars[0], buf);
+
+					pte = buf;
+
+					for (i = 1 ; i < 100 ; i++)
+					{
+						pte = get_arg_in_braces(ses, pte, temp, GET_ALL);
+
+						RESTRING(gtd->vars[i], temp);
+
+						if (*pte == 0)
+						{
+							while (++i < 100)
+							{
+								if (*gtd->vars[i])
+								{
+									RESTRING(gtd->vars[i], "");
+								}
+							}
+							break;
+						}
+
+						if (*pte == COMMAND_SEPARATOR)
+						{
+							pte++;
+						}
+
+					}
+
+					substitute(ses, node->arg2, buf, SUB_ARG);
+
+					if (HAS_BIT(node->flags, NODE_FLAG_ONESHOT))
+					{
+						delete_node_list(ses, LIST_FUNCTION, node);
+					}
+
+					script_driver(ses, LIST_FUNCTION, buf);
+
+					substitute(ses, "$result", pto, flags_neol|SUB_VAR);
+
+					pto += strlen(pto);
+				}
+				else
+				{
+					if (HAS_BIT(flags, SUB_SEC) && !HAS_BIT(flags, SUB_ARG) && is_function(ses, pti))
+					{
+						*pto++ = '\\';
+					}
+					*pto++ = *pti++;
+				}
+				break;
+
+			case '*':
+				if (HAS_BIT(flags, SUB_VAR) && !HAS_BIT(ses->list[LIST_VARIABLE]->flags, LIST_FLAG_IGNORE) && pti[1])
+				{
+					int brace = FALSE;
+					i = 1;
+					escape = FALSE;
+
+					while (pti[i] == '*')
+					{
+						escape = TRUE;
+
+						i++;
+					}
+
+					if (pti[i] == DEFAULT_OPEN)
+					{
+						brace = TRUE;
+
+						ptt = get_arg_in_braces(ses, &pti[i], buf, GET_ALL);
+
+						i += strlen(buf) + 2;
+
+						substitute(ses, buf, temp, flags_neol);
+					}
+					else
+					{
+						ptt = temp;
+
+						while (isalnum((int) pti[i]) || pti[i] == '_')
+						{
+							*ptt++ = pti[i];
+
+							i++;
+						}
+						*ptt = 0;
+					}
+
+					if (*temp)
+					{
+						root = local_list(ses);
+
+						if ((node = search_node_list(root, temp)) == NULL)
+						{
+							root = ses->list[LIST_VARIABLE];
+
+							node = search_node_list(root, temp);
+						}
+					}
+					else
+					{
+						root = ses->list[LIST_VARIABLE];
+						node = NULL;
+					}
+
+					if (brace == FALSE && node == NULL)
+					{
+						while (*pti == '*')
+						{
+							*pto++ = *pti++;
+						}
+						continue;
+					}
+
+					if (escape)
+					{
+						pti++;
+
+						while (*pti == '*')
+						{
+							*pto++ = *pti++;
+						}
+						continue;
+					}
+
+					if (brace == FALSE)
+					{
+						pti = get_arg_at_brackets(ses, &pti[i], temp + strlen(temp));
+					}
+					else
+					{
+						pti = ptt;
+					}
+
+					substitute(ses, temp, buf, flags_neol);
+
+					str = str_dup("");
+
+					get_nest_node_key(root, buf, &str, brace);
+
+					substitute(ses, str, pto, flags_neol - SUB_VAR);
+
+					pto += strlen(pto);
+
+					str_free(str);
+				}
+				else
+				{
+					if (HAS_BIT(flags, SUB_SEC) && !HAS_BIT(flags, SUB_ARG) && is_variable(ses, pti))
+					{
+						*pto++ = '\\';
+					}
+					*pto++ = *pti++;
+				}
+				break;
+
+			case '$':
+				if (HAS_BIT(flags, SUB_VAR) && !HAS_BIT(ses->list[LIST_VARIABLE]->flags, LIST_FLAG_IGNORE) && pti[1])
+				{
+					int brace = FALSE;
+					i = 1;
+					escape = FALSE;
+
+					while (pti[i] == '$')
+					{
+						escape = TRUE;
+
+						i++;
+					}
+
+					if (pti[i] == DEFAULT_OPEN)
+					{
+						brace = TRUE;
+
+						ptt = get_arg_in_braces(ses, &pti[i], buf, GET_ALL);
+
+						i += strlen(buf) + 2;
+
+						substitute(ses, buf, temp, flags_neol);
+					}
+					else
+					{
+						ptt = temp;
+
+						while (isalnum((int) pti[i]) || pti[i] == '_')
+						{
+							*ptt++ = pti[i];
+
+							i++;
+						}
+						*ptt = 0;
+					}
+
+					if (*temp)
+					{
+						root = search_nest_base_ses(ses, temp);
+
+						if (root)
+						{
+							node = search_node_list(root, temp);
+						}
+						else
+						{
+							root = ses->list[LIST_VARIABLE];
+							node = NULL;
+						}
+					}
+					else
+					{
+						root = ses->list[LIST_VARIABLE];
+						node = NULL;
+					}
+
+					if (brace == FALSE && node == NULL)
+					{
+						while (*pti == '$')
+						{
+							*pto++ = *pti++;
+						}
+						continue;
+					}
+
+					if (escape)
+					{
+						pti++;
+
+						while (*pti == '$')
+						{
+							*pto++ = *pti++;
+						}
+						continue;
+					}
+
+					if (brace == FALSE)
+					{
+						pti = get_arg_at_brackets(ses, &pti[i], temp + strlen(temp));
+					}
+					else
+					{
+						pti = ptt;
+					}
+
+					substitute(ses, temp, buf, flags_neol);
+
+					str = str_dup("");
+
+					get_nest_node_val(root, buf, &str, brace);
+
+					substitute(ses, str, pto, flags_neol - SUB_VAR);
+
+					pto += strlen(pto);
+
+					str_free(str);
+				}
+				else
+				{
+					if (HAS_BIT(flags, SUB_SEC) && !HAS_BIT(flags, SUB_ARG) && is_variable(ses, pti))
+					{
+						*pto++ = '\\';
+					}
+					*pto++ = *pti++;
+				}
+				break;
+
+			case '&':
+				if (HAS_BIT(flags, SUB_CMD) && (isdigit((int) pti[1]) || pti[1] == '&'))
+				{
+					if (pti[1] == '&')
+					{
+						while (pti[1] == '&')
+						{
+							*pto++ = *pti++;
+						}
+						if (isdigit((int) pti[1]))
+						{
+							pti++;
+						}
+						else
+						{
+							*pto++ = *pti++;
+						}
+					}
+					else
+					{
+						i = isdigit((int) pti[2]) ? (pti[1] - '0') * 10 + pti[2] - '0' : pti[1] - '0';
+
+						for (cnt = 0 ; gtd->cmds[i][cnt] ; cnt++)
+						{
+							*pto++ = gtd->cmds[i][cnt];
+						}
+						pti += isdigit((int) pti[2]) ? 3 : 2;
+					}
+				}
+				else if (HAS_BIT(flags, SUB_VAR) && !HAS_BIT(ses->list[LIST_VARIABLE]->flags, LIST_FLAG_IGNORE))
+				{
+					int brace = FALSE;
+					i = 1;
+					escape = FALSE;
+
+					while (pti[i] == '&')
+					{
+						escape = TRUE;
+
+						i++;
+					}
+
+					if (pti[i] == DEFAULT_OPEN)
+					{
+						brace = TRUE;
+
+						ptt = get_arg_in_braces(ses, &pti[i], buf, GET_ALL);
+
+						i += strlen(buf) + 2;
+
+						substitute(ses, buf, temp, flags_neol);
+					}
+					else
+					{
+						ptt = temp;
+
+						while (isalnum((int) pti[i]) || pti[i] == '_')
+						{
+							*ptt++ = pti[i];
+
+							i++;
+						}
+						*ptt = 0;
+					}
+
+					if (*temp)
+					{
+						root = local_list(ses);
+
+						if ((node = search_node_list(root, temp)) == NULL)
+						{
+							root = ses->list[LIST_VARIABLE];
+							node = search_node_list(root, temp);
+						}
+					}
+					else
+					{
+						root = ses->list[LIST_VARIABLE];
+						node = NULL;
+					}
+
+					if (brace == FALSE && node == NULL)
+					{
+						while (*pti == '&')
+						{
+							*pto++ = *pti++;
+						}
+						continue;
+					}
+
+					if (escape)
+					{
+						pti++;
+
+						while (*pti == '&')
+						{
+							*pto++ = *pti++;
+						}
+						continue;
+					}
+
+					if (brace == FALSE)
+					{
+						pti = get_arg_at_brackets(ses, &pti[i], temp + strlen(temp));
+					}
+					else
+					{
+						pti = ptt;
+					}
+
+					substitute(ses, temp, buf, flags_neol);
+
+					str = str_dup("");
+
+					get_nest_index(root, buf, &str, brace);
+
+					substitute(ses, str, pto, flags_neol - SUB_VAR);
+
+					pto += strlen(pto);
+
+					str_free(str);
+				}
+				else
+				{
+					if (HAS_BIT(flags, SUB_SEC) && !HAS_BIT(flags, SUB_ARG) && is_variable(ses, pti))
+					{
+						*pto++ = '\\';
+					}
+					*pto++ = *pti++;
+				}
+				break;
+
+			case '%':
+				if (HAS_BIT(flags, SUB_ARG) && (isdigit((int) pti[1]) || pti[1] == '%'))
+				{
+					if (pti[1] == '%')
+					{
+						while (pti[1] == '%')
+						{
+							*pto++ = *pti++;
+						}
+						pti++;
+					}
+					else
+					{
+						i = isdigit((int) pti[2]) ? (pti[1] - '0') * 10 + pti[2] - '0' : pti[1] - '0';
+
+						ptt = gtd->vars[i];
+
+						while (*ptt)
+						{
+							if (HAS_BIT(ses->charset, CHARSET_FLAG_EUC) && is_euc_head(ses, ptt))
+							{
+								*pto++ = *ptt++;
+								*pto++ = *ptt++;
+								continue;
+							}
+
+							if (HAS_BIT(flags, SUB_SEC))
+							{
+								switch (*ptt)
+								{
+									case '\\':
+										*pto++ = '\\';
+										*pto++ = '\\';
+										break;
+
+									case '{':
+										*pto++ = '\\';
+										*pto++ = 'x';
+										*pto++ = '7';
+										*pto++ = 'B';
+										break;
+
+									case '}':
+										*pto++ = '\\';
+										*pto++ = 'x';
+										*pto++ = '7';
+										*pto++ = 'D';
+										break;
+
+									case '$':
+									case '&':
+									case '*':
+										if (is_variable(ses, ptt))
+										{
+											*pto++ = '\\';
+											*pto++ = *ptt;
+										}
+										else
+										{
+											*pto++ = *ptt;
+										}
+										break;
+
+									case '@':
+										if (is_function(ses, ptt))
+										{
+											*pto++ = '\\';
+											*pto++ = *ptt;
+										}
+										else
+										{
+											*pto++ = *ptt;
+										}
+										break;
+
+									case COMMAND_SEPARATOR:
+										*pto++ = '\\';
+										*pto++ = COMMAND_SEPARATOR;
+										break;
+
+									default:
+										*pto++ = *ptt;
+										break;
+								}
+								ptt++;
+							}
+							else
+							{
+								*pto++ = *ptt++;
+							}
+						}
+						pti += isdigit((int) pti[2]) ? 3 : 2;
+					}
+				}
+				else if (pti[1] == '*') // avoid %*variable triggers
+				{
+					*pto++ = *pti++;
+					*pto++ = *pti++;
+				}
+				else
+				{
+					*pto++ = *pti++;
+				}
+				break;
+
+			case '<':
+				if (HAS_BIT(flags, SUB_COL))
+				{
+					if (HAS_BIT(flags, SUB_CMP) && old[0] && !strncmp(old, pti, strlen(old)))
+					{
+						pti += strlen(old);
+					}
+					else if (isdigit(pti[1]) && isdigit(pti[2]) && isdigit(pti[3]) && pti[4] == '>')
+					{
+						if (pti[1] != '8' || pti[2] != '8' || pti[3] != '8')
+						{
+							*pto++ = ASCII_ESC;
+							*pto++ = '[';
+
+							switch (pti[1])
+							{
+								case '2':
+									*pto++ = '2';
+									*pto++ = '2';
+									*pto++ = ';';
+									break;
+								case '8':
+									break;
+								default:
+									*pto++ = pti[1];
+									*pto++ = ';';
+							}
+							switch (pti[2])
+							{
+								case '8':
+									break;
+								default:
+									*pto++ = '3';
+									*pto++ = pti[2];
+									*pto++ = ';';
+									break;
+							}
+							switch (pti[3])
+							{
+								case '8':
+									break;
+								default:
+									*pto++ = '4';
+									*pto++ = pti[3];
+									*pto++ = ';';
+									break;
+							}
+							pto--;
+							*pto++ = 'm';
+						}
+						pti += sprintf(old, "<%c%c%c>", pti[1], pti[2], pti[3]);
+					}
+					else if (pti[1] >= 'a' && pti[1] <= 'f' && pti[2] >= 'a' && pti[2] <= 'f' && pti[3] >= 'a' && pti[3] <= 'f' && pti[4] == '>')
+					{
+						cnt = 16 + (pti[1] - 'a') * 36 + (pti[2] - 'a') * 6 + (pti[3] - 'a');
+
+						if (ses->color >= 256)
+						{
+							pto += sprintf(pto, "\e[38;5;%dm", cnt);
+						}
+						else if (ses->color == 16)
+						{
+							pto += sprintf(pto, "%s", c256to16_fg[cnt]);
+						}
+						pti += sprintf(old, "<%c%c%c>", pti[1], pti[2], pti[3]);
+					}
+					else if (pti[1] >= 'A' && pti[1] <= 'F' && pti[2] >= 'A' && pti[2] <= 'F' && pti[3] >= 'A' && pti[3] <= 'F' && pti[4] == '>')
+					{
+						cnt = 16 + (pti[1] - 'A') * 36 + (pti[2] - 'A') * 6 + (pti[3] - 'A');
+
+						if (ses->color >= 256)
+						{
+							pto += sprintf(pto, "\e[48;5;%dm", cnt);
+						}
+						else if (ses->color == 16)
+						{
+							pto += sprintf(pto, "%s", c256to16_bg[cnt]);
+						}
+						pti += sprintf(old, "<%c%c%c>", pti[1], pti[2], pti[3]);
+					}
+					else if (pti[1] == 'g' && isdigit((int) pti[2]) && isdigit((int) pti[3]) && pti[4] == '>')
+					{
+						cnt = 232 + (pti[2] - '0') * 10 + (pti[3] - '0');
+
+						if (ses->color >= 256)
+						{
+							pto += sprintf(pto, "\e[38;5;%dm", cnt);
+						}
+						else if (ses->color == 16)
+						{
+							pto += sprintf(pto, "%s", c256to16_fg[cnt]);
+						}
+						pti += sprintf(old, "<g%c%c>", pti[2], pti[3]);
+					}
+					else if (pti[1] == 'G' && isdigit((int) pti[2]) && isdigit((int) pti[3]) && pti[4] == '>')
+					{
+						cnt = 232 + (pti[2] - '0') * 10 + (pti[3] - '0');
+
+						if (ses->color >= 256)
+						{
+							pto += sprintf(pto, "\e[48;5;%dm", cnt);
+						}
+						else if (ses->color == 16)
+						{
+							pto += sprintf(pto, "%s", c256to16_bg[cnt]);
+						}
+						pti += sprintf(old, "<G%c%c>", pti[2], pti[3]);
+					}
+					else if (toupper((int) pti[1]) == 'F' && isxdigit(pti[2]) && isxdigit(pti[3]) && isxdigit(pti[4]) && pti[5] == '>')
+					{
+						if (ses->color == 4096)
+						{
+							pto += sprintf(pto, "\e[38;2;%d;%d;%dm", c4096_val(pti[2], pti[2]), c4096_val(pti[3], pti[3]), c4096_val(pti[4], pti[4]));
+						}
+						else if (ses->color == 256)
+						{
+							pto += sprintf(pto, "\033[38;5;%dm",  16 + c4096_to_256_val(pti[2], pti[2]) * 36 + c4096_to_256_val(pti[3], pti[3]) * 6 + c4096_to_256_val(pti[4], pti[4]));
+						}
+						else if (ses->color == 16)
+						{
+							pto += sprintf(pto, "%s", c256to16_fg[16 + c4096_to_256_val(pti[2], pti[2]) * 36 + c4096_to_256_val(pti[3], pti[3]) * 6 + c4096_to_256_val(pti[4], pti[4])]);
+						}
+						pti += sprintf(old, "<F%c%c%c>", pti[2], pti[3], pti[4]);
+					}
+					else if (toupper(pti[1]) == 'F' && pti[2] == '?' && pti[3] == '?' && pti[4] == '?' && pti[5] == '>')
+					{
+						c4096_rnd(ses, &pti[2]);
+
+						if (ses->color == 4096)
+						{
+							pto += sprintf(pto, "\e[38;2;%d;%d;%dm", c4096_val(pti[2], pti[2]), c4096_val(pti[3], pti[3]), c4096_val(pti[4], pti[4]));
+						}
+						else if (ses->color == 256)
+						{
+							pto += sprintf(pto, "\033[38;5;%dm",  16 + c4096_to_256_val(pti[2], pti[2]) * 36 + c4096_to_256_val(pti[3], pti[3]) * 6 + c4096_to_256_val(pti[4], pti[4]));
+						}
+						else if (ses->color == 16)
+						{
+							pto += sprintf(pto, "%s", c256to16_fg[16 + c4096_to_256_val(pti[2], pti[2]) * 36 + c4096_to_256_val(pti[3], pti[3]) * 6 + c4096_to_256_val(pti[4], pti[4])]);
+						}
+						pti += sprintf(old, "<F%c%c%c>", pti[2], pti[3], pti[4]);
+					}
+					else if (toupper((int) pti[1]) == 'F' && isxdigit(pti[2]) && isxdigit(pti[3]) && isxdigit(pti[4]) && isxdigit(pti[5]) && isxdigit(pti[6]) && isxdigit(pti[7]) && pti[8] == '>')
+					{
+						if (ses->color == 4096)
+						{
+							pto += sprintf(pto, "\e[38;2;%d;%d;%dm", c4096_val(pti[2], pti[3]), c4096_val(pti[4], pti[5]), c4096_val(pti[6], pti[7]));
+						}
+						else if (ses->color == 256)
+						{
+							pto += sprintf(pto, "\033[38;5;%dm",  16 + c4096_to_256_val(pti[2], pti[3]) * 36 + c4096_to_256_val(pti[4], pti[5]) * 6 + c4096_to_256_val(pti[6], pti[7]));
+						}
+						else if (ses->color == 16)
+						{
+							pto += sprintf(pto, "%s", c256to16_fg[16 + c4096_to_256_val(pti[2], pti[3]) * 36 + c4096_to_256_val(pti[4], pti[5]) * 6 + c4096_to_256_val(pti[6], pti[7])]);
+						}
+						pti += sprintf(old, "<F%c%c%c%c%c%c>", pti[2], pti[3], pti[4], pti[5], pti[6], pti[7]);
+					}
+					else if (toupper(pti[1]) == 'B' && isxdigit(pti[2]) && isxdigit(pti[3]) && isxdigit(pti[4]) && pti[5] == '>')
+					{
+						if (ses->color == 4096)
+						{
+							pto += sprintf(pto, "\e[48;2;%d;%d;%dm", c4096_val(pti[2], pti[2]), c4096_val(pti[3], pti[3]), c4096_val(pti[4], pti[4]));
+						}
+						else if (ses->color == 256)
+						{
+							pto += sprintf(pto, "\033[48;5;%dm",  16 + c4096_to_256_val(pti[2], pti[2]) * 36 + c4096_to_256_val(pti[3], pti[3]) * 6 + c4096_to_256_val(pti[4], pti[4]));
+						}
+						else if (ses->color == 16)
+						{
+							pto += sprintf(pto, "%s", c256to16_bg[16 + c4096_to_256_val(pti[2], pti[2]) * 36 + c4096_to_256_val(pti[3], pti[3]) * 6 + c4096_to_256_val(pti[4], pti[4])]);
+						}
+						pti += sprintf(old, "<B%c%c%c>", pti[2], pti[3], pti[4]);
+					}
+					else if (toupper(pti[1]) == 'B' && pti[2] == '?' && pti[3] == '?' && pti[4] == '?' && pti[5] == '>')
+					{
+						c4096_rnd(ses, &pti[2]);
+
+						if (ses->color == 4096)
+						{
+							pto += sprintf(pto, "\e[48;2;%d;%d;%dm", c4096_val(pti[2], pti[2]), c4096_val(pti[3], pti[3]), c4096_val(pti[4], pti[4]));
+						}
+						else if (ses->color == 256)
+						{
+							pto += sprintf(pto, "\033[48;5;%dm",  16 + c4096_to_256_val(pti[2], pti[2]) * 36 + c4096_to_256_val(pti[3], pti[3]) * 6 + c4096_to_256_val(pti[4], pti[4]));
+						}
+						else if (ses->color == 16)
+						{
+							pto += sprintf(pto, "%s", c256to16_bg[16 + c4096_to_256_val(pti[2], pti[2]) * 36 + c4096_to_256_val(pti[3], pti[3]) * 6 + c4096_to_256_val(pti[4], pti[4])]);
+						}
+						pti += sprintf(old, "<F%c%c%c>", pti[2], pti[3], pti[4]);
+					}
+					else if (toupper(pti[1]) == 'B' && isxdigit(pti[2]) && isxdigit(pti[3]) && isxdigit(pti[4]) && isxdigit(pti[5]) && isxdigit(pti[6]) && isxdigit(pti[7]) && pti[8] == '>')
+					{
+						if (ses->color == 4096)
+						{
+							pto += sprintf(pto, "\e[48;2;%d;%d;%dm", c4096_val(pti[2], pti[3]), c4096_val(pti[4], pti[5]), c4096_val(pti[6], pti[7]));
+						}
+						else if (ses->color == 256)
+						{
+							pto += sprintf(pto, "\033[48;5;%dm",  16 + c4096_to_256_val(pti[2], pti[3]) * 36 + c4096_to_256_val(pti[4], pti[5]) * 6 + c4096_to_256_val(pti[6], pti[7]));
+						}
+						else if (ses->color == 16)
+						{
+							pto += sprintf(pto, "%s", c256to16_bg[16 + c4096_to_256_val(pti[2], pti[3]) * 36 + c4096_to_256_val(pti[4], pti[5]) * 6 + c4096_to_256_val(pti[6], pti[7])]);
+						}
+						pti += sprintf(old, "<B%c%c%c%c%c%c>", pti[2], pti[3], pti[4], pti[5], pti[6], pti[7]);
+					}
+					else
+					{
+						*pto++ = *pti++;
+					}
+				}
+				else
+				{
+					*pto++ = *pti++;
+				}
+				break;
+
+
+			case '\\':
+				if (HAS_BIT(flags, SUB_ESC))
+				{
+					pti++;
+
+					switch (*pti)
+					{
+						case 'a':
+							*pto++ = '\a';
+							break;
+						case 'b':
+							*pto++ = '\b';
+							break;
+						case 'c':
+							if (pti[1])
+							{
+								pti++;
+								*pto++ = *pti % 32;
+							}
+							break;
+						case 'e':
+							*pto++ = '\e';
+							break;
+						case 'f':
+							*pto++ = '\f';
+							break;
+						case 'n':
+							*pto++ = '\n';
+							break;
+						case 'r':
+							*pto++ = '\r';
+							break;
+						case 't':
+							*pto++ = '\t';
+							break;
+						case 'x':
+							if (pti[1] && pti[2])
+							{
+								if (pti[1] == '0' && pti[2] == '0' && pti[3] == 0)
+								{
+									pti += 2;
+									DEL_BIT(flags, SUB_EOL);
+									DEL_BIT(flags, SUB_LNF);
+								}
+								else
+								{
+									pti++;
+									*pto++ = hex_number_8bit(pti);
+									pti++;
+								}
+							}
+							break;
+
+						case 'u':
+							if (pti[1] && pti[2] && pti[3] && pti[4])
+							{
+								pto += unicode_16_bit(&pti[1], pto);
+								pti += 4;
+							}
+							break;
+						case 'U':
+							if (pti[1] && pti[2] && pti[3] && pti[4] && pti[5] && pti[6])
+							{
+								pto += unicode_21_bit(&pti[1], pto);
+								pti += 6;
+							}
+							break;
+
+						case 'v':
+							*pto++ = '\v';
+							break;
+						case '0':
+							if (pti[1] == 0)
+							{
+								DEL_BIT(flags, SUB_EOL);
+								DEL_BIT(flags, SUB_LNF);
+							}
+							else if (pti[1] && pti[2])
+							{
+								pti++;
+								*pto++ = oct_number(pti);
+								pti++;
+							}
+							break;
+
+						case '\0':
+							DEL_BIT(flags, SUB_EOL);
+							DEL_BIT(flags, SUB_LNF);
+							continue;
+
+						default:
+							*pto++ = *pti;
+							break;
+					}
+					pti++;
+				}
+				else if (HAS_BIT(flags, SUB_SEC) && !HAS_BIT(flags, SUB_ARG))
+				{
+					*pto++ = '\\';
+					*pto++ = *pti++;
+				}
+				else if (HAS_BIT(flags, SUB_LIT))
+				{
+					*pto++ = *pti++;
+				}
+				else
+				{
+					*pto++ = *pti++;
+
+					if (*pti)
+					{
+						*pto++ = *pti++;
+					}
+				}
+				break;
+
+			case ASCII_ESC:
+				if (HAS_BIT(flags, SUB_COL) && ses->color == 0)
+				{
+					skip = find_color_code(pti);
+
+					if (skip)
+					{
+						pti += skip;
+					}
+					else
+					{
+						*pto++ = *pti++;
+					}
+				}
+				else
+				{
+					*pto++ = *pti++;
+				}
+				break;
+
+			default:
+				if (HAS_BIT(flags, SUB_SEC) && !HAS_BIT(flags, SUB_ARG))
+				{
+					switch (*pti)
+					{
+						case '{':
+							*pto++ = '\\';
+							*pto++ = 'x';
+							*pto++ = '7';
+							*pto++ = 'B';
+							break;
+
+						case '}':
+							*pto++ = '\\';
+							*pto++ = 'x';
+							*pto++ = '7';
+							*pto++ = 'D';
+							break;
+
+						case COMMAND_SEPARATOR:
+							*pto++ = '\\';
+							*pto++ = COMMAND_SEPARATOR;
+							break;
+
+						default:
+							*pto++ = *pti;
+							break;
+					}
+					pti++;
+				}
+				else
+				{
+					*pto++ = *pti++;
+				}
+				break;
+		}
+	}
+	pop_call();
+	return 0;
+}

+ 344 - 0
system.c

@@ -0,0 +1,344 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2019 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*               (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                  *
+*                                                                             *
+*                     coded by Igor van den Hoven 2007                        *
+******************************************************************************/
+
+#include "tintin.h"
+
+#ifdef HAVE_PTY_H
+#include <pty.h>
+#else
+#ifdef HAVE_UTIL_H
+#include <util.h>
+#endif
+#endif
+#include <fcntl.h>  
+#include <dirent.h>
+#include <termios.h>
+#include <sys/un.h>
+
+#include <sys/wait.h>
+
+DO_COMMAND(do_run)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], temp[BUFFER_SIZE], file[BUFFER_SIZE];
+	int desc, pid;
+	struct winsize size;
+
+	char *argv[4] = {"sh", "-c", "", NULL};
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ALL, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, file, GET_ONE, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0 || *arg2 == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #RUN {NAME} {SYSTEM SHELL COMMAND}");
+		
+		return ses;
+	}
+
+	size.ws_row = get_scroll_rows(ses);
+	size.ws_col = get_scroll_cols(ses);
+	size.ws_ypixel = size.ws_row * gtd->screen->char_height;
+	size.ws_xpixel = size.ws_col * gtd->screen->char_width;
+
+	pid = forkpty(&desc, temp, &gtd->old_terminal, &size);
+
+	switch (pid)
+	{
+		case -1:
+			syserr_printf(ses, "do_run: forkpty");
+			break;
+
+		case 0:
+			sprintf(temp, "exec %s", arg2);
+			argv[2] = temp;
+			execv("/bin/sh", argv);
+			break;
+
+		default:
+			sprintf(temp, "{%s} {%d} {%s}", arg2, pid, file);
+			ses = new_session(ses, arg1, temp, desc, 0);
+			break;
+	}
+	return ses;
+}
+
+
+DO_COMMAND(do_script)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], *cptr, buf[BUFFER_SIZE], var[BUFFER_SIZE], tmp[BUFFER_SIZE];
+	FILE *script;
+	int index;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SCRIPT: ONE ARGUMENT REQUIRED.");
+	}
+	else if (*arg2 == 0)
+	{
+		script = popen(arg1, "r");
+
+		if (script)
+		{
+			while (fgets(buf, BUFFER_SIZE - 1, script))
+			{
+				cptr = strchr(buf, '\n');
+
+				if (cptr)
+				{
+					*cptr = 0;
+				}
+
+				ses = script_driver(ses, LIST_COMMAND, buf);
+			}
+
+			pclose(script);
+		}
+		else
+		{
+			syserr_printf(ses, "do_script: popen 1");
+		}
+	}
+	else
+	{
+		index = 1;
+
+		script = popen(arg2, "r");
+
+		if (script)
+		{
+			var[0] = 0;
+
+			while (fgets(buf, BUFFER_SIZE - 1, script))
+			{
+				cptr = strchr(buf, '\n');
+
+				if (cptr)
+				{
+					*cptr = 0;
+				}
+
+				substitute(ses, buf, tmp, SUB_SEC);
+
+				cat_sprintf(var, "{%d}{%s}", index++, tmp);
+			}
+
+			set_nest_node_ses(ses, arg1, "%s", var);
+
+			pclose(script);
+		}
+		else
+		{
+			syserr_printf(ses, "do_script: popen 2");
+		}
+	}
+	refresh_session_terminal(ses);
+
+	return ses;
+}
+
+
+DO_COMMAND(do_suspend)
+{
+	print_stdout("\e[r\e[%d;%dH", gtd->screen->rows, 1);
+
+	fflush(NULL);
+
+	reset_terminal(gtd->ses);
+
+	kill(0, SIGSTOP);
+
+	init_terminal(gtd->ses);
+
+	dirty_screen(gtd->ses);
+
+	tintin_puts(NULL, "#RETURNING BACK TO TINTIN++.");
+
+	return ses;
+}
+
+// use #run + waitpid() ?
+
+/*
+DO_COMMAND(do_system)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], temp[BUFFER_SIZE], buffer[BUFFER_SIZE];
+	int desc, pid;
+	struct winsize size;
+	char *argv[4] = {"sh", "-c", "", NULL};
+	int filedes[2];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SYSTEM {SHELL COMMAND}");
+		
+		return ses;
+	}
+
+	size.ws_row = get_scroll_rows(ses);
+	size.ws_col = get_scroll_cols(ses);
+	size.ws_ypixel = size.ws_row * gtd->screen->char_height;
+	size.ws_xpixel = size.ws_col * gtd->screen->char_width;
+
+	if (pipe(filedes) == -1)
+	{
+		perror("pipe");
+
+		return ses;
+	}
+
+	pid = fork();
+
+	switch (pid)
+	{
+		case -1:
+			syserr_printf(ses, "do_system: forkpty");
+			break;
+
+		case 0:
+			while ((dup2(filedes[1], STDOUT_FILENO) == -1) && (errno == EINTR)) {}
+			close(filedes[1]);
+			close(filedes[0]);
+
+			sprintf(temp, "exec %s", arg1);
+			argv[2] = temp;
+			execv("/bin/sh", argv);
+			perror("execv");
+			break;
+
+		default:
+			close(filedes[1]);
+
+			while (TRUE)
+			{
+				ssize_t count = read(filedes[0], buffer, sizeof(buffer));
+
+				if (count == -1)
+				{
+					if (errno == EINTR)
+					{
+						continue;
+					}
+					else
+					{
+						perror("read");
+					}
+					return ses;
+				}
+				else if (count == 0)
+				{
+					break;
+				}
+				else
+				{
+					tintin_printf2(ses, "%s", buffer);
+				}
+			}
+			close(filedes[0]);
+			wait(0);
+			break;
+	}
+	return ses;
+}
+*/
+
+DO_COMMAND(do_system)
+{
+	char arg1[BUFFER_SIZE];
+
+	sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: #SYSTEM {COMMAND}.");
+		
+		return ses;
+	}
+
+	show_message(ses, LIST_COMMAND, "#OK: EXECUTING '%s'", arg1);
+
+	if (!HAS_BIT(gtd->ses->flags, SES_FLAG_READMUD) && IS_SPLIT(gtd->ses))
+	{
+		save_pos(gtd->ses);
+
+		goto_pos(gtd->ses, gtd->ses->split->bot_row, 1);
+	}
+
+	system(arg1);
+
+	if (!HAS_BIT(gtd->ses->flags, SES_FLAG_READMUD) && IS_SPLIT(gtd->ses))
+	{
+		restore_pos(gtd->ses);
+	}
+
+	refresh_session_terminal(gtd->ses);
+
+	return ses;
+}
+
+
+DO_COMMAND(do_textin)
+{
+	FILE *fp;
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], buffer[BUFFER_SIZE], *cptr;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = get_arg_in_braces(ses, arg, arg2, GET_ALL);
+
+	if ((fp = fopen(arg1, "r")) == NULL)
+	{
+		show_error(ses, LIST_COMMAND, "#ERROR: #TEXTIN {%s} - FILE NOT FOUND.", arg1);
+		
+		return ses;
+	}
+
+	while (fgets(buffer, BUFFER_SIZE - 1, fp))
+	{
+		cptr = strchr(buffer, '\n');
+
+		if (cptr)
+		{
+			*cptr = 0;
+		}
+
+		write_mud(ses, buffer, SUB_EOL);
+
+		if (*arg2)
+		{
+			usleep((long long) (get_number(ses, arg2) * 1000000));
+		}
+	}
+	fclose(fp);
+
+	show_message(ses, LIST_COMMAND, "#TEXTIN {%s} - FILE READ.", arg1);
+
+	return ses;
+}

+ 2117 - 0
tables.c

@@ -0,0 +1,2117 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2019 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
+*                                                                             *
+*                      coded by Igor van den Hoven 2004                       *
+******************************************************************************/
+
+
+#include "tintin.h"
+#include "telnet.h"
+
+struct command_type command_table[] =
+{
+	{    "action",            do_action,            TOKEN_TYPE_COMMAND },
+	{    "advertise",         do_advertise,         TOKEN_TYPE_COMMAND },
+	{    "alias",             do_alias,             TOKEN_TYPE_COMMAND },
+	{    "all",               do_all,               TOKEN_TYPE_COMMAND },
+	{    "bell",              do_bell,              TOKEN_TYPE_COMMAND },
+	{    "break",             do_nop,               TOKEN_TYPE_BREAK   },
+	{    "buffer",            do_buffer,            TOKEN_TYPE_COMMAND },
+	{    "button",            do_button,            TOKEN_TYPE_COMMAND },
+	{    "case",              do_nop,               TOKEN_TYPE_CASE    },
+	{    "cat",               do_cat,               TOKEN_TYPE_COMMAND },
+	{    "chat",              do_chat,              TOKEN_TYPE_COMMAND },
+	{    "class",             do_class,             TOKEN_TYPE_COMMAND },
+	{    "commands",          do_commands,          TOKEN_TYPE_COMMAND },
+	{    "config",            do_configure,         TOKEN_TYPE_COMMAND },
+	{    "continue",          do_nop,               TOKEN_TYPE_CONTINUE},
+	{    "cr",                do_cr,                TOKEN_TYPE_COMMAND },
+	{    "cursor",            do_cursor,            TOKEN_TYPE_COMMAND },
+	{    "daemon",            do_daemon,            TOKEN_TYPE_COMMAND },
+	{    "debug",             do_debug,             TOKEN_TYPE_COMMAND },
+	{    "default",           do_nop,               TOKEN_TYPE_DEFAULT },
+	{    "delay",             do_delay,             TOKEN_TYPE_COMMAND },
+	{    "dictionary",        do_dictionary,        TOKEN_TYPE_COMMAND },
+	{    "draw",              do_draw,              TOKEN_TYPE_COMMAND },
+	{    "echo",              do_echo,              TOKEN_TYPE_COMMAND },
+	{    "else",              do_nop,               TOKEN_TYPE_ELSE    },
+	{    "elseif",            do_nop,               TOKEN_TYPE_ELSEIF  },
+	{    "end",               do_end,               TOKEN_TYPE_COMMAND },
+	{    "event",             do_event,             TOKEN_TYPE_COMMAND },
+	{    "foreach",           do_nop,               TOKEN_TYPE_FOREACH },
+	{    "format",            do_format,            TOKEN_TYPE_COMMAND },
+	{    "function",          do_function,          TOKEN_TYPE_COMMAND },
+	{    "gag",               do_gag,               TOKEN_TYPE_COMMAND },
+	{    "grep",              do_grep,              TOKEN_TYPE_COMMAND },
+	{    "help",              do_help,              TOKEN_TYPE_COMMAND },
+	{    "highlight",         do_highlight,         TOKEN_TYPE_COMMAND },
+	{    "history",           do_history,           TOKEN_TYPE_COMMAND },
+	{    "if",                do_nop,               TOKEN_TYPE_IF      },
+	{    "ignore",            do_ignore,            TOKEN_TYPE_COMMAND },
+	{    "info",              do_info,              TOKEN_TYPE_COMMAND },
+	{    "kill",              do_kill,              TOKEN_TYPE_COMMAND },
+	{    "killall",           do_killall,           TOKEN_TYPE_COMMAND },
+	{    "line",              do_line,              TOKEN_TYPE_COMMAND },
+	{    "list",              do_list,              TOKEN_TYPE_COMMAND },
+	{    "local",             do_local,             TOKEN_TYPE_COMMAND },
+	{    "log",               do_log,               TOKEN_TYPE_COMMAND },
+	{    "loop",              do_nop,               TOKEN_TYPE_LOOP    },
+	{    "macro",             do_macro,             TOKEN_TYPE_COMMAND },
+	{    "map",               do_map,               TOKEN_TYPE_COMMAND },
+	{    "math",              do_math,              TOKEN_TYPE_COMMAND },
+	{    "message",           do_message,           TOKEN_TYPE_COMMAND },
+	{    "nop",               do_nop,               TOKEN_TYPE_COMMAND },
+	{    "parse",             do_nop,               TOKEN_TYPE_PARSE   },
+	{    "path",              do_path,              TOKEN_TYPE_COMMAND },
+	{    "pathdir",           do_pathdir,           TOKEN_TYPE_COMMAND },
+	{    "port",              do_port,              TOKEN_TYPE_COMMAND },
+	{    "prompt",            do_prompt,            TOKEN_TYPE_COMMAND },
+	{    "read",              do_read,              TOKEN_TYPE_COMMAND },
+	{    "regexp",            do_regexp,            TOKEN_TYPE_REGEX   },
+	{    "replace",           do_replace,           TOKEN_TYPE_COMMAND },
+	{    "return",            do_nop,               TOKEN_TYPE_RETURN  },
+	{    "run",               do_run,               TOKEN_TYPE_COMMAND },
+	{    "scan",              do_scan,              TOKEN_TYPE_COMMAND },
+	{    "screen",            do_screen,            TOKEN_TYPE_COMMAND },
+	{    "script",            do_script,            TOKEN_TYPE_COMMAND },
+	{    "send",              do_send,              TOKEN_TYPE_COMMAND },
+	{    "session",           do_session,           TOKEN_TYPE_COMMAND },
+	{    "showme",            do_showme,            TOKEN_TYPE_COMMAND },
+	{    "snoop",             do_snoop,             TOKEN_TYPE_COMMAND },
+	{    "split",             do_split,             TOKEN_TYPE_COMMAND },
+	{    "ssl",               do_ssl,               TOKEN_TYPE_COMMAND },
+	{    "substitute",        do_substitute,        TOKEN_TYPE_COMMAND },
+	{    "switch",            do_nop,               TOKEN_TYPE_SWITCH  },
+	{    "system",            do_system,            TOKEN_TYPE_COMMAND },
+	{    "tab",               do_tab,               TOKEN_TYPE_COMMAND },
+	{    "test",              do_test,              TOKEN_TYPE_COMMAND },
+	{    "textin",            do_textin,            TOKEN_TYPE_COMMAND },
+	{    "ticker",            do_tick,              TOKEN_TYPE_COMMAND },
+	{    "unaction",          do_unaction,          TOKEN_TYPE_COMMAND },
+	{    "unalias",           do_unalias,           TOKEN_TYPE_COMMAND },
+	{    "unbutton",          do_unbutton,          TOKEN_TYPE_COMMAND },
+	{    "undelay",           do_undelay,           TOKEN_TYPE_COMMAND },
+	{    "unevent",           do_unevent,           TOKEN_TYPE_COMMAND },
+	{    "unfunction",        do_unfunction,        TOKEN_TYPE_COMMAND },
+	{    "ungag",             do_ungag,             TOKEN_TYPE_COMMAND },
+	{    "unhighlight",       do_unhighlight,       TOKEN_TYPE_COMMAND },
+	{    "unmacro",           do_unmacro,           TOKEN_TYPE_COMMAND },
+	{    "unpathdir",         do_unpathdir,         TOKEN_TYPE_COMMAND },
+	{    "unprompt",          do_unprompt,          TOKEN_TYPE_COMMAND },
+	{    "unsplit",           do_unsplit,           TOKEN_TYPE_COMMAND },
+	{    "unsubstitute",      do_unsubstitute,      TOKEN_TYPE_COMMAND },
+	{    "untab",             do_untab,             TOKEN_TYPE_COMMAND },
+	{    "unticker",          do_untick,            TOKEN_TYPE_COMMAND },
+	{    "unvariable",        do_unvariable,        TOKEN_TYPE_COMMAND },
+	{    "variable",          do_variable,          TOKEN_TYPE_COMMAND },
+	{    "while",             do_nop,               TOKEN_TYPE_WHILE   },
+	{    "write",             do_write,             TOKEN_TYPE_COMMAND },
+	{    "zap",               do_zap,               TOKEN_TYPE_COMMAND },
+	{    "",                  NULL,                 TOKEN_TYPE_COMMAND }
+};
+
+
+struct list_type list_table[LIST_MAX] =
+{
+	{    "ACTION",            "ACTIONS",            SORT_PRIORITY,    3, 2, 3, LIST_FLAG_MESSAGE|LIST_FLAG_READ|LIST_FLAG_WRITE|LIST_FLAG_CLASS|LIST_FLAG_INHERIT|LIST_FLAG_REGEX|LIST_FLAG_PRIORITY },
+	{    "ALIAS",             "ALIASES",            SORT_PRIORITY,    3, 2, 3, LIST_FLAG_MESSAGE|LIST_FLAG_READ|LIST_FLAG_WRITE|LIST_FLAG_CLASS|LIST_FLAG_INHERIT|LIST_FLAG_REGEX|LIST_FLAG_PRIORITY },
+	{    "BUTTON",            "BUTTONS",            SORT_PRIORITY,    3, 2, 3, LIST_FLAG_MESSAGE|LIST_FLAG_READ|LIST_FLAG_WRITE|LIST_FLAG_CLASS|LIST_FLAG_INHERIT|LIST_FLAG_PRIORITY },
+	{    "CLASS",             "CLASSES",            SORT_PRIORITY,    2, 0, 0, LIST_FLAG_MESSAGE|LIST_FLAG_READ|LIST_FLAG_INHERIT                                 },
+	{    "COMMAND",           "COMMANDS",           SORT_ALPHA,       1, 0, 0, LIST_FLAG_MESSAGE                                                                  },
+	{    "CONFIG",            "CONFIGS",            SORT_ALPHA,       2, 0, 0, LIST_FLAG_MESSAGE|LIST_FLAG_READ|LIST_FLAG_WRITE|LIST_FLAG_CLASS|LIST_FLAG_INHERIT },
+	{    "DELAY",             "DELAYS",             SORT_DELAY,       3, 2, 0, LIST_FLAG_MESSAGE|LIST_FLAG_READ                                                   },
+	{    "EVENT",             "EVENTS",             SORT_ALPHA,       2, 2, 0, LIST_FLAG_MESSAGE|LIST_FLAG_READ|LIST_FLAG_WRITE|LIST_FLAG_CLASS|LIST_FLAG_INHERIT },
+	{    "FUNCTION",          "FUNCTIONS",          SORT_ALPHA,       2, 2, 0, LIST_FLAG_MESSAGE|LIST_FLAG_READ|LIST_FLAG_WRITE|LIST_FLAG_CLASS|LIST_FLAG_INHERIT },
+	{    "GAG",               "GAGS",               SORT_ALPHA,       1, 0, 0, LIST_FLAG_MESSAGE|LIST_FLAG_READ|LIST_FLAG_WRITE|LIST_FLAG_CLASS|LIST_FLAG_INHERIT },
+	{    "HIGHLIGHT",         "HIGHLIGHTS",         SORT_PRIORITY,    3, 0, 3, LIST_FLAG_MESSAGE|LIST_FLAG_READ|LIST_FLAG_WRITE|LIST_FLAG_CLASS|LIST_FLAG_INHERIT|LIST_FLAG_REGEX|LIST_FLAG_PRIORITY },
+	{    "HISTORY",           "HISTORIES",          SORT_APPEND,      1, 0, 0, LIST_FLAG_MESSAGE|LIST_FLAG_HIDE },
+	{    "LANDMARK",          "LANDMARKS",          SORT_ALPHA,       4, 0, 0, LIST_FLAG_MESSAGE|LIST_FLAG_HIDE },
+	{    "MACRO",             "MACROS",             SORT_ALPHA,       2, 2, 0, LIST_FLAG_MESSAGE|LIST_FLAG_READ|LIST_FLAG_WRITE|LIST_FLAG_CLASS|LIST_FLAG_INHERIT },
+	{    "PATH",              "PATHS",              SORT_APPEND,      2, 0, 0, LIST_FLAG_MESSAGE|LIST_FLAG_HIDE },
+	{    "PATHDIR",           "PATHDIRS",           SORT_ALPHA,       3, 0, 0, LIST_FLAG_MESSAGE|LIST_FLAG_READ|LIST_FLAG_WRITE|LIST_FLAG_CLASS|LIST_FLAG_INHERIT },
+	{    "PROMPT",            "PROMPTS",            SORT_ALPHA,       4, 0, 0, LIST_FLAG_MESSAGE|LIST_FLAG_READ|LIST_FLAG_WRITE|LIST_FLAG_CLASS|LIST_FLAG_INHERIT|LIST_FLAG_REGEX },
+	{    "SUBSTITUTE",        "SUBSTITUTES",        SORT_PRIORITY,    3, 0, 3, LIST_FLAG_MESSAGE|LIST_FLAG_READ|LIST_FLAG_WRITE|LIST_FLAG_CLASS|LIST_FLAG_INHERIT|LIST_FLAG_REGEX|LIST_FLAG_PRIORITY },
+	{    "TAB",               "TABS",               SORT_ALPHA,       1, 0, 0, LIST_FLAG_MESSAGE|LIST_FLAG_READ|LIST_FLAG_WRITE|LIST_FLAG_CLASS|LIST_FLAG_INHERIT },
+	{    "TERRAIN",           "TERRAINS",           SORT_ALPHA,       2, 0, 0, LIST_FLAG_MESSAGE|LIST_FLAG_HIDE },
+	{    "TICKER",            "TICKERS",            SORT_ALPHA,       3, 2, 0, LIST_FLAG_MESSAGE|LIST_FLAG_READ|LIST_FLAG_WRITE|LIST_FLAG_CLASS|LIST_FLAG_INHERIT },
+	{    "VARIABLE",          "VARIABLES",          SORT_ALPHA,       2, 0, 0, LIST_FLAG_MESSAGE|LIST_FLAG_READ|LIST_FLAG_WRITE|LIST_FLAG_CLASS|LIST_FLAG_INHERIT|LIST_FLAG_NEST }
+};
+
+struct substitution_type substitution_table[] =
+{
+	{    "ARGUMENTS",            1  },
+	{    "VARIABLES",            2  },
+	{    "FUNCTIONS",            4  },
+	{    "COLORS",               8  },
+	{    "ESCAPES",             16  },
+//	{    "COMMANDS",            32  },
+	{    "SECURE",              64  },
+	{    "EOL",                128  },
+	{    "LNF",                256  },
+//	{    "FIX",                512  },
+        {    "COMPRESS",          1024  },
+	{    "",                  0     }
+};
+
+struct config_type config_table[] =
+{
+	{
+		"AUTO TAB",
+		"",
+		"Buffer lines used for tab completion",
+		config_autotab
+	},
+
+	{
+		"BUFFER SIZE",
+		"",
+		"The size of the scrollback buffer",
+		config_buffersize
+	},
+
+	{
+		"CHARSET",
+		"",
+		"The character set encoding used",
+		config_charset
+	},
+
+	{
+		"CHILD LOCK",
+		"TinTin++ is child locked.",
+		"TinTin++ is not child locked.",
+		config_childlock
+	},
+
+	{
+		"COLOR MODE",
+		"",
+		"The color code encoding used",
+		config_colormode
+	},
+
+	{
+		"COLOR PATCH",
+		"Color the start of each line",
+		"Leave color handling up to the server",
+		config_colorpatch
+	},
+
+	{
+		"COMMAND COLOR",
+		"",
+		"The color of echoed commands",
+		config_commandcolor
+	},
+
+	{
+		"COMMAND ECHO",
+		"Commands are echoed in split mode",
+		"Commands are not echoed in split mode",
+		config_commandecho
+	},
+
+	{
+		"CONNECT RETRY",
+		"",
+		"Seconds sessions try to connect on failure",
+		config_connectretry
+	},
+
+	{
+		"CONVERT META",
+		"TinTin++ converts meta characters",
+		"TinTin++ doesn't convert meta characters",
+		config_convertmeta
+	},
+
+	{
+		"DEBUG TELNET",
+		"You see telnet negotiations",
+		"You do not see telnet negotatiations",
+		config_debugtelnet
+	},
+
+	{
+		"HISTORY SIZE",
+		"",
+		"The size of the command history",
+		config_historysize
+	},
+
+	{
+		"INHERITANCE",
+		"The startup session is inherited",
+		"The startup session is not inherited",
+		config_inheritance
+	},
+
+	{
+		"LOG MODE",
+		"",
+		"The data type mode of log files",
+		config_logmode
+	},
+
+	{
+		"LOG LEVEL",
+		"TinTin++ only logs low level server data",
+		"TinTin++ only logs high level server data",
+		config_loglevel
+	},
+
+	{
+		"MCCP",
+		"MCCP is enabled.",
+		"MCCP is disabled.",
+		config_mccp
+	},
+
+	{
+		"MOUSE TRACKING",
+		"Generate mouse tracking events.",
+		"Do not generate mouse events.",
+		config_mousetracking
+	},
+
+	{
+		"PACKET PATCH",
+		"",
+		"Seconds to try to patch broken packets",
+		config_packetpatch
+	},
+
+	{
+		"PID",
+		"",
+		"The PID of the master process.",
+		config_pid
+	},
+
+	{
+		"RANDOM SEED",
+		"",
+		"Seed value used for random numbers",
+		config_randomseed
+	},
+
+	{
+		"REPEAT CHAR",
+		"",
+		"Character used for repeating commands",
+		config_repeatchar
+	},
+
+	{
+		"REPEAT ENTER",
+		"You send the last command on an enter",
+		"You send a carriage return on an enter",
+		config_repeatenter
+	},
+
+	{
+		"SCREEN READER",
+		"You are using a screen reader",
+		"You are not using a screen reader",
+		config_screenreader
+	},
+
+	{
+		"SCROLL LOCK",
+		"You do not see server output while scrolling",
+		"You see server output while scrolling",
+		config_scrolllock
+	},
+
+	{
+		"SPEEDWALK",
+		"Your input is scanned for speedwalks",
+		"Your input is not scanned for speedwalks",
+		config_speedwalk
+	},
+
+	{
+		"TAB WIDTH",
+		"",
+		"Number of spaces used for a tab",
+		config_tabwidth
+	},
+
+	{
+		"TELNET",
+		"TELNET support is enabled.",
+		"TELNET support is disabled.",
+		config_telnet
+	},
+
+	{
+		"TINTIN CHAR",
+		"",
+		"Character used for TinTin++ commands",
+		config_tintinchar
+	},
+
+	{
+		"VERBATIM",
+		"Keyboard input is send as is",
+		"Keyboard input is parsed by TinTin++",
+		config_verbatim
+	},
+
+	{
+		"VERBATIM CHAR",
+		"",
+		"Character used for verbatim lines",
+		config_verbatimchar
+	},
+
+	{
+		"VERBOSE",
+		"Read script files verbosely",
+		"Read script files quietly",
+		config_verbose
+	},
+
+	{
+		"WORDWRAP",
+		"Server output is word wrapped",
+		"Server output is line wrapped",
+		config_wordwrap
+	},
+
+
+	{
+		"",
+		"",
+		0,
+		0
+	}
+};
+
+struct color_type color_table[] =
+{
+	{    "azure",         "<abd>",  5 },
+	{    "ebony",         "<aaa>",  5 },
+	{    "jade",          "<adb>",  4 },
+	{    "lime",          "<bda>",  4 },
+	{    "orange",        "<dba>",  6 },
+	{    "pink",          "<dab>",  4 },
+	{    "silver",        "<ccc>",  6 },
+	{    "tan",           "<cba>",  3 },
+	{    "violet",        "<bad>",  6 },
+
+	{    "light azure",   "<acf>", 11 },
+	{    "light ebony",   "<bbb>", 11 },
+	{    "light jade",    "<afc>", 10 },
+	{    "light lime",    "<cfa>", 10 },
+	{    "light orange",  "<fca>", 12 },
+	{    "light pink",    "<fac>", 10 },
+	{    "light silver",  "<eee>", 12 },
+	{    "light tan",     "<eda>",  9 },
+	{    "light violet",  "<caf>", 12 },
+
+	{    "light black",   "<108>", 11 },
+	{    "light red",     "<118>",  9 },
+	{    "light green",   "<128>", 11 },
+	{    "light yellow",  "<138>", 12 },
+	{    "light blue",    "<148>", 10 },
+	{    "light magenta", "<158>", 13 },
+	{    "light cyan",    "<168>", 10 },
+	{    "light white",   "<178>", 11 },
+
+	{    "dark black",    "<208>",  5 },
+	{    "dark red",      "<218>",  4 },
+	{    "dark green",    "<228>",  5 },
+	{    "dark yellow",   "<238>",  6 },
+	{    "dark blue",     "<248>",  4 },
+	{    "dark magenta",  "<258>",  7 },
+	{    "dark cyan",     "<268>",  4 },
+	{    "dark white",    "<278>",  5 },
+
+	{    "Azure",         "<acf>",  5 },
+	{    "Ebony",         "<bbb>",  5 },
+	{    "Jade",          "<afc>",  4 },
+	{    "Lime",          "<cfa>",  4 },
+	{    "Orange",        "<fca>",  6 },
+	{    "Pink",          "<fac>",  4 },
+	{    "Silver",        "<eee>",  6 },
+	{    "Tan",           "<eda>",  3 },
+	{    "Violet",        "<caf>",  6 },
+
+	{    "reset",         "<088>",  5 },
+	{    "light",         "<188>",  5 },
+	{    "bold",          "<188>",  4 },
+	{    "faint",         "<288>",  5 },
+	{    "dim",           "<288>",  3 },
+	{    "dark",          "<288>",  4 },
+	{    "underscore",    "<488>", 10 },
+	{    "blink",         "<588>",  5 },
+	{    "reverse",       "<788>",  7 },
+
+	{    "ununderscore", "\e[24m",13 },
+	{    "unblink",      "\e[25m", 8 },
+	{    "unreverse",    "\e[27m",10 },
+
+	{    "black",         "<aaa>",  5 },
+	{    "red",           "<daa>",  4 },
+	{    "green",         "<ada>",  5 },
+	{    "yellow",        "<dda>",  6 },
+	{    "blue",          "<aad>",  4 },
+	{    "magenta",       "<dad>",  7 },
+	{    "cyan",          "<add>",  4 },
+	{    "white",         "<ddd>",  5 },
+
+	{    "Black",         "<bbb>",  5 },
+	{    "Red",           "<faa>",  3 },
+	{    "Green",         "<afa>",  5 },
+	{    "Yellow",        "<ffa>",  6 },
+	{    "Blue",          "<aaf>",  4 },
+	{    "Magenta",       "<faf>",  7 },
+	{    "Cyan",          "<aff>",  4 },
+	{    "White",         "<fff>",  5 },
+
+	{    "b black",       "<AAA>",  7 },
+	{    "b red",         "<DAA>",  5 },
+	{    "b green",       "<ADA>",  7 },
+	{    "b yellow",      "<DDA>",  8 },
+	{    "b blue",        "<AAD>",  6 },
+	{    "b magenta",     "<DAD>",  9 },
+	{    "b cyan",        "<ADD>",  6 },
+	{    "b white",       "<DDD>",  7 },
+
+	{    "b azure",       "<ABD>",  7 },
+	{    "b ebony",       "<AAA>",  7 },
+	{    "b jade",        "<ADB>",  6 },
+	{    "b lime",        "<BDA>",  6 },
+	{    "b orange",      "<DBA>",  8 },
+	{    "b pink",        "<DAB>",  6 },
+	{    "b silver",      "<CCC>",  8 },
+	{    "b tan",         "<CBA>",  5 },
+	{    "b violet",      "<BAD>",  8 },
+
+	{    "b Azure",       "<ACF>",  7 },
+	{    "b Black",       "<BBB>",  7 },
+	{    "b Blue",        "<AAF>",  6 },
+	{    "b Cyan",        "<AFF>",  6 },
+	{    "b Ebony",       "<BBB>",  7 },
+	{    "b Green",       "<AFA>",  7 },
+	{    "b Jade",        "<AFC>",  6 },
+	{    "b Lime",        "<CFA>",  6 },
+	{    "b Magenta",     "<FAF>",  9 },
+	{    "b Orange",      "<FCA>",  8 },
+	{    "b Pink",        "<FAC>",  6 },
+	{    "b Red",         "<FAA>",  5 },
+	{    "b Silver",      "<EEE>",  8 },
+	{    "b Tan",         "<EDA>",  5 },
+	{    "b Violet",      "<CAF>",  8 },
+	{    "b White",       "<FFF>",  7 },
+	{    "b Yellow",      "<FFA>",  8 },
+	{    "",              "<888>",  0 }
+};
+
+struct color_type map_color_table[] =
+{
+	{     "AVOID",            "<118>" },
+	{     "BACKGROUND",       ""      },
+	{     "BLOCK",            "<218>" },
+	{     "EXITS",            "<278>" },
+	{     "HIDE",             "<168>" },
+	{     "INVISIBLE",        "<208>" },
+	{     "PATHS",            "<138>" },
+	{     "ROOMS",            "<178>" },
+	{     "SYMBOLS",          "<128>" },
+	{     "USER",             "<258>" },
+	{     NULL,               "<888>" }
+};
+
+struct class_type class_table[] =
+{
+	{    "OPEN",              class_open             },
+	{    "CLOSE",             class_close            },
+	{    "KILL",              class_kill             },
+	{    "LIST",              class_list             },
+	{    "LOAD",              class_load             },
+	{    "READ",              class_read             },
+	{    "SAVE",              class_save             },
+	{    "SIZE",              class_size             },
+	{    "WRITE",             class_write            },
+
+	{    "",                  NULL                   },
+};
+
+struct chat_type chat_table[] =
+{
+	{     "ACCEPT",           chat_accept,         0, 1, "Accept a file transfer"		              },
+	{     "CALL",             chat_call,           0, 0, "Call a buddy"                                   },
+	{     "CANCELFILE",       chat_cancelfile,     1, 0, "Cancel a file transfer"                         },
+	{     "COLOR",            chat_color,          1, 0, "Set the default chat color"                     },
+	{     "DECLINE",          chat_decline,        1, 0, "Decline a file transfer"                        },
+	{     "DND",              chat_dnd,            0, 0, "Decline new connections"                        },
+	{     "DOWNLOADDIR",      chat_downloaddir,    1, 0, "Set the download directory"                     },
+	{     "EMOTE",            chat_emote,          0, 1, "Send an emoted chat message"                    },
+	{     "FORWARD",          chat_forward,        1, 0, "Forward all chat messages to a buddy"           },
+	{     "FORWARDALL",       chat_forwardall,     1, 0, "Forward all chat/server messages to a buddy"    },
+	{     "FILESTAT",         chat_filestat,       1, 0, "Show file transfer data"                        },
+	{     "GROUP",            chat_group,          0, 1, "Assign a group to a buddy"                      },
+	{     "IGNORE",           chat_ignore,         1, 0, "Ignore all messages from a buddy"               },
+	{     "INITIALIZE",       chat_initialize,     1, 0, "Initialize chat with an optional port number"   },
+	{     "INFO",             chat_info,           0, 0, "Display the chat settings"                      },
+	{     "IP",               chat_ip,             1, 0, "Change the IP address, unset by default"        },
+	{     "MESSAGE",          chat_message,        0, 1, "Send a private message to a buddy"              },
+	{     "NAME",             chat_name,           1, 0, "Change the chat name"                           },
+	{     "PASTE",            chat_paste,          0, 1, "Paste a block of text to a buddy"               },
+	{     "PEEK",             chat_peek,           1, 0, "Show a buddy's public connections"              },
+	{     "PING",             chat_ping,           1, 0, "Display a buddy's response time"                },
+	{     "PREFIX",           chat_prefix,         1, 0, "Prefix before each chat message"                },
+	{     "PRIVATE",          chat_private,        1, 0, "Do not share a buddy's IP address"              },
+	{     "PUBLIC",           chat_public,         1, 0, "Share a buddy's IP address"                     },
+	{     "REPLY",            chat_reply,          1, 0, "Reply to last private message"                  },
+	{     "REQUEST",          chat_request,        1, 0, "Request a buddy's public connections"           },
+	{     "SEND",             chat_send,           0, 1, "Send a raw data message to a buddy"             },
+	{     "SENDFILE",         chat_sendfile,       0, 1, "Send a file to a buddy"                         },
+	{     "SERVE",            chat_serve,          1, 0, "Forward all public chat messages to a buddy"    },
+	{     "UNINITIALIZE",     chat_uninitialize,   0, 0, "Uninitializes the chat server"                  },
+	{     "WHO",              chat_who,            0, 0, "Show all connections"                           },
+	{     "ZAP",              chat_zap,            1, 0, "Close the connection to a buddy"                },
+	{     "",                 NULL,                0, 0, ""                                               }
+};
+
+struct daemon_type daemon_table[] =
+{
+	{    "ATTACH",            daemon_attach,             "Attach to a daemon"                             },
+	{    "DETACH",            daemon_detach,             "Turn into a daemon and detach"                  },
+	{    "INPUT",             daemon_input,              "Send input to an attached daemon"               },
+	{    "KILL",              daemon_kill,               "Kill a daemon"                                  },
+	{    "LIST",              daemon_list,               "List a daemon"                                  },
+	{    "",                  NULL,                      ""                                               }
+};
+
+struct port_type port_table[] =
+{
+	{     "CALL",             port_call,           0, 0, "Create outgoing socket connection"              },
+	{     "COLOR",            port_color,          1, 0, "Set the default port message color"             },
+	{     "FLAG",             port_flag,           0, 0, "Set various flags."                             },
+	{     "GROUP",            port_group,          0, 1, "Assign a group to a socket"                     },
+	{     "IGNORE",           port_ignore,         1, 0, "Ignore all messages from a socket"              },
+	{     "INITIALIZE",       port_initialize,     0, 0, "Initialize port with optional file name"        },
+	{     "INFO",             port_info,           0, 0, "Display the port settings"                      },
+	{     "NAME",             port_name,           0, 0, "Change a socket name"                           },
+	{     "PREFIX",           port_prefix,         1, 0, "Prefix before each port message"                },
+	{     "RANK",             port_rank,           0, 0, "Assign a rank to a socket"                      },
+	{     "SEND",             port_send,           0, 1, "Send a message to a socket"                     },
+	{     "UNINITIALIZE",     port_uninitialize,   0, 0, "Uninitializes the port"                         },
+	{     "WHO",              port_who,            0, 0, "Show all socket connections"                    },
+	{     "ZAP",              port_zap,            1, 0, "Close the connection to a socket"               },
+	{     "",                 NULL,                0, 0, ""                                               }
+};
+
+struct rank_type rank_table[] =
+{
+	{     "SPY",              0                   },
+	{     "SCOUT",            RANK_FLAG_SCOUT     }
+};
+
+struct array_type array_table[] =
+{
+	{     "ADD",              array_add,         "Add an item to a list table"             },
+	{     "CLEAR",            array_clear,       "Clear a list"                            },
+	{     "CLR",              array_clear,       NULL                                      },
+	{     "COLLAPSE",         array_collapse,    "Collapse the list into a variable"       },
+	{     "CREATE",           array_create,      "Create a list table with given items"    },
+	{     "DELETE",           array_delete,      "Delete a list item with given index"     },
+	{     "EXPLODE",          array_explode,     "Explode the variable into a list"        },
+	{     "FIND",             array_find,        "Find a list item with given regex"       },
+	{     "FND",              array_find,        NULL                                      },
+	{     "GET",              array_get,         "Retrieve a list item with given index"   },
+	{     "INSERT",           array_insert,      "Insert a list item at given index"       },
+	{     "LENGTH",           array_size,        NULL                                      },
+	{     "SET",              array_set,         "Change a list item at given index"       },
+	{     "SHUFFLE",          array_shuffle,     "Sort a list table randomly"              },
+	{     "SIMPLIFY",         array_simplify,    "Turn a list table into a simple list"    },
+	{     "SIZE",             array_size,        NULL                                      },
+	{     "SORT",             array_sort,        "Sort a list table alphabetically"        },
+	{     "SRT",              array_sort,        NULL                                      },
+	{     "TOKENIZE",         array_tokenize,    "Create a list with given characters"     },
+	{     "",                 NULL,                                                        }
+};
+
+// 0 no map, 1 has map, 2 is inside map
+
+struct map_type map_table[] =
+{
+	{     "AT",               map_at,              0,              2, "Execute command at given location"    },
+	{     "CENTER",           map_center,          MAP_FLAG_VTMAP, 2, "Set the center of the map display"    },
+	{     "COLOR",            map_color,           MAP_FLAG_VTMAP, 1, "Set the color for given field"        },
+	{     "CREATE",           map_create,          MAP_FLAG_VTMAP, 0, "Creates the initial map"              },
+	{     "DEBUG",            map_debug,           0,              2, "Obscure debug information"            },
+	{     "DELETE",           map_delete,          MAP_FLAG_VTMAP, 1, "Delete the room at given direction"   },
+	{     "DESTROY",          map_destroy,         MAP_FLAG_VTMAP, 1, "Destroy area or map"                  },
+	{     "DIG",              map_dig,             MAP_FLAG_VTMAP, 2, "Create new room at given direction"   },
+	{     "ENTRANCE",         map_entrance,        MAP_FLAG_VTMAP, 2, "Change the given exit's entrance"     },
+	{     "EXIT",             map_exit,            MAP_FLAG_VTMAP, 2, "Change the given exit"                },
+	{     "EXITFLAG",         map_exitflag,        MAP_FLAG_VTMAP, 2, "Change the given exit's flags"        },
+	{     "EXPLORE",          map_explore,         MAP_FLAG_VTMAP, 2, "Save explored path to #path"          },
+	{     "FIND",             map_find,            MAP_FLAG_VTMAP, 2, "Save found path to #path"             },
+	{     "FLAG",             map_flag,            MAP_FLAG_VTMAP, 1, "Change the map's flags"               },
+	{     "GET",              map_get,             0,              2, "Get various room values"              },
+	{     "GLOBAL",           map_global,          0,              1, "Set the global exit room"             },
+	{     "GOTO",             map_goto,            MAP_FLAG_VTMAP, 1, "Move to the given room"               },
+	{     "INFO",             map_info,            0,              1, "Display map and room information"     },
+	{     "INSERT",           map_insert,          MAP_FLAG_VTMAP, 2, "Insert a room at given direction"     },
+	{     "JUMP",             map_jump,            MAP_FLAG_VTMAP, 2, "Move to the given coordinate"         },
+	{     "LANDMARK",         map_landmark,        0,              1, "Create a global room reference"       },
+	{     "LEAVE",            map_leave,           MAP_FLAG_VTMAP, 2, "Leave the map"                        },
+	{     "LEGEND" ,          map_legend,          MAP_FLAG_VTMAP, 1, "Manipulate the map legend"            },
+	{     "LINK",             map_link,            MAP_FLAG_VTMAP, 2, "Link room at given direction"         },
+	{     "LIST",             map_list,            0,              2, "List matching rooms"                  },
+	{     "MAP",              map_map,             0,              2, "Display the map"                      },
+	{     "MOVE",             map_move,            MAP_FLAG_VTMAP, 2, "Move to the given direction"          },
+	{     "NAME",             map_name,            MAP_FLAG_VTMAP, 2, "(obsolete) Use SET ROOMNAME instead"  },
+	{     "OFFSET",           map_offset,          MAP_FLAG_VTMAP, 1, "Set the offset of the vt map"         },
+	{     "READ",             map_read,            MAP_FLAG_VTMAP, 0, "Read a map file"                      },
+	{     "RESIZE",           map_resize,          0,              1, "Resize the map room vnum range"       },
+	{     "RETURN",           map_return,          MAP_FLAG_VTMAP, 1, "Return to last known room"            },
+	{     "ROOMFLAG",         map_roomflag,        MAP_FLAG_VTMAP, 2, "Change the room's flags"              },
+	{     "RUN",              map_run,             MAP_FLAG_VTMAP, 2, "Save found path to #path and run it"  },
+	{     "SET",              map_set,             MAP_FLAG_VTMAP, 2, "Set various room values"              },
+	{     "SYNC",             map_sync,            MAP_FLAG_VTMAP, 0, "Read a map file without overwriting"  },
+	{     "TERRAIN",          map_terrain,         MAP_FLAG_VTMAP, 1, "Create a terrain type"                },
+	{     "TRAVEL",           map_travel,          MAP_FLAG_VTMAP, 2, "Save explored path to #path and run it" },
+	{     "UNDO",             map_undo,            MAP_FLAG_VTMAP, 2, "Undo last map action"                 },
+	{     "UNINSERT",         map_uninsert,        MAP_FLAG_VTMAP, 2, "Uninsert room in given direction"     },
+	{     "UNLANDMARK",       map_unlandmark,      0,              1, "Remove a landmark"                    },
+	{     "UNLINK",           map_unlink,          MAP_FLAG_VTMAP, 2, "Remove given exit"                    },
+	{     "UNTERRAIN",        map_unterrain,       0,              1, "Remove a terrain type"                },
+	{     "UPDATE",           map_update,          0,              0, "Mark vt map for an auto update"       },
+	{     "VNUM",             map_vnum,            MAP_FLAG_VTMAP, 2, "Change the room vnum to given vnum"   },
+	{     "WRITE",            map_write,           0,              1, "Save the map to given file"           },
+	{     "",                 NULL,                0,              0, ""                                     }
+};
+
+
+struct cursor_type cursor_table[] =
+{
+/*
+	{
+		"AUTO TAB BACKWARD",
+		"Tab completion from scrollback buffer, backward",
+		"",
+		CURSOR_FLAG_GET_ALL,
+		cursor_auto_tab_backward
+	},
+	{
+		"AUTO TAB FORWARD",
+		"Tab completion from scrollback buffer, forward",
+		"",
+		CURSOR_FLAG_GET_ALL,
+		cursor_auto_tab_forward
+	},
+*/
+	{
+		"BACKSPACE",
+		"Delete backward character",
+		"",
+		CURSOR_FLAG_GET_ONE,
+		cursor_backspace
+	},
+
+	{
+		"BRACE OPEN",
+		"Insert the { character",
+		"",
+		CURSOR_FLAG_GET_ALL,
+		cursor_brace_open
+	},
+
+	{
+		"BRACE CLOSE",
+		"Insert the } character",
+		"",
+		CURSOR_FLAG_GET_ALL,
+		cursor_brace_close
+	},
+
+	{
+		"BACKWARD",
+		"Move cursor backward",
+		"",
+		CURSOR_FLAG_GET_ALL,
+		cursor_left
+	},
+	{
+		"CLEAR",
+		"Delete the input line",
+		"",
+		CURSOR_FLAG_GET_ALL,
+		cursor_clear_line
+	},
+	{
+		"CLEAR LEFT",
+		"Delete from cursor to start of input",
+		"",
+		CURSOR_FLAG_GET_ALL,
+		cursor_clear_left
+	},
+	{
+		"CLEAR LINE", /* obsolete */
+		"Delete the input line",
+		"",
+		CURSOR_FLAG_GET_ALL,
+		cursor_clear_line
+	},
+	{
+		"CLEAR RIGHT",
+		"Delete from cursor to end of input",
+		"",
+		CURSOR_FLAG_GET_ALL,
+		cursor_clear_right
+	},
+	{
+		"CONVERT META",
+		"Meta convert the next character",
+		"",
+		CURSOR_FLAG_GET_ALL,
+		cursor_convert_meta
+	},
+	{
+		"CTRL DELETE",
+		"Delete one character, exit on an empty line",
+		"",
+		CURSOR_FLAG_GET_ALL,
+		cursor_delete_or_exit
+	},
+	{
+		"DELETE",
+		"Delete character at cursor",
+		"\e[3~",
+		CURSOR_FLAG_GET_ALL,
+		cursor_delete
+	},
+	{
+		"DELETE WORD LEFT",
+		"Delete backwards till next space",
+		"",
+		CURSOR_FLAG_GET_ALL,
+		cursor_delete_word_left
+	},
+	{
+		"DELETE WORD RIGHT",
+		"Delete forwards till next space",
+		"",
+		CURSOR_FLAG_GET_ALL,
+		cursor_delete_word_right
+	},
+	{
+		"ECHO",
+		"Turn local echoing on or off",
+		"",
+		CURSOR_FLAG_GET_ONE,
+		cursor_echo
+	},
+	{
+		"END",
+		"Move cursor to end of input",
+		"",
+		CURSOR_FLAG_GET_ALL,
+		cursor_end
+	},
+	{
+		"ENTER",
+		"Process the input line",
+		"",
+		CURSOR_FLAG_GET_ALL,
+		cursor_enter
+	},
+	{
+		"EXIT",
+		"Exit current session",
+		"",
+		CURSOR_FLAG_GET_ALL,
+		cursor_exit
+	},
+	{
+		"FORWARD",
+		"Move cursor forward",
+		"",
+		CURSOR_FLAG_GET_ALL,
+		cursor_right
+	},
+	{
+		"GET",
+		"Copy input line to given variable",
+		"",
+		CURSOR_FLAG_GET_ONE,
+		cursor_get
+	},
+	{
+		"HISTORY NEXT",
+		"Select next command history entry",
+		"",
+		CURSOR_FLAG_GET_ALL,
+		cursor_history_next
+	},
+	{
+		"HISTORY PREV",
+		"Select previous command history entry",
+		"",
+		CURSOR_FLAG_GET_ALL,
+		cursor_history_prev
+	},
+	{
+		"HISTORY SEARCH",
+		"Search command history",
+		"",
+		CURSOR_FLAG_GET_ALL,
+		cursor_history_search
+	},
+	{
+		"HOME",
+		"Move the cursor to start of input",
+		"",
+		CURSOR_FLAG_GET_ALL,
+		cursor_home
+	},
+	{
+		"INFO",
+		"Print debugging information",
+		"",
+		CURSOR_FLAG_GET_ALL,
+		cursor_info
+	},
+	{
+		"INSERT",
+		"Turn insert mode on or off",
+		"",
+		CURSOR_FLAG_GET_ONE,
+		cursor_insert
+	},
+/*
+	{
+		"MIXED TAB BACKWARD",
+		"Tab completion on last word, search backward",
+		"\e[Z", // shift-tab
+		CURSOR_FLAG_GET_ALL,
+		cursor_mixed_tab_backward
+	},
+	{
+		"MIXED TAB FORWARD",
+		"Tab completion on last word, search forward",
+		"\t",
+		CURSOR_FLAG_GET_ALL,
+		cursor_mixed_tab_forward
+	},
+*/
+	{
+		"NEXT WORD",
+		"Move cursor to the next word",
+		"\ef",
+		CURSOR_FLAG_GET_ALL,
+		cursor_right_word
+	},
+	{
+		"PASTE BUFFER",
+		"Paste the previously deleted input text",
+		"",
+		CURSOR_FLAG_GET_ALL,
+		cursor_paste_buffer
+	},
+	{
+		"PREV WORD",
+		"Move cursor to the previous word",
+		"\eb",
+		CURSOR_FLAG_GET_ALL,
+		cursor_left_word
+	},
+	{
+		"REDRAW INPUT",
+		"Redraw the input line",
+		"",
+		CURSOR_FLAG_GET_ALL,
+		cursor_redraw_input
+	},
+	{
+		"SCREEN FOCUS IN",
+		"Window is focussed in event",
+		"\e[I",
+		CURSOR_FLAG_GET_ALL|CURSOR_FLAG_ALWAYS,
+		cursor_screen_focus_in
+	},
+	{
+		"SCREEN FOCUS OUT",
+		"Window is focussed out event",
+		"\e[O",
+		CURSOR_FLAG_GET_ALL|CURSOR_FLAG_ALWAYS,
+		cursor_screen_focus_out
+	},
+	{
+		"SET",
+		"Copy given string to input line",
+		"",
+		CURSOR_FLAG_GET_ONE,
+		cursor_set
+	},
+	{
+		"SUSPEND",
+		"Suspend program, return with fg",
+		"",
+		CURSOR_FLAG_GET_ALL,
+		cursor_suspend
+	},
+	{
+		"TAB",
+		"<LIST|SCROLLBACK> <BACKWARD|FORWARD>",
+		"",
+		CURSOR_FLAG_GET_ONE,
+		cursor_tab
+	},
+	{
+		"TAB L S BACKWARD",
+		"Tab completion on last word, search backward",
+		"\e[Z", // shift-tab
+		CURSOR_FLAG_GET_ALL,
+		cursor_mixed_tab_backward
+	},
+	{
+		"TAB L S FORWARD",
+		"Tab completion on last word, search forward",
+		"\t",
+		CURSOR_FLAG_GET_ALL,
+		cursor_mixed_tab_forward
+	},
+/*
+	{
+		"TAB BACKWARD",
+		"Tab completion from tab list, backward",
+		"",
+		CURSOR_FLAG_GET_ALL,
+		cursor_tab_backward
+	},
+	{
+		"TAB FORWARD",
+		"Tab completion from tab list, forward",
+		"",
+		CURSOR_FLAG_GET_ALL,
+		cursor_tab_forward
+	},
+*/
+	{
+		"", "", "\e[5~",   0, cursor_buffer_up
+	},
+	{
+		"", "", "\e[6~",   0, cursor_buffer_down
+	},
+	{
+		"", "", "",       0, cursor_buffer_lock
+	},
+	{
+		"","", "\e[13;2u", 0, cursor_enter
+	},
+	{
+		"", "", "\eOM",    0, cursor_enter
+	},
+	{
+		"", "", "\e[7~",   0, cursor_home
+	},
+	{
+		"", "", "\e[1~",   0, cursor_home
+	},
+	{
+		"", "", "\eOH",    0, cursor_home
+	},
+	{
+		"", "", "\e[H",    0, cursor_home
+	},
+	{
+		"", "", "\eOD",    0, cursor_left
+	},
+	{
+		"", "", "\e[D",    0, cursor_left
+	},
+	{
+		"", "", "\e[8~",   0, cursor_end
+	},
+	{
+		"", "", "\e[4~",   0, cursor_end
+	},
+	{
+		"", "", "\eOF",    0, cursor_end
+	},
+	{
+		"", "", "\e[F",    0, cursor_end
+	},
+	{
+		"", "", "\eOC",    0, cursor_right
+	},
+	{
+		"", "", "\e[C",    0, cursor_right
+	},
+	{
+		"", "", "\x7F",    0, cursor_backspace
+	},
+	{
+		"", "", "\eOB",    0, cursor_history_next
+	},
+	{
+		"", "", "\e[B",    0, cursor_history_next
+	},
+	{
+		"", "", "\eOA",    0, cursor_history_prev
+	},
+	{
+		"", "", "\e[A",    0, cursor_history_prev
+	},
+	{
+		"", "", "\e\x7F",  0, cursor_delete_word_left
+	},
+	{
+		"", "", "\ed",     0, cursor_delete_word_right
+	},
+	{
+		"", "", "",        0, NULL
+	}
+};
+
+struct draw_type draw_table[] =
+{
+	{
+		"BOX",
+		"Draw four sides of a box.",
+		DRAW_FLAG_BOXED|DRAW_FLAG_LEFT|DRAW_FLAG_RIGHT|DRAW_FLAG_TOP|DRAW_FLAG_BOT,
+		draw_box
+	},
+
+	{
+		"CORNER",
+		"Draw a corner",
+		DRAW_FLAG_CORNERED,
+		draw_corner
+	},
+
+	{
+		"LINE",
+		"Draw a line.",
+		DRAW_FLAG_NONE,
+		draw_line
+	},
+
+	{
+		"RAIN",
+		"Draw digital rain.",
+		DRAW_FLAG_NONE,
+		draw_rain
+	},
+
+	{
+		"SIDE",
+		"Draw a line with corners.",
+		DRAW_FLAG_BOXED,
+		draw_side
+	},
+
+	{
+		"TABLE",
+		"Draw a table.",
+		DRAW_FLAG_BOXED|DRAW_FLAG_LEFT|DRAW_FLAG_RIGHT|DRAW_FLAG_TOP|DRAW_FLAG_BOT,
+		draw_table_grid
+	},
+
+	{
+		"TILE",
+		"Draw a tile.",
+		0,
+		draw_square
+	},
+
+	{
+		"",
+		"",
+		0,
+		NULL
+	}
+};
+
+struct screen_type screen_table[] =
+{
+	{
+		"BLUR",
+		"Shuffle the screen to the back of the desktop.",
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_CSIP,
+		screen_blur
+	},
+	{
+		"CLEAR",
+		"Clear the screen.",
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_GET_NONE,
+		SCREEN_FLAG_CSIP,
+		screen_clear
+	},
+	{
+		"CURSOR",
+		"Cursor settings.",
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_CSIP,
+		screen_cursor
+	},
+
+	{
+		"FILL",
+		"Fill given region with given character.",
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_CSIP,
+		screen_fill
+	},
+	{
+		"FOCUS",
+		"Shuffle the screen to the front of the desktop.",
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_CSIP,
+		screen_focus
+	},
+	{
+		"FULLSCREEN",
+		"Toggle fullscreen mode.",
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_CSIP,
+		screen_fullscreen
+	},
+	{
+		"GET",
+		"Save screen information to given variable.",
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_CSIP,
+		screen_get
+	},
+	{
+		"INFO",
+		"Show some debugging information.",
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_CSIP,
+		screen_info
+	},
+	{
+		"LOAD",
+		"Load screen information from memory.",
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_OSCT,
+		screen_load
+	},
+	{
+		"MAXIMIZE",
+		"Maximize or restore the screen.",
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_CSIP,
+		screen_maximize
+	},
+
+	{
+		"MINIMIZE",
+		"Minimize or restore the screen.",
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_CSIP,
+		screen_minimize
+	},
+	{
+		"MOVE",
+		"Move the screen to the given position.",
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_CSIP,
+		screen_move
+	},
+	{
+		"RAISE",
+		"Raise a screen event.",
+		SCREEN_FLAG_GET_ALL,
+		SCREEN_FLAG_GET_ALL,
+		SCREEN_FLAG_CSIP,
+		screen_raise
+	},
+	{
+		"REFRESH",
+		"Force a refresh of the screen.",
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_CSIP,
+		screen_refresh
+	},
+	{
+		"RESCALE",
+		"Rescale the screen to {height} {width} pixels.",
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_CSIP,
+		screen_rescale
+	},
+	{
+		"RESIZE",
+		"Resize the screen to {rows} {cols} characters.",
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_CSIP,
+		screen_resize
+	},
+	{
+		"SAVE",
+		"Save screen information to memory.",
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_CSIP,
+		screen_save
+	},
+
+	{
+		"SCROLL",
+		"Set the scroll region to {square}.",
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_CSIP,
+		screen_scrollregion
+	},
+
+	{
+		"SCROLLBAR",
+		"Scrollbar settings.",
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_CSIP,
+		screen_scrollbar
+	},
+
+
+	{
+		"SET",
+		"Set screen information.",
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_GET_ONE,
+		SCREEN_FLAG_OSCT,
+		screen_set
+	},
+	{
+		"", "", 0, 0, 0, NULL
+	}
+};
+
+
+struct timer_type timer_table[] =
+{
+	{    "Update Input"                },
+	{    "Update Sessions"             },
+	{    "Update Delays"               },
+	{    "Update Chat"                 },
+	{    "Update Port"                 },
+	{    "Update Tickers"              },
+	{    "Update Paths"                },
+	{    "Update Packet Patcher"       },
+	{    "Update Terminal"             },
+	{    "Update Time Events"          },
+	{    "Update Memory"               },
+	{    "Stall Program"               }
+};
+
+struct event_type event_table[] =
+{
+	{    "CATCH ",                                 EVENT_FLAG_CATCH,    "Triggers on catch events."               },
+	{    "CHAT MESSAGE",                           EVENT_FLAG_PORT,     "Triggers on any chat related message."   },
+	{    "CLASS ACTIVATED",                        EVENT_FLAG_CLASS,    "Triggers on class activations."          },
+	{    "CLASS CREATED",                          EVENT_FLAG_CLASS,    "Triggers on class creation."             },
+	{    "CLASS DEACTIVATED",                      EVENT_FLAG_CLASS,    "Triggers on class deactivations."        },
+	{    "CLASS DESTROYED",                        EVENT_FLAG_CLASS,    "Triggers on class destruction."          },
+	{    "DATE",                                   EVENT_FLAG_TIME,     "Triggers on the given date."             },
+	{    "DAY",                                    EVENT_FLAG_TIME,     "Triggers each day or given day."         },
+	{    "DOUBLE-CLICKED ",                        EVENT_FLAG_MOUSE,    "Triggers when mouse is double-clicked"   },
+	{    "END OF PATH",                            EVENT_FLAG_MAP,      "Triggers when walking the last room."    },
+	{    "HOUR",                                   EVENT_FLAG_TIME,     "Triggers each hour or given hour."       },
+	{    "IAC ",                                   EVENT_FLAG_TELNET,   "Triggers on telopt negotiation."         },
+	{    "LONG-CLICKED ",                          EVENT_FLAG_MOUSE,    "Triggers when mouse is long-clicked."    },
+	{    "MAP DOUBLE-CLICKED ",                    EVENT_FLAG_MOUSE,    "Triggers on vt map click."               },
+	{    "MAP ENTER MAP",                          EVENT_FLAG_MAP,      "Triggers when entering the map."         },
+	{    "MAP ENTER ROOM",                         EVENT_FLAG_MAP,      "Triggers when entering a map room."      },
+	{    "MAP EXIT MAP",                           EVENT_FLAG_MAP,      "Triggers when exiting the map."          },
+	{    "MAP EXIT ROOM",                          EVENT_FLAG_MAP,      "Triggers when exiting a map room."       },
+	{    "MAP FOLLOW MAP",                         EVENT_FLAG_MAP,      "Triggers when moving to a map room."     },
+	{    "MAP LOCATION",                           EVENT_FLAG_MOUSE,    "Triggers on vt map click."               },
+	{    "MAP LONG-CLICKED ",                      EVENT_FLAG_MOUSE,    "Triggers on vt map click."               },
+	{    "MAP MOUSE LOCATION",                     EVENT_FLAG_MOUSE,    "Triggers when called by #screen raise."  }, 
+	{    "MAP PRESSED ",                           EVENT_FLAG_MOUSE,    "Triggers on vt map click."               },
+	{    "MAP RELEASED ",                          EVENT_FLAG_MOUSE,    "Triggers on vt map click."               },
+	{    "MAP SHORT-CLICKED ",                     EVENT_FLAG_MOUSE,    "Triggers on vt map click."               },
+	{    "MAP TRIPLE-CLICKED ",                    EVENT_FLAG_MOUSE,    "Triggers on vt map click."               },
+	{    "MAP UPDATED VTMAP",                      EVENT_FLAG_MAP,      "Triggers on vt map update."              },
+	{    "MINUTE",                                 EVENT_FLAG_TIME,     "Triggers each minute or given minute."   },
+	{    "MONTH",                                  EVENT_FLAG_TIME,     "Triggers each month or given month."     },
+	{    "MOVED ",                                 EVENT_FLAG_MOUSE,    "Triggers when mouse is moved."           },
+	{    "PORT CONNECTION",                        EVENT_FLAG_PORT,     "Triggers when socket connects."          },
+	{    "PORT DISCONNECTION",                     EVENT_FLAG_PORT,     "Triggers when socket disconnects."       },
+	{    "PORT LOG MESSAGE",                       EVENT_FLAG_PORT,     "Triggers on local port log messages."    },
+	{    "PORT MESSAGE",                           EVENT_FLAG_PORT,     "Triggers on local port messages."        },
+	{    "PORT RECEIVED MESSAGE",                  EVENT_FLAG_PORT,     "Triggers when socket data is received."  },
+	{    "PRESSED ",                               EVENT_FLAG_MOUSE,    "Triggers when mouse button is pressed."  },
+	{    "PROGRAM START",                          EVENT_FLAG_SYSTEM,   "Triggers when main session starts."      },
+	{    "PROGRAM TERMINATION",                    EVENT_FLAG_SYSTEM,   "Triggers when main session exists."      },
+	{    "READ ERROR",                             EVENT_FLAG_SYSTEM,   "Triggers when the read command fails."   },
+	{    "RECEIVED INPUT",                         EVENT_FLAG_INPUT,    "Triggers when new input is received."    },
+	{    "RECEIVED KEYPRESS",                      EVENT_FLAG_INPUT,    "Triggers when a keypress is received."   },
+	{    "RECEIVED LINE",                          EVENT_FLAG_OUTPUT,   "Triggers when a new line is received."   },
+	{    "RECEIVED OUTPUT",                        EVENT_FLAG_OUTPUT,   "Triggers when new output is received."   },
+	{    "RECEIVED PROMPT",                        EVENT_FLAG_OUTPUT,   "Triggers when a prompt is received."     },
+	{    "RELEASED ",                              EVENT_FLAG_MOUSE,    "Triggers when mouse button is released." },
+	{    "SCAN CSV HEADER",                        EVENT_FLAG_SCAN,     "Triggers when scanning a csv file."      },
+	{    "SCAN CSV LINE",                          EVENT_FLAG_SCAN,     "Triggers when scanning a csv file."      },
+	{    "SCAN TSV HEADER",                        EVENT_FLAG_SCAN,     "Triggers when scanning a tsv file."      },
+	{    "SCAN TSV LINE",                          EVENT_FLAG_SCAN,     "Triggers when scanning a tsv file."      },
+	{    "SCREEN DESKTOP DIMENSIONS",              EVENT_FLAG_SCREEN,   "Triggers when called by #screen raise."  },
+	{    "SCREEN DESKTOP SIZE",                    EVENT_FLAG_SCREEN,   "Triggers when called by #screen raise."  },
+	{    "SCREEN DIMENSIONS",                      EVENT_FLAG_SCREEN,   "Triggers when called by #screen raise."  },
+	{    "SCREEN FOCUS",                           EVENT_FLAG_SCREEN,   "Triggers when focus changes.",           },
+	{    "SCREEN LOCATION",                        EVENT_FLAG_SCREEN,   "Triggers when called by #screen raise."  },
+	{    "SCREEN MINIMIZED",                       EVENT_FLAG_SCREEN,   "Triggers when called by #screen raise."  },
+	{    "SCREEN MOUSE LOCATION",                  EVENT_FLAG_MOUSE,    "Triggers when called by #screen raise."  },
+	{    "SCREEN REFRESH",                         EVENT_FLAG_SCREEN,   "Triggers when the screen is refreshed."  },
+	{    "SCREEN RESIZE",                          EVENT_FLAG_SCREEN,   "Triggers when the screen is resized."    },
+	{    "SCREEN ROTATE LANDSCAPE",                EVENT_FLAG_SCREEN,   "Triggers when the screen is rotated."    },
+	{    "SCREEN ROTATE PORTRAIT",                 EVENT_FLAG_SCREEN,   "Triggers when the screen is rotated."    },
+	{    "SCREEN SIZE",                            EVENT_FLAG_SCREEN,   "Triggers when called by #screen raise."  },
+	{    "SCREEN SPLIT",                           EVENT_FLAG_SCREEN,   "Triggers when the screen is split."      },
+	{    "SCREEN UNSPLIT",                         EVENT_FLAG_SCREEN,   "Triggers when the screen is unsplit."    },
+	{    "SCROLLED ",                              EVENT_FLAG_MOUSE,    "Triggers when mouse wheel is scrolled."  },
+	{    "SECOND",                                 EVENT_FLAG_TIME,     "Triggers each second or given second."   },
+	{    "SEND OUTPUT",                            EVENT_FLAG_INPUT,    "Triggers before sending output."         },
+	{    "SENT OUTPUT",                            EVENT_FLAG_INPUT,    "Triggers after sending output."          },
+	{    "SESSION ACTIVATED",                      EVENT_FLAG_SESSION,  "Triggers when a session is activated."   },
+	{    "SESSION CONNECTED",                      EVENT_FLAG_SESSION,  "Triggers when a new session connects."   },
+	{    "SESSION CREATED",                        EVENT_FLAG_SESSION,  "Triggers when a new session is created." },
+	{    "SESSION DEACTIVATED",                    EVENT_FLAG_SESSION,  "Triggers when a session is deactivated." },
+	{    "SESSION DISCONNECTED",                   EVENT_FLAG_SESSION,  "Triggers when a session disconnects."    },
+	{    "SESSION TIMED OUT",                      EVENT_FLAG_SESSION,  "Triggers when a session doesn't connect."},
+	{    "SHORT-CLICKED",                          EVENT_FLAG_MOUSE,    "Triggers when mouse is short-clicked."   },
+	{    "SWIPED",                                 EVENT_FLAG_MOUSE,    "Triggers on mouse swipe."},
+	{    "SYSTEM ERROR",                           EVENT_FLAG_SYSTEM,   "Triggers on system errors."              },
+	{    "TIME",                                   EVENT_FLAG_TIME,     "Triggers on the given time."             },
+	{    "TRIPLE-CLICKED",                         EVENT_FLAG_MOUSE,    "Triggers when mouse is triple-clicked."  },
+	{    "UNKNOWN COMMAND",                        EVENT_FLAG_SYSTEM,   "Triggers on unknown tintin command."     },
+	{    "VARIABLE UPDATE ",                       EVENT_FLAG_SYSTEM,   "Triggers before a variable updates."     },
+	{    "VARIABLE UPDATED ",                      EVENT_FLAG_SYSTEM,   "Triggers after a variable update."       },
+	{    "VT100 CPR",                              EVENT_FLAG_VT100,    "Triggers on an ESC [ 6 n call."          },
+	{    "VT100 DA",                               EVENT_FLAG_VT100,    "Triggers on an ESC [ c call."            },
+	{    "VT100 DECID",                            EVENT_FLAG_VT100,    "Triggers on an ESC Z call."              },
+	{    "VT100 DSR",                              EVENT_FLAG_VT100,    "Triggers on an ESC [ 5 n call."          },
+	{    "VT100 ENQ",                              EVENT_FLAG_VT100,    "Triggers on an \\x05 call."              },
+	{    "VT100 SCROLL REGION",                    EVENT_FLAG_VT100,    "Triggers on vt100 scroll region change." },
+	{    "WEEK",                                   EVENT_FLAG_TIME,     "Triggers each week or given week."       },
+	{    "WRITE ERROR",                            EVENT_FLAG_SYSTEM,   "Triggers when the write command fails."  },
+	{    "YEAR",                                   EVENT_FLAG_TIME,     "Triggers each year or given year."       },
+	{    "",                                       0,                   ""                                        }
+};
+
+struct path_type path_table[] =
+{
+	{    "CREATE",            path_create,         "Clear the path and start path mapping."         },
+	{    "DELETE",            path_delete,         "Delete the last command from the path."         },
+	{    "DESCRIBE",          path_describe,       "Describe the path and current position."        },
+	{    "DESTROY",           path_destroy,        "Clear the path and stop path mapping."          },
+	{    "END",               path_end,            ""                                               },
+	{    "GOTO",              path_goto,           "Move position to given index."                  },
+	{    "INSERT",            path_insert,         "Insert a command to the end of the path."       },
+	{    "LOAD",              path_load,           "Load a path from a variable."                   },
+	{    "MAP",               path_map,            "Display the path and current position."         },
+	{    "MOVE",              path_move,           "Move one position forward or backward."         },
+	{    "NEW",               path_new,            ""                                               },
+	{    "RUN",               path_run,            "Execute the current path with optional delay."  },
+	{    "SAVE",              path_save,           "Save the current path to the given variable."   },
+	{    "SHOW",              path_map,            ""                                               },
+	{    "START",             path_start,          "Start path mapping."                            },
+	{    "STOP",              path_stop,           "Stop path mapping."                             },
+	{    "SWAP",              path_swap,           "Reverse the path, forward becoming backward."   },
+	{    "UNDO",              path_undo,           "Undo last step."                                },
+	{    "UNZIP",             path_unzip,          "Turn speedwalk into a path."                    },
+	{    "WALK",              path_walk,           "Walk one step forward or backward."             },
+	{    "ZIP",               path_zip,            "Turn path into a speedwalk."                    },
+	{    "",                  NULL,                ""                                               }
+};
+
+struct line_type line_table[] =
+{
+	{    "BACKGROUND",        line_background,     "Execute line without stealing session focus."   },
+	{    "BENCHMARK",         line_benchmark,      "Execute line and provide timing information."   },
+	{    "CAPTURE",           line_capture,        "Capture output in the given variable."          },
+	{    "DEBUG",             line_debug,          "Execute line in debug mode."                    },
+	{    "GAG",               line_gag,            "Gag the next line."                             },
+	{    "IGNORE",            line_ignore,         "Execute line with triggers ignored."            },
+	{    "LOG",               line_log,            "Log the next line or given line."               },
+	{    "LOGMODE",           line_logmode,        "Execute line with given log mode."              },
+	{    "LOGVERBATIM",       line_logverbatim,    "Log the line as plain text verbatim."           },
+	{    "ONESHOT",           line_oneshot,        "Execute line creating oneshot triggers."        },
+	{    "QUIET",             line_quiet,          "Execute line with all system messages off."     },
+	{    "STRIP",             line_strip,          "Execute line with escape codes stripped."       },
+	{    "SUBSTITUTE",        line_substitute,     "Execute line with given substitution."          },
+	{    "VERBATIM",          line_verbatim,       "Execute line as plain text."                    },
+	{    "VERBOSE",           line_verbose,        "Execute line with all system messages on."      },
+	{    "",                  NULL,                ""                                               }
+};
+
+struct history_type history_table[] =
+{
+/*	{    "CHARACTER",         history_character,   "Set the character used for repeating commands." }, */
+	{    "DELETE",            history_delete,      "Delete last command history entry."             },
+	{    "INSERT",            history_insert,      "Insert a new command history entry."            },
+	{    "LIST",              history_list,        "Display command history list."                  },
+	{    "READ",              history_read,        "Read a command history list from file."         },
+/*	{    "SIZE",              history_size,        "The size of the command history."               }, */
+	{    "WRITE",             history_write,       "Write a command history list to file."          },
+	{    "",                  NULL,                ""                                               }
+};
+
+struct buffer_type buffer_table[] =
+{
+	{    "CLEAR",             buffer_clear,        "Clear buffer."                                  },
+	{    "DOWN",              buffer_down,         "Scroll down one page."                          },
+	{    "END",               buffer_end,          "Scroll down to the end of the buffer."          },
+	{    "FIND",              buffer_find,         "Move to the given string in the buffer."        },
+	{    "GET",               buffer_get,          "Store in given variable a given line or range." },
+	{    "HOME",              buffer_home,         "Scroll up to the start of the buffer."          },
+	{    "INFO",              buffer_info,         "Display statistics about the buffer."           },
+	{    "LOCK",              buffer_lock,         "Toggle the locking state of the buffer."        },
+	{    "UP",                buffer_up,           "Scroll up one page."                            },
+	{    "WRITE",             buffer_write,        "Write the buffer to file."                      },
+	{    "",                  NULL,                ""                                               }
+};
+
+char *telcmds[] =
+{
+        "EOF",    "SUSP",   "ABORT",  "EOR",    "SE",
+        "NOP",    "DMARK",  "BRK",    "IP",     "AO",
+        "AYT",    "EC",     "EL",     "GA",     "SB",
+        "WILL",   "WONT",   "DO",     "DONT",   "IAC",
+};
+
+struct telopt_type telopt_table[] =
+{
+	{    "BINARY",            TEL_N,               0 },
+	{    "ECHO",              TEL_Y,               0 },
+	{    "RCP",               TEL_N,               0 },
+	{    "SGA",               TEL_Y,               0 },
+	{    "NAME",              TEL_N,               0 },
+	{    "STATUS",            TEL_N,               0 },
+	{    "TIMING MARK",       TEL_N,               0 },
+	{    "RCTE",              TEL_N,               0 },
+	{    "NAOL",              TEL_N,               0 },
+	{    "NAOP",              TEL_N,               0 },
+	{    "NAORCD",            TEL_N,               0 }, /* 10 */
+	{    "NAOHTS",            TEL_N,               0 },
+	{    "NAOHTD",            TEL_N,               0 },
+	{    "NAOFFD",            TEL_N,               0 },
+	{    "NAOVTS",            TEL_N,               0 },
+	{    "NAOVTD",            TEL_N,               0 },
+	{    "NAOLFD",            TEL_N,               0 },
+	{    "EXTEND ASCII",      TEL_N,               0 },
+	{    "LOGOUT",            TEL_N,               0 },
+	{    "BYTE MACRO",        TEL_N,               0 },
+	{    "DATA ENTRY TERML",  TEL_N,               0 }, /* 20 */
+	{    "SUPDUP",            TEL_N,               0 },
+	{    "SUPDUP OUTPUT",     TEL_N,               0 },
+	{    "SEND LOCATION",     TEL_N,               0 },
+	{    "TERMINAL TYPE",     TEL_Y,               ANNOUNCE_DO },
+	{    "EOR",               TEL_Y,               0 },
+	{    "TACACS UID",        TEL_N,               0 },
+	{    "OUTPUT MARKING",    TEL_N,               0 },
+	{    "TTYLOC",            TEL_N,               0 },
+	{    "3270 REGIME",       TEL_N,               0 },
+	{    "X.3 PAD",           TEL_N,               0 }, /* 30 */
+	{    "NAWS",              TEL_Y,               ANNOUNCE_DO },
+	{    "TSPEED",            TEL_Y,               0 },
+	{    "LFLOW",             TEL_N,               0 },
+	{    "LINEMODE",          TEL_N,               0 },
+	{    "XDISPLOC",          TEL_N,               0 },
+	{    "OLD-ENVIRON",       TEL_N,               0 },
+	{    "AUTH",              TEL_N,               0 },
+	{    "ENCRYPT",           TEL_N,               0 },
+	{    "NEW-ENVIRON",       TEL_Y,               ANNOUNCE_DO },
+	{    "TN3270E",           TEL_N,               0 }, /* 40 */
+	{    "XAUTH",             TEL_N,               0 },
+	{    "CHARSET",           TEL_Y,               ANNOUNCE_WILL },
+	{    "RSP",               TEL_N,               0 },
+	{    "COM PORT",          TEL_N,               0 },
+	{    "SLE",               TEL_N,               0 },
+	{    "STARTTLS",          TEL_N,               0 },
+	{    "KERMIT",            TEL_N,               0 },
+	{    "SEND-URL",          TEL_N,               0 },
+	{    "FORWARD_X",         TEL_N,               0 },
+	{    "50",                TEL_N,               0 }, /* 50 */
+	{    "51",                TEL_N,               0 },
+	{    "52",                TEL_N,               0 },
+	{    "53",                TEL_N,               0 },
+	{    "54",                TEL_N,               0 },
+	{    "55",                TEL_N,               0 },
+	{    "56",                TEL_N,               0 },
+	{    "57",                TEL_N,               0 },
+	{    "58",                TEL_N,               0 },
+	{    "59",                TEL_N,               0 },
+	{    "60",                TEL_N,               0 }, /* 60 */
+	{    "61",                TEL_N,               0 },
+	{    "62",                TEL_N,               0 },
+	{    "63",                TEL_N,               0 },
+	{    "64",                TEL_N,               0 },
+	{    "65",                TEL_N,               0 },
+	{    "66",                TEL_N,               0 },
+	{    "67",                TEL_N,               0 },
+	{    "68",                TEL_N,               0 },
+	{    "MSDP",              TEL_N,               ANNOUNCE_WILL }, /* Mud Server Data Protocol */
+	{    "MSSP",              TEL_N,               ANNOUNCE_WILL }, /* Mud Server Status Protocol */
+	{    "71",                TEL_N,               0 },
+	{    "72",                TEL_N,               0 },
+	{    "73",                TEL_N,               0 },
+	{    "74",                TEL_N,               0 },
+	{    "75",                TEL_N,               0 },
+	{    "76",                TEL_N,               0 },
+	{    "77",                TEL_N,               0 },
+	{    "78",                TEL_N,               0 },
+	{    "79",                TEL_N,               0 },
+	{    "80",                TEL_N,               0 }, /* 80 */
+	{    "81",                TEL_N,               0 },
+	{    "82",                TEL_N,               0 },
+	{    "83",                TEL_N,               0 },
+	{    "84",                TEL_N,               0 },
+	{    "MCCP1",             TEL_N,               0 }, /* Obsolete */
+	{    "MCCP2",             TEL_Y,               ANNOUNCE_WILL }, /* Mud Client Compression Protocol v2 */
+	{    "MCCP3",             TEL_N,               ANNOUNCE_WILL }, /* Mud Client Compression Protocol v3 */
+	{    "88",                TEL_N,               0 },
+	{    "89",                TEL_N,               0 },
+	{    "MSP",               TEL_N,               0 }, /* Mud Sound Protocl */
+	{    "MXP",               TEL_N,               0 }, /* Mud eXtension Protocol */
+	{    "92",                TEL_N,               0 }, /* Unadopted - MSP2 draft */
+	{    "ZMP",               TEL_N,               0 }, /* Unadopted - Zenith Mud Protocl draft */
+	{    "94",                TEL_N,               0 },
+	{    "95",                TEL_N,               0 },
+	{    "96",                TEL_N,               0 },
+	{    "97",                TEL_N,               0 },
+	{    "98",                TEL_N,               0 },
+	{    "99",                TEL_N,               0 },
+	{    "100",               TEL_N,               0 },
+	{    "101",               TEL_N,               0 },
+	{    "102",               TEL_N,               0 }, /* Obsolete - Aardwolf */
+	{    "103",               TEL_N,               0 },
+	{    "104",               TEL_N,               0 },
+	{    "105",               TEL_N,               0 },
+	{    "106",               TEL_N,               0 },
+	{    "107",               TEL_N,               0 },
+	{    "108",               TEL_N,               0 },
+	{    "109",               TEL_N,               0 },
+	{    "110",               TEL_N,               0 },
+	{    "111",               TEL_N,               0 },
+	{    "112",               TEL_N,               0 },
+	{    "113",               TEL_N,               0 },
+	{    "114",               TEL_N,               0 },
+	{    "115",               TEL_N,               0 },
+	{    "116",               TEL_N,               0 },
+	{    "117",               TEL_N,               0 },
+	{    "118",               TEL_N,               0 },
+	{    "119",               TEL_N,               0 },
+	{    "120",               TEL_N,               0 },
+	{    "121",               TEL_N,               0 },
+	{    "122",               TEL_N,               0 },
+	{    "123",               TEL_N,               0 },
+	{    "124",               TEL_N,               0 },
+	{    "125",               TEL_N,               0 },
+	{    "126",               TEL_N,               0 },
+	{    "127",               TEL_N,               0 },
+	{    "128",               TEL_N,               0 },
+	{    "129",               TEL_N,               0 },
+	{    "130",               TEL_N,               0 },
+	{    "131",               TEL_N,               0 },
+	{    "132",               TEL_N,               0 },
+	{    "133",               TEL_N,               0 },
+	{    "134",               TEL_N,               0 },
+	{    "135",               TEL_N,               0 },
+	{    "136",               TEL_N,               0 },
+	{    "137",               TEL_N,               0 },
+	{    "138",               TEL_N,               0 },
+	{    "139",               TEL_N,               0 },
+	{    "140",               TEL_N,               0 },
+	{    "141",               TEL_N,               0 },
+	{    "142",               TEL_N,               0 },
+	{    "143",               TEL_N,               0 },
+	{    "144",               TEL_N,               0 },
+	{    "145",               TEL_N,               0 },
+	{    "146",               TEL_N,               0 },
+	{    "147",               TEL_N,               0 },
+	{    "148",               TEL_N,               0 },
+	{    "149",               TEL_N,               0 },
+	{    "150",               TEL_N,               0 },
+	{    "151",               TEL_N,               0 },
+	{    "152",               TEL_N,               0 },
+	{    "153",               TEL_N,               0 },
+	{    "154",               TEL_N,               0 },
+	{    "155",               TEL_N,               0 },
+	{    "156",               TEL_N,               0 },
+	{    "157",               TEL_N,               0 },
+	{    "158",               TEL_N,               0 },
+	{    "159",               TEL_N,               0 },
+	{    "160",               TEL_N,               0 },
+	{    "161",               TEL_N,               0 },
+	{    "162",               TEL_N,               0 },
+	{    "163",               TEL_N,               0 },
+	{    "164",               TEL_N,               0 },
+	{    "165",               TEL_N,               0 },
+	{    "166",               TEL_N,               0 },
+	{    "167",               TEL_N,               0 },
+	{    "168",               TEL_N,               0 },
+	{    "169",               TEL_N,               0 },
+	{    "170",               TEL_N,               0 },
+	{    "171",               TEL_N,               0 },
+	{    "172",               TEL_N,               0 },
+	{    "173",               TEL_N,               0 },
+	{    "174",               TEL_N,               0 },
+	{    "175",               TEL_N,               0 },
+	{    "176",               TEL_N,               0 },
+	{    "177",               TEL_N,               0 },
+	{    "178",               TEL_N,               0 },
+	{    "179",               TEL_N,               0 },
+	{    "180",               TEL_N,               0 },
+	{    "181",               TEL_N,               0 },
+	{    "182",               TEL_N,               0 },
+	{    "183",               TEL_N,               0 },
+	{    "184",               TEL_N,               0 },
+	{    "185",               TEL_N,               0 },
+	{    "186",               TEL_N,               0 },
+	{    "187",               TEL_N,               0 },
+	{    "188",               TEL_N,               0 },
+	{    "189",               TEL_N,               0 },
+	{    "190",               TEL_N,               0 },
+	{    "191",               TEL_N,               0 },
+	{    "192",               TEL_N,               0 },
+	{    "193",               TEL_N,               0 },
+	{    "194",               TEL_N,               0 },
+	{    "195",               TEL_N,               0 },
+	{    "196",               TEL_N,               0 },
+	{    "197",               TEL_N,               0 },
+	{    "198",               TEL_N,               0 },
+	{    "199",               TEL_N,               0 },
+	{    "ATCP",              TEL_N,               0 }, /* Obsolete - Achaea Telnet Communication Protocol */
+	{    "GMCP",              TEL_N,               ANNOUNCE_WILL }, /* MSDP over GMCP */
+	{    "202",               TEL_N,               0 },
+	{    "203",               TEL_N,               0 },
+	{    "204",               TEL_N,               0 },
+	{    "205",               TEL_N,               0 },
+	{    "206",               TEL_N,               0 },
+	{    "207",               TEL_N,               0 },
+	{    "208",               TEL_N,               0 },
+	{    "209",               TEL_N,               0 },
+	{    "210",               TEL_N,               0 },
+	{    "211",               TEL_N,               0 },
+	{    "212",               TEL_N,               0 },
+	{    "213",               TEL_N,               0 },
+	{    "214",               TEL_N,               0 },
+	{    "215",               TEL_N,               0 },
+	{    "216",               TEL_N,               0 },
+	{    "217",               TEL_N,               0 },
+	{    "218",               TEL_N,               0 },
+	{    "219",               TEL_N,               0 },
+	{    "220",               TEL_N,               0 },
+	{    "221",               TEL_N,               0 },
+	{    "222",               TEL_N,               0 },
+	{    "223",               TEL_N,               0 },
+	{    "224",               TEL_N,               0 },
+	{    "225",               TEL_N,               0 },
+	{    "226",               TEL_N,               0 },
+	{    "227",               TEL_N,               0 },
+	{    "228",               TEL_N,               0 },
+	{    "229",               TEL_N,               0 },
+	{    "230",               TEL_N,               0 },
+	{    "231",               TEL_N,               0 },
+	{    "232",               TEL_N,               0 },
+	{    "233",               TEL_N,               0 },
+	{    "234",               TEL_N,               0 },
+	{    "235",               TEL_N,               0 },
+	{    "236",               TEL_N,               0 },
+	{    "237",               TEL_N,               0 },
+	{    "238",               TEL_N,               0 },
+	{    "239",               TEL_N,               0 },
+	{    "240",               TEL_N,               0 },
+	{    "NOP",               TEL_N,               0 },
+	{    "242",               TEL_N,               0 },
+	{    "243",               TEL_N,               0 },
+	{    "244",               TEL_N,               0 },
+	{    "245",               TEL_N,               0 },
+	{    "246",               TEL_N,               0 },
+	{    "247",               TEL_N,               0 },
+	{    "248",               TEL_N,               0 },
+	{    "249",               TEL_N,               0 },
+	{    "250",               TEL_N,               0 },
+	{    "251",               TEL_N,               0 },
+	{    "252",               TEL_N,               0 },
+	{    "253",               TEL_N,               0 },
+	{    "254",               TEL_N,               0 },
+	{    "255",               TEL_N,               0 }
+};
+
+
+struct map_legend_type map_legend_table[] =
+{
+	{ "NO EXITS",		"ASCII NESW LINE",	"x",	"1x1"	},
+	{ "N",			"ASCII NESW LINE",	"o",	"1x1"	},
+	{ "  E",		"ASCII NESW LINE",	"o",	"1x1"	},
+	{ "N E",		"ASCII NESW LINE",	"+",	"1x1"	},
+	{ "    S",		"ASCII NESW LINE",	"o",	"1x1"	},
+	{ "N   S",		"ASCII NESW LINE",	"|",	"1x1"	},
+	{ "  E S",		"ASCII NESW LINE",	"+",	"1x1"	},
+	{ "N E S",		"ASCII NESW LINE",	"+",	"1x1"	},
+	{ "      W",		"ASCII NESW LINE",	"o",	"1x1"	},
+	{ "N     W",		"ASCII NESW LINE",	"+",	"1x1"	},
+	{ "  E   W",		"ASCII NESW LINE",	"-",	"1x1"	},
+	{ "N E   W",		"ASCII NESW LINE",	"+",	"1x1"	},
+	{ "    S W",		"ASCII NESW LINE",	"+",	"1x1"	},
+	{ "N   S W",		"ASCII NESW LINE",	"+",	"1x1"	},
+	{ "  E S W",		"ASCII NESW LINE",	"+",	"1x1"	},
+	{ "N E S W",		"ASCII NESW LINE",	"+",	"1x1"	},
+
+	{ "USER",		"ASCII NESW MISC",	"x",	"1x1"	},
+	{ "DIR UNKNOWN",	"ASCII NESW MISC",	"+",	"1x1"	},
+	{ "N S VOID",		"ASCII NESW MISC",	"|",	"1x1"	},
+	{ "E W VOID",		"ASCII NESW MISC",	"-",	"1x1"	},
+
+	{ "N E CURVED",		"ASCII NESW CURVED",	"+",	"1x1"	},
+	{ "S E CURVED",		"ASCII NESW CURVED",	"+",	"1x1"	},
+	{ "S W CURVED",		"ASCII NESW CURVED",	"+",	"1x1"	},
+	{ "N W CURVED",		"ASCII NESW CURVED",	"+",	"1x1"	},
+
+	{ "DIR N",		"ASCII NESW DIRS",	"|",	"1x1"	},
+	{ "DIR NE",		"ASCII NESW DIRS",	"/",	"1x1"	},
+	{ "DIR E",		"ASCII NESW DIRS",	"-",	"1x1"	},
+	{ "DIR SE",		"ASCII NESW DIRS",	"\\",	"1x1"	},
+	{ "DIR S",		"ASCII NESW DIRS",	"|",	"1x1"	},
+	{ "DIR SW",		"ASCII NESW DIRS",	"/",	"1x1"	},
+	{ "DIR W",		"ASCII NESW DIRS",	"-",	"1x1"	},
+	{ "DIR NW",		"ASCII NESW DIRS",	"\\",	"1x1"	},
+
+	{ "NO EXITS",		"NESW LINE",		"1x1",	"1x1"	},
+	{ "N",			"NESW LINE",		"1x1",	"1x1"	},
+	{ "  E",		"NESW LINE",		"1x1",	"1x1"	},
+	{ "N E",		"NESW LINE",		"1x1",	"1x1"	},
+	{ "    S",		"NESW LINE",		"1x1",	"1x1"	},
+	{ "N   S",		"NESW LINE",		"1x1",	"1x1"	},
+	{ "  E S",		"NESW LINE",		"1x1",	"1x1"	},
+	{ "N E S",		"NESW LINE",		"1x1",	"1x1"	},
+	{ "      W",		"NESW LINE",		"1x1",	"1x1"	},
+	{ "N     W",		"NESW LINE",		"1x1",	"1x1"	},
+	{ "  E   W",		"NESW LINE",		"1x1",	"1x1"	},
+	{ "N E   W",		"NESW LINE",		"1x1",	"1x1"	},
+	{ "    S W",		"NESW LINE",		"1x1",	"1x1"	},
+	{ "N   S W",		"NESW LINE",		"1x1",	"1x1"	},
+	{ "  E S W",		"NESW LINE",		"1x1",	"1x1"	},
+	{ "N E S W",		"NESW LINE",		"1x1",	"1x1"	},
+
+	{ "USER",		"NESW MISC",		"1x1",	"1x1"	},
+	{ "DIR UNKNOWN",	"NESW MISC",		"1x1",	"1x1"	},
+	{ "N S VOID",		"NESW MISC",		"1x1",	"1x1"	},
+	{ "E W VOID",		"NESW MISC",		"1x1",	"1x1"	},
+
+	{ "N E CURVED",		"NESW CURVED",		"1x1",	"1x1"	},
+	{ "S E CURVED",		"NESW CURVED",		"1x1",	"1x1"	},
+	{ "S W CURVED",		"NESW CURVED",		"1x1",	"1x1"	},
+	{ "N W CURVED",		"NESW CURVED",		"1x1",	"1x1"	},
+
+	{ "DIR N",		"NESW DIRS",		"1x1",	"1x1"	},
+	{ "DIR NE",		"NESW DIRS",		"1x1",	"1x1"	},
+	{ "DIR E",		"NESW DIRS",		"1x1",	"1x1"	},
+	{ "DIR SE",		"NESW DIRS",		"1x1",	"1x1"	},
+	{ "DIR S",		"NESW DIRS",		"1x1",	"1x1"	},
+	{ "DIR SW",		"NESW DIRS",		"1x1",	"1x1"	},
+	{ "DIR W",		"NESW DIRS",		"1x1",	"1x1"	},
+	{ "DIR NW",		"NESW DIRS",		"1x1",	"1x1"	},
+
+	{ "NO EXITS",		"MUDFONT NWS",		"1x2",	"1x2"	},
+	{ "N",			"MUDFONT NWS",		"1x2",	"1x2"	},
+	{ "  NW",		"MUDFONT NWS",		"1x2",	"1x2"	},
+	{ "N NW",		"MUDFONT NWS",		"1x2",	"1x2"	},
+	{ "     W",		"MUDFONT NWS",		"1x2",	"1x2"	},
+	{ "N    W",		"MUDFONT NWS",		"1x2",	"1x2"	},
+	{ "  NW W",		"MUDFONT NWS",		"1x2",	"1x2"	},
+	{ "N NW W",		"MUDFONT NWS",		"1x2",	"1x2"	},
+	{ "       SW",		"MUDFONT NWS",		"1x2",	"1x2"	},
+	{ "N      SW",		"MUDFONT NWS",		"1x2",	"1x2"	},
+	{ "  NW   SW",		"MUDFONT NWS",		"1x2",	"1x2"	},
+	{ "N NW   SW",		"MUDFONT NWS",		"1x2",	"1x2"	},
+	{ "     W SW",		"MUDFONT NWS",		"1x2",	"1x2"	},
+	{ "N    W SW",		"MUDFONT NWS",		"1x2",	"1x2"	},
+	{ "  NW W SW",		"MUDFONT NWS",		"1x2",	"1x2"	},
+	{ "N NW W SW",		"MUDFONT NWS",		"1x2",	"1x2"	},
+	{ "          S",	"MUDFONT NWS",		"1x2",	"1x2"	},
+	{ "N         S",	"MUDFONT NWS",		"1x2",	"1x2"	},
+	{ "  NW      S",	"MUDFONT NWS",		"1x2",	"1x2"	},
+	{ "N NW      S",	"MUDFONT NWS",		"1x2",	"1x2"	},
+	{ "     W    S",	"MUDFONT NWS",		"1x2",	"1x2"	},
+	{ "N    W    S",	"MUDFONT NWS",		"1x2",	"1x2"	},
+	{ "  NW W    S",	"MUDFONT NWS",		"1x2",	"1x2"	},
+	{ "N NW W    S",	"MUDFONT NWS",		"1x2",	"1x2"	},
+	{ "       SW S",	"MUDFONT NWS",		"1x2",	"1x2"	},
+	{ "N      SW S",	"MUDFONT NWS",		"1x2",	"1x2"	},
+	{ "  NW   SW S",	"MUDFONT NWS",		"1x2",	"1x2"	},
+	{ "N NW   SW S",	"MUDFONT NWS",		"1x2",	"1x2"	},
+	{ "     W SW S",	"MUDFONT NWS",		"1x2",	"1x2"	},
+	{ "N    W SW S",	"MUDFONT NWS",		"1x2",	"1x2"	},
+	{ "  NW W SW S",	"MUDFONT NWS",		"1x2",	"1x2"	},
+	{ "N NW W SW S",	"MUDFONT NWS",		"1x2",	"1x2"	},
+
+  	{ "NO EXITS",  		"MUDFONT NES",		"1x2",	"1x2"	},
+	{ "N",         		"MUDFONT NES",		"1x2",	"1x2"	},
+	{ "  NE",       	"MUDFONT NES",		"1x2",	"1x2"	},
+	{ "N NE",       	"MUDFONT NES",		"1x2",	"1x2"	},
+	{ "     W",     	"MUDFONT NES",		"1x2",	"1x2"	},
+	{ "N    W",     	"MUDFONT NES",		"1x2",	"1x2"	},
+	{ "  NE W",     	"MUDFONT NES",		"1x2",	"1x2"	},
+	{ "N NE W",     	"MUDFONT NES",		"1x2",	"1x2"	},
+	{ "       SE",  	"MUDFONT NES",		"1x2",	"1x2"	},
+	{ "N      SE",  	"MUDFONT NES",		"1x2",	"1x2"	},
+	{ "  NE   SE",  	"MUDFONT NES",		"1x2",	"1x2"	},
+	{ "N NE   SE",  	"MUDFONT NES",		"1x2",	"1x2"	},
+	{ "     W SE",  	"MUDFONT NES",		"1x2",	"1x2"	},
+	{ "N    W SE",  	"MUDFONT NES",		"1x2",	"1x2"	},
+	{ "  NE W SE",  	"MUDFONT NES",		"1x2",	"1x2"	},
+	{ "N NE W SE",  	"MUDFONT NES",		"1x2",	"1x2"	},
+	{ "          S",	"MUDFONT NES",		"1x2",	"1x2"	},
+	{ "N         S",	"MUDFONT NES",		"1x2",	"1x2"	},
+	{ "  NE      S",	"MUDFONT NES",		"1x2",	"1x2"	},
+	{ "N NE      S",	"MUDFONT NES",		"1x2",	"1x2"	},
+	{ "     W    S",	"MUDFONT NES",		"1x2",	"1x2"	},
+	{ "N    W    S",	"MUDFONT NES",		"1x2",	"1x2"	},
+	{ "  NE W    S",	"MUDFONT NES",		"1x2",	"1x2"	},
+	{ "N NE W    S",	"MUDFONT NES",		"1x2",	"1x2"	},
+	{ "       SE S",	"MUDFONT NES",		"1x2",	"1x2"	},
+	{ "N      SE S",	"MUDFONT NES",		"1x2",	"1x2"	},
+	{ "  NE   SE S",	"MUDFONT NES",		"1x2",	"1x2"	},
+	{ "N NE   SE S",	"MUDFONT NES",		"1x2",	"1x2"	},
+	{ "     W SE S",	"MUDFONT NES",		"1x2",	"1x2"	},
+	{ "N    W SE S",	"MUDFONT NES",		"1x2",	"1x2"	},
+	{ "  NE W SE S",	"MUDFONT NES",		"1x2",	"1x2"	},
+	{ "N NE W SE S",	"MUDFONT NES",		"1x2",	"1x2"	},
+
+	{ "NO EXITS",		"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+	{ "N",  		"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+	{ "  NW",		"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+	{ "N NW",		"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+	{ "     W",		"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+	{ "N    W",		"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+	{ "  NW W",		"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+	{ "N NW W",		"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+	{ "       SW",		"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+	{ "N      SW",		"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+	{ "  NW   SW",		"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+	{ "N NW   SW",		"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+	{ "     W SW",		"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+	{ "N    W SW",		"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+	{ "  NW W SW",		"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+	{ "N NW W SW",		"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+	{ "          S",	"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+	{ "N         S",	"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+	{ "  NW      S",	"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+	{ "N NW      S",	"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+	{ "     W    S",	"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+	{ "N    W    S",	"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+	{ "  NW W    S",	"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+	{ "N NW W    S",	"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+	{ "       SW S",	"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+	{ "N      SW S",	"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+	{ "  NW   SW S",	"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+	{ "N NW   SW S",	"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+	{ "     W SW S",	"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+	{ "N    W SW S",	"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+	{ "  NW W SW S",	"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+	{ "N NW W SW S",	"MUDFONT VOID NWS",	"1x2",	"1x2"	},
+
+	{ "NO EXITS",		"MUDFONT VOID NES",	"1x2",	"1x2"	},
+	{ "N",			"MUDFONT VOID NES",	"1x2",	"1x2"	},
+	{ "  NE",		"MUDFONT VOID NES",	"1x2",	"1x2"	},
+	{ "N NE",		"MUDFONT VOID NES",	"1x2",	"1x2"	},
+	{ "     W",		"MUDFONT VOID NES",	"1x2",	"1x2"	},
+	{ "N    W",		"MUDFONT VOID NES",	"1x2",	"1x2"	},
+	{ "  NE W",		"MUDFONT VOID NES",	"1x2",	"1x2"	},
+	{ "N NE W",		"MUDFONT VOID NES",	"1x2",	"1x2"	},
+	{ "       SE",		"MUDFONT VOID NES",	"1x2",	"1x2"	},
+	{ "N      SE",		"MUDFONT VOID NES",	"1x2",	"1x2"	},
+	{ "  NE   SE",		"MUDFONT VOID NES",	"1x2",	"1x2"	},
+	{ "N NE   SE",		"MUDFONT VOID NES",	"1x2",	"1x2"	},
+	{ "     W SE",		"MUDFONT VOID NES",	"1x2",	"1x2"	},
+	{ "N    W SE",		"MUDFONT VOID NES",	"1x2",	"1x2"	},
+	{ "  NE W SE",		"MUDFONT VOID NES",	"1x2",	"1x2"	},
+	{ "N NE W SE",		"MUDFONT VOID NES",	"1x2",	"1x2"	},
+	{ "          S",	"MUDFONT VOID NES",	"1x2",	"1x2"	},
+	{ "N         S",	"MUDFONT VOID NES",	"1x2",	"1x2"	},
+	{ "  NE      S",	"MUDFONT VOID NES",	"1x2",	"1x2"	},
+	{ "N NE      S",	"MUDFONT VOID NES",	"1x2",	"1x2"	},
+	{ "     W    S",	"MUDFONT VOID NES",	"1x2",	"1x2"	},
+	{ "N    W    S",	"MUDFONT VOID NES",	"1x2",	"1x2"	},
+	{ "  NE W    S",	"MUDFONT VOID NES",	"1x2",	"1x2"	},
+	{ "N NE W    S",	"MUDFONT VOID NES",	"1x2",	"1x2"	},
+	{ "       SE S",	"MUDFONT VOID NES",	"1x2",	"1x2"	},
+	{ "N      SE S",	"MUDFONT VOID NES",	"1x2",	"1x2"	},
+	{ "  NE   SE S",	"MUDFONT VOID NES",	"1x2",	"1x2"	},
+	{ "N NE   SE S",	"MUDFONT VOID NES",	"1x2",	"1x2"	},
+	{ "     W SE S",	"MUDFONT VOID NES",	"1x2",	"1x2"	},
+	{ "N    W SE S",	"MUDFONT VOID NES",	"1x2",	"1x2"	},
+	{ "  NE W SE S",	"MUDFONT VOID NES",	"1x2",	"1x2"	},
+	{ "N NE W SE S",	"MUDFONT VOID NES",	"1x2",	"1x2"	},
+
+	{ "N E",		"MUDFONT CURVED",	"1x2",	"1x2"	},
+	{ "S E",		"MUDFONT CURVED",	"1x2",	"1x2"	},
+	{ "S W",		"MUDFONT CURVED",	"1x2",	"1x2"	},
+	{ "N W",		"MUDFONT CURVED",	"1x2",	"1x2"	},
+
+        { "NO DIAGONAL",        "UNICODE GRAPHICS",     "2x5",  "2x5"   },
+	{ "SE",                 "UNICODE GRAPHICS",     "⸌",    "2x5"   },
+	{ "NE",                 "UNICODE GRAPHICS",     "⸝",    "2x5"   }, 
+	{ "SE NE",              "UNICODE GRAPHICS",     ">",    "2x5"   }, 
+	{ "SW",                 "UNICODE GRAPHICS",     "⸍",    "2x5"   }, 
+	{ "SE SW",              "UNICODE GRAPHICS",     "⸌⸍",   "2x5"   }, 
+	{ "NE SW",              "UNICODE GRAPHICS",     "/",   "2x5"   }, 
+	{ "SE NE SW",           "UNICODE GRAPHICS",     ">⸍",   "2x5"   }, 
+	{ "NW",                 "UNICODE GRAPHICS",     "⸜",    "2x5"   }, 
+	{ "SE NW",              "UNICODE GRAPHICS",     "\",   "2x5"   }, 
+	{ "NE NW",              "UNICODE GRAPHICS",     "⸝⸜",   "2x5"   }, 
+	{ "SE NE NW",           "UNICODE GRAPHICS",     ">⸜",   "2x5"   }, 
+	{ "SW NW",              "UNICODE GRAPHICS",     "<",    "2x5"   },
+	{ "SE SW NW",           "UNICODE GRAPHICS",     "⸌<",   "2x5"   }, 
+	{ "NE SW NW",           "UNICODE GRAPHICS",     "⸝<",   "2x5"   }, 
+	{ "SE NE SW NW",        "UNICODE GRAPHICS",     "><",   "2x5"   }, 
+	{ "D",                  "UNICODE GRAPHICS",     "-",    "2x5"   }, 
+	{ "N",                  "UNICODE GRAPHICS",     "↑",    "2x5"   }, 
+	{ "S",                  "UNICODE GRAPHICS",     "↓",    "2x5"   }, 
+	{ "N S",                "UNICODE GRAPHICS",     "│",    "2x5"   }, 
+	{ "U",                  "UNICODE GRAPHICS",     "+",    "2x5"   }, 
+	{ "E",                  "UNICODE GRAPHICS",     "🠆",    "2x5"   }, 
+	{ "W",                  "UNICODE GRAPHICS",     "🠄",    "2x5"   }, 
+	{ "E W",                "UNICODE GRAPHICS",     "─",    "2x5"   }, 
+	{ "ROOM L",             "UNICODE GRAPHICS",     "[",    "2x5"   }, 
+	{ "ROOM L CURVED",      "UNICODE GRAPHICS",     "(",    "2x5"   }, 
+	{ "ROOM R",             "UNICODE GRAPHICS",     "]",    "2x5"   }, 
+	{ "ROOM R CURVED",      "UNICODE GRAPHICS",     ")",    "2x5"   },
+
+	{ NULL,			NULL,			NULL,	NULL	}
+};
+
+struct map_group_type map_group_table[] =
+{
+	{ "ALL",                        "",                     1, 1,   1, 2,   0, 0,   "" },
+
+	{ "ASCII NESW",			"ASCII NESW",		1, 1,	1, 1,	0, 0,	"{x} {o} {o} {+} {o} {|} {+} {+} {o} {+} {-} {+} {+} {+} {+} {+} {x} {+} {|} {-} {+} {+} {+} {+} {|} {/} {-} {\\\\} {|} {/} {-} {\\\\}" },
+	{ "ASCII NESW LINE",		"ASCII NESW LINE",	1, 1,	1, 1,	0, 0,	"{x} {o} {o} {+} {o} {|} {+} {+} {o} {+} {-} {+} {+} {+} {+} {+}" },
+	{ "ASCII NESW MISC",		"ASCII NESW MISC",	1, 1,	1, 1,	0, 0,	"{x} {+} {|} {-}" },
+	{ "ASCII NESW CURVED",		"ASCII NESW CURVED",	1, 1,	1, 1,	0, 0,	"{+} {+} {+} {+}" },
+	{ "ASCII NESW DIRS",		"ASCII NESW DIRS",	1, 1,	1, 1,	0, 0,	"{|} {/} {-} {\\\\} {|} {/} {-} {\\\\}" },
+
+	{ "NESW",			"NESW",			1, 1,	1, 1,	0, 0,	"{\\u2B51} {\\u2579} {\\u257A} {\\u2517} {\\u257B} {\\u2503} {\\u250F} {\\u2523} {\\u2578} {\\u251B} {\\u2501} {\\u253B} {\\u2513} {\\u252B} {\\u2533} {\\u254B} {\\u2B51} {\\u2012} {\\u2507} {\\u254D} {\\u2570} {\\u256D} {\\u256E} {\\u256F} {\\u2191} {\\u2B08} {\\u2B95} {\\u2b0A} {\\u2193} {\\u2B0B} {\\u2B05} {\\u2B09}" },
+	{ "NESW TUBE",			"NESW",			1, 1,	1, 1,	0, 0,	"{\\u25AB}{\\U01F791}{\\U01F791}{\\u255A}{\\U01F791}{\\u2551}{\\u2554} {\\u2560}{\\U01F791}{\\u255D} {\\u2550} {\\u2569} {\\u2557} {\\u2563} {\\u2566} {\\u256C} {\\u25CF} {}        {\\u2502} {\\u2500} {\\u2570} {\\u256D} {\\u256E} {\\u256F} {\\u2191} {\\u2B08} {\\u2B95} {\\u2b0A} {\\u2193} {\\u2B0B} {\\u2B05} {\\u2B09}" },
+	{ "NESW LINE",			"NESW LINE",		1, 1,	1, 1,	0, 0,	"{\\u25AA} {\\u2579} {\\u257A} {\\u2517} {\\u257B} {\\u2503} {\\u250F} {\\u2523} {\\u2578} {\\u251B} {\\u2501} {\\u253B} {\\u2513} {\\u252B} {\\u2533} {\\u254B}" },
+	{ "NESW MISC",			"NESW MISC",		1, 1,	1, 1,	0, 0,	"{\\u2B51} {\\u2012} {\\u2507} {\\u254D}" },
+	{ "NESW CURVED",		"NESW CURVED",		1, 1,	1, 1,	0, 0,	"{\\u2570} {\\u256D} {\\u256E} {\\u256F}" },
+	{ "NESW DIRS",			"NESW DIRS",		1, 1,	1, 1,	0, 0,	"{\\u2191} {\\u2B08} {\\u2B95} {\\u2b0A} {\\u2193} {\\u2B0B} {\\u2B05} {\\u2B09}" },
+//	{ "NESW DIRS",			"NESW DIRS",		1, 1,	1, 1,	0, 0,	"{\\U01F805}{\\u2B08}{\\U01F806}{\\u2b0A}{\\U01F807}{\\u2B0B}{\\U01F804}{\\u2B09}" }, poor cross-platform support.
+
+	{ "MUDFONT",			"MUDFONT",		1, 2,	1, 2,	0, 0,	"" },
+	{ "MUDFONT PRIVATE",		"MUDFONT",		1, 2,	1, 2,	0, 0,	"{\\uE000} {\\uE001} {\\uE002} {\\uE003} {\\uE004} {\\uE005} {\\uE006} {\\uE007} {\\uE008} {\\uE009} {\\uE00A} {\\uE00B} {\\uE00C} {\\uE00D} {\\uE00E} {\\uE00F} {\\uE010} {\\uE011} {\\uE012} {\\uE013} {\\uE014} {\\uE015} {\\uE016} {\\uE017} {\\uE018} {\\uE019} {\\uE01A} {\\uE01B} {\\uE01C} {\\uE01D} {\\uE01E} {\\uE01F} {\\uE020} {\\uE021} {\\uE022} {\\uE023} {\\uE024} {\\uE025} {\\uE026} {\\uE027} {\\uE028} {\\uE029} {\\uE02A} {\\uE02B} {\\uE02C} {\\uE02D} {\\uE02E} {\\uE02F} {\\uE030} {\\uE031} {\\uE032} {\\uE033} {\\uE034} {\\uE035} {\\uE036} {\\uE037} {\\uE038} {\\uE039} {\\uE03A} {\\uE03B} {\\uE03C} {\\uE03D} {\\uE03E} {\\uE03F} {\\uE040} {\\uE041} {\\uE042} {\\uE043} {\\uE044} {\\uE045} {\\uE046} {\\uE047} {\\uE048} {\\uE049} {\\uE04A} {\\uE04B} {\\uE04C} {\\uE04D} {\\uE04E} {\\uE04F} {\\uE050} {\\uE051} {\\uE052} {\\uE053} {\\uE054} {\\uE055} {\\uE056} {\\uE057} {\\uE058} {\\uE059} {\\uE05A} {\\uE05B} {\\uE05C} {\\uE05D} {\\uE05E} {\\uE05F} {\\uE060} {\\uE061} {\\uE062} {\\uE063} {\\uE064} {\\uE065} {\\uE066} {\\uE067} {\\uE068} {\\uE069} {\\uE06A} {\\uE06B} {\\uE06C} {\\uE06D} {\\uE06E} {\\uE06F} {\\uE070} {\\uE071} {\\uE072} {\\uE073} {\\uE074} {\\uE075} {\\uE076} {\\uE077} {\\uE078} {\\uE079} {\\uE07A} {\\uE07B} {\\uE07C} {\\uE07D} {\\uE07E} {\\uE07F} {\\uE080} {\\uE081} {\\uE082} {\\uE083} {\\uE084} {\\uE085} {\\uE086} {\\uE087}" },
+	{ "MUDFONT BRAILLE TUBE",	"MUDFONT",		1, 2,	1, 2,	0, 0,	"{\\u28CF} {\\u28C7} {\\u28CE} {\\u28C6} {\\u28C9} {\\u28C1} {\\u28C8} {\\u28C0} {\\u288F} {\\u2887} {\\u288E} {\\u2886} {\\u2889} {\\u2881} {\\u2888} {\\u2880} {\\u284F} {\\u2847} {\\u284E} {\\u2846} {\\u2849} {\\u2841} {\\u2848} {\\u2880} {\\u280F} {\\u2807} {\\u280E} {\\u2806} {\\u2809} {\\u2801} {\\u2808} {\\u2800} {\\u28F9} {\\u28F8} {\\u28F1} {\\u28F0} {\\u28C9} {\\u28C8} {\\u28C2} {\\u28C0} {\\u2879} {\\u2878} {\\u2871} {\\u2870} {\\u2849} {\\u2848} {\\u2842} {\\u2840} {\\u28B9} {\\u28B8} {\\u28B1} {\\u28B0} {\\u2889} {\\u2888} {\\u2882} {\\u2880} {\\u2839} {\\u2838} {\\u2831} {\\u2830} {\\u2809} {\\u2808} {\\u2802} {\\u2800} {\\u2830} {\\u2838} {\\u2831} {\\u2839} {\\u2836} {\\u283E} {\\u2837} {\\u283F} {\\u2870} {\\u2878} {\\u2871} {\\u2879} {\\u2876} {\\u287E} {\\u2877} {\\u287F} {\\u28B0} {\\u28B8} {\\u28B1} {\\u28B9} {\\u28B6} {\\u28BE} {\\u28B7} {\\u28BF} {\\u28F0} {\\u28F8} {\\u28F1} {\\u28F9} {\\u28F6} {\\u28FE} {\\u28F7} {\\u28FF} {\\u2806} {\\u2807} {\\u280E} {\\u280F} {\\u2836} {\\u2837} {\\u283E} {\\u283F} {\\u2886} {\\u2887} {\\u288E} {\\u288F} {\\u28B6} {\\u28B7} {\\u28BE} {\\u28BF} {\\u2846} {\\u2847} {\\u284E} {\\u284F} {\\u2876} {\\u2877} {\\u287E} {\\u287F} {\\u28C6} {\\u28C7} {\\u28CE} {\\u28CF} {\\u28F6} {\\u28F7} {\\u28FE} {\\u28FF} {\\u2818} {\\u28A0} {\\u2844} {\\u2803} {} {} {} {}" },
+	{ "MUDFONT BRAILLE LINE",	"MUDFONT",		1, 2,	1, 2,	0, 0,	"{\\u2830} {\\u2838} {\\u2831} {\\u2839} {\\u2836} {\\u283E} {\\u2837} {\\u283F} {\\u2870} {\\u2878} {\\u2871} {\\u2879} {\\u2876} {\\u287E} {\\u2877} {\\u287F} {\\u28B0} {\\u28B8} {\\u28B1} {\\u28B9} {\\u28B6} {\\u28BE} {\\u28B7} {\\u28BF} {\\u28F0} {\\u28F8} {\\u28F1} {\\u28F9} {\\u28F6} {\\u28FE} {\\u28F7} {\\u28FF} {\\u2806} {\\u2807} {\\u280E} {\\u280F} {\\u2836} {\\u2837} {\\u283E} {\\u283F} {\\u2886} {\\u2887} {\\u288E} {\\u288F} {\\u28B6} {\\u28B7} {\\u28BE} {\\u28BF} {\\u2846} {\\u2847} {\\u284E} {\\u284F} {\\u2876} {\\u2877} {\\u287E} {\\u287F} {\\u28C6} {\\u28C7} {\\u28CE} {\\u28CF} {\\u28F6} {\\u28F7} {\\u28FE} {\\u28FF} {\\u2800} {\\u2808} {\\u2801} {\\u2809} {\\u2806} {\\u280E} {\\u2807} {\\u280F} {\\u2840} {\\u2848} {\\u2841} {\\u2849} {\\u2846} {\\u284E} {\\u2847} {\\u284F} {\\u2880} {\\u2888} {\\u2881} {\\u2889} {\\u2886} {\\u288E} {\\u2887} {\\u288F} {\\u28C0} {\\u28C8} {\\u28C1} {\\u28C9} {\\u28C6} {\\u28CE} {\\u28C7} {\\u28CF} {\\u2800} {\\u2801} {\\u2808} {\\u2809} {\\u2830} {\\u2831} {\\u2838} {\\u2839} {\\u2880} {\\u2881} {\\u2888} {\\u2889} {\\u28B0} {\\u28B1} {\\u28B8} {\\u28B9} {\\u2840} {\\u2841} {\\u2848} {\\u2849} {\\u2870} {\\u2871} {\\u2878} {\\u2879} {\\u28C0} {\\u28C1} {\\u28C8} {\\u28C9} {\\u28F0} {\\u28F1} {\\u28F8} {\\u28F9} {\\u2818} {\\u28A0} {\\u2844} {\\u2803} {} {} {} {}" },
+
+	{ "MUDFONT NWS",		"MUDFONT NWS",		1, 2,	1, 2,	0, 0,	"" },
+  	{ "MUDFONT NES",		"MUDFONT NES",		1, 2,	1, 2,	0, 0,	"" },
+
+	{ "MUDFONT VOID NWS",		"MUDFONT VOID NWS",	1, 2,	1, 2,	0, 0,	"" },
+	{ "MUDFONT VOID NES",		"MUDFONT VOID NES",	1, 2,	1, 2,	0, 0,	"" },
+
+	{ "MUDFONT CURVED",		"MUDFONT CURVED",	1, 2,	1, 2,	0, 0,	"" },
+	{ "MUDFONT CURVED BRAILLE",	"MUDFONT CURVED",	1, 2,	1, 2,	0, 0,	"{\\u2818} {\\u28A0} {\\u2844} {\\u2803} {} {} {} {}" },
+
+	{ "UNICODE GRAPHICS",           "UNICODE GRAPHICS",     2, 5,   2, 5,   0, 0,   "{ } {\\u2E0C} {\\u2E1D} {>} {\\u2E0D} {\\u2E0C\\u2E0D} {\\uFF0F} {>\\u2E0D} {\\u2E1C} {\\uFF3C} {\\u2E1D\\u2E1C} {>\\u2E1C} {<} {\\u2E0C<} {\\u2E1D<} {><} {-} {\\u2191} {\\u2193} {\\u2502} {+} {\\U01F806} {\\U01F804} {\\u2500} {[} {(} {]} {)}" },
+
+	{ NULL,				NULL,			0, 0,	0, 0,	0, 0,	"" }
+};
+
+struct stamp_type huge_stamp_table[] =
+{
+	{ " ",  23, "   \n   \n   \n   \n   \n   " },
+	{ "!",  23, "██╗\n██║\n██║\n╚═╝\n██╗\n╚═╝" },
+	{ "\"", 41, "██╗██╗\n2██║██║\n╚═╝╚═╝\n   \n   \n   " },
+	{ "#",  59, " ██╗ ██╗ \n████████╗\n╚██╔═██╔╝\n████████╗\n╚██╔═██╔╝\n ╚═╝ ╚═╝ " },
+	{ "&",  59, "  ████╗  \n ██╔═██╗ \n ╚████╔╝ \n██╔══██═╗\n╚█████╔█║\n ╚════╝╚╝" },
+	{ "%",  47, "██╗ ██╗\n╚═╝██╔╝\n  ██╔╝ \n ██╔╝  \n██╔╝██╗\n╚═╝ ╚═╝" },
+	{ "'",  23, "╗██╗\n██║\n╚═╝\n   \n   \n   " },
+
+	{ "+",  59, "   ██╗   \n   ██║   \n████████╗\n╚══██╔══╝\n   ██║   \n   ╚═╝   " },
+
+	{ "0",  53, " █████╗ \n██╔══██╗\n██║  ██║\n██║  ██║\n╚█████╔╝\n ╚════╝ " },
+	{ "1",  53, "  ▄██╗  \n ████║  \n ╚═██║  \n   ██║  \n ██████╗\n ╚═════╝" },
+	{ "2",  53, "██████╗ \n╚════██╗\n █████╔╝\n██╔═══╝ \n███████╗\n╚══════╝" },
+	{ "3",  53, "██████╗ \n╚════██╗\n █████╔╝\n ╚═══██╗\n██████╔╝\n╚═════╝ " },
+	{ "4",  53, "██╗  ██╗\n██║  ██║\n███████║\n╚════██║\n     ██║\n     ╚═╝" },
+	{ "5",  53, "███████╗\n██╔════╝\n███████╗\n╚════██║\n███████║\n╚══════╝" },
+	{ "6",  53, " █████╗ \n██╔═══╝ \n██████╗ \n██╔══██╗\n╚█████╔╝\n ╚════╝ " },
+	{ "7",  53, "███████╗\n╚════██║\n    ██╔╝\n   ██╔╝ \n   ██║  \n   ╚═╝  " },
+	{ "8",  53, " █████╗ \n██╔══██╗\n╚█████╔╝\n██╔══██╗\n╚█████╔╝\n ╚════╝ " },
+	{ "9",  53, " █████╗ \n██╔══██╗\n╚██████║\n ╚═══██║\n █████╔╝\n ╚════╝ " },
+
+	{ ":",  53, "        \n   ██╗  \n   ╚═╝  \n        \n   ██╗  \n   ╚═╝  " },
+
+	{ "A",  53, " █████╗ \n██╔══██╗\n███████║\n██╔══██║\n██║  ██║\n╚═╝  ╚═╝" },
+	{ "B",  53, "██████╗ \n██╔══██╗\n██████╔╝\n██╔══██╗\n██████╔╝\n╚═════╝ " },
+	{ "C",  53, " ██████╗\n██╔════╝\n██║     \n██║     \n╚██████╗\n ╚═════╝" },
+	{ "D",  53, "██████╗ \n██╔══██╗\n██║  ██║\n██║  ██║\n██████╔╝\n╚═════╝ " },
+	{ "E",  53, "███████╗\n██╔════╝\n█████╗  \n██╔══╝  \n███████╗\n╚══════╝" },
+	{ "F",  53, "███████╗\n██╔════╝\n█████╗  \n██╔══╝  \n██║     \n╚═╝     " },
+	{ "G",  59, " ██████╗ \n██╔════╝ \n██║  ███╗\n██║   ██║\n╚██████╔╝\n ╚═════╝ " },
+	{ "H",  53, "██╗  ██╗\n██║  ██║\n███████║\n██╔══██║\n██║  ██║\n╚═╝  ╚═╝" },
+	{ "I",  47, "██████╗\n╚═██╔═╝\n  ██║  \n  ██║  \n██████╗\n╚═════╝" },
+	{ "J",  53, "     ██╗\n     ██║\n     ██║\n██   ██║\n╚█████╔╝\n ╚════╝ " },
+	{ "K",  53, "██╗  ██╗\n██║ ██╔╝\n█████╔╝ \n██╔═██╗ \n██║  ██╗\n╚═╝  ╚═╝" },
+	{ "L",  53, "██╗     \n██║     \n██║     \n██║     \n███████╗\n╚══════╝" },
+	{ "M",  71, "███╗  ███╗\n████╗████║\n██╔███╔██║\n██║╚█╔╝██║\n██║ ╚╝ ██║\n╚═╝    ╚═╝" },
+	{ "N",  65, "███╗   ██╗\n████╗  ██║\n██╔██╗ ██║\n██║╚██╗██║\n██║ ╚████║\n╚═╝  ╚═══╝" },
+	{ "O",  59, " ██████╗ \n██╔═══██╗\n██║   ██║\n██║   ██║\n╚██████╔╝\n ╚═════╝ " },
+	{ "P",  53, "██████╗ \n██╔══██╗\n██████╔╝\n██╔═══╝ \n██║     \n╚═╝     " },
+	{ "Q",  59, " ██████╗ \n██╔═══██╗\n██║   ██║\n██║▄▄ ██║\n╚██████╔╝\n ╚══▀▀═╝ " },
+	{ "R",  53, "██████╗ \n██╔══██╗\n██████╔╝\n██╔══██╗\n██║  ██║\n╚═╝  ╚═╝" },
+	{ "S",  53, "███████╗\n██╔════╝\n███████╗\n╚════██║\n███████║\n╚══════╝" },
+	{ "T",  59, "████████╗\n╚══██╔══╝\n   ██║   \n   ██║   \n   ██║   \n   ╚═╝   " },
+	{ "U",  59, "██╗   ██╗\n██║   ██║\n██║   ██║\n██║   ██║\n╚██████╔╝\n ╚═════╝ " },
+	{ "V",  59, "██╗   ██╗\n██║   ██║\n██║   ██║\n╚██╗ ██╔╝\n ╚████╔╝ \n  ╚═══╝  " },
+	{ "W",  65, "██╗    ██╗\n██║    ██║\n██║ █╗ ██║\n██║███╗██║\n╚███╔███╔╝\n ╚══╝╚══╝ " },
+	{ "X",  53, "██╗  ██╗\n╚██╗██╔╝\n ╚███╔╝ \n ██╔██╗ \n██╔╝ ██╗\n╚═╝  ╚═╝" },
+	{ "Y",  59, "██╗   ██╗\n╚██╗ ██╔╝\n ╚████╔╝ \n  ╚██╔╝  \n   ██║   \n   ╚═╝   " },
+	{ "Z",  53, "███████╗\n╚══███╔╝\n  ███╔╝ \n ███╔╝  \n███████╗\n╚══════╝" },
+
+	{ "i",  23, "██╗\n╚═╝\n██╗\n██║\n██║\n╚═╝" },
+	{ "n",  47, "       \n       \n██▟███╗\n██║ ██║\n██║ ██║\n╚═╝ ╚═╝" },
+
+	{ NULL, 0, NULL }
+};
+

+ 149 - 0
telnet.h

@@ -0,0 +1,149 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2004                       *
+******************************************************************************/
+
+#include "tintin.h"
+
+/*
+	telnet protocol.
+*/
+
+#define     IAC     255
+#define     DONT    254
+#define     DO      253
+#define     WONT    252
+#define     WILL    251
+#define     SB      250
+#define     GA      249   /* Used for prompt marking */
+#define     EL      248
+#define     EC      247
+#define     AYT     246
+#define     AO      245
+#define     IP      244
+#define     BREAK   243
+#define     DM      242
+#define     NOP     241
+#define     SE      240
+#define     EOR     239   /* Used for prompt marking */
+#define     ABORT   238
+#define     SUSP    237
+#define     xEOF    236
+
+/*
+	telnet options
+*/
+
+#define     TELOPT_BINARY         0
+#define     TELOPT_ECHO           1  /* Echo */
+#define     TELOPT_RCP            2
+#define     TELOPT_SGA            3  /* Supress Go Ahead */
+#define     TELOPT_NAMS           4
+#define     TELOPT_STATUS         5
+#define     TELOPT_TIMINGMARK     6
+#define     TELOPT_RCTE           7
+#define     TELOPT_NAOL           8
+#define     TELOPT_NAOP           9
+#define     TELOPT_NAOCRD        10
+#define     TELOPT_NAOHTS        11
+#define     TELOPT_NAOHTD        12
+#define     TELOPT_NAOFFD        13
+#define     TELOPT_NAOVTS        14
+#define     TELOPT_NAOVTD        15
+#define     TELOPT_NAOLFD        16
+#define     TELOPT_XASCII        17
+#define     TELOPT_LOGOUT        18
+#define     TELOPT_BM            19
+#define     TELOPT_DET           20
+#define     TELOPT_SUPDUP        21
+#define     TELOPT_SUPDUPOUTPUT  22
+#define     TELOPT_SNDLOC        23
+#define     TELOPT_TTYPE         24  /* Terminal Type */
+#define     TELOPT_EOR           25  /* End of Record */
+#define     TELOPT_TUID          26
+#define     TELOPT_OUTMRK        27
+#define     TELOPT_TTYLOC        28
+#define     TELOPT_3270REGIME    29
+#define     TELOPT_X3PAD         30
+#define     TELOPT_NAWS          31  /* Negotiate About Window Size */
+#define     TELOPT_TSPEED        32
+#define     TELOPT_LFLOW         33
+#define     TELOPT_LINEMODE      34
+#define     TELOPT_XDISPLOC      35
+#define     TELOPT_OLD_ENVIRON   36
+#define     TELOPT_AUTH          37
+#define     TELOPT_ENCRYPT       38
+#define     TELOPT_NEW_ENVIRON   39
+#define     TELOPT_CHARSET       42  /* Charset */
+#define     TELOPT_STARTTLS      46
+#define     TELOPT_MSDP          69  /* Mud Server Data Protocol */
+#define     TELOPT_MSSP          70  /* Mud Server Status Protocol */
+#define     TELOPT_MCCP1         85
+#define     TELOPT_MCCP2         86  /* Mud Client Compression Protocol v2 */
+#define     TELOPT_MCCP3         87  /* Mud Client Compression Protocol v3 */
+#define     TELOPT_MSP           90  /* Mud Sound Protocol */
+#define     TELOPT_MXP           91  /* Mud eXtention Protocol */
+#define     TELOPT_ZMP           93  /* Zenith Mud Protocol */
+#define     TELOPT_GMCP         201  /* Generic Mud Communication Protocol */
+#define     TELOPT_EXOPL        255
+
+#define     TELCMD_OK(c)     ((c) >= xEOF)
+#define     TELCMD(c)        telcmds[(c)-xEOF]
+#define     TELOPT(c)       (telopt_table[(unsigned char) (c)].name)
+
+/*
+        Sub negotiation
+*/
+
+#define	    ENV_IS                0
+#define	    ENV_SEND              1
+#define     ENV_INFO              2
+
+#define     ENV_VAR               0
+#define     ENV_VAL               1
+#define     ENV_ESC               2 /* Not implemented in tintin */
+#define     ENV_USR               3
+
+#define     CHARSET_REQUEST       1
+#define     CHARSET_ACCEPTED      2
+#define     CHARSET_REJECTED      3
+/*
+ TTABLE-IS ..................04
+ TTABLE-REJECTED ............05
+ TTABLE-ACK .................06
+ TTABLE-NAK .................07
+*/
+
+#define     MSSP_VAR              1
+#define     MSSP_VAL              2
+
+#define     MSDP_VAR              1
+#define     MSDP_VAL              2
+#define     MSDP_TABLE_OPEN       3
+#define     MSDP_TABLE_CLOSE      4
+#define     MSDP_ARRAY_OPEN       5
+#define     MSDP_ARRAY_CLOSE      6
+
+#define     ANNOUNCE_NONE         0
+#define     ANNOUNCE_WILL         1
+#define     ANNOUNCE_DO           2

+ 2335 - 0
telopt_client.c

@@ -0,0 +1,2335 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2004                       *
+******************************************************************************/
+
+#include "tintin.h"
+#include "telnet.h"
+
+extern  int  client_send_do_eor(struct session *ses, int cplen, unsigned char *cpsrc);
+extern  int  client_mark_prompt(struct session *ses, int cplen, unsigned char *cpsrc);
+extern  int  client_recv_do_naws(struct session *ses, int cplen, unsigned char *cpsrc);
+extern  int  client_send_sb_naws(struct session *ses, int cplen, unsigned char *cpsrc);
+extern  int  client_recv_sb_tspeed(struct session *ses, int cplen, unsigned char *cpsrc);
+extern  int  client_recv_dont_ttype(struct session *ses, int cplen, unsigned char *cpsrc);
+extern  int  client_recv_sb_ttype(struct session *ses, int cplen, unsigned char *cpsrc);
+extern  int  client_send_wont_status(struct session *ses, int cplen, unsigned char *cpsrc);
+extern  int  client_send_dont_status(struct session *ses, int cplen, unsigned char *cpsrc);
+extern  int  client_recv_do_sga(struct session *ses, int cplen, unsigned char *cpsrc);
+extern  int  client_recv_will_sga(struct session *ses, int cplen, unsigned char *cpsrc);
+extern  int  client_send_wont_oldenviron(struct session *ses, int cplen, unsigned char *cpsrc);
+extern  int  client_recv_wont_echo(struct session *ses, int cplen, unsigned char *cpsrc);
+extern  int  client_recv_will_echo(struct session *ses, int cplen, unsigned char *cpsrc);
+extern  int  client_recv_do_echo(struct session *ses, int cplen, unsigned char *cpsrc);
+extern  int  client_send_ip(struct session *ses, int cplen, unsigned char *cpsrc);
+extern  int  client_send_wont_telopt(struct session *ses, int cplen, unsigned char *cpsrc);
+extern  int  client_send_dont_telopt(struct session *ses, int cplen, unsigned char *cpsrc);
+extern  int  client_send_will_telopt(struct session *ses, int cplen, unsigned char *cpsrc);
+extern  int  client_send_do_telopt(struct session *ses, int cplen, unsigned char *cpsrc);
+extern  int  client_recv_sb_mssp(struct session *ses, int cplen, unsigned char *src);
+extern  int  client_recv_sb_msdp(struct session *ses, int cplen, unsigned char *src);
+extern  int  client_recv_sb_gmcp(struct session *ses, int cplen, unsigned char *src);
+extern  int  client_recv_sb_charset(struct session *ses, int cplen, unsigned char *src);
+extern  int  client_recv_sb_new_environ(struct session *ses, int cplen, unsigned char *src);
+extern  int  client_recv_sb_zmp(struct session *ses, int cplen, unsigned char *cpsrc);
+extern  int  client_recv_will_mssp(struct session *ses, int cplen, unsigned char *cpsrc);
+extern  int  client_recv_will_mccp2(struct session *ses, int cplen, unsigned char *cpsrc);
+extern  int  client_send_dont_mccp2(struct session *ses, int cplen, unsigned char *cpsrc);
+extern  int  client_init_mccp2(struct session *ses, int cplen, unsigned char *cpsrc);
+extern  int  client_recv_will_mccp3(struct session *ses, int cplen, unsigned char *cpsrc);
+extern  int  client_recv_dont_mccp3(struct session *ses, int cplen, unsigned char *cpsrc);
+extern  int  client_recv_wont_mccp3(struct session *ses, int cplen, unsigned char *cpsrc);
+
+extern  int  client_init_mccp3(struct session *ses);
+extern  int  client_skip_sb(struct session *ses, int cplen, unsigned char *cpsrc);
+extern  int  client_recv_sb(struct session *ses, int cplen, unsigned char *cpsrc);
+
+struct iac_type
+{
+	int      size;
+	unsigned char * code;
+	int   (* func) (struct session *ses, int cplen, unsigned char *cpsrc);
+};
+
+struct iac_type iac_client_table [] =
+{
+	{   3,  (unsigned char []) {IAC, DO,   TELOPT_SGA},                       &client_recv_do_sga             },
+	{   3,  (unsigned char []) {IAC, WILL, TELOPT_SGA},                       &client_recv_will_sga           },
+	{   3,  (unsigned char []) {IAC, DO,   TELOPT_NAWS},                      &client_recv_do_naws            },
+	{   3,  (unsigned char []) {IAC, DO,   TELOPT_ECHO},                      &client_recv_do_echo            },
+	{   3,  (unsigned char []) {IAC, WILL, TELOPT_ECHO},                      &client_recv_will_echo          },
+	{   3,  (unsigned char []) {IAC, WONT, TELOPT_ECHO},                      &client_recv_wont_echo          },
+	{   3,  (unsigned char []) {IAC, WILL, TELOPT_MCCP2},                     &client_recv_will_mccp2         },
+	{   3,  (unsigned char []) {IAC, WILL, TELOPT_MCCP3},                     &client_recv_will_mccp3         },
+	{   3,  (unsigned char []) {IAC, DONT, TELOPT_MCCP3},                     &client_recv_dont_mccp3         },
+	{   3,  (unsigned char []) {IAC, WONT, TELOPT_MCCP3},                     &client_recv_wont_mccp3         },
+	{   3,  (unsigned char []) {IAC, WILL, TELOPT_MSSP},                      &client_recv_will_mssp          },
+	{   3,  (unsigned char []) {IAC, SB,   TELOPT_MSSP},                      &client_recv_sb_mssp            },
+	{   3,  (unsigned char []) {IAC, SB,   TELOPT_MSDP},                      &client_recv_sb_msdp            },
+	{   3,  (unsigned char []) {IAC, SB,   TELOPT_GMCP},                      &client_recv_sb_gmcp            },
+	{   3,  (unsigned char []) {IAC, SB,   TELOPT_CHARSET},                   &client_recv_sb_charset         },
+	{   3,  (unsigned char []) {IAC, SB,   TELOPT_NEW_ENVIRON},               &client_recv_sb_new_environ     },
+	{   6,  (unsigned char []) {IAC, SB,   TELOPT_TSPEED, ENV_SEND, IAC, SE}, &client_recv_sb_tspeed          },
+	{   3,  (unsigned char []) {IAC, DONT, TELOPT_TTYPE},                     &client_recv_dont_ttype         },
+	{   6,  (unsigned char []) {IAC, SB,   TELOPT_TTYPE,  ENV_SEND, IAC, SE}, &client_recv_sb_ttype           },
+	{   3,  (unsigned char []) {IAC, SB,   TELOPT_ZMP},                       &client_recv_sb_zmp             },
+	{   5,  (unsigned char []) {IAC, SB,   TELOPT_MCCP1, IAC, SE},            &client_init_mccp2              },
+	{   5,  (unsigned char []) {IAC, SB,   TELOPT_MCCP2, IAC, SE},            &client_init_mccp2              },
+	{   2,  (unsigned char []) {IAC, EOR},                                    &client_mark_prompt             },
+	{   2,  (unsigned char []) {IAC, GA},                                     &client_mark_prompt             },
+	{   0,  NULL,                                                             NULL                            }
+};
+
+
+void client_telopt_debug(struct session *ses, char *format, ...)
+{
+	char buf[BUFFER_SIZE];
+	va_list args;
+
+	if (HAS_BIT(ses->telopts, TELOPT_FLAG_DEBUG))
+	{
+		va_start(args, format);
+		vsprintf(buf, format, args);
+		va_end(args);
+
+		tintin_puts(ses, buf);
+	}
+}
+
+
+int client_translate_telopts(struct session *ses, unsigned char *src, int cplen)
+{
+	int skip, cnt, retval;
+	unsigned char *cpdst, *cpsrc;
+
+	push_call("client_translate_telopts(%p,%p,%d)",ses,src,cplen);
+
+	if (cplen == 0)
+	{
+		gtd->mud_output_buf[gtd->mud_output_len] = 0;	
+
+		pop_call();
+		return 0;
+	}
+
+	if (ses->mccp2)
+	{
+		ses->mccp2->next_in   = src;
+		ses->mccp2->avail_in  = cplen;
+
+		ses->mccp2->next_out  = gtd->mccp_buf;
+		ses->mccp2->avail_out = gtd->mccp_len;
+
+		inflate:
+
+		retval = inflate(ses->mccp2, Z_SYNC_FLUSH);
+
+		switch (retval)
+		{
+			case Z_BUF_ERROR:
+				if (ses->mccp2->avail_out == 0)
+				{
+					gtd->mccp_len *= 2;
+					gtd->mccp_buf  = (unsigned char *) realloc(gtd->mccp_buf, gtd->mccp_len);
+
+					ses->mccp2->avail_out = gtd->mccp_len / 2;
+					ses->mccp2->next_out  = gtd->mccp_buf + gtd->mccp_len / 2;
+
+					goto inflate;
+				}
+				else
+				{
+					tintin_puts2(ses, "");
+					tintin_puts2(ses, "#COMPRESSION ERROR, Z_BUF_ERROR, DISABLING MCCP2.");
+					client_send_dont_mccp2(ses, 0, NULL);
+					inflateEnd(ses->mccp2);
+					free(ses->mccp2);
+					ses->mccp2 = NULL;
+					cpsrc = src;
+					cplen = 0;
+				}
+				break;
+
+			case Z_OK:
+				if (ses->mccp2->avail_out == 0)
+				{
+					gtd->mccp_len *= 2;
+					gtd->mccp_buf  = (unsigned char *) realloc(gtd->mccp_buf, gtd->mccp_len);
+
+					ses->mccp2->avail_out = gtd->mccp_len / 2;
+					ses->mccp2->next_out  = gtd->mccp_buf + gtd->mccp_len / 2;
+
+					goto inflate;
+				}
+				cplen = ses->mccp2->next_out - gtd->mccp_buf;
+				cpsrc = gtd->mccp_buf;
+				break;
+
+			case Z_STREAM_END:
+				client_telopt_debug(ses, "#COMPRESSION END, DISABLING MCCP2.");
+
+				cnt = ses->mccp2->next_out - gtd->mccp_buf;
+
+				cpsrc = src + (cplen - ses->mccp2->avail_in);
+				cplen = ses->mccp2->avail_in;
+
+				inflateEnd(ses->mccp2);
+				free(ses->mccp2);
+				ses->mccp2 = NULL;
+
+				client_translate_telopts(ses, gtd->mccp_buf, cnt);
+				break;
+
+			default:
+				tintin_puts2(ses, "");
+				tintin_printf2(ses, "#COMPRESSION ERROR, DISABLING MCCP2, RETVAL %d.", retval);
+				client_send_dont_mccp2(ses, 0, NULL);
+				inflateEnd(ses->mccp2);
+				free(ses->mccp2);
+				ses->mccp2 = NULL;
+				cpsrc = src;
+				cplen = 0;
+				break;
+		}
+	}
+	else
+	{
+		cpsrc = src;
+	}
+
+	if (HAS_BIT(ses->logmode, LOG_FLAG_LOW) && ses->logfile)
+	{
+		fwrite(cpsrc, 1, cplen, ses->logfile);
+	}
+
+ 	if (ses->read_len + cplen >= ses->read_max)
+	{
+		ses->read_max = ses->read_len + cplen + 1000;
+		ses->read_buf = (unsigned char *) realloc(ses->read_buf, ses->read_max);
+	}
+
+	memcpy(ses->read_buf + ses->read_len, cpsrc, cplen);
+
+	cpsrc = ses->read_buf;
+	cplen = ses->read_len + cplen;
+
+	if (gtd->mud_output_len + cplen >= gtd->mud_output_max)
+	{
+		gtd->mud_output_max = gtd->mud_output_len + cplen + 1000;
+		gtd->mud_output_buf = (char *) realloc(gtd->mud_output_buf, gtd->mud_output_max);
+	}
+
+	cpdst = (unsigned char *) gtd->mud_output_buf + gtd->mud_output_len;
+
+	while (cplen > 0)
+	{
+		if (*cpsrc == IAC && HAS_BIT(ses->flags, SES_FLAG_TELNET) && !HAS_BIT(ses->flags, SES_FLAG_RUN))
+		{
+			skip = 2;
+
+			if (HAS_BIT(ses->telopts, TELOPT_FLAG_DEBUG))
+			{
+				switch(cpsrc[1])
+				{
+					case NOP:   
+					case DM:
+					case BREAK: 
+					case IP:    
+					case AO:    
+					case AYT:   
+					case EC:    
+					case EL:
+					case IAC:
+					case GA:
+					case EOR:
+						tintin_printf2(ses, "RCVD IAC %s", TELCMD(cpsrc[1]));
+						break;
+
+					case DO:
+					case DONT:
+					case WILL:
+					case WONT:
+						if (cplen > 2)
+						{
+							tintin_printf2(ses, "RCVD IAC %s %s", TELCMD(cpsrc[1]), telopt_table[cpsrc[2]].name);
+						}
+						else
+						{
+							tintin_printf2(ses, "RCVD IAC %s %d (BAD TELOPT)", TELCMD(cpsrc[1]), cpsrc[2]);
+						}
+						break;
+
+					case SB:
+						if (cplen > 2)
+						{
+							tintin_printf2(ses, "RCVD IAC SB %s", telopt_table[cpsrc[2]].name);
+						}
+						break;
+
+					default:
+						if (TELCMD_OK(cpsrc[1]))
+						{
+							tintin_printf2(ses, "RCVD IAC %s %d", TELCMD(cpsrc[1]), cpsrc[2]);
+						}
+						else
+						{
+							tintin_printf2(ses, "RCVD IAC %d %d", cpsrc[1], cpsrc[2]);
+						}
+						break;
+				}
+			}
+
+			for (cnt = 0 ; iac_client_table[cnt].code ; cnt++)
+			{
+				if (cplen < iac_client_table[cnt].size && !memcmp(cpsrc, iac_client_table[cnt].code, cplen))
+				{
+					skip = iac_client_table[cnt].size; // broken packet handling
+
+					break;
+				}
+
+				if (cplen >= iac_client_table[cnt].size && !memcmp(cpsrc, iac_client_table[cnt].code, iac_client_table[cnt].size))
+				{
+					skip = iac_client_table[cnt].func(ses, cplen, cpsrc);
+
+					if (iac_client_table[cnt].func == client_init_mccp2)
+					{
+						pop_call();
+						return client_translate_telopts(ses, cpsrc + skip, cplen - skip);
+					}
+					break;
+				}
+			}
+
+			if (iac_client_table[cnt].code == NULL && cplen > 1)
+			{
+				switch (cpsrc[1])
+				{
+					case SE:
+					case NOP:
+					case DM:
+					case BREAK:
+					case IP:
+					case AO:
+					case AYT:
+					case EC:
+					case EL:
+					case GA:
+					case EOR:
+					     skip = 2;
+					     break;
+
+					case WILL:
+						if (cplen > 2)
+						{
+							if (!check_all_events(ses, SUB_ARG|SUB_SEC, 1, 0, "IAC WILL %s", telopt_table[cpsrc[2]].name) && !check_all_events(ses, SUB_ARG|SUB_SEC, 1, 0, "CATCH IAC WILL %s", telopt_table[cpsrc[2]].name))
+							{
+								if (!HAS_BIT(ses->telopt_flag[cpsrc[2] / 32], 1 << cpsrc[2] % 32))
+								{
+									if (telopt_table[cpsrc[2]].want)
+									{
+										skip = client_send_do_telopt(ses, cplen, cpsrc);
+									}
+									else
+									{
+										skip = client_send_dont_telopt(ses, cplen, cpsrc);
+									}
+									SET_BIT(ses->telopt_flag[cpsrc[2] / 32], 1 << cpsrc[2] % 32);
+								}
+							}
+						}
+						skip = 3;
+						break;
+
+					case DO:
+						if (cplen > 2)
+						{
+							if (!check_all_events(ses, SUB_ARG|SUB_SEC, 1, 0, "IAC DO %s", telopt_table[cpsrc[2]].name) && !check_all_events(ses, SUB_ARG|SUB_SEC, 1, 0, "IAC DO %s", telopt_table[cpsrc[2]].name))
+							{
+								if (!HAS_BIT(ses->telopt_flag[cpsrc[2] / 32], 1 << cpsrc[2] % 32))
+								{
+									if (telopt_table[cpsrc[2]].want)
+									{
+										skip = client_send_will_telopt(ses, cplen, cpsrc);
+									}
+									else
+									{
+										skip = client_send_wont_telopt(ses, cplen, cpsrc);
+									}
+									SET_BIT(ses->telopt_flag[cpsrc[2] / 32], 1 << cpsrc[2] % 32);
+								}
+							}
+						}
+						skip = 3;
+						break;
+
+					case WONT:
+					case DONT:
+						if (cplen > 2)
+						{
+							check_all_events(ses, SUB_ARG|SUB_SEC, 2, 0, "IAC %s %s", TELCMD(cpsrc[1]), telopt_table[cpsrc[2]].name);
+
+							DEL_BIT(ses->telopt_flag[cpsrc[2] / 32], 1 << cpsrc[2] % 32);
+						}
+						skip = 3;
+						break;
+
+					case SB:
+						skip = client_recv_sb(ses, cplen, cpsrc);
+						break;
+
+					case IAC:
+						gtd->mud_output_len++;
+						*cpdst++ = 0xFF;
+						skip = 2;
+						break;
+
+					default:
+						tintin_printf(NULL, "RCVD IAC %d (BAD TELOPT)", cpsrc[1]);
+						skip = 1;
+						break;
+				}
+			}
+
+			if (skip <= cplen)
+			{
+				cplen -= skip;
+				cpsrc += skip;
+			}
+			else
+			{
+				memmove(ses->read_buf, cpsrc, cplen);
+
+				gtd->mud_output_buf[gtd->mud_output_len] = 0;
+
+				pop_call();
+				return cplen;
+			}
+		}
+		else
+		{
+			/*
+				skip '\0' and '\r' in text input
+			*/
+
+			switch (*cpsrc)
+			{
+				case ASCII_NUL:
+					cpsrc++;
+					cplen--;
+					continue;
+
+				case ASCII_ENQ:
+					check_all_events(ses, SUB_ARG, 0, 1, "VT100 ENQ", gtd->term); // obsolete, but we'll handle it for now.
+					cpsrc++;
+					cplen--;
+					continue;
+
+				case ASCII_CR:
+					if (cplen > 1 && cpsrc[1] == ASCII_LF)
+					{
+						cpsrc++;
+						cplen--;
+						continue;
+					}
+					break;
+
+				case ASCII_LF:
+					if (HAS_BIT(ses->telopts, TELOPT_FLAG_PROMPT))
+					{
+						DEL_BIT(ses->telopts, TELOPT_FLAG_PROMPT);
+					}
+
+					*cpdst++ = *cpsrc++;
+					gtd->mud_output_len++;
+					cplen--;
+
+					while (*cpsrc == ASCII_CR)
+					{
+						cpsrc++;
+						cplen--;
+					}
+
+					continue;
+
+				default:
+					if (cpsrc[0] == ASCII_ESC)
+					{
+						if (cplen >= 2 && cpsrc[1] == 'Z')
+						{
+							check_all_events(ses, SUB_ARG, 0, 0, "VT100 DECID");
+							cpsrc += 2;
+							cplen -= 2;
+							continue;
+						}
+
+						if (cplen >= 3 && cpsrc[1] == '[')
+						{
+							if (cpsrc[2] == 'c')
+							{
+								check_all_events(ses, SUB_ARG, 0, 0, "VT100 DA");
+								cpsrc += 3;
+								cplen -= 3;
+								continue;
+							}
+							if (cplen >= 4)
+							{
+								if (cpsrc[2] >= '5' && cpsrc[2] <= '6' && cpsrc[3] == 'n')
+								{
+									if (cpsrc[2] == '5')
+									{
+										check_all_events(ses, SUB_ARG, 0, 0, "VT100 DSR");
+									}
+									if (cpsrc[2] == '6')
+									{
+										check_all_events(ses, SUB_ARG, 0, 2, "VT100 CPR", ntos(gtd->screen->cols), ntos(gtd->screen->rows));
+									}
+									cpsrc += 4;
+									cplen -= 4;
+									continue;
+								}
+								if (cpsrc[2] == '0' && cpsrc[3] == 'c')
+								{
+									check_all_events(ses, SUB_ARG, 0, 0, "VT100 DA");
+									cpsrc += 4;
+									cplen -= 4;
+									continue;
+								}
+							}
+						}
+
+						if (cplen >= 3 && cpsrc[1] == ']')
+						{
+							char osc[BUFFER_SIZE];
+
+							for (skip = 2 ; cplen >= skip ; skip++)
+							{
+								if (cpsrc[skip] == ASCII_BEL)
+								{
+									break;
+								}
+							}
+							sprintf(osc, "%.*s", skip - 2, cpsrc + 2);
+
+							check_all_events(ses, SUB_ARG|SUB_SEC, 0, 1, "VT100 OSC", osc);
+
+							if (check_all_events(ses, SUB_ARG|SUB_SEC, 0, 1, "CATCH VT100 OSC", osc))
+							{
+								cpsrc += skip;
+								cplen -= skip;
+
+								continue;
+							}
+						}
+					}
+
+					if (HAS_BIT(ses->telopts, TELOPT_FLAG_PROMPT))
+					{
+						DEL_BIT(ses->telopts, TELOPT_FLAG_PROMPT);
+
+						/*
+							Fix up non vt muds
+						*/
+
+						if (HAS_BIT(ses->flags, SES_FLAG_SPLIT) || !IS_SPLIT(ses))
+						{
+							*cpdst++ = '\n';
+							gtd->mud_output_len++;
+						}
+					}
+					break;
+			}
+			*cpdst++ = *cpsrc++;
+			gtd->mud_output_len++;
+			cplen--;
+		}
+	}
+
+	gtd->mud_output_buf[gtd->mud_output_len] = 0;
+
+	pop_call();
+	return 0;
+}
+
+/*
+	SGA
+*/
+
+int client_recv_will_sga(struct session *ses, int cplen, unsigned char *cpsrc)
+{
+	if (check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "CATCH IAC WILL SGA"))
+	{
+		return 3;
+	}
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "IAC WILL SGA");
+
+	SET_BIT(ses->telopts, TELOPT_FLAG_SGA);
+
+	if (!HAS_BIT(ses->telopt_flag[TELOPT_SGA / 32], 1 << TELOPT_SGA % 32))
+	{
+		SET_BIT(ses->telopt_flag[TELOPT_SGA / 32], 1 << TELOPT_SGA % 32);
+
+		telnet_printf(ses, 3, "%c%c%c", IAC, DO, TELOPT_SGA);
+
+		client_telopt_debug(ses, "SENT IAC DO %s", telopt_table[TELOPT_SGA].name);
+	}
+	return 3;
+}
+
+int client_recv_do_sga(struct session *ses, int cplen, unsigned char *cpsrc)
+{
+	if (check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "CATCH IAC DO SGA"))
+	{
+		return 3;
+	}
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "IAC DO SGA");
+
+	SET_BIT(ses->telopts, TELOPT_FLAG_SGA);
+
+	if (!HAS_BIT(ses->telopt_flag[TELOPT_SGA / 32], 1 << TELOPT_SGA % 32))
+	{
+		SET_BIT(ses->telopt_flag[TELOPT_SGA / 32], 1 << TELOPT_SGA % 32);
+
+		telnet_printf(ses, 3, "%c%c%c", IAC, WILL, TELOPT_SGA);
+
+		client_telopt_debug(ses, "SENT IAC WILL %s", telopt_table[TELOPT_SGA].name);
+	}
+	return 3;
+}
+
+int client_mark_prompt(struct session *ses, int cplen, unsigned char *cpsrc)
+{
+	SET_BIT(ses->telopts, TELOPT_FLAG_PROMPT);
+
+	if (cpsrc[1] == GA)
+	{
+		check_all_events(ses, SUB_ARG, 0, 0, "IAC GA");
+	}
+	else if (cpsrc[1] == EOR)
+	{
+		check_all_events(ses, SUB_ARG, 0, 0, "IAC EOR");
+	}
+	return 2;
+}
+
+/*
+	TTYPE
+*/
+
+int client_recv_dont_ttype(struct session *ses, int cplen, unsigned char *cpsrc)
+{
+	if (check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "CATCH IAC DONT TTYPE"))
+	{
+		return 3;
+	}
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "IAC DONT TTYPE");
+
+	DEL_BIT(ses->telopts, TELOPT_FLAG_TTYPE);
+
+	DEL_BIT(ses->telopt_flag[cpsrc[2] / 32], 1 << cpsrc[2] % 32);
+
+	return 3;
+}
+
+int client_recv_sb_ttype(struct session *ses, int cplen, unsigned char *cpsrc)
+{
+	if (check_all_events(ses, SUB_ARG|SUB_SEC, 0, 1, "CATCH IAC SB TTYPE", ntos(cpsrc[3])))
+	{
+		return 6;
+	}
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 0, 1, "IAC SB TTYPE", ntos(cpsrc[3]));
+
+	if (HAS_BIT(ses->telopts, TELOPT_FLAG_MTTS))
+	{
+		char mtts[BUFFER_SIZE];
+
+		sprintf(mtts, "MTTS %d",
+			(ses->color > 0 ? 1 : 0) +
+			(HAS_BIT(ses->flags, SES_FLAG_SPLIT) ? 0 : 2) +
+			(HAS_BIT(ses->charset, CHARSET_FLAG_UTF8) && !HAS_BIT(ses->charset, CHARSET_FLAG_ALL_TOUTF8) ? 4 : 0) +
+			(ses->color > 16 ? 8 : 0) +
+			(HAS_BIT(ses->flags, SES_FLAG_SCREENREADER) ? 64 : 0) +
+			(ses->color > 256 ? 256 : 0));
+
+		telnet_printf(ses, 6 + strlen(mtts), "%c%c%c%c%s%c%c", IAC, SB, TELOPT_TTYPE, 0, mtts, IAC, SE);
+
+		client_telopt_debug(ses, "SENT IAC SB TTYPE %s", mtts);
+	}
+	else if (HAS_BIT(ses->telopts, TELOPT_FLAG_TTYPE))
+	{
+		telnet_printf(ses, 6 + strlen(gtd->term), "%c%c%c%c%s%c%c", IAC, SB, TELOPT_TTYPE, 0, gtd->term, IAC, SE);
+
+		client_telopt_debug(ses, "SENT IAC SB TTYPE %s", gtd->term);
+
+		SET_BIT(ses->telopts, TELOPT_FLAG_MTTS);
+	}
+	else
+	{
+		telnet_printf(ses, 14, "%c%c%c%c%s%c%c", IAC, SB, TELOPT_TTYPE, 0, "TINTIN++", IAC, SE);
+
+		client_telopt_debug(ses, "SENT IAC SB TTYPE %s", "TINTIN++");
+
+		SET_BIT(ses->telopts, TELOPT_FLAG_TTYPE);
+	}
+	return 6;
+}
+
+
+/*
+	TSPEED
+*/
+
+int client_recv_sb_tspeed(struct session *ses, int cplen, unsigned char *cpsrc)
+{
+	if (check_all_events(ses, SUB_ARG|SUB_SEC, 0, 1, "CATCH IAC SB TSPEED", ntos(cpsrc[3])))
+	{
+		return 6;
+	}
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 0, 1, "IAC SB TSPEED", ntos(cpsrc[3]));
+
+	SET_BIT(ses->telopts, TELOPT_FLAG_TSPEED);
+
+	telnet_printf(ses, 17, "%c%c%c%c%s%c%c", IAC, SB, TELOPT_TSPEED, 0, "38400,38400", IAC, SE);
+
+	client_telopt_debug(ses, "SENT IAC SB 0 %s 38400,38400 IAC SB", telopt_table[TELOPT_TSPEED].name);
+
+	return 6;
+}
+
+
+/*
+	NAWS
+*/
+
+int client_recv_do_naws(struct session *ses, int cplen, unsigned char *cpsrc)
+{
+	if (check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "CATCH IAC DO NAWS"))
+	{
+		return 3;
+	}
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "IAC DO NAWS");
+
+	SET_BIT(ses->telopts, TELOPT_FLAG_NAWS);
+
+	if (!HAS_BIT(ses->telopt_flag[TELOPT_NAWS / 32], 1 << TELOPT_NAWS % 32))
+	{
+		SET_BIT(ses->telopt_flag[TELOPT_NAWS / 32], 1 << TELOPT_NAWS % 32);
+
+		telnet_printf(ses, 3, "%c%c%c", IAC, WILL, TELOPT_NAWS);
+
+		client_telopt_debug(ses, "SENT IAC WILL NAWS");
+	}
+
+	return client_send_sb_naws(ses, cplen, cpsrc);
+}
+
+int client_send_sb_naws(struct session *ses, int cplen, unsigned char *cpsrc)
+{
+	int rows;
+	int cols;
+
+	rows = HAS_BIT(ses->flags, SES_FLAG_SPLIT) ? ses->split->bot_row - ses->split->top_row + 1 : gtd->screen->rows;
+
+	cols = get_scroll_cols(ses);
+
+	// Properly handle row and colum size of 255
+
+	if (cols % 256 == IAC && gtd->screen->rows % 256 == IAC)
+	{
+		telnet_printf(ses, 11, "%c%c%c%c%c%c%c%c%c%c%c", IAC, SB, TELOPT_NAWS, cols / 256, IAC, cols % 256, rows / 256, IAC, rows % 256, IAC, SE);
+	}
+	else if (cols % 256 == IAC)
+	{
+		telnet_printf(ses, 10, "%c%c%c%c%c%c%c%c%c%c", IAC, SB, TELOPT_NAWS, cols / 256, IAC, cols % 256, rows / 256, rows % 256, IAC, SE);
+	}
+	else if (gtd->screen->rows % 256 == IAC)
+	{
+		telnet_printf(ses, 10, "%c%c%c%c%c%c%c%c%c%c", IAC, SB, TELOPT_NAWS, cols / 256, cols % 256, rows / 256, IAC, rows % 256, IAC, SE);
+	}
+	else
+	{
+		telnet_printf(ses, 9, "%c%c%c%c%c%c%c%c%c", IAC, SB, TELOPT_NAWS, cols / 256, cols % 256, rows / 256, rows % 256, IAC, SE);
+	}
+
+	client_telopt_debug(ses, "SENT IAC SB NAWS %d %d %d %d", cols / 256, cols % 256, gtd->screen->rows / 256, gtd->screen->rows % 256);
+
+	return 3;
+}
+
+// Server requests client to enable local echo
+
+int client_recv_wont_echo(struct session *ses, int cplen, unsigned char *cpsrc)
+{
+	if (check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "CATCH IAC WONT ECHO"))
+	{
+		return 3;
+	}
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "IAC WONT ECHO");
+
+	SET_BIT(ses->telopts, TELOPT_FLAG_ECHO);
+
+	if (HAS_BIT(ses->telopt_flag[TELOPT_ECHO / 32], 1 << TELOPT_ECHO % 32))
+	{
+		DEL_BIT(ses->telopt_flag[TELOPT_ECHO / 32], 1 << TELOPT_ECHO % 32);
+
+//		telnet_printf(ses, 3, "%c%c%c", IAC, DONT, TELOPT_ECHO);
+
+		client_telopt_debug(ses, "SENT IAC DONT ECHO (SKIPPED)");
+	}
+	else
+	{
+		client_telopt_debug(ses, "DID NOT SEND IAC DONT ECHO, INFINITE LOOP PROTECTION.");
+	}
+
+	return 3;
+}
+
+// Server requests client to disable local echo
+
+int client_recv_will_echo(struct session *ses, int cplen, unsigned char *cpsrc)
+{
+	if (check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "CATCH IAC WILL ECHO"))
+	{
+		return 3;
+	}
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "IAC WILL ECHO");
+
+	DEL_BIT(ses->telopts, TELOPT_FLAG_ECHO);
+
+	if (!HAS_BIT(ses->telopt_flag[TELOPT_ECHO / 32], 1 << TELOPT_ECHO % 32))
+	{
+		SET_BIT(ses->telopt_flag[TELOPT_ECHO / 32], 1 << TELOPT_ECHO % 32);
+
+		telnet_printf(ses, 3, "%c%c%c", IAC, DO, TELOPT_ECHO);
+
+		client_telopt_debug(ses, "SENT IAC DO ECHO (SKIPPED)");
+	}
+	else
+	{
+		client_telopt_debug(ses, "DID NOT SEND IAC DO ECHO, INFINITE LOOP PROTECTION.");
+	}
+	return 3;
+}
+
+// Shouldn't be received, but we'll handle it as a disable local echo request
+
+int client_recv_do_echo(struct session *ses, int cplen, unsigned char *cpsrc)
+{
+	if (check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "CATCH IAC DO ECHO"))
+	{
+		return 3;
+	}
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "IAC DO ECHO");
+
+	DEL_BIT(ses->telopts, TELOPT_FLAG_ECHO);
+
+	if (!HAS_BIT(ses->telopt_flag[TELOPT_ECHO / 32], 1 << TELOPT_ECHO % 32))
+	{
+		SET_BIT(ses->telopt_flag[TELOPT_ECHO / 32], 1 << TELOPT_ECHO % 32);
+
+		telnet_printf(ses, 3, "%c%c%c", IAC, WILL, TELOPT_ECHO);
+
+		client_telopt_debug(ses, "SENT IAC WILL ECHO");
+
+	}
+	else
+	{
+		client_telopt_debug(ses, "DID NOT SEND IAC WILL ECHO, INFINITE LOOP PROTECTION.");
+	}
+
+	return 3;
+}
+
+/*
+	IP
+*/
+
+int client_send_ip(struct session *ses, int cplen, unsigned char *cpsrc)
+{
+	telnet_printf(ses, 5, "%c%c%c%c%c", IAC, IP, IAC, DO, TELOPT_TIMINGMARK);
+
+	client_telopt_debug(ses, "SENT IAC IP");
+	client_telopt_debug(ses, "SENT IAC DO TIMING MARK");
+
+	return 3;
+}
+
+/*
+	Automatic telopt handling
+*/
+
+int client_send_wont_telopt(struct session *ses, int cplen, unsigned char *cpsrc)
+{
+	telnet_printf(ses, 3, "%c%c%c", IAC, WONT, cpsrc[2]);
+
+	client_telopt_debug(ses, "SENT IAC WONT %s", telopt_table[cpsrc[2]].name);
+
+	return 3;
+}
+
+int client_send_dont_telopt(struct session *ses, int cplen, unsigned char *cpsrc)
+{
+	telnet_printf(ses, 3, "%c%c%c", IAC, DONT, cpsrc[2]);
+
+	client_telopt_debug(ses, "SENT IAC DONT %s", telopt_table[cpsrc[2]].name);
+
+	return 3;
+}
+
+int client_send_will_telopt(struct session *ses, int cplen, unsigned char *cpsrc)
+{
+	telnet_printf(ses, 3, "%c%c%c", IAC, WILL, cpsrc[2]);
+
+	client_telopt_debug(ses, "SENT IAC WILL %s", telopt_table[cpsrc[2]].name);
+
+	return 3;
+}
+
+int client_send_do_telopt(struct session *ses, int cplen, unsigned char *cpsrc)
+{
+	telnet_printf(ses, 3, "%c%c%c", IAC, DO, cpsrc[2]);
+
+	client_telopt_debug(ses, "SENT IAC DO %s", telopt_table[cpsrc[2]].name);
+
+	return 3;
+}
+
+/*
+	MSSP (Mud Server Status Protocol)
+*/
+
+int client_recv_will_mssp(struct session *ses, int cplen, unsigned char *cpsrc)
+{
+	if (!check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "IAC WILL MSSP") && !check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "CATCH IAC WILL MSSP"))
+	{
+		if (HAS_BIT(ses->telopts, TELOPT_FLAG_DEBUG))
+		{
+			telnet_printf(ses, 3, "%c%c%c", IAC, DO, TELOPT_MSSP);
+
+			client_telopt_debug(ses, "SENT IAC DO MSSP");
+		}
+	}
+	return 3;
+}
+
+int client_recv_sb_mssp(struct session *ses, int cplen, unsigned char *src)
+{
+	char var[BUFFER_SIZE], val[BUFFER_SIZE];
+	char *pto;
+	int i;
+
+	var[0] = val[0] = i = 0;
+
+	if (client_skip_sb(ses, cplen, src) > cplen)
+	{
+		return cplen + 1;
+	}
+
+	while (i < cplen && src[i] != SE)
+	{
+		switch (src[i])
+		{
+			case MSSP_VAR:
+				i++;
+				pto = var;
+
+				while (i < cplen && src[i] >= 3 && src[i] != IAC)
+				{
+					*pto++ = src[i++];
+				}
+				*pto = 0;
+				break;
+
+			case MSSP_VAL:
+				i++;
+				pto = val;
+
+				while (i < cplen && src[i] >= 3 && src[i] != IAC)
+				{
+					*pto++ = src[i++];
+				}
+				*pto = 0;
+
+				client_telopt_debug(ses, "RCVD IAC SB MSSP VAR %-20s VAL %s", var, val);
+
+				check_all_events(ses, SUB_ARG|SUB_SEC, 0, 2, "IAC SB MSSP", var, val);
+				check_all_events(ses, SUB_ARG|SUB_SEC, 1, 2, "IAC SB MSSP %s", var, var, val);
+				break;
+
+			default:
+				i++;
+				break;
+		}
+	}
+
+	client_telopt_debug(ses, "RCVD IAC SB MSSP IAC SE");
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "IAC SB MSSP IAC SE");
+
+	return UMIN(i + 1, cplen);
+}
+
+/*
+	MSDP (Mud Server Data Protocol)
+*/
+
+int client_recv_sb_msdp(struct session *ses, int cplen, unsigned char *src)
+{
+	char var[BUFFER_SIZE], val[BUFFER_SIZE], plain[BUFFER_SIZE], *pto;
+	int i, nest, state[100], last;
+
+	var[0] = val[0] = state[0] = nest = last = 0;
+
+	if (client_skip_sb(ses, cplen, src) > cplen)
+	{
+		return cplen + 1;
+	}
+
+	i = 3;
+	pto = var;
+
+	while (i < cplen && nest < 99)
+	{
+		if (src[i] == IAC && src[i+1] == SE)
+		{
+			break;
+		}
+
+		switch (src[i])
+		{
+			case MSDP_TABLE_OPEN:
+				nest++;
+				state[nest] = 0;
+				last = MSDP_TABLE_OPEN;
+				break;
+
+			case MSDP_TABLE_CLOSE:
+				if (nest)
+				{
+					if (last == MSDP_VAL || last == MSDP_VAR)
+					{
+						*pto++ = '}';
+					}
+					nest--;
+				}
+				if (nest)
+				{
+					*pto++ = '}';
+				}
+				last = MSDP_TABLE_CLOSE;
+				break;
+
+			case MSDP_ARRAY_OPEN:
+				nest++;
+				state[nest] = 1;
+				last = MSDP_ARRAY_OPEN;
+				break;
+
+			case MSDP_ARRAY_CLOSE:
+				if (nest)
+				{
+					if (last == MSDP_VAL)
+					{
+						*pto++ = '}';
+					}
+					nest--;
+				}
+				if (nest)
+				{
+					*pto++ = '}';
+				}
+				last = MSDP_ARRAY_CLOSE;
+				break;
+
+			case MSDP_VAR:
+				if (nest)
+				{
+					if (last == MSDP_VAL)
+					{
+						*pto++ = '}';
+					}
+					*pto++ = '{';
+				}
+				else
+				{
+					*pto = 0;
+
+					if (last)
+					{
+						strip_vt102_codes(val, plain);
+						client_telopt_debug(ses, "RCVD IAC SB MSDP VAR %-20s VAL %s", var, val);
+						check_all_events(ses, SUB_ARG, 1, 3, "IAC SB MSDP %s", var, var, val, plain);
+						check_all_events(ses, SUB_ARG, 0, 3, "IAC SB MSDP", var, val, plain);
+					}
+					pto = var;
+				}
+				last = MSDP_VAR;
+				break;
+
+			case MSDP_VAL:
+				if (nest)
+				{
+					if (last == MSDP_VAR || last == MSDP_VAL)
+					{
+						*pto++ = '}';
+					}
+					if (state[nest])
+					{
+						pto += sprintf(pto, "{%d}", state[nest]++);
+					}
+					*pto++ = '{';
+				}
+				else
+				{
+					*pto = 0;
+
+					if (last != MSDP_VAR)
+					{
+						strip_vt102_codes(val, plain);
+						client_telopt_debug(ses, "RCVD IAC SB MSDP VAR %-20s VAL %s", var, val);
+						check_all_events(ses, SUB_ARG, 1, 3, "IAC SB MSDP %s", var, var, val, plain);
+						check_all_events(ses, SUB_ARG, 0, 3, "IAC SB MSDP", var, val, plain);
+					}
+					pto = val;
+				}
+				last = MSDP_VAL;
+				break;
+
+			case '\r':
+				break;
+
+			case '\\':
+				*pto++ = '\\';
+				*pto++ = '\\';
+				break;
+
+			case '{':
+				*pto++ = '\\';
+				*pto++ = 'x';
+				*pto++ = '7';
+				*pto++ = 'B';
+				break;
+
+			case '}':
+				*pto++ = '\\';
+				*pto++ = 'x';
+				*pto++ = '7';
+				*pto++ = 'D';
+				break;
+
+			case COMMAND_SEPARATOR:
+				*pto++ = '\\';
+				*pto++ = COMMAND_SEPARATOR;
+				break;
+
+			default:
+				*pto++ = src[i];
+				break;
+		}
+		i++;
+	}
+
+	if (src[i] == IAC && nest < 99)
+	{
+		*pto = 0;
+
+		if (last)
+		{
+			strip_vt102_codes(val, plain);
+			client_telopt_debug(ses, "RCVD IAC SB MSDP VAR %-20s VAL %s", var, val);
+			check_all_events(ses, SUB_ARG, 1, 3, "IAC SB MSDP %s", var, var, val, plain);
+			check_all_events(ses, SUB_ARG, 0, 3, "IAC SB MSDP", var, val, plain);
+		}
+		i++;
+	}
+
+	var[0] = val[0] = last = 0;
+
+	i = 3;
+	pto = var;
+
+	while (i < cplen && nest < 99)
+	{
+		if (src[i] == IAC && src[i+1] == SE)
+		{
+			break;
+		}
+
+		switch (src[i])
+		{
+			case MSDP_TABLE_OPEN:
+				*pto++ = '{';
+				nest++;
+				state[nest] = 0;
+				last = MSDP_TABLE_OPEN;
+				break;
+
+			case MSDP_TABLE_CLOSE:
+				if (last == MSDP_VAL || last == MSDP_VAR)
+				{
+					*pto++ = '"';
+				}
+				if (nest)
+				{
+					nest--;
+				}
+				*pto++ = '}';
+				last = MSDP_TABLE_CLOSE;
+				break;
+
+			case MSDP_ARRAY_OPEN:
+				*pto++ = '[';
+				nest++;
+				state[nest] = 1;
+				last = MSDP_ARRAY_OPEN;
+				break;
+
+			case MSDP_ARRAY_CLOSE:
+				if (last == MSDP_VAL || last == MSDP_VAR)
+				{
+					*pto++ = '"';
+				}
+				if (nest)
+				{
+					nest--;
+				}
+				*pto++ = ']';
+				last = MSDP_ARRAY_CLOSE;
+				break;
+
+			case MSDP_VAR:
+				if (nest)
+				{
+					if (last == MSDP_VAL || last == MSDP_VAR)
+					{
+						*pto++ = '"';
+					}
+					if (last == MSDP_VAL || last == MSDP_VAR || last == MSDP_TABLE_CLOSE || last == MSDP_ARRAY_CLOSE)
+					{
+						*pto++ = ',';
+					}
+					*pto++ = '"';
+				}
+				else
+				{
+					*pto = 0;
+
+					if (last)
+					{
+						strip_vt102_codes(val, plain);
+						check_all_events(ses, SUB_ARG, 1, 2, "IAC SB MSDP2JSON %s", var, var, plain);
+						check_all_events(ses, SUB_ARG, 0, 2, "IAC SB MSDP2JSON", var, plain);
+					}
+					pto = var;
+				}
+				last = MSDP_VAR;
+				break;
+
+			case MSDP_VAL:
+				if (nest)
+				{
+					if (last == MSDP_VAR)
+					{
+						*pto++ = '"';
+						*pto++ = ':';
+					}
+					if (last == MSDP_VAL)
+					{
+						*pto++ = '"';
+						*pto++ = ',';
+					}
+
+					if (src[i+1] != MSDP_TABLE_OPEN && src[i+1] != MSDP_ARRAY_OPEN)
+					{
+						*pto++ = '"';
+					}
+				}
+				else
+				{
+					*pto = 0;
+
+					if (last != MSDP_VAR)
+					{
+						strip_vt102_codes(val, plain);
+						check_all_events(ses, SUB_ARG, 1, 2, "IAC SB MSDP2JSON %s", var, var, plain);
+						check_all_events(ses, SUB_ARG, 0, 2, "IAC SB MSDP2JSON", var, plain);
+					}
+					pto = val;
+				}
+				last = MSDP_VAL;
+				break;
+
+			case '\\':
+				*pto++ = '\\';
+				*pto++ = '\\';
+				break;
+
+			case '{':
+				*pto++ = '\\';
+				*pto++ = 'x';
+				*pto++ = '7';
+				*pto++ = 'B';
+				break;
+
+			case '}':
+				*pto++ = '\\';
+				*pto++ = 'x';
+				*pto++ = '7';
+				*pto++ = 'D';
+				break;
+
+			case COMMAND_SEPARATOR:
+				*pto++ = '\\';
+				*pto++ = COMMAND_SEPARATOR;
+				break;
+
+			default:
+				*pto++ = src[i];
+				break;
+		}
+		i++;
+	}
+
+	if (src[i] == IAC && nest < 99)
+	{
+		*pto = 0;
+
+		if (last)
+		{
+			strip_vt102_codes(val, plain);
+			check_all_events(ses, SUB_ARG, 1, 2, "IAC SB MSDP2JSON %s", var, var, plain);
+			check_all_events(ses, SUB_ARG, 0, 2, "IAC SB MSDP2JSON", var, plain);
+		}
+		i++;
+	}
+
+	client_telopt_debug(ses, "RCVD IAC SB MSDP IAC SE");
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "IAC SB MSDP IAC SE");
+
+	return UMIN(i + 1, cplen);
+}
+
+
+/*
+	CHARSET
+*/
+
+int client_recv_sb_charset(struct session *ses, int cplen, unsigned char *src)
+{
+	char buf[BUFFER_SIZE], var[BUFFER_SIZE];
+	char *pto;
+	int i;
+
+	if (client_skip_sb(ses, cplen, src) > cplen)
+	{
+		return cplen + 1;
+	}
+
+	client_telopt_debug(ses, "IAC SB CHARSET %d %d", src[3], src[4]);
+
+	i = 5;
+
+	while (i < cplen && src[i] != SE)
+	{
+		pto = buf;
+
+		while (i < cplen && src[i] != src[4] && src[i] != IAC)
+		{
+			*pto++ = src[i++];
+		}
+		*pto = 0;
+
+		substitute(ses, buf, var, SUB_SEC);
+
+		switch (src[3])
+		{
+			case CHARSET_REQUEST:
+				strcpy(buf, "REQUEST");
+				break;
+			case CHARSET_ACCEPTED:
+				strcpy(buf, "ACCEPTED");
+				break;
+			case CHARSET_REJECTED:
+				strcpy(buf, "REJECTED");
+				break;
+			default:
+				sprintf(buf, "%d", src[4]);
+				break;
+		}
+
+		client_telopt_debug(ses, "IAC SB CHARSET %s %s", buf, var);
+
+		check_all_events(ses, SUB_ARG|SUB_SEC, 0, 2, "IAC SB CHARSET", buf, var);
+		check_all_events(ses, SUB_ARG|SUB_SEC, 2, 2, "IAC SB CHARSET %s %s", buf, var, buf, var);
+
+		if (!check_all_events(ses, SUB_ARG|SUB_SEC, 2, 2, "CATCH IAC SB CHARSET %s %s", buf, var, buf, var))
+		{
+			if (!strcmp(buf, "REQUEST"))
+			{
+				if (!strcasecmp(var, "UTF-8"))
+				{
+					if (HAS_BIT(ses->charset, CHARSET_FLAG_UTF8) && !HAS_BIT(ses->charset, CHARSET_FLAG_ALL_TOUTF8))
+					{
+						telnet_printf(ses, 12, "%c%c%c%c UTF-8%c%c", IAC, SB, TELOPT_CHARSET, CHARSET_ACCEPTED, IAC, SE);
+
+						client_telopt_debug(ses, "SENT IAC SB CHARSET ACCEPTED UTF-8");
+					}
+					else
+					{
+						telnet_printf(ses, 12, "%c%c%c%c UTF-8%c%c", IAC, SB, TELOPT_CHARSET, CHARSET_REJECTED, IAC, SE);
+
+						client_telopt_debug(ses, "SENT IAC SB CHARSET REJECTED UTF-8");
+					}
+				}
+				else if (!strcasecmp(var, "BIG-5"))
+				{
+					if (HAS_BIT(ses->charset, CHARSET_FLAG_BIG5) || HAS_BIT(ses->charset, CHARSET_FLAG_BIG5TOUTF8))
+					{
+						telnet_printf(ses, 11, "%c%c%c%c BIG-5%c%c", IAC, SB, TELOPT_CHARSET, CHARSET_ACCEPTED, IAC, SE);
+
+						client_telopt_debug(ses, "SENT IAC SB CHARSET ACCEPTED BIG-5");
+					}
+					else
+					{
+						telnet_printf(ses, 11, "%c%c%c%c BIG-5%c%c", IAC, SB, TELOPT_CHARSET, CHARSET_REJECTED, IAC, SE);
+
+						client_telopt_debug(ses, "SENT IAC SB CHARSET REJECTED BIG-5");
+					}
+				}
+				else if (!strcasecmp(var, "FANSI"))
+				{
+					if (!check_all_events(ses, SUB_ARG|SUB_SEC, 2, 2, "CATCH IAC SB CHARSET %s %s", buf, var, buf, var))
+					{
+						if (HAS_BIT(ses->charset, CHARSET_FLAG_FANSITOUTF8))
+						{
+							telnet_printf(ses, 11, "%c%c%c%c FANSI%c%c", IAC, SB, TELOPT_CHARSET, CHARSET_ACCEPTED, IAC, SE);
+
+							client_telopt_debug(ses, "SENT IAC SB CHARSET ACCEPTED FANSI");
+						}
+						else
+						{
+							telnet_printf(ses, 11, "%c%c%c%c FANSI%c%c", IAC, SB, TELOPT_CHARSET, CHARSET_REJECTED, IAC, SE);
+
+							client_telopt_debug(ses, "SENT IAC SB CHARSET REJECTED FANSI");
+						}
+					}
+				}
+				else if (!strcasecmp(var, "ISO-8859-1") || !strcasecmp(var, "ISO-1"))
+				{
+					if (!check_all_events(ses, SUB_ARG|SUB_SEC, 2, 2, "CATCH IAC SB CHARSET %s %s", buf, var, buf, var))
+					{
+						if (HAS_BIT(ses->charset, CHARSET_FLAG_ISO1TOUTF8))
+						{
+							telnet_printf(ses, 11, "%c%c%c%c %s%c%c", IAC, SB, TELOPT_CHARSET, CHARSET_ACCEPTED, var, IAC, SE);
+							
+							client_telopt_debug(ses, "SENT IAC SB CHARSET ACCEPTED %s", var);
+						}
+						else
+						{
+							telnet_printf(ses, 11, "%c%c%c%c %s%c%c", IAC, SB, TELOPT_CHARSET, CHARSET_REJECTED, var, IAC, SE);
+
+							client_telopt_debug(ses, "SENT IAC SB CHARSET REJECTED %s", var);
+						}
+					}
+				}
+				else if (!strcasecmp(var, "ISO-8859-2") || !strcasecmp(var, "ISO-2"))
+				{
+					if (!check_all_events(ses, SUB_ARG|SUB_SEC, 2, 2, "CATCH IAC SB CHARSET %s %s", buf, var, buf, var))
+					{
+						if (HAS_BIT(ses->charset, CHARSET_FLAG_ISO2TOUTF8))
+						{
+							telnet_printf(ses, 11, "%c%c%c%c %s%c%c", IAC, SB, TELOPT_CHARSET, CHARSET_ACCEPTED, var, IAC, SE);
+							
+							client_telopt_debug(ses, "SENT IAC SB CHARSET ACCEPTED %s", var);
+						}
+						else
+						{
+							telnet_printf(ses, 11, "%c%c%c%c %s%c%c", IAC, SB, TELOPT_CHARSET, CHARSET_REJECTED, var, IAC, SE);
+
+							client_telopt_debug(ses, "SENT IAC SB CHARSET REJECTED %s", var);
+						}
+					}
+				}
+				else if (!strcasecmp(var, "GBK-1"))
+				{
+					if (!check_all_events(ses, SUB_ARG|SUB_SEC, 2, 2, "CATCH IAC SB CHARSET %s %s", buf, var, buf, var))
+					{
+						if (HAS_BIT(ses->charset, CHARSET_FLAG_GBK1TOUTF8))
+						{
+							telnet_printf(ses, 11, "%c%c%c%c GBK-1%c%c", IAC, SB, TELOPT_CHARSET, CHARSET_ACCEPTED, IAC, SE);
+
+							client_telopt_debug(ses, "SENT IAC SB CHARSET ACCEPTED GBK-1");
+						}
+						else
+						{
+							telnet_printf(ses, 11, "%c%c%c%c GB-18030%c%c", IAC, SB, TELOPT_CHARSET, CHARSET_REJECTED, IAC, SE);
+
+							client_telopt_debug(ses, "SENT IAC SB CHARSET REJECTED GBK-1");
+						}
+					}
+				}
+			}
+		}
+		i++;
+	}
+
+	client_telopt_debug(ses, "IAC SB CHARSET IAC SE");
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "IAC SB CHARSET IAC SE");
+
+	return i + 1;
+}
+
+/*
+	NEW-ENVIRON
+*/
+
+int get_mtts_val(struct session *ses)
+{
+	return
+		(ses->color > 0 ? 1 : 0)
+		+
+		(HAS_BIT(ses->flags, SES_FLAG_SPLIT) ? 0 : 2)
+		+
+		(HAS_BIT(ses->charset, CHARSET_FLAG_UTF8) && !HAS_BIT(ses->charset, CHARSET_FLAG_ALL_TOUTF8) ? 4 : 0)
+		+
+		(ses->color > 16 ? 8 : 0)
+		+
+		(HAS_BIT(ses->flags, SES_FLAG_SCREENREADER) ? 64 : 0)
+		+
+//		proxy ? 128 : 0
+//		+
+		(ses->color > 256 ? 256 : 0)
+		+
+		512;
+}
+
+int client_recv_sb_new_environ(struct session *ses, int cplen, unsigned char *src)
+{
+	char buf[BUFFER_SIZE], var[BUFFER_SIZE], val[BUFFER_SIZE], sub1[NUMBER_SIZE], sub2[NUMBER_SIZE];
+	char *pto;
+	int i;
+
+	var[0] = val[0] = 0;
+
+	if (client_skip_sb(ses, cplen, src) > cplen)
+	{
+		return cplen + 1;
+	}
+
+	client_telopt_debug(ses, "IAC SB NEW-ENVIRON %d %d", src[3], src[4]);
+
+	switch (src[3])
+	{
+		case ENV_IS:
+			strcpy(sub1, "IS");
+			break;
+		case ENV_SEND:
+			strcpy(sub1, "SEND");
+			break;
+		case ENV_INFO:
+			strcpy(sub1, "INFO");
+			break;
+		default:
+			strcpy(sub1, "UNKNOWN");
+			break;
+	}
+
+	i = 4;
+
+	while (i < cplen && src[i] != SE)
+	{
+		switch (src[i])
+		{
+			case ENV_VAR:
+				strcpy(sub2, "VAR");
+				break;
+			case ENV_VAL:
+				strcpy(sub2, "VAL");
+				break;
+			case ENV_USR:
+				strcpy(sub2, "USERVAR");
+				break;
+			default:
+				strcpy(sub2, "UNKNOWN");
+				break;
+		}
+
+		switch (src[i])
+		{
+			case ENV_VAR:
+			case ENV_USR:
+				i++;
+				pto = buf;
+
+				while (i < cplen && src[i] >= 4 && src[i] != IAC)
+				{
+					*pto++ = src[i++];
+				}
+				*pto = 0;
+
+				substitute(ses, buf, var, SUB_SEC);
+
+				if (src[3] == ENV_SEND)
+				{
+					client_telopt_debug(ses, "IAC SB NEW-ENVIRON SEND %s", sub2);
+
+					if (!check_all_events(ses, SUB_ARG|SUB_SEC, 0, 4, "CATCH IAC SB NEW-ENVIRON", sub1, sub2, var, ""))
+					{
+						check_all_events(ses, SUB_ARG|SUB_SEC, 0, 4, "IAC SB NEW-ENVIRON", sub1, sub2, var, "");
+
+						if (!check_all_events(ses, SUB_ARG|SUB_SEC, 1, 4, "CATCH IAC SB NEW-ENVIRON SEND %s", var, sub1, sub2, var, ""))
+						{
+							check_all_events(ses, SUB_ARG|SUB_SEC, 1, 4, "IAC SB NEW-ENVIRON SEND %s", var, sub1, sub2, var, "");
+
+							if (*var == 0)
+							{
+								telnet_printf(ses, -1, "%c%c%c" "%c%c%s%c%s" "%c%c%s%c%s" "%c%c%s%c%s" "%c%c%s%c%s" "%c%c%s%c%s" "%c%c",
+									IAC, SB, TELOPT_NEW_ENVIRON,
+									ENV_IS, ENV_VAR, "CHARSET", ENV_VAL, get_charset(ses),
+									ENV_IS, ENV_VAR, "CLIENT_NAME", ENV_VAL, CLIENT_NAME,
+									ENV_IS, ENV_VAR, "CLIENT_VERSION", ENV_VAL, CLIENT_VERSION,
+									ENV_IS, ENV_VAR, "MTTS", ENV_VAL, get_mtts_val(ses),
+									ENV_IS, ENV_VAR, "TERMINAL_TYPE", ENV_VAL, gtd->term,
+									IAC, SE);
+							}
+							else if (!strcasecmp(var, "CHARSET"))
+							{
+								telnet_printf(ses, -1, "%c%c%c%c%c%s%c%s%c%c", IAC, SB, TELOPT_NEW_ENVIRON, ENV_IS, ENV_VAR, "CHARSET", ENV_VAL, get_charset(ses), IAC, SE);
+
+								client_telopt_debug(ses, "SENT IAC SB NEW-ENVIRON IS VAR %s VAL %s", "CHARSET", get_charset(ses));
+							}
+							else if (!strcasecmp(var, "CLIENT_NAME"))
+							{
+								telnet_printf(ses, -1, "%c%c%c%c%c%s%c%s%c%c", IAC, SB, TELOPT_NEW_ENVIRON, ENV_IS, ENV_VAR, "CLIENT_NAME", ENV_VAL, CLIENT_NAME, IAC, SE);
+
+								client_telopt_debug(ses, "SENT IAC SB NEW-ENVIRON IS VAR %s VAL %s", "CLIENT_NAME", CLIENT_NAME);
+							}
+							else if (!strcasecmp(var, "CLIENT_VERSION"))
+							{
+								telnet_printf(ses, -1, "%c%c%c%c%c%s%c%s%c%c", IAC, SB, TELOPT_NEW_ENVIRON, ENV_IS, ENV_VAR, "CLIENT_VERSION", ENV_VAL, CLIENT_VERSION, IAC, SE);
+
+								client_telopt_debug(ses, "SENT IAC SB NEW-ENVIRON IS VAR %s VAL %s", "CLIENT_VERSION", CLIENT_VERSION);
+							}
+							else if (!strcasecmp(var, "MTTS") || *var == 0)
+							{
+								telnet_printf(ses, -1, "%c%c%c%c%c%s%c%d%c%c", IAC, SB, TELOPT_NEW_ENVIRON, ENV_IS, ENV_VAR, "MTTS", ENV_VAL, get_mtts_val(ses), IAC, SE);
+
+								client_telopt_debug(ses, "SENT IAC SB NEW-ENVIRON IS VAR MTTS VAL %d", get_mtts_val(ses));
+							}
+							else if (!strcasecmp(var, "TERMINAL_TYPE"))
+							{
+								telnet_printf(ses, -1, "%c%c%c%c%c%s%c%s%c%c", IAC, SB, TELOPT_NEW_ENVIRON, ENV_IS, ENV_VAR, "TERMINAL_TYPE", ENV_VAL, gtd->term, IAC, SE);
+
+								client_telopt_debug(ses, "SENT IAC SB NEW-ENVIRON IS VAR TERMINAL_TYPE VAL %s", gtd->term);
+							}
+						}
+					}
+				}
+				break;
+
+			case ENV_VAL:
+				i++;
+				pto = buf;
+
+				while (i < cplen && src[i] >= 4 && src[i] != IAC)
+				{
+					*pto++ = src[i++];
+				}
+				*pto = 0;
+
+				substitute(ses, buf, val, SUB_SEC);
+
+				client_telopt_debug(ses, "IAC SB NEW-ENVIRON %s %s", sub1, sub2);
+
+				check_all_events(ses, SUB_ARG|SUB_SEC, 0, 4, "IAC SB NEW-ENVIRON", sub1, sub2, var, val);
+				check_all_events(ses, SUB_ARG|SUB_SEC, 2, 4, "IAC SB NEW-ENVIRON %s %s", sub1, sub2, sub1, sub2, var, val);
+				break;
+
+			case IAC:
+				client_telopt_debug(ses, "IAC SB NEW-ENVIRON (ERROR) %s %s (%s) (%s)", sub1, sub2, var, val);
+				i++;
+				break;
+
+			default:
+				client_telopt_debug(ses, "IAC SB NEW-ENVIRON (ERROR) %03d %c", src[i], src[i]);
+				i++;
+				break;
+		}
+	}
+
+	client_telopt_debug(ses, "IAC SB NEW-ENVIRON IAC SE");
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "IAC SB NEW-ENVIRON IAC SE");
+
+	return i + 1;
+}
+
+int client_recv_sb_zmp(struct session *ses, int cplen, unsigned char *src)
+{
+	char buf[BUFFER_SIZE], var[BUFFER_SIZE], val[BUFFER_SIZE];
+	char *pto;
+	int i, x;
+
+	var[0] = val[0] = x = 0;
+
+	if (client_skip_sb(ses, cplen, src) > cplen)
+	{
+		return cplen + 1;
+	}
+
+	i = 3;
+
+	while (i < cplen && src[i] != SE)
+	{
+		switch (src[i])
+		{
+			case IAC:
+				i++;
+				break;
+
+			default:
+				pto = buf;
+
+				while (i < cplen && src[i])
+				{
+					*pto++ = src[i++];
+				}
+				*pto = src[i++];
+
+				substitute(ses, buf, x ? val : var, SUB_SEC);
+
+				if (x++)
+				{
+					client_telopt_debug(ses, "IAC SB ZMP %s", var);
+
+					check_all_events(ses, SUB_ARG|SUB_SEC, 1, 1, "IAC SB ZMP %s", var, val);
+				}
+				break;
+		}
+	}
+
+	client_telopt_debug(ses, "IAC SB ZMP %s IAC SE", var);
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 1, 0, "IAC SB ZMP %s IAC SE", var);
+
+	return UMIN(i + 1, cplen);
+}
+
+int client_recv_sb_gmcp(struct session *ses, int cplen, unsigned char *src)
+{
+	char mod[BUFFER_SIZE], val[BUFFER_SIZE], json[BUFFER_SIZE], *pto;
+	int i, state[100], nest, type;
+
+	push_call("client_recv_sb_gmcp(%p,%d,%p)",ses,cplen,src);
+
+	if (client_skip_sb(ses, cplen, src) > cplen)
+	{
+		pop_call();
+		return cplen + 1;
+	}
+
+	mod[0] = val[0] = state[0] = nest = type = 0;
+
+	i = 3;
+
+	pto = mod;
+
+	// space out
+
+	while (i < cplen && src[i] == ' ')
+	{
+		i++;
+	}
+
+	// grab module
+
+	while (i < cplen && src[i] != IAC)
+	{
+		if (src[i] == ' ' || src[i] == '{' || src[i] == '[')
+		{
+			break;
+		}
+		*pto++ = src[i++];
+	}
+
+	*pto = 0;
+
+	// parse JSON content
+
+	pto = val;
+
+	while (i < cplen && src[i] != IAC && nest < 99)
+	{
+		switch (src[i])
+		{
+			case ' ':
+				i++;
+				break;
+
+			case '{':
+				if (nest != 0)
+				{
+					*pto++ = '{';
+				}
+				i++;
+				state[++nest] = 0;
+				break;
+
+			case '}':
+				nest--;
+				i++;
+				if (nest != 0)
+				{
+					*pto++ = '}';
+				}
+				break;
+
+			case '[':
+				if (nest != 0)
+				{
+					*pto++ = '{';
+				}
+				i++;
+				state[++nest] = 1;
+				pto += sprintf(pto, "{%d}", state[nest]);
+				break;
+
+			case ']':
+				nest--;
+				i++;
+				if (nest != 0)
+				{
+					*pto++ = '}';
+				}
+				break;
+
+			case ':':
+				i++;
+				break;
+
+			case ',':
+				i++;
+				if (state[nest])
+				{
+					pto += sprintf(pto, "{%d}", ++state[nest]);
+				}
+				break;
+
+			case '"':
+				i++;
+				if (nest)
+				{
+					*pto++ = '{';
+				}
+				type = 1;
+
+				while (i < cplen && src[i] != IAC && type == 1)
+				{
+					switch (src[i])
+					{
+						case '\\':
+							i++;
+
+							if (i < cplen && src[i] == '"')
+							{
+								*pto++ = src[i++];
+							}
+							else
+							{
+								*pto++ = '\\';
+							}
+							break;
+
+						case '"':
+							i++;
+							type = 0;
+							break;
+
+						case '{':
+							i++;
+							*pto++ = '\\';
+							*pto++ = 'x';
+							*pto++ = '7';
+							*pto++ = 'B';
+							break;
+
+						case '}':
+							i++;
+							*pto++ = '\\';
+							*pto++ = 'x';
+							*pto++ = '7';
+							*pto++ = 'D';
+							break;
+
+						case COMMAND_SEPARATOR:
+							i++;
+							*pto++ = '\\';
+							*pto++ = COMMAND_SEPARATOR;
+							break;
+
+						default:
+							*pto++ = src[i++];
+							break;
+					}
+				}
+
+				if (nest)
+				{
+					*pto++ = '}';
+				}
+				break;
+
+			default:
+				if (nest)
+				{
+					*pto++ = '{';
+				}
+
+				type = 1;
+
+				while (i < cplen && src[i] != IAC && type == 1)
+				{
+					switch (src[i])
+					{
+						case '}':
+						case ']':
+						case ',':
+						case ':':
+							type = 0;
+							break;
+
+						case ' ':
+							i++;
+							break;
+
+						default:
+							*pto++ = src[i++];
+							break;
+					}
+				}
+
+				if (nest)
+				{
+					*pto++ = '}';
+				}
+				break;
+		}
+	}
+	*pto = 0;
+
+	// Raw json data for debugging purposes.
+
+	pto = json;
+	i = 3;
+
+	while (i < cplen && src[i] != IAC)
+	{
+		switch (src[i])
+		{
+			case '\\':
+				i++;
+				*pto++ = '\\';
+				*pto++ = '\\';
+				break;
+
+			case '{':
+				i++;
+				*pto++ = '\\';
+				*pto++ = 'x';
+				*pto++ = '7';
+				*pto++ = 'B';
+				break;
+
+			case '}':
+				i++;
+				*pto++ = '\\';
+				*pto++ = 'x';
+				*pto++ = '7';
+				*pto++ = 'D';
+				break;
+
+			case COMMAND_SEPARATOR:
+				i++;
+				*pto++ = '\\';
+				*pto++ = COMMAND_SEPARATOR;
+				break;
+
+			default:
+				*pto++ = src[i++];
+				break;
+		}
+	}
+	*pto = 0;
+
+	while (i < cplen && src[i] != SE)
+	{
+		i++;
+	}
+
+	client_telopt_debug(ses, "IAC SB GMCP %s IAC SE", mod);
+
+	check_all_events(ses, SUB_ARG, 1, 2, "IAC SB GMCP %s IAC SE", mod, val, json);
+
+	pop_call();
+	return UMIN(i + 1, cplen);
+}
+
+/*
+	MCCP2
+*/
+
+int client_recv_will_mccp2(struct session *ses, int cplen, unsigned char *cpsrc)
+{
+	if (check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "CATCH IAC WILL MCCP2"))
+	{
+		return 3;
+	}
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "IAC WILL MCCP2");
+
+	if (HAS_BIT(ses->flags, SES_FLAG_MCCP))
+	{
+		telnet_printf(ses, 3, "%c%c%c", IAC, DO, TELOPT_MCCP2);
+
+		client_telopt_debug(ses, "SENT IAC DO MCCP2");
+	}
+	else
+	{
+		telnet_printf(ses, 3, "%c%c%c", IAC, DONT, TELOPT_MCCP2);
+
+		client_telopt_debug(ses, "SENT IAC DONT MCCP2 (#CONFIG MCCP HAS BEEN DISABLED)");
+	}
+	return 3;
+}
+
+int client_send_dont_mccp2(struct session *ses, int cplen, unsigned char *cpsrc)
+{
+	telnet_printf(ses, 3, "%c%c%c", IAC, DONT, TELOPT_MCCP2);
+
+	client_telopt_debug(ses, "SENT DONT MCCP2");
+
+	return 3;
+}
+
+
+int client_init_mccp2(struct session *ses, int cplen, unsigned char *cpsrc)
+{
+	if (ses->mccp2)
+	{
+		return 5;
+	}
+
+	ses->mccp2 = (z_stream *) calloc(1, sizeof(z_stream));
+
+	ses->mccp2->data_type = Z_ASCII;
+	ses->mccp2->zalloc    = zlib_alloc;
+	ses->mccp2->zfree     = zlib_free;
+	ses->mccp2->opaque    = NULL;
+
+	if (inflateInit(ses->mccp2) != Z_OK)
+	{
+		tintin_puts2(ses, "MCCP2: FAILED TO INITIALIZE");
+		client_send_dont_mccp2(ses, 0, NULL);
+		free(ses->mccp2);
+		ses->mccp2 = NULL;
+	}
+	else
+	{
+		client_telopt_debug(ses, "MCCP2: INITIALIZED");
+	}
+	return 5;
+}
+
+void client_end_mccp2(struct session *ses)
+{
+	if (ses->mccp2 == NULL)
+	{
+		return;
+	}
+
+	if (deflateEnd(ses->mccp2) != Z_OK)
+	{
+		tintin_printf2(ses, "MCCP2: FAILED TO DEFLATE_END");
+	}
+
+	free(ses->mccp2);
+
+	ses->mccp2 = NULL;
+
+	client_telopt_debug(ses, "MCCP2: COMPRESSION END, DISABLING MCCP2");
+
+	return;
+}
+
+
+// MCCP3
+
+int client_recv_will_mccp3(struct session *ses, int cplen, unsigned char *cpsrc)
+{
+	if (check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "CATCH IAC WILL MCCP3"))
+	{
+		return 3;
+	}
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "IAC WILL MCCP3");
+
+	if (HAS_BIT(ses->flags, SES_FLAG_MCCP))
+	{
+		telnet_printf(ses, 3, "%c%c%c", IAC, DO, TELOPT_MCCP3);
+
+		client_telopt_debug(ses, "SENT IAC DO MCCP3");
+
+		client_init_mccp3(ses);
+
+	}
+	else
+	{
+		telnet_printf(ses, 3, "%c%c%c", IAC, DONT, TELOPT_MCCP3);
+
+		client_telopt_debug(ses, "SENT IAC DONT MCCP3 (#CONFIG MCCP HAS BEEN DISABLED)");
+	}
+	return 3;
+}
+
+int client_recv_dont_mccp3(struct session *ses, int cplen, unsigned char *cpsrc)
+{
+	if (check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "CATCH IAC DONT MCCP3"))
+	{
+	 	return 3;
+	}
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "IAC DONT MCCP3");
+
+	if (ses->mccp3)
+	{
+		client_end_mccp3(ses);
+	}
+	return 3;
+}
+
+int client_recv_wont_mccp3(struct session *ses, int cplen, unsigned char *cpsrc)
+{
+	if (check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "CATCH IAC WONT MCCP3"))
+	{
+	 	return 3;
+	}
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "IAC WONT MCCP3");
+
+	if (ses->mccp3)
+	{
+		client_end_mccp3(ses);
+	}
+	return 3;
+}
+
+int client_init_mccp3(struct session *ses)
+{
+	z_stream *stream;
+
+	if (ses->mccp3)
+	{
+		client_telopt_debug(ses, "MCCP3: ALREADY INITIALIZED");
+
+		return TRUE;
+	}
+
+	stream = calloc(1, sizeof(z_stream));
+
+	stream->next_in	    = NULL;
+	stream->avail_in    = 0;
+
+	stream->next_out    = gtd->mccp_buf;
+	stream->avail_out   = gtd->mccp_len;
+
+	stream->data_type   = Z_ASCII;
+	stream->zalloc      = zlib_alloc;
+	stream->zfree       = zlib_free;
+	stream->opaque      = Z_NULL;
+
+	if (deflateInit(stream, Z_BEST_COMPRESSION) != Z_OK)
+	{
+		client_telopt_debug(ses, "MCCP3: FAILED TO INITIALIZE");
+
+		free(stream);
+
+		return FALSE;
+	}
+
+	telnet_printf(ses, 5, "%c%c%c%c%c", IAC, SB, TELOPT_MCCP3, IAC, SE);
+
+	client_telopt_debug(ses, "SENT IAC SB MCCP3 IAC SE");
+
+	client_telopt_debug(ses, "MCCP3: INITIALIZED");
+
+	ses->mccp3 = stream;
+
+	return TRUE;
+}
+
+void client_end_mccp3(struct session *ses)
+{
+	if (ses->mccp3 == NULL)
+	{
+		return;
+	}
+
+	ses->mccp3->next_in	= NULL;
+	ses->mccp3->avail_in	= 0;
+
+	ses->mccp3->next_out	= gtd->mccp_buf;
+	ses->mccp3->avail_out	= gtd->mccp_len;
+
+	if (deflate(ses->mccp3, Z_FINISH) != Z_STREAM_END)
+	{
+		tintin_printf2(ses, "MCCP3: FAILED TO DEFLATE");
+	}
+
+//	process_compressed(d);
+
+	if (deflateEnd(ses->mccp3) != Z_OK)
+	{
+		tintin_printf2(ses, "MCCP3: FAILED TO DEFLATE_END");
+	}
+
+	free(ses->mccp3);
+
+	ses->mccp3 = NULL;
+
+	client_telopt_debug(ses, "MCCP3: COMPRESSION END, DISABLING MCCP3");
+
+	return;
+}
+
+int client_write_compressed(struct session *ses, char *txt, int length)
+{
+	int result;
+
+	ses->mccp3->next_in    = (unsigned char *) txt;
+	ses->mccp3->avail_in   = length;
+
+	ses->mccp3->next_out   = gtd->mccp_buf;
+	ses->mccp3->avail_out  = gtd->mccp_len;
+
+	if (deflate(ses->mccp3, Z_SYNC_FLUSH) != Z_OK)
+	{
+		syserr_printf(ses, "client_write_compressed: deflate");
+
+		return 0;
+	}
+
+#ifdef HAVE_GNUTLS_H
+
+	if (ses->ssl)
+	{
+		result = gnutls_record_send(ses->ssl, gtd->mccp_buf, gtd->mccp_len - ses->mccp3->avail_out);
+
+		while (result == GNUTLS_E_INTERRUPTED || result == GNUTLS_E_AGAIN)
+		{
+			result = gnutls_record_send(ses->ssl, 0, 0);
+		}
+		return result;
+	}
+	else
+#endif
+
+	result = write(ses->socket, gtd->mccp_buf, gtd->mccp_len - ses->mccp3->avail_out);
+
+	if (result < 1)
+	{
+		syserr_printf(ses, "client_write_compressed: write");
+
+		return -1;
+	}
+
+	return result;
+}
+
+/*
+	Returns the length of a telnet subnegotiation
+*/
+
+int client_skip_sb(struct session *ses, int cplen, unsigned char *cpsrc)
+{
+	int i;
+
+	for (i = 1 ; i < cplen ; i++)
+	{
+		if (cpsrc[i] == SE && cpsrc[i-1] == IAC)
+		{
+			return i + 1;
+		}
+	}
+
+	client_telopt_debug(ses, "SKIP SB (%d)", cplen);
+
+	return cplen + 1;
+}
+
+int client_recv_sb(struct session *ses, int cplen, unsigned char *cpsrc)
+{
+	char *pt1, *pt2, var1[BUFFER_SIZE], var2[BUFFER_SIZE];
+	int i;
+
+	if (client_skip_sb(ses, cplen, cpsrc) > cplen)
+	{
+		return cplen + 1;
+	}
+
+	pt1 = var1;
+	pt2 = var2;
+
+	for (i = 3 ; i < cplen ; i++)
+	{
+		if (cpsrc[i] == IAC && i + 1 < cplen && cpsrc[i+1] == SE)
+		{
+			break;
+		}
+		else
+		{
+			*pt1++ = cpsrc[i];
+
+			sprintf(pt2, "%03d ", cpsrc[i]);
+
+			pt2 += 4;
+		}
+	}
+
+	*pt1 = 0;
+	*pt2 = 0;
+
+	check_all_events(ses, SUB_ARG|SUB_SEC, 1, 2, "IAC SB %s", telopt_table[cpsrc[2]].name, var1, var2);
+
+	return i + 2;
+}

+ 1163 - 0
telopt_server.c

@@ -0,0 +1,1163 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2001-2019 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
+*                                                                             *
+*                      coded by Igor van den Hoven 2009                       *
+******************************************************************************/
+
+#include "tintin.h"
+#include "telnet.h"
+
+void unannounce_support(struct session *ses, struct port_data *buddy);
+void telopt_debug(struct session *ses, char *format, ...);
+void debug_telopts(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen);
+void send_echo_off(struct session *ses, struct port_data *buddy);
+void send_echo_on(struct session *ses, struct port_data *buddy);
+void send_eor(struct session *ses, struct port_data *buddy);
+int process_do_eor(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen );
+int process_will_ttype(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen );
+int process_sb_ttype_is(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen );
+int process_sb_naws(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen );
+int process_will_new_environ(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen );
+int process_sb_new_environ(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen );
+int process_do_charset(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen );
+int process_sb_charset(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen );
+int process_do_msdp(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen );
+int process_sb_msdp(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen );
+int process_do_gmcp(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen );
+int process_sb_gmcp(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen );
+int process_do_mssp(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen );
+int start_mccp2(struct session *ses, struct port_data *buddy);
+void process_mccp2(struct session *ses, struct port_data *buddy);
+int process_do_mccp2(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen );
+int process_dont_mccp2(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen );
+int process_do_mccp3(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen );
+int process_sb_mccp3(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen );
+
+int skip_sb(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen );
+
+#define TELOPT_DEBUG 1
+
+struct iac_type
+{
+	int      size;
+	unsigned char * code;
+	int   (* func) (struct session *ses, struct port_data *buddy, unsigned char *src, int srclen );
+};
+
+struct iac_type iac_server_table [] =
+{
+	{ 3, (unsigned char []) { IAC, DO,   TELOPT_EOR, 0 },                       &process_do_eor},
+
+	{ 3, (unsigned char []) { IAC, WILL, TELOPT_TTYPE, 0 },                     &process_will_ttype},
+	{ 4, (unsigned char []) { IAC, SB,   TELOPT_TTYPE, ENV_IS, 0 },             &process_sb_ttype_is},
+
+	{ 3, (unsigned char []) { IAC, SB,   TELOPT_NAWS, 0 },                      &process_sb_naws},
+
+	{ 3, (unsigned char []) { IAC, WILL, TELOPT_NEW_ENVIRON, 0 },               &process_will_new_environ},
+	{ 3, (unsigned char []) { IAC, SB,   TELOPT_NEW_ENVIRON, 0 },               &process_sb_new_environ},
+
+	{ 3, (unsigned char []) { IAC, DO,   TELOPT_CHARSET, 0 },                   &process_do_charset},
+	{ 3, (unsigned char []) { IAC, SB,   TELOPT_CHARSET, 0 },                   &process_sb_charset},
+
+	{ 3, (unsigned char []) { IAC, DO,   TELOPT_MSSP, 0 },                      &process_do_mssp},
+
+	{ 3, (unsigned char []) { IAC, DO,   TELOPT_MSDP, 0 },                      &process_do_msdp},
+	{ 3, (unsigned char []) { IAC, SB,   TELOPT_MSDP, 0 },                      &process_sb_msdp},
+
+	{ 3, (unsigned char []) { IAC, DO,   TELOPT_GMCP, 0 },                      &process_do_gmcp},
+	{ 3, (unsigned char []) { IAC, SB,   TELOPT_GMCP, 0 },                      &process_sb_gmcp},
+
+	{ 3, (unsigned char []) { IAC, DO,   TELOPT_MCCP2, 0 },                     &process_do_mccp2},
+	{ 3, (unsigned char []) { IAC, DONT, TELOPT_MCCP2, 0 },                     &process_dont_mccp2},
+
+	{ 3, (unsigned char []) { IAC, DO,   TELOPT_MCCP3, 0 },                     &process_do_mccp3},
+	{ 5, (unsigned char []) { IAC, SB,   TELOPT_MCCP3, IAC, SE, 0 },            &process_sb_mccp3},
+
+	{ 0, NULL,                                                                  NULL}
+};
+
+/*
+	Call this to announce support for telopts marked as such in tables.c
+*/
+
+void announce_support(struct session *ses, struct port_data *buddy)
+{
+	int i;
+
+	push_call("announce_support(%p,%p)",ses,buddy);
+	
+	for (i = 0 ; i < 255 ; i++)
+	{
+		if (telopt_table[i].flags)
+		{
+			if (HAS_BIT(telopt_table[i].flags, ANNOUNCE_WILL))
+			{
+				port_telnet_printf(ses, buddy, 3, "%c%c%c", IAC, WILL, i);
+			}
+			if (HAS_BIT(telopt_table[i].flags, ANNOUNCE_DO))
+			{
+				port_telnet_printf(ses, buddy, 3, "%c%c%c", IAC, DO, i);
+			}
+		}
+	}
+	pop_call();
+	return;
+}
+
+/*
+	Call this right before a copyover to reset the telnet state
+*/
+
+void unannounce_support(struct session *ses, struct port_data *buddy)
+{
+	int i;
+
+	for (i = 0 ; i < 255 ; i++)
+	{
+		if (telopt_table[i].flags)
+		{
+			if (HAS_BIT(telopt_table[i].flags, ANNOUNCE_WILL))
+			{
+				port_telnet_printf(ses, buddy, 3, "%c%c%c", IAC, WONT, i);
+			}
+			if (HAS_BIT(telopt_table[i].flags, ANNOUNCE_DO))
+			{
+				port_telnet_printf(ses, buddy, 3, "%c%c%c", IAC, DONT, i);
+			}
+		}
+	}
+}
+
+/*
+	This is the main routine that strips out and handles telopt negotiations.
+	It also deals with \r and \0 so commands are separated by a single \n.
+*/
+
+int server_translate_telopts(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen, unsigned char *out, int outlen)
+{
+	int cnt, skip;
+	unsigned char *pti, *pto;
+
+	push_call("server_translate_telopts(%p,%p,%p,%d,%p,%d)",ses,buddy,src,srclen,out,outlen);
+
+	pti = src;
+	pto = out + outlen;
+
+	if (srclen > 0 && buddy->mccp3)
+	{
+		buddy->mccp3->next_in   = pti;
+		buddy->mccp3->avail_in  = srclen;
+
+		buddy->mccp3->next_out   = gtd->mccp_buf;
+		buddy->mccp3->avail_out  = gtd->mccp_len;
+
+		inflate:
+
+		switch (inflate(buddy->mccp3, Z_SYNC_FLUSH))
+		{
+			case Z_BUF_ERROR:
+				if (buddy->mccp3->avail_out == 0)
+				{
+					gtd->mccp_len *= 2;
+					gtd->mccp_buf  = (unsigned char *) realloc(gtd->mccp_buf, gtd->mccp_len);
+
+					buddy->mccp3->avail_out = gtd->mccp_len / 2;
+					buddy->mccp3->next_out  = gtd->mccp_buf + gtd->mccp_len / 2;
+
+					goto inflate;
+				}
+				else
+				{
+					port_socket_printf(ses, buddy, "%c%c%c", IAC, DONT, TELOPT_MCCP3);
+					inflateEnd(buddy->mccp3);
+					free(buddy->mccp3);
+					buddy->mccp3 = NULL;
+					srclen = 0;
+				}
+				break;
+
+			case Z_OK:
+				if (buddy->mccp3->avail_out == 0)
+				{
+					gtd->mccp_len *= 2;
+					gtd->mccp_buf  = (unsigned char *) realloc(gtd->mccp_buf, gtd->mccp_len);
+
+					buddy->mccp3->avail_out = gtd->mccp_len / 2;
+					buddy->mccp3->next_out  = gtd->mccp_buf + gtd->mccp_len / 2;
+
+					goto inflate;
+				}
+				srclen = buddy->mccp3->next_out - gtd->mccp_buf;
+				pti = gtd->mccp_buf;
+
+				if (srclen + outlen > BUFFER_SIZE)
+				{
+					srclen = BUFFER_SIZE - outlen - 1;
+				}
+				break;
+
+			case Z_STREAM_END:
+				port_log_printf(ses, buddy, "MCCP3: Compression end, disabling MCCP3.");
+
+				skip = buddy->mccp3->next_out - gtd->mccp_buf;
+
+				pti += (srclen - buddy->mccp3->avail_in);
+				srclen = buddy->mccp3->avail_in;
+
+				inflateEnd(buddy->mccp3);
+				free(buddy->mccp3);
+				buddy->mccp3 = NULL;
+
+				while (skip + srclen + 1 > gtd->mccp_len)
+				{
+					gtd->mccp_len *= 2;
+					gtd->mccp_buf  = (unsigned char *) realloc(gtd->mccp_buf, gtd->mccp_len);
+				}
+				memcpy(gtd->mccp_buf + skip, pti, srclen);
+				pti = gtd->mccp_buf;
+				srclen += skip;
+				break;
+
+			default:
+				port_log_printf(ses, buddy, "MCCP3: Compression error, disabling MCCP3.");
+
+				syserr_printf(ses, "server_translate_telopts: inflate:");
+
+				port_socket_printf(ses, buddy, "%c%c%c", IAC, DONT, TELOPT_MCCP3);
+				inflateEnd(buddy->mccp3);
+				free(buddy->mccp3);
+				buddy->mccp3 = NULL;
+				srclen = 0;
+				break;
+		}
+	}
+
+	// packet patching
+
+	if (buddy->teltop)
+	{
+		if (buddy->teltop + srclen + 1 < BUFFER_SIZE)
+		{
+			memcpy(buddy->telbuf + buddy->teltop, pti, srclen);
+
+			srclen += buddy->teltop;
+
+			pti = (unsigned char *) buddy->telbuf;
+		}
+		else
+		{
+			port_log_printf(ses, buddy, "server_translate_telopts: buffer overflow.");
+		}
+		buddy->teltop = 0;
+	}
+
+	while (srclen > 0)
+	{
+		switch (*pti)
+		{
+			case IAC:
+				skip = 2;
+
+				debug_telopts(ses, buddy, pti, srclen);
+
+				for (cnt = 0 ; iac_server_table[cnt].code ; cnt++)
+				{
+					if (srclen < iac_server_table[cnt].size)
+					{
+						if (!memcmp(pti, iac_server_table[cnt].code, srclen))
+						{
+							skip = iac_server_table[cnt].size;
+
+							break;
+						}
+					}
+					else
+					{
+						if (!memcmp(pti, iac_server_table[cnt].code, iac_server_table[cnt].size))
+						{
+							skip = iac_server_table[cnt].func(ses, buddy, pti, srclen);
+
+							if (iac_server_table[cnt].func == process_sb_mccp3)
+							{
+								pop_call();
+								return server_translate_telopts(ses, buddy, pti + skip, srclen - skip, out, pto - out);
+							}
+							break;
+						}
+					}
+				}
+
+				if (iac_server_table[cnt].code == NULL && srclen > 1)
+				{
+					switch (pti[1])
+					{
+						case WILL:
+						case DO:
+						case WONT:
+						case DONT:
+							skip = 3;
+							break;
+
+						case SB:
+							skip = skip_sb(ses, buddy, pti, srclen);
+							break;
+
+						case IAC:
+							*pto++ = *pti++;
+							srclen--;
+							skip = 1;
+							break;
+
+						default:
+							if (TELCMD_OK(pti[1]))
+							{
+								skip = 2;
+							}
+							else
+							{
+								skip = 1;
+							}
+							break;
+					}
+				}
+
+				if (skip <= srclen)
+				{
+					pti += skip;
+					srclen -= skip;
+				}
+				else
+				{
+					memcpy(buddy->telbuf, pti, srclen);
+					buddy->teltop = srclen;
+
+					*pto = 0;
+					pop_call();
+					return strlen((char *) out);
+				}
+				break;
+
+			case '\r':
+				if (srclen > 1 && pti[1] == '\0')
+				{
+					*pto++ = '\n';
+				}
+				pti++;
+				srclen--;
+				break;
+
+			case '\0':
+				pti++;
+				srclen--;
+				break;
+
+			default:
+				*pto++ = *pti++;
+				srclen--;
+				break;
+		}
+	}
+	*pto = 0;
+
+	pop_call();
+	return strlen((char *) out);
+}
+
+void telopt_debug(struct session *ses, char *format, ...)
+{
+	char buf[BUFFER_SIZE];
+	va_list args;
+
+	if (HAS_BIT(ses->telopts, TELOPT_FLAG_DEBUG))
+	{
+		va_start(args, format);
+		vsprintf(buf, format, args);
+		va_end(args);
+
+		tintin_puts(ses, buf);
+	}
+}
+
+void debug_telopts(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen)
+{
+	if (srclen > 1 && TELOPT_DEBUG)
+	{
+		switch(src[1])
+		{
+			case IAC:
+				tintin_printf2(ses, "RCVD IAC IAC");
+				break;
+
+			case DO:
+			case DONT:
+			case WILL:
+			case WONT:
+			case SB:
+				if (srclen > 2)
+				{
+					if (src[1] == SB)
+					{
+						if (skip_sb(ses, buddy, src, srclen) == srclen + 1)
+						{
+							tintin_printf2(ses, "RCVD IAC SB %s ?", TELOPT(src[2]));
+						}
+						else
+						{
+							tintin_printf2(ses, "RCVD IAC SB %s IAC SE", TELOPT(src[2]));
+						}
+					}
+					else
+					{
+						tintin_printf2(ses, "RCVD IAC %s %s", TELCMD(src[1]), TELOPT(src[2]));
+					}
+				}
+				else
+				{
+					tintin_printf2(ses, "RCVD IAC %s ?", TELCMD(src[1]));
+				}
+				break;
+
+			default:
+				if (TELCMD_OK(src[1]))
+				{
+					tintin_printf2(ses, "RCVD IAC %s", TELCMD(src[1]));
+				}
+				else
+				{
+					tintin_printf2(ses, "RCVD IAC %d", src[1]);
+				}
+				break;
+		}
+	}
+	else
+	{
+		tintin_printf2(ses, "RCVD IAC ?");
+	}
+}
+
+/*
+	Send to client to have it disable local echo
+*/
+
+void send_echo_off(struct session *ses, struct port_data *buddy)
+{
+	SET_BIT(buddy->comm_flags, COMM_FLAG_PASSWORD);
+
+	port_socket_printf(ses, buddy, "%c%c%c", IAC, WILL, TELOPT_ECHO);
+}
+
+/*
+	Send to client to have it enable local echo
+*/
+
+void send_echo_on(struct session *ses, struct port_data *buddy)
+{
+	DEL_BIT(buddy->comm_flags, COMM_FLAG_PASSWORD);
+
+	port_socket_printf(ses, buddy, "%c%c%c", IAC, WONT, TELOPT_ECHO);
+}
+
+/*
+	Send right after the prompt to mark it as such.
+*/
+
+void send_eor(struct session *ses, struct port_data *buddy)
+{
+	if (HAS_BIT(buddy->comm_flags, COMM_FLAG_EOR))
+	{
+		port_socket_printf(ses, buddy, "%c%c", IAC, EOR);
+	}
+}
+
+/*
+	End Of Record negotiation - not enabled by default in tables.c
+*/
+
+int process_do_eor(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen )
+{
+	SET_BIT(buddy->comm_flags, COMM_FLAG_EOR);
+
+	return 3;
+}
+
+/*
+	Terminal Type negotiation - make sure buddy->ttype is initialized.
+*/
+
+int process_will_ttype(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen )
+{
+	if (*buddy->ttype == 0)
+	{
+		// Request the first three terminal types to see if MTTS is supported, next reset to default.
+
+		port_socket_printf(ses, buddy, "%c%c%c%c%c%c", IAC, SB, TELOPT_TTYPE, ENV_SEND, IAC, SE);
+		port_socket_printf(ses, buddy, "%c%c%c%c%c%c", IAC, SB, TELOPT_TTYPE, ENV_SEND, IAC, SE);
+		port_socket_printf(ses, buddy, "%c%c%c%c%c%c", IAC, SB, TELOPT_TTYPE, ENV_SEND, IAC, SE);
+		port_socket_printf(ses, buddy, "%c%c%c", IAC, DONT, TELOPT_TTYPE);
+	}
+	return 3;
+}
+
+int process_sb_ttype_is(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen )
+{
+	char val[BUFFER_SIZE];
+	char *pto;
+	int i;
+
+	if (skip_sb(ses, buddy, src, srclen) > srclen)
+	{
+		return srclen + 1;
+	}
+
+	pto = val;
+
+	for (i = 4 ; i < srclen && src[i] != SE ; i++)
+	{
+		switch (src[i])
+		{
+			default:			
+				*pto++ = src[i];
+				break;
+
+			case IAC:
+				*pto = 0;
+
+				if (TELOPT_DEBUG)
+				{
+					tintin_printf2(ses, "INFO IAC SB TTYPE RCVD VAL %s.", val);
+				}
+
+				if (*buddy->ttype == 0)
+				{
+					RESTRING(buddy->ttype, val);
+				}
+				else
+				{
+					if (sscanf(val, "MTTS %lld", &buddy->mtts_flags) == 1)
+					{
+						if (HAS_BIT(buddy->mtts_flags, MTTS_FLAG_256COLORS))
+						{
+							SET_BIT(buddy->comm_flags, COMM_FLAG_256COLORS);
+						}
+
+						if (HAS_BIT(buddy->mtts_flags, MTTS_FLAG_UTF8))
+						{
+							SET_BIT(buddy->comm_flags, COMM_FLAG_UTF8);
+						}
+					}
+
+					if (strstr(val, "-256color") || strstr(val, "-256COLOR") || strcasecmp(val, "xterm"))
+					{
+						SET_BIT(buddy->comm_flags, COMM_FLAG_256COLORS);
+					}
+				}
+				break;
+		}
+	}
+	return i + 1;
+}
+
+/*
+	NAWS: Negotiate About Window Size
+*/
+
+int process_sb_naws(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen )
+{
+	int i, j;
+
+	buddy->cols = buddy->rows = 0;
+
+	if (skip_sb(ses, buddy, src, srclen) > srclen)
+	{
+		return srclen + 1;
+	}
+
+	for (i = 3, j = 0 ; i < srclen && j < 4 ; i++, j++)
+	{
+		switch (j)
+		{
+			case 0:
+				buddy->cols += (src[i] == IAC) ? src[i++] * 256 : src[i] * 256;
+				break;
+			case 1:
+				buddy->cols += (src[i] == IAC) ? src[i++] : src[i];
+				break;
+			case 2:
+				buddy->rows += (src[i] == IAC) ? src[i++] * 256 : src[i] * 256;
+				break;
+			case 3:
+				buddy->rows += (src[i] == IAC) ? src[i++] : src[i];
+				break;
+		}
+	}
+
+	if (TELOPT_DEBUG)
+	{
+		tintin_printf2(ses, "INFO IAC SB NAWS RCVD ROWS %d COLS %d", buddy->rows, buddy->cols);
+	}
+
+	return skip_sb(ses, buddy, src, srclen);
+}
+
+/*
+	NEW ENVIRON, used here to discover Windows telnet.
+*/
+
+int process_will_new_environ(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen )
+{
+	port_socket_printf(ses, buddy, "%c%c%c%c%c%s%c%c", IAC, SB, TELOPT_NEW_ENVIRON, ENV_SEND, ENV_VAR, "SYSTEMTYPE", IAC, SE);
+
+	return 3;
+}
+
+int process_sb_new_environ(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen )
+{
+	char var[BUFFER_SIZE], val[BUFFER_SIZE];
+	char *pto;
+	int i;
+
+	if (skip_sb(ses, buddy, src, srclen) > srclen)
+	{
+		return srclen + 1;
+	}
+
+	var[0] = val[0] = 0;
+
+	i = 4;
+
+	while (i < srclen && src[i] != SE)
+	{
+		switch (src[i])
+		{
+			case ENV_VAR:
+			case ENV_USR:
+				i++;
+				pto = var;
+
+				while (i < srclen && src[i] >= 32 && src[i] != IAC)
+				{
+					*pto++ = src[i++];
+				}
+				*pto = 0;
+
+				if (src[i] != ENV_VAL)
+				{
+					tintin_printf2(ses, "INFO IAC SB NEW-ENVIRON RCVD %d VAR %s", src[3], var);
+				}
+				break;
+
+			case ENV_VAL:
+				i++;
+				pto = val;
+
+				while (i < srclen && src[i] >= 32 && src[i] != IAC)
+				{
+					*pto++ = src[i++];
+				}
+				*pto = 0;
+
+				if (TELOPT_DEBUG)
+				{
+					tintin_printf2(ses, "INFO IAC SB NEW-ENVIRON RCVD %d VAR %s VAL %s", src[3], var, val);
+				}
+
+				if (src[3] == ENV_IS)
+				{
+					// Detect Windows telnet and enable remote echo.
+
+					if (!strcasecmp(var, "SYSTEMTYPE") && !strcasecmp(val, "WIN32"))
+					{
+						if (!strcasecmp(buddy->ttype, "ANSI"))
+						{
+							SET_BIT(buddy->comm_flags, COMM_FLAG_REMOTEECHO);
+
+							RESTRING(buddy->ttype, "WINDOWS TELNET");
+						}
+					}
+
+					// Get the real IP address when connecting to mudportal and other MTTS compliant proxies.
+
+					if (!strcasecmp(var, "IPADDRESS"))
+					{
+						RESTRING(buddy->proxy, val);
+					}
+				}
+				break;
+
+			default:
+				i++;
+				break;
+		}
+	}
+	return i + 1;
+}
+
+/*
+	CHARSET, used to detect UTF-8 support
+*/
+
+int process_do_charset(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen )
+{
+	port_socket_printf(ses, buddy, "%c%c%c%c%c%s%c%c", IAC, SB, TELOPT_CHARSET, CHARSET_REQUEST, ' ', "UTF-8", IAC, SE);
+
+	return 3;
+}
+
+int process_sb_charset(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen )
+{
+	char val[BUFFER_SIZE];
+	char *pto;
+	int i;
+
+	if (skip_sb(ses, buddy, src, srclen) > srclen)
+	{
+		return srclen + 1;
+	}
+
+	val[0] = 0;
+
+	i = 5;
+
+	while (i < srclen && src[i] != SE && src[i] != src[4])
+	{
+		pto = val;
+
+		while (i < srclen && src[i] != src[4] && src[i] != IAC)
+		{
+			*pto++ = src[i++];
+		}
+		*pto = 0;
+
+		if (TELOPT_DEBUG)
+		{
+			tintin_printf2(ses, "INFO IAC SB CHARSET RCVD %d VAL %s", src[3], val);
+		}
+
+		if (src[3] == CHARSET_ACCEPTED)
+		{
+			if (!strcasecmp(val, "UTF-8"))
+			{
+				SET_BIT(buddy->comm_flags, COMM_FLAG_UTF8);
+			}
+		}
+		else if (src[3] == CHARSET_REJECTED)
+		{
+			if (!strcasecmp(val, "UTF-8"))
+			{
+				DEL_BIT(buddy->comm_flags, COMM_FLAG_UTF8);
+			}
+		}
+		i++;
+	}
+	return i + 1;
+}
+
+/*
+	MSDP: Mud Server Data Protocol
+
+	http://tintin.sourceforge.net/msdp
+*/
+
+int process_do_msdp(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen )
+{
+	int index;
+
+	if (buddy->msdp_data)
+	{
+		return 3;
+	}
+
+	buddy->msdp_data = (struct msdp_data **) calloc(gtd->msdp_table_size, sizeof(struct msdp_data *));
+
+	for (index = 0 ; index < gtd->msdp_table_size ; index++)
+	{
+		buddy->msdp_data[index] = (struct msdp_data *) calloc(1, sizeof(struct msdp_data));
+
+		buddy->msdp_data[index]->flags = msdp_table[index].flags;
+		buddy->msdp_data[index]->value = strdup("");
+	}
+
+	tintin_printf2(ses, "INFO MSDP INITIALIZED");
+
+	// Easiest to handle variable initialization here.
+
+	msdp_update_var(ses, buddy, "SPECIFICATION", "http://tintin.sourceforge.net/msdp");
+
+	msdp_update_varf(ses, buddy, "SCREEN_ROWS",     "%d", gtd->screen->rows);
+	msdp_update_varf(ses, buddy, "SCREEN_COLS",     "%d", gtd->screen->cols);
+	msdp_update_varf(ses, buddy, "SCREEN_HEIGHT",   "%d", gtd->screen->height);
+	msdp_update_varf(ses, buddy, "SCREEN_WIDTH",    "%d", gtd->screen->width);
+
+	msdp_update_varf(ses, buddy, "SCREEN_POSITION_HEIGHT", "%d", gtd->screen->pos_height);
+	msdp_update_varf(ses, buddy, "SCREEN_POSITION_WIDTH",  "%d", gtd->screen->pos_width);
+
+	msdp_update_varf(ses, buddy, "SCREEN_FOCUS",     "%d", gtd->screen->focus);
+	msdp_update_varf(ses, buddy, "SCREEN_MINIMIZED", "%d", gtd->screen->minimized);
+
+	return 3;
+}
+
+int process_sb_msdp(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen )
+{
+	char var[BUFFER_SIZE], val[BUFFER_SIZE];
+	char *pto;
+	int i, nest;
+
+	if (skip_sb(ses, buddy, src, srclen) > srclen)
+	{
+		return srclen + 1;
+	}
+
+	var[0] = val[0] = 0;
+
+	i = 3;
+	nest = 0;
+
+	while (i < srclen && src[i] != SE)
+	{
+		switch (src[i])
+		{
+			case MSDP_VAR:
+				i++;
+				pto = var;
+
+				while (i < srclen && src[i] != MSDP_VAL && src[i] != IAC)
+				{
+					*pto++ = src[i++];
+				}
+				*pto = 0;
+
+				break;
+
+			case MSDP_VAL:
+				i++;
+				pto = val;
+
+				while (i < srclen && src[i] != IAC)
+				{
+					if (src[i] == MSDP_TABLE_OPEN || src[i] == MSDP_ARRAY_OPEN)
+					{
+						nest++;
+					}
+					else if (src[i] == MSDP_TABLE_CLOSE || src[i] == MSDP_ARRAY_CLOSE)
+					{
+						nest--;
+					}
+					else if (nest == 0 && (src[i] == MSDP_VAR || src[i] == MSDP_VAL))
+					{
+						break;
+					}
+					*pto++ = src[i++];
+				}
+				*pto = 0;
+
+				if (nest == 0)
+				{
+					if (buddy->msdp_data)
+					{
+						process_msdp_varval(ses, buddy, var, val);
+					}
+				}
+				break;
+
+			default:
+				i++;
+				break;
+		}
+	}
+	return i + 1;
+}
+
+// MSDP over GMCP
+
+int process_do_gmcp(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen )
+{
+	if (buddy->msdp_data)
+	{
+		return 3;
+	}
+	tintin_printf2(ses, "INFO MSDP OVER GMCP INITIALIZED");
+
+	SET_BIT(buddy->comm_flags, COMM_FLAG_GMCP);
+
+	return process_do_msdp(ses, buddy, src, srclen);
+}
+
+int process_sb_gmcp(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen )
+{
+	char out[BUFFER_SIZE];
+	int outlen, skiplen;
+
+	skiplen = skip_sb(ses, buddy, src, srclen);
+
+	if (skiplen > srclen)
+	{
+		return srclen + 1;
+	}
+
+	outlen = json2msdp(src, srclen, out);
+
+	process_sb_msdp(ses, buddy, (unsigned char *) out, outlen);
+
+	return skiplen;
+}
+
+/*
+	MSSP: Mud Server Status Protocol
+
+	http://tintin.sourceforge.net/mssp
+
+	Uncomment and update as needed
+*/
+
+int process_do_mssp(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen )
+{
+	char buffer[BUFFER_SIZE] = { 0 };
+
+	cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "NAME",              MSSP_VAL, "TINTIN COMMANDER");
+	cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "PLAYERS",           MSSP_VAL, ses->port->total);
+	cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "UPTIME",            MSSP_VAL, ses->created);
+
+//	cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "HOSTNAME",          MSSP_VAL, "example.com");
+	cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "PORT",              MSSP_VAL, ses->port->port);
+
+	cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "CODEBASE",          MSSP_VAL, CLIENT_NAME);
+//	cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "CONTACT",           MSSP_VAL, "mud@example.com");
+//	cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "LANGUAGE",          MSSP_VAL, "English");
+//	cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "MINIMUM AGE",       MSSP_VAL, "13");
+	cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "WEBSITE",           MSSP_VAL, "https://tintin.sourceforge.io");
+
+	cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "FAMILY",            MSSP_VAL, "TINTIN");
+	cat_sprintf(buffer, "%c%s%c%s", MSSP_VAR, "INTERMUD",          MSSP_VAL, "Arachnos");
+
+	cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "ANSI",              MSSP_VAL, 1);
+	cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "MCCP",              MSSP_VAL, 1);
+	cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "MSDP",              MSSP_VAL, 1);
+//	cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "MSP",               MSSP_VAL, 0);
+	cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "UTF-8",             MSSP_VAL, 1);
+	cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "VT100",             MSSP_VAL, 1);
+	cat_sprintf(buffer, "%c%s%c%d", MSSP_VAR, "XTERM 256 COLORS",  MSSP_VAL, 1);
+
+	port_socket_printf(ses, buddy, "%c%c%c%s%c%c", IAC, SB, TELOPT_MSSP, buffer, IAC, SE);
+
+	return 3;
+}
+
+/*
+	MCCP: Mud Client Compression Protocol
+*/
+
+int start_mccp2(struct session *ses, struct port_data *buddy)
+{
+	z_stream *stream;
+
+	if (buddy->mccp2)
+	{
+		return TRUE;
+	}
+
+	stream = calloc(1, sizeof(z_stream));
+
+	stream->next_in	    = NULL;
+	stream->avail_in    = 0;
+
+	stream->next_out    = gtd->mccp_buf;
+	stream->avail_out   = gtd->mccp_len;
+
+	stream->data_type   = Z_ASCII;
+	stream->zalloc      = zlib_alloc;
+	stream->zfree       = zlib_free;
+	stream->opaque      = Z_NULL;
+
+	/*
+		12, 5 = 32K of memory, more than enough
+	*/
+
+	if (deflateInit2(stream, Z_BEST_COMPRESSION, Z_DEFLATED, 12, 5, Z_DEFAULT_STRATEGY) != Z_OK)
+	{
+		tintin_printf2(ses, "start_mccp2: failed deflateInit2");
+		free(stream);
+
+		return FALSE;
+	}
+
+	port_socket_printf(ses, buddy, "%c%c%c%c%c", IAC, SB, TELOPT_MCCP2, IAC, SE);
+
+	/*
+		The above call must send all pending output to the descriptor, since from now on we'll be compressing.
+	*/
+
+	buddy->mccp2 = stream;
+
+	return TRUE;
+}
+
+
+void end_mccp2(struct session *ses, struct port_data *buddy)
+{
+	if (buddy->mccp2 == NULL)
+	{
+		return;
+	}
+
+	buddy->mccp2->next_in	= NULL;
+	buddy->mccp2->avail_in	= 0;
+
+	buddy->mccp2->next_out	= gtd->mccp_buf;
+	buddy->mccp2->avail_out	= gtd->mccp_len;
+
+	if (deflate(buddy->mccp2, Z_FINISH) != Z_STREAM_END)
+	{
+		tintin_printf2(ses, "end_mccp2: failed to deflate");
+	}
+
+	if (!HAS_BIT(buddy->comm_flags, COMM_FLAG_DISCONNECT))
+	{
+		process_mccp2(ses, buddy);
+	}
+
+	if (deflateEnd(buddy->mccp2) != Z_OK)
+	{
+		tintin_printf2(ses, "end_mccp2: failed to deflateEnd");
+	}
+
+	free(buddy->mccp2);
+
+	buddy->mccp2 = NULL;
+
+	tintin_printf2(ses, "MCCP2: COMPRESSION END");
+
+	return;
+}
+
+
+void write_mccp2(struct session *ses, struct port_data *buddy, char *txt, int length)
+{
+	buddy->mccp2->next_in    = (unsigned char *) txt;
+	buddy->mccp2->avail_in   = length;
+
+	buddy->mccp2->next_out   = (unsigned char *) gtd->mccp_buf;
+	buddy->mccp2->avail_out  = gtd->mccp_len;
+
+	if (deflate(buddy->mccp2, Z_SYNC_FLUSH) != Z_OK)
+	{
+		return;
+	}
+
+	process_mccp2(ses, buddy);
+
+	return;
+}
+
+void process_mccp2(struct session *ses, struct port_data *buddy)
+{
+	if (HAS_BIT(buddy->flags, PORT_FLAG_LINKLOST))
+	{
+		return;
+	}
+
+	if (write(buddy->fd, gtd->mccp_buf, gtd->mccp_len - buddy->mccp2->avail_out) < 1)
+	{
+		syserr_printf(ses, "process_mccp2: write");
+
+		SET_BIT(buddy->comm_flags, COMM_FLAG_DISCONNECT);
+	}
+}
+
+int process_do_mccp2(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen )
+{
+	start_mccp2(ses, buddy);
+
+	return 3;
+}
+
+int process_dont_mccp2(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen )
+{
+	end_mccp2(ses, buddy);
+
+	return 3;
+}
+
+// MCCP3
+
+int process_do_mccp3(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen )
+{
+	return 3;
+}
+
+int process_sb_mccp3(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen )
+{
+	if (buddy->mccp3)
+	{
+		tintin_printf2(ses, "\e[1;31mERROR: MCCP3 ALREADY INITIALIZED");
+		return 5;
+	}
+
+	buddy->mccp3 = (z_stream *) calloc(1, sizeof(z_stream));
+
+	buddy->mccp3->data_type = Z_ASCII;
+	buddy->mccp3->zalloc    = zlib_alloc;
+	buddy->mccp3->zfree     = zlib_free;
+	buddy->mccp3->opaque    = NULL;
+
+	if (inflateInit(buddy->mccp3) != Z_OK)
+	{
+		tintin_printf2(ses, "INFO IAC SB MCCP3 FAILED TO INITIALIZE");
+
+		port_socket_printf(ses, buddy, "%c%c%c", IAC, WONT, TELOPT_MCCP3);
+
+		free(buddy->mccp3);
+		buddy->mccp3 = NULL;
+	}
+	else
+	{
+		tintin_printf2(ses, "INFO IAC SB MCCP3 INITIALIZED");
+	}
+	return 5;
+}
+
+void end_mccp3(struct session *ses, struct port_data *buddy)
+{
+	if (buddy->mccp3)
+	{
+		tintin_printf2(ses, "MCCP3: COMPRESSION END");
+		inflateEnd(buddy->mccp3);
+		free(buddy->mccp3);
+		buddy->mccp3 = NULL;
+	}
+}
+
+int skip_sb(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen )
+{
+	int i;
+
+	for (i = 1 ; i < srclen ; i++)
+	{
+		if (src[i] == SE && src[i-1] == IAC)
+		{
+			return i + 1;
+		}
+	}
+
+	return srclen + 1;
+}

+ 239 - 0
terminal.c

@@ -0,0 +1,239 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2006                       *
+******************************************************************************/
+
+
+#include "tintin.h"
+
+#ifdef HAVE_SYS_IOCTL_H
+  #include <sys/ioctl.h>
+#endif
+#include <termios.h>
+
+void init_terminal(struct session *ses)
+{
+	struct termios io;
+
+	if (tcgetattr(0, &gtd->old_terminal))
+	{
+		syserr_fatal(-1, "init_terminal: tcgetattr 1");
+	}
+
+	io = gtd->old_terminal;
+
+	/*
+		Canonical mode off
+	*/
+
+	DEL_BIT(io.c_lflag, ICANON);
+
+	io.c_cc[VMIN]   = 1;
+	io.c_cc[VTIME]  = 0;
+	io.c_cc[VSTART] = 255;
+	io.c_cc[VSTOP]  = 255;
+	io.c_cc[VINTR]  = 4; // ctrl-d
+
+	/*
+		Make the terminalal as raw as possible
+	*/
+
+/*
+	DEL_BIT(io.c_iflag, IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
+	DEL_BIT(io.c_oflag, OPOST);
+	DEL_BIT(io.c_cflag, CSIZE|PARENB);
+*/
+
+	DEL_BIT(io.c_lflag, ECHO|ECHONL|IEXTEN|ISIG);
+//	DEL_BIT(io.c_lflag, ECHO|ECHONL|IEXTEN|ISIG);
+
+	SET_BIT(io.c_cflag, CS8);
+
+	if (tcsetattr(0, TCSANOW, &io))
+	{
+		syserr_printf(ses, "init_terminal: tcsetattr");
+	}
+
+	if (tcgetattr(0, &gts->cur_terminal))
+	{
+		syserr_fatal(-1, "init_terminal: tcgetattr 2");
+	}
+
+	print_stdout("\e=");
+	print_stdout("\e[>4;1m");
+}
+
+void reset_terminal(struct session *ses)
+{
+	if (gtd->detach_port == 0)
+	{
+		if (tcsetattr(0, TCSANOW, &gtd->old_terminal))
+		{
+			syserr_printf(ses, "reset_terminal: tcsetattr");
+		}
+	}
+
+	if (HAS_BIT(gtd->flags, TINTIN_FLAG_MOUSETRACKING))
+	{
+		print_stdout("\e[?1000l\e[?1002l\e[?1004l\e[?1006l");
+	}
+	print_stdout("\e[?25h");
+	print_stdout("\e[23t");
+	print_stdout("\e[>4n");
+}
+
+
+void save_session_terminal(struct session *ses)
+{
+	tcgetattr(0, &ses->cur_terminal);
+}
+
+void refresh_session_terminal(struct session *ses)
+{
+//	tcsetattr(0, TCSANOW, &ses->cur_terminal);
+}
+
+void echo_off(struct session *ses)
+{
+	struct termios io;
+
+	tcgetattr(STDIN_FILENO, &io);
+
+	DEL_BIT(io.c_lflag, ECHO|ECHONL);
+
+	tcsetattr(STDIN_FILENO, TCSADRAIN, &io);
+}
+
+void echo_on(struct session *ses)
+{
+	struct termios io;
+
+	tcgetattr(STDIN_FILENO, &io);
+
+	SET_BIT(io.c_lflag, ECHO|ECHONL);
+
+	tcsetattr(STDIN_FILENO, TCSADRAIN, &io);
+}
+
+void init_terminal_size(struct session *ses)
+{
+	struct winsize screen;
+	static int old_rows, old_cols;
+
+	push_call("init_terminal_size(%p)",ses);
+
+	if (ses == gts)
+	{
+		old_rows = gtd->screen->rows;
+		old_cols = gtd->screen->cols;
+
+		if (ioctl(1, TIOCGWINSZ, &screen) >= 0)
+		{
+			init_screen(screen.ws_row, screen.ws_col, screen.ws_ypixel, screen.ws_xpixel);
+
+			if (gtd->attach_sock)
+			{
+				char buf[100];
+				sprintf(buf, "\e[8;%d;%dt\e[4;%d;%dt\e[7t", screen.ws_row, screen.ws_col, screen.ws_ypixel, screen.ws_xpixel);
+				write(gtd->attach_sock, buf, strlen(buf));
+			}
+		}
+	}
+
+	if (ses->scroll)
+	{
+		SET_BIT(ses->scroll->flags, SCROLL_FLAG_RESIZE);
+	}
+
+	if (ses->map)
+	{
+		SET_BIT(ses->map->flags, MAP_FLAG_RESIZE);
+	}
+
+	init_split(ses, ses->split->sav_top_row, ses->split->sav_top_col, ses->split->sav_bot_row, ses->split->sav_bot_col);
+
+	check_all_events(ses, SUB_ARG, 0, 4, "SCREEN RESIZE", ntos(gtd->screen->rows), ntos(gtd->screen->cols), ntos(gtd->screen->height), ntos(gtd->screen->width));
+
+	if (old_rows <= old_cols / 2 && gtd->screen->rows > gtd->screen->cols / 2)
+	{
+		check_all_events(ses, SUB_ARG, 0, 4, "SCREEN ROTATE PORTRAIT", ntos(gtd->screen->rows), ntos(gtd->screen->cols), ntos(gtd->screen->height), ntos(gtd->screen->width));
+	}
+	else if (old_rows >= old_cols / 2 && gtd->screen->rows < gtd->screen->cols / 2)
+	{
+		check_all_events(ses, SUB_ARG, 0, 4, "SCREEN ROTATE LANDSCAPE", ntos(gtd->screen->rows), ntos(gtd->screen->cols), ntos(gtd->screen->height), ntos(gtd->screen->width));
+	}
+
+	msdp_update_all("SCREEN_ROWS",   "%d", gtd->screen->rows);
+	msdp_update_all("SCREEN_COLS",   "%d", gtd->screen->cols);
+	msdp_update_all("SCREEN_HEIGHT", "%d", gtd->screen->height);
+	msdp_update_all("SCREEN_WIDTH",  "%d", gtd->screen->width);
+
+	pop_call();
+	return;
+}
+
+int get_scroll_rows(struct session *ses)
+{
+	return (ses->split->bot_row - ses->split->top_row);
+}
+
+int get_scroll_cols(struct session *ses)
+{
+	return ses->wrap;
+}
+
+char *get_charset(struct session *ses)
+{
+	switch (HAS_BIT(ses->charset, CHARSET_FLAG_ALL))
+	{
+		case CHARSET_FLAG_BIG5:
+			return "BIG-5";
+
+		case CHARSET_FLAG_GBK1:
+			return "GBK-1";
+
+		case CHARSET_FLAG_UTF8:
+			return "UTF-8";
+
+		case CHARSET_FLAG_UTF8|CHARSET_FLAG_BIG5TOUTF8:
+			return "BIG5TOUTF8";
+
+		case CHARSET_FLAG_UTF8|CHARSET_FLAG_FANSITOUTF8:
+			return "FANSI";
+		
+		case CHARSET_FLAG_UTF8|CHARSET_FLAG_GBK1TOUTF8:
+			return "GBK1TOUTF8";
+
+		case CHARSET_FLAG_UTF8|CHARSET_FLAG_KOI8TOUTF8:
+			return "KOI8TOUTF8";
+
+		case CHARSET_FLAG_UTF8|CHARSET_FLAG_ISO1TOUTF8:
+			return "ISO1TOUTF8";
+
+		case CHARSET_FLAG_UTF8|CHARSET_FLAG_ISO2TOUTF8:
+			return "ISO2TOUTF8";
+
+		default:
+			return "ASCII";
+	}
+}

+ 563 - 0
text.c

@@ -0,0 +1,563 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2004                       *
+******************************************************************************/
+
+#include "tintin.h"
+
+
+void print_line(struct session *ses, char **str, int prompt)
+{
+	int height, width;
+	char *out;
+
+	push_call("print_line(%p,%p,%d)",ses,*str,prompt);
+
+	if (ses->scroll->line != -1 && HAS_BIT(ses->flags, SES_FLAG_SCROLLLOCK))
+	{
+		pop_call();
+		return;
+	}
+
+	if (HAS_BIT(ses->flags, SES_FLAG_SCAN) && gtd->level->verbose == 0)
+	{
+		pop_call();
+		return;
+	}
+
+	if (HAS_BIT(ses->flags, SES_FLAG_SPLIT) && ses->wrap != gtd->screen->cols)
+	{
+		SET_BIT(ses->flags, SES_FLAG_PRINTLINE);
+
+		pop_call();
+		return;
+	}
+
+	out = str_alloc(BUFFER_SIZE + strlen(*str));
+
+	if (HAS_BIT(ses->flags, SES_FLAG_CONVERTMETA))
+	{
+		convert_meta(*str, out, TRUE);
+
+		str_cpy(str, out);
+	}
+
+	if (HAS_BIT(ses->flags, SES_FLAG_SPLIT) || HAS_BIT(ses->flags, SES_FLAG_WORDWRAP))
+	{
+		word_wrap(ses, *str, out, TRUE, &height, &width);
+	}
+	else
+	{
+		str_cpy(&out, *str);
+	}
+
+	if (prompt)
+	{
+		print_stdout("%s", out);
+	}
+	else
+	{
+		print_stdout("%s\n", out);
+	}
+	add_line_screen(out);
+
+	str_free(out);
+
+	SET_BIT(gtd->flags, TINTIN_FLAG_FLUSH);
+
+	pop_call();
+	return;
+}
+
+void print_stdout(char *format, ...)
+{
+	char *buffer;
+	va_list args;
+	int len;
+
+	va_start(args, format);
+	len = vasprintf(&buffer, format, args);
+	va_end(args);
+
+	if (gtd->detach_port)
+	{
+		if (gtd->detach_sock)
+		{
+			write(gtd->detach_sock, buffer, len);
+		}
+	}
+	else
+	{
+		write(STDIN_FILENO, buffer, len);
+	}
+	free(buffer);
+}
+
+/*
+	Word wrapper, only wraps scrolling region
+*/
+
+int word_wrap(struct session *ses, char *textin, char *textout, int flags, int *height, int *width)
+{
+	char color[COLOR_SIZE] = { 0 };
+	char *pti, *pto, *lis, *los, *chi, *cho;
+	int cur_height, cur_width, size, i, skip, lines, cur_col, tab, wrap, cur_space;
+
+	push_call("word_wrap(%s,%p,%p)",ses->name,textin,textout);
+
+	pti = chi = lis = textin;
+	pto = cho = los = textout;
+
+	cur_height   = 1;
+	lines        = 0;
+	*height      = 0;
+
+	cur_col      = ses->cur_col;
+	ses->cur_col = 1;
+
+	cur_width    = 0;
+	*width       = 0;
+
+	skip         = 0;
+
+	wrap = get_scroll_cols(ses);
+
+	while (*pti && pto - textout < BUFFER_SIZE)
+	{
+		skip = skip_vt102_codes(pti);
+
+		if (skip)
+		{
+			if (ses->color)
+			{
+				get_color_codes(color, pti, color, GET_ONE);
+
+				if (HAS_BIT(flags, WRAP_FLAG_DISPLAY))
+				{
+					interpret_vt102_codes(ses, pti, TRUE);
+				}
+
+				for (i = 0 ; i < skip ; i++)
+				{
+					*pto++ = *pti++;
+				}
+			}
+			else
+			{
+				pti += skip;
+			}
+			continue;
+		}
+
+		if (*pti == '\n')
+		{
+			lines++;
+			cur_height++;
+
+			*pto++ = *pti++;
+
+			lis = pti;
+			los = pto;
+
+			if (*pti)
+			{
+				pto += sprintf(pto, "%s", color);
+			}
+
+			if (cur_width > *width)
+			{
+				*width = cur_width;
+			}
+
+			cur_width    = 0;
+			ses->cur_col = 1;
+			cur_space    = 1;
+
+			continue;
+		}
+
+		if (*pti == ' ' || *pti == '\t')
+		{
+			cur_space = ses->cur_col;
+			los = pto;
+			lis = pti;
+		}
+
+		if (ses->cur_col > wrap)
+		{
+			cur_height++;
+
+			if (HAS_BIT(ses->flags, SES_FLAG_WORDWRAP))
+			{
+				if (ses->cur_col - cur_space >= 15 || wrap <= 20 || !SCROLL(ses))
+				{
+					*pto++ = '\n';
+					pto += sprintf(pto, "%s", color);
+
+					los = pto;
+					lis = pti;
+				}
+				else if (lis != chi) // infinite VT loop detection
+				{
+					pto = los;
+					*pto++ = '\n';
+					pto += sprintf(pto, "%s", color);
+					pti = chi = lis;
+					pti++;
+				}
+				else if (los != cho)
+				{
+					pto = cho = los;
+					pto++;
+					pti = chi = lis;
+					pti++;
+				}
+			}
+			else if (ses->wrap)
+			{
+				*pto++ = '\n';
+			}
+			ses->cur_col = 1;
+			cur_space = 1;
+		}
+		else
+		{
+			if (HAS_BIT(ses->charset, CHARSET_FLAG_EUC) && is_euc_head(ses, pti))
+			{
+				size = get_euc_width(ses, pti, &tab);
+
+				while (size--)
+				{
+					*pto++ = *pti++;
+				}
+				cur_width += tab;
+				ses->cur_col += tab;
+			}
+			else if (HAS_BIT(ses->charset, CHARSET_FLAG_UTF8) && is_utf8_head(pti))
+			{
+				size = get_utf8_width(pti, &tab);
+
+				while (size--)
+				{
+					*pto++ = *pti++;
+				}
+				cur_width += tab;
+				ses->cur_col += tab;
+			}
+			else if (*pti == '\t')
+			{
+				tab = ses->tab_width - (ses->cur_col - 1) % ses->tab_width;
+
+				if (ses->cur_col + tab >= wrap) // xterm tabs
+				{
+					tab = (wrap - ses->cur_col);
+				}
+				pto += sprintf(pto, "%.*s", tab, "                ");
+				pti++;
+
+				cur_width += tab;
+				ses->cur_col += tab;
+				cur_space = ses->cur_col;
+			}
+			else
+			{
+				*pto++ = *pti++;
+
+				cur_width++;
+				ses->cur_col++;
+			}
+		}
+	}
+	*pto = 0;
+
+	*height = cur_height + 1;
+
+	if (cur_width > *width)
+	{
+		*width = cur_width;
+	}
+
+	ses->cur_col = cur_col;
+
+	pop_call();
+	return lines + 1;
+}
+
+// store whatever falls inbetween skip and keep. Used by #buffer not checking SCROLL().
+
+int word_wrap_split(struct session *ses, char *textin, char *textout, int wrap, int start, int end, int flags, int *height, int *width)
+{
+	char color[COLOR_SIZE] = { 0 };
+	char *pti, *pto, *lis, *los;
+	int cur_height, size, i, lines, cur_col, cur_width, tab, skip, cur_space;
+
+	push_call("word_wrap_split(%s,%p,%p,%d,%d,%d,%d)",ses->name,textin,textout,wrap,start,end,flags);
+
+	pti = lis = textin;
+	pto = los = textout;
+
+	if (wrap <= 0)
+	{
+		wrap = ses->wrap;
+
+		if (ses->wrap == 0)
+		{
+			print_stdout("debug: word_wrap_split: wrap is 0\n");
+			pop_call();
+			return 1;
+		}
+	}
+
+	lines      = 0;
+	*height    = 0;
+	cur_height = 0;
+	cur_width  = 0;
+	*width     = 0;
+	cur_col    = 1;
+
+	if (HAS_BIT(flags, WRAP_FLAG_SPLIT) && end == 0)
+	{
+		print_stdout("debug: word_wrap_split: end point is 0.");
+	}
+
+	while (*pti && pto - textout < BUFFER_SIZE - 20)
+	{
+		if (cur_height > 10000 || cur_width > 100000)
+		{
+			print_stdout("debug: word_wrap_split: wrap %d height %d width %d los %d start %d end %d\n", wrap, cur_height, cur_width, pto - los, start, end);
+			pop_call();
+			return 1;
+		}
+
+		skip = skip_vt102_codes(pti);
+
+		if (skip)
+		{
+			if (ses->color)
+			{
+				get_color_codes(color, pti, color, GET_ONE);
+
+				for (i = 0 ; i < skip ; i++)
+				{
+					*pto++ = *pti++;
+				}
+			}
+			else
+			{
+				pti += skip;
+			}
+			continue;
+		}
+
+		if (*pti == '\n')
+		{
+			lines++;
+			cur_height++;
+
+			lis = pti;
+			los = pto;
+
+			if (!HAS_BIT(flags, WRAP_FLAG_SPLIT) || (cur_height > start && cur_height < end))
+			{
+				*pto++ = *pti++;
+			}
+			else
+			{
+				pti++;
+			}
+
+			if (!HAS_BIT(flags, WRAP_FLAG_SPLIT) || (cur_height >= start && cur_height < end))
+			{
+				if (*pti)
+				{
+					pto += sprintf(pto, "%s", color);
+				}
+			}
+
+			if (cur_width > *width)
+			{
+				*width = cur_width;
+			}
+
+			cur_col = 1;
+			cur_space = 1;
+			cur_width = 0;
+			continue;
+		}
+
+		if (*pti == ' ' || *pti == '\t')
+		{
+			cur_space = cur_col;
+			lis = pti;
+			los = pto;
+		}
+
+		if (cur_col > wrap)
+		{
+			cur_height++;
+
+			if (HAS_BIT(ses->flags, SES_FLAG_WORDWRAP))
+			{
+				if (cur_col - cur_space > 15 || wrap <= 20)
+				{
+					if (!HAS_BIT(flags, WRAP_FLAG_SPLIT) || (cur_height > start && cur_height < end))
+					{
+						*pto++ = '\n';
+					}
+
+					if (!HAS_BIT(flags, WRAP_FLAG_SPLIT) || (cur_height >= start && cur_height < end))
+					{
+						pto += sprintf(pto, "%s", color);
+//						pto += sprintf(pto, "%s(%d,%d,%d)", color, start, end, cur_height);
+
+					}
+				}
+				else
+				{
+					pti = lis;
+					pto = los;
+
+					if (!HAS_BIT(flags, WRAP_FLAG_SPLIT) || (cur_height > start && cur_height < end))
+					{
+						*pto++ = '\n';
+					}
+
+					if (!HAS_BIT(flags, WRAP_FLAG_SPLIT) || (cur_height >= start && cur_height < end))
+					{
+						pto += sprintf(pto, "%s", color);
+
+					}
+					pti++;
+				}
+			}
+			else
+			{
+				if (!HAS_BIT(flags, WRAP_FLAG_SPLIT) || (cur_height > start && cur_height < end))
+				{
+					*pto++ = '\n';
+				}
+
+				if (!HAS_BIT(flags, WRAP_FLAG_SPLIT) || (cur_height >= start && cur_height < end))
+				{
+					pto += sprintf(pto, "%s", color);
+				}
+			}
+			cur_col = 1;
+			cur_space = 1;
+			continue;
+		}
+
+		if (HAS_BIT(ses->charset, CHARSET_FLAG_EUC) && is_euc_head(ses, pti))
+		{
+			size = get_euc_width(ses, pti, &tab);
+
+			if (!HAS_BIT(flags, WRAP_FLAG_SPLIT) || (cur_height >= start && cur_height < end))
+			{
+				while (size--)
+				{
+					*pto++ = *pti++;
+				}
+			}
+			else
+			{
+				pti += size;
+			}
+			cur_width += tab;
+			cur_col += tab;
+		}
+		else if (HAS_BIT(ses->charset, CHARSET_FLAG_UTF8) && is_utf8_head(pti))
+		{
+			size = get_utf8_width(pti, &tab);
+
+			if (size)
+			{
+				if (!HAS_BIT(flags, WRAP_FLAG_SPLIT) || (cur_height >= start && cur_height < end))
+				{
+					while (size--)
+					{
+						*pto++ = *pti++;
+					}
+				}
+				else
+				{
+					pti += size;
+				}
+				cur_width += tab;
+				cur_col += tab;
+			}
+			else
+			{
+				print_stdout("debug: word_wrap_split: utf8 error\n");
+				*pto++ = *pti++;
+				cur_width++;
+				cur_col++;
+			}
+		}
+		else
+		{
+			if (*pti == '\t')
+			{
+				tab = ses->tab_width - (cur_col - 1) % ses->tab_width;
+
+				if (cur_col + tab >= wrap)
+				{
+					tab = (wrap - cur_col);
+				}
+
+				if (!HAS_BIT(flags, WRAP_FLAG_SPLIT) || (cur_height >= start && cur_height < end))
+				{
+					pto += sprintf(pto, "%.*s", tab, "                ");
+				}
+				pti++;
+
+				cur_width += tab;
+				cur_col += tab;
+				cur_space = cur_col;
+			}
+			else
+			{
+				if (!HAS_BIT(flags, WRAP_FLAG_SPLIT) || (cur_height >= start && cur_height < end))
+				{
+					*pto++ = *pti++;
+				}
+				else
+				{
+					pti++;
+				}
+				cur_width++;
+				cur_col++;
+			}
+		}
+	}
+	*pto = 0;
+
+	if (cur_width > *width)
+	{
+		*width = cur_width;
+	}
+	*height = cur_height + 1;
+
+	pop_call();
+	return lines + 1;
+}
+

+ 2873 - 0
tintin.h

@@ -0,0 +1,2873 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2019 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                                                                             *
+*              (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                   *
+*                                                                             *
+*                        coded by peter unold 1992                            *
+*                       modified by Bill Reiss 1993                           *
+*                    recoded by Igor van den Hoven 2004                       *
+******************************************************************************/
+	
+#include <stdio.h>
+#include <zlib.h>
+#include <signal.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <termios.h>
+#include <pcre.h>
+#include <errno.h>
+#include <math.h>
+#include <stdbool.h>
+#include <sys/socket.h>
+
+/******************************************************************************
+*   Autoconf patching by David Hedbor                                         *
+*******************************************************************************/
+
+#include "config.h"
+
+#if defined(HAVE_STRING_H)
+#include <string.h>
+#elif defined(HAVE_STRINGS_H)
+#include <strings.h>
+#endif
+
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+
+#ifdef HAVE_TIME_H
+#include <time.h>
+#endif
+
+
+#ifdef TIME_WITH_SYS_TIME
+#include <sys/time.h>
+#endif
+
+
+#ifdef SOCKS
+#include <socks.h>
+#endif
+
+#ifndef BADSIG
+#define BADSIG (RETSIGTYPE (*)(int))-1
+#endif
+
+
+#ifdef HAVE_NET_ERRNO_H
+#include <net/errno.h>
+#endif
+
+
+#ifdef HAVE_GNUTLS_H
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+#else
+#define gnutls_session_t int
+#endif
+
+
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+
+
+#if !defined(SO_PEERCRED)
+	#define SO_PEERCRED 17 
+#endif
+
+#ifndef __TINTIN_H__
+#define __TINTIN_H__
+
+/*
+	A bunch of constants
+*/
+
+#define FLAG_NONE                        0
+
+#define FALSE                            0
+#define TRUE                             1
+
+#define GET_ONE                          0 /* stop at spaces */
+#define GET_ALL                          1 /* stop at semicolon */
+#define GET_NST                          2 /* nest square brackets */
+#define GET_VBT                          4 /* ignore semicolon for verbatim mode */
+
+#define TEL_N                            0
+#define TEL_Y                            1
+#define TEL_I                            2
+
+#define SCREEN_WIDTH                    80
+#define SCREEN_HEIGHT                   24
+
+#define SORT_PRIORITY                    0
+#define SORT_ALPHA                       1
+#define SORT_APPEND                      2
+#define SORT_DELAY                       3
+
+#define DEFAULT_OPEN                   '{'
+#define DEFAULT_CLOSE                  '}'
+
+#define COMMAND_SEPARATOR              ';'
+
+#define ASCII_NUL                        0
+#define ASCII_ENQ                        5 // Ignore if possible
+#define ASCII_HTML_AMP                   6 // Might conflict with VTON
+#define ASCII_BEL                        7
+#define ASCII_BS                         8
+#define ASCII_HTAB                       9
+#define ASCII_LF                        10
+#define ASCII_VTAB                      11
+#define ASCII_FF                        12
+#define ASCII_CR                        13
+#define ASCII_ESC                       27
+#define ASCII_HTML_OPEN                 28 // Also file separator, whatever that is
+#define ASCII_HTML_CLOSE                30 // Also record separator, whatever that is
+
+#define ASCII_DEL                      127
+
+#define DAEMON_DIR               "daemons"
+#define TINTIN_DIR               ".tintin"
+
+#define HISTORY_FILE         "history.txt"
+
+#define STRING_SIZE                  50000
+#define BUFFER_SIZE                  24000
+#define INPUT_SIZE                   10000
+#define STACK_SIZE                    1000
+#define NUMBER_SIZE                    100
+#define LEGEND_SIZE                     50
+#define COLOR_SIZE                      50
+#define CHAR_SIZE                        5
+#define LIST_SIZE                        2
+
+#define CLIENT_NAME              "TinTin++"
+#define CLIENT_VERSION           "2.02.00 "
+
+#define XT_E                            0x27
+#define XT_C                            0x5B
+#define XT_O                            0x5D
+#define XT_W                            0x74
+#define XT_T                            0x07
+#define XT_CS                           0x73
+#define XT_S                            "\073"
+#define XT_V                            "\000"
+
+/*
+	<faa> - red
+	<afa> - green
+	<ffa> - yellow
+	<aaf> - blue
+	<faf> - magenta
+	<aff> - cyan
+	<fff> - white
+	<acf> - Azure
+	<afc> - Jade
+	<caf> - Violet
+	<cfa> - Lime
+	<fac> - Pink
+	<fca> - Orange
+*/
+
+#define COLOR_BRACE         "\e[38;5;164m" // "<eae>" // magenta
+#define COLOR_COMMAND       "\e[38;5;044m" // "<aee>" // cyan
+#define COLOR_RESET         "\e[0m"        // "<088>" // reset
+#define COLOR_SEPARATOR     "\e[38;5;160m" // "<eaa>" // red
+#define COLOR_STATEMENT     "\e[38;5;040m" // "<aea>" // green
+#define COLOR_STRING        "\e[38;5;188m" // "<eee>" // white
+#define COLOR_TEXT          "\e[0m"        // "<ddd>" // grey
+#define COLOR_TINTIN        "\e[38;5;184m" // "<eea>" // yellow
+
+#define TIMER_UPDATE_INPUT               0
+#define TIMER_UPDATE_SESSIONS            1
+#define TIMER_UPDATE_DELAYS              2
+#define TIMER_UPDATE_CHAT                3
+#define TIMER_UPDATE_PORT                4
+#define TIMER_UPDATE_TICKS               5
+#define TIMER_UPDATE_PATHS               6
+#define TIMER_UPDATE_PACKETS             7
+#define TIMER_UPDATE_TERMINAL            8
+#define TIMER_UPDATE_TIME                9
+#define TIMER_UPDATE_MEMORY             10
+#define TIMER_STALL_PROGRAM             11
+#define TIMER_CPU                       12
+
+
+#define PULSE_PER_SECOND               100
+
+#define PULSE_UPDATE_INPUT               1
+#define PULSE_UPDATE_SESSIONS            1
+#define PULSE_UPDATE_DELAYS              1
+#define PULSE_UPDATE_DAEMON              1
+#define PULSE_UPDATE_CHAT               10
+#define PULSE_UPDATE_PORT               10
+#define PULSE_UPDATE_TICKS              10
+#define PULSE_UPDATE_PATHS              10
+#define PULSE_UPDATE_PACKETS            10
+#define PULSE_UPDATE_TERMINAL           10
+#define PULSE_UPDATE_MEMORY             10
+#define PULSE_UPDATE_TIME               10
+
+/*
+	Index for lists used by tintin
+*/
+
+
+#define LIST_ACTION                      0
+#define LIST_ALIAS                       1
+#define LIST_BUTTON                      2
+#define LIST_CLASS                       3
+#define LIST_COMMAND                     4
+#define LIST_CONFIG                      5
+#define LIST_DELAY                       6
+#define LIST_EVENT                       7
+#define LIST_FUNCTION                    8
+#define LIST_GAG                         9
+#define LIST_HIGHLIGHT                  10
+#define LIST_HISTORY                    11
+#define LIST_LANDMARK                   12
+#define LIST_MACRO                      13
+#define LIST_PATH                       14
+#define LIST_PATHDIR                    15
+#define LIST_PROMPT                     16
+#define LIST_SUBSTITUTE                 17
+#define LIST_TAB                        18
+#define LIST_TERRAIN                    19
+#define LIST_TICKER                     20
+#define LIST_VARIABLE                   21
+#define LIST_MAX                        22
+
+/*
+	Command type
+*/
+
+enum operators
+{
+	TOKEN_TYPE_BREAK,
+	TOKEN_TYPE_CASE,
+	TOKEN_TYPE_COMMAND,
+	TOKEN_TYPE_CONTINUE,
+	TOKEN_TYPE_DEFAULT,
+	TOKEN_TYPE_END,
+	TOKEN_TYPE_ELSE,
+	TOKEN_TYPE_ELSEIF,
+	TOKEN_TYPE_FOREACH,
+	TOKEN_TYPE_BROKEN_FOREACH,
+	TOKEN_TYPE_IF,
+	TOKEN_TYPE_LOOP,
+	TOKEN_TYPE_BROKEN_LOOP,
+	TOKEN_TYPE_PARSE,
+	TOKEN_TYPE_BROKEN_PARSE,
+	TOKEN_TYPE_REGEX,
+	TOKEN_TYPE_RETURN,
+	TOKEN_TYPE_SESSION,
+	TOKEN_TYPE_STRING,
+	TOKEN_TYPE_SWITCH,
+	TOKEN_TYPE_WHILE,
+	TOKEN_TYPE_BROKEN_WHILE
+};
+
+/*
+	Various flags
+*/
+
+#define BV01 (1   <<  0)
+#define BV02 (1   <<  1)
+#define BV03 (1   <<  2)
+#define BV04 (1   <<  3)
+#define BV05 (1   <<  4)
+#define BV06 (1   <<  5)
+#define BV07 (1   <<  6)
+#define BV08 (1   <<  7)
+#define BV09 (1   <<  8)
+#define BV10 (1   <<  9)
+#define BV11 (1   << 10)
+#define BV12 (1   << 11)
+#define BV13 (1   << 12)
+#define BV14 (1   << 13)
+#define BV15 (1   << 14)
+#define BV16 (1   << 15)
+#define BV17 (1   << 16)
+#define BV18 (1   << 17)
+#define BV19 (1   << 18)
+#define BV20 (1   << 19)
+#define BV21 (1   << 20)
+#define BV22 (1   << 21)
+#define BV23 (1   << 22)
+#define BV24 (1   << 23)
+#define BV25 (1   << 24)
+#define BV26 (1   << 25)
+#define BV27 (1   << 26)
+#define BV28 (1   << 27)
+#define BV29 (1   << 28)
+#define BV30 (1   << 29)
+#define BV31 (1   << 30)
+#define BV32 (1LL << 31)
+#define BV33 (1LL << 32)
+#define BV34 (1LL << 33)
+#define BV35 (1LL << 34)
+#define BV36 (1LL << 35)
+#define BV37 (1LL << 36)
+#define BV38 (1LL << 37)
+#define BV39 (1LL << 38)
+
+#define BUFFER_FLAG_GREP              BV01
+
+
+#define CHARSET_FLAG_UTF8                 BV01
+#define CHARSET_FLAG_BIG5                 BV02
+#define CHARSET_FLAG_GBK1                 BV03
+
+#define CHARSET_FLAG_BIG5TOUTF8           BV04
+#define CHARSET_FLAG_FANSITOUTF8          BV05
+#define CHARSET_FLAG_GBK1TOUTF8           BV06
+#define CHARSET_FLAG_ISO1TOUTF8           BV07
+#define CHARSET_FLAG_ISO2TOUTF8           BV08
+#define CHARSET_FLAG_KOI8TOUTF8           BV09
+
+
+#define CHARSET_FLAG_EUC                  CHARSET_FLAG_BIG5|CHARSET_FLAG_GBK1
+#define CHARSET_FLAG_ALL_TOUTF8           CHARSET_FLAG_BIG5TOUTF8|CHARSET_FLAG_FANSITOUTF8|CHARSET_FLAG_GBK1TOUTF8|CHARSET_FLAG_ISO1TOUTF8|CHARSET_FLAG_ISO2TOUTF8|CHARSET_FLAG_KOI8TOUTF8
+#define CHARSET_FLAG_ALL                  CHARSET_FLAG_UTF8|CHARSET_FLAG_ALL_TOUTF8|CHARSET_FLAG_EUC
+
+
+#define COL_BLD                       BV01
+#define COL_UND                       BV02
+#define COL_BLK                       BV03
+#define COL_REV                       BV04
+#define COL_XTF                       BV05
+#define COL_XTF_5                     BV06
+#define COL_XTF_R                     BV07
+#define COL_XTB                       BV08
+#define COL_XTB_5                     BV09
+#define COL_XTB_R                     BV10
+#define COL_TCF                       BV11
+#define COL_TCF_2                     BV12
+#define COL_TCF_R                     BV13
+#define COL_TCB                       BV14
+#define COL_TCB_2                     BV15
+#define COL_TCB_R                     BV16
+
+#define CHAT_NAME_CHANGE                 1
+#define CHAT_REQUEST_CONNECTIONS         2
+#define CHAT_CONNECTION_LIST             3
+#define CHAT_TEXT_EVERYBODY              4
+#define CHAT_TEXT_PERSONAL               5
+#define CHAT_TEXT_GROUP                  6
+#define CHAT_MESSAGE                     7
+#define CHAT_DO_NOT_DISTURB              8
+
+#define CHAT_SEND_ACTION                 9
+#define CHAT_SEND_ALIAS                 10
+#define CHAT_SEND_MACRO                 11
+#define CHAT_SEND_VARIABLE              12
+#define CHAT_SEND_EVENT                 13
+#define CHAT_SEND_GAG                   14
+#define CHAT_SEND_HIGHLIGHT             15
+#define CHAT_SEND_LIST                  16
+#define CHAT_SEND_ARRAY                 17
+#define CHAT_SEND_BARITEM               18
+
+#define CHAT_VERSION                    19
+#define CHAT_FILE_START                 20
+#define CHAT_FILE_DENY                  21
+#define CHAT_FILE_BLOCK_REQUEST         22
+#define CHAT_FILE_BLOCK                 23     
+#define CHAT_FILE_END                   24
+#define CHAT_FILE_CANCEL                25
+#define CHAT_PING_REQUEST               26 
+#define CHAT_PING_RESPONSE              27 
+
+#define CHAT_PEEK_CONNECTIONS           28
+#define CHAT_PEEK_LIST                  29
+#define CHAT_SNOOP_START                30
+#define CHAT_SNOOP_DATA                 31
+
+#define CHAT_END_OF_COMMAND            255
+
+#define CHAT_FLAG_PRIVATE             BV01
+#define CHAT_FLAG_REQUEST             BV02
+#define CHAT_FLAG_SERVE               BV03
+#define CHAT_FLAG_IGNORE              BV04
+#define CHAT_FLAG_FORWARD             BV05
+#define CHAT_FLAG_FORWARDBY           BV06
+#define CHAT_FLAG_FORWARDALL          BV07
+#define CHAT_FLAG_DND                 BV08
+#define CHAT_FLAG_LINKLOST            BV09
+
+#define DRAW_FLAG_NONE                   0
+#define DRAW_FLAG_ASCII               BV01
+#define DRAW_FLAG_BLANKED             BV02
+#define DRAW_FLAG_BOT                 BV03
+#define DRAW_FLAG_BOXED               BV04
+#define DRAW_FLAG_BUMP                BV05
+#define DRAW_FLAG_CIRCLED             BV06
+#define DRAW_FLAG_COLOR               BV07
+#define DRAW_FLAG_CONVERT             BV08
+#define DRAW_FLAG_COPY                BV30 // new
+#define DRAW_FLAG_CORNERED            BV09
+#define DRAW_FLAG_CROSSED             BV10
+#define DRAW_FLAG_FILLED              BV11
+#define DRAW_FLAG_GRID                BV12
+#define DRAW_FLAG_HOR                 BV13
+#define DRAW_FLAG_HUGE                BV14
+#define DRAW_FLAG_JEWELED             BV15
+#define DRAW_FLAG_LEFT                BV16
+#define DRAW_FLAG_LINED               BV17
+#define DRAW_FLAG_NUMBERED            BV18
+#define DRAW_FLAG_PRUNED              BV19
+#define DRAW_FLAG_RIGHT               BV20 
+#define DRAW_FLAG_ROUNDED             BV21
+#define DRAW_FLAG_SCROLL              BV22
+#define DRAW_FLAG_SHADOWED            BV23
+#define DRAW_FLAG_TEED                BV24
+#define DRAW_FLAG_TOP                 BV25
+#define DRAW_FLAG_TRACED              BV26
+#define DRAW_FLAG_TUBED               BV27
+#define DRAW_FLAG_UTF8                BV28
+#define DRAW_FLAG_VER                 BV29
+
+
+#define EVENT_FLAG_CATCH              BV01
+#define EVENT_FLAG_CLASS              BV02
+#define EVENT_FLAG_INPUT              BV03
+#define EVENT_FLAG_MAP                BV04
+#define EVENT_FLAG_MOUSE              BV05
+#define EVENT_FLAG_OUTPUT             BV06
+#define EVENT_FLAG_PORT               BV07
+#define EVENT_FLAG_SCAN               BV08
+#define EVENT_FLAG_SCREEN             BV09
+#define EVENT_FLAG_SESSION            BV10
+#define EVENT_FLAG_SYSTEM             BV11
+#define EVENT_FLAG_TELNET             BV12
+#define EVENT_FLAG_TIME               BV13
+#define EVENT_FLAG_VT100              BV14
+
+
+
+#define PORT_FLAG_PRIVATE             BV01
+#define PORT_FLAG_REQUEST             BV02
+#define PORT_FLAG_SERVE               BV03
+#define PORT_FLAG_IGNORE              BV04
+#define PORT_FLAG_FORWARD             BV05
+#define PORT_FLAG_FORWARDBY           BV06
+#define PORT_FLAG_FORWARDALL          BV07
+#define PORT_FLAG_DND                 BV08
+#define PORT_FLAG_LINKLOST            BV09
+
+#define PORT_RANK_SPY                    0
+#define PORT_RANK_SCOUT                  1
+
+#define RANK_FLAG_SCOUT               BV01
+
+#define COMM_FLAG_DISCONNECT          BV01
+#define COMM_FLAG_PASSWORD            BV02
+#define COMM_FLAG_REMOTEECHO          BV03
+#define COMM_FLAG_EOR                 BV04
+#define COMM_FLAG_MSDPUPDATE          BV05
+#define COMM_FLAG_256COLORS           BV06
+#define COMM_FLAG_UTF8                BV07
+#define COMM_FLAG_GMCP                BV08
+
+#define MSDP_FLAG_COMMAND             BV01
+#define MSDP_FLAG_LIST                BV02
+#define MSDP_FLAG_SENDABLE            BV03
+#define MSDP_FLAG_REPORTABLE          BV04
+#define MSDP_FLAG_CONFIGURABLE        BV05
+#define MSDP_FLAG_REPORTED            BV06
+#define MSDP_FLAG_UPDATED             BV07
+
+#define MTTS_FLAG_ANSI                BV01
+#define MTTS_FLAG_VT100               BV02
+#define MTTS_FLAG_UTF8                BV03
+#define MTTS_FLAG_256COLORS           BV04
+#define MTTS_FLAG_MOUSETRACKING       BV05
+#define MTTS_FLAG_COLORPALETTE        BV06
+#define MTTS_FLAG_SCREENREADER        BV07
+#define MTTS_FLAG_PROXY               BV08
+#define MTTS_FLAG_TRUECOLOR           BV09
+
+#define SCREEN_FLAG_CSIP              BV01
+#define SCREEN_FLAG_OSCT              BV02
+#define SCREEN_FLAG_OMIT              BV03
+#define SCREEN_FLAG_GET_ONE           BV04
+#define SCREEN_FLAG_GET_ALL           BV05
+#define SCREEN_FLAG_GET_NONE          BV06
+
+#define SCROLL_FLAG_RESIZE            BV01
+
+#define SUB_NONE                      0
+#define SUB_ARG                       (1 <<  0)
+#define SUB_VAR                       (1 <<  1)
+#define SUB_FUN                       (1 <<  2)
+#define SUB_COL                       (1 <<  3)
+#define SUB_ESC                       (1 <<  4)
+#define SUB_CMD                       (1 <<  5)
+#define SUB_SEC                       (1 <<  6)
+#define SUB_EOL                       (1 <<  7)
+#define SUB_LNF                       (1 <<  8)
+//#define SUB_FIX                       (1 <<  9)
+#define SUB_CMP                       (1 << 10)
+#define SUB_LIT                       (1 << 11)
+
+#define TAB_FLAG_FORWARD              BV01
+#define TAB_FLAG_BACKWARD             BV02
+#define TAB_FLAG_LIST                 BV03
+#define TAB_FLAG_SCROLLBACK           BV04
+
+#define REGEX_FLAG_NONE               0
+#define REGEX_FLAG_FIX                (1 <<  0)
+#define REGEX_FLAG_ARG                (1 <<  1)
+#define REGEX_FLAG_CMD                (1 <<  2)
+
+
+
+//#define TINTIN_FLAG_RESETBUFFER       (1 <<  0)
+
+#define TINTIN_FLAG_CONVERTMETACHAR   (1 <<  1)
+#define TINTIN_FLAG_HISTORYBROWSE     (1 <<  2)
+#define TINTIN_FLAG_HISTORYSEARCH     (1 <<  3)
+#define TINTIN_FLAG_PROCESSINPUT      (1 <<  4)
+#define TINTIN_FLAG_INHERITANCE       (1 <<  5)
+#define TINTIN_FLAG_INSERTINPUT       (1 <<  6)
+#define TINTIN_FLAG_CHILDLOCK         (1 <<  7)
+#define TINTIN_FLAG_TERMINATE         (1 <<  8)
+#define TINTIN_FLAG_MOUSETRACKING     (1 <<  9)
+#define TINTIN_FLAG_FLUSH             (1 << 10)
+#define TINTIN_FLAG_DAEMONIZE         (1 << 11)
+#define TINTIN_FLAG_HIDDENCURSOR      (1 << 12)
+#define TINTIN_FLAG_LOCAL             (1 << 13)
+
+#define SES_FLAG_ECHOCOMMAND          BV01
+#define SES_FLAG_SNOOP                BV02
+#define SES_FLAG_MCCP                 BV03
+#define SES_FLAG_PATHMAPPING          BV04
+#define SES_FLAG_SPLIT                BV05
+#define SES_FLAG_SPEEDWALK            BV06
+#define SES_FLAG_WORDWRAP             BV07
+#define SES_FLAG_READMUD              BV08
+#define SES_FLAG_CLOSED               BV09
+#define SES_FLAG_VERBATIM             BV10
+#define SES_FLAG_CONNECTED            BV11
+#define SES_FLAG_REPEATENTER          BV12
+#define SES_FLAG_VERBOSE              BV13
+#define SES_FLAG_CONVERTMETA          BV14
+#define SES_FLAG_PRINTLINE            BV15
+#define SES_FLAG_PRINTBUFFER          BV16
+#define SES_FLAG_AUTOPATCH            BV17
+#define SES_FLAG_GAG                  BV18
+#define SES_FLAG_UPDATEVTMAP          BV19
+#define SES_FLAG_COLORPATCH           BV20
+#define SES_FLAG_SCROLLLOCK           BV21
+#define SES_FLAG_TELNET               BV22 // new
+#define SES_FLAG_SCAN                 BV23
+#define SES_FLAG_SCANABORT            BV24
+#define SES_FLAG_MOUSETRACKING        BV25
+#define SES_FLAG_RUN                  BV26
+#define SES_FLAG_PORT                 BV27
+#define SES_FLAG_SCREENREADER         BV28
+#define SES_FLAG_SCROLLSPLIT          BV29
+#define SES_FLAG_MOUSEDEBUG           BV30
+#define SES_FLAG_MOUSEINFO            BV31
+
+
+#define TELOPT_FLAG_TELNET            BV01
+#define TELOPT_FLAG_SGA               BV02
+#define TELOPT_FLAG_ECHO              BV03
+#define TELOPT_FLAG_NAWS              BV04
+#define TELOPT_FLAG_PROMPT            BV05
+#define TELOPT_FLAG_DEBUG             BV06
+#define TELOPT_FLAG_TSPEED            BV07
+#define TELOPT_FLAG_TTYPE             BV08
+#define TELOPT_FLAG_MTTS              BV09
+
+#define LIST_FLAG_IGNORE              BV01
+#define LIST_FLAG_PRIORITY            BV02
+#define LIST_FLAG_MESSAGE             BV03
+#define LIST_FLAG_DEBUG               BV04
+#define LIST_FLAG_INFO                BV05
+#define LIST_FLAG_LOG                 BV06
+#define LIST_FLAG_CLASS               BV07
+#define LIST_FLAG_READ                BV08
+#define LIST_FLAG_WRITE               BV09
+#define LIST_FLAG_HIDE                BV10
+#define LIST_FLAG_INHERIT             BV11
+#define LIST_FLAG_REGEX               BV12
+#define LIST_FLAG_NEST                BV13
+#define LIST_FLAG_DEFAULT             LIST_FLAG_MESSAGE
+
+#define NODE_FLAG_ONESHOT             BV01
+
+#define LOG_FLAG_NONE                    0
+#define LOG_FLAG_LINEFEED             BV01
+#define LOG_FLAG_OVERWRITE            BV02
+#define LOG_FLAG_APPEND               BV03
+#define LOG_FLAG_NEXT                 BV04
+#define LOG_FLAG_LOW                  BV05
+
+#define LOG_FLAG_HTML                 BV06
+#define LOG_FLAG_PLAIN                BV07
+#define LOG_FLAG_RAW                  BV08
+#define LOG_FLAG_OLD_HTML             BV09
+#define LOG_FLAG_OLD_PLAIN            BV10
+#define LOG_FLAG_OLD_RAW              BV11
+
+
+// Saved in map files, so don't swap around
+
+// keep synced with exit flags
+
+#define ROOM_FLAG_AVOID               BV01
+#define ROOM_FLAG_HIDE                BV02
+#define ROOM_FLAG_LEAVE               BV03
+#define ROOM_FLAG_VOID                BV04
+#define ROOM_FLAG_STATIC              BV05
+#define ROOM_FLAG_CURVED              BV06
+#define ROOM_FLAG_PATH                BV07
+#define ROOM_FLAG_NOGLOBAL            BV08
+#define ROOM_FLAG_INVIS               BV09
+
+#define ROOM_FLAG_AVOID_TMP           BV10|ROOM_FLAG_AVOID // To realign exit and room flags in the future.
+#define ROOM_FLAG_HIDE_TMP            BV11|ROOM_FLAG_HIDE
+#define ROOM_FLAG_LEAVE_TMP           BV12|ROOM_FLAG_LEAVE
+#define ROOM_FLAG_VOID_TMP            BV13|ROOM_FLAG_VOID
+#define ROOM_FLAG_STATIC_TMP          BV13|ROOM_FLAG_STATIC
+#define ROOM_FLAG_CURVED_TMP          BV14|ROOM_FLAG_CURVED
+#define ROOM_FLAG_BLOCK               BV15
+#define ROOM_FLAG_TERRAIN             BV20
+
+// keep synced with room flags
+
+#define EXIT_FLAG_HIDE                BV01
+#define EXIT_FLAG_AVOID               BV02
+#define EXIT_FLAG_INVIS               BV03
+#define EXIT_FLAG_BLOCK               BV04
+#define EXIT_FLAG_ALL                 BV01|BV02|BV03|BV04
+
+#define EXIT_GRID_0                     0
+#define EXIT_GRID_N                     1
+#define EXIT_GRID_E                     2
+#define EXIT_GRID_S                     3
+#define EXIT_GRID_W                     4
+#define EXIT_GRID_U                     5
+#define EXIT_GRID_D                     6
+#define EXIT_GRID_NE                    7
+#define EXIT_GRID_NW                    8
+#define EXIT_GRID_SE                    9
+#define EXIT_GRID_SW                   10
+
+#define MAP_FLAG_STATIC               BV01
+#define MAP_FLAG_VTMAP                BV02
+#define MAP_FLAG_DIRECTION            BV03
+#define MAP_FLAG_ASCIIGRAPHICS        BV04
+#define MAP_FLAG_ASCIIVNUMS           BV05
+#define MAP_FLAG_MUDFONT              BV06
+#define MAP_FLAG_NOFOLLOW             BV07
+#define MAP_FLAG_SYMBOLGRAPHICS       BV08
+#define MAP_FLAG_UNICODEGRAPHICS      BV09
+#define MAP_FLAG_BLOCKGRAPHICS        BV10
+#define MAP_FLAG_RESIZE               BV11
+#define MAP_FLAG_SYNC                 BV12
+#define MAP_FLAG_ASCIILENGTH          BV13 // For debugging but might be useful
+#define MAP_FLAG_TERRAIN              BV14
+#define MAP_FLAG_UPDATETERRAIN        BV15
+#define MAP_FLAG_DOUBLED              BV16
+
+#define MAP_SEARCH_NAME                0
+#define MAP_SEARCH_EXITS               1
+#define MAP_SEARCH_DESC                2
+#define MAP_SEARCH_AREA                3
+#define MAP_SEARCH_NOTE                4
+#define MAP_SEARCH_TERRAIN             5
+#define MAP_SEARCH_FLAG                6
+#define MAP_SEARCH_ID                  7
+#define MAP_SEARCH_MAX                 8
+
+#define MAP_EXIT_N                     1
+#define MAP_EXIT_E                     2
+#define MAP_EXIT_S                     4
+#define MAP_EXIT_W                     8
+#define MAP_EXIT_U                    16
+#define MAP_EXIT_D                    32
+
+#define MAP_DIR_N                     (1LL << MAP_EXIT_N)
+#define MAP_DIR_E                     (1LL << MAP_EXIT_E)
+#define MAP_DIR_S                     (1LL << MAP_EXIT_S)
+#define MAP_DIR_W                     (1LL << MAP_EXIT_W)
+#define MAP_DIR_U                     (1LL << MAP_EXIT_U)
+#define MAP_DIR_D                     (1LL << MAP_EXIT_D)
+
+#define MAP_DIR_NE                    (1LL << (MAP_EXIT_N|MAP_EXIT_E))
+#define MAP_DIR_NW                    (1LL << (MAP_EXIT_N|MAP_EXIT_W))
+#define MAP_DIR_SE                    (1LL << (MAP_EXIT_S|MAP_EXIT_E))
+#define MAP_DIR_SW                    (1LL << (MAP_EXIT_S|MAP_EXIT_W))
+
+#define MAP_UNDO_MOVE                 (1 <<  0)
+#define MAP_UNDO_CREATE               (1 <<  1)
+#define MAP_UNDO_LINK                 (1 <<  2)
+#define MAP_UNDO_INSERT               (1 <<  3)
+
+#define TERRAIN_FLAG_DENSE            BV01
+#define TERRAIN_FLAG_AMPLE            BV02
+#define TERRAIN_FLAG_SPARSE           BV03
+#define TERRAIN_FLAG_SCANT            BV04
+#define TERRAIN_FLAG_NARROW           BV05
+#define TERRAIN_FLAG_STANDARD         BV06
+#define TERRAIN_FLAG_WIDE             BV07
+#define TERRAIN_FLAG_VAST             BV08
+#define TERRAIN_FLAG_FADEIN           BV09
+#define TERRAIN_FLAG_FADEOUT          BV10
+#define TERRAIN_FLAG_DOUBLE           BV11
+
+#define MOUSE_FLAG_BUTTON_A             1
+#define MOUSE_FLAG_BUTTON_B             2
+#define MOUSE_FLAG_SHIFT                4
+#define MOUSE_FLAG_ALT                  8
+#define MOUSE_FLAG_CTRL                16
+#define MOUSE_FLAG_MOTION              32
+#define MOUSE_FLAG_WHEEL               64
+#define MOUSE_FLAG_EXTRA              128
+#define MOUSE_FLAG_UNKNOWN            256
+
+#define CURSOR_FLAG_ALWAYS              1
+//#define CURSOR_FLAG_NEVER               2
+#define CURSOR_FLAG_GET_ONE             4
+#define CURSOR_FLAG_GET_ALL             8
+
+#define STARTUP_FLAG_NOGREETING         1
+#define STARTUP_FLAG_SCREENREADER       2
+#define STARTUP_FLAG_NORESET            4
+#define STARTUP_FLAG_ARGUMENT           8
+#define STARTUP_FLAG_NOTITLE           16
+
+#define WRAP_FLAG_DISPLAY             (1 <<  0)
+#define WRAP_FLAG_WORD                (1 <<  1)
+#define WRAP_FLAG_SPLIT               (1 <<  2)
+
+#define LEGEND_ASCII                    0
+#define LEGEND_ASCII_MISC              16
+#define LEGEND_ASCII_CURVED            20
+#define LEGEND_ASCII_DIRS              24
+#define LEGEND_UNICODE                 32
+#define LEGEND_UNICODE_MISC            48
+#define LEGEND_UNICODE_CURVED          52
+#define LEGEND_UNICODE_DIRS            56
+#define LEGEND_MUDFONT                 64
+#define LEGEND_MUDFONT_NWS             64
+#define LEGEND_MUDFONT_NES             96
+#define LEGEND_MUDFONT_CURVED         192
+#define LEGEND_UNICODE_GRAPHICS       196
+#define LEGEND_MAX                    230
+
+#define UNICODE_DIR_SE                  1
+#define UNICODE_DIR_NE                  2
+#define UNICODE_DIR_SW                  4
+#define UNICODE_DIR_NW                  8
+#define UNICODE_DIR_D                  16
+#define UNICODE_DIR_N                  17
+#define UNICODE_DIR_S                  18
+#define UNICODE_DIR_NS                 19
+#define UNICODE_DIR_U                  20
+#define UNICODE_DIR_E                  21
+#define UNICODE_DIR_W                  22
+#define UNICODE_DIR_EW                 23
+#define UNICODE_DIR_RL                 24
+#define UNICODE_DIR_RL_CURVED          25
+#define UNICODE_DIR_RR                 26
+#define UNICODE_DIR_RR_CURVED          27
+
+
+
+#define MAP_COLOR_AVOID                 0
+#define MAP_COLOR_BACK                  1
+#define MAP_COLOR_BLOCK                 2
+#define MAP_COLOR_EXIT                  3
+#define MAP_COLOR_HIDE                  4
+#define MAP_COLOR_INVIS                 5
+#define MAP_COLOR_PATH                  6
+#define MAP_COLOR_ROOM                  7
+#define MAP_COLOR_SYMBOL                8
+#define MAP_COLOR_USER                  9
+#define MAP_COLOR_MAX                   10
+
+
+/*
+	Some macros to deal with double linked lists
+*/
+
+#define LINK(link, head, tail) \
+{ \
+	if ((head) == NULL) \
+	{ \
+		(head) = (link); \
+	} \
+	else \
+	{ \
+		(tail)->next = (link); \
+	} \
+	(link)->next = NULL; \
+	(link)->prev = (tail); \
+	(tail)				    = (link); \
+}
+
+
+#define INSERT_LEFT(link, right, head) \
+{ \
+	(link)->prev = (right)->prev; \
+	(right)->prev = (link); \
+	(link)->next = (right); \
+	\
+	if ((link)->prev) \
+	{ \
+		(link)->prev->next = (link); \
+	} \
+	else \
+	{ \
+		(head) = (link); \
+	} \
+}
+
+
+#define INSERT_RIGHT(link, left, tail) \
+{ \
+	(link)->next = (left)->next; \
+	(left)->next = (link); \
+	(link)->prev = (left); \
+\
+	if ((link)->next) \
+	{ \
+		(link)->next->prev = (link); \
+	} \
+	else \
+	{ \
+		(tail) = (link); \
+	} \
+}
+
+#define UNLINK(link, head, tail) \
+{ \
+	if (((link)->prev == NULL && (link) != head) \
+	||  ((link)->next == NULL && (link) != tail)) \
+	{ \
+		tintin_printf2(NULL, "#UNLINK ERROR in file %s on line %d", __FILE__, __LINE__); \
+		dump_stack(); \
+	} \
+	if ((link)->prev == NULL) \
+	{ \
+		(head)			   = (link)->next; \
+	} \
+	else \
+	{ \
+		(link)->prev->next	  = (link)->next; \
+	} \
+	if ((link)->next == NULL) \
+	{ \
+		(tail)			    = (link)->prev; \
+	} \
+	else \
+	{ \
+		(link)->next->prev	  = (link)->prev; \
+	} \
+	(link)->next = NULL; \
+	(link)->prev = NULL; \
+}
+
+/*
+	string allocation
+*/
+
+#define RESTRING(point, value) \
+{ \
+	if (point) free(point); \
+	point = strdup((value)); \
+}
+
+#define FREE(point) \
+{ \
+	free((point)); \
+	point = NULL; \
+}
+
+/*
+	Bit operations
+*/
+
+#define HAS_BIT(bitvector, bit)   ((bitvector)  & (bit))
+#define SET_BIT(bitvector, bit)   ((bitvector) |= (bit))
+#define DEL_BIT(bitvector, bit)   ((bitvector) &= (~(bit)))
+#define TOG_BIT(bitvector, bit)   ((bitvector) ^= (bit))
+#define FFS_BIT(bitvector)        ((ffs(bitvector) - 1)) 
+
+/*
+	Generic
+*/
+
+#define URANGE(a, b, c)           ((b) < (a) ? (a) : (b) > (c) ? (c) : (b))
+//#define URANGE(a, b, c)           ((b) <= (a) ? (a) : (c) >= (b) ? (b) : (c) < (b) ? (b) : (c))
+
+#define UMAX(a, b)                ((a) > (b) ? (a) : (b))
+#define UMIN(a, b)                ((a) < (b) ? (a) : (b))
+
+#define next_arg(u)               (u < 99 ? u++ : u)
+
+#define IS_SPLIT(ses)             (gtd->screen->rows != (ses)->split->bot_row)
+#define SCROLL(ses)               ((ses)->cur_row == 0 || ((ses)->cur_row >= (ses)->split->top_row && (ses)->cur_row <= (ses)->split->bot_row) || (ses)->cur_row == gtd->screen->rows)
+#define VERBATIM(ses)             (gtd->level->verbatim || (gtd->level->input == 0 && HAS_BIT((ses)->flags, SES_FLAG_VERBATIM)))
+
+#define DO_ARRAY(array) struct session *array (struct session *ses, struct listnode *list, char *arg, char *var)
+#define DO_BUFFER(buffer) void buffer (struct session *ses, char *arg)
+#define DO_CHAT(chat) void chat (char *arg1, char *arg2)
+#define DO_CLASS(class) struct session *class (struct session *ses, struct listnode *node, char *arg1, char *arg2)
+#define DO_COMMAND(command) struct session  *command (struct session *ses, char *arg)
+#define DO_CONFIG(config) struct session *config (struct session *ses, char *arg1, char *arg2, int index)
+#define DO_CURSOR(cursor) void cursor (struct session *ses, char *arg)
+#define DO_DAEMON(daemon) void daemon (struct session *ses, char *arg)
+#define DO_HISTORY(history) void history (struct session *ses, char *arg)
+#define DO_LINE(line) struct session *line (struct session *ses, char *arg)
+#define DO_MAP(map) void map (struct session *ses, char *arg, char *arg1, char *arg2)
+#define DO_PATH(path) void path (struct session *ses, char *arg)
+#define DO_PORT(port) struct session *port (struct session *ses, char *arg1, char *arg2, char *arg)
+#define DO_SCREEN(screen) void screen (struct session *ses, int ind, char *arg, char *arg1, char *arg2)
+#define DO_DRAW(draw) void draw (struct session *ses, int top_row, int top_col, int bot_row, int bot_col, int rows, int cols, long long flags, char *color, char *arg, char *arg1, char *arg2)
+
+/*
+	Compatibility
+*/
+
+
+#define atoll(str) (strtoll(str, NULL, 10))
+
+/************************ structures *********************/
+
+struct listroot
+{
+	struct listnode      ** list;
+	struct session        * ses;
+	int                     size;
+	int                     used;
+	int                     update;
+	short                   type;
+	short                   flags;
+};
+
+
+struct listnode
+{
+	struct listroot       * root;
+	char                  * arg1;
+	char                  * arg2;
+	char                  * arg3;
+	char                  * arg4;
+	char                  * group;
+	int                     flags;
+	union
+	{
+		pcre              * regex;      // act, alias, gag, highlight, substitute
+		char              * data;       // class
+		struct room_data  * room;       // terrain
+		long long           val64;      // delay, tick, path
+		short               val16[4];   // button
+		int                 val32[2];   // landmark, class
+	};
+};
+
+struct scriptroot
+{
+	struct scriptnode    * next;
+	struct scriptnode    * prev;
+	struct session       * ses;
+	struct listroot      * local;
+	int list;
+};
+
+struct process_data
+{
+	pid_t                   pid;
+	uid_t                   uid;
+	gid_t                   gid;
+};
+
+struct tintin_data
+{
+	struct session        * ses;
+	struct session        * update;
+	struct session        * all;
+	struct session        * dispose_next;
+	struct session        * dispose_prev;
+	struct chat_data      * chat;
+	struct termios          old_terminal;
+	struct screen_data    * screen;
+	struct level_data     * level;
+	struct str_data       * memory;
+	char                  * detach_file;
+	int                     detach_port;
+	struct process_data     detach_info;
+	int                     detach_pid;
+	int                     detach_sock;
+	char                 *  attach_file;
+	int                     attach_pid;
+	int                     attach_sock;
+//	int                     fd_attach;
+	int                     daemon;
+	char                  * buf;
+	char                  * out;
+	char                  * mud_output_buf;
+	int                     mud_output_max;
+	int                     mud_output_len;
+	unsigned char         * mccp_buf;
+	int                     mccp_len;
+	char                    input_buf[BUFFER_SIZE];
+	char                    input_tmp[BUFFER_SIZE];
+	char                    macro_buf[BUFFER_SIZE];
+	char                    paste_buf[BUFFER_SIZE];
+	char                    is_result[NUMBER_SIZE];
+	int                     input_off;
+	int                     input_len;
+	int                     input_cur;
+	int                     input_pos;
+	int                     input_hid;
+	int                     input_tab;
+	char                  * home;
+	char                  * lang;
+	char                  * os;
+	char                  * term;
+	char                  * exec;
+	time_t                  time;
+	time_t                  time_input;
+	time_t                  time_session;
+	struct tm		calendar;
+	unsigned long long      utime;
+	long long               timer[TIMER_CPU][5];
+	long long               total_io_ticks;
+	long long               total_io_exec;
+	long long               total_io_delay;
+	int                     history_size;
+	int                     command_ref[26];
+	int                     msdp_table_size;
+	int                     flags;
+	struct scriptroot     * script_stack[STACK_SIZE];
+	int                     script_index;
+	char                    tintin_char;
+	char                    verbatim_char;
+	char                    repeat_char;
+	char                  * vars[100];
+	char                  * cmds[100];
+	int                     args[100];
+};
+
+struct session
+{
+	struct session        * next;
+	struct session        * prev;
+	struct map_data       * map;
+	struct port_data      * port;
+	z_stream              * mccp2;
+	z_stream              * mccp3;
+	gnutls_session_t        ssl;
+	struct termios          cur_terminal;
+	struct scroll_data    * scroll;
+	struct split_data     * split;
+	char                  * name;
+	char                  * group;
+	FILE                  * logfile;
+	int                     logmode;
+	FILE                  * lognext_file;
+	char                  * lognext_name;
+	time_t                  lognext_time;
+	FILE                  * logline_file;
+	char                  * logline_name;
+	time_t                  logline_time;
+	char                  * line_capturefile;
+	int                     line_captureindex;
+	struct listroot       * list[LIST_MAX];
+	int                     created;
+	int                     cur_row;
+	int                     sav_row;
+	int                     cur_col;
+	int                     sav_col;
+	int                     wrap;
+	int                     fgc;
+	int                     bgc;
+	int                     vtc;
+	int                     socket;
+	int                     telopts;
+	int                     telopt_flag[8];
+	int                     event_flags;
+	long long               flags;
+	int                     charset;
+	char                  * session_host;
+	char                  * session_ip;
+	char                  * session_port;
+	char                  * cmd_color;
+	unsigned char         * read_buf;
+	int                     read_len;
+	int                     read_max;
+	unsigned long long      connect_retry;
+	int                     connect_error;
+	char                    more_output[BUFFER_SIZE];
+	int                     color;
+	char                    color_patch[100];
+	unsigned long long      packet_patch;
+	unsigned long long      check_output;
+	int                     auto_tab;
+	int                     tab_width;
+	unsigned long long      rand;
+};
+
+
+struct level_data
+{
+	int                     background;
+	int                     debug;
+	int                     grep;
+	int                     ignore;
+	int                     info;
+	int                     input;
+	int                     oneshot;
+	int                     quiet;
+	int                     scroll;
+	int                     verbatim;
+	int                     verbose;
+};
+
+struct split_data
+{
+	int                     sav_top_row;
+	int                     sav_top_col;
+	int                     sav_bot_row;
+	int                     sav_bot_col;
+	int                     top_row;
+	int                     top_col;
+	int                     bot_row;
+	int                     bot_col;
+};
+
+struct scroll_data
+{
+	struct buffer_data   ** buffer;
+	int                     base;
+	int                     line;
+	int                     used;
+	int                     size;
+	int                     wrap;
+	int                     time;
+	char                  * input;
+	int                     flags;
+};
+
+
+struct buffer_data
+{
+	int                     width;
+	int                     height;
+	unsigned short          lines;
+	unsigned short          flags;
+	time_t                  time;
+	char                  * str;
+};
+
+struct chat_data
+{
+	struct chat_data      * next;
+	struct chat_data      * prev;
+	struct chat_data      * update;
+	char                  * name;
+	char                  * ip;
+	char                  * version;
+	char                  * download;
+	char                  * reply;
+	char                  * prefix;
+	char                  * paste_buf;
+	char                  * color;
+	char                  * group;
+	int                     port;
+	int                     fd;
+	time_t                  timeout;
+	int                     flags;
+	unsigned long long      paste_time;
+	FILE                  * file_pt;
+	char                  * file_name;
+	long long               file_size;
+	int                     file_block_cnt;
+	int                     file_block_tot;
+	int                     file_block_patch;
+	unsigned long long      file_start_time;
+};
+
+struct port_data
+{
+	struct port_data      * next;
+	struct port_data      * prev;
+	struct port_data      * update;
+	char                  * name;
+	char                  * ip;
+	char                  * prefix;
+	char                  * color;
+	char                  * group;
+	int                     port;
+	int                     fd;
+	int                     flags;
+	int                     comm_flags;
+	long long               mtts_flags;
+	struct msdp_data     ** msdp_data;
+	char                  * proxy;
+	char                  * ttype;
+	char                    telbuf[BUFFER_SIZE];
+	int                     teltop;
+	char                    inbuf[BUFFER_SIZE];
+	int                     intop;
+	int                     cols;
+	int                     rows;
+	int                     total;
+	int                     rank;
+	z_stream              * mccp2;
+	z_stream              * mccp3;
+};
+
+struct link_data
+{
+	struct link_data     * next;
+	struct link_data     * prev;
+	char                 * str1;
+	char                 * str2;
+	char                 * str3;
+};
+
+// Unused
+
+struct map_group_type
+{
+	char                  * name;
+	char                  * group;
+	int                     min_row;
+	int                     min_col;
+	int                     max_row;
+	int                     max_col;
+	int                     start;
+	int                     end;
+	char                  * reset;
+};
+
+struct msdp_data
+{
+	char                 * value;
+	int                    flags;
+};
+
+struct str_data
+{
+	struct str_data         * next;
+	struct str_data         * prev;
+	int                       max;
+	int                       len;
+};
+
+
+struct row_data
+{
+	char                  * str;
+};
+
+struct screen_data
+{
+	struct row_data      ** lines;
+	int                     rows;
+	int                     cols;
+	int                     height;
+	int                     width;
+	int                     pos_height;
+	int                     pos_width;
+	int                     minimized;
+	int                     focus;
+	int                     char_height;
+	int                     char_width;
+	int                     desk_rows;
+	int                     desk_cols;
+	int                     desk_height;
+	int                     desk_width;
+	int                     top_row;
+	int                     bot_row;
+	int                     cur_row;
+	int                     cur_col;
+//	int                     sav_row;
+//	int                     sav_col;
+	int                     max_row;
+	int                     sav_lev;
+	int                     sav_row[STACK_SIZE];
+	int                     sav_col[STACK_SIZE];
+};
+
+struct map_data
+{
+	struct room_data     ** room_list;
+	struct room_data     ** grid_rooms;
+	FILE                  * logfile;
+	struct link_data      * undo_head;
+	struct link_data      * undo_tail;
+	struct search_data    * search;
+	char                  * buf;
+	char                  * out;
+	char                    color[MAP_COLOR_MAX][COLOR_SIZE];
+	char                    color_raw[MAP_COLOR_MAX][COLOR_SIZE];
+	int                     center_x;
+	int                     center_y;
+	int                     center_z;
+	int                     max_grid_x;
+	int                     max_grid_y;
+	int                     sav_top_row;
+	int                     sav_top_col;
+	int                     sav_bot_row;
+	int                     sav_bot_col;
+	int                     top_row;
+	int                     top_col;
+	int                     bot_row;
+	int                     bot_col;
+	int                     rows;
+	int                     cols;
+	int                     undo_size;
+	int                     dir;
+	int                     size;
+	int                     flags;
+	int                     in_room;
+	int                     at_room;
+	int                     last_room;
+	int                     global_vnum;
+	struct exit_data      * global_exit;
+	int                     version;
+	short                   display_stamp;
+	int                     nofollow;
+	char                    legend[LEGEND_MAX][LEGEND_SIZE];
+	char                    legend_raw[LEGEND_MAX][LEGEND_SIZE];
+};
+
+struct room_data
+{
+	struct exit_data        * f_exit;
+	struct exit_data        * l_exit;
+	struct exit_data        * exit_grid[11];
+	int                       vnum;
+	short                     exit_size;
+	long long                 exit_dirs;
+	float                     length;
+	float                     weight;
+	short                     search_stamp;
+	short                     display_stamp;
+	int                       flags;
+	int                       w;
+	int                       x;
+	int                       y;
+	int                       z;
+	int                       terrain_index;
+	short                     terrain_flags;
+	char                    * area;
+	char                    * color;
+	char                    * data;
+	char                    * desc;
+	char                    * id;
+	char                    * name;
+	char                    * note;
+	char                    * symbol;
+	char                    * terrain;
+
+};
+
+struct exit_data
+{
+	struct exit_data        * next;
+	struct exit_data        * prev;
+	int                       vnum;
+	int                       dir;
+	int                       grid;
+	int                       flags;
+	float                     weight;
+	char                    * name;
+	char                    * cmd;
+	char                    * color;
+	char                    * data;
+};
+
+struct search_data
+{
+	int                     vnum;
+	short                   stamp;
+	char                  * arg;
+	pcre                  * name;
+	int                     exit_size;
+	long long               exit_dirs;
+	char                  * exit_list;
+	pcre                  * desc;
+	pcre                  * area;
+	pcre                  * note;
+	pcre                  * terrain;
+	long long               flag;
+	char                  * id;
+};
+
+
+/*
+	Typedefs
+*/
+
+typedef struct session *ARRAY   (struct session *ses, struct listnode *list, char *arg, char *var);
+typedef void            CHAT    (char *arg1, char *arg2);
+typedef struct session *CLASS   (struct session *ses, struct listnode *node, char *left, char *right);
+typedef struct session *CONFIG  (struct session *ses, char *arg1, char *arg2, int index);
+typedef struct session *COMMAND (struct session *ses, char *arg);
+typedef void            DAEMON  (struct session *ses, char *arg);
+typedef void            MAP     (struct session *ses, char *arg, char *arg1, char *arg2);
+typedef void            CURSOR  (struct session *ses, char *arg);
+typedef void            PATH    (struct session *ses, char *arg);
+typedef struct session *PORT    (struct session *ses, char *arg1, char *arg2, char *arg);
+typedef struct session *LINE    (struct session *ses, char *arg);
+typedef void            HISTORY (struct session *ses, char *arg);
+typedef void            BUFFER  (struct session *ses, char *arg);
+typedef void            MSDP    (struct session *ses, struct port_data *buddy, int index);
+typedef void            SCREEN  (struct session *ses, int ind, char *arg, char *arg1, char *arg2);
+typedef void            DRAW    (struct session *ses, int top_row, int top_col, int bot_row, int bot_col, int rows, int cols, long long flags, char *color, char *arg, char *arg1, char *arg2);
+
+/*
+	Structures for tables.c
+*/
+
+struct array_type
+{
+	char                  * name;
+	ARRAY                 * fun;
+	char                  * desc;
+};
+
+struct buffer_type
+{
+	char                  * name;
+	BUFFER                * fun;
+	char                  * desc;
+};
+
+struct chat_type
+{
+	char                  * name;
+	CHAT                  * fun;
+	int                     lval;
+	int                     rval;
+	char                  * desc;
+};
+
+struct class_type
+{
+	char                  * name;
+	CLASS                 * fun;
+};
+
+struct color_type
+{
+	char                  * name;
+	char                  * code;
+	int                     len;
+};
+
+struct command_type
+{
+	char                  * name;
+	COMMAND               * command;
+	int                     type;
+};
+
+struct config_type
+{
+	char                  * name;
+	char                  * msg_on;
+	char                  * msg_off;
+	CONFIG                * config;
+};
+
+struct cursor_type
+{
+	char                  * name;
+	char                  * desc;
+	char                  * code;
+	int                     flags;
+	CURSOR                * fun;
+};
+
+struct daemon_type
+{
+	char                  * name;
+	PATH                  * fun;
+	char                  * desc;
+};
+
+struct draw_type
+{
+	char                  * name;
+	char                  * desc;
+	int                     flags;
+	DRAW                  * fun;
+};
+
+struct event_type
+{
+	char                  * name;
+	int                     flags;
+	char                  * desc;
+};
+
+struct history_type
+{
+	char                  * name;
+	HISTORY               * fun;
+	char                  * desc;
+};
+
+struct list_type
+{
+	char                  * name;
+	char                  * name_multi;
+	int                     mode;
+	int                     args;
+	int                     script_arg;
+	int                     priority_arg;
+	int                     flags;
+};
+
+
+struct line_type
+{
+	char                  * name;
+	LINE                  * fun;
+	char                  * desc;
+};
+
+
+struct map_type
+{
+	char                  * name;
+	MAP                   * fun;
+	int                     flags;
+	int                     check;
+	char                  * desc;
+};
+
+struct msdp_type
+{
+	char                 * name;
+	int                    flags;
+	int                    rank;
+	MSDP                 * fun;
+};
+
+struct path_type
+{
+	char                  * name;
+	PATH                  * fun;
+	char                  * desc;
+};
+
+struct port_type
+{
+	char                  * name;
+	PORT                  * fun;
+	int                     lval;
+	int                     rval;
+	char                  * desc;
+};
+
+struct pulse_type
+{
+	unsigned char           update_input;
+	unsigned char           update_sessions;
+	unsigned char           update_delays;
+	unsigned char           update_daemon;
+	unsigned char           update_chat;
+	unsigned char           update_port;
+	unsigned char           update_ticks;
+	unsigned char           update_paths;
+	unsigned char           update_packets;
+	unsigned char           update_terminal;
+	unsigned char           update_memory;
+	unsigned char           update_time;
+};
+
+
+struct rank_type
+{
+	char                  * name;
+	int                     flags;
+};
+
+struct stamp_type
+{
+	char                  * name;
+	int                     length;
+	char                  * desc;
+};
+
+struct substitution_type
+{
+	char                  * name;
+	int                     bitvector;
+};
+
+struct timer_type
+{
+	char                  * name;
+};
+
+struct telopt_type
+{
+	char                  * name;
+	int                     want;
+	int                     flags;
+};
+
+
+struct screen_type
+{
+	char                  * name;
+	char                  * desc;
+	int                     get1;
+	int                     get2;
+	int                     flags;
+	SCREEN                * fun;
+
+};
+
+struct map_legend_type
+{
+	char                  * name;
+	char                  * group;
+	char                  * min;
+	char                  * max;
+};
+
+
+/*
+	Various structures
+*/
+
+
+#endif
+
+
+/*
+	Function declarations
+*/
+
+#ifndef __ADVERTISE_H__
+#define __ADVERTISE_H__
+
+extern DO_COMMAND(do_advertise);
+
+#endif
+
+
+#ifndef __ARRAY_H__
+#define __ARRAY_H__
+
+extern DO_COMMAND(do_list);
+extern DO_ARRAY(array_add);
+extern DO_ARRAY(array_clear);
+extern DO_ARRAY(array_collapse);
+extern DO_ARRAY(array_create);
+extern DO_ARRAY(array_explode);
+extern DO_ARRAY(array_insert);
+extern DO_ARRAY(array_delete);
+extern DO_ARRAY(array_find);
+extern DO_ARRAY(array_get);
+extern DO_ARRAY(array_shuffle);
+extern DO_ARRAY(array_simplify);
+extern DO_ARRAY(array_size);
+extern DO_ARRAY(array_set);
+extern DO_ARRAY(array_sort);
+extern DO_ARRAY(array_tokenize);
+
+#endif
+
+
+#ifndef __BUFFER_H__
+#define __BUFFER_H__
+
+extern DO_COMMAND(do_buffer);
+extern DO_COMMAND(do_grep);
+
+extern void init_buffer(struct session *ses, int size);
+extern void add_line_buffer(struct session *ses, char *line, int more_output);
+extern int show_buffer(struct session *ses);
+
+extern DO_BUFFER(buffer_up);
+extern DO_BUFFER(buffer_clear);
+extern DO_BUFFER(buffer_down);
+extern DO_BUFFER(buffer_get);
+extern DO_BUFFER(buffer_home);
+extern DO_BUFFER(buffer_end);
+extern DO_BUFFER(buffer_lock);
+extern DO_BUFFER(buffer_find);
+extern DO_BUFFER(buffer_write);
+extern DO_BUFFER(buffer_info);
+
+#endif
+
+
+#ifndef __CHAT_H__
+#define __CHAT_H__
+
+extern DO_COMMAND(do_chat);
+
+extern void process_chat_connections(fd_set *read_set, fd_set *write_set, fd_set *exc_set);
+extern void chat_socket_printf(struct chat_data *buddy, char *format, ...);
+extern void close_chat(struct chat_data *buddy, int unlink);
+extern void chat_forward_session(struct session *ses, char *linelog);
+
+extern DO_CHAT(chat_accept);
+extern DO_CHAT(chat_call);
+extern DO_CHAT(chat_cancelfile);
+extern DO_CHAT(chat_color);
+extern DO_CHAT(chat_decline);
+extern DO_CHAT(chat_dnd);
+extern DO_CHAT(chat_downloaddir);
+extern DO_CHAT(chat_emote);
+extern DO_CHAT(chat_filestat);
+extern DO_CHAT(chat_group);
+extern DO_CHAT(chat_forward);
+extern DO_CHAT(chat_forwardall);
+extern DO_CHAT(chat_ignore);
+extern DO_CHAT(chat_initialize);
+extern DO_CHAT(chat_info);
+extern DO_CHAT(chat_ip);
+extern DO_CHAT(chat_message);
+extern DO_CHAT(chat_name);
+extern DO_CHAT(chat_paste);
+extern DO_CHAT(chat_peek);
+extern DO_CHAT(chat_ping);
+extern DO_CHAT(chat_prefix);
+extern DO_CHAT(chat_private);
+extern DO_CHAT(chat_public);
+extern DO_CHAT(chat_reply);
+extern DO_CHAT(chat_request);
+extern DO_CHAT(chat_send);
+extern DO_CHAT(chat_sendfile);
+extern DO_CHAT(chat_transfer);
+extern DO_CHAT(chat_serve);
+extern DO_CHAT(chat_uninitialize);
+extern DO_CHAT(chat_who);
+extern DO_CHAT(chat_zap);
+
+#endif
+
+
+#ifndef __CLASS_H__
+#define __CLASS_H__
+
+extern DO_COMMAND(do_class);
+
+extern  int count_class(struct session *ses, struct listnode *group);
+extern void parse_class(struct session *ses, char *input, struct listnode *group);
+
+extern DO_CLASS(class_close);
+extern DO_CLASS(class_kill);
+extern DO_CLASS(class_list);
+extern DO_CLASS(class_load);
+extern DO_CLASS(class_open);
+extern DO_CLASS(class_read);
+extern DO_CLASS(class_save);
+extern DO_CLASS(class_size);
+extern DO_CLASS(class_write);
+
+#endif
+
+
+#ifndef __CURSOR_H__
+#define __CURSOR_H__
+
+extern DO_COMMAND(do_cursor);
+
+int inputline_cur_pos(void);
+
+extern DO_CURSOR(cursor_backspace);
+extern DO_CURSOR(cursor_brace_open);
+extern DO_CURSOR(cursor_brace_close);
+extern DO_CURSOR(cursor_buffer_down);
+extern DO_CURSOR(cursor_buffer_end);
+extern DO_CURSOR(cursor_buffer_home);
+extern DO_CURSOR(cursor_buffer_lock);
+extern DO_CURSOR(cursor_buffer_up);
+extern DO_CURSOR(cursor_check_line);
+extern DO_CURSOR(cursor_check_line_modified);
+extern DO_CURSOR(cursor_clear_left);
+extern DO_CURSOR(cursor_clear_line);
+extern DO_CURSOR(cursor_clear_right);
+extern DO_CURSOR(cursor_convert_meta);
+extern DO_CURSOR(cursor_delete);
+extern DO_CURSOR(cursor_delete_or_exit);
+extern DO_CURSOR(cursor_delete_word_left);
+extern DO_CURSOR(cursor_delete_word_right);
+extern DO_CURSOR(cursor_echo);
+extern DO_CURSOR(cursor_end);
+extern DO_CURSOR(cursor_enter);
+extern DO_CURSOR(cursor_exit);
+extern DO_CURSOR(cursor_get);
+extern DO_CURSOR(cursor_history_find);
+extern DO_CURSOR(cursor_history_next);
+extern DO_CURSOR(cursor_history_prev);
+extern DO_CURSOR(cursor_history_search);
+extern DO_CURSOR(cursor_home);
+extern DO_CURSOR(cursor_info);
+extern DO_CURSOR(cursor_insert);
+extern DO_CURSOR(cursor_left);
+extern DO_CURSOR(cursor_left_word);
+extern DO_CURSOR(cursor_paste_buffer);
+extern DO_CURSOR(cursor_redraw_input);
+extern DO_CURSOR(cursor_redraw_line);
+extern DO_CURSOR(cursor_right);
+extern DO_CURSOR(cursor_right_word);
+extern DO_CURSOR(cursor_set);
+extern DO_CURSOR(cursor_suspend);
+extern DO_CURSOR(cursor_tab);
+extern DO_CURSOR(cursor_tab_backward);
+extern DO_CURSOR(cursor_tab_forward);
+extern DO_CURSOR(cursor_auto_tab_backward);
+extern DO_CURSOR(cursor_auto_tab_forward);
+extern DO_CURSOR(cursor_mixed_tab_backward);
+extern DO_CURSOR(cursor_mixed_tab_forward);
+extern DO_CURSOR(cursor_screen_focus_in);
+extern DO_CURSOR(cursor_screen_focus_out);
+
+#endif
+
+
+#ifndef __INPUT_H__
+#define __INPUT_H__
+
+extern void  process_input(void);
+extern void  read_line(char *input, int len);
+extern void  read_key(char *input, int len);
+extern int   check_key(char *input, int len);
+extern void  convert_meta(char *input, char *output, int eol);
+extern char *str_convert_meta(char *input, int eol);
+extern void  echo_command(struct session *ses, char *line);
+extern void  input_printf(char *format, ...);
+extern void  modified_input(void);
+
+#endif
+
+
+#ifndef __MAPPER_H__
+#define __MAPPER_H__
+
+extern DO_COMMAND(do_map);
+
+extern  int follow_map(struct session *ses, char *argument);
+extern void show_vtmap(struct session *ses);
+extern void map_mouse_handler(struct session *ses, char *left, char *right, int row, int col, int height, int width);
+extern  int delete_map(struct session *ses);
+
+extern DO_MAP(map_at);
+extern DO_MAP(map_center);
+extern DO_MAP(map_color);
+extern DO_MAP(map_create);
+extern DO_MAP(map_debug);
+extern DO_MAP(map_delete);
+extern DO_MAP(map_destroy);
+extern DO_MAP(map_dig);
+extern DO_MAP(map_entrance);
+extern DO_MAP(map_exit);
+extern DO_MAP(map_exitflag);
+extern DO_MAP(map_explore);
+extern DO_MAP(map_find);
+extern DO_MAP(map_flag);
+extern DO_MAP(map_get);
+extern DO_MAP(map_global);
+extern DO_MAP(map_goto);
+extern DO_MAP(map_info);
+extern DO_MAP(map_insert);
+extern DO_MAP(map_jump);
+extern DO_MAP(map_landmark);
+extern DO_MAP(map_leave);
+extern DO_MAP(map_legend);
+extern DO_MAP(map_link);
+extern DO_MAP(map_list);
+extern DO_MAP(map_map);
+extern DO_MAP(map_move);
+extern DO_MAP(map_name);
+extern DO_MAP(map_offset);
+extern DO_MAP(map_read);
+extern DO_MAP(map_resize);
+extern DO_MAP(map_return);
+extern DO_MAP(map_roomflag);
+extern DO_MAP(map_run);
+extern DO_MAP(map_set);
+extern DO_MAP(map_sync);
+extern DO_MAP(map_terrain);
+extern DO_MAP(map_travel);
+extern DO_MAP(map_undo);
+extern DO_MAP(map_uninsert);
+extern DO_MAP(map_unlandmark);
+extern DO_MAP(map_unlink);
+extern DO_MAP(map_unterrain);
+extern DO_MAP(map_update);
+extern DO_MAP(map_vnum);
+extern DO_MAP(map_write);
+
+#endif
+
+
+#ifndef __TT_MATH_H__
+#define __TT_MATH_H__
+
+extern DO_COMMAND(do_math);
+
+extern int is_math(struct session *ses, char *str);
+extern int get_ellipsis(struct listroot *root, char *name, int *min, int *max);
+extern long double get_number(struct session *ses, char *str);
+extern unsigned long long get_integer(struct session *ses, char *str);
+extern long double get_double(struct session *ses, char *str);
+extern void get_number_string(struct session *ses, char *str, char *result);
+extern long double mathswitch(struct session *ses, char *left, char *right);
+extern void mathexp(struct session *ses, char *str, char *result, int seed);
+extern int mathexp_tokenize(struct session *ses, char *str, int seed, int debug);
+extern void mathexp_level(struct session *ses, struct link_data *node);
+extern void mathexp_compute(struct session *ses, struct link_data *node);
+extern long double tintoi(char *str);
+extern unsigned long long tintou(char *str);
+extern long double tincmp(char *left, char *right);
+extern long double tineval(struct session *ses, char *left, char *right);
+extern long double tindice(struct session *ses, char *left, char *right);
+
+#endif
+
+
+#ifndef __CONFIG_H__
+#define __CONFIG_H__
+
+extern DO_COMMAND(do_configure);
+
+extern DO_CONFIG(config_autotab);
+extern DO_CONFIG(config_buffersize);
+extern DO_CONFIG(config_charset);
+extern DO_CONFIG(config_colormode);
+extern DO_CONFIG(config_colorpatch);
+extern DO_CONFIG(config_commandcolor);
+extern DO_CONFIG(config_commandecho);
+extern DO_CONFIG(config_connectretry);
+extern DO_CONFIG(config_childlock);
+extern DO_CONFIG(config_convertmeta);
+extern DO_CONFIG(config_debugtelnet);
+extern DO_CONFIG(config_historysize);
+extern DO_CONFIG(config_inheritance);
+extern DO_CONFIG(config_loglevel);
+extern DO_CONFIG(config_logmode);
+extern DO_CONFIG(config_mccp);
+extern DO_CONFIG(config_mousetracking);
+extern DO_CONFIG(config_packetpatch);
+extern DO_CONFIG(config_pid);
+extern DO_CONFIG(config_randomseed);
+extern DO_CONFIG(config_repeatchar);
+extern DO_CONFIG(config_repeatenter);
+extern DO_CONFIG(config_screenreader);
+extern DO_CONFIG(config_scrolllock);
+extern DO_CONFIG(config_speedwalk);
+extern DO_CONFIG(config_tabwidth);
+extern DO_CONFIG(config_telnet);
+extern DO_CONFIG(config_tintinchar);
+extern DO_CONFIG(config_verbatim);
+extern DO_CONFIG(config_verbatimchar);
+extern DO_CONFIG(config_verbose);
+extern DO_CONFIG(config_wordwrap);
+
+#endif
+
+
+#ifndef __SUBSTITUTE_H__
+#define __SUBSTITUTE_H__
+
+extern char *fuzzy_color_code(struct session *ses, char *pti);
+extern char *dim_color_code(struct session *ses, char *pti, int mod);
+extern char *lit_color_code(struct session *ses, char *pti, int mod);
+extern int is_color_code(char *str);
+extern int substitute_color(char *input, char *output, int colors);
+
+#endif
+
+#ifndef __DAEMON_H__
+#define __DAEMON_H__
+
+extern DO_COMMAND(do_daemon);
+
+extern DO_DAEMON(daemon_attach);
+extern DO_DAEMON(daemon_detach);
+extern DO_DAEMON(daemon_input);
+extern DO_DAEMON(daemon_kill);
+extern DO_DAEMON(daemon_list);
+
+extern void reset_daemon(void);
+extern void winch_daemon(void);
+
+#endif
+
+#ifndef __DATA_H__
+#define __DATA_H__
+
+extern DO_COMMAND(do_kill);
+extern DO_COMMAND(do_killall);
+extern DO_COMMAND(do_message);
+extern DO_COMMAND(do_ignore);
+extern DO_COMMAND(do_debug);
+
+
+extern void kill_list(struct listroot *root);
+extern void free_list(struct listroot *root);
+extern  int show_node_with_wild(struct session *ses, char *cptr, struct listroot *root);
+extern void show_node(struct listroot *root, struct listnode *node, int level);
+extern void show_nest(struct listnode *node, char *result);
+extern void show_list(struct listroot *root, int level);
+extern void delete_node_list(struct session *ses, int type, struct listnode *node);
+extern  int delete_node_with_wild(struct session *ses, int index, char *string);
+extern void delete_index_list(struct listroot *root, int index);
+extern  int search_index_list(struct listroot *root, char *text, char *priority);
+extern  int locate_index_list(struct listroot *root, char *text, char *priority);
+extern  int bsearch_alpha_list(struct listroot *root, char *text, int seek);
+extern  int bsearch_priority_list(struct listroot *root, char *text, char *priority, int seek);
+extern  int nsearch_list(struct listroot *root, char *text);
+extern struct listroot *init_list(struct session *ses, int type, int size);
+extern struct listroot *copy_list(struct session *ses, struct listroot *sourcelist, int type);
+extern struct listnode *insert_node_list(struct listroot *root, char *arg1, char *arg2, char *arg3, char *arg4);
+extern struct listnode *update_node_list(struct listroot *root, char *arg1, char *arg2, char *arg3, char *arg4);
+extern struct listnode *insert_index_list(struct listroot *root, struct listnode *node, int index);
+extern struct listnode *search_node_list(struct listroot *root, char *text);
+
+
+#endif
+
+
+#ifndef __DEBUG_H__
+#define __DEBUG_H__
+
+extern int push_call(char *f, ...);
+extern void pop_call(void);
+extern void dump_stack(void);
+extern void dump_stack_fatal(void);
+extern void dump_full_stack(void);
+
+#endif
+
+#ifndef __DICT_H__
+#define __DICT_H__
+
+DO_COMMAND(do_dictionary);
+
+extern int spellcheck_count(struct session *ses, char *in);
+
+#endif
+
+
+#ifndef __DRAW_H__
+#define __DRAW_H__
+
+extern DO_COMMAND(do_draw);
+
+DO_DRAW(draw_blank);
+DO_DRAW(draw_bot_side);
+DO_DRAW(draw_box);
+DO_DRAW(draw_corner);
+DO_DRAW(draw_horizontal_line);
+DO_DRAW(draw_left_side);
+DO_DRAW(draw_line);
+DO_DRAW(draw_map);
+DO_DRAW(draw_right_side);
+DO_DRAW(draw_side);
+DO_DRAW(draw_square);
+DO_DRAW(draw_stamp);
+DO_DRAW(draw_rain);
+DO_DRAW(draw_table_grid);
+DO_DRAW(draw_text);
+DO_DRAW(draw_top_side);
+//DO_DRAW(draw_vertical_line);
+DO_DRAW(draw_vertical_lines);
+
+#endif
+
+
+#ifndef __EVENT_H__
+#define __EVENT_H__
+
+extern DO_COMMAND(do_event);
+extern DO_COMMAND(do_unevent);
+
+extern  int check_all_events(struct session *ses, int flags, int args, int vars, char *fmt, ...);
+extern void mouse_handler(struct session *ses, int val1, int val2, int val3, char type);
+
+#endif
+
+
+#ifndef __FILES_H__
+#define __FILES_H__
+
+extern DO_COMMAND(do_read);
+extern DO_COMMAND(do_write);
+
+extern struct session *read_file(struct session *ses, FILE *fp, char *filename);
+extern void write_node(struct session *ses, int mode, struct listnode *node, FILE *file);
+
+#endif 
+
+
+#ifndef __HELP_H__
+#define __HELP_H__
+
+extern DO_COMMAND(do_help);
+
+#endif
+
+
+#ifndef __HISTORY_H__
+#define __HISTORY_H__
+
+extern DO_COMMAND(do_history);
+extern void add_line_history(struct session *ses, char *line);
+extern void insert_line_history(struct session *ses, char *line);
+extern void search_line_history(struct session *ses, char *line);
+extern int write_history(struct session *ses, char *filename);
+extern int read_history(struct session *ses, char *filename);
+
+DO_HISTORY(history_character);
+DO_HISTORY(history_delete);
+DO_HISTORY(history_insert);
+DO_HISTORY(history_list);
+DO_HISTORY(history_size);
+DO_HISTORY(history_read);
+DO_HISTORY(history_write);
+
+#endif
+
+
+#ifndef __LINE_H__
+#define __LINE_H__
+
+extern DO_COMMAND(do_line);
+extern DO_LINE(line_background);
+extern DO_LINE(line_benchmark);
+extern DO_LINE(line_capture);
+extern DO_LINE(line_debug);
+extern DO_LINE(line_gag);
+extern DO_LINE(line_ignore);
+extern DO_LINE(line_log);
+extern DO_LINE(line_logmode);
+extern DO_LINE(line_logverbatim);
+extern DO_LINE(line_oneshot);
+extern DO_LINE(line_quiet);
+extern DO_LINE(line_strip);
+extern DO_LINE(line_substitute);
+extern DO_LINE(line_verbatim);
+extern DO_LINE(line_verbose);
+
+#endif
+
+
+#ifndef __LOG_H__
+#define __LOG_H__
+
+extern DO_COMMAND(do_log);
+
+extern void loginit(struct session *ses, FILE *file, int newline);
+extern void logit(struct session *ses, char *txt, FILE *file, int newline);
+extern void write_html_header(struct session *ses, FILE *fp);
+extern void vt102_to_html(struct session *ses, char *txt, char *out);
+
+#endif
+
+
+#ifndef __MAIN_H__
+#define __MAIN_H__
+
+extern struct session *gts;
+extern struct tintin_data *gtd;
+extern void winch_handler(int signal);
+extern void abort_handler(int signal);
+extern void pipe_handler(int signal);
+extern void suspend_handler(int signal);
+extern void trap_handler(int signal);
+extern  int main(int argc, char **argv);
+extern void init_tintin(int greeting);
+extern void quitmsg(char *message);
+extern void syserr_fatal(int signal, char *msg);
+extern void syserr_printf(struct session *ses, char *fmt, ...);
+
+#endif
+
+#ifndef __MCCP_H__
+#define __MCCP_H__
+
+void *zlib_alloc(void *opaque, unsigned int items, unsigned int size);
+void  zlib_free(void *opaque, void *address);
+
+#endif
+
+#ifndef __MEMORY_H__
+#define __MEMORY_H__
+
+extern char *restring(char *point, char *string);
+extern char *restringf(char *point, char *fmt, ...);
+extern  int str_len(char *str);
+extern void str_fix(char *str);
+extern char *str_alloc(int len);
+extern void str_clone(char **clone, char *original);
+extern char *str_mim(char *original);
+extern char *str_dup(char *original);
+extern char *str_dup_clone(char *original);
+extern char *str_dup_printf(char *fmt, ...);
+extern char *str_cpy(char **ptr, char *str);
+extern char *str_cpy_printf(char **ptr, char *fmt, ...);
+extern char *str_ndup(char *original, int len);
+extern char *str_ncpy(char **ptr, char *str, int len);
+extern char *str_cat(char **ptr, char *str);
+extern char *str_cat_chr(char **ptr, char chr);
+extern char *str_cat_printf(char **ptr, char *fmt, ...);
+extern char *str_ins(char **str, int index, char *buf);
+extern void str_free(char *ptr);
+
+#endif
+
+
+#ifndef __MISC_H__
+#define __MISC_H__
+
+extern DO_COMMAND(do_all);
+extern DO_COMMAND(do_bell);
+extern DO_COMMAND(do_commands);
+extern DO_COMMAND(do_cr);
+extern DO_COMMAND(do_echo);
+extern DO_COMMAND(do_end);
+extern DO_COMMAND(do_forall);
+extern DO_COMMAND(do_info);
+extern DO_COMMAND(do_nop);
+extern DO_COMMAND(do_send);
+extern DO_COMMAND(do_snoop);
+extern DO_COMMAND(do_suspend);
+extern DO_COMMAND(do_test);
+extern DO_COMMAND(do_zap);
+
+#endif
+
+#ifndef __MSDP_H__
+#define __MSDP_H__
+
+extern void init_msdp_table(void);
+extern  int msdp_find(char *var);
+extern void arachnos_devel(struct session *ses, char *fmt, ...);
+extern void arachnos_mudlist(struct session *ses, char *fmt, ...);
+extern void msdp_update_all(char *var, char *fmt, ...);
+extern void msdp_update_var(struct session *ses, struct port_data *buddy, char *var, char *str);
+extern void msdp_update_varf(struct session *ses, struct port_data *buddy, char *var, char *fmt, ...);
+extern void msdp_update_var_instant(struct session *ses, struct port_data *buddy, char *var, char *fmt, ...);
+extern void msdp_send_update(struct session *ses, struct port_data *buddy);
+extern char *msdp_get_var(struct session *ses, struct port_data *buddy, char *var);
+extern void process_msdp_varval(struct session *ses, struct port_data *buddy, char *var, char *val );
+extern void msdp_command_list(struct session *ses, struct port_data *buddy, int index);
+extern void msdp_command_report(struct session *ses, struct port_data *buddy, int index);
+extern void msdp_command_reset(struct session *ses, struct port_data *buddy, int index);
+extern void msdp_command_send(struct session *ses, struct port_data *buddy, int index);
+extern void msdp_command_unreport(struct session *ses, struct port_data *buddy, int index);
+extern void msdp_configure_arachnos(struct session *ses, struct port_data *buddy, int index);
+extern void write_msdp_to_descriptor(struct session *ses, struct port_data *buddy, char *src, int length);
+extern  int msdp2json(unsigned char *src, int srclen, char *out);
+extern  int json2msdp(unsigned char *src, int srclen, char *out);
+extern void arachnos_devel(struct session *ses, char *fmt, ...);
+extern void arachnos_mudlist(struct session *ses, char *fmt, ...);
+extern struct msdp_type msdp_table[];
+
+#endif
+
+#ifndef __NEST_H__
+#define __NEST_H__
+
+extern struct listroot *search_nest_base_ses(struct session *ses, char *arg);
+extern struct listroot *search_nest_root(struct listroot *root, char *arg);
+extern struct listnode *search_base_node(struct listroot *root, char *variable);
+extern struct listnode *search_nest_node(struct listroot *root, char *variable);
+extern struct listnode *search_nest_node_ses(struct session *ses, char *variable);
+extern int search_nest_index(struct listroot *root, char *variable);
+extern struct listroot *update_nest_root(struct listroot *root, char *arg);
+extern void update_nest_node(struct listroot *root, char *arg);
+extern int delete_nest_node(struct listroot *root, char *variable);
+extern int get_nest_size_key(struct listroot *root, char *variable, char **result);
+extern int get_nest_size_val(struct listroot *root, char *variable, char **result);
+extern struct listnode *get_nest_node_key(struct listroot *root, char *variable, char **result, int def);
+extern struct listnode *get_nest_node_val(struct listroot *root, char *variable, char **result, int def);
+extern int get_nest_index(struct listroot *root, char *variable, char **result, int def);
+extern void show_nest_node(struct listnode *node, char **result, int initialize);
+extern void view_nest_node(struct listnode *node, char **str_result, int nest, int initialize);
+extern struct listnode *set_nest_node_ses(struct session *ses, char *arg1, char *format, ...);
+extern struct listnode *add_nest_node_ses(struct session *ses, char *arg1, char *format, ...);
+extern struct listnode *set_nest_node(struct listroot *root, char *arg1, char *format, ...);
+extern struct listnode *add_nest_node(struct listroot *root, char *arg1, char *format, ...);
+extern void copy_nest_node(struct listroot *dst_root, struct listnode *dst, struct listnode *src);
+
+#endif
+
+
+#ifndef __NET_H__
+#define __NET_H__
+
+extern int connect_mud(struct session *ses, char *host, char *port);
+extern void write_line_mud(struct session *ses, char *line, int size);
+extern int read_buffer_mud(struct session *ses);
+extern void readmud(struct session *ses);
+extern void process_mud_output(struct session *ses, char *linebuf, int prompt);
+
+#endif
+
+#ifndef __PARSE_H__
+#define __PARSE_H__
+
+extern  int is_abbrev(char *s1, char *s2);
+extern void filename_string(char *input, char *output);
+extern struct session *parse_input(struct session *ses, char *input);
+extern struct session *parse_command(struct session *ses, char *input);
+extern  int is_speedwalk(struct session *ses, char *input);
+extern char *substitute_speedwalk(struct session *ses, char *input, char *output);
+extern void process_speedwalk(struct session *ses, char *input);
+extern struct session *parse_tintin_command(struct session *ses, char *input);
+extern int cnt_arg_all(struct session *ses, char *string, int flag);
+extern char *get_arg_all(struct session *ses, char *string, char *result, int verbatim);
+extern char *get_arg_in_braces(struct session *ses, char *string, char *result, int flag);
+extern char *sub_arg_in_braces(struct session *ses, char *string, char *result, int flag, int sub);
+extern char *get_arg_with_spaces(struct session *ses, char *string, char *result, int flag);
+extern char *get_arg_stop_spaces(struct session *ses, char *string, char *result, int flag);
+extern char *get_arg_stop_digits(struct session *ses, char *string, char *result, int flag);
+extern char *space_out(char *string);
+extern char *get_arg_to_brackets(struct session *ses, char *string, char *result);
+extern char *get_arg_at_brackets(struct session *ses, char *string, char *result);
+extern char *get_arg_in_brackets(struct session *ses, char *string, char *result);
+extern char *get_char(struct session *ses, char *string, char *result);
+extern void write_mud(struct session *ses, char *command, int flags);
+extern void do_one_line(char *line, struct session *ses);
+
+#endif
+
+
+#ifndef __PATH_H__
+#define __PATH_H__
+
+extern DO_COMMAND(do_path);
+extern DO_COMMAND(do_pathdir);
+extern DO_COMMAND(do_unpathdir);
+
+extern void check_append_path(struct session *ses, char *forward, char *backward, int follow);
+
+extern DO_PATH(path_create);
+extern DO_PATH(path_describe);
+extern DO_PATH(path_delete);
+extern DO_PATH(path_destroy);
+extern DO_PATH(path_goto);
+extern DO_PATH(path_insert);
+extern DO_PATH(path_load);
+extern DO_PATH(path_map);
+extern DO_PATH(path_move);
+extern DO_PATH(path_run);
+extern DO_PATH(path_save);
+extern DO_PATH(path_start);
+extern DO_PATH(path_stop);
+extern DO_PATH(path_swap);
+extern DO_PATH(path_undo);
+extern DO_PATH(path_unzip);
+extern DO_PATH(path_walk);
+extern DO_PATH(path_zip);
+
+// old
+
+extern DO_PATH(path_new);
+extern DO_PATH(path_end);
+
+#endif
+
+
+#ifndef __PORT_H__
+#define __PORT_H__
+
+
+extern DO_COMMAND(do_port);
+extern DO_PORT(port_call);
+extern DO_PORT(port_color);
+extern DO_PORT(port_flag);
+extern DO_PORT(port_group);
+extern DO_PORT(port_ignore);
+extern DO_PORT(port_initialize);
+extern DO_PORT(port_info);
+extern DO_PORT(port_message);
+extern DO_PORT(port_name);
+extern DO_PORT(port_prefix);
+extern DO_PORT(port_rank);
+extern DO_PORT(port_send);
+extern DO_PORT(port_uninitialize);
+extern DO_PORT(port_who);
+extern DO_PORT(port_zap);
+
+extern  int port_new(struct session *ses, int s);
+extern void close_port(struct session *ses, struct port_data *buddy, int unlink);
+extern void process_port_connections(struct session *ses, fd_set *read_set, fd_set *write_set, fd_set *exc_set);
+extern void port_forward_session(struct session *ses, char *linelog);
+extern void port_socket_printf(struct session *ses, struct port_data *buddy, char *format, ...);
+extern void port_telnet_printf(struct session *ses, struct port_data *buddy, size_t length, char *format, ...);
+extern void port_log_printf(struct session *ses, struct port_data *buddy, char *format, ...);
+extern void port_printf(struct session *ses, char *format, ...);
+extern  int process_port_input(struct session *ses, struct port_data *buddy);
+extern void get_port_commands(struct session *ses, struct port_data *buddy, char *buf, int len);
+extern void port_name_change(struct session *ses, struct port_data *buddy, char *txt);
+extern void port_receive_message(struct session *ses, struct port_data *buddy, char *txt);
+
+extern void port_puts(struct session *ses, char *arg);
+extern struct port_data *find_port_buddy(struct session *ses, char *arg);
+extern struct port_data *find_port_group(struct session *ses, char *arg);
+
+#endif
+
+
+#ifndef __SCREEN_H__
+#define __SCREEN_H__
+
+extern DO_COMMAND(do_screen);
+
+extern DO_SCREEN(screen_blur);
+extern DO_SCREEN(screen_clear);
+extern DO_SCREEN(screen_cursor);
+extern DO_SCREEN(screen_fill);
+extern DO_SCREEN(screen_focus);
+extern DO_SCREEN(screen_fullscreen);
+extern DO_SCREEN(screen_get);
+extern DO_SCREEN(screen_info);
+extern DO_SCREEN(screen_load);
+extern DO_SCREEN(screen_maximize);
+extern DO_SCREEN(screen_minimize);
+extern DO_SCREEN(screen_move);
+extern DO_SCREEN(screen_raise);
+extern DO_SCREEN(screen_refresh);
+extern DO_SCREEN(screen_resize);
+extern DO_SCREEN(screen_rescale);
+extern DO_SCREEN(screen_save);
+extern DO_SCREEN(screen_scrollbar);
+extern DO_SCREEN(screen_scrollregion);
+extern DO_SCREEN(screen_set);
+
+extern  int get_row_index(struct session *ses, char *arg);
+extern  int get_col_index(struct session *ses, char *arg);
+extern void csip_handler(int var1, int var2, int var3);
+extern void csit_handler(int var1, int var2, int var3);
+extern void rqlp_handler(int event, int button, int row, int col);
+extern void osc_handler(char ind, char *arg);
+extern void print_screen();
+extern void init_screen(int rows, int cols, int pix_rows, int pix_cols);
+extern void destroy_screen();
+extern void add_line_screen(char *str);
+extern void set_line_screen(char *str, int row, int col);
+extern void erase_scroll_region(struct session *ses);
+extern void erase_split_region(struct session *ses);
+extern void erase_bot_region(struct session *ses);
+extern void erase_top_region(struct session *ses);
+extern void erase_left_region(struct session *ses);
+extern void erase_right_region(struct session *ses);
+extern void erase_square(struct session *ses, int top_row, int top_col, int bot_row, int bot_col);
+extern void fill_top_region(struct session *ses, char *arg);
+extern void fill_bot_region(struct session *ses, char *arg);
+extern void fill_left_region(struct session *ses, char *arg);
+extern void fill_right_region(struct session *ses, char *arg);
+extern void fill_split_region(struct session *ses, char *arg);
+extern void get_line_screen(char *str, int row);
+extern void get_word_screen(char *str, int row, int col);
+
+#endif
+
+
+#ifndef __SESSION_H__
+#define __SESSION_H__
+
+extern DO_COMMAND(do_session);
+extern struct session *session_command(char *arg, struct session *ses);
+extern void show_session(struct session *ses, struct session *ptr);
+extern struct session *find_session(char *name);
+extern struct session *newactive_session(void);
+extern struct session *activate_session(struct session *ses);
+extern struct session *new_session(struct session *ses, char *name, char *address, int desc, int ssl);
+extern struct session *connect_session(struct session *ses);
+extern void cleanup_session(struct session *ses);
+extern void dispose_session(struct session *ses);
+
+#endif
+
+
+#ifndef __SHOW_H__
+#define __SHOW_H__
+
+extern DO_COMMAND(do_showme);
+
+extern void show_message(struct session *ses, int index, char *format, ...);
+extern void show_error(struct session *ses, int index, char *format, ...);
+extern void show_debug(struct session *ses, int index, char *format, ...);
+extern void show_info(struct session *ses, int index, char *format, ...);
+extern void print_lines(struct session *ses, int flags, char *format, ...);
+extern void show_lines(struct session *ses, char *str);
+extern void tintin_header(struct session *ses, char *format, ...);
+extern void socket_printf(struct session *ses, size_t length, char *format, ...);
+extern void telnet_printf(struct session *ses, int length, char *format, ...);
+
+extern void tintin_printf2(struct session *ses, char *format, ...);
+extern void tintin_printf(struct session *ses, char *format, ...);
+
+extern void tintin_puts3(struct session *ses, char *string);
+extern void tintin_puts2(struct session *ses, char *string);
+extern void tintin_puts(struct session *ses, char *string);
+
+#endif
+
+
+#ifndef __SPLIT_H__
+#define __SPLIT_H__
+
+extern DO_COMMAND(do_split);
+extern DO_COMMAND(do_unsplit);
+
+extern void init_split(struct session *ses, int top, int bot, int left, int right);
+extern void reset_screen(struct session *ses);
+extern void dirty_screen(struct session *ses);
+extern void split_show(struct session *ses, char *prompt, int row, int col);
+
+#endif
+
+
+#ifndef __SSL_H__
+#define __SSL_H__
+
+extern DO_COMMAND(do_ssl);
+
+extern gnutls_session_t ssl_negotiate(struct session *ses);
+
+#endif
+
+
+#ifndef __SYSTEM_H__
+#define __SYSTEM_H__
+
+
+extern DO_COMMAND(do_run);
+extern DO_COMMAND(do_scan);
+extern DO_COMMAND(do_script);
+extern DO_COMMAND(do_system);
+extern DO_COMMAND(do_textin);
+
+#endif
+
+
+#ifndef __TABLES_H__
+#define __TABLES_H__
+
+extern struct array_type array_table[];
+extern struct buffer_type buffer_table[];
+extern struct chat_type chat_table[];
+extern struct class_type class_table[];
+extern struct color_type color_table[];
+extern struct color_type map_color_table[];
+extern struct command_type command_table[];
+extern struct config_type config_table[];
+extern struct cursor_type cursor_table[];
+extern struct daemon_type daemon_table[];
+extern struct draw_type draw_table[];
+extern struct event_type event_table[];
+extern struct history_type history_table[];
+extern struct line_type line_table[];
+extern struct list_type list_table[LIST_MAX];
+extern struct map_type map_table[];
+extern struct path_type path_table[];
+extern struct port_type port_table[];
+extern struct rank_type rank_table[];
+extern struct stamp_type huge_stamp_table[];
+extern struct substitution_type substitution_table[];
+extern struct telopt_type telopt_table[];
+extern   char *telcmds[];
+extern struct timer_type timer_table[];
+extern struct screen_type screen_table[];
+extern struct map_legend_type map_legend_table[];
+extern struct map_group_type map_group_table[];
+
+#endif
+
+
+#ifndef __TELOPT_H__
+#define __TELOPT_H__
+
+extern  int client_translate_telopts(struct session *ses, unsigned char *src, int cplen);
+extern  int client_write_compressed(struct session *ses, char *txt, int length);
+extern  int client_send_sb_naws(struct session *ses, int cplen, unsigned char *cpsrc);
+extern void announce_support(struct session *ses, struct port_data *buddy);
+extern  int server_translate_telopts(struct session *ses, struct port_data *buddy, unsigned char *src, int srclen, unsigned char *out, int outlen);
+extern void write_mccp2(struct session *ses, struct port_data *buddy, char *txt, int length);
+extern void client_end_mccp2(struct session *ses);
+extern void end_mccp2(struct session *ses, struct port_data *buddy);
+extern void client_end_mccp3(struct session *ses);
+extern void end_mccp3(struct session *ses, struct port_data *buddy);
+extern void init_msdp_table(void);
+
+#endif
+
+
+#ifndef __TERMINAL_H__
+#define __TERMINAL_H__
+
+extern void  init_terminal(struct session *ses);
+extern void  reset_terminal(struct session *ses);
+extern void  save_session_terminal(struct session *ses);
+extern void  refresh_session_terminal(struct session *ses);
+extern void  echo_on(struct session *ses);
+extern void  echo_off(struct session *ses);
+extern void  init_terminal_size(struct session *ses);
+extern  int  get_scroll_rows(struct session *ses);
+extern  int  get_scroll_cols(struct session *ses);
+extern char *get_charset(struct session *ses);
+
+#endif
+
+
+#ifndef __TEXT_H__
+#define __TEXT_H__
+
+extern void print_line(struct session *ses, char **str, int isaprompt);
+extern void print_stdout(char *format, ...);
+extern  int word_wrap(struct session *ses, char *textin, char *textout, int display, int *height, int *width);
+extern  int word_wrap_split(struct session *ses, char *textin, char *textout, int wrap, int start, int end, int flags, int *height, int *width);
+
+#endif
+
+
+
+
+#ifndef __TINEXP_H__
+#define __TINEXP_H__
+
+DO_COMMAND(do_regexp);
+
+extern int substitute(struct session *ses, char *string, char *result, int flags);
+extern int match(struct session *ses, char *str, char *exp, int flags);
+extern int find(struct session *ses, char *str, char *exp, int sub, int flag);
+extern int regexp_compare(struct session *ses, pcre *regex, char *str, char *exp, int option, int flag);
+extern int check_one_regexp(struct session *ses, struct listnode *node, char *line, char *original, int option);
+extern int tintin_regexp_check(struct session *ses, char *exp);
+extern int tintin_regexp(struct session *ses, pcre *pcre, char *str, char *exp, int option, int flag);
+extern pcre *regexp_compile(struct session *ses, char *exp, int option);
+extern pcre *tintin_regexp_compile(struct session *ses, struct listnode *node, char *exp, int option);
+extern void  tintin_macro_compile(char *input, char *output);
+
+#endif
+
+
+#ifndef __TOKENIZE_H__
+#define __TOKENIZE_H__
+
+extern void init_local(struct session *ses);
+extern struct listroot *local_list(struct session *ses);
+extern struct session *script_driver(struct session *ses, int list, char *str);
+extern char *script_writer(struct session *ses, char *str);
+extern char *script_viewer(struct session *ses, char *str);
+#endif
+
+
+#ifndef __TRIGGER_H__
+#define __TRIGGER_H__
+
+
+DO_COMMAND(do_action);
+DO_COMMAND(do_unaction);
+extern void check_all_actions(struct session *ses, char *original, char *line);
+
+DO_COMMAND(do_alias);
+DO_COMMAND(do_unalias);
+extern int check_all_aliases(struct session *ses, char *input);
+
+DO_COMMAND(do_button);
+DO_COMMAND(do_unbutton);
+extern void check_all_buttons(struct session *ses, short row, short col, char *arg1, char *arg2, char *word, char *line);
+
+extern DO_COMMAND(do_delay);
+extern DO_COMMAND(do_undelay);
+
+extern DO_COMMAND(do_function);
+extern DO_COMMAND(do_unfunction);
+
+extern DO_COMMAND(do_gag);
+extern DO_COMMAND(do_ungag);
+extern void check_all_gags(struct session *ses, char *original, char *line);
+
+extern DO_COMMAND(do_highlight);
+extern DO_COMMAND(do_unhighlight);
+extern void check_all_highlights(struct session *ses, char *original, char *line);
+
+extern DO_COMMAND(do_macro);
+extern DO_COMMAND(do_unmacro);
+
+extern DO_COMMAND(do_prompt);
+extern DO_COMMAND(do_unprompt);
+extern void check_all_prompts(struct session *ses, char *original, char *line);
+
+extern DO_COMMAND(do_substitute);
+extern DO_COMMAND(do_unsubstitute);
+extern void check_all_substitutions(struct session *ses, char *original, char *line);
+
+extern DO_COMMAND(do_tab);
+extern DO_COMMAND(do_untab);
+
+extern DO_COMMAND(do_tick);
+extern DO_COMMAND(do_untick);
+
+#endif
+
+// update.c
+
+extern void mainloop(void);
+extern void show_cpu(struct session *ses);
+
+
+#ifndef __UTILS_H__
+#define __UTILS_H__
+
+
+
+extern int is_number(char *str);
+extern unsigned long long hex_number_64bit(char *str);
+extern int hex_number_8bit(char *str);
+extern int oct_number(char *str);
+extern int unicode_16_bit(char *str, char *out);
+extern int unicode_21_bit(char *str, char *out);
+extern unsigned long long utime(void);
+extern unsigned long long generate_rand(struct session *ses);
+extern void seed_rand(struct session *ses, unsigned long long seed);
+extern char *capitalize(char *str);
+extern char *ntos(long long number);
+extern char *indent_one(int len);
+extern char *indent(int len);
+extern int cat_sprintf(char *dest, char *fmt, ...);
+extern void ins_sprintf(char *dest, char *fmt, ...);
+extern int str_suffix(char *str1, char *str2);
+
+
+
+#endif
+
+
+#ifndef __UTF8_H__
+#define __UTF8_H__
+
+extern int is_utf8_head(char *str);
+extern int is_utf8_tail(char *str);
+extern int get_utf8_size(char *str);
+extern int get_utf8_width(char *str, int *width);
+extern int get_utf8_index(char *str, int *index);
+extern int unicode_to_utf8(int index, char *out);
+extern int utf8_strlen(char *str);
+
+extern int utf8_to_all(struct session *ses, char *in, char *out);
+extern int all_to_utf8(struct session *ses, char *in, char *out);
+extern int iso1_to_utf8(char *input, char *output);
+extern int utf8_to_iso1(char *input, char *output);
+extern int iso2_to_utf8(char *input, char *output);
+extern int utf8_to_iso2(char *input, char *output);
+extern int koi8_to_utf8(char *input, char *output);
+extern int utf8_to_koi8(char *input, char *output);
+extern int fansi_to_utf8(char *input, char *output);
+extern int is_euc_head(struct session *ses, char *str);
+extern int get_euc_size(struct session *ses, char *str);
+extern int get_euc_width(struct session *ses, char *str, int *width);
+extern int is_big5(char *str);
+extern int big5_to_utf8(char *input, char *output);
+extern int utf8_to_big5(char *input, char *output);
+extern int is_gbk1(char *str);
+extern int gbk1_to_utf8(char *input, char *output);
+extern int utf8_to_gbk1(char *input, char *output);
+
+#endif
+
+#ifndef __VARIABLE_H__
+#define __VARIABLE_H__
+
+extern DO_COMMAND(do_variable);
+extern DO_COMMAND(do_unvariable);
+extern DO_COMMAND(do_local);
+extern DO_COMMAND(do_cat);
+extern DO_COMMAND(do_format);
+extern DO_COMMAND(do_replace);
+
+extern  int valid_variable(struct session *ses, char *arg);
+extern  int string_raw_str_len(struct session *ses, char *str, int start, int end);
+extern  int string_str_raw_len(struct session *ses, char *str, int start, int end);
+extern  int translate_color_names(struct session *ses, char *string, char *result);
+extern  int get_color_names(struct session *ses, char *htype, char *result);
+extern void lowerstring(char *str);
+extern void upperstring(char *str);
+extern void numbertocharacter(struct session *ses, char *str);
+extern void charactertonumber(struct session *ses, char *str);
+extern  int delete_variable(struct session *ses, char *variable);
+extern void justify_string(struct session *ses, char *in, char *out, int align, int cut);
+extern void format_string(struct session *ses, char *format, char *arg, char *out);
+extern struct listnode *search_variable(struct session *ses, char *variable);
+extern struct listnode *get_variable(struct session *ses, char *variable, char *result);
+extern struct listnode *set_variable(struct session *ses, char *variable, char *format, ...);
+
+
+#endif
+
+
+#ifndef __VT102_H__
+#define __VT102_H__
+
+extern void init_pos(struct session *ses, int row, int col);
+extern void save_pos(struct session *ses);
+extern void goto_pos(struct session *ses, int row, int col);
+extern void restore_pos(struct session *ses);
+extern void erase_cols(int cnt);
+extern void erase_row(struct session *ses);
+extern void erase_lines(struct session *ses, int rows);
+extern void erase_toeol(void);
+extern void erase_scroll_region(struct session *ses);
+extern void reset(struct session *ses);
+extern void scroll_region(struct session *ses, int top, int bottom);
+extern void reset_scroll_region(struct session *ses);
+extern int find_color_code(char *str);
+extern int find_secure_color_code(char *str);
+extern int skip_vt102_codes(char *str);
+extern int skip_vt102_codes_non_graph(char *str);
+extern void strip_vt102_codes(char *str, char *buf);
+extern void strip_vt102_codes_non_graph(char *str, char *buf);
+extern void strip_non_vt102_codes(char *str, char *buf);
+extern void get_color_codes(char *old, char *str, char *buf, int flags);
+extern int strip_vt102_strlen(struct session *ses, char *str);
+extern int strip_vt102_width(struct session *ses, char *str, int *lines);
+extern int strip_color_strlen(struct session *ses, char *str);
+extern char *strip_vt102_strstr(char *str, char *buf, int *len);
+extern int interpret_vt102_codes(struct session *ses, char *str, int real);
+
+#endif

+ 1229 - 0
tokenize.c

@@ -0,0 +1,1229 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2008                       *
+******************************************************************************/
+
+#include "tintin.h"
+
+
+struct scriptdata
+{
+	long long              min;
+	long long              max;
+	long long              cnt;
+	int                    inc;
+	char                 * cpy;
+	char                 * hlt;
+	char                 * str;
+	char                 * arg;
+};
+
+struct scriptnode
+{
+	struct scriptnode    * next;
+	struct scriptnode    * prev;
+	union
+	{
+		struct scriptdata   * data;
+		struct script_regex * regex;
+	};
+	char                 * str;
+	short                  lvl;
+	short                  type;
+	short                  cmd;
+};
+
+struct script_regex
+{
+	char                 * str;
+	char                 * bod;
+	char                 * buf;
+	int                    val;
+};
+
+void debugtoken(struct session *ses, struct scriptroot *root, struct scriptnode *token)
+{
+	push_call("debugtoken(%p,%d,%p,%d)",ses,root->list,token,token->type);
+
+	if (gtd->level->debug || HAS_BIT(root->ses->list[root->list]->flags, LIST_FLAG_DEBUG) || HAS_BIT(root->ses->list[root->list]->flags, LIST_FLAG_LOG))
+	{
+		switch (token->type)
+		{
+			case TOKEN_TYPE_STRING:
+			case TOKEN_TYPE_SESSION:
+				show_debug(ses, root->list, "[%02d] %s%s", token->type, indent(token->lvl), token->str);
+				break;
+
+			case TOKEN_TYPE_ELSE:
+			case TOKEN_TYPE_END:
+				show_debug(ses, root->list, "[%02d] %s" COLOR_STATEMENT "%s\e[0m", token->type, indent(token->lvl), token->str);
+				break;
+
+			case TOKEN_TYPE_DEFAULT:
+				show_debug(ses, root->list, "[%02d] %s" COLOR_STATEMENT "%s\e[0m", token->type, indent(token->lvl), command_table[token->cmd].name);
+				break;
+
+			case TOKEN_TYPE_BREAK:
+			case TOKEN_TYPE_CONTINUE:
+				show_debug(ses, root->list, "[%02d] %s" COLOR_STATEMENT "%s\e[0m", token->type, indent(token->lvl), command_table[token->cmd].name);
+				break;
+
+			case TOKEN_TYPE_COMMAND:
+				show_debug(ses, root->list, "[%02d] %s" COLOR_COMMAND   "%s " COLOR_STRING "%s\e[0m", token->type, indent(token->lvl), command_table[token->cmd].name, token->str);
+				break;
+
+			case TOKEN_TYPE_RETURN:
+				show_debug(ses, root->list, "[%02d] %s" COLOR_STATEMENT "%s " COLOR_STRING "%s\e[0m", token->type, indent(token->lvl), command_table[token->cmd].name, token->str);
+				break;
+
+			case TOKEN_TYPE_CASE:
+			case TOKEN_TYPE_ELSEIF:
+			case TOKEN_TYPE_IF:
+			case TOKEN_TYPE_WHILE:
+				show_debug(ses, root->list, "[%02d] %s" COLOR_STATEMENT "%s " COLOR_BRACE "{" COLOR_STRING "%s" COLOR_BRACE "}\e[0m", token->type, indent(token->lvl), command_table[token->cmd].name, token->str);
+				break;
+
+			case TOKEN_TYPE_FOREACH:
+			case TOKEN_TYPE_LOOP:
+			case TOKEN_TYPE_PARSE:
+			case TOKEN_TYPE_SWITCH:
+				show_debug(ses, root->list, "[%02d] %s" COLOR_STATEMENT "%s " COLOR_STRING "%s\e[0m", token->type, indent(token->lvl), command_table[token->cmd].name, token->data->hlt);
+				break;
+
+			case TOKEN_TYPE_REGEX:
+				show_debug(ses, root->list, "[%02d] %s" COLOR_STATEMENT "%s " COLOR_BRACE "{" COLOR_STRING "%s" COLOR_BRACE "} {" COLOR_STRING "%s" COLOR_BRACE "}\e[0m", token->type, indent(token->lvl), command_table[token->cmd].name, token->str, token->regex->str);
+				break;
+
+			default:
+				if (token == (struct scriptnode *) ses)
+				{
+					show_debug(ses, root->list, "[--] (error) token == ses");
+				}
+				else
+				{
+					show_debug(ses, root->list, "[%02d] %s\e[1;33m%d {\e[0m%s\e[1;32m}\e[0m", token->type, indent(token->lvl), token->cmd, token->str);
+				}
+				break;
+		}
+	}
+	pop_call();
+	return;
+}
+
+
+void addtoken(struct scriptroot *root, int lvl, int opr, int cmd, char *str)
+{
+	struct scriptnode *token;
+
+	token = (struct scriptnode *) calloc(1, sizeof(struct scriptnode));
+
+	token->lvl = lvl;
+	token->type = opr;
+	token->cmd = cmd;
+	token->str = strdup(str);
+
+	LINK(token, root->next, root->prev);
+}
+
+
+char *addlooptoken(struct scriptroot *root, int lvl, int opr, int cmd, char *str)
+{
+	struct scriptdata *data;
+
+	char min[BUFFER_SIZE], max[BUFFER_SIZE], var[BUFFER_SIZE];
+
+	data = (struct scriptdata *) calloc(1, sizeof(struct scriptdata));
+
+	str = get_arg_in_braces(root->ses, str, min, GET_ONE);
+	str = get_arg_in_braces(root->ses, str, max, GET_ONE);
+	str = get_arg_in_braces(root->ses, str, var, GET_ONE);
+
+	data->cpy = restringf(NULL, "{%s} {%s} {%s}", min, max, var);
+	data->hlt = restringf(NULL, COLOR_BRACE "{" COLOR_STRING "%s" COLOR_BRACE "} {" COLOR_STRING "%s" COLOR_BRACE "} {" COLOR_STRING "%s" COLOR_BRACE "}", min, max, var);
+
+	addtoken(root, lvl, opr, cmd, var);
+
+	data->str = strdup("");
+
+	root->prev->data = data;
+
+	return str;
+}
+
+char *addswitchtoken(struct scriptroot *root, int lvl, int opr, int cmd, char *str)
+{
+	struct scriptdata *data;
+
+	char arg[BUFFER_SIZE];
+
+	data = (struct scriptdata *) calloc(1, sizeof(struct scriptdata));
+
+	str = get_arg_in_braces(root->ses, str, arg, GET_ONE);
+
+	data->cpy = restringf(NULL, "{%s}", arg);
+	data->hlt = restringf(NULL, COLOR_BRACE "{" COLOR_STRING "%s" COLOR_BRACE "}", arg);
+
+	addtoken(root, lvl, opr, cmd, arg);
+
+	data->str = strdup("");
+
+	root->prev->data = data;
+
+	return str;
+}
+
+void resetlooptoken(struct session *ses, struct scriptnode *token)
+{
+	char *str, min[BUFFER_SIZE], max[BUFFER_SIZE];
+
+	str = token->data->cpy;
+
+	str = get_arg_in_braces(ses, str, min, GET_ONE);
+	str = get_arg_in_braces(ses, str, max, GET_ONE);
+
+	token->data->min = (int) get_number(ses, min);
+	token->data->max = (int) get_number(ses, max);
+
+	token->data->inc = token->data->min <= token->data->max ? 1 : -1;
+	token->data->cnt = token->data->min;
+}
+
+void breaklooptoken(struct scriptnode *token)
+{
+	token->data->min = token->data->max = token->data->cnt = token->data->inc = 0;
+}
+
+char *addforeachtoken(struct scriptroot *root, int lvl, int opr, int cmd, char *str)
+{
+	struct scriptdata *data;
+
+	char arg[BUFFER_SIZE], var[BUFFER_SIZE];
+
+	str = get_arg_in_braces(root->ses, str, arg, GET_ONE);
+	str = get_arg_in_braces(root->ses, str, var, GET_ONE);
+
+	addtoken(root, lvl, opr, cmd, var);
+
+	data = (struct scriptdata *) calloc(1, sizeof(struct scriptdata));
+
+	data->cpy = restringf(NULL, "{%s} {%s}", arg, var);
+	data->hlt = restringf(NULL, COLOR_BRACE "{" COLOR_STRING "%s" COLOR_BRACE "} {" COLOR_STRING "%s" COLOR_BRACE "}", arg, var);
+	data->str = strdup("");
+	data->arg = data->str;
+
+	root->prev->data = data;
+
+	return str;
+}
+
+void resetforeachtoken(struct session *ses, struct scriptnode *token)
+{
+	char *str, arg[BUFFER_SIZE];
+
+	str = token->data->cpy;
+
+	str = sub_arg_in_braces(ses, str, arg, GET_ONE, SUB_VAR|SUB_FUN);
+
+	RESTRING(token->data->str, arg);
+
+	token->data->arg = token->data->str;
+}
+
+void breakforeachtoken(struct scriptnode *token)
+{
+	RESTRING(token->data->str, "");
+
+	token->data->arg = token->data->str;
+}
+
+void handlereturntoken(struct session *ses, struct scriptnode *token)
+{
+	char arg[BUFFER_SIZE];
+
+	substitute(ses, token->str, arg, SUB_VAR|SUB_FUN);
+
+	set_nest_node_ses(ses, "result", "%s", arg);
+}
+
+void handleswitchtoken(struct session *ses, struct scriptnode *token)
+{
+	char arg[BUFFER_SIZE];
+
+	mathexp(ses, token->str, arg, 0);
+
+	RESTRING(token->data->str, arg);
+}
+
+char *get_arg_foreach(struct scriptroot *root, struct scriptnode *token)
+{
+	static char buf[BUFFER_SIZE];
+
+	token->data->arg = get_arg_in_braces(root->ses, token->data->arg, buf, GET_ALL);
+
+	if (*token->data->arg == COMMAND_SEPARATOR)
+	{
+		token->data->arg++;
+	}
+	return buf;
+}
+
+char *addparsetoken(struct scriptroot *root, int lvl, int opr, int cmd, char *str)
+{
+	struct scriptdata *data;
+
+	char arg[BUFFER_SIZE], var[BUFFER_SIZE];
+
+	str = get_arg_in_braces(root->ses, str, arg, GET_ONE);
+	str = get_arg_in_braces(root->ses, str, var, GET_ONE);
+
+	addtoken(root, lvl, opr, cmd, var);
+
+	data = (struct scriptdata *) calloc(1, sizeof(struct scriptdata));
+
+	data->cpy = restringf(NULL, "{%s} {%s}", arg, var);
+	data->hlt = restringf(NULL, COLOR_BRACE "{" COLOR_STRING "%s" COLOR_BRACE "} {" COLOR_STRING "%s" COLOR_BRACE "}", arg, var);
+
+	data->str = strdup("");
+	data->arg = data->str;
+
+	root->prev->data = data;
+
+	return str;
+}
+
+void resetparsetoken(struct session *ses, struct scriptnode *token)
+{
+	char *str, arg[BUFFER_SIZE];
+
+	str = token->data->cpy;
+
+	str = sub_arg_in_braces(ses, str, arg, GET_ONE, SUB_VAR|SUB_FUN);
+
+	RESTRING(token->data->str, arg);
+
+	token->data->arg = token->data->str;
+}
+
+void breakparsetoken(struct scriptnode *token)
+{
+	RESTRING(token->data->str, "");
+
+	token->data->arg = token->data->str;
+}
+
+char *get_arg_parse(struct session *ses, struct scriptnode *token)
+{
+	static char buf[5];
+
+	if (HAS_BIT(ses->charset, CHARSET_FLAG_EUC) && is_euc_head(ses, token->data->arg))
+	{
+		token->data->arg += sprintf(buf, "%.*s", get_euc_size(ses, token->data->arg), token->data->arg);
+	}
+	else if (HAS_BIT(ses->charset, CHARSET_FLAG_UTF8) && is_utf8_head(token->data->arg))
+	{
+		token->data->arg += sprintf(buf, "%.*s", get_utf8_size(token->data->arg), token->data->arg);
+	}
+	else
+	{
+		token->data->arg += sprintf(buf, "%c", token->data->arg[0]);
+	}
+
+	return buf;
+}
+
+char *addregextoken(struct scriptroot *root, int lvl, int type, int cmd, char *str)
+{
+	struct script_regex *regex;
+
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], arg3[BUFFER_SIZE];
+
+	str = get_arg_in_braces(root->ses, str, arg1, GET_ONE);
+	str = get_arg_in_braces(root->ses, str, arg2, GET_ONE);
+	str = get_arg_in_braces(root->ses, str, arg3, GET_ALL);
+
+	addtoken(root, lvl, type, cmd, arg1);
+
+	regex = (struct script_regex *) calloc(1, sizeof(struct script_regex));
+
+	regex->str = strdup(arg2);
+	regex->bod = strdup(arg3);
+	regex->buf = calloc(1, BUFFER_SIZE);
+
+	root->prev->regex = regex;
+
+	return str;
+}
+
+void deltoken(struct scriptroot *root, struct scriptnode *token)
+{
+	push_call("deltoken(%p,%p)",root,token);
+
+	UNLINK(token, root->next, root->prev);
+
+	free(token->str);
+
+	switch (token->type)
+	{
+		case TOKEN_TYPE_REGEX:
+			free(token->regex->str);
+			free(token->regex->bod);
+			free(token->regex->buf);
+			free(token->regex);
+			break;
+
+		case TOKEN_TYPE_LOOP:
+		case TOKEN_TYPE_FOREACH:
+		case TOKEN_TYPE_PARSE:
+		case TOKEN_TYPE_SWITCH:
+			free(token->data->cpy);
+			free(token->data->hlt);
+			free(token->data->str);
+			free(token->data);
+			break;
+	}
+
+	free(token);
+
+	pop_call();
+	return;
+}
+
+
+int find_command(char *command)
+{
+	int cmd;
+
+	if (find_session(command))
+	{
+		return -1;
+	}
+
+	if (isalpha((int) *command))
+	{
+		for (cmd = gtd->command_ref[tolower((int) *command) - 'a'] ; *command_table[cmd].name ; cmd++)
+		{
+			if (is_abbrev(command, command_table[cmd].name))
+			{
+				return cmd;
+			}
+		}
+	}
+	return -1;
+}
+
+void init_local(struct session *ses)
+{
+	struct scriptroot *root;
+
+	root = (struct scriptroot *) calloc(1, sizeof(struct scriptroot));
+
+	root->ses = ses;
+	root->list = LIST_VARIABLE;
+	root->local = init_list(ses, LIST_VARIABLE, LIST_SIZE);
+
+	gtd->script_stack[0] = root;
+
+	return;
+}
+
+struct listroot *local_list(struct session *ses)
+{
+	struct listroot *root;
+
+	push_call("local_list(%p)",ses);
+
+	root = gtd->script_stack[gtd->script_index]->local;
+
+	pop_call();
+	return root;
+}
+
+void tokenize_script(struct scriptroot *root, int lvl, char *str)
+{
+	char *arg, *line;
+	int cmd;
+
+	if (*str == 0)
+	{
+		addtoken(root, lvl, TOKEN_TYPE_STRING, -1, "");
+
+		return;
+	}
+
+	line = (char *) calloc(1, UMAX(BUFFER_SIZE, strlen(str)));
+
+	while (*str)
+	{
+		if (!VERBATIM(root->ses))
+		{
+			str = space_out(str);
+		}
+
+		if (*str != gtd->tintin_char)
+		{
+			str = get_arg_all(root->ses, str, line, VERBATIM(root->ses));
+
+			addtoken(root, lvl, TOKEN_TYPE_STRING, -1, line);
+		}
+		else
+		{
+			arg = get_arg_stop_spaces(root->ses, str, line, 0);
+
+			cmd = find_command(line+1);
+
+			if (cmd == -1)
+			{
+				str = get_arg_all(root->ses, str, line, 0);
+				addtoken(root, lvl, TOKEN_TYPE_SESSION, -1, line+1);
+			}
+			else
+			{
+				switch (command_table[cmd].type)
+				{
+					case TOKEN_TYPE_BREAK:
+						str = get_arg_with_spaces(root->ses, arg, line, 1);
+						addtoken(root, lvl, TOKEN_TYPE_BREAK, cmd, line);
+						break;
+
+					case TOKEN_TYPE_CASE:
+						str = get_arg_in_braces(root->ses, arg, line, GET_ONE);
+						addtoken(root, lvl++, TOKEN_TYPE_CASE, cmd, line);
+
+						str = get_arg_in_braces(root->ses, str, line, GET_ALL);
+						tokenize_script(root, lvl--, line);
+
+						addtoken(root, lvl, TOKEN_TYPE_END, -1, "endcase");
+						break;
+
+					case TOKEN_TYPE_CONTINUE:
+						str = get_arg_with_spaces(root->ses, arg, line, 1);
+						addtoken(root, lvl, TOKEN_TYPE_CONTINUE, cmd, line);
+						break;
+
+					case TOKEN_TYPE_DEFAULT:
+						addtoken(root, lvl++, TOKEN_TYPE_DEFAULT, cmd, "");
+
+						str = get_arg_in_braces(root->ses, arg, line, GET_ALL);
+						tokenize_script(root, lvl--, line);
+
+						addtoken(root, lvl, TOKEN_TYPE_END, -1, "enddefault");
+						break;
+
+					case TOKEN_TYPE_ELSE:
+						addtoken(root, lvl++, TOKEN_TYPE_ELSE, cmd, "else");
+
+						str = get_arg_in_braces(root->ses, arg, line, GET_ALL);
+						tokenize_script(root, lvl--, line);
+
+						addtoken(root, lvl, TOKEN_TYPE_END, -1, "endelse");
+						break;
+
+					case TOKEN_TYPE_ELSEIF:
+						str = get_arg_in_braces(root->ses, arg, line, GET_ONE);
+						addtoken(root, lvl++, TOKEN_TYPE_ELSEIF, cmd, line);
+
+						str = get_arg_in_braces(root->ses, str, line, GET_ALL);
+						tokenize_script(root, lvl--, line);
+
+						addtoken(root, lvl, TOKEN_TYPE_END, -1, "endif");
+						break;
+
+					case TOKEN_TYPE_FOREACH:
+						str = addforeachtoken(root, lvl++, TOKEN_TYPE_FOREACH, cmd, arg);
+
+						str = get_arg_in_braces(root->ses, str, line, GET_ALL);
+						tokenize_script(root, lvl--, line);
+
+						addtoken(root, lvl, TOKEN_TYPE_END, -1, "endforeach");
+						break;
+
+					case TOKEN_TYPE_IF:
+						str = get_arg_in_braces(root->ses, arg, line, GET_ONE);
+						addtoken(root, lvl++, TOKEN_TYPE_IF, cmd, line);
+
+						str = get_arg_in_braces(root->ses, str, line, GET_ALL);
+						tokenize_script(root, lvl--, line);
+
+						addtoken(root, lvl, TOKEN_TYPE_END, -1, "endif");
+
+						if (*str && *str != COMMAND_SEPARATOR)
+						{
+							addtoken(root, lvl++, TOKEN_TYPE_ELSE, -1, "else");
+
+							str = get_arg_in_braces(root->ses, str, line, GET_ALL);
+							tokenize_script(root, lvl--, line);
+
+							addtoken(root, lvl, TOKEN_TYPE_END, -1, "endif");
+						}
+						break;
+
+					case TOKEN_TYPE_LOOP:
+						str = addlooptoken(root, lvl++, TOKEN_TYPE_LOOP, cmd, arg);
+
+						str = get_arg_in_braces(root->ses, str, line, GET_ALL);
+						tokenize_script(root, lvl--, line);
+
+						addtoken(root, lvl, TOKEN_TYPE_END, -1, "endloop");
+						break;
+
+					case TOKEN_TYPE_PARSE:
+						str = addparsetoken(root, lvl++, TOKEN_TYPE_PARSE, cmd, arg);
+
+						str = get_arg_in_braces(root->ses, str, line, GET_ALL);
+						tokenize_script(root, lvl--, line);
+
+						addtoken(root, lvl, TOKEN_TYPE_END, -1, "endparse");
+						break;
+
+					case TOKEN_TYPE_REGEX:
+						str = addregextoken(root, lvl, TOKEN_TYPE_REGEX, cmd, arg);
+
+//						addtoken(root, --lvl, TOKEN_TYPE_END, -1, "endregex");
+
+						if (*str && *str != COMMAND_SEPARATOR)
+						{
+							addtoken(root, lvl++, TOKEN_TYPE_ELSE, -1, "else");
+
+							str = get_arg_in_braces(root->ses, str, line, GET_ALL);
+							tokenize_script(root, lvl--, line);
+
+							addtoken(root, lvl, TOKEN_TYPE_END, -1, "endregex");
+						}
+						break;
+
+					case TOKEN_TYPE_RETURN:
+						str = get_arg_in_braces(root->ses, arg, line, GET_ALL);
+						addtoken(root, lvl, TOKEN_TYPE_RETURN, cmd, line);
+						break;
+
+					case TOKEN_TYPE_SWITCH:
+						str = addswitchtoken(root, lvl++, TOKEN_TYPE_SWITCH, cmd, arg);
+
+						str = get_arg_in_braces(root->ses, str, line, GET_ALL);
+						tokenize_script(root, lvl--, line);
+
+						addtoken(root, lvl, TOKEN_TYPE_END, -1, "end");
+						break;
+
+					case TOKEN_TYPE_WHILE:
+						str = get_arg_in_braces(root->ses, arg, line, GET_ONE);
+						addtoken(root, lvl++, TOKEN_TYPE_WHILE, cmd, line);
+
+						str = get_arg_in_braces(root->ses, str, line, GET_ALL);
+						tokenize_script(root, lvl--, line);
+
+						addtoken(root, lvl, TOKEN_TYPE_END, -1, "endwhile");
+						break;
+
+					default:
+						str = get_arg_with_spaces(root->ses, arg, line, GET_ALL);
+						addtoken(root, lvl, TOKEN_TYPE_COMMAND, cmd, line);
+						break;
+				}
+			}
+		}
+		str = space_out(str);
+
+		if (*str == COMMAND_SEPARATOR)
+		{
+			str++;
+		}
+
+	}
+
+	free(line);
+}
+
+
+struct scriptnode *parse_script(struct scriptroot *root, int lvl, struct scriptnode *token, struct scriptnode *shift)
+{
+	struct scriptnode *split = NULL;
+
+	while (token)
+	{
+		if (token->lvl < lvl)
+		{
+			if (shift->lvl + 1 == lvl)
+			{
+				switch (shift->type)
+				{
+					case TOKEN_TYPE_FOREACH:
+					case TOKEN_TYPE_LOOP:
+					case TOKEN_TYPE_PARSE:
+					case TOKEN_TYPE_WHILE:
+						debugtoken(root->ses, root, token);
+						return shift;
+
+					case TOKEN_TYPE_BROKEN_FOREACH:
+					case TOKEN_TYPE_BROKEN_LOOP:
+					case TOKEN_TYPE_BROKEN_PARSE:
+					case TOKEN_TYPE_BROKEN_WHILE:
+						shift->type--;
+						return token;
+				}
+			}
+			return token;
+		}
+
+		debugtoken(root->ses, root, token);
+
+		switch (token->type)
+		{
+			case TOKEN_TYPE_BREAK:
+				switch (shift->type)
+				{
+					case TOKEN_TYPE_FOREACH:
+						breakforeachtoken(shift);
+						shift->type++;
+						break;
+					case TOKEN_TYPE_LOOP:
+						breaklooptoken(shift);
+						shift->type++;
+						break;
+					case TOKEN_TYPE_PARSE:
+						breakparsetoken(shift);
+						shift->type++;
+						break;
+					case TOKEN_TYPE_WHILE:
+						shift->type++;
+						break;
+				}
+
+				do
+				{
+					token = token->next;
+				}
+				while (token && token->lvl > shift->lvl);
+
+				continue;
+
+			case TOKEN_TYPE_CASE:
+				if (shift->data && mathswitch(root->ses, shift->data->str, token->str))
+				{
+
+					token = token->next;
+
+					token = parse_script(root, lvl + 1, token, shift);
+
+					while (token && token->lvl >= lvl)
+					{
+						token = token->next;
+					}
+				}
+				else
+				{
+					do
+					{
+						token = token->next;
+					}
+					while (token && token->lvl > lvl);
+				}
+				continue;
+
+			case TOKEN_TYPE_COMMAND:
+				push_call("do_%s(%p,%p)", command_table[token->cmd].name, root->ses, token->str);
+
+				root->ses = (*command_table[token->cmd].command) (root->ses, token->str);
+
+				pop_call();
+/*
+	return;
+}
+*/
+
+				break;
+
+			case TOKEN_TYPE_CONTINUE:
+
+				do
+				{
+					token = token->next;
+				}
+				while (token && token->lvl > shift->lvl);
+
+				continue;
+
+			case TOKEN_TYPE_DEFAULT:
+				token = token->next;
+
+				token = parse_script(root, lvl + 1, token, shift);
+
+				while (token && token->lvl >= lvl)
+				{
+					token = token->next;
+				}
+				continue;
+
+			case TOKEN_TYPE_ELSE:
+				if (split)
+				{
+					token = parse_script(root, lvl + 1, token->next, shift);
+
+					split = NULL;
+				}
+				else
+				{
+					do
+					{
+						token = token->next;
+					}
+					while (token && token->lvl > lvl);
+				}
+				continue;
+
+			case TOKEN_TYPE_ELSEIF:
+				if (split && get_number(root->ses, token->str))
+				{
+					token = parse_script(root, lvl + 1, token->next, shift);
+
+					split = NULL;
+				}
+				else
+				{
+					do
+					{
+						token = token->next;
+					}
+					while (token && token->lvl > lvl);
+				}
+				continue;
+
+			case TOKEN_TYPE_END:
+				break;
+
+			case TOKEN_TYPE_FOREACH:
+				if (*token->data->arg == 0)
+				{
+					resetforeachtoken(root->ses, token);
+				}
+
+				if (*token->data->arg == 0)
+				{
+					token->type++;
+
+					do
+					{
+						token = token->next;
+					}
+					while (token && token->lvl > lvl);
+				}
+				else
+				{
+					set_nest_node_ses(root->ses, token->str, "%s", get_arg_foreach(root, token));
+
+					if (*token->data->arg == 0)
+					{
+						token->type++;
+					}
+					token = parse_script(root, lvl + 1, token->next, token);
+				}
+				continue;
+
+			case TOKEN_TYPE_IF:
+				split = NULL;
+
+				if (get_number(root->ses, token->str))
+				{
+					token = parse_script(root, lvl + 1, token->next, shift);
+				}
+				else
+				{
+					split = token;
+
+					do
+					{
+						token = token->next;
+					}
+					while (token && token->lvl > lvl);
+				}
+				continue;
+
+			case TOKEN_TYPE_LOOP:
+				if (token->data->cnt == token->data->max + token->data->inc)
+				{
+					resetlooptoken(root->ses, token);
+				}
+
+				set_nest_node_ses(root->ses, token->str, "%lld", token->data->cnt);
+
+				token->data->cnt += token->data->inc;
+
+				if (token->data->cnt == token->data->max + token->data->inc)
+				{
+					token->type++;
+				}
+
+				token = parse_script(root, lvl + 1, token->next, token);
+
+				continue;
+
+			case TOKEN_TYPE_PARSE:
+				if (*token->data->arg == 0)
+				{
+					resetparsetoken(root->ses, token);
+
+					if (*token->data->arg == 0)
+					{
+						token->type++;
+
+						do
+						{
+							token = token->next;
+						}
+						while (token && token->lvl > lvl);
+
+						continue;
+					}
+
+				}
+
+				set_nest_node_ses(root->ses, token->str, "%s", get_arg_parse(root->ses, token));
+
+				if (*token->data->arg == 0)
+				{
+					token->type++;
+				}
+				token = parse_script(root, lvl + 1, token->next, token);
+
+				continue;
+
+			case TOKEN_TYPE_REGEX:
+				split = NULL;
+
+				token->regex->val = find(root->ses, token->str, token->regex->str, SUB_VAR|SUB_FUN, REGEX_FLAG_CMD);
+
+				if (token->regex->val)
+				{
+					substitute(root->ses, token->regex->bod, token->regex->buf, SUB_CMD);
+
+					root->ses = script_driver(root->ses, LIST_COMMAND, token->regex->buf);
+				}
+				else
+				{
+					split = token;
+				}
+				break;
+
+			case TOKEN_TYPE_RETURN:
+				handlereturntoken(root->ses, token);
+
+				if (lvl)
+				{
+					return NULL;
+				}
+				else
+				{
+					return (struct scriptnode *) root->ses;
+				}
+				break;
+
+			case TOKEN_TYPE_SESSION:
+				root->ses = parse_tintin_command(root->ses, token->str);
+				break;
+
+			case TOKEN_TYPE_STRING:
+				root->ses = parse_input(root->ses, token->str);
+				break;
+
+			case TOKEN_TYPE_SWITCH:
+				handleswitchtoken(root->ses, token);
+
+				token = parse_script(root, lvl + 1, token->next, token);
+				continue;
+
+			case TOKEN_TYPE_WHILE:
+				if (get_number(root->ses, token->str))
+				{
+					token = parse_script(root, lvl + 1, token->next, token);
+				}
+				else
+				{
+//					token->type++;
+
+					do
+					{
+						token = token->next;
+					}
+					while (token && token->lvl > lvl);
+				}
+				continue;
+		}
+
+		if (token)
+		{
+			token = token->next;
+		}
+	}
+
+	if (lvl)
+	{
+		return NULL;
+	}
+
+	return (struct scriptnode *) root->ses;
+}
+
+
+char *write_script(struct session *ses, struct scriptroot *root)
+{
+	struct scriptnode *token;
+	static char buf[STRING_SIZE];
+
+	token = root->next;
+
+	buf[0] = 0;
+
+	while (token)
+	{
+		switch (token->type)
+		{
+			case TOKEN_TYPE_STRING:
+				cat_sprintf(buf, "%s%s", indent(token->lvl), token->str);
+				break;
+
+			case TOKEN_TYPE_BREAK:
+			case TOKEN_TYPE_CONTINUE:
+				cat_sprintf(buf, "%s%c%s", indent(token->lvl), gtd->tintin_char, command_table[token->cmd].name);
+				break;
+
+			case TOKEN_TYPE_COMMAND:
+			case TOKEN_TYPE_RETURN:
+				cat_sprintf(buf, "%s%c%s%s%s", indent(token->lvl), gtd->tintin_char, command_table[token->cmd].name, *token->str ? " " : "", token->str);
+				break;
+
+			case TOKEN_TYPE_ELSE:
+				cat_sprintf(buf, "%s%c%s\n%s{\n", indent(token->lvl), gtd->tintin_char, token->str, indent(token->lvl));
+				break;
+
+			case TOKEN_TYPE_DEFAULT:
+				cat_sprintf(buf, "%s%c%s\n%s{\n", indent(token->lvl), gtd->tintin_char, command_table[token->cmd].name, indent(token->lvl));
+				break;
+
+			case TOKEN_TYPE_FOREACH:
+			case TOKEN_TYPE_LOOP:
+			case TOKEN_TYPE_PARSE:
+			case TOKEN_TYPE_SWITCH:
+				cat_sprintf(buf, "%s%c%s %s\n%s{\n", indent(token->lvl), gtd->tintin_char, command_table[token->cmd].name, token->data->cpy, indent(token->lvl));
+				break;
+
+			case TOKEN_TYPE_CASE:
+			case TOKEN_TYPE_ELSEIF:
+			case TOKEN_TYPE_IF:
+			case TOKEN_TYPE_WHILE:
+				cat_sprintf(buf, "%s%c%s {%s}\n%s{\n", indent(token->lvl), gtd->tintin_char, command_table[token->cmd].name, token->str, indent(token->lvl));
+				break;
+
+			case TOKEN_TYPE_REGEX:
+				cat_sprintf(buf, "%s%c%s {%s} {%s}\n%s{\n%s%s\n%s}", indent(token->lvl), gtd->tintin_char, command_table[token->cmd].name, token->str, token->regex->str, indent(token->lvl), indent(token->lvl + 1), token->regex->bod, indent(token->lvl));
+				break;
+
+			case TOKEN_TYPE_END:
+				cat_sprintf(buf, "\n%s}", indent(token->lvl));
+				break;
+
+			case TOKEN_TYPE_SESSION:
+				cat_sprintf(buf, "%s%c%s", indent(token->lvl), gtd->tintin_char, token->str);
+				break;
+
+			default:
+				tintin_printf2(ses, "#WRITE: UNKNOWN TOKEN TYPE: %d", token->type);
+				break;
+		}
+
+		if (token->next && token->lvl == token->next->lvl)
+		{
+			strcat(buf, ";\n");
+		}
+
+		token = token->next;
+	}
+
+	while (root->next)
+	{
+		deltoken(root, root->next);
+	}
+
+	free(root);
+
+	return buf;
+}
+
+char *view_script(struct session *ses, struct scriptroot *root)
+{
+	struct scriptnode *token;
+	static char buf[STRING_SIZE];
+
+	token = root->next;
+
+	buf[0] = 0;
+
+	while (token)
+	{
+		switch (token->type)
+		{
+			case TOKEN_TYPE_STRING:
+				cat_sprintf(buf, "%s" COLOR_STRING "%s", indent(token->lvl), token->str);
+				break;
+
+			case TOKEN_TYPE_BREAK:
+			case TOKEN_TYPE_CONTINUE:
+				cat_sprintf(buf, "%s" COLOR_TINTIN "%c" COLOR_STATEMENT "%s", indent(token->lvl), gtd->tintin_char, command_table[token->cmd].name);
+				break;
+
+			case TOKEN_TYPE_RETURN:
+				cat_sprintf(buf, "%s" COLOR_TINTIN "%c" COLOR_STATEMENT "%s" COLOR_STRING "%s%s", indent(token->lvl), gtd->tintin_char, command_table[token->cmd].name, *token->str ? " " : "", token->str);
+				break;
+
+			case TOKEN_TYPE_COMMAND:
+				cat_sprintf(buf, "%s" COLOR_TINTIN "%c" COLOR_COMMAND "%s" COLOR_STRING "%s%s", indent(token->lvl), gtd->tintin_char, command_table[token->cmd].name, *token->str ? " " : "", token->str);
+				break;
+
+			case TOKEN_TYPE_ELSE:
+				cat_sprintf(buf, "%s" COLOR_TINTIN "%c" COLOR_STATEMENT "%s\n%s" COLOR_BRACE "{\n", indent(token->lvl), gtd->tintin_char, token->str, indent(token->lvl));
+				break;
+
+			case TOKEN_TYPE_DEFAULT:
+				cat_sprintf(buf, "%s" COLOR_TINTIN "%c" COLOR_STATEMENT "%s\n%s" COLOR_BRACE "{\n", indent(token->lvl), gtd->tintin_char, command_table[token->cmd].name, indent(token->lvl));
+				break;
+
+			case TOKEN_TYPE_FOREACH:
+			case TOKEN_TYPE_LOOP:
+			case TOKEN_TYPE_PARSE:
+			case TOKEN_TYPE_SWITCH:
+				cat_sprintf(buf, "%s" COLOR_TINTIN "%c" COLOR_STATEMENT "%s " COLOR_STRING "%s\n%s" COLOR_BRACE "{\n", indent(token->lvl), gtd->tintin_char, command_table[token->cmd].name, token->data->hlt, indent(token->lvl));
+				break;
+
+			case TOKEN_TYPE_CASE:
+			case TOKEN_TYPE_ELSEIF:
+			case TOKEN_TYPE_IF:
+			case TOKEN_TYPE_WHILE:
+				cat_sprintf(buf, "%s" COLOR_TINTIN "%c" COLOR_STATEMENT "%s " COLOR_BRACE "{" COLOR_STRING "%s" COLOR_BRACE "}" COLOR_STRING "\n%s" COLOR_BRACE "{\n", indent(token->lvl), gtd->tintin_char, command_table[token->cmd].name, token->str, indent(token->lvl));
+				break;
+
+			case TOKEN_TYPE_REGEX:
+				cat_sprintf(buf, "%s" COLOR_TINTIN "%c" COLOR_STATEMENT "%s " COLOR_BRACE "{" COLOR_STRING "%s" COLOR_BRACE "} {" COLOR_STRING "%s" COLOR_BRACE "}\n%s{\n%s" COLOR_STRING "%s\n" COLOR_BRACE "%s}", indent(token->lvl), gtd->tintin_char, command_table[token->cmd].name, token->str, token->regex->str, indent(token->lvl), indent(token->lvl + 1), token->regex->bod, indent(token->lvl));
+				break;
+
+			case TOKEN_TYPE_END:
+				cat_sprintf(buf, "\n%s" COLOR_BRACE "}" COLOR_STRING, indent(token->lvl));
+				break;
+
+			case TOKEN_TYPE_SESSION:
+				cat_sprintf(buf, "%s" COLOR_TINTIN "%c" COLOR_STRING "%s", indent(token->lvl), gtd->tintin_char, token->str);
+				break;
+
+			default:
+				tintin_printf2(ses, "#ERROR: UNKNOWN TOKEN TYPE: %d", token->type);
+				break;
+		}
+
+		if (token->next && token->lvl == token->next->lvl)
+		{
+			strcat(buf, COLOR_SEPARATOR ";\n");
+		}
+
+		token = token->next;
+	}
+
+	while (root->next)
+	{
+		deltoken(root, root->next);
+	}
+
+	free(root);
+
+	return buf;
+}
+
+struct session *script_driver(struct session *ses, int list, char *str)
+{
+	struct scriptroot *root;
+
+	push_call("script_driver(%p,%d,%p)",ses,list,str);
+
+	root = (struct scriptroot *) calloc(1, sizeof(struct scriptroot));
+
+	root->ses = ses;
+	root->list = list;
+	root->local = init_list(ses, LIST_VARIABLE, LIST_SIZE);
+
+	gtd->level->input += list != LIST_COMMAND;
+
+	gtd->script_stack[++gtd->script_index] = root;
+
+	tokenize_script(root, 0, str);
+
+	ses = (struct session *) parse_script(root, 0, root->next, root->prev);
+
+	if (--gtd->script_index == 0)
+	{
+		DEL_BIT(gtd->flags, TINTIN_FLAG_LOCAL);
+	}
+
+	gtd->level->input -= list != LIST_COMMAND;
+
+	while (root->prev)
+	{
+		deltoken(root, root->prev);
+	}
+
+	free_list(root->local);
+	free(root);
+
+
+	
+	if (HAS_BIT(ses->flags, SES_FLAG_CLOSED))
+	{
+		pop_call();
+		return gtd->ses;
+	}
+	pop_call();
+	return ses;
+}
+
+
+char *script_writer(struct session *ses, char *str)
+{
+	struct scriptroot *root;
+
+	root = (struct scriptroot *) calloc(1, sizeof(struct scriptroot));
+
+	root->ses = ses;
+
+	tokenize_script(root, 1, str);
+
+	return write_script(ses, root);
+}
+
+char *script_viewer(struct session *ses, char *str)
+{
+	struct scriptroot *root;
+
+	root = (struct scriptroot *) calloc(1, sizeof(struct scriptroot));
+
+	root->ses = ses;
+
+	tokenize_script(root, 1, str);
+
+	return view_script(ses, root);
+}

+ 989 - 0
trigger.c

@@ -0,0 +1,989 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2019 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+#include "tintin.h"
+
+/******************************************************************************
+*               (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                  *
+*                                                                             *
+*                        coded by Peter Unold 1992                            *
+*                   recoded by Igor van den Hoven 2009                        *
+******************************************************************************/
+
+DO_COMMAND(do_action)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], arg3[BUFFER_SIZE];
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ONE);
+	arg = get_arg_in_braces(ses, arg, arg2, GET_ALL);
+	arg = get_arg_in_braces(ses, arg, arg3, GET_ALL);
+
+	if (*arg1 == 0)
+	{
+		show_list(ses->list[LIST_ACTION], 0);
+	}
+	else if (*arg1 && *arg2 == 0)
+	{
+		if (show_node_with_wild(ses, arg1, ses->list[LIST_ACTION]) == FALSE)
+		{
+			show_message(ses, LIST_ACTION, "#ACTION: NO MATCH(ES) FOUND FOR {%s}.", arg1);
+		}
+	}
+	else
+	{
+		update_node_list(ses->list[LIST_ACTION], arg1, arg2, arg3, "");
+
+		show_message(ses, LIST_ACTION, "#OK. #ACTION {%s} NOW TRIGGERS {%s} @ {%s}.", arg1, arg2, arg3);
+	}
+	return ses;
+}
+
+
+DO_COMMAND(do_unaction)
+{
+	delete_node_with_wild(ses, LIST_ACTION, arg);
+
+	return ses;
+}
+
+
+void check_all_actions(struct session *ses, char *original, char *line)
+{
+	struct listroot *root = ses->list[LIST_ACTION];
+	struct listnode *node;
+	char buf[BUFFER_SIZE];
+
+	for (root->update = 0 ; root->update < root->used ; root->update++)
+	{
+		node = root->list[root->update];
+
+		if (check_one_regexp(ses, node, line, original, 0))
+		{
+			show_debug(ses, LIST_ACTION, "#DEBUG ACTION {%s}", node->arg1);
+
+			substitute(ses, node->arg2, buf, SUB_ARG|SUB_SEC);
+
+			if (HAS_BIT(node->flags, NODE_FLAG_ONESHOT))
+			{
+				delete_node_list(ses, LIST_ACTION, node);
+			}
+
+			script_driver(ses, LIST_ACTION, buf);
+
+			return;
+		}
+	}
+}
+
+/******************************************************************************
+*               (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                  *
+*                                                                             *
+*                        coded by Peter Unold 1992                            *
+*                   recoded by Igor van den Hoven 2009                        *
+******************************************************************************/
+
+DO_COMMAND(do_alias)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], arg3[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = get_arg_in_braces(ses, arg, arg2, GET_ALL);
+	arg = get_arg_in_braces(ses, arg, arg3, GET_ALL);
+
+	if (*arg1 == 0)
+	{
+		show_list(ses->list[LIST_ALIAS], 0);
+	}
+	else if (*arg2 == 0)
+	{
+		if (show_node_with_wild(ses, arg1, ses->list[LIST_ALIAS]) == FALSE)
+		{
+			show_message(ses, LIST_ALIAS, "#ALIAS: NO MATCH(ES) FOUND FOR {%s}.", arg1);
+		}
+	}
+	else
+	{
+		update_node_list(ses->list[LIST_ALIAS], arg1, arg2, arg3, "");
+
+		show_message(ses, LIST_ALIAS, "#ALIAS {%s} NOW TRIGGERS {%s} @ {%s}.", arg1, arg2, arg3);
+	}
+	return ses;
+}
+
+
+DO_COMMAND(do_unalias)
+{
+	delete_node_with_wild(ses, LIST_ALIAS, arg);
+
+	return ses;
+}
+
+int check_all_aliases(struct session *ses, char *input)
+{
+	struct listnode *node;
+	struct listroot *root;
+	char tmp[BUFFER_SIZE], line[BUFFER_SIZE], *arg;
+	int i;
+
+	root = ses->list[LIST_ALIAS];
+
+	if (HAS_BIT(root->flags, LIST_FLAG_IGNORE))
+	{
+		return FALSE;
+	}
+
+	substitute(ses, input, line, SUB_VAR|SUB_FUN);
+
+	for (root->update = 0 ; root->update < root->used ; root->update++)
+	{
+		node = root->list[root->update];
+
+		if (check_one_regexp(ses, node, line, line, PCRE_ANCHORED))
+		{
+			i = strlen(node->arg1);
+
+			if (!strncmp(node->arg1, line, i))
+			{
+				if (line[i])
+				{
+					if (line[i] != ' ')
+					{
+						continue;
+					}
+					arg = &line[i + 1];
+				}
+				else
+				{
+					arg = &line[i];
+				}
+
+				RESTRING(gtd->vars[0], arg)
+
+				for (i = 1 ; i < 100 ; i++)
+				{
+					arg = get_arg_in_braces(ses, arg, tmp, GET_ONE);
+
+					RESTRING(gtd->vars[i], tmp);
+
+					if (*arg == 0)
+					{
+						while (++i < 100)
+						{
+							if (*gtd->vars[i])
+							{
+								RESTRING(gtd->vars[i], "");
+							}
+						}
+						break;
+					}
+
+				}
+			}
+
+			substitute(ses, node->arg2, tmp, SUB_ARG);
+
+			if (!strncmp(node->arg1, line, strlen(node->arg1)) && !strcmp(node->arg2, tmp) && *gtd->vars[0])
+			{
+				sprintf(input, "%s %s", tmp, gtd->vars[0]);
+			}
+			else
+			{
+				sprintf(input, "%s", tmp);
+			}
+
+			show_debug(ses, LIST_ALIAS, "#DEBUG ALIAS {%s} {%s}", node->arg1, gtd->vars[0]);
+
+			if (HAS_BIT(node->flags, NODE_FLAG_ONESHOT))
+			{
+				delete_node_list(ses, LIST_ALIAS, node);
+			}
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+
+
+/******************************************************************************
+*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
+*                                                                             *
+*                      coded by Igor van den Hoven 2019                       *
+******************************************************************************/
+
+
+DO_COMMAND(do_button)
+{
+	struct listnode *node;
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], arg3[BUFFER_SIZE];
+	int index;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+	arg = get_arg_in_braces(ses, arg, arg2, GET_ALL);
+	arg = get_arg_in_braces(ses, arg, arg3, GET_ALL);
+
+	if (*arg1 == 0)
+	{
+		show_list(ses->list[LIST_BUTTON], 0);
+	}
+	else if (*arg1 && *arg2 == 0)
+	{
+		if (show_node_with_wild(ses, arg1, ses->list[LIST_BUTTON]) == FALSE)
+		{
+			show_message(ses, LIST_BUTTON, "#BUTTON: NO MATCH(ES) FOUND FOR {%s}.", arg1);
+		}
+	}
+	else
+	{
+		node = update_node_list(ses->list[LIST_BUTTON], arg1, arg2, arg3, "");
+
+		show_message(ses, LIST_BUTTON, "#OK. BUTTON {%s} NOW TRIGGERS {%s} @ {%s}.", arg1, arg2, arg3);
+
+		arg = arg1;
+
+		for (index = 0 ; index < 4 ; index++)
+		{
+			arg = get_arg_in_braces(ses, arg, arg2, GET_ONE);
+
+			node->val16[index] = (short) get_number(ses, arg2);
+
+			if (*arg == COMMAND_SEPARATOR)
+			{
+				arg++;
+			}
+		}
+
+		if (*arg)
+		{
+			arg = get_arg_in_braces(ses, arg, arg2, GET_ALL);
+
+			RESTRING(node->arg4, arg2);
+		}
+		else
+		{
+			RESTRING(node->arg4, "PRESSED MOUSE BUTTON ONE");
+		}
+	}
+	return ses;
+}
+
+
+DO_COMMAND(do_unbutton)
+{
+	delete_node_with_wild(ses, LIST_BUTTON, arg);
+
+	return ses;
+}
+
+void check_all_buttons(struct session *ses, short row, short col, char *arg1, char *arg2, char *word, char *line)
+{
+	char buf[BUFFER_SIZE], arg4[BUFFER_SIZE];
+	struct listnode *node;
+	struct listroot *root;
+	short val[4];
+
+	root = ses->list[LIST_BUTTON];
+
+	if (HAS_BIT(root->flags, LIST_FLAG_IGNORE))
+	{
+		return;
+	}
+
+	sprintf(arg4, "%s %s", arg1, arg2);
+
+	show_info(ses, LIST_BUTTON, "#INFO BUTTON {%d;%d;%d;%d;%s}", row, col, -1 - (gtd->screen->rows - row), -1 - (gtd->screen->cols - col), arg4);
+
+	for (root->update = 0 ; root->update < root->used ; root->update++)
+	{
+		node = root->list[root->update];;
+
+		val[0] = node->val16[0] < 0 ? 1 + gtd->screen->rows + node->val16[0] : node->val16[0];
+		val[1] = node->val16[1] < 0 ? 1 + gtd->screen->cols + node->val16[1] : node->val16[1];
+
+		if (row < val[0] || col < val[1])
+		{
+			continue;
+		}
+
+		val[2] = node->val16[2] < 0 ? 1 + gtd->screen->rows + node->val16[2] : node->val16[2];
+		val[3] = node->val16[3] < 0 ? 1 + gtd->screen->cols + node->val16[3] : node->val16[3];
+
+		if (row > val[2] || col > val[3])
+		{
+			continue;
+		}
+
+		if (!strcmp(arg4, node->arg4))
+		{
+			show_debug(ses, LIST_BUTTON, "#DEBUG BUTTON {%s}", node->arg1);
+
+			RESTRING(gtd->vars[0], ntos(row));
+			RESTRING(gtd->vars[1], ntos(col));
+			RESTRING(gtd->vars[2], ntos(-1 - (gtd->screen->rows - row)));
+			RESTRING(gtd->vars[3], ntos(-1 - (gtd->screen->cols - col)));
+			RESTRING(gtd->vars[4], word);
+			RESTRING(gtd->vars[5], line);
+
+			substitute(ses, node->arg2, buf, SUB_ARG|SUB_SEC);
+
+			if (HAS_BIT(node->flags, NODE_FLAG_ONESHOT))
+			{
+				delete_node_list(ses, LIST_BUTTON, node);
+			}
+			script_driver(ses, LIST_BUTTON, buf);
+
+			return;
+		}
+	}
+}
+
+
+/******************************************************************************
+*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
+*                                                                             *
+*                      coded by Igor van den Hoven 2004                       *
+******************************************************************************/
+
+DO_COMMAND(do_delay)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], arg3[BUFFER_SIZE], time[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = get_arg_in_braces(ses, arg, arg2, GET_ALL);
+	arg = sub_arg_in_braces(ses, arg, arg3, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		show_list(ses->list[LIST_DELAY], 0);
+	}
+	else if (*arg2 == 0)
+	{
+		if (show_node_with_wild(ses, arg1, ses->list[LIST_DELAY]) == FALSE)
+		{
+			show_message(ses, LIST_DELAY, "#DELAY: NO MATCH(ES) FOUND FOR {%s}.", arg1);
+		}
+	}
+	else
+	{
+		if (*arg3 == 0)
+		{
+			sprintf(arg3, "%lld", utime() + (long long) (1000000 * get_number(ses, arg1)));
+
+			get_number_string(ses, arg1, time);
+
+			update_node_list(ses->list[LIST_DELAY], arg3, arg2, time, "");
+
+			show_message(ses, LIST_DELAY, "#OK, IN {%s} SECONDS {%s} IS EXECUTED.", time, arg2);
+		}
+		else
+		{
+			get_number_string(ses, arg3, time);
+
+			gtd->level->oneshot++;
+
+			update_node_list(ses->list[LIST_TICKER], arg1, arg2, time, "");
+
+			gtd->level->oneshot--;
+			
+			show_message(ses, LIST_TICKER, "#OK. #TICK {%s} WILL EXECUTE {%s} IN {%s} SECONDS.", arg1, arg2, time);
+		}
+	}
+	return ses;
+}
+
+DO_COMMAND(do_undelay)
+{
+	if (isalpha(*arg))
+	{
+		delete_node_with_wild(ses, LIST_TICKER, arg);
+	}
+	else
+	{
+		delete_node_with_wild(ses, LIST_DELAY, arg);
+	}
+
+	return ses;
+}
+
+// checked in update.c
+
+
+/******************************************************************************
+*               (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                  *
+*                                                                             *
+*                       coded by Sverre Normann 1999                          *
+*                    recoded by Igor van den Hoven 2004                       *
+******************************************************************************/
+
+DO_COMMAND(do_function)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = get_arg_in_braces(ses, arg, arg2, GET_ALL);
+
+	if (*arg1 == 0)
+	{
+		show_list(ses->list[LIST_FUNCTION], 0);
+	}
+
+	else if (*arg1 && *arg2 == 0)
+	{
+		if (show_node_with_wild(ses, arg1, ses->list[LIST_FUNCTION]) == FALSE)
+		{
+			show_message(ses, LIST_FUNCTION, "#FUNCTION: NO MATCH(ES) FOUND FOR {%s}.", arg1);
+		}
+	}
+	else
+	{
+		update_node_list(ses->list[LIST_FUNCTION], arg1, arg2, "", "");
+
+		show_message(ses, LIST_FUNCTION, "#OK. FUNCTION {%s} HAS BEEN SET TO {%s}.", arg1, arg2);
+	}
+	return ses;
+}
+
+
+DO_COMMAND(do_unfunction)
+{
+	delete_node_with_wild(ses, LIST_FUNCTION, arg);
+
+	return ses;
+}
+
+// checked in tinexp.c
+
+
+/******************************************************************************
+*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
+*                                                                             *
+*                       coded by Igor van den Hoven 2007                      *
+******************************************************************************/
+
+DO_COMMAND(do_gag)
+{
+	char arg1[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		show_list(ses->list[LIST_GAG], 0);
+	}
+	else
+	{
+		update_node_list(ses->list[LIST_GAG], arg1, "", "", "");
+
+		show_message(ses, LIST_GAG, "#OK. {%s} IS NOW GAGGED.", arg1);
+	}
+	return ses;
+}
+
+
+DO_COMMAND(do_ungag)
+{
+	delete_node_with_wild(ses, LIST_GAG, arg);
+
+	return ses;
+}
+
+void check_all_gags(struct session *ses, char *original, char *line)
+{
+	struct listroot *root = ses->list[LIST_GAG];
+	struct listnode *node;
+
+	for (root->update = 0 ; root->update < root->used ; root->update++)
+	{
+		node = root->list[root->update];
+
+		if (check_one_regexp(ses, node, line, original, 0))
+		{
+			show_debug(ses, LIST_GAG, "#DEBUG GAG {%s}", node->arg1);
+
+			if (HAS_BIT(node->flags, NODE_FLAG_ONESHOT))
+			{
+				delete_node_list(ses, LIST_GAG, node);
+			}
+			SET_BIT(ses->flags, SES_FLAG_GAG);
+
+			return;
+		}
+	}
+}
+
+
+/******************************************************************************
+*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
+*                                                                             *
+*                          coded by Bill Reiss 1993                           *
+*                     recoded by Igor van den Hoven 2004                      *
+******************************************************************************/
+
+
+DO_COMMAND(do_highlight)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], arg3[BUFFER_SIZE], temp[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ALL, SUB_VAR|SUB_FUN);
+	arg = get_arg_in_braces(ses, arg, arg3, GET_ALL);
+
+	if (*arg1 == 0)
+	{
+		show_list(ses->list[LIST_HIGHLIGHT], 0);
+	}
+	else if (*arg1 && *arg2 == 0)
+	{
+		if (show_node_with_wild(ses, arg1, ses->list[LIST_HIGHLIGHT]) == FALSE)
+		{
+			show_message(ses, LIST_HIGHLIGHT, "#HIGHLIGHT: NO MATCH(ES) FOUND FOR {%s}.", arg1);
+		}
+	}
+	else
+	{
+		if (get_color_names(ses, arg2, temp) == FALSE)
+		{
+			tintin_printf2(ses, "#HIGHLIGHT: VALID COLORS ARE:\n");
+			tintin_printf2(ses, "reset, bold, light, faint, dim, dark, underscore, blink, reverse, black, red, green, yellow, blue, magenta, cyan, white, b black, b red, b green, b yellow, b blue, b magenta, b cyan, b white, azure, ebony, jade, lime, orange, pink, silver, tan, violet.");
+		}
+		else
+		{
+			update_node_list(ses->list[LIST_HIGHLIGHT], arg1, arg2, arg3, "");
+
+			show_message(ses, LIST_HIGHLIGHT, "#OK. {%s} NOW HIGHLIGHTS {%s} @ {%s}.", arg1, arg2, arg3);
+		}
+	}
+	return ses;
+}
+
+
+DO_COMMAND(do_unhighlight)
+{
+	delete_node_with_wild(ses, LIST_HIGHLIGHT, arg);
+
+	return ses;
+}
+
+void check_all_highlights(struct session *ses, char *original, char *line)
+{
+	struct listroot *root = ses->list[LIST_HIGHLIGHT];
+	struct listnode *node;
+	char *pto, *ptl, *ptm;
+	char match[BUFFER_SIZE], color[BUFFER_SIZE], reset[BUFFER_SIZE], output[BUFFER_SIZE], plain[BUFFER_SIZE];
+	int len;
+
+	push_call("check_all_highlights(%p,%p,%p)",ses,original,line);
+
+	for (root->update = 0 ; root->update < root->used ; root->update++)
+	{
+		node = root->list[root->update];
+
+		if (check_one_regexp(ses, node, line, original, 0))
+		{
+			get_color_names(ses, node->arg2, color);
+
+			*output = *reset = 0;
+
+			pto = original;
+			ptl = line;
+
+			do
+			{
+				if (*gtd->vars[0] == 0)
+				{
+					break;
+				}
+
+				strcpy(match, gtd->vars[0]);
+
+				strip_vt102_codes(match, plain);
+
+				if (*node->arg1 == '~')
+				{
+					ptm = strstr(pto, match);
+
+					len = strlen(match);
+				}
+				else
+				{
+					ptm = strip_vt102_strstr(pto, match, &len);
+
+					ptl = strstr(ptl, match) + strlen(match);
+				}
+
+				*ptm = 0;
+
+				get_color_codes(reset, pto, reset, GET_ALL);
+
+				cat_sprintf(output, "%s%s%s\e[0m%s", pto, color, plain, reset);
+
+				pto = ptm + len;
+
+				show_debug(ses, LIST_HIGHLIGHT, "#DEBUG HIGHLIGHT {%s}", node->arg1);
+			}
+			while (check_one_regexp(ses, node, ptl, pto, 0));
+
+			if (HAS_BIT(node->flags, NODE_FLAG_ONESHOT))
+			{
+				delete_node_list(ses, LIST_HIGHLIGHT, node);
+			}
+			strcat(output, pto);
+
+			strcpy(original, output);
+		}
+	}
+	pop_call();
+	return;
+}
+
+
+/******************************************************************************
+*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
+*                                                                             *
+*                      coded by Igor van den Hoven 2006                       *
+******************************************************************************/
+
+
+DO_COMMAND(do_macro)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], arg3[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = get_arg_in_braces(ses, arg, arg2, GET_ALL);
+
+	if (*arg1 == 0)
+	{
+		show_list(ses->list[LIST_MACRO], 0);
+	}
+	else if (*arg1 && *arg2 == 0)
+	{
+		if (show_node_with_wild(ses, arg1, ses->list[LIST_MACRO]) == FALSE)
+		{
+			show_message(ses, LIST_MACRO, "#MACRO: NO MATCH(ES) FOUND FOR {%s}.", arg1);
+		}
+	}
+	else
+	{
+		tintin_macro_compile(arg1, arg3);
+
+		update_node_list(ses->list[LIST_MACRO], arg1, arg2, arg3, "");
+
+		show_message(ses, LIST_MACRO, "#OK. MACRO {%s} HAS BEEN SET TO {%s}.", arg1, arg2);
+	}
+	return ses;
+}
+
+
+DO_COMMAND(do_unmacro)
+{
+	delete_node_with_wild(ses, LIST_MACRO, arg);
+
+	return ses;
+}
+
+// checked in input.c
+
+
+/******************************************************************************
+*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
+*                                                                             *
+*                      coded by Igor van den Hoven 2004                       *
+******************************************************************************/
+
+
+DO_COMMAND(do_prompt)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], arg3[BUFFER_SIZE], arg4[BUFFER_SIZE];
+
+	arg = get_arg_in_braces(ses, arg, arg1, GET_ALL);
+	arg = get_arg_in_braces(ses, arg, arg2, GET_ALL);
+
+	if (*arg1 == 0)
+	{
+		show_list(ses->list[LIST_PROMPT], 0);
+	}
+	else if (*arg1 && *arg2 == 0)
+	{
+		if (show_node_with_wild(ses, arg1, ses->list[LIST_PROMPT]) == FALSE)
+		{
+			show_message(ses, LIST_PROMPT, "#PROMPT: NO MATCH(ES) FOUND FOR {%s}.", arg1);
+		}
+	}
+	else
+	{
+		arg = sub_arg_in_braces(ses, arg, arg3, GET_ONE, SUB_VAR|SUB_FUN);
+		arg = sub_arg_in_braces(ses, arg, arg4, GET_ONE, SUB_VAR|SUB_FUN);
+
+		update_node_list(ses->list[LIST_PROMPT], arg1, arg2, arg3, arg4);
+
+		show_message(ses, LIST_PROMPT, "#OK. {%s} NOW PROMPTS {%s} @ {%s} {%s}.", arg1, arg2, arg3, arg4);
+	}
+	return ses;
+}
+
+
+DO_COMMAND(do_unprompt)
+{
+	delete_node_with_wild(ses, LIST_PROMPT, arg);
+
+	return ses;
+}
+
+
+void check_all_prompts(struct session *ses, char *original, char *line)
+{
+	struct listroot *root = ses->list[LIST_PROMPT];
+	struct listnode *node;
+
+	if (!HAS_BIT(ses->flags, SES_FLAG_SPLIT))
+	{
+		return;
+	}
+
+	for (root->update = 0 ; root->update < root->used ; root->update++)
+	{
+		node = root->list[root->update];
+
+		if (check_one_regexp(ses, node, line, original, 0))
+		{
+			if (*node->arg2)
+			{
+				substitute(ses, node->arg2, original, SUB_ARG);
+				substitute(ses, original, original, SUB_VAR|SUB_FUN|SUB_COL|SUB_ESC);
+			}
+
+			show_debug(ses, LIST_PROMPT, "#DEBUG PROMPT {%s}", node->arg1);
+			show_debug(ses, LIST_GAG, "#DEBUG GAG {%s}", node->arg1);
+
+			split_show(ses, original, atoi(node->arg3), atoi(node->arg4));
+
+			if (HAS_BIT(node->flags, NODE_FLAG_ONESHOT))
+			{
+				delete_node_list(ses, LIST_GAG, node);
+			}
+			SET_BIT(ses->flags, SES_FLAG_GAG);
+		}
+	}
+}
+
+
+/******************************************************************************
+*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
+*                                                                             *
+*                         coded by Peter Unold 1992                           *
+*                    recoded by Igor van den Hoven 2004                       *
+******************************************************************************/
+
+
+DO_COMMAND(do_substitute)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], arg3[BUFFER_SIZE], *str;
+
+	str = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = get_arg_in_braces(ses, str, arg2, GET_ALL);
+	arg = get_arg_in_braces(ses, arg, arg3, GET_ALL);
+
+	if (*arg1 == 0)
+	{
+		show_list(ses->list[LIST_SUBSTITUTE], 0);
+	}
+	else if (*str == 0)
+	{
+		if (show_node_with_wild(ses, arg1, ses->list[LIST_SUBSTITUTE]) == FALSE)
+		{
+			show_message(ses, LIST_SUBSTITUTE, "#SUBSTITUTE: NO MATCH(ES) FOUND FOR {%s}.", arg1);
+		}
+	}
+	else
+	{
+		update_node_list(ses->list[LIST_SUBSTITUTE], arg1, arg2, arg3, "");
+
+		show_message(ses, LIST_SUBSTITUTE, "#OK. {%s} IS NOW SUBSTITUTED AS {%s} @ {%s}.", arg1, arg2, arg3);
+	}
+	return ses;
+}
+
+
+DO_COMMAND(do_unsubstitute)
+{
+	delete_node_with_wild(ses, LIST_SUBSTITUTE, arg);
+
+	return ses;
+}
+
+void check_all_substitutions(struct session *ses, char *original, char *line)
+{
+	char match[BUFFER_SIZE], subst[BUFFER_SIZE], output[BUFFER_SIZE], temp[BUFFER_SIZE], *ptl, *ptm, *pto;
+	struct listroot *root = ses->list[LIST_SUBSTITUTE];
+	struct listnode *node;
+	int len;
+
+	for (root->update = 0 ; root->update < root->used ; root->update++)
+	{
+		node = root->list[root->update];
+
+		if (check_one_regexp(ses, node, line, original, 0))
+		{
+			pto = original;
+			ptl = line;
+
+			*output = 0;
+
+			do
+			{
+				if (*gtd->vars[0] == 0)
+				{
+					break;
+				}
+
+				strcpy(match, gtd->vars[0]);
+
+				substitute(ses, node->arg2, temp, SUB_ARG);
+				substitute(ses, temp, subst, SUB_VAR|SUB_FUN|SUB_COL|SUB_ESC);
+
+				if (*node->arg1 == '~')
+				{
+					ptm = strstr(pto, match);
+
+					len = strlen(match);
+				}
+				else
+				{
+					ptm = strip_vt102_strstr(pto, match, &len);
+
+					ptl = strstr(ptl, match) + strlen(match);
+				}
+
+				*ptm = 0;
+
+				cat_sprintf(output, "%s%s", pto, subst);
+
+				pto = ptm + len;
+
+				show_debug(ses, LIST_SUBSTITUTE, "#DEBUG SUBSTITUTE {%s} {%s}", node->arg1, match);
+			}
+			while (check_one_regexp(ses, node, ptl, pto, 0));
+
+			if (HAS_BIT(node->flags, NODE_FLAG_ONESHOT))
+			{
+				delete_node_list(ses, LIST_SUBSTITUTE, node);
+			}
+			strcat(output, pto);
+
+			strcpy(original, output);
+
+			strip_vt102_codes(original, line);
+		}
+	}
+}
+
+
+/******************************************************************************
+*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
+*                                                                             *
+*                      coded by Igor van den Hoven 2006                       *
+******************************************************************************/
+
+
+DO_COMMAND(do_tab)
+{
+	char arg1[BUFFER_SIZE];
+
+	sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		show_list(ses->list[LIST_TAB], 0);
+	}
+	else
+	{
+		update_node_list(ses->list[LIST_TAB], arg1, "", "", "");
+
+		show_message(ses, LIST_TAB, "#OK. {%s} IS NOW A TAB.", arg1);
+	}
+	return ses;
+}
+
+
+DO_COMMAND(do_untab)
+{
+	delete_node_with_wild(ses, LIST_TAB, arg);
+
+	return ses;
+}
+
+// checked in cursor.c
+
+
+/******************************************************************************
+*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
+*                                                                             *
+*                         coded by Peter Unold 1992                           *
+*                     recoded by Igor van den Hoven 2004                      *
+******************************************************************************/
+
+
+DO_COMMAND(do_tick)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], arg3[BUFFER_SIZE], arg4[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = get_arg_in_braces(ses, arg, arg2, GET_ALL);
+	arg = get_arg_in_braces(ses, arg, arg4, GET_ALL);
+
+	if (*arg4 == 0)
+	{
+		strcpy(arg3, "60");
+	}
+	else
+	{
+		get_number_string(ses, arg4, arg3);
+	}
+
+	if (*arg1 == 0)
+	{
+		show_list(ses->list[LIST_TICKER], 0);
+	}
+	else if (*arg1 && *arg2 == 0)
+	{
+		if (show_node_with_wild(ses, arg1, ses->list[LIST_TICKER]) == FALSE) 
+		{
+			show_message(ses, LIST_TICKER, "#TICK, NO MATCH(ES) FOUND FOR {%s}.", arg1);
+		}
+	}
+	else
+	{
+		update_node_list(ses->list[LIST_TICKER], arg1, arg2, arg3, "");
+
+		show_message(ses, LIST_TICKER, "#OK. #TICK {%s} NOW EXECUTES {%s} EVERY {%s} SECONDS.", arg1, arg2, arg3);
+	}
+	return ses;
+}
+
+
+DO_COMMAND(do_untick)
+{
+	delete_node_with_wild(ses, LIST_TICKER, arg);
+
+	return ses;
+}
+
+
+// checked in update.c
+

+ 1172 - 0
update.c

@@ -0,0 +1,1172 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2006                       *
+******************************************************************************/
+
+#include "tintin.h"
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <sys/un.h>
+
+extern void update_input(void);
+extern void update_sessions(void);
+extern void update_daemon(void);
+extern void update_chat(void);
+extern void update_port(void);
+extern void tick_update(void);
+extern void delay_update(void);
+extern void path_update(void);
+extern void packet_update(void);
+extern void terminal_update(void);
+extern void memory_update(void);
+extern void time_update(void);
+
+extern long long display_timer(struct session *ses, int timer);
+extern void open_timer(int timer);
+extern void close_timer(int timer);
+
+void mainloop(void)
+{
+	static struct timeval start_time, end_time, wait_time;
+	static struct pulse_type pulse;
+	static int wait_time_val, span_time_val;
+
+	pulse.update_input    =  0 + PULSE_UPDATE_INPUT;
+	pulse.update_sessions =  0 + PULSE_UPDATE_SESSIONS;
+	pulse.update_delays   =  0 + PULSE_UPDATE_DELAYS;
+	pulse.update_daemon   =  0 + PULSE_UPDATE_DAEMON;
+	pulse.update_chat     =  2 + PULSE_UPDATE_CHAT;
+	pulse.update_port     =  2 + PULSE_UPDATE_PORT;
+	pulse.update_ticks    =  3 + PULSE_UPDATE_TICKS;
+	pulse.update_paths    =  3 + PULSE_UPDATE_PATHS;
+	pulse.update_packets  =  4 + PULSE_UPDATE_PACKETS;
+	pulse.update_terminal =  6 + PULSE_UPDATE_TERMINAL;
+	pulse.update_memory   =  7 + PULSE_UPDATE_MEMORY;
+	pulse.update_time     =  8 + PULSE_UPDATE_TIME;
+
+	push_call("mainloop()");
+
+	while (TRUE)
+	{
+		gettimeofday(&start_time, NULL);
+
+		gtd->total_io_exec  += span_time_val;
+		gtd->total_io_delay += wait_time_val;
+
+		if (--pulse.update_delays == 0)
+		{
+			pulse.update_delays = PULSE_UPDATE_DELAYS;
+
+			delay_update();
+		}
+
+		if (--pulse.update_input == 0)
+		{
+			open_timer(TIMER_UPDATE_INPUT);
+
+			pulse.update_input = PULSE_UPDATE_INPUT;
+
+			update_input();
+
+			close_timer(TIMER_UPDATE_INPUT);
+		}
+
+		if (--pulse.update_sessions == 0)
+		{
+			pulse.update_sessions = PULSE_UPDATE_SESSIONS;
+
+			update_sessions();
+		}
+
+		if (--pulse.update_daemon == 0)
+		{
+			pulse.update_daemon = PULSE_UPDATE_DAEMON;
+
+			update_daemon();
+		}
+
+		if (--pulse.update_chat == 0)
+		{
+			pulse.update_chat = PULSE_UPDATE_CHAT;
+
+			update_chat();
+		}	
+
+		if (--pulse.update_port == 0)
+		{
+			pulse.update_port = PULSE_UPDATE_PORT;
+
+			update_port();
+		}	
+
+		if (--pulse.update_ticks == 0)
+		{
+			pulse.update_ticks = PULSE_UPDATE_TICKS;
+
+			tick_update();
+		}
+
+		if (--pulse.update_paths == 0)
+		{
+			pulse.update_paths = PULSE_UPDATE_PATHS;
+
+			path_update();
+		}
+
+
+		if (--pulse.update_packets == 0)
+		{
+			pulse.update_packets = PULSE_UPDATE_PACKETS;
+
+			packet_update();
+		}
+
+		if (--pulse.update_terminal == 0)
+		{
+			pulse.update_terminal = PULSE_UPDATE_TERMINAL;
+
+			terminal_update();
+		}
+
+		if (--pulse.update_memory == 0)
+		{
+			pulse.update_memory = PULSE_UPDATE_MEMORY;
+
+			memory_update();
+		}
+
+		if (--pulse.update_time == 0)
+		{
+			pulse.update_time = PULSE_UPDATE_TIME;
+
+			time_update();
+		}
+
+		gettimeofday(&end_time, NULL);
+
+		if (start_time.tv_sec == end_time.tv_sec)
+		{
+			span_time_val = end_time.tv_usec - start_time.tv_usec;
+		}
+		else
+		{
+			span_time_val = (end_time.tv_sec * 1000000LL + end_time.tv_usec) - (start_time.tv_sec * 1000000LL + start_time.tv_usec);
+		}
+
+		wait_time_val = 1000000 / PULSE_PER_SECOND - span_time_val;
+
+		wait_time.tv_usec = 1000000 / PULSE_PER_SECOND - span_time_val;
+
+		if (wait_time_val > 0)
+		{
+			wait_time.tv_usec = wait_time_val;
+
+			select(0, NULL, NULL, NULL, &wait_time);
+		}
+		else
+		{
+			wait_time_val = 0;
+		}
+	}
+	pop_call();
+	return;
+}
+
+void update_input(void)
+{
+	fd_set read_fd;
+	static struct timeval timeout;
+	static int sleep;
+
+	if (gtd->detach_port)
+	{
+		return;
+
+		if (gtd->detach_sock)
+		{
+			while (TRUE)
+			{
+				FD_ZERO(&read_fd);
+
+				FD_SET(gtd->detach_sock, &read_fd);
+			
+				if (select(FD_SETSIZE, &read_fd, NULL, NULL, &timeout) <= 0)
+				{
+					break;
+				}
+
+				if (!FD_ISSET(gtd->detach_sock, &read_fd))
+				{
+					break;
+				}
+//				process_input();
+			}
+		}
+		return;
+	}
+
+	if (gtd->time_input + 10 < gtd->time)
+	{
+		if (sleep < 10)
+		{
+			sleep++;
+			return;
+		}
+		sleep = 0;
+	}
+
+	while (TRUE)
+	{
+		FD_ZERO(&read_fd);
+
+		FD_SET(STDIN_FILENO, &read_fd);
+
+		if (select(FD_SETSIZE, &read_fd, NULL, NULL, &timeout) <= 0)
+		{
+			break;
+		}
+
+		if (!FD_ISSET(STDIN_FILENO, &read_fd))
+		{
+			break;
+		}
+
+		process_input();
+
+		SET_BIT(gtd->flags, TINTIN_FLAG_FLUSH);
+
+		if (gtd->detach_port)
+		{
+			return;
+		}
+	}
+	return;
+}
+
+void update_sessions(void)
+{
+	fd_set read_fd, error_fd;
+	static struct timeval timeout;
+	static int sleep;
+	struct session *ses;
+	int rv;
+
+	if (gtd->time_session + 10 < gtd->time)
+	{
+		if (sleep < 10)
+		{
+			sleep++;
+			return;
+		}
+		sleep = 0;
+	}
+
+	push_call("update_sessions(void)");
+
+	open_timer(TIMER_UPDATE_SESSIONS);
+
+	if (gts->next)
+	{
+		FD_ZERO(&read_fd);
+		FD_ZERO(&error_fd);
+
+		for (ses = gts->next ; ses ; ses = gtd->update)
+		{
+			gtd->update = ses->next;
+
+			if (HAS_BIT(ses->flags, SES_FLAG_CONNECTED))
+			{
+				while (TRUE)
+				{
+					FD_SET(ses->socket, &read_fd);
+					FD_SET(ses->socket, &error_fd);
+
+					rv = select(FD_SETSIZE, &read_fd, NULL, &error_fd, &timeout);
+
+					if (rv < 0)
+					{
+						break; // bug report after removal.
+
+						syserr_printf(ses, "update_sessions: select:");
+
+						cleanup_session(ses);
+
+						gtd->mud_output_len = 0;
+
+						break;
+					}
+
+					if (rv == 0)
+					{
+						break;
+					}
+
+					if (FD_ISSET(ses->socket, &read_fd))
+					{
+						if (read_buffer_mud(ses) == FALSE)
+						{
+							readmud(ses);
+
+							cleanup_session(ses);
+
+							gtd->mud_output_len = 0;
+
+							break;
+						}
+					}
+
+					if (FD_ISSET(ses->socket, &error_fd))
+					{
+						FD_CLR(ses->socket, &read_fd);
+
+						cleanup_session(ses);
+
+						gtd->mud_output_len = 0;
+
+						break;
+					}
+				}
+
+				gtd->time_session = gtd->time;
+
+				if (gtd->mud_output_len)
+				{
+					readmud(ses);
+				}
+			}
+		}
+	}
+
+	for (ses = gts ; ses ; ses = gtd->update)
+	{
+		gtd->update = ses->next;
+
+		if (ses->check_output == 0 && HAS_BIT(ses->flags, SES_FLAG_PRINTLINE))
+		{
+			DEL_BIT(ses->flags, SES_FLAG_PRINTLINE);
+			SET_BIT(ses->flags, SES_FLAG_PRINTBUFFER);
+
+			buffer_end(ses, "");
+
+			DEL_BIT(ses->flags, SES_FLAG_PRINTBUFFER);
+		}
+	}
+
+
+	if (HAS_BIT(gtd->flags, TINTIN_FLAG_FLUSH))
+	{
+		DEL_BIT(gtd->flags, TINTIN_FLAG_FLUSH);
+
+//		if (gtd->detach_port == 0)
+		{
+			fflush(stdout);
+		}
+	}
+
+	close_timer(TIMER_UPDATE_SESSIONS);
+
+	pop_call();
+	return;
+}
+
+void update_daemon(void)
+{
+	fd_set read_fd, error_fd;
+	static struct timeval timeout;
+	socklen_t len;
+	int rv;
+
+	if (gtd->detach_port)
+	{
+		if (TRUE)
+		{
+			FD_ZERO(&read_fd);
+
+			FD_SET(gtd->detach_port, &read_fd);
+
+			rv = select(FD_SETSIZE, &read_fd, NULL, NULL, &timeout);
+
+			if (rv > 0)
+			{
+				if (FD_ISSET(gtd->detach_port, &read_fd))
+				{
+					if (gtd->detach_sock)
+					{
+						tintin_printf2(gtd->ses, "#DAEMON UPDATE: ANOTHER CONNECTION IS TAKING OVER {%s}.", gtd->detach_file);
+						kill((pid_t) gtd->detach_sock, SIGTSTP);
+						close(gtd->detach_sock);
+					}
+
+					gtd->detach_sock = accept(gtd->detach_port, 0, 0);
+
+					if (gtd->detach_sock < 0)
+					{
+						syserr_printf(gtd->ses, "update_daemon: detach_port: accept");
+						
+						gtd->detach_sock = close(gtd->detach_sock);
+						
+						goto attach;
+					}
+
+					if (fcntl(gtd->detach_sock, F_SETFL, O_NDELAY|O_NONBLOCK) == -1)
+					{
+						syserr_printf(gtd->ses, "update_daemon: detach_port: fcntl O_NDELAY|O_NONBLOCK");
+
+						gtd->detach_sock = close(gtd->detach_sock);
+						
+						goto attach;
+					}
+
+					len = sizeof(struct process_data);
+
+					if (getsockopt(gtd->detach_sock, SOL_SOCKET, SO_PEERCRED, &gtd->detach_info, &len) == -1)
+					{
+						syserr_printf(gtd->ses, "update_daemon: getsockopt:");
+
+						gtd->detach_sock = close(gtd->detach_sock);
+
+						goto attach;
+					}
+
+					if (geteuid() != gtd->detach_info.uid)
+					{
+						tintin_printf2(gtd->ses, "#DAEMON UPDATE: YOUR UID IS %d WHILE {%s} HAS UID {%d}.", geteuid(), gtd->detach_file, gtd->detach_info.uid);
+
+						gtd->detach_sock = close(gtd->detach_sock);
+
+						goto attach;
+					}
+
+//					tintin_printf2(gtd->ses, "sock=%d pid=%d, euid=%d, egid=%d", gtd->detach_port, getpid(), geteuid(), getegid());
+//					tintin_printf2(gtd->ses, "sock=%d pid=%d, euid=%d, egid=%d", gtd->detach_sock, gtd->detach_info.pid, gtd->detach_info.uid, gtd->detach_info.gid);
+
+					winch_handler(0);
+
+					dirty_screen(gtd->ses);
+
+					tintin_printf2(gtd->ses, "#DAEMON UPDATE: ATTACHED {%s} TO PID {%d}.", gtd->detach_file, gtd->detach_info.pid);
+				}
+			}
+			else if (rv < 0)
+			{
+				if (errno != EINTR)
+				{
+					syserr_printf(gtd->ses, "update_daemon: select:");
+				}
+			}
+		}
+
+		if (gtd->detach_sock > 0)
+		{
+			while (gtd->detach_sock)
+			{
+				FD_ZERO(&read_fd);
+//				FD_ZERO(&write_fd);
+				FD_ZERO(&error_fd);
+
+				FD_SET(gtd->detach_sock, &read_fd);
+//				FD_SET(gtd->detach_sock, &write_fd);
+				FD_SET(gtd->detach_sock, &error_fd);
+
+				rv = select(FD_SETSIZE, &read_fd, NULL, &error_fd, &timeout);
+
+//				tintin_printf2(gtd->ses, "debug: rv: %d (%d,%d,%d)\n", rv, FD_ISSET(gtd->detach_sock, &read_fd), FD_ISSET(gtd->detach_sock, &write_fd), FD_ISSET(gtd->detach_sock, &error_fd));
+
+				if (rv == 0)
+				{
+					break;
+				}
+				else if (rv < 0)
+				{
+					FD_CLR(gtd->detach_sock, &read_fd);
+
+					gtd->detach_sock = close(gtd->detach_sock);
+
+	                                syserr_printf(gtd->ses, "update_daemon: detach_sock: select:");
+
+	                                break;
+				}
+				else if (rv > 0)
+				{
+					if (FD_ISSET(gtd->detach_sock, &error_fd))
+					{
+						FD_CLR(gtd->detach_sock, &read_fd);
+
+						gtd->detach_sock = close(gtd->detach_sock);
+
+						show_error(gtd->ses, LIST_COMMAND, "update_daemon: detach_sock: error_fd");
+
+						goto attach;
+					}
+
+/*					if (!FD_ISSET(gtd->detach_sock, &write_fd))
+					{
+						FD_CLR(gtd->detach_sock, &read_fd);
+
+						gtd->detach_sock = close(gtd->detach_sock);
+
+						show_error(gtd->ses, LIST_COMMAND, "update_daemon: detach_sock: write_fd");
+
+						goto attach;
+					}
+*/
+					if (!FD_ISSET(gtd->detach_sock, &read_fd))
+					{
+//						gtd->detach_sock = close(gtd->detach_sock); // experimental
+						break;
+					}
+					process_input();
+				}
+			}
+		}
+	}
+
+	attach:
+
+	if (gtd->attach_sock)
+	{
+		FD_ZERO(&read_fd);
+		FD_ZERO(&error_fd);
+
+		FD_SET(gtd->attach_sock, &read_fd);
+		FD_SET(gtd->attach_sock, &error_fd);
+
+		rv = select(FD_SETSIZE, &read_fd, NULL, &error_fd, &timeout);
+
+		if (rv < 0)
+		{
+			gtd->attach_sock = close(gtd->attach_sock);
+	
+			show_message(gtd->ses, LIST_COMMAND, "#DAEMON UPDATE: UNATTACHING {%s} DUE TO SELECT ERROR.", gtd->attach_file);
+		}
+		else if (rv > 0)
+		{
+			if (FD_ISSET(gtd->attach_sock, &read_fd))
+			{
+				char buffer[BUFFER_SIZE];
+
+				rv = read(gtd->attach_sock, buffer, BUFFER_SIZE -1);
+
+				if (rv <= 0)
+				{
+					gtd->attach_sock = close(gtd->attach_sock);
+
+					winch_handler(0);
+
+					show_message(gtd->ses, LIST_COMMAND, "#DAEMON UPDATE: UNATTACHING {%s} DUE TO READ ERROR.", gtd->attach_file);
+				}
+				else
+				{
+					buffer[rv] = 0;
+/*
+					if (buffer[rv - 1] == (char) 255)
+					{
+						gtd->attach_sock = close(gtd->attach_sock);
+
+						show_message(gtd->ses, LIST_COMMAND, "\n#DAEMON {%s} SIGTSTP: UNATTACHING.", gtd->attach_file);
+
+						dirty_screen(gtd->ses);
+
+						return;
+					}
+*/
+					if (gtd->level->quiet == 0)
+					{
+						printf("%s", buffer);
+					}
+
+					if (FD_ISSET(gtd->attach_sock, &error_fd))
+					{
+						FD_CLR(gtd->attach_sock, &read_fd);
+
+						gtd->attach_sock = close(gtd->attach_sock);
+
+						show_message(gtd->ses, LIST_COMMAND, "#DAEMON UPDATE: UNATTACHING {%s} DUE TO EXCEPTION ERROR.", gtd->attach_file);
+					}
+				}
+			}
+			else
+			{
+				return;
+			}
+		}
+		fflush(stdout);
+	}
+
+}
+
+void update_chat(void)
+{
+	fd_set read_fd, write_fd, error_fd;
+	static struct timeval timeout;
+	struct chat_data *buddy, *buddy_next;
+	int rv;
+
+	open_timer(TIMER_UPDATE_CHAT);
+
+	if (gtd->chat)
+	{
+		for (buddy = gtd->chat->next ; buddy ; buddy = buddy_next)
+		{
+			buddy_next = buddy->next;
+
+			if (buddy->timeout && buddy->timeout < gtd->time)
+			{
+				chat_socket_printf(buddy, "<CHAT> Connection timed out.");
+
+				close_chat(buddy, TRUE);
+			}
+		}
+
+		if (gtd->chat->paste_time && gtd->chat->paste_time < gtd->utime)
+		{
+			chat_paste(NULL, NULL);
+		}
+
+		FD_ZERO(&read_fd);
+		FD_ZERO(&write_fd);
+		FD_ZERO(&error_fd);
+
+		FD_SET(gtd->chat->fd, &read_fd);
+
+		for (buddy = gtd->chat->next ; buddy ; buddy = buddy->next)
+		{
+			FD_SET(buddy->fd, &read_fd);
+			FD_SET(buddy->fd, &write_fd);
+			FD_SET(buddy->fd, &error_fd);
+		}
+
+		rv = select(FD_SETSIZE, &read_fd, &write_fd, &error_fd, &timeout);
+
+		if (rv <= 0)
+		{
+			if (rv == 0 || errno == EINTR)
+			{
+				goto update_chat_end;
+			}
+			syserr_fatal(-1, "update_chat: select");
+		}
+		process_chat_connections(&read_fd, &write_fd, &error_fd);
+
+	}
+	update_chat_end:
+
+	close_timer(TIMER_UPDATE_CHAT);
+}
+
+void update_port(void)
+{
+	struct session *ses;
+	fd_set read_fd, write_fd, error_fd;
+	static struct timeval timeout;
+	struct port_data *buddy;
+	int rv;
+
+	open_timer(TIMER_UPDATE_PORT);
+
+	for (ses = gts->next ; ses ; ses = gtd->update)
+	{
+		gtd->update = ses->next;
+
+		if (ses->port)
+		{
+			FD_ZERO(&read_fd);
+			FD_ZERO(&write_fd);
+			FD_ZERO(&error_fd);
+
+			FD_SET(ses->port->fd, &read_fd);
+
+			for (buddy = ses->port->next ; buddy ; buddy = buddy->next)
+			{
+				FD_SET(buddy->fd, &read_fd);
+				FD_SET(buddy->fd, &write_fd);
+				FD_SET(buddy->fd, &error_fd);
+			}
+
+			rv = select(FD_SETSIZE, &read_fd, &write_fd, &error_fd, &timeout);
+
+			if (rv <= 0)
+			{
+				if (rv == 0 || errno == EINTR)
+				{
+					continue;
+				}
+				syserr_fatal(-1, "update_port: select");
+			}
+
+			process_port_connections(ses, &read_fd, &write_fd, &error_fd);
+		}
+	}
+
+	close_timer(TIMER_UPDATE_PORT);
+}
+
+void tick_update(void)
+{
+	struct session *ses;
+	struct listnode *node;
+	struct listroot *root;
+	char buf[BUFFER_SIZE];
+
+	open_timer(TIMER_UPDATE_TICKS);
+
+	utime();
+
+	for (ses = gts->next ; ses ; ses = gtd->update)
+	{
+		gtd->update = ses->next;
+
+		root = ses->list[LIST_TICKER];
+
+		for (root->update = 0 ; root->update < root->used ; root->update++)
+		{
+			node = root->list[root->update];
+
+			if (node->val64 == 0)
+			{
+				node->val64 = gtd->utime + (long long) (get_number(ses, node->arg3) * 1000000LL);
+
+				show_info(ses, LIST_TICKER, "#INFO TICK {%s} INITIALIZED WITH TIMESTAMP {%lld}", node->arg1, node->val64);
+			}
+
+			if (node->val64 <= gtd->utime)
+			{
+				node->val64 += (long long) (get_number(ses, node->arg3) * 1000000LL);
+
+				show_info(ses, LIST_TICKER, "#INFO TICK {%s} INITIALIZED WITH TIMESTAMP {%lld}", node->arg1, node->val64);
+
+				if (!HAS_BIT(root->flags, LIST_FLAG_IGNORE))
+				{
+					show_debug(ses, LIST_TICKER, "#DEBUG TICKER {%s}", node->arg2);
+
+					if (HAS_BIT(node->flags, NODE_FLAG_ONESHOT))
+					{
+						strcpy(buf, node->arg2);
+
+						delete_node_list(ses, LIST_TICKER, node);
+
+						script_driver(ses, LIST_TICKER, buf);
+					}
+					else
+					{
+						script_driver(ses, LIST_TICKER, node->arg2);
+					}
+				}
+			}
+		}
+	}
+	close_timer(TIMER_UPDATE_TICKS);
+}
+
+void delay_update(void)
+{
+	struct session *ses;
+	struct listnode *node;
+	struct listroot *root;
+	char buf[BUFFER_SIZE];
+
+	open_timer(TIMER_UPDATE_DELAYS);
+
+	for (ses = gts ; ses ; ses = gtd->update)
+	{
+		gtd->update = ses->next;
+
+		root = ses->list[LIST_DELAY];
+
+		for (root->update = 0 ; root->update < root->used ; root->update++)
+		{
+			node = root->list[root->update];
+
+			if (node->val64 == 0)
+			{
+				node->val64 = gtd->utime + (long long) (get_number(ses, node->arg3) * 1000000LL);
+
+				show_info(ses, LIST_DELAY, "#INFO DELAY {%s} INITIALIZED WITH TIMESTAMP {%lld}", node->arg1, node->val64);
+			}
+
+			if (node->val64 <= gtd->utime)
+			{
+				strcpy(buf, node->arg2);
+
+				show_debug(ses, LIST_DELAY, "#DEBUG DELAY {%s}", buf);
+
+				delete_node_list(ses, LIST_DELAY, node);
+
+				script_driver(ses, LIST_DELAY, buf);
+			}
+		}
+	}
+	close_timer(TIMER_UPDATE_DELAYS);
+}
+
+void path_update(void)
+{
+	struct session *ses;
+	struct listnode *node;
+	struct listroot *root;
+
+	open_timer(TIMER_UPDATE_PATHS);
+
+	for (ses = gts ; ses ; ses = gtd->update)
+	{
+		gtd->update = ses->next;
+
+		root = ses->list[LIST_PATH];
+
+		while (root->update < root->used)
+		{
+			node = root->list[root->update];
+
+			if (node->val64 > 0 && node->val64 <= gtd->utime)
+			{
+				root->update++;
+
+				node->val64 = 0;
+
+				show_debug(ses, LIST_COMMAND, "#DEBUG PATH {%s}", node->arg1);
+
+				script_driver(ses, LIST_COMMAND, node->arg1);
+			}
+			break;
+		}
+	}
+	close_timer(TIMER_UPDATE_PATHS);
+}
+
+void packet_update(void)
+{
+	char result[STRING_SIZE];
+	struct session *ses;
+
+	open_timer(TIMER_UPDATE_PACKETS);
+
+	for (ses = gts->next ; ses ; ses = gtd->update)
+	{
+		gtd->update = ses->next;
+
+		if (ses->check_output && gtd->utime > ses->check_output)
+		{
+			if (HAS_BIT(ses->flags, SES_FLAG_SPLIT))
+			{
+				save_pos(ses);
+
+				goto_pos(ses, ses->split->bot_row, 1);
+			}
+
+			SET_BIT(ses->flags, SES_FLAG_READMUD);
+
+			strcpy(result, ses->more_output);
+
+			ses->more_output[0] = 0;
+
+			if (HAS_BIT(ses->charset, CHARSET_FLAG_ALL_TOUTF8))
+			{
+				char buf[BUFFER_SIZE];
+
+				all_to_utf8(ses, result, buf);
+
+				process_mud_output(ses, buf, TRUE);
+			}
+			else
+			{
+				process_mud_output(ses, result, TRUE);
+			}
+			DEL_BIT(ses->flags, SES_FLAG_READMUD);
+
+			if (HAS_BIT(ses->flags, SES_FLAG_SPLIT))
+			{
+				restore_pos(ses);
+			}
+		}
+	}
+	close_timer(TIMER_UPDATE_PACKETS);
+}
+
+
+
+void terminal_update(void)
+{
+	struct session *ses;
+
+	open_timer(TIMER_UPDATE_TERMINAL);
+
+	for (ses = gts ; ses ; ses = ses->next)
+	{
+		if (HAS_BIT(ses->flags, SES_FLAG_UPDATEVTMAP))
+		{
+			DEL_BIT(ses->flags, SES_FLAG_UPDATEVTMAP);
+
+			show_vtmap(ses);
+
+			check_all_events(ses, SUB_ARG|SUB_SEC, 0, 0, "MAP UPDATED VTMAP");
+
+			SET_BIT(gtd->flags, TINTIN_FLAG_FLUSH);
+		}
+	}
+
+	close_timer(TIMER_UPDATE_TERMINAL);
+}
+
+void memory_update(void)
+{
+	open_timer(TIMER_UPDATE_MEMORY);
+
+	while (gtd->dispose_next)
+	{
+		dispose_session(gtd->dispose_next);
+	}
+
+	close_timer(TIMER_UPDATE_MEMORY);
+}
+
+void time_update(void)
+{
+	static char str_sec[9], str_min[9], str_hour[9], str_wday[9], str_mday[9], str_mon[9], str_year[9];
+
+	static struct tm old_calendar;
+
+	gtd->time = time(NULL);
+
+	gtd->calendar = *localtime(&gtd->time);
+
+	open_timer(TIMER_UPDATE_TIME);
+
+	// Initialize on the first call.
+
+	if (old_calendar.tm_year == 0)
+	{
+		old_calendar.tm_sec  = gtd->calendar.tm_sec;
+		old_calendar.tm_min  = gtd->calendar.tm_min;
+		old_calendar.tm_hour = gtd->calendar.tm_hour;
+		old_calendar.tm_wday = gtd->calendar.tm_wday;
+		old_calendar.tm_mday = gtd->calendar.tm_mday;
+		old_calendar.tm_mon  = gtd->calendar.tm_mon;
+		old_calendar.tm_year = gtd->calendar.tm_year;
+
+		strftime(str_sec,  9, "%S", &gtd->calendar);
+		strftime(str_min,  9, "%M", &gtd->calendar);
+		strftime(str_hour, 9, "%H", &gtd->calendar);
+		strftime(str_wday, 9, "%w", &gtd->calendar);
+		strftime(str_mday, 9, "%d", &gtd->calendar);
+		strftime(str_mon,  9, "%m", &gtd->calendar);
+		strftime(str_year, 9, "%Y", &gtd->calendar);
+
+		return;
+	}
+
+	if (gtd->calendar.tm_sec == old_calendar.tm_sec)
+	{
+		goto time_event_end;
+	}
+
+	strftime(str_min, 9, "%S", &gtd->calendar);
+	old_calendar.tm_sec = gtd->calendar.tm_sec;
+
+	if (gtd->calendar.tm_min == old_calendar.tm_min)
+	{
+		goto time_event_sec;
+	}
+
+	strftime(str_min, 9, "%M", &gtd->calendar);
+	old_calendar.tm_min = gtd->calendar.tm_min;
+
+	if (gtd->calendar.tm_hour == old_calendar.tm_hour)
+	{
+		goto time_event_min;
+	}
+
+	strftime(str_hour, 9, "%H", &gtd->calendar);
+	old_calendar.tm_hour = gtd->calendar.tm_hour;
+
+	if (gtd->calendar.tm_mday == old_calendar.tm_mday)
+	{
+		goto time_event_hour;
+	}
+
+	strftime(str_wday, 9, "%w", &gtd->calendar);
+	old_calendar.tm_wday = gtd->calendar.tm_wday;
+
+	strftime(str_mday, 9, "%d", &gtd->calendar);
+	old_calendar.tm_mday = gtd->calendar.tm_mday;
+
+	if (gtd->calendar.tm_mon == old_calendar.tm_mon)
+	{
+		goto time_event_mday;
+	}
+
+	strftime(str_mon, 9, "%m", &gtd->calendar);
+	old_calendar.tm_mon = gtd->calendar.tm_mon;
+
+	if (gtd->calendar.tm_year == old_calendar.tm_year)
+	{
+		goto time_event_mon;
+	}
+
+	strftime(str_year, 9, "%Y", &gtd->calendar);
+	old_calendar.tm_year = gtd->calendar.tm_year;
+
+	check_all_events(NULL, SUB_ARG, 0, 7, "YEAR", str_year, str_mon, str_wday, str_mday, str_hour, str_min, str_sec);
+	check_all_events(NULL, SUB_ARG, 1, 7, "YEAR %s", str_year, str_year, str_mon, str_wday, str_mday, str_hour, str_min, str_sec);
+
+
+	time_event_mon:
+
+	check_all_events(NULL, SUB_ARG, 0, 7, "MONTH", str_year, str_mon, str_wday, str_mday, str_hour, str_min, str_sec);
+	check_all_events(NULL, SUB_ARG, 1, 7, "MONTH %s", str_mon, str_year, str_mon, str_wday, str_mday, str_hour, str_min, str_sec);
+
+
+	time_event_mday:
+
+	check_all_events(NULL, SUB_ARG, 0, 7, "WEEK", str_year, str_mon, str_wday, str_mday, str_hour, str_min, str_sec);
+	check_all_events(NULL, SUB_ARG, 1, 7, "WEEK %s", str_wday, str_year, str_mon, str_wday, str_mday, str_hour, str_min, str_sec);
+
+	check_all_events(NULL, SUB_ARG, 2, 7, "DATE %s-%s", str_mon, str_mday, str_year, str_mon, str_wday, str_mday, str_hour, str_min, str_sec);
+
+	check_all_events(NULL, SUB_ARG, 0, 7, "DAY", str_year, str_mon, str_wday, str_mday, str_hour, str_min, str_sec);
+	check_all_events(NULL, SUB_ARG, 1, 7, "DAY %s", str_mday, str_year, str_mon, str_wday, str_mday, str_hour, str_min, str_sec);
+
+
+	time_event_hour:
+
+	check_all_events(NULL, SUB_ARG, 0, 7, "HOUR", str_year, str_mon, str_wday, str_mday, str_hour, str_min, str_sec);
+	check_all_events(NULL, SUB_ARG, 1, 7, "HOUR %s", str_hour, str_year, str_mon, str_wday, str_mday, str_hour, str_min, str_sec);
+
+
+	time_event_min:
+
+	check_all_events(NULL, SUB_ARG, 4, 7, "DATE %s-%s %s:%s", str_mon, str_mday, str_hour, str_min, str_year, str_mon, str_wday, str_mday, str_hour, str_min, str_sec);
+
+	check_all_events(NULL, SUB_ARG, 2, 7, "TIME %s:%s", str_hour, str_min, str_year, str_mon, str_wday, str_mday, str_hour, str_min, str_sec);
+
+	check_all_events(NULL, SUB_ARG, 0, 7, "MINUTE", str_year, str_mon, str_wday, str_mday, str_hour, str_min, str_sec);
+	check_all_events(NULL, SUB_ARG, 1, 7, "MINUTE %s", str_min, str_year, str_mon, str_wday, str_mday, str_hour, str_min, str_sec);
+
+
+	time_event_sec:
+
+	old_calendar.tm_sec = gtd->calendar.tm_sec;
+
+	check_all_events(NULL, SUB_ARG, 3, 7, "TIME %s:%s:%s", str_hour, str_min, str_sec, str_year, str_mon, str_wday, str_mday, str_hour, str_min, str_sec);
+
+	check_all_events(NULL, SUB_ARG, 0, 7, "SECOND", str_year, str_mon, str_wday, str_mday, str_hour, str_min, str_sec);
+	check_all_events(NULL, SUB_ARG, 1, 7, "SECOND %s", str_sec, str_year, str_mon, str_wday, str_mday, str_hour, str_min, str_sec);
+
+	time_event_end:
+
+	close_timer(TIMER_UPDATE_TIME);
+}
+
+
+void show_cpu(struct session *ses)
+{
+	long long total_cpu = 0;
+	int timer;
+
+	tintin_printf2(ses, "Section                           Time (usec)    Freq (msec)  %%Prog         %%CPU");
+
+	tintin_printf2(ses, "");
+
+	for (timer = 0 ; timer < TIMER_CPU ; timer++)
+	{
+		total_cpu += display_timer(ses, timer);
+	}
+
+	tintin_printf2(ses, "");
+
+	tintin_printf2(ses, "Unknown CPU Usage:             %7.3f percent", (gtd->total_io_exec - total_cpu) * 100.0 / (gtd->total_io_delay + gtd->total_io_exec));
+	tintin_printf2(ses, "Average CPU Usage:             %7.3f percent", (gtd->total_io_exec)             * 100.0 / (gtd->total_io_delay + gtd->total_io_exec));
+//	tintin_printf2(ses, "Total   CPU Usecs:             %10ld", gtd->total_io_exec);
+//	tintin_printf2(ses, "Total   CPU Delay:             %10ld", gtd->total_io_delay);
+
+}
+
+
+long long display_timer(struct session *ses, int timer)
+{
+	long long total_usage, indicated_usage;
+
+	total_usage = gtd->total_io_exec + gtd->total_io_delay;
+
+	if (total_usage == 0)
+	{
+		return 0;
+	}
+
+	if (gtd->timer[timer][1] == 0 || gtd->timer[timer][4] == 0)
+	{
+		return 0;
+	}
+
+//	indicated_usage = gtd->timer[timer][0] / gtd->timer[timer][1] * gtd->timer[timer][4];
+
+	indicated_usage = gtd->timer[timer][0];
+
+	tintin_printf2(ses, "%-30s%8lld       %8lld      %8.2f     %8.3f",
+		timer_table[timer].name,
+		gtd->timer[timer][0] / gtd->timer[timer][1],
+		gtd->timer[timer][3] / gtd->timer[timer][4] / 1000,
+		(double) (100000 * indicated_usage / gtd->total_io_exec) / 1000.0,
+		(double) (100000 * indicated_usage / total_usage) / 1000.0
+		);
+
+	return indicated_usage;
+}
+
+
+void open_timer(int timer)
+{
+	struct timeval last_time;
+	long long current_time;
+
+	gettimeofday(&last_time, NULL);
+
+	current_time = (long long) last_time.tv_usec + 1000000LL * (long long) last_time.tv_sec;
+
+	if (gtd->timer[timer][2] == 0)
+	{
+		gtd->timer[timer][2] = current_time;
+	}
+	else
+	{
+		gtd->timer[timer][3] += current_time - gtd->timer[timer][2];
+		gtd->timer[timer][2]  = current_time;
+		gtd->timer[timer][4] ++;
+	}
+}
+
+
+void close_timer(int timer)
+{
+	struct timeval last_time;
+	long long current_time;
+
+	gettimeofday(&last_time, NULL);
+
+	current_time = (long long) last_time.tv_usec + 1000000LL * (long long) last_time.tv_sec;
+
+	gtd->timer[timer][0] += (current_time - gtd->timer[timer][2]);
+	gtd->timer[timer][1] ++;
+}

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 1174 - 0
utf8.c


+ 498 - 0
utils.c

@@ -0,0 +1,498 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2019 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
+*                                                                             *
+*                      coded by Igor van den Hoven 2004                       *
+******************************************************************************/
+
+#include "tintin.h"
+
+
+int hex_digit(char *str)
+{
+	if (isdigit((int) *str))
+	{
+		return *str - '0';
+	}
+	else
+	{
+		return toupper((int) *str) - 'A' + 10;
+	}
+}
+
+unsigned long long hex_number_64bit(char *str)
+{
+	unsigned long long len, mul, val = 0;
+
+	for (len = 0 ; len < 16 ; len++)
+	{
+		if (!isxdigit((int) str[len]))
+		{
+			break;
+		}
+	}
+
+	for (mul = 1 ; len > 0 ; mul *= 16)
+	{
+		val += mul * hex_digit(str + --len);
+	}
+
+	return val;
+}
+
+int hex_number_8bit(char *str)
+{
+	int value = 0;
+
+	if (str)
+	{
+		if (isdigit((int) *str))
+		{
+			value += 16 * (*str - '0');
+		}
+		else
+		{
+			value += 16 * (toupper((int) *str) - 'A' + 10);
+		}
+		str++;
+	}
+
+	if (str)
+	{
+		if (isdigit((int) *str))
+		{
+			value += *str - '0';
+		}
+		else
+		{
+			value += toupper((int) *str) - 'A' + 10;
+		}
+		str++;
+	}
+
+	return value;
+}
+
+int oct_number(char *str)
+{
+	int value = 0;
+
+	if (str)
+	{
+		if (isdigit((int) *str))
+		{
+			value += 8 * (*str - '0');
+		}
+		str++;
+	}
+
+	if (str)
+	{
+		if (isdigit((int) *str))
+		{
+			value += *str - '0';
+		}
+		str++;
+	}
+
+	return value;
+}
+
+int unicode_16_bit(char *str, char *out)
+{
+	int val = 0;
+	unsigned char *pto = (unsigned char *) out;
+
+	if (isdigit((int) *str))
+	{
+		val += 4096 * (*str - '0');
+	}
+	else
+	{
+		val += 4096 * (toupper((int) *str) - 'A' + 10);
+	}
+	str++;
+
+	if (isdigit((int) *str))
+	{
+		val += 256 * (*str - '0');
+	}
+	else
+	{
+		val += 256 * (toupper((int) *str) - 'A' + 10);
+	}
+	str++;
+
+	if (isdigit((int) *str))
+	{
+		val += 16 * (*str - '0');
+	}
+	else
+	{
+		val += 16 * (toupper((int) *str) - 'A' + 10);
+	}
+	str++;
+
+	if (isdigit((int) *str))
+	{
+		val += (*str - '0');
+	}
+	else
+	{
+		val += (toupper((int) *str) - 'A' + 10);
+	}
+	str++;
+
+	if (val < 128)
+	{
+		*pto++ = val;
+		*pto++ = 0;
+		return 1;
+	}
+	else if (val < 4096)
+	{
+		*pto++ = 192 + val / 64;
+		*pto++ = 128 + val % 64;
+		*pto++ = 0;
+		return 2;
+	}
+	else
+	{
+		*pto++ = 224 + val / 4096;
+		*pto++ = 128 + val / 64 % 64;
+		*pto++ = 128 + val % 64;
+		*pto++ = 0;
+		return 3;
+	}
+}
+
+int unicode_21_bit(char *str, char *out)
+{
+	int val = 0;
+	unsigned char *pto = (unsigned char *) out;
+
+	if (str)
+	{
+		if (isdigit((int) *str))
+		{
+			val += 1048576 * (*str - '0');
+		}
+		else
+		{
+			val += 1048576 * (toupper((int) *str) - 'A' + 10);
+		}
+		str++;
+	}
+
+	if (str)
+	{
+		if (isdigit((int) *str))
+		{
+			val += 65536 * (*str - '0');
+		}
+		else
+		{
+			val += 65536 * (toupper((int) *str) - 'A' + 10);
+		}
+		str++;
+	}
+
+	if (str)
+	{
+		if (isdigit((int) *str))
+		{
+			val += 4096 * (*str - '0');
+		}
+		else
+		{
+			val += 4096 * (toupper((int) *str) - 'A' + 10);
+		}
+		str++;
+	}
+
+	if (str)
+	{
+		if (isdigit((int) *str))
+		{
+			val += 256 * (*str - '0');
+		}
+		else
+		{
+			val += 256 * (toupper((int) *str) - 'A' + 10);
+		}
+		str++;
+	}
+
+	if (str)
+	{
+		if (isdigit((int) *str))
+		{
+			val += 16 * (*str - '0');
+		}
+		else
+		{
+			val += 16 * (toupper((int) *str) - 'A' + 10);
+		}
+		str++;
+	}
+
+	if (str)
+	{
+		if (isdigit((int) *str))
+		{
+			val += (*str - '0');
+		}
+		else
+		{
+			val += (toupper((int) *str) - 'A' + 10);
+		}
+		str++;
+	}
+
+	if (val < 128)
+	{
+		*pto++ = val;
+		return 1;
+	}
+	else if (val < 4096)
+	{
+		*pto++ = 192 + val / 64;
+		*pto++ = 128 + val % 64;
+		*pto++ = 0;
+		return 2;
+	}
+	else if (val < 65536)
+	{
+		*pto++ = 224 + val / 4096;
+		*pto++ = 128 + val / 64 % 64;
+		*pto++ = 128 + val % 64;
+		*pto++ = 0;
+		return 3;
+	}
+	else if (val < 1114112)
+	{
+		*pto++ = 240 + val / 262144;
+		*pto++ = 128 + val / 4096 % 64;
+		*pto++ = 128 + val / 64 % 64;
+	        *pto++ = 128 + val % 64;
+		*pto++ = 0;
+	        return 4;
+	}
+	else
+	{
+		*pto++ = 239;
+		*pto++ = 191;
+		*pto++ = 189;
+		*pto++ = 0;
+		return 3;
+	}
+}
+
+unsigned long long utime()
+{
+	struct timeval now_time;
+
+	gettimeofday(&now_time, NULL);
+
+	if (gtd->utime >= now_time.tv_sec * 1000000ULL + now_time.tv_usec)
+	{
+		gtd->utime++;
+	}
+	else
+	{
+		gtd->utime = now_time.tv_sec * 1000000ULL + now_time.tv_usec;
+	}
+	return gtd->utime;
+}
+
+void seed_rand(struct session *ses, unsigned long long seed)
+{
+	ses->rand = seed % 4294967291ULL;
+}
+
+unsigned long long generate_rand(struct session *ses)
+{
+	ses->rand = ses->rand * 279470273ULL % 4294967291ULL;
+
+//	return ses->rand % 1000000000ULL;
+
+	return ses->rand;
+}
+/*
+uint32_t lcg_rand(uint32_t *state)
+{
+    return *state = (uint64_t)*state * 279470273u % 0xfffffffb;
+}
+	ses->rand = 6364136223846793005ULL * ses->rand + 1ULL;
+
+	return ses->rand;
+}
+*/
+char *capitalize(char *str)
+{
+	static char outbuf[BUFFER_SIZE];
+	int cnt;
+
+	for (cnt = 0 ; str[cnt] != 0 ; cnt++)
+	{
+		outbuf[cnt] = toupper((int) str[cnt]);
+	}
+	outbuf[cnt] = 0;
+
+	return outbuf;
+}
+
+char *ntos(long long number)
+{
+	static char outbuf[100][NUMBER_SIZE];
+	static int cnt;
+
+	cnt = (cnt + 1) % 100;
+
+	sprintf(outbuf[cnt], "%lld", number);
+
+	return outbuf[cnt];
+}
+
+char *indent_one(int len)
+{
+	static char outbuf[10][STACK_SIZE];
+	static int cnt;
+
+	cnt = (cnt + 1) % 10;
+
+	memset(outbuf[cnt], ' ', UMAX(1, len));
+
+	outbuf[cnt][len] = 0;
+
+	return outbuf[cnt];
+}
+
+char *indent(int len)
+{
+	static char outbuf[10][STACK_SIZE];
+	static int cnt;
+
+	cnt = (cnt + 1) % 10;
+
+	memset(outbuf[cnt], ' ', UMAX(1, len * 5));
+
+	outbuf[cnt][len * 5] = 0;
+
+	return outbuf[cnt];
+}
+
+int cat_sprintf(char *dest, char *fmt, ...)
+{
+	char buf[STRING_SIZE];
+	int size;
+
+	va_list args;
+
+	va_start(args, fmt);
+	size = vsprintf(buf, fmt, args);
+	va_end(args);
+
+	strcat(dest, buf);
+
+	return size;
+}
+
+void ins_sprintf(char *dest, char *fmt, ...)
+{
+	char buf[STRING_SIZE], tmp[STRING_SIZE];
+
+	va_list args;
+
+	va_start(args, fmt);
+	vsprintf(buf, fmt, args);
+	va_end(args);
+
+	strcpy(tmp, dest);
+	strcpy(dest, buf);
+	strcat(dest, tmp);
+}
+
+int str_suffix(char *str1, char *str2)
+{
+	int len1, len2;
+
+	len1 = strlen(str1);
+	len2 = strlen(str2);
+
+	if (len1 >= len2)
+	{
+		if (!strcasecmp(str1 + len1 - len2, str2))
+		{
+			return FALSE;
+		}
+	}
+	return TRUE;
+}
+
+void socket_printf(struct session *ses, size_t length, char *format, ...)
+{
+	size_t size;
+
+	char buf[STRING_SIZE];
+	va_list args;
+
+	va_start(args, format);
+	size = vsprintf(buf, format, args);
+	va_end(args);
+
+	if (size != length && HAS_BIT(ses->telopts, TELOPT_FLAG_DEBUG))
+	{
+		tintin_printf(ses, "DEBUG TELNET: socket_printf size difference: %d vs %d", size, length);
+	}
+
+	if (HAS_BIT(ses->flags, SES_FLAG_CONNECTED))
+	{
+		write_line_mud(ses, buf, length);
+	}
+}
+
+void telnet_printf(struct session *ses, int length, char *format, ...)
+{
+	size_t size;
+
+	char buf[STRING_SIZE];
+	va_list args;
+
+	va_start(args, format);
+	size = vsprintf(buf, format, args);
+	va_end(args);
+
+	if (length != -1 && size != length && HAS_BIT(ses->telopts, TELOPT_FLAG_DEBUG))
+	{
+		tintin_printf(ses, "DEBUG TELNET: telnet_printf size difference: %d vs %d", size, length);
+	}
+
+	if (HAS_BIT(ses->flags, SES_FLAG_CONNECTED))
+	{
+		SET_BIT(ses->telopts, TELOPT_FLAG_TELNET);
+
+		write_line_mud(ses, buf, size);
+
+		DEL_BIT(ses->telopts, TELOPT_FLAG_TELNET);
+	}
+}

+ 1404 - 0
variable.c

@@ -0,0 +1,1404 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2019 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                (T)he K(I)cki(N) (T)ickin D(I)kumud Clie(N)t                 *
+*                                                                             *
+*                          coded by Bill Reiss 1993                           *
+*                     recoded by Igor van den Hoven 2004                      *
+******************************************************************************/
+
+#include "tintin.h"
+
+DO_COMMAND(do_variable)
+{
+	char arg1[BUFFER_SIZE], *str;
+	struct listroot *root = ses->list[LIST_VARIABLE];
+	struct listnode *node;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_NST, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		show_list(root, 0);
+	}
+	else if (*arg == 0)
+	{
+		node = search_nest_node(root, arg1);
+
+		if (node)
+		{
+			if (node->root)
+			{
+				char *str_result;
+
+				str_result = str_dup("");
+
+				view_nest_node(node, &str_result, 0, 1);
+
+				print_lines(ses, SUB_NONE, COLOR_TINTIN "%c" COLOR_COMMAND "%s " COLOR_BRACE "{" COLOR_STRING "%s" COLOR_BRACE "}\n{\n" COLOR_STRING "%s" COLOR_BRACE "}" COLOR_RESET "\n", gtd->tintin_char, list_table[LIST_VARIABLE].name, node->arg1, str_result);
+
+				str_free(str_result);
+			}
+			else
+			{
+				tintin_printf2(ses, COLOR_TINTIN "%c" COLOR_COMMAND "%s " COLOR_BRACE "{" COLOR_STRING "%s" COLOR_BRACE "} {" COLOR_STRING "%s" COLOR_BRACE "}" COLOR_RESET "\n", gtd->tintin_char, list_table[LIST_VARIABLE].name, node->arg1, node->arg2);
+			}
+		}
+		else if (show_node_with_wild(ses, arg1, ses->list[LIST_VARIABLE]) == FALSE)
+		{
+			show_message(ses, LIST_VARIABLE, "#VARIABLE: NO MATCH(ES) FOUND FOR {%s}.", arg1);
+		}
+	}
+	else
+	{
+		if (!valid_variable(ses, arg1))
+		{
+			show_message(ses, LIST_VARIABLE, "#VARIABLE: INVALID VARIALBE NAME {%s}.", arg1);
+			return ses;
+		}
+		str = str_alloc(UMAX(strlen(arg), BUFFER_SIZE));
+
+		arg = sub_arg_in_braces(ses, arg, str, GET_ALL, SUB_VAR|SUB_FUN);
+
+		node = set_nest_node(root, arg1, "%s", str);
+
+		while (*arg)
+		{
+			arg = sub_arg_in_braces(ses, arg, str, GET_ALL, SUB_VAR|SUB_FUN);
+
+			if (*str)
+			{
+				add_nest_node(root, arg1, "%s", str);
+			}
+		}
+
+		show_nest_node(node, &str, 1);
+
+		show_message(ses, LIST_VARIABLE, "#OK. VARIABLE {%s} HAS BEEN SET TO {%s}.", arg1, str);
+
+		str_free(str);
+	}
+	return ses;
+}
+
+DO_COMMAND(do_local)
+{
+	char arg1[BUFFER_SIZE], *str;
+	struct listroot *root;
+	struct listnode *node;
+
+	root = local_list(ses);
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_NST, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0)
+	{
+		show_list(root, 0);
+	}
+	else if (*arg1 && *arg == 0)
+	{
+		root = search_nest_base_ses(ses, arg1);
+
+		if (root)
+		{
+			node = search_nest_node_ses(ses, arg1);
+		}
+		else
+		{
+			root = local_list(ses);
+			node = NULL;
+		}
+
+		if (node)
+		{
+			show_node(root, node, 0);
+		}
+		else if (show_node_with_wild(ses, arg1, root) == FALSE)
+		{
+			show_message(ses, LIST_VARIABLE, "#LOCAL: NO MATCH(ES) FOUND FOR {%s}.", arg1);
+		}
+	}
+	else
+	{
+		str = str_alloc(UMAX(strlen(arg), BUFFER_SIZE));
+
+		arg = sub_arg_in_braces(ses, arg, str, GET_ALL, SUB_VAR|SUB_FUN);
+
+		node = set_nest_node_ses(ses, arg1, "%s", str);
+
+		while (*arg)
+		{
+			arg = sub_arg_in_braces(ses, arg, str, GET_ALL, SUB_VAR|SUB_FUN);
+
+			if (*str)
+			{
+				add_nest_node(root, arg1, "%s", str);
+			}
+		}
+
+		show_nest_node(node, &str, 1);
+
+		show_message(ses, LIST_VARIABLE, "#OK. LOCAL VARIABLE {%s} HAS BEEN SET TO {%s}.", arg1, str);
+
+		str_free(str);
+
+		SET_BIT(gtd->flags, TINTIN_FLAG_LOCAL);
+	}
+	return ses;
+}
+
+DO_COMMAND(do_unvariable)
+{
+	char arg1[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+
+	do
+	{
+		if (delete_nest_node(ses->list[LIST_VARIABLE], arg1))
+		{
+			show_message(ses, LIST_VARIABLE, "#OK. {%s} IS NO LONGER A VARIABLE.", arg1);
+		}
+		else
+		{
+			delete_node_with_wild(ses, LIST_VARIABLE, arg1);
+		}
+		arg = sub_arg_in_braces(ses, arg, arg1, GET_ALL, SUB_VAR|SUB_FUN);
+	}
+	while (*arg1);
+
+	return ses;
+}
+
+DO_COMMAND(do_cat)
+{
+	char arg1[BUFFER_SIZE], *str;
+	struct listnode *node;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_NST, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0 || *arg == 0)
+	{
+		show_error(ses, LIST_COMMAND, "#SYNTAX: CAT {<VARIABLE>} {<ARGUMENT>}");
+	}
+	else
+	{
+		str = str_alloc(UMAX(strlen(arg), BUFFER_SIZE));
+
+		if ((node = search_nest_node_ses(ses, arg1)) == NULL)
+		{
+			arg = sub_arg_in_braces(ses, arg, str, GET_ALL, SUB_VAR|SUB_FUN);
+
+			node = set_nest_node(ses->list[LIST_VARIABLE], arg1, "%s", str);
+		}
+
+		while (*arg)
+		{
+			arg = sub_arg_in_braces(ses, arg, str, GET_ALL, SUB_VAR|SUB_FUN);
+
+			check_all_events(ses, SUB_ARG, 1, 2, "VARIABLE UPDATE %s", arg1, arg1, str);
+
+			if (*str)
+			{
+				str_cat(&node->arg2, str);
+			}
+		}
+
+		check_all_events(ses, SUB_ARG, 1, 1, "VARIABLE UPDATED %s", arg1, arg1, str);
+
+		show_message(ses, LIST_VARIABLE, "#CAT: VARIABLE {%s} HAS BEEN SET TO {%s}.", arg1, node->arg2);
+
+		str_free(str);
+	}
+	return ses;
+}
+
+DO_COMMAND(do_replace)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], arg3[BUFFER_SIZE], tmp[BUFFER_SIZE], *pti, *ptm, *str;
+	struct listnode *node;
+
+	arg = sub_arg_in_braces(ses, arg, arg1, GET_NST, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg2, GET_ONE, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, arg3, GET_ALL, SUB_VAR|SUB_FUN);
+
+	if (*arg1 == 0 || *arg2 == 0)
+	{
+		show_error(ses, LIST_VARIABLE, "#SYNTAX: #REPLACE {VARIABLE} {OLD TEXT} {NEW TEXT}");
+
+		return ses;
+	}
+
+	if ((node = search_nest_node_ses(ses, arg1)) == NULL)
+	{
+		show_error(ses, LIST_VARIABLE, "#REPLACE: VARIABLE {%s} NOT FOUND.", arg1);
+
+		return ses;
+	}
+
+	if (tintin_regexp(ses, NULL, node->arg2, arg2, 0, REGEX_FLAG_CMD) == FALSE)
+	{
+		show_message(ses, LIST_VARIABLE, "#REPLACE: {%s} NOT FOUND IN {%s}.", arg2, node->arg2);
+	}
+	else
+	{
+		pti = node->arg2;
+		str = str_dup("");
+
+		do
+		{
+			if (*gtd->cmds[0] == 0) // Set by tintin_regexp
+			{
+				break;
+			}
+
+			ptm = strstr(pti, gtd->cmds[0]);
+
+			if (ptm == NULL)
+			{
+				break;
+			}
+
+			*ptm = 0;
+
+			substitute(ses, arg3, tmp, SUB_CMD);
+
+			str_cat_printf(&str, "%s%s", pti, tmp);
+
+			pti = ptm + strlen(gtd->cmds[0]);
+		}
+		while (tintin_regexp(ses, NULL, pti, arg2, 0, REGEX_FLAG_CMD));
+
+		str_cat(&str, pti);
+
+		str_cpy(&node->arg2, str);
+
+		str_free(str);
+	}
+	return ses;
+}
+
+int valid_variable(struct session *ses, char *arg)
+{
+	if (*arg == 0)
+	{
+		return FALSE;
+	}
+
+	if (is_math(ses, arg))
+	{
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+/*
+	support routines for #format
+*/
+
+unsigned long long generate_hash_key(char *str)
+{
+	unsigned long long len, h = 4321;
+
+	for (len = 0 ; *str != 0 ; str++, len++)
+	{
+		h = ((h << 5) + h) + *str;
+	}
+
+	h += len;
+
+	return h;
+}
+
+void numbertocharacter(struct session *ses, char *str)
+{
+	if (get_number(ses, str) < 256)
+	{
+		sprintf(str, "%c", (int) get_number(ses, str));
+	}
+	else if (HAS_BIT(ses->charset, CHARSET_FLAG_EUC))
+	{
+		sprintf(str, "%c%c", (unsigned int) get_number(ses, str) % 256, (unsigned int) get_number(ses, str) / 256);
+	}
+	else if (HAS_BIT(ses->charset, CHARSET_FLAG_UTF8))
+	{
+		unicode_to_utf8((int) get_number(ses, str), str);
+	}
+	else
+	{
+		sprintf(str, "%c", (int) get_number(ses, str));
+	}
+}
+
+void charactertonumber(struct session *ses, char *str)
+{
+	int result;
+
+	if (HAS_BIT(ses->charset, CHARSET_FLAG_EUC) && is_euc_head(ses, str))
+	{
+		if (get_euc_size(ses, str) == 4)
+		{
+			result = (unsigned char) str[0] + (unsigned char) str[1] * 256 + (unsigned char) str[2] * 256 * 256 + (unsigned char) str[3] * 256 * 256 * 256;
+		}
+		else
+		{
+			result = (unsigned char) str[0] + (unsigned char) str[1] * 256;
+		}
+	}
+	else if (HAS_BIT(ses->charset, CHARSET_FLAG_UTF8) && is_utf8_head(str))
+	{
+		get_utf8_index(str, &result);
+	}
+	else
+	{
+		result = (unsigned char) str[0];
+	}
+	sprintf(str, "%d", result);
+}
+
+void charactertohex(struct session *ses, char *str)
+{
+	if (HAS_BIT(ses->charset, CHARSET_FLAG_EUC) && is_euc_head(ses, str))
+	{
+		if (get_euc_size(ses, str) == 4)
+		{
+			sprintf(str, "%u", (unsigned char) str[0] + (unsigned char) str[1] * 256 + (unsigned char) str[2] * 256 * 256 + (unsigned char) str[3] * 256 * 256 * 256);
+		}
+		else
+		{
+			sprintf(str, "%u", (unsigned char) str[0] + (unsigned char) str[1] * 256);
+		}
+	}
+	else if (HAS_BIT(ses->charset, CHARSET_FLAG_UTF8) && is_utf8_head(str))
+	{
+		int result;
+
+		get_utf8_index(str, &result);
+
+		sprintf(str, "%u", result);
+	}
+	else if (!is_math(ses, str))
+	{
+		sprintf(str, "%u", (unsigned int) str[0]);
+	}
+}
+
+
+void colorstring(struct session *ses, char *str)
+{
+	char result[BUFFER_SIZE];
+
+	get_color_names(ses, str, result);
+
+	strcpy(str, result);
+}
+
+int translate_color_names(struct session *ses, char *string, char *result)
+{
+	int cnt;
+
+	*result = 0;
+
+	if (*string == '<')
+	{
+		strcpy(result, string);
+
+		return TRUE;
+	}
+
+	if (*string == '\\')
+	{
+		strcpy(result, string);
+
+		return TRUE;
+	}
+
+	while (*string)
+	{
+
+		if (isalpha(*string))
+		{
+			for (cnt = 0 ; *color_table[cnt].name ; cnt++)
+			{
+				if (!strncmp(color_table[cnt].name, string, color_table[cnt].len))
+				{
+					result += sprintf(result, "%s", color_table[cnt].code);
+
+					break;
+				}
+			}
+
+			if (*color_table[cnt].name == 0)
+			{
+				for (cnt = 0 ; *color_table[cnt].name ; cnt++)
+				{
+					if (!strncasecmp(color_table[cnt].name, string, color_table[cnt].len))
+					{
+						result += sprintf(result, "%s", color_table[cnt].code);
+
+						break;
+					}
+				}
+
+				if (*color_table[cnt].name == 0)
+				{
+					return FALSE;
+				}
+			}
+			string += strlen(color_table[cnt].name);
+		}
+
+		switch (*string)
+		{
+			case ' ':
+			case ';':
+			case ',':
+			case '{':
+			case '}':
+				string++;
+				break;
+
+			case 0:
+				return TRUE;
+
+			default:
+				return FALSE;
+		}
+	}
+	return TRUE;
+}
+
+int get_color_names(struct session *ses, char *string, char *result)
+{
+	int cnt;
+
+	*result = 0;
+
+	if (*string == '<')
+	{
+		substitute(ses, string, result, SUB_COL);
+
+		return TRUE;
+	}
+
+	if (*string == '\\')
+	{
+		substitute(ses, string, result, SUB_ESC);
+
+		return TRUE;
+	}
+
+	while (*string)
+	{
+		if (isalpha(*string))
+		{
+			for (cnt = 0 ; *color_table[cnt].name ; cnt++)
+			{
+				if (!strncmp(color_table[cnt].name, string, color_table[cnt].len))
+				{
+					substitute(ses, color_table[cnt].code, result, SUB_COL);
+
+					result += strlen(result);
+
+					break;
+				}
+			}
+
+			if (*color_table[cnt].name == 0)
+			{
+				for (cnt = 0 ; *color_table[cnt].name ; cnt++)
+				{
+					if (!strncasecmp(color_table[cnt].name, string, color_table[cnt].len))
+					{
+						substitute(ses, color_table[cnt].code, result, SUB_COL);
+
+						result += strlen(result);
+
+						break;
+					}
+				}
+
+				if (*color_table[cnt].name == 0)
+				{
+					return FALSE;
+				}
+			}
+			string += strlen(color_table[cnt].name);
+		}
+
+		switch (*string)
+		{
+			case ' ':
+			case ';':
+			case ',':
+			case '{':
+			case '}':
+				string++;
+				break;
+
+			case 0:
+				return TRUE;
+
+			default:
+				return FALSE;
+		}
+	}
+	return TRUE;
+}
+
+void headerstring(struct session *ses, char *str)
+{
+	char buf[BUFFER_SIZE], fill[BUFFER_SIZE];
+	int len, max;
+
+	len = string_raw_str_len(ses, str, 0, BUFFER_SIZE);
+	max =  get_scroll_cols(ses);
+
+	if (len > max - 2)
+	{
+		str[max] = 0;
+
+		return;
+	}
+
+	memset(fill, '#', max);
+
+	sprintf(buf, "%.*s%s%.*s%s", (max - len) / 2, fill, str, (max - len) / 2, fill, (max - len) % 2 ? "#" : "");
+
+	strcpy(str, buf);
+}
+
+void lowerstring(char *str)
+{
+	char *pts;
+
+	for (pts = str ; *pts ; pts++)
+	{
+		*pts = tolower((int) *pts);
+	}
+}
+
+void upperstring(char *str)
+{
+	char *pts;
+
+	for (pts = str ; *pts ; pts++)
+	{
+		*pts = toupper((int) *pts);
+	}
+}
+
+void hexstring(char *str)
+{
+	unsigned long long result = hex_number_64bit(str);
+
+	unicode_to_utf8(result, str);
+}
+
+void reversestring(char *str)
+{
+	char t;
+	int a = 0, z = strlen(str) - 1;
+
+	while (z > a)
+	{
+		t = str[z];
+		str[z--] = str[a];
+		str[a++] = t;
+	}
+
+	z = strlen(str) - 1;
+
+	for (a = 1 ; a < z ; a++)
+	{
+		if (str[a] == '\\' && str[a + 1] != '\\')
+		{
+			str[a] = str[a - 1];
+			str[a - 1] = '\\';
+		}
+	}
+
+}
+
+void mathstring(struct session *ses, char *str)
+{
+	get_number_string(ses, str, str);
+}
+
+void thousandgroupingstring(struct session *ses, char *str)
+{
+	char result[BUFFER_SIZE], strold[BUFFER_SIZE];
+	int cnt1, cnt2, cnt3, cnt4;
+
+	get_number_string(ses, str, strold);
+
+	cnt1 = strlen(strold);
+	cnt2 = BUFFER_SIZE / 2;
+	cnt4 = strchr(strold, '.') ? 1 : 0;
+
+	result[cnt2+1] = 0;
+
+	for (cnt3 = 0 ; cnt1 >= 0 ; cnt1--, cnt2--)
+	{
+		if (cnt3++ % 3 == 0 && cnt3 != 1 && cnt4 == 0 && isdigit((int) strold[cnt1]))
+		{
+			result[cnt2--] = ',';
+		}
+
+		result[cnt2] = strold[cnt1];
+
+		if (!isdigit((int) result[cnt2]))
+		{
+			cnt4 = 0;
+			cnt3 = 0;
+			continue;
+		}
+	}
+
+	strcpy(str, result + cnt2 + 1);
+}
+
+void chronosgroupingstring(struct session *ses, char *str)
+{
+	char *sign = "-";
+	long long val = (long long) get_number(ses, str);
+	int days, hours, minutes, seconds;
+
+	if (val < 0)
+	{
+		val *= -1;
+	}
+	else
+	{
+		sign = "";
+	}
+
+	seconds = val % 60;
+	val /= 60;
+
+	minutes = val % 60;
+	val /= 60;
+
+	hours = val % 24;
+	val /= 24;
+
+	days = val;
+
+	if (days)
+	{
+		sprintf(str, "%s%d:%02d:%02d:%02d", sign, days, hours, minutes, seconds);
+	}
+	else if (hours)
+	{
+		sprintf(str, "%s%d:%02d:%02d", sign, hours, minutes, seconds);
+	}
+	else
+	{
+		sprintf(str, "%s%d:%02d", sign, minutes, seconds);
+	}
+}
+
+void metricgroupingstring(struct session *ses, char *str)
+{
+	char big[]   = {' ', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y', '?', '?', '?', '?', '?', '?', '?', '?'};
+	char small[] = {' ', 'm', 'u', 'n', 'p', 'f', 'a', 'z', 'y', '?', '?', '?', '?', '?', '?', '?', '?'};
+	char tmp[NUMBER_SIZE];
+	long double val = get_number(ses, str);
+	int index = 0;
+
+	if (val >= 1000)
+	{
+                while (val >= 1000)
+                {
+                        val = val / 1000;
+                        index++;
+                }
+                if (val >= 100)
+                {
+                	sprintf(tmp, " %Lf", val);
+		}
+		else
+		{
+                	sprintf(tmp, "%Lf", val);
+		}
+                sprintf(str, "%.4s%c", tmp, big[index]);
+	}
+	else if (val > 0 && val < 0.01)
+	{
+		while (val < 0.01)
+		{
+			val = val * 1000;
+			index++;
+		}
+		sprintf(tmp, "%Lf", val);
+		sprintf(str, "%.4s%c", tmp, small[index]);
+	}
+	else if (val >= 0)
+	{
+		if (val >= 100)
+		{
+			sprintf(tmp, " %Lf", val);
+		}
+		else
+		{
+			sprintf(tmp, "%Lf", val);
+		}
+		sprintf(str, "%.4s%c", tmp, big[index]);
+	}
+	else if (val <= -0.01 && val > -1000)
+	{
+		if (val <= -100)
+		{
+			sprintf(tmp, " %Lf", val);
+		}
+		else
+		{
+			sprintf(tmp, "%Lf", val);
+		} 
+		sprintf(str, "%.5s%c", tmp, small[index]);
+	}
+	else if (val <= -1000)
+	{
+                while (val <= -100)
+                {
+                        if (val <= -10000)
+                        {
+                                val = (long double) ((long long) val / 100LL * 100LL);
+                        }
+                        val = val / 1000;
+                        index++;
+                }
+                sprintf(tmp, "%Lf", val);
+                sprintf(str, "%.5s%c", tmp, big[index]);
+	}
+
+	else if (val < 0 /*&& val > -0.01*/)
+	{
+		while (val > -0.01)
+		{
+			val = val * 1000;
+			index++;
+		}
+		sprintf(tmp, "%Lf", val);
+		sprintf(str, "%.5s%c", tmp, small[index]);
+	}
+}
+
+void stripspaces(char *str)
+{
+	int cnt;
+
+	for (cnt = strlen(str) - 1 ; cnt >= 0 ; cnt--)
+	{
+		if (!isspace((int) str[cnt]))
+		{
+			break;
+		}
+		str[cnt] = 0;
+	}
+
+	for (cnt = 0 ; str[cnt] != 0 ; cnt++)
+	{
+		if (!isspace((int) str[cnt]))
+		{
+			break;
+		}
+	}
+	memmove(str, &str[cnt], strlen(&str[cnt]) + 1);
+//	strcpy(str, &str[cnt]);
+}
+
+void wrapstring(struct session *ses, char *str, char *wrap)
+{
+	char  arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+	char *pts, *pte, *arg;
+	int cnt, width, height;
+
+	push_call("wrapstring(%p,%p,%p)",ses,str,wrap);
+
+	arg = sub_arg_in_braces(ses, str, arg1, GET_ALL, SUB_COL);
+
+	if (*arg == COMMAND_SEPARATOR)
+	{
+		arg++;
+	}
+
+	arg = get_arg_in_braces(ses, arg, arg2, GET_ALL);
+
+	if (*arg2)
+	{
+		cnt = get_number(ses, arg2);
+	}
+	else if (*wrap)
+	{
+		cnt = atoi(wrap);
+	}
+	else
+	{
+		cnt = get_scroll_cols(ses);
+	}
+
+	if (cnt <= 0)
+	{
+		cnt = get_scroll_cols(ses) + cnt;
+
+		if (cnt <= 0)
+		{
+			show_error(ses, LIST_VARIABLE, "#FORMAT %w: INVALID LENTGH {%s}", arg2);
+
+			pop_call();
+			return;
+		}
+	}
+
+	word_wrap_split(ses, arg1, arg2, cnt, 0, 0, 0, &height, &width);
+
+	pts = pte = arg2;
+
+	str[0] = cnt = 0;
+
+	while (*pte != 0)
+	{
+		if (*pte == '\n')
+		{
+			*pte++ = 0;
+
+			cat_sprintf(str, "{%d}{%s}", ++cnt, pts);
+
+			pts = pte;
+		}
+		else
+		{
+			pte++;
+		}
+	}
+	cat_sprintf(str, "{%d}{%s}", ++cnt, pts);
+
+	pop_call();
+	return;
+}
+
+int stringlength(struct session *ses, char *str)
+{
+	char temp[BUFFER_SIZE];
+
+	substitute(ses, str, temp, SUB_COL|SUB_ESC);
+
+	return strip_vt102_strlen(ses, temp);
+}
+
+
+// stripped range raw return
+
+int string_str_raw_len(struct session *ses, char *str, int start, int end)
+{
+	int raw_cnt, str_cnt, ret_cnt, tmp_cnt, tot_len, width, col_len;
+
+	raw_cnt = str_cnt = ret_cnt = 0;
+
+	tot_len = strlen(str);
+
+	while (raw_cnt < tot_len)
+	{
+		if (skip_vt102_codes(&str[raw_cnt]))
+		{
+			ret_cnt += (str_cnt >= start) ? skip_vt102_codes(&str[raw_cnt]) : 0;
+			raw_cnt += skip_vt102_codes(&str[raw_cnt]);
+
+			continue;
+		}
+
+		col_len = is_color_code(&str[raw_cnt]);
+		
+		if (col_len)
+		{
+			ret_cnt += (str_cnt >= start) ? col_len : 0;
+			raw_cnt += col_len;
+
+			continue;
+		}
+
+		if (str_cnt >= end)
+		{
+			break;
+		}
+
+		if (str[raw_cnt] == '\\')
+		{
+			ret_cnt += (str_cnt >= start) ? 1 : 0;
+			raw_cnt++;
+			
+			if (str[raw_cnt] == '\\')
+			{
+				ret_cnt += (str_cnt >= start) ? 1 : 0;
+				raw_cnt++;
+				str_cnt++;
+			}
+			continue;
+		}
+
+		if (HAS_BIT(ses->charset, CHARSET_FLAG_UTF8) && is_utf8_head(&str[raw_cnt]))
+		{
+			tmp_cnt = get_utf8_width(&str[raw_cnt], &width);
+
+			if (str_cnt >= start)
+			{
+				ret_cnt += tmp_cnt;
+			}
+			raw_cnt += tmp_cnt;
+			str_cnt += width;
+		}
+		else
+		{
+			ret_cnt += (str_cnt >= start) ? 1 : 0;
+			raw_cnt++;
+			str_cnt++;
+		}
+	}
+	return ret_cnt;
+}
+
+// raw range stripped return
+
+int string_raw_str_len(struct session *ses, char *str, int raw_start, int raw_end)
+{
+	int raw_cnt, ret_cnt, tot_len, width, col_len;
+
+	raw_cnt = raw_start;
+	ret_cnt = 0;
+	tot_len = strlen(str);
+
+	while (raw_cnt < tot_len)
+	{
+		if (raw_cnt >= raw_end)
+		{
+			break;
+		}
+
+		if (skip_vt102_codes(&str[raw_cnt]))
+		{
+			raw_cnt += skip_vt102_codes(&str[raw_cnt]);
+
+			continue;
+		}
+
+		col_len = is_color_code(&str[raw_cnt]);
+		
+		if (col_len)
+		{
+			raw_cnt += col_len;
+
+			continue;
+		}
+
+		if (str[raw_cnt] == '\\')
+		{
+			raw_cnt++;
+
+			if (str[raw_cnt] == '\\')
+			{
+				raw_cnt++;
+				ret_cnt++;
+			}
+			continue;
+		}
+
+		if (HAS_BIT(gtd->ses->charset, CHARSET_FLAG_EUC)  && is_euc_head(ses, &str[raw_cnt]))
+		{
+			raw_cnt += get_euc_width(ses, &str[raw_cnt], &width);
+
+			ret_cnt += width;
+		}
+		else if (HAS_BIT(gtd->ses->charset, CHARSET_FLAG_UTF8) && is_utf8_head(&str[raw_cnt]))
+		{
+			raw_cnt += get_utf8_width(&str[raw_cnt], &width);
+
+			ret_cnt += width;
+		}
+		else
+		{
+			raw_cnt++;
+			ret_cnt++;
+		}
+	}
+	return ret_cnt;
+}
+
+void timestring(struct session *ses, char *str)
+{
+	char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE], *arg;
+
+	struct tm timeval_tm;
+	time_t    timeval_t;
+
+	arg = get_arg_in_braces(ses, str, arg1, GET_ALL);
+
+	if (*arg == COMMAND_SEPARATOR)
+	{
+		arg++;
+	}
+	arg = get_arg_in_braces(ses, arg, arg2, GET_ALL);
+
+	if (*arg2)
+	{
+		timeval_t = (time_t) get_number(ses, arg2);
+	}
+	else
+	{
+		timeval_t = gtd->time;
+	}
+
+	timeval_tm = *localtime(&timeval_t);
+
+	strftime(str, BUFFER_SIZE, arg1, &timeval_tm);
+}
+
+void justify_string(struct session *ses, char *in, char *out, int align, int cut)
+{
+	char temp[BUFFER_SIZE];
+
+	if (align < 0)
+	{
+		sprintf(temp, "%%%d.%ds", align - ((int) strlen(in) - string_raw_str_len(ses, in, 0, BUFFER_SIZE)), string_str_raw_len(ses, in, 0, cut));
+	}
+	else
+	{
+		sprintf(temp, "%%%d.%ds", align + ((int) strlen(in) - string_raw_str_len(ses, in, 0, BUFFER_SIZE)), string_str_raw_len(ses, in, 0, cut));
+	}
+
+	sprintf(out, temp, in);
+}
+
+void format_string(struct session *ses, char *format, char *arg, char *out)
+{
+	char argformat[BUFFER_SIZE], newformat[BUFFER_SIZE], arglist[30][20000], *ptf, *ptt, *pts, *ptn;
+	struct tm timeval_tm;
+	time_t    timeval_t;
+	int i;
+
+	for (i = 0 ; i < 30 ; i++)
+	{
+		arg = sub_arg_in_braces(ses, arg, arglist[i], GET_ONE, SUB_VAR|SUB_FUN);
+	}
+
+	i = 0;
+
+	ptf = format;
+	ptn = newformat;
+
+	while (*ptf)
+	{
+		if (i == 30)
+		{
+			break;
+		}
+
+		if (*ptf == '%')
+		{
+			pts = ptn;
+
+			*ptn++ = *ptf++;
+
+			if (*ptf == 0)
+			{
+				*ptn++ = '%';
+				break;
+			}
+			else if (*ptf == '%')
+			{
+				*ptn++ = *ptf++;
+			}
+			else if (*ptf == ' ')
+			{
+				*ptn++ = '%';
+			}
+			else
+			{
+				while (!isalpha((int) *ptf))
+				{
+					if (*ptf == 0)
+					{
+						break;
+					}
+					*ptn++ = *ptf++;
+				}
+
+				*ptn = 0;
+
+				if (*ptf == 0)
+				{
+					show_error(ses, LIST_VARIABLE, "#FORMAT STRING: UNKNOWN ARGUMENT {%s}.", pts);
+					continue;
+				}
+
+				if (*ptf == 'd' || *ptf == 'f' || *ptf == 'X')
+				{
+					strcpy(argformat, pts);
+
+					ptn = pts + 1;
+					*ptn = 0;
+				}
+				else if (*ptf == 'w')
+				{
+					strcpy(argformat, pts+1);
+					ptn = pts + 1;
+					*ptn = 0;
+				}
+				else if (pts[1])
+				{
+					char arg1[BUFFER_SIZE], arg2[BUFFER_SIZE];
+
+					ptt = arg1;
+					ptn = pts + 1;
+
+					while (*ptn && *ptn != '.')
+					{
+						*ptt++ = *ptn++;
+					}
+
+					*ptt = 0;
+
+					if (*ptn == 0)
+					{
+						if (atoi(arg1) < 0)
+						{
+							sprintf(argformat, "%%%d", atoi(arg1) - ((int) strlen(arglist[i]) - string_raw_str_len(ses, arglist[i], 0, BUFFER_SIZE)));
+						}
+						else
+						{
+							sprintf(argformat, "%%%d", atoi(arg1) + ((int) strlen(arglist[i]) - string_raw_str_len(ses, arglist[i], 0, BUFFER_SIZE)));
+						}
+					}
+					else
+					{
+						ptt = arg2;
+						ptn = ptn + 1;
+
+						while (*ptn)
+						{
+							*ptt++ = *ptn++;
+						}
+
+						*ptt = 0;
+
+						if (atoi(arg1) < 0)
+						{
+							sprintf(argformat, "%%%d.%d",
+								atoi(arg1) - ((int) strlen(arglist[i]) - string_raw_str_len(ses, arglist[i], 0, BUFFER_SIZE)),
+								string_str_raw_len(ses, arglist[i], 0, atoi(arg2)));
+						}
+						else
+						{
+							sprintf(argformat, "%%%d.%d",
+								atoi(arg1) + ((int) strlen(arglist[i]) - string_raw_str_len(ses, arglist[i], 0, BUFFER_SIZE)),
+								string_str_raw_len(ses, arglist[i], 0, atoi(arg2)));
+						}
+					}
+
+					ptt = argformat;
+					ptn = pts;
+
+					while (*ptt)
+					{
+						*ptn++ = *ptt++;
+					}
+
+					*ptn = 0;
+				}
+
+				switch (*ptf)
+				{
+					case 'a':
+						numbertocharacter(ses, arglist[i]);
+//						sprintf(arglist[i], "%c", (char) get_number(ses, arglist[i]));
+						break;
+
+					case 'c':
+						colorstring(ses, arglist[i]);
+						break;
+
+					case 'd':
+						strcat(argformat, "lld");
+						sprintf(arglist[i], argformat, (long long) get_number(ses, arglist[i]));
+						break;
+
+					case 'f':
+						strcat(argformat, "Lf");
+						sprintf(arglist[i], argformat, get_double(ses, arglist[i]));
+						break;
+
+					case 'g':
+						thousandgroupingstring(ses, arglist[i]);
+						break;
+
+					case 'h':
+						headerstring(ses, arglist[i]);
+						break;
+
+					case 'l':
+						lowerstring(arglist[i]);
+						break;
+
+					case 'm':
+						mathstring(ses, arglist[i]);
+						break;
+
+					case 'n':
+						arglist[i][0] = toupper((int) arglist[i][0]);
+						break;
+
+					case 'p':
+						stripspaces(arglist[i]);
+						break;
+
+					case 'r':
+						reversestring(arglist[i]);
+						break;
+
+					case 's':
+						break;
+
+					case 't':
+						timestring(ses, arglist[i]);
+						break;
+
+					case 'u':
+						upperstring(arglist[i]);
+						break;
+
+					case 'w':
+						substitute(ses, arglist[i], arglist[i], SUB_VAR|SUB_FUN);
+						wrapstring(ses, arglist[i], argformat);
+						break;
+
+					case 'x':
+						hexstring(arglist[i]);
+						break;
+
+					case 'A':
+						charactertonumber(ses, arglist[i]);
+						break;
+
+					case 'C':
+						chronosgroupingstring(ses, arglist[i]);
+						break;
+
+					case 'D':
+						sprintf(arglist[i], "%llu", hex_number_64bit(arglist[i]));
+
+						break;
+
+/*					case 'D':
+						timeval_t  = (time_t) *arglist[i] ? atoll(arglist[i]) : gtd->time;
+						timeval_tm = *localtime(&timeval_t);
+						strftime(arglist[i], BUFFER_SIZE, "%d", &timeval_tm);
+						break;
+*/
+					case 'G':
+						thousandgroupingstring(ses, arglist[i]);
+						break;
+
+					case 'H':
+						sprintf(arglist[i], "%llu", generate_hash_key(arglist[i]));
+						break;
+
+					case 'L':
+						sprintf(arglist[i], "%d", stringlength(ses, arglist[i]));
+						break;
+
+					case 'M':
+						metricgroupingstring(ses, arglist[i]);
+						break;
+
+					case 'R':
+						tintin_printf2(ses, "\e[1;31m#echo/#format %%R please use #screen {get} {rows} to get screen height.");
+						sprintf(arglist[i], "%d", gtd->screen->rows);
+						break;
+
+					case 'S':
+						sprintf(arglist[i], "%d", spellcheck_count(ses, arglist[i]));
+						break;
+
+					case 'T':
+						sprintf(arglist[i], "%ld", gtd->time);
+						break;
+
+					case 'U':
+						sprintf(arglist[i], "%lld", utime());
+						break;
+
+					case 'X':
+						strcat(argformat, "llX");
+						charactertohex(ses, arglist[i]);
+						sprintf(arglist[i], argformat, (unsigned long long) get_number(ses, arglist[i]));
+						break;
+
+					// undocumented
+					case 'Y': // print the year, experimental
+						timeval_t  = (time_t) *arglist[i] ? atoll(arglist[i]) : gtd->time;
+						timeval_tm = *localtime(&timeval_t);
+						strftime(arglist[i], BUFFER_SIZE, "%Y", &timeval_tm);
+						break;
+
+					default:
+						show_error(ses, LIST_VARIABLE, "#FORMAT STRING: UNKNOWN ARGUMENT {%s%c}.", pts, *ptf);
+						break;
+				}
+				*ptn++ = 's';
+				i++;
+				ptf++;
+			}
+		}
+		else
+		{
+			*ptn++ = *ptf++;
+		}
+	}
+	*ptn = 0;
+
+	snprintf(out, BUFFER_SIZE - 1, newformat, arglist[0], arglist[1], arglist[2], arglist[3], arglist[4], arglist[5], arglist[6], arglist[7], arglist[8], arglist[9], arglist[10], arglist[11], arglist[12], arglist[13], arglist[14], arglist[15], arglist[16], arglist[17], arglist[18], arglist[19], arglist[20], arglist[21], arglist[22], arglist[23], arglist[24], arglist[25], arglist[26], arglist[27], arglist[28], arglist[29]);
+
+	return;
+}
+
+DO_COMMAND(do_format)
+{
+	char destvar[BUFFER_SIZE], format[BUFFER_SIZE], result[BUFFER_SIZE];
+
+	arg = sub_arg_in_braces(ses, arg, destvar,  GET_NST, SUB_VAR|SUB_FUN);
+	arg = sub_arg_in_braces(ses, arg, format,   GET_ONE, SUB_VAR|SUB_FUN);
+
+	if (*destvar == 0)
+	{
+		show_error(ses, LIST_VARIABLE, "#SYNTAX: #format {variable} {format} {arg1} {arg2}");
+
+		return ses;
+	}
+
+	format_string(ses, format, arg, result);
+
+	set_nest_node_ses(ses, destvar, "%s", result);
+
+	show_message(ses, LIST_VARIABLE, "#OK. VARIABLE {%s} HAS BEEN SET TO {%s}.", destvar, result);
+
+	return ses;
+}

+ 1195 - 0
vt102.c

@@ -0,0 +1,1195 @@
+/******************************************************************************
+*   This file is part of TinTin++                                             *
+*                                                                             *
+*   Copyright 2004-2020 Igor van den Hoven                                    *
+*                                                                             *
+*   TinTin++ is free software; you can redistribute it and/or modify          *
+*   it under the terms of the GNU General Public License as published by      *
+*   the Free Software Foundation; either version 3 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 General Public License for more details.                              *
+*                                                                             *
+*   You should have received a copy of the GNU General Public License         *
+*   along with TinTin++.  If not, see https://www.gnu.org/licenses.           *
+******************************************************************************/
+
+/******************************************************************************
+*                               T I N T I N + +                               *
+*                                                                             *
+*                      coded by Igor van den Hoven 2004                       *
+******************************************************************************/
+
+#include "tintin.h"
+
+void init_pos(struct session *ses, int row, int col)
+{
+	push_call("init_pos(%p)",ses);
+
+	goto_pos(ses, row, col);
+
+	gtd->screen->sav_row[0] = ses->cur_row;
+	gtd->screen->sav_col[0] = ses->cur_col;
+
+	gtd->screen->sav_lev = 1;
+
+	pop_call();
+	return;
+}
+
+void save_pos(struct session *ses)
+{
+	push_call("save_pos(%p)",ses);
+
+	gtd->screen->sav_row[gtd->screen->sav_lev] = ses->cur_row;
+	gtd->screen->sav_col[gtd->screen->sav_lev] = ses->cur_col;
+
+	if (gtd->screen->sav_lev < STACK_SIZE)
+	{
+		gtd->screen->sav_lev++;
+	}
+	else
+	{
+		syserr_printf(ses, "sav_lev++ above 1000.");
+	}
+
+	if (!HAS_BIT(gtd->flags, TINTIN_FLAG_HIDDENCURSOR))
+	{
+		print_stdout("\e[?25l");
+	}
+
+	pop_call();
+	return;
+}
+
+void restore_pos(struct session *ses)
+{
+	if (gtd->screen->sav_lev > 0)
+	{
+		gtd->screen->sav_lev--;
+	}
+	else
+	{
+		gtd->screen->sav_lev = 0;
+		syserr_printf(ses, "sav_lev-- below 0.");
+	}
+
+	if (gtd->screen->sav_row[gtd->screen->sav_lev] == gtd->screen->rows)
+	{
+		goto_pos(ses, gtd->screen->rows, inputline_cur_pos());
+	}
+	else
+	{
+		goto_pos(ses, gtd->screen->sav_row[gtd->screen->sav_lev], gtd->screen->sav_col[gtd->screen->sav_lev]);
+	}
+
+	if (!HAS_BIT(gtd->flags, TINTIN_FLAG_HIDDENCURSOR))
+	{
+		print_stdout("\e[?25h");
+	}
+
+	SET_BIT(gtd->flags, TINTIN_FLAG_FLUSH);
+}
+
+void goto_pos(struct session *ses, int row, int col)
+{
+	print_stdout("\e[%d;%dH", row, col);
+
+	if (col < 1)
+	{
+		print_stdout("debug: goto_pos(%d,%d)\n",row,col);
+	}
+	ses->cur_row = row;
+	ses->cur_col = col;
+}
+
+void erase_cols(int cnt)
+{
+	if (cnt)
+	{
+		print_stdout("\e[%dX", cnt);
+	}
+}
+
+void erase_row(struct session *ses)
+{
+	if (ses->wrap == gtd->screen->cols || ses->cur_row == gtd->screen->rows)
+	{
+		print_stdout("\e[2K");
+	}
+	else
+	{
+		save_pos(ses);
+
+		goto_pos(ses, ses->cur_row, ses->split->top_col);
+
+		erase_cols(ses->wrap);
+
+		restore_pos(ses);
+	}
+}
+
+void erase_lines(struct session *ses, int rows)
+{
+	print_stdout("\e[%dM", rows);
+}
+
+void erase_toeol(void)
+{
+	print_stdout("\e[K");
+}
+
+/*
+	unused
+*/
+
+void reset(struct session *ses)
+{
+	ses->cur_row = 1;
+	ses->cur_col = 1;
+
+	print_stdout("\ec");
+}
+
+
+void scroll_region(struct session *ses, int top, int bot)
+{
+	push_call("scroll_region(%p,%d,%d)",ses,top,bot);
+
+	print_stdout("\e[%d;%dr", top, bot);
+
+	ses->split->top_row = top;
+	ses->split->bot_row = bot;
+
+	check_all_events(ses, SUB_ARG, 0, 4, "VT100 SCROLL REGION", ntos(top), ntos(bot), ntos(gtd->screen->rows), ntos(gtd->screen->cols), ntos(get_scroll_cols(ses)));
+
+	pop_call();
+	return;
+}
+
+void reset_scroll_region(struct session *ses)
+{
+	if (ses == gtd->ses)
+	{
+		print_stdout("\e[r");
+	}
+	ses->split->top_row = 1;
+	ses->split->top_col = 1;
+	ses->split->bot_row = gtd->screen->rows;
+	ses->split->bot_col = gtd->screen->cols;
+}
+
+
+int skip_vt102_codes(char *str)
+{
+	int skip = 0;
+
+	push_call("skip_vt102_codes(%p)",str);
+
+	switch (str[0])
+	{
+		case   5:   /* ENQ */
+		case   7:   /* BEL */
+		case   8:   /* BS  */
+	//	case   9:      HT
+	//	case  10:      LF
+		case  11:   /* VT  */
+		case  12:   /* FF  */
+		case  13:   /* CR  */
+		case  14:   /* SO  */
+		case  15:   /* SI  */
+		case  17:   /* DC1 */
+		case  19:   /* DC3 */
+		case  24:   /* CAN */
+		case  26:   /* SUB */
+			pop_call();
+			return 1;
+
+		case  27:   /* ESC */
+			break;
+
+		case  28: // HTML_OPEN
+			for (skip = 1 ; str[skip] ; skip++)
+			{
+				if (str[skip] == 30) // HTML_CLOSE
+				{
+					return skip + 1;
+				}
+			}
+			return 0;
+
+		case 127:   /* DEL */
+			pop_call();
+			return 1;
+
+		default:
+			pop_call();
+			return 0;
+	}
+
+	switch (str[1])
+	{
+		case '\0':
+			pop_call();
+			return 1;
+
+		case '%':
+		case '#':
+		case '(':
+		case ')':
+			pop_call();
+			return str[2] ? 3 : 2;
+
+		case ']':
+			switch (str[2])
+			{
+				case 0:
+					pop_call();
+					return 2;
+
+				case 'P':
+					for (skip = 3 ; skip < 10 ; skip++)
+					{
+						if (str[skip] == 0)
+						{
+							break;
+						}
+					}
+					pop_call();
+					return skip;
+
+				case 'R':
+					pop_call();
+					return str[3] ? 3 : 2;
+			}
+			pop_call();
+			return 2;
+
+		case '[':
+			break;
+
+		default:
+			pop_call();
+			return 2;
+	}
+
+	for (skip = 2 ; str[skip] != 0 ; skip++)
+	{
+		if (isalpha((int) str[skip]))
+		{
+			pop_call();
+			return skip + 1;
+		}
+
+		switch (str[skip])
+		{
+			case '@':
+			case '`':
+			case ']':
+				pop_call();
+				return skip + 1;
+		}
+	}
+	pop_call();
+	return skip;
+}
+
+int find_color_code(char *str)
+{
+	int skip;
+
+	switch (str[0])
+	{
+		case  27:   /* ESC */
+			break;
+
+		default:
+			return 0;
+	}
+
+	switch (str[1])
+	{
+		case '[':
+			break;
+
+		default:
+			return 0;
+	}
+
+	for (skip = 2 ; str[skip] != 0 ; skip++)
+	{
+		switch (str[skip])
+		{
+			case 'm':
+				return skip + 1;
+			case '@':
+			case '`':
+			case ']':
+				return 0;
+		}
+
+		if (isalpha((int) str[skip]))
+		{
+			return 0;
+		}
+	}
+	return 0;
+}
+
+int find_secure_color_code(char *str)
+{
+	int skip, valid = 1;
+
+	switch (str[0])
+	{
+		case  27:   /* ESC */
+			break;
+
+		default:
+			return 0;
+	}
+
+	switch (str[1])
+	{
+		case '[':
+			break;
+
+		default:
+			return 0;
+	}
+
+	for (skip = 2 ; str[skip] != 0 ; skip++)
+	{
+		switch (str[skip])
+		{
+			case 'm':
+				if (valid)
+				{
+					return skip + 1;
+				}
+				return 0;
+
+			case ';':
+				valid = 0;
+				break;
+
+			case '0':
+			case '1':
+			case '2':
+			case '3':
+			case '4':
+			case '5':
+			case '6':
+			case '7':
+			case '8':
+			case '9':
+				valid = 1;
+				break;
+
+			default:
+				return 0;
+		}
+	}
+	return 0;
+}
+
+
+int skip_vt102_codes_non_graph(char *str)
+{
+	int skip = 0;
+
+	switch (str[skip])
+	{
+		case   5:   /* ENQ */
+		case   7:   /* BEL */
+	/*	case   8: *//* BS  */
+	/*	case   9: *//* HT  */
+	/*	case  10: *//* LF  */
+		case  11:   /* VT  */
+		case  12:   /* FF  */
+		case  13:   /* CR  */
+		case  14:   /* SO  */
+		case  15:   /* SI  */
+		case  17:   /* DC1 */
+		case  19:   /* DC3 */
+		case  24:   /* CAN */
+		case  26:   /* SUB */
+		case 127:   /* DEL */
+			return 1;
+
+		case  27:   /* ESC */
+			break;
+
+		default:
+			return 0;
+	}
+
+	switch (str[1])
+	{
+		case '\0':
+			return 0;
+
+		case 'c':
+		case 'D':
+		case 'E':
+		case 'H':
+		case 'M':
+		case 'Z':
+		case '7':
+		case '8':
+		case '>':
+		case '=':
+			return 2;
+
+		case '%':
+		case '#':
+		case '(':
+		case ')':
+			return str[2] ? 3 : 2;
+
+		case ']':
+			switch (str[2])
+			{
+				case 'P':
+					for (skip = 3 ; skip < 10 ; skip++)
+					{
+						if (str[skip] == 0)
+						{
+							break;
+						}
+					}
+					return skip;
+				case 'R':
+					return 3;
+			}
+			return 2;
+
+		case '[':
+			break;
+
+		default:
+			return 2;
+	}
+
+	for (skip = 2 ; str[skip] != 0 ; skip++)
+	{
+		switch (str[skip])
+		{
+			case 'm':
+				return 0;
+			case '@':
+			case '`':
+			case ']':
+				return skip + 1;
+		}
+
+		if (isalpha((int) str[skip]))
+		{
+			return skip + 1;
+		}
+	}
+	return 0;
+}
+
+void strip_vt102_codes(char *str, char *buf)
+{
+	char *pti, *pto;
+
+	pti = (char *) str;
+	pto = (char *) buf;
+
+	while (*pti)
+	{
+		while (skip_vt102_codes(pti))
+		{
+			pti += skip_vt102_codes(pti);
+		}
+
+		if (*pti)
+		{
+			*pto++ = *pti++;
+		}
+	}
+	*pto = 0;
+}
+
+
+void strip_vt102_codes_non_graph(char *str, char *buf)
+{
+	char *pti, *pto;
+
+	pti = str;
+	pto = buf;
+
+	while (*pti)
+	{
+		while (skip_vt102_codes_non_graph(pti))
+		{
+			pti += skip_vt102_codes_non_graph(pti);
+		}
+
+		if (*pti)
+		{
+			*pto++ = *pti++;
+		}
+	}
+	*pto = 0;
+}
+
+void strip_non_vt102_codes(char *str, char *buf)
+{
+	char *pti, *pto;
+	int len;
+
+	pti = str;
+	pto = buf;
+
+	while (*pti)
+	{
+		while ((len = skip_vt102_codes(pti)) != 0)
+		{
+			memcpy(pto, pti, len);
+			pti += len;
+			pto += len;
+		}
+
+		if (*pti)
+		{
+			pti++;
+		}
+	}
+	*pto = 0;
+}
+
+char *strip_vt102_strstr(char *str, char *buf, int *len)
+{ 
+	char *pti, *ptm, *pts;
+
+	push_call("strip_vt102_strstr(%p,%p,%p)",str,buf,len);
+
+	pts = str;
+
+	while (*pts)
+	{
+		pti = pts;
+		ptm = buf;
+
+		while (*pti)
+		{
+			if (*pti++ != *ptm++)
+			{
+				break;
+			}
+
+			if (*ptm == 0)
+			{
+				if (len)
+				{
+					*len = pti - pts;
+				}
+				pop_call();
+				return pts;
+			}
+
+			while (skip_vt102_codes(pti))
+			{
+				pti += skip_vt102_codes(pti);
+			}
+		}
+		pts++;
+	}
+	pop_call();
+	return NULL;
+}
+
+// mix old and str, then copy compressed color string to buf which can point to old.
+
+void get_color_codes(char *old, char *str, char *buf, int flags)
+{
+	char *pti, *pto, col[100], tmp[BUFFER_SIZE];
+	int len, vtc, fgc, bgc, cnt;
+	int rgb[6] = { 0, 0, 0, 0, 0, 0 };
+
+	push_call("get_color_codes(%p,%p,%p,%d)",old,str,buf,flags);
+
+	pto = tmp;
+
+	pti = old;
+
+	while (*pti)
+	{
+		while ((len = find_color_code(pti)) != 0)
+		{
+			memcpy(pto, pti, len);
+			pti += len;
+			pto += len;
+		}
+
+		if (*pti)
+		{
+			pti++;
+		}
+	}
+
+	pti = str;
+
+	while (*pti)
+	{
+		while ((len = find_color_code(pti)) != 0)
+		{
+			memcpy(pto, pti, len);
+			pti += len;
+			pto += len;
+		}
+
+		if (!HAS_BIT(flags, GET_ALL))
+		{
+			break;
+		}
+
+		if (*pti == '\n')
+		{
+			break;
+		}
+
+		if (*pti)
+		{
+			pti++;
+		}
+	}
+
+	*pto = 0;
+
+	if (strlen(tmp) == 0)
+	{
+		buf[0] = 0;
+		pop_call();
+		return;
+	}
+
+	vtc =  0;
+	fgc = -1;
+	bgc = -1;
+
+	pti = tmp;
+
+	while (*pti)
+	{
+		switch (*pti)
+		{
+			case 27:
+				pti += 2;
+
+				if (pti[-1] == 'm')
+				{
+					vtc =  0;
+					fgc = -1;
+					bgc = -1;
+					break;
+				}
+
+				for (cnt = 0 ; pti[cnt] ; cnt++)
+				{
+					col[cnt] = pti[cnt];
+
+					if (pti[cnt] == ';' || pti[cnt] == 'm')
+					{
+						col[cnt] = 0;
+
+						cnt = -1;
+						pti += 1 + strlen(col);
+
+						if (HAS_BIT(vtc, COL_XTF_R))
+						{
+							fgc = URANGE(0, atoi(col), 255);
+							DEL_BIT(vtc, COL_XTF_5|COL_XTF_R);
+							SET_BIT(vtc, COL_XTF);
+						}
+						else if (HAS_BIT(vtc, COL_XTB_R))
+						{
+							bgc = URANGE(0, atoi(col), 255);
+							DEL_BIT(vtc, COL_XTB_5|COL_XTB_R);
+							SET_BIT(vtc, COL_XTB);
+						}
+						else if (HAS_BIT(vtc, COL_TCF_R))
+						{
+							if (rgb[0] == 256)
+							{
+								rgb[0] = URANGE(0, atoi(col), 255);
+							}
+							else if (rgb[1] == 256)
+							{
+								rgb[1] = URANGE(0, atoi(col), 255);
+							}
+							else
+							{
+								rgb[2] = URANGE(0, atoi(col), 255);
+
+								fgc = rgb[0] * 256 * 256 + rgb[1] * 256 + rgb[2];
+								
+								DEL_BIT(vtc, COL_TCF_2|COL_TCF_R);
+								SET_BIT(vtc, COL_TCB);
+							}
+						}
+						else if (HAS_BIT(vtc, COL_TCB_R))
+						{
+							if (rgb[3] == 256)
+							{
+								rgb[3] = URANGE(0, atoi(col), 255);
+							}
+							else if (rgb[4] == 256)
+							{
+								rgb[4] = URANGE(0, atoi(col), 255);
+							}
+							else
+							{
+								rgb[5] = URANGE(0, atoi(col), 255);
+
+								fgc = rgb[3] * 256 * 256 + rgb[4] * 256 + rgb[5];
+								
+								DEL_BIT(vtc, COL_TCB_2|COL_TCF_R);
+								SET_BIT(vtc, COL_TCB);
+							}
+						}
+						else
+						{
+							switch (atoi(col))
+							{
+								case 0:
+									vtc = 0;
+									fgc = -1;
+									bgc = -1;
+									break;
+								case 1:
+									SET_BIT(vtc, COL_BLD);
+									break;
+								case 2:
+									if (HAS_BIT(vtc, COL_TCF_2))
+									{
+										rgb[0] = rgb[1] = rgb[2] = 256;
+										SET_BIT(vtc, COL_TCF_R);
+									}
+									else if (HAS_BIT(vtc, COL_TCB_2))
+									{
+										rgb[3] = rgb[4] = rgb[5] = 256;
+										SET_BIT(vtc, COL_TCB_R);
+									}
+									else
+									{
+										DEL_BIT(vtc, COL_BLD);
+									}
+									break;
+								case 4:
+									SET_BIT(vtc, COL_UND);
+									break;
+								case 5:
+									if (HAS_BIT(vtc, COL_XTF_5))
+									{
+										SET_BIT(vtc, COL_XTF_R);
+									}
+									else if (HAS_BIT(vtc, COL_XTB_5))
+									{
+										SET_BIT(vtc, COL_XTB_R);
+									}
+									else
+									{
+										SET_BIT(vtc, COL_BLK);
+									}
+									break;
+								case 7:
+									SET_BIT(vtc, COL_REV);
+									break;
+								case 21:
+								case 22:
+									DEL_BIT(vtc, COL_BLD);
+									break;
+								case 24:
+									DEL_BIT(vtc, COL_UND);
+									break;
+								case 25:
+									DEL_BIT(vtc, COL_BLK);
+									break;
+								case 27:
+									DEL_BIT(vtc, COL_REV);
+									break;
+								case 38:
+									SET_BIT(vtc, COL_XTF_5|COL_TCF_2);
+									fgc = -1;
+									break;
+								case 48:
+									SET_BIT(vtc, COL_XTB_5|COL_TCB_2);
+									bgc = -1;
+									break;
+								default:
+									switch (atoi(col) / 10)
+									{
+										case 3:
+										case 10:
+											fgc = atoi(col);
+											DEL_BIT(vtc, COL_XTF|COL_TCF);
+											break;
+										
+										case 4:
+										case 9:
+											bgc = atoi(col);
+											DEL_BIT(vtc, COL_XTB|COL_TCB);
+											break;
+									}
+									break;
+							}
+						}
+					}
+
+					if (pti[-1] == 'm')
+					{
+						break;
+					}
+				}
+				break;
+
+			default:
+				pti++;
+				break;
+		}
+	}
+
+	strcpy(buf, "\e[0");
+
+	if (HAS_BIT(vtc, COL_BLD))
+	{
+		strcat(buf, ";1");
+	}
+	if (HAS_BIT(vtc, COL_UND))
+	{
+		strcat(buf, ";4");
+	}
+	if (HAS_BIT(vtc, COL_BLK))
+	{
+		strcat(buf, ";5");
+	}
+	if (HAS_BIT(vtc, COL_REV))
+	{
+		strcat(buf, ";7");
+	}
+
+	if (HAS_BIT(vtc, COL_XTF))
+	{
+		cat_sprintf(buf, ";38;5;%d", fgc);
+	}
+	else if (HAS_BIT(vtc, COL_TCF))
+	{
+		cat_sprintf(buf, ";38;2;%d;%d;%d", fgc / 256 / 256, fgc / 256 % 256, fgc % 256);
+	}
+	else if (fgc != -1)
+	{
+		cat_sprintf(buf, ";%d", fgc);
+	}
+
+	if (HAS_BIT(vtc, COL_XTB))
+	{
+		cat_sprintf(buf, ";48;5;%d", bgc);
+	}
+	else if (HAS_BIT(vtc, COL_TCB))
+	{
+		cat_sprintf(buf, ";48;2;%d;%d;%d", bgc / 256 / 256, bgc / 256 % 256, bgc % 256);
+	}
+	else if (bgc != -1)
+	{
+		cat_sprintf(buf, ";%d", bgc);
+	}
+
+	strcat(buf, "m");
+
+	pop_call();
+	return;
+}
+
+int strip_vt102_strlen(struct session *ses, char *str)
+{
+	char *pti;
+	int w, i = 0;
+
+	pti = str;
+
+	while (*pti)
+	{
+		if (skip_vt102_codes(pti))
+		{
+			pti += skip_vt102_codes(pti);
+
+			continue;
+		}
+
+		if (*pti == '\t')
+		{
+			i += ses->tab_width - i % ses->tab_width;
+			pti++;
+		}
+		else if (HAS_BIT(ses->charset, CHARSET_FLAG_UTF8) && is_utf8_head(pti))
+		{
+			pti += get_utf8_width(pti, &w);
+
+			i += w;
+		}
+		else
+		{
+			pti++;
+			i++;
+		}
+	}
+	return i;
+}
+
+
+int strip_vt102_width(struct session *ses, char *str, int *lines)
+{
+	char *pti;
+	int w, i = 0, max = 0;
+
+	*lines = 1;
+
+	pti = str;
+
+	while (*pti)
+	{
+		if (*pti == '\n')
+		{
+			*lines += 1;
+
+			if (i > max)
+			{
+				max = i;
+				i = 0;
+			}
+		}
+
+		if (skip_vt102_codes(pti))
+		{
+			pti += skip_vt102_codes(pti);
+
+			continue;
+		}
+
+		if (*pti == '\t')
+		{
+			i += ses->tab_width - i % ses->tab_width;
+			pti++;
+		}
+		else if (HAS_BIT(ses->charset, CHARSET_FLAG_UTF8) && is_utf8_head(pti))
+		{
+			pti += get_utf8_width(pti, &w);
+
+			i += w;
+		}
+		else
+		{
+			pti++;
+			i++;
+		}
+	}
+
+	if (i > max)
+	{
+		return i;
+	}
+	return max;
+}
+
+int strip_color_strlen(struct session *ses, char *str)
+{
+	char buf[BUFFER_SIZE];
+
+	substitute(ses, str, buf, SUB_ESC|SUB_COL);
+
+	return strip_vt102_strlen(ses, buf);
+}
+
+int interpret_vt102_codes(struct session *ses, char *str, int real)
+{
+	char data[BUFFER_SIZE] = { 0 };
+	int skip = 0;
+
+	switch (str[skip])
+	{
+		case   8:
+			ses->cur_col = UMAX(1, ses->cur_col - 1);
+			return TRUE;
+
+		case  27:   /* ESC */
+			break;
+
+		case 11:    /* VT  */
+			ses->cur_row = UMIN(gtd->screen->rows, ses->cur_row + 1);
+			return TRUE;
+
+		case 12:    /* FF  */
+			ses->cur_row = UMIN(gtd->screen->rows, ses->cur_row + 1);
+			return TRUE;
+
+		case  13:   /* CR  */
+			ses->cur_col = 1;
+			return TRUE;
+
+		default:
+			return TRUE;
+	}
+
+	switch (str[1])
+	{
+		case '7':
+			ses->sav_row = ses->cur_row;
+			ses->sav_col = ses->cur_col;
+			return TRUE;
+
+		case '8':
+			ses->cur_row = ses->sav_row;
+			ses->cur_col = ses->sav_col;
+			return TRUE;
+
+		case 'c':
+			ses->cur_row = 1;
+			ses->cur_col = 1;
+			ses->sav_row = ses->cur_row;
+			ses->sav_col = ses->cur_col;
+			return TRUE;
+
+		case 'D':
+			ses->cur_row = URANGE(1, ses->cur_row + 1, gtd->screen->rows);
+			return TRUE;
+
+		case 'E':
+			ses->cur_row = URANGE(1, ses->cur_row + 1, gtd->screen->rows);
+			ses->cur_col = 1;
+			return TRUE;
+
+		case 'M':
+			ses->cur_row = URANGE(1, ses->cur_row - 1, gtd->screen->rows);
+			return TRUE;
+
+		case '[':
+			break;
+
+		default:
+			return TRUE;
+	}
+
+	for (skip = 2 ; str[skip] != 0 ; skip++)
+	{
+		switch (str[skip])
+		{
+			case '@':
+			case '`':
+			case ']':
+				return TRUE;
+
+			case 'c':
+				return FALSE;
+
+			case 'A':
+				ses->cur_row -= UMAX(1, atoi(data));
+				break;
+
+			case 'B':
+			case 'e':
+				ses->cur_row += UMAX(1, atoi(data));
+				break;
+
+			case 'C':
+			case 'a':
+				ses->cur_col += UMAX(1, atoi(data));
+				break;
+
+			case 'D':
+				ses->cur_col -= UMAX(1, atoi(data));
+				break;
+
+			case 'E':
+				ses->cur_row -= UMAX(1, atoi(data));
+				ses->cur_col = 1;
+				break;
+
+			case 'F':
+				ses->cur_row -= UMAX(1, atoi(data));
+				ses->cur_col = 1;
+				break;
+
+			case 'G':
+				ses->cur_col = UMAX(1, atoi(data));
+				break;
+
+			case 'H':
+			case 'f':
+				if (sscanf(data, "%d;%d", &ses->cur_row, &ses->cur_col) != 2)
+				{
+					if (sscanf(data, "%d", &ses->cur_row) == 1)
+					{
+						ses->cur_col = 1;
+					}
+					else
+					{
+						ses->cur_row = 1;
+						ses->cur_col = 1;
+					}
+				}
+				break;
+
+			case 'd':
+				ses->cur_row = atoi(data);
+				break;
+
+			case 'r':
+				if (sscanf(data, "%d;%d", &ses->split->top_row, &ses->split->bot_row) != 2)
+				{
+					if (sscanf(data, "%d", &ses->split->top_row) != 1)
+					{
+						ses->split->top_row = 1;
+						ses->split->bot_row = gtd->screen->rows;
+					}
+					else
+					{
+						ses->split->bot_row = gtd->screen->rows;
+					}
+				}
+				ses->cur_row = 1;
+				ses->cur_col = 1;
+				break;
+
+			case 's':
+				ses->sav_row = ses->cur_row;
+				ses->sav_col = ses->cur_col;
+				break;
+
+			case 'u':
+				ses->cur_row = ses->sav_row;
+				ses->cur_col = ses->sav_col;
+				break;
+
+			case 'x':
+				return FALSE;
+
+			default:
+				data[skip - 2] = str[skip];
+				data[skip - 1] = 0;
+				break;
+		}
+
+		if (isalpha((int) str[skip]))
+		{
+			ses->cur_row = URANGE(1, ses->cur_row, gtd->screen->rows);
+
+			ses->cur_col = URANGE(1, ses->cur_col, gtd->screen->cols + 1);
+
+
+			ses->split->top_row = URANGE(1, ses->split->top_row, gtd->screen->rows);
+
+			ses->split->bot_row = ses->split->bot_row ? URANGE(1, ses->split->bot_row, gtd->screen->rows) : gtd->screen->rows;
+			
+			return TRUE;
+		}
+	}
+	return TRUE;
+}
+

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels