diff --git a/src/Home/Telnet/Art/BIZ-DUNE.ans b/src/Home/Telnet/Art/BIZ-DUNE.ans new file mode 100644 index 00000000..0bbb686a --- /dev/null +++ b/src/Home/Telnet/Art/BIZ-DUNE.ans @@ -0,0 +1,133 @@ +  +    +  ÜÜ ÜÜ ÜÜ  + bisounours presents his first ansi under the fire label. Û²±Ü²ÛÛÜܱ²²°Ü + original art: gen13    ßÞßÛßÛ²ÜÛ²±°ß  + huhu to all members of fire, tiny toons, telepathy, aegis ÜÜÜÜÜßÛÛÛ²ÜÛ° + btw, if you wonderÞwhere my handle comÝes from, it means Þ²ÛÜÞÛ²±ÜÛ²±Ý ° + carebear Þin frenchÝ° ÝÞÝ  ß°ßܲßܲ±°ÜÜÜÞ + ÝÞÝ Þ°ÛÝ ÞÝÞÛÞÜÜÜ°Ü°ÜÛÛ²±ÜÛÛÛ²°Ü ÞÝ + ÞÝ Ý° ÛÞ°Û Ý  °°Ý Ý° Ý ÝÞ²²ÛßÞß°ß ßÛ²ßÜÜÛ²° °° + Û° ÝÞ°Ý ÞÝÝ °ÛÛÝ ÞÞÝÞ° ÞÝÞ  ÞÝ ÞÝß²ß޲ܲ±°Ü۲ܰßßßß Þ°Ý + Þ°ÞÝÝ°±° ÛÝÞ ÞÛ°ÛÝ °Û°° Û° ²Û Û ÞÛ°Û²  ܲÛÜßÛÜÛÛß°²²±°±² °± +°°ÛÛÛ°Þ°°ÞÛ²Û °°ÛÝÞÛÛ Þ±Ý²Û°Ý ÛÛÝÛÝ°Û°²ÛÝ °ÝÞ±ÛÛ²±°°ßÞ±²²±°±²ßÞ±Ý +Þ°Þ²ÛÝÝ  °±±²ÛÛÝÝ ÞÛ±Û  Û²ÛÝ  °±Þ° Û°ÞÛÛ²°ÛÞ°ÞÛÛÛ ²ÛÜß²²±°ß()ß±°²²ß °±²±Ü +ÛÛ±ßß°  Þ°°ÛßÛ²°ÛÜÛÛß ßß Þ°±°ÝßÛÛÛ°ßß°ÛÛÛÛÛß²ÛßÛ°±Û Þ±²±°Ý + ß²ßßßßßßß²°ßßß ß²ßßßßßß²ßß   ßß° ß±²ß  +  ° + + ÜÜÜÜÜ  +Û Ü°°° ÛÛ°±²²ÜÜ  + °°±²±° ÛÛ°±²ÛÛÛ²ÜÜ  +  °ÜÜÜÜÜ Þ°±²Û²±° ÛÛ°±²²ÛÛÛÛ²Ü  + ÜÛÛ²²±±°°°± °±²ÛÛ²±° ÛÛÛ°°±²²ÛÛÛÛ²Ü °ÜÜÜÜÜ  +  ±²²ÛÛÛ²²±±±±Þ°±²ÛÛÛ²±° °°±²²ÛÛÛÛ²Ü ±Üܲ۲²ßÛ°°  +  ܱ±°°°Ü°±±²²²ÛÛÛÛ²²²Ý°±²ÛÛÛ²±°ÛÛÛÛÛÛÛ°°±±²ÛÛÛÛ²Ü °²²ÛÛÛÛ²ßÛ °±°°  + ÜÛ°²Û²²±°°°Ü°±±²²²²ÛÛ²²°±²ÛÛÛÛ²±±°ÛÛÛÛÛÛÛÛ°°°±±²²²±°Üß°±±²ßßÛÛÛÛ°±±°°  +  ÜÛÛ °±²ÛÛÛÛ²±°°ÜÜ°±±²²²±°±²²ÛÛÛÛÛ²±±°° ÛÛÛÛÛÛÛ°°°±±±±°° ÛÛÛÛÛÛÛÛÛÛ°±±°Ý + ÜÛÛ °°°±±²²ÛÛ²±°°Ü°±±±°±²²ÛÛÛÛÛÛÛ²²±±±²ÜÜ°   ÛÛ°±±° + ÜÛ°° °°°°±±²²Û²°°ÛÛ °²²ÛÛÛÛÛßßÛÛÛ²²²ÛÛ²ßß±° °±ßß²ÛÛÛÛÛÛ²ÜÜÜ° °±± + ÜÛ°±±°° °°°°±±²²° °±²²ÜܲßÛ²ÛÜܲßß²±° Ü° °ÜÜÜ°°°°°°°°±ßß²²ÛÛ²ÜÜ° °° +Þ°±²²²±°°  °°°°±±²²°ÛÛÛ±ÜÞ°°²±°ÜÜÜ °°° °ßßß°°°Ü±²Ü±±°° ° °°±ß²²Û²Ü°  ß°±²²²²±°°  °±²²²ß°ÛÛ²±°°ßÛ° ß°Ü ßßÜÜßßÛ°°°Üܰ߰ܲ۲²Û۲ܲÜܱ°°°ß²² + ß°±²Û²²±±° °±±²²²ßÞ°²Û²°ß Þ° ßßÜÛßßܲÜßßÜß°°Ü°ßݱ²ÛÛÛÛÛÛÛÛ²Û²²Ü±° +  Üܲ°±²ÛÛ²²±±±°  °±±²²Û²°Üß°°±²° ÛÝÛÛ ß  °ÞÛÛÝÝÛ Ü°ß°ÜÞ°±²ÛÛÛÛÛÛÛÛÛÛ²²ß° +Ü°±±°ß°±²ÛÛÛ²²²±±±±²²Û²°°ÞÝ ²°Û±ÝÛÛÛÛÛÛ°°°ß°ÜÜßÛÝÞ°°ÛÞÝÛÜßÜ ß° °±²ÛÛÛ²Û²²±±°  + °±±±ßܱ²ÛÛÛÛ²²²²ÛÛ²°ÜßÜß°Üß²ÛÜ° ÛÛÛ°±²± ÜÞ±þ²ßÜÛ²±°Û°°° Þ  °° °²Û²²±±°°  +ÛÛÛ°°±±ßܱ²ÛÛÛÛÛÛÛ²°Üß°Ü° Üß²ÛÛÛßÝÛ°±²²ÛÝÜÝßßÜÜÛÛÛÛ²±ÝÜ °°Ý°°Ü ݱ²²±°°  +°°°ÛÛ°°±°Ý°²ÛÛÛÛ²° ß ÛÞÝ ß°°ß° ßßß²²°±±²ÛÛÛÛÛÛÛÛÛ²±°°° ÞÝޱ߰ܲ±°   +±±±°°°Û°°Þ°±ÛÛ²²°ÜßÜ° Û°  Ûß ÞÝ°þÜ  ß±°±±²ÛÛÛÛÛÛÛÛ²±°±±Ý°°Þ²± °Û°   +²²²±±±°°Û° ²²²°°Û°Û±ÝÞÝ  °ÛÜÜßßßÜÜÛÛ²°±±±²ÛÛÛÛÛÛÛ²±Ý±±° ÞÞ²²°Þ°Û   +ÛÛÛ²²²±±°Û Û°°Û°°°²°°ÛÝ  Û°±±±²ÛÛÛ²±°±ßÜÛÛÛÛÛÛ²±°Þ±±±Ý°²ÛÛ²Þ°°Ý  +²²²ÛÛÛ²²± ²±°Û°°°²Û±Þ°Ý °ÜÞ ß°°±±²²ÛÛ²ÜÜÛÛßßß ÛÛ²±ÝÝ°±±° Þ²ÛÝÞ°°°  +±±±²²²ß²Û²± Þ°Ý°±Û²ÝÞ°Û° ÞÝ °°ÝßÛ°±±²²²ß Üܱ°ÞÛ²±°ÞÛÞ°±° °±²ÝÛ°°°  +ÜÜÜÜ°°°²²± °Þ°°°²Û²Ý°²°Û° ÛÞ°° ÛÜ ßÛ°±±Ü  ßß±°ÜÛ²±° ²Û °°ÝÞ°°±°Û° Ý  +°°°°°°²²±°°° ÛÝ°²ÛÝÝÛ±±°Ý°ÛÞ°°° Ü ßßÛ°°±ßÜܱ²²±ßܱ±²Û°ß°Û°°ÝÞ°°°   +°°°°°²²±Ü°°°ÛÛ°²ÛÛ±ÞÛÛ°°Ý ÞÝÛ°°±° °   ßßÛÛ°°ßßܲ°±²Ûß±°ÜÜÛßÜ°°ÜÜÛÛÛÛÛÛÜÜ  +Û°°°²²±Û°°°Û°±°±ß°ÝÛ °Û°° °ÛÜÛ°°²Ü²°Ü   °Ü² °±Ûß±±°°Û°±±Û°±²ÛÛÛÛÛÛÛÛÛÛÜ  +°°±²²±Þ°°ÝÛ±²²Ü°°²Þ°Ü±Û°° Û°Ü۲߱ÛܱÜ°±ÜÜÜÛÛÛÛßß±±°°°ÛÛ°°°±Þ°±²²ÛÛÛÛÛÛÛÛÛÛÛ  +°±²²° °°ÛÛ±²ÛÛ²±²Þ°²ÛÝ°°Ý °ß°±ܲßß²²Ü°Ü°±±±±±±±°°°°ÛÛÛ°° °°±Þ°±±²²ÛÛÛÛÛÛÛÛÛÛ  +±²²° °°°Û±²ÛÛÛÝ°Þ°²ÛÛ²°Ý° ÜÜÜÜÜ߲ܰÜßßÛ²°Û °°°°°°°ÛÛ Û°°  °°±Þ°°±²²²ÛÛÛÛÛÛÛÛÝ +²²° Þ°°Û±²ÛÛÛ²°Û°²ÛÛÛ²°ÜÛÛÛÛÛÛÛÛÝÛ²±°Üßß²°Þ° Û°° ÛÛ°°±ÞÛ°°±²²²²ÛÛÛÛÛÛ +²° °°°°ÞÛÛÛ²° °²ÛÛ²±±ÞÛÛÛÛÛÛÛÛÛ±±°Û±°°°Þ²°Û° Û°° ÛÛÛ°°±ÞÛ °°±±±²²²ÛÛÛ +°()°°Û ²ÛÛ²°Û°²ÛÛÛ±°°ÛÛÛÛÛÛÛÛÛ±±°°²°Ý°±°Þ±ÝÛ° Üß°° °°±ÞÛ °°°±±²²² + °Ü°Û°²ÛÛÝ °²ÛÛ²±°ÛÛ²ÛÛÛÛÛÛÛݱ°°Þ±°ÛÞ±²Þ°°°ßܲÜß°°   °°±ÞÛ °°° + Û±ÝÛ°ÞÛ²°ÞÞÛÛ±°°ÜÛ°±²ÛÛÛÛÛݱ°°Ü±°Û°Û²ÛÝÛ  ÜÜßÜÜ  °° Û°°±ÞÛ Ý + °±²°ÝÛÛ±²±ÝÛ²²±°ÛÜÛÛ°±±²²ÛÛݱ°°Ü°Û°ÝÞÞÛÛ²ÝÛÛ° ß  Û° °ÛÛÛ°°±±±°° Û°°±ÞÛ ÛÛ  +°±²ÛÜÝÛÛ°±ÛÞÞ²±°ÛÝÞÛÛÛ°°±²²Ý±°ÜÛßÜ°Û °²Û²± Û°ßÜ Üß°ÛÛÛ°°°±²²²²²±±°°Û°±Þ ÛÛ±  +°ÞÛÛ²ÝÛÛ°°ÝÛ±±°Ûß °²ÛÛÛ °°Û±Üß°°°Ûܱ ÞÛ²±Ý°°ÛÛÛß°ßß°°°±±²²²ÛÛÛÛ²²²±°Û°±Þ² Û²°  +±²ÛÛÝ°Þ°±°Þ°°Ûß°  °²ÛÛÛÛÛÝß°°Üܲ²ßÛ°²ß°ß°Üß°ÛÛ° °Û°±²²²ÛÛÛÛÛÛÛÛÛ²²±°Û°°Þ²Û²±  +ÞÛÛ²°Û°±²°Þ°ÛÞ  °²²ÛÛÛ±°Üܲ²²ÝÛ°²°Û²±°°°Üß°Ý°Û°±²²ÛÛÛÛÛÛÛÛÛÛÛÛ²²°Û°±°±²±°  +±²ß°ÛÛ±²ÛÝÝÛÝÛÝ  °°²²Û°Þ²²²ÛÛÛ°±°ÛÛÛ²±°°Û°ÞÞ°Û°±²²ÛÛÛÛÛÛÛÛÛÛÛÛ²²°°°±±Ý±°  +°±°ÛÛ°ÞÛÛ²°ÞÞ°Û  °°°°°Þ²ÛÛÛÝ°°°ÛÛÛÛÛ²±°° Ý°°°Û°±²²ÛÛÛÛÛÛÛÛÛÛ²²±°°°±±Ý°  +°°ÛÛÞ±²ÛÛÛÝÝ°°°Û °Þ°°°±²ÛÛÛÛ°°ÛÛÛÛÛ²²±°°°²Þ±°°Û°°±²²ÛÛÛÛÛÛ²²²±°Û°°±± °  +°° ÛÞ°²ÛÛÛ²Þ °°ÛÜ °±°°°±²²ÛÛ °ÞÛÛÛ²²±°°°°Ý°±±±°°Û°°±²²²²²²±°°Û°°°±±Ý  +Û° ÛÝ°±²Û²±ÝÝ °ß°°ÛÜ  °±°°°°±±²²Ý°ÛÛ²²²±°°°±°°°°°±±±°°°Û°°°±±±°°°°°°±±Ý  +Û° ÛÝÛ°±²±°ÛÞ  ß°°ÛÜ Þ°±°°Û°°±±±Û²²±±°°°°±°Ý°°±°ß±±±±±°°°°°°°°°°±±±°ß  +ÛÛ°ÛÛ°Û°°±°ÛÝßÜ °ß°°°ÜÛ°±±°°°°°°Þ°°°°°°±±°ß°°±±±°°ßß°±±±±±±±±±±±±°ß   +ÞÛ° ÛÛ°Û°°° Û ßÜ ßÜß°°±±±±°°°°°±±±±°ß°°°±±±²²²±±°°ßßß°±±±±°ßß°    +°ÛÛ°ÛÛÛ°Û°°°ÛÝ ßßÜÜ ß°°°±±±±±±±°ßß°°°±±±±²²²Û²²²±±±°°°°°°°°°    + °ÛÛÛÛÛÛ°ßÛ°°ÛÜ ° °±±ßßßßßß°°°±±±±±²²²²ÛÛÛÛÛÛÛ²²²±±±±±°°    +   ß°°ÛÛ² °ßÛ°°ÛÜ  ° °±°°°°°°±±±±²²²²²ÛÛÛÛÛÛÛÛÛÛÛÛÛÛ²²²²²²  + °ß°°°Ü  ßÛÛÛÛÜÜ Þ° ²ÜÜÜÜÜÛÛÛÛ°°±°°ÛÛÛÛÛ°°±°° ÛÛÛÛÛ°°°° °  + ßß°°ÜÜ °ßßß°°°Þ±° °°ÛÛÛÛÛÛ°°±²±°ÛÛÛÛ°°±²±±°ÛÛÛÛ°°°° °±  + ßßß Û±° Þ°°ÛÛÛÛ°°±²Û²°ÛÛÛ°°±²ÛÛ²±°ÛÛ°°°° °±²  +  ÞÛ²±°  °°ÛÛÛÛ°°²ÛÛ²±°ÛÛ°±²ÛÛÛ²±°Û°°°° °±²ÛÝ  + ÛÛ²±±° °°ÛÛÛÛ°°±²ÛÛ²±°Û°°±²ÛÛ²±°ÛÛ°°°Ý °±²Û°°  + ÞÛÛÛ²±°° Ü°°ÛÛÛÛ °°±²ÛÛ²±°Û°±²ÛÛÛ²±°Û°°°° °°±²ÛÛ °Ý  + ÛÛÛÛÛßܲ² ÛÛÛÛÛÛ°°±²ÛÛÛ²±°Û°±²ÛÛ²±°°Û°°°° °±±²ÛÛÛ °°  + ÛÛÛßܲ²²²² ÛÛÛÛ°°±²²ÛÛ²±°°Û°±²ÛÛ²±°°Û°°°° °±²ÛÛÛÛÛ °°  + ÛßÜ°°Þ²±±±± ÛÛ°°±²²ÛÛÛ²±°°ÛÛ°±²ÛÛ²±°° °°°Ý °±²ÛÛÛ ÛÛÛ°Ý  + Ü°°±°°±±±°°°Û°°±±²ÛÛÛ²±°°°Û°±±²ÛÛ²±°°Û°°°Ý °²Û°°°ÛÛÛÛ°Ý  + Ü°°±±°°°Þ°°°°°°°°±²ÛÛÛÛ²±°°ÛÛ°±²ÛÛÛ²±°°Û°°°° ÞÛ°°±±°ÛÛÛ°  + Ü°°±±°°°Û °°°°°±Ý±²ÛÛÛÛ²±°°°ÛÛ°±²ÛÛÛ²±°°Û °°° Þ°°±²±±°ÛÛ   +  °°±±°°°° °°Þ°°±±²²°²ÛÛÛ²±°°°ÛÛÛÛ°±²ÛÛ²±°°ÛÛÛß²Ý °°±²²±°°ÛÝ  +  Ü°°±±°°° °°°±Þ±±²²ÛÛݲ²²²±°°°ÛÛ ÛÛÛ°±²Û²±°Ûܱ±²²² °°±²Û²±°ÛÝ  + Ü°°±±°°°Û °°°±±²±²²ÛÛ²²²°²±±°°°ÛÛ ÛÛÛÛ°±²±ÜÛ°°°±±²² °°±²Û²±°ÛÝ  +  Ü°±±°°°°Û°°°±±²²Û²ÛÛ²²±±±°°°°°°Û ÛÛÛÛ Ü±±°Û°°°±²² Þ°±²Û²±±°Ý  + Ü°±±°°°ÛÛ°°±±²²ÛÛÛ²²²±±±°°°Ý°°ÛÛÛ ÛÛÜ°°±±±±°°°°ßÛÝ Û°±²Û²±°  + °°±±°°°ÛÛ°°±²²ÛÛÛÛÛÛÞ±±°°°°°±ÛÛÛÛ Ü±±°°°°±±±°ßßÛÛÛ Þ°±²Û²±±  + °°±±°°°ÛÛ°°±²²ÛÛÛÛÛÛ²Þ°°°ÛÛÛ°°±ÛÛÛ Üܱ±°°°ÛÛ°°±±ß°°°°ÛÝ °°±Û²±°  + °°±±°°°ÛÛ°°±²²ÛÛÛÛÛÛ²±Þ°ÛÛÛÛÛÛ°°±Ü±±°°°ÛÛÛ°°°°ß °°°°ÛÛÛ Þ°°±±°°  + °°±±°°°ÛÛ°°±²²ÛÛÛÛÛÛ²±°°ÛÛÛÛÛÛÛÛÛ°°°ÛÛÛÛÛ°°±ß °°°°°°ÛÛÛÝ  Û°°±°²ÛÛÜ Ü + °°±±°°°ÛÛ°°±²²ÛÛÛÛÛÛ²±° ±°ÛÛÛÛÛÛÛÛÛÛÛÛÛÛ°°±ß °°±±²±°°ÛÛÛÝ ÞÛ°°ÝÛ°±²ÛÛÛ + Þ°±±°°°ÛÛ°°±²²ÛÛÛÛÛÛ²±° Þ°°ÛÛÛÛÛÛÛÛÛÛÛÛÛ°°Ü Û°±±²²²±°ÛÛÛÛ ÛÛÛÞÛÛ°±²²Û + °°±°°°ÛÛ°°±²²ÛÛÛÛÛÛ²±°°ÛÞ°°ÛÛÛÛÛÛÛÛÛÛ°°±± ÛÛ°±²²²²±°°ÛÛÛÛ ßÛÛÛÛÛÛ°±± + Þ°±±°°°Û°°±²²ÛÛÛÛÛÛ²²±°ÛÛÞ°°ÛÛÛÛÛÛÛÛÛ°°±ßÛÛÛ°±²²Û²²±° ÛÛÛ° ßÛÛÛÛÛ  + °°±°°°ÛÛ°°±²ÛÛÛÛÛÛÛ²±°°ÛÛ ±°°ÛÛÛÛÛÛ°°±ß ÛÛÛ°±²²ÛÛ²²±°ÛÛÛ°Ý ßßßßß +  °±±°°°Û°°±²²ÛÛÛÛÛÛ²²±° Û ±°°ÛÛÛÛ°°±ß°°ÛÛ °±²²ÛÛÛ²±°° Û°±  + Þ°±±°°ÛÛ°°±²ÛÛÛÛÛÛÛ²±±°ÛÛÛ Þ°°ÛÛ°°±ß°±°ÛÛÛ°±²²ÛÛÛ²²±° Û°±°  + °°±°°°Û°°±²²ÛÛÛÛÛÛÛ²±°ÛÛÛÛ° ±°°°±ß°±±° ÛÛ°±²²ÛÛÛÛ²²±°Û°±°Ý  + °°±°°ÛÛ°°±²ÛÛÛÛÛÛÛ²²±°ÛÛÛ°°°Þ°±ß°°±±°ÛÛÛÛ°±²ÛÛÛÛÛ²±°°Û°°°  +Þ°±±°°Û°°±²²ÛÛÛÛÛÛÛ²±°°ÛÛÛ°±° Þ°±±±°ÛÛÛ °±²ÛÛÛÛÛ²²±°Û°±°Ý   +Þ°±±°°Û°°±²ÛÛÛÛÛÛÛÛ²±°ÛÛÛÛ°±° °±±±° ÛÛÛ°±²ÛÛÛÛÛÛ²²±°Û°±°Ý  +°±±±°°Û°°±²ÛÛÛÛÛÛÛÛ²±°ÛÛÛÛ°±°ÝÞ±±±°° ÛÛ°±²²ÛÛÛÛÛÛ²±°°°±°°  +°±±°°°Û°°±²ÛÛÛÛÛÛÛÛ²±°ÛÛÛÛ°±°Ý°±±°° ÛÛÛ°±²ÛÛÛÛÛÛ²²±° °±°Ý  +°±±°°°Û°°±²²ÛÛÛÛÛÛÛ²±°°ÛÛÛÛ°±°Þ±±° ÛÛ°±²²ÛÛÛÛÛÛ²²±°Û°±°Ý  °±±°°°ÛÛ°°±²ÛÛÛÛÛÛÛ²²±°ÛÛÛÛ°±°Þ±°°ÛÛÛ °±²ÛÛÛÛÛÛ²²±°°°±°°     +°±±±°°°Û°°±²²ÛÛÛÛÛÛÛ²±°ÛÛÛÛ°±°Þ°°ÛÛÛÛ°±²ÛÛÛÛÛÛÛ²²±°Û°±°Ý   +Þ°±±°°°ÛÛ°°±²ÛÛÛÛÛÛÛ²±°°ÛÛÛ°±°Þ°°ÛÛÛ°±²ÛÛÛÛÛÛÛ²²±°°°±°°   +Þ°±±°°°ÛÛ°°±²ÛÛÛÛÛÛÛ²²±°ÛÛÛ°±°Þ°ÛÛÛ°±²²ÛÛÛÛÛÛ²²±°°Û°±°Ý  + °±±°°°ÛÛ°°±²Ûß²ßßßÛÛ²±°ÛÛÛÛ°±ÝÛÛÛÛ°±²ÛÛÛÛÛÛ²²±°°Û°±°ß   + °±±°°°ßßßÛ²ÛÛ±ÛÛ° Û° ß°ßßßÛ°±ÝÛÛÛ°±²ÛÛÛÛÛÛÛ²±°ß°Û°°²ÛÛÛÜÜÜÜ° +ÜÞ°ßß²°°ÛÛ°ÛÛÛÛÛ°°°ÛÛÛÛÛ²ÛÛ°ÛÛÛßßßß²ÛÛÛÛßßß²ÛÛÛ°Û°°ÛÛÛÛÛÛ°ÛÛÛÛÛ²ÛÜÜÜ ÜÜ²Ü Ü ÜÜÜ ß°ßß²ßß°°°Û°ÛÛÛÛß°ßß±ßßß ²° ßßß²ßßÛÛÛÛÛÛ²ßßß °   + °ß²ßß°° ÜÜ ÜÜÜÜÛÛ°±ÛܲÜÜÜÜÜ Ü °°   + °ßß²ßßßß + + +²Ü° +Û²ÛÜ° +ÛÛÛÛ²Ü  +ÛÛÛÛÛÛÛÜ +ÛÛÛ۲߰   + ÜÜÜÜÜÜÜÜÜ ²ÛÛß ÜÜܲÜÜÜÜÜÜ °ÜÜÜÜÜÜÜÜÜÜ + ²ÛÛÛÛÛ²ßßßÛÛ²ßßÛ ±ßÛÛÜ Ü°n°ßßßßßÛÜÜ° Ü þßßßßß°ß²ÛÛÛ + ÞÛÛ²Ûß²°ßßÛ²°Ü ° ²ÛÛÜ ß±°ß²ÛÜÜ e°ß±ßÞÛÛÝ + °ÛÛÛÝ ± °ßÛ Ý ÞÛ²ÛÝ °ÞÛÛ² °±° °Û²  + ²Û °Þ Þ °°ÛÛÛÛÞÛÛÝ °ÞÛ²  +  °ÛÝ Þ  °°Û²ÛÞ²ÛÛ ÜÛß° ° + °ÜÛ  ²   d° °ÞÞÛÛÝÛÛÛÛÜ²ß  ²Ü° + ÜÛÛ²Û°  ß °Ü Ü  °Ý°Üu²ÛßÝÞ²ÛÛÝÜ° ßß ° ²ÛÛÛÜ +  ܲÛÛÛÛÛÛÜÜ° ÜÜÜÜÜÜß°° þÜÜ  ÜÛÜÛß°ÜÜ  Û۲߰þÜÜÜÜÜ   °ÜܲÛÛÛÛ²ÛÜ +ܲÛÛÛÛÛÛÛÛ²ÛÛÛÛÛÛÛÛÛ²ßß° ßß± ßß Û°ßß°ßßÛÛÛÜ°Üܲßß ßß°ßß²ÜÜÜܲÛÛÛÛÛÛ²ÛÛÛÛ²Ü + a world  °far beyond ÛÛÛ²ÛÜ  your°dreams  ± +²ÛÛÛÛ۲߰ + +33-1-4879o422 ÛÛÛÛÛß sysops gizmo & bisounours ÛÛ²ß ²ß  ° SAUCE00Dune Bisounours Fire 19960527þqP‹ \ No newline at end of file diff --git a/src/Home/Telnet/Art/BZ-DDLNE.ans b/src/Home/Telnet/Art/BZ-DDLNE.ans new file mode 100644 index 00000000..f3762147 Binary files /dev/null and b/src/Home/Telnet/Art/BZ-DDLNE.ans differ diff --git a/src/Home/Telnet/Telnet.ZC b/src/Home/Telnet/Telnet.ZC index ccd16e92..cede9d2f 100644 --- a/src/Home/Telnet/Telnet.ZC +++ b/src/Home/Telnet/Telnet.ZC @@ -16,28 +16,11 @@ Cd(__DIR__);; #define MAX_ANSI_PARAMS 32 -#include "TelnetNegotiation" +#include "TelnetClass" #include "TelnetHelpers" Bool force_disconnect = FALSE; -class Terminal { - I64 sock; - Bool sock_ready; - I64 window_width; - I64 window_height; - CDoc *doc; - CTask *task; - - I64 current_color; - I64 current_bgcolor; - I64 cursor_x; - I64 cursor_y; - - U8 buffer[BUF_SIZE]; - I64 buffer_len; -} term; - I64 TelnetOpen(U8 *host, U16 port) { I64 sock; @@ -143,52 +126,6 @@ U0 HandleControlCodes(U8 ch) { } } -I64 LoadSplashScreen(U8 *filename) { - CFile *file = FOpen(filename, "rb"); - if (!file) { - PrintErr("Failed to open file"); - return -1; - } - - // Ensure that the file size isn't larger than the buffer - if (file->de.size > BUF_SIZE) { - PrintErr("File is too large for the buffer."); - FClose(file); - return -1; - } - - // Calculate the number of full blocks to read based on file size and block size - I64 full_blocks = file->de.size / BLK_SIZE; - I64 remaining_bytes = file->de.size % BLK_SIZE; - - SysLog("File size: %d, Number of full blocks: %d, Remaining bytes: %d\n", file->de.size, full_blocks, remaining_bytes); - - // Read the full blocks into the buffer - I64 i, blocks_read = 0; - for (i = 0; i < full_blocks; i++) { - blocks_read += FBlkRead(file, term.buffer + i * BLK_SIZE, i, 1); - } - - // Check if there are any remaining bytes in the last block - if (remaining_bytes != 0) { - // Read the remaining bytes - U8 temp_buffer[BLK_SIZE]; - if (FBlkRead(file, temp_buffer, full_blocks, 1)) { - blocks_read++; - MemCopy(term.buffer + full_blocks * BLK_SIZE, temp_buffer, remaining_bytes); - } - } - - FClose(file); - - if (blocks_read != (full_blocks + (remaining_bytes != 0))) { - PrintErr("Failed to read all the blocks"); - return -1; - } - - return file->de.size; // Return the number of bytes read -} - U0 ANSIParse() { // Basic Telnet protocol parser @@ -247,8 +184,7 @@ U0 ANSIParse() deviceStatusResponse[2] = 0x30; // '0' deviceStatusResponse[3] = 0x6E; // 'n' deviceStatusResponse[4] = 0x00; // Null-terminator - TCPSocketSend(term.sock, deviceStatusResponse, 4); - // TCPSocketSendString(term.sock, "\x1B[0n"); + if (term.sock_ready) TCPSocketSend(term.sock, deviceStatusResponse, 4); } else if (ansi_code[0] == 6) { // Respond with cursor position @@ -264,8 +200,7 @@ U0 ANSIParse() cursorResponse[6] = 0x30; cursorResponse[6] = 0x52; cursorResponse[7] = 0x00; - TCPSocketSend(term.sock, cursorResponse, 7); - // TCPSocketSendString(term.sock, "\x1B\[25;80R"); + if (term.sock_ready) TCPSocketSend(term.sock, cursorResponse, 7); } else if (ansi_code[0] == 255) { // https://github.com/NuSkooler/enigma-bbs/blob/97cd0c3063b0c9f93a0fa4a44a85318ca81aef43/core/ansi_term.js#L140 @@ -277,7 +212,6 @@ U0 ANSIParse() case 'c': // Respond with device attributes SysLog("reported device attributes\n"); - // TCPSocketSendString(term.sock, "\x1B[?1;0c"); // Reports at VT101 (not sure why though) U8 deviceAttributesResponse[8]; deviceAttributesResponse[0] = ANSI_ESC; @@ -288,7 +222,7 @@ U0 ANSIParse() deviceAttributesResponse[5] = 0x32; // '0' deviceAttributesResponse[6] = 0x63; // 'c' deviceAttributesResponse[7] = 0x00; // Null-terminator - TCPSocketSend(term.sock, deviceAttributesResponse, 7); + if (term.sock_ready) TCPSocketSend(term.sock, deviceAttributesResponse, 7); ptr++; break; case 'm': @@ -687,18 +621,20 @@ U0 TerminalTask() { } U0 Telnet(U8 *host=NULL, U16 port=TELNET_PORT) { - I64 sc; - + CHostForm form; term.window_width = 80; term.window_height = 25; term.doc = Fs->display_doc; + term.waiting_for_input = TRUE; + term.sock_ready = 0; + I64 art_path = "Art/TelnetSplash.ans"; StrCopy(Fs->task_title, "TELNET"); - Fs->border_src = BDS_CONST; - Fs->border_attr = LTGREEN << 4 + DriveTextAttrGet(':') & 15; - Fs->text_attr = BLACK << 4 + WHITE; - Fs->title_src = TTS_LOCKED_CONST; - DocClear(Fs->border_doc, TRUE); + Fs->border_src = BDS_CONST; + Fs->border_attr = LTGREEN << 4 + DriveTextAttrGet(':') & 15; + Fs->text_attr = BLACK << 4 + WHITE; + Fs->title_src = TTS_LOCKED_CONST; + DocClear(Fs->border_doc, TRUE); Fs->win_width = term.window_width; WinHorz((TEXT_COLS / 2) - (Fs->win_width / 2), @@ -712,8 +648,11 @@ U0 Telnet(U8 *host=NULL, U16 port=TELNET_PORT) { Fs); DocClear; +show_splash: // SplashScreen - term.buffer_len = LoadSplashScreen("Art/TelnetSplash.ans"); + // I64 buffer_size = sizeof(term.buffer); + term.buffer_len = ANSIArtLoad(art_path, term.buffer); + if (term.buffer_len > 0) { term.buffer[term.buffer_len] = '\0'; // parse the buffer @@ -723,41 +662,35 @@ U0 Telnet(U8 *host=NULL, U16 port=TELNET_PORT) { "Error: Could not load splash screen.\n"; } - // PopUp if no host is specified - if (host == NULL) { - try - { - while (host == NULL) { - CHostForm form; - TelnetPrompt(&form); - host = form.host; - port = form.port; - DocClear; - break; - } + +init_connection: + while (!term.waiting_for_input || host != NULL) + { + // if(term.sock != NULL) TCPSocketClose(term.sock); + // TODO: should probably kill the task if it already exists too? + + // Spawn a task to receive data from the socket + term.task = Spawn(&TerminalTask, NULL, "Telnet"); + + // probably should use word wrap? + DocPrint(, "$$WW,1$$"); + DocCursor(OFF); + + term.sock = TelnetOpen(host, port); + if (term.sock <= 0) { + return; } - catch - PutExcept; + term.sock_ready = 1; // Signal that the socket is ready + term.waiting_for_input = FALSE; + + "$$BG,GREEN$$$$WHITE$$Connected$$FG$$$$BG$$\n"; + Sleep(1000); + DocClear; + break; } - // Spawn a task to receive data from the socket - term.task = Spawn(&TerminalTask, NULL, "Telnet"); - - // probably should use word wrap? - DocPrint(, "$$WW,1$$"); - DocCursor(OFF); - - term.sock_ready = 0; // Initialize the semaphore - term.sock = TelnetOpen(host, port); - if (term.sock <= 0) { - return; - } - term.sock_ready = 1; // Signal that the socket is ready - - "$$BG,GREEN$$$$WHITE$$Connected$$FG$$$$BG$$\n"; - Sleep(1000); - DocClear; - + + I64 sc; try { while (!force_disconnect) { @@ -768,19 +701,19 @@ U0 Telnet(U8 *host=NULL, U16 port=TELNET_PORT) { switch (sc.u8[0]) { case SC_CURSOR_LEFT: - TCPSocketSendString(term.sock, "\x1B[D"); + if (term.sock_ready) TCPSocketSendString(term.sock, "\x1B[D"); break; case SC_CURSOR_RIGHT: - TCPSocketSendString(term.sock, "\x1B[C"); + if (term.sock_ready) TCPSocketSendString(term.sock, "\x1B[C"); break; case SC_CURSOR_UP: - TCPSocketSendString(term.sock, "\x1B[A"); + if (term.sock_ready) TCPSocketSendString(term.sock, "\x1B[A"); break; case SC_CURSOR_DOWN: - TCPSocketSendString(term.sock, "\x1B[B"); + if (term.sock_ready) TCPSocketSendString(term.sock, "\x1B[B"); break; default: break; @@ -790,24 +723,43 @@ U0 Telnet(U8 *host=NULL, U16 port=TELNET_PORT) { switch (sc.u8[0]) { case SC_TAB: - TCPSocketSendString(term.sock, "\x09"); + if (term.sock_ready) TCPSocketSendString(term.sock, "\x09"); break; default: break; } case CH_BACKSPACE: - TCPSocketSendString(term.sock, "\x08"); + if (term.sock_ready) TCPSocketSendString(term.sock, "\x08"); break; case CH_ESC: - TCPSocketSendString(term.sock, "\x1B"); + if (term.sock_ready) TCPSocketSendString(term.sock, "\x1B"); break; case CH_SHIFT_ESC: force_disconnect = TRUE; break; // send buffer on enter case '\n': - TCPSocketSendString(term.sock, "\r\n"); - + if (term.sock_ready) TCPSocketSendString(term.sock, "\r\n"); + break; + case CH_CTRLN: + TelnetPrompt(&form); + host = form.host; + port = form.port; + DocClear; + term.waiting_for_input = FALSE; + goto init_connection; + break; + case CH_CTRLO: + art_path = ANSIArtBrowser; + if (art_path != NULL) { + term.sock_ready = 0; + term.waiting_for_input = TRUE; + SysLog("%s\n", art_path); + goto show_splash; + } + else { + "Error: Could not load art.\n"; + } break; default: if (key >= ' ' && key <= '~') { @@ -815,7 +767,7 @@ U0 Telnet(U8 *host=NULL, U16 port=TELNET_PORT) { U8 input_buf[2]; input_buf[0] = key; input_buf[1] = '\0'; - TCPSocketSend(term.sock, input_buf, 1); + if (term.sock_ready) TCPSocketSend(term.sock, input_buf, 1); } break; } @@ -830,5 +782,5 @@ U0 Telnet(U8 *host=NULL, U16 port=TELNET_PORT) { // dev server -Telnet("localhost", 8888); -// Telnet; \ No newline at end of file +// Telnet("localhost", 8888); +Telnet; \ No newline at end of file diff --git a/src/Home/Telnet/TelnetClass.ZC b/src/Home/Telnet/TelnetClass.ZC new file mode 100644 index 00000000..4968dd32 --- /dev/null +++ b/src/Home/Telnet/TelnetClass.ZC @@ -0,0 +1,17 @@ +class Terminal { + I64 sock; + Bool sock_ready; + Bool waiting_for_input; + I64 window_width; + I64 window_height; + CDoc *doc; + CTask *task; + + I64 current_color; + I64 current_bgcolor; + I64 cursor_x; + I64 cursor_y; + + U8 buffer[BUF_SIZE]; + I64 buffer_len; +} term; diff --git a/src/Home/Telnet/TelnetGr.ZC b/src/Home/Telnet/TelnetGr.ZC index bb7f1385..574cd06a 100755 --- a/src/Home/Telnet/TelnetGr.ZC +++ b/src/Home/Telnet/TelnetGr.ZC @@ -177,11 +177,58 @@ U0 HandleControlCodes(U8 ch) { } +I64 LoadSplashScreen(U8 *filename) { + CFile *file = FOpen(filename, "rb"); + if (!file) { + PrintErr("Failed to open file"); + return -1; + } + + // Ensure that the file size isn't larger than the buffer + if (file->de.size > BUF_SIZE) { + PrintErr("File is too large for the buffer."); + FClose(file); + return -1; + } + + // Calculate the number of full blocks to read based on file size and block size + I64 full_blocks = file->de.size / BLK_SIZE; + I64 remaining_bytes = file->de.size % BLK_SIZE; + + SysLog("File size: %d, Number of full blocks: %d, Remaining bytes: %d\n", file->de.size, full_blocks, remaining_bytes); + + // Read the full blocks into the buffer + I64 i, blocks_read = 0; + for (i = 0; i < full_blocks; i++) { + blocks_read += FBlkRead(file, term.buffer + i * BLK_SIZE, i, 1); + } + + // Check if there are any remaining bytes in the last block + if (remaining_bytes != 0) { + // Read the remaining bytes + U8 temp_buffer[BLK_SIZE]; + if (FBlkRead(file, temp_buffer, full_blocks, 1)) { + blocks_read++; + MemCopy(term.buffer + full_blocks * BLK_SIZE, temp_buffer, remaining_bytes); + } + } + + FClose(file); + + if (blocks_read != (full_blocks + (remaining_bytes != 0))) { + PrintErr("Failed to read all the blocks"); + return -1; + } + + return file->de.size; // Return the number of bytes read +} + U0 TerminalDrawIt(CTask *task, CDC *dc) { // Clear the document // DocClear(term.doc); - DCFill(dc,BLUE); + Sleep(100); + DCFill; I64 row, col; // Loop over the screen array and draw each character @@ -204,10 +251,498 @@ U0 TerminalDrawIt(CTask *task, CDC *dc) // Draw the curso // Note: this draws the cursor as a white rectangle. You may want to customize this. // dc->color = WHITE; - // GrRect(dc, term.cursor_x * 8, term.cursor_y * 8, + // GrRect(term.dc, term.cursor_x * 8, term.cursor_y * 8, // (term.cursor_x + 1) * 8 - 1, (term.cursor_y + 1) * 8 - 1); } +U0 ANSIParse() +{ + // Parse the buffer and draw the contents + U8 *ptr = term.buffer; + while (ptr < term.buffer + term.buffer_len) { + // Telnet negotiation sequence + if (*ptr == NEGOTIATE) { + // FIXME: i don't think the telnet negotiation is actually working properly? + TelnetNegotiate(term.sock, *ptr); + ptr += 3; + } + else if (*ptr == ANSI_ESC) { + // ANSI escape sequence + ptr++; + if (*ptr == ANSI_CSI) { + ptr++; + I64 ansi_code[MAX_ANSI_PARAMS], counter; + for (counter = 0; counter < MAX_ANSI_PARAMS; counter++) { + ansi_code[counter] = 0; // Initialize all elements to 0 + } + I64 ansi_param_count = 0; + while (IsDigit(*ptr) || *ptr == ';') { + if (IsDigit(*ptr)) { + ansi_code[ansi_param_count] = ansi_code[ansi_param_count] * 10 + (*ptr - '0'); + ptr++; + } + else if (*ptr == ';') { + ansi_param_count++; + if (ansi_param_count >= MAX_ANSI_PARAMS) { + // Error handling: too many parameters + break; + } + ptr++; + if(!IsDigit(*ptr) || *ptr == ';'){ + break; + } + } + } + + // Handle specific ANSI escape sequences + switch (*ptr) { + case 'n': + SysLog("Case n, %d\n",ansi_code[0]); + if (ansi_code[0] == 5) { + // Respond with terminal readiness + SysLog("reported terminal readiness\n"); + U8 deviceStatusResponse[5]; + deviceStatusResponse[0] = ANSI_ESC; + deviceStatusResponse[1] = ANSI_CSI; + deviceStatusResponse[2] = 0x30; // '0' + deviceStatusResponse[3] = 0x6E; // 'n' + deviceStatusResponse[4] = 0x00; // Null-terminator + TCPSocketSend(term.sock, deviceStatusResponse, 4); + // TCPSocketSendString(term.sock, "\x1B[0n"); + } + else if (ansi_code[0] == 6) { + // Respond with cursor position + // U8 response[32] = "\x1B[%d;%dR", window_width, term.window_height; + SysLog("reported cursor position\n"); + U8 cursorResponse[8]; + cursorResponse[0] = ANSI_ESC; + cursorResponse[1] = ANSI_CSI; + cursorResponse[2] = 0x32; + cursorResponse[3] = 0x35; + cursorResponse[4] = 0x3B; + cursorResponse[5] = 0x38; + cursorResponse[6] = 0x30; + cursorResponse[6] = 0x52; + cursorResponse[7] = 0x00; + TCPSocketSend(term.sock, cursorResponse, 7); + // TCPSocketSendString(term.sock, "\x1B\[25;80R"); + } + else if (ansi_code[0] == 255) { + // https://github.com/NuSkooler/enigma-bbs/blob/97cd0c3063b0c9f93a0fa4a44a85318ca81aef43/core/ansi_term.js#L140 + SysLog("reported screensize?\n"); + SendWindowSize(term.sock, 25, 80); + } + ptr++; + break; + case 'c': + // Respond with device attributes + SysLog("reported device attributes\n"); + // TCPSocketSendString(term.sock, "\x1B[?1;0c"); + // Reports at VT101 (not sure why though) + U8 deviceAttributesResponse[8]; + deviceAttributesResponse[0] = ANSI_ESC; + deviceAttributesResponse[1] = ANSI_CSI; + deviceAttributesResponse[2] = 0x3F; // '?' + deviceAttributesResponse[3] = 0x31; // '1' + deviceAttributesResponse[4] = 0x3B; // ';' + deviceAttributesResponse[5] = 0x32; // '0' + deviceAttributesResponse[6] = 0x63; // 'c' + deviceAttributesResponse[7] = 0x00; // Null-terminator + TCPSocketSend(term.sock, deviceAttributesResponse, 7); + ptr++; + break; + case 'm': + // Set graphics mode (colors) + I64 m; + Bool isBright = FALSE; + for (m = 0; m <= ansi_param_count; m++) { + if (ansi_code[m] <= 10) { + switch (ansi_code[m]) { + case 0: + term.current_bgcolor = BLACK; // should be BG FG for full reset + term.current_color = WHITE; // should be BG FG for full reset + isBright = FALSE; + break; // reset + case 1: isBright = TRUE; break; + case 2: isBright = FALSE; break; + } + } + else if ((ansi_code[m] >= 30 && ansi_code[m] <= 39) || (ansi_code[m] >= 90 && ansi_code[m] <= 97)) { + // Set foreground color + // SysLog("ansi_code[%d] = %d\n", m, ansi_code[m]); + if(!isBright){ + switch (ansi_code[m]) { + case 30: + term.current_color = BLACK; + break; + case 31: + term.current_color = RED; + break; + case 32: + term.current_color = GREEN; + break; + case 33: + term.current_color = YELLOW; + break; + case 34: + term.current_color = BLUE; + break; + case 35: + term.current_color = PURPLE; + break; + case 36: + term.current_color = CYAN; + break; + case 37: + term.current_color = WHITE; + break; + case 39: + term.current_color = WHITE; + break; + default: break; + } + } + else { + switch (ansi_code[m]) { + case 90: + case 30: + term.current_color = DKGRAY; + break; + case 91: + case 31: + term.current_color = LTRED; + break; + case 92: + case 32: + term.current_color = LTGREEN; + break; + case 93: + case 33: + term.current_color = YELLOW; + break; + case 94: + case 34: + term.current_color = LTBLUE; + break; + case 95: + case 35: + term.current_color = LTPURPLE; + break; + case 96: + case 36: + term.current_color = LTCYAN; + break; + case 97: + case 37: + term.current_color = LTGRAY; + break; + case 39: + term.current_color = WHITE; + break; + default: break; + } + } + } + // this is a dumb approach, just do a CatPrint or something + // until we properly catch the `;` it will stay buggy + else if ((ansi_code[m] >= 40 && ansi_code[m] <= 49) || (ansi_code[m] >= 100 && ansi_code[m] <= 107)) { + // Set background color + // SysLog("ansi_code[%d] = %d\n", m, ansi_code[m]); + if(!isBright){ + switch (ansi_code[m]) { + case 40: + term.current_bgcolor = BLACK; + break; + case 41: + term.current_bgcolor = RED; + break; + case 42: + term.current_bgcolor = GREEN; + break; + case 43: + term.current_bgcolor = YELLOW; + break; + case 44: + term.current_bgcolor = BLUE; + break; + case 45: + term.current_bgcolor = PURPLE; + break; + case 46: + term.current_bgcolor = CYAN; + break; + case 47: + term.current_bgcolor = WHITE; + break; + case 49: + // reset + term.current_bgcolor = BLACK; + break; + default: break; + } + } + else { + switch (ansi_code[m]) { + case 100: + case 40: + term.current_bgcolor = DKGRAY; + break; + case 101: + case 41: + term.current_bgcolor = LTRED; + break; + case 102: + case 42: + term.current_bgcolor = LTGREEN; + break; + case 103: + case 43: + term.current_bgcolor = YELLOW; + break; + case 104: + case 44: + term.current_bgcolor = LTBLUE; + break; + case 105: + case 45: + term.current_bgcolor = LTPURPLE; + break; + case 106: + case 46: + term.current_bgcolor = LTCYAN; + break; + case 107: + case 47: + term.current_bgcolor = LTGRAY; + break; + case 49: + // reset + term.current_bgcolor = BLACK; + default: break; + } + } + } + } + ptr++; + break; + case 'A': + // Cursor Up + SysLog("Cursor Up\n"); + term.cursor_y -= ansi_code[0]; + ptr++; + break; + case 'B': + // Cursor Down + SysLog("Cursor Down\n"); + term.cursor_y += ansi_code[0]; + ptr++; + break; + case 'C': + // Cursor Right + // SysLog("Cursor Right %d %d\n", ansi_param_count, ansi_code[0]); + term.cursor_x += ansi_code[0]; + // NOTE: this has been "fixed" since we now change the window's background color + // if we just move the cursor, + // you dont get the colored background since we skip over it directly + // I64 C; + // for (C = 0; C < ansi_code[0]; C++) { + // " "; + // } + ptr++; + break; + case 'D': + // Cursor Left + SysLog("Cursor Left\n"); + term.cursor_x -= ansi_code[0]; + ptr++; + break; + case 'E': + // Cursor Next Line + SysLog("Cursor Next Line\n"); + term.cursor_x = 0; + term.cursor_y++; + if (term.cursor_y >= term.window_height) { + // scroll + } + ptr++; + break; + case 'F': + // Cursor Previous Line + SysLog("Cursor Previous Line\n"); + term.cursor_x = 0; + term.cursor_y -= ansi_code[0]; + if (term.cursor_y < 0) { + term.cursor_y = 0; // prevent y from going below 0 + } + ptr++; + break; + case 'G': + // Cursor Horizontal Absolute + SysLog("Cursor Horizontal Absolute\n"); + term.cursor_x = ansi_code[0]; + ptr++; + break; + case 'H': + case 'f': + I64 row = 1, col = 1; // default values + // Parse the row number + if(ansi_code[0] != 1) + row = ansi_code[0]; + if(ansi_code[1] != 1) + col = ansi_code[1]; + + // TODO: This is a hack, dont skip row 0, col 0 (maybe?) + if (row == 0 && col == 0) { + ptr++; + break; + } + // SysLog("H or f AFTER row:%d, col:%d, cnt:%d\n", row, col, ansi_param_count); + + if (row > term.window_height) + row = term.window_height - 1; + if (col > term.window_width) + col = term.window_width - 1; + // "$$CM,0,0$$"; + term.cursor_x = col-1; + term.cursor_y = row-1; + ptr++; + break; + case 'J': + // SysLog("J code, %d %d\n", ansi_param_count, ansi_code[0]); + // Erase in Display + if (ansi_code[0] == 0) { + // Erase from cursor to end of display + // DocDelToNum(Fs->display_doc, Fs->display_doc->cur_entry->line_num); + } else if (ansi_code[0] == 1) { + // Erase from cursor to beginning of display + // DocDelToEntry(Fs->display_doc, Fs->display_doc->cur_entry, FALSE); + } else if (ansi_code[0] == 2) { + // Erase entire display + // DocClear(term.doc); + DCFill(term.dc,BLACK); + // term.cursor_x = 0; + // term.cursor_y = 0; + // redraw_needed = TRUE; + } + ptr++; + break; + case 'K': + // TODO: I have no idea if this actually works + SysLog("K code\n"); + // Erase in Line + // CDocEntry *cur_entry = Fs->display_doc->cur_entry; + // CDocEntry *next_entry = cur_entry->next; + + // // Delete the current entry + // if (!(cur_entry->de_flags & (DOCEF_HOLD | DOCEF_FILTER_SKIP))) { + // Fs->display_doc->cur_entry = next_entry; + // Fs->display_doc->cur_col = next_entry->min_col; + // DocEntryDel(Fs->display_doc, cur_entry); + // } + + // // Create a new entry (line) in its place + // CDocEntry *new_entry = DocEntryNewTag(Fs->display_doc, cur_entry, ""); + // DocInsEntry(Fs->display_doc, new_entry); + + ptr++; + break; + case 'L': + SysLog("L code\n"); + ptr++; + break; + case 'S': + // TODO: Scroll Up + SysLog("Scroll Up"); + ptr++; + break; + case 'T': + // TODO: Scroll Down + SysLog("Scroll Down"); + ptr++; + break; + case 'M': + SysLog("Case M\n"); + term.cursor_y--; + ptr++; + break; + case '?': + ptr++; + I64 code = 0; + + while (IsDigit(*ptr)) { + code = code * 10 + (*ptr - '0'); + ptr++; + } + switch (code) { + case 25: + if (*ptr == 'l') DocCursor(OFF); // Hide cursor + if (*ptr == 'h') DocCursor(ON); // Show cursor + ptr++; // Move past 'l' or 'h' + break; + case 47: + if (*ptr == 'l') SysLog("code 47l\n"); // restore screen + if (*ptr == 'h') SysLog("code 47h\n"); // save screen + ptr++; // Move past 'l' or 'h' + break; + case 1049: + if (*ptr == 'l') SysLog("code 1049l\n"); // enables the alternative buffer + if (*ptr == 'h') SysLog("code 1049h\n"); // disables the alternative buffer + ptr++; // Move past 'l' or 'h' + break; + default: + ptr++; + break; + } + break; + case 's': + SysLog("SaveCurrentCursorPosition\n"); + ptr++; + break; + case 'u': + SysLog("RestoreCurrentCursorPosition\n"); + ptr++; + break; + case 'r': + // self.restoreCursorPosition(); + SysLog("r case \n"); + ptr++; + break; + case 'h': + case 'l': + // TODO: Handle 'h' (set mode) or 'l' (reset mode) codes + SysLog("h or l case \n"); + ptr++; // Skip 'h' or 'l' + break; + case '=': + SysLog("ScreenMode attempt\n"); + ptr++; + break; + default: + if(!IsDigit(*ptr)) { + SysLog("Unknown code: %c\n", *ptr); + } + ptr++; + break; + } + } + } + else { + // Print the received character + HandleControlCodes(*ptr); + ptr++; + } + } + if (redraw_needed) { + // TerminalDrawIt(term.task, term.task); + // DocClear(term.doc); + + // DCFill(,RED); + // term.cursor_x = 0; + // term.cursor_y = 0; + redraw_needed = FALSE; + } +} + U0 TerminalTask() { while (!term.sock_ready) { Sleep(100); // Avoid busy waiting @@ -219,490 +754,7 @@ U0 TerminalTask() { if (term.buffer_len > 0) { redraw_needed = TRUE; term.buffer[term.buffer_len] = '\0'; - // Parse the buffer and draw the contents - U8 *ptr = term.buffer; - while (ptr < term.buffer + term.buffer_len) { - // Telnet negotiation sequence - if (*ptr == NEGOTIATE) { - // FIXME: i don't think the telnet negotiation is actually working properly? - TelnetNegotiate(term.sock, *ptr); - ptr += 3; - } - else if (*ptr == ANSI_ESC) { - // ANSI escape sequence - ptr++; - if (*ptr == ANSI_CSI) { - ptr++; - I64 ansi_code[MAX_ANSI_PARAMS], counter; - for (counter = 0; counter < MAX_ANSI_PARAMS; counter++) { - ansi_code[counter] = 0; // Initialize all elements to 0 - } - I64 ansi_param_count = 0; - while (IsDigit(*ptr) || *ptr == ';') { - if (IsDigit(*ptr)) { - ansi_code[ansi_param_count] = ansi_code[ansi_param_count] * 10 + (*ptr - '0'); - ptr++; - } - else if (*ptr == ';') { - ansi_param_count++; - if (ansi_param_count >= MAX_ANSI_PARAMS) { - // Error handling: too many parameters - break; - } - ptr++; - if(!IsDigit(*ptr) || *ptr == ';'){ - break; - } - } - } - - // Handle specific ANSI escape sequences - switch (*ptr) { - case 'n': - SysLog("Case n, %d\n",ansi_code[0]); - if (ansi_code[0] == 5) { - // Respond with terminal readiness - SysLog("reported terminal readiness\n"); - U8 deviceStatusResponse[5]; - deviceStatusResponse[0] = ANSI_ESC; - deviceStatusResponse[1] = ANSI_CSI; - deviceStatusResponse[2] = 0x30; // '0' - deviceStatusResponse[3] = 0x6E; // 'n' - deviceStatusResponse[4] = 0x00; // Null-terminator - TCPSocketSend(term.sock, deviceStatusResponse, 4); - // TCPSocketSendString(term.sock, "\x1B[0n"); - } - else if (ansi_code[0] == 6) { - // Respond with cursor position - // U8 response[32] = "\x1B[%d;%dR", window_width, term.window_height; - SysLog("reported cursor position\n"); - U8 cursorResponse[8]; - cursorResponse[0] = ANSI_ESC; - cursorResponse[1] = ANSI_CSI; - cursorResponse[2] = 0x32; - cursorResponse[3] = 0x35; - cursorResponse[4] = 0x3B; - cursorResponse[5] = 0x38; - cursorResponse[6] = 0x30; - cursorResponse[6] = 0x52; - cursorResponse[7] = 0x00; - TCPSocketSend(term.sock, cursorResponse, 7); - // TCPSocketSendString(term.sock, "\x1B\[25;80R"); - } - else if (ansi_code[0] == 255) { - // https://github.com/NuSkooler/enigma-bbs/blob/97cd0c3063b0c9f93a0fa4a44a85318ca81aef43/core/ansi_term.js#L140 - SysLog("reported screensize?\n"); - SendWindowSize(term.sock, 25, 80); - } - ptr++; - break; - case 'c': - // Respond with device attributes - SysLog("reported device attributes\n"); - // TCPSocketSendString(term.sock, "\x1B[?1;0c"); - // Reports at VT101 (not sure why though) - U8 deviceAttributesResponse[8]; - deviceAttributesResponse[0] = ANSI_ESC; - deviceAttributesResponse[1] = ANSI_CSI; - deviceAttributesResponse[2] = 0x3F; // '?' - deviceAttributesResponse[3] = 0x31; // '1' - deviceAttributesResponse[4] = 0x3B; // ';' - deviceAttributesResponse[5] = 0x32; // '0' - deviceAttributesResponse[6] = 0x63; // 'c' - deviceAttributesResponse[7] = 0x00; // Null-terminator - TCPSocketSend(term.sock, deviceAttributesResponse, 7); - ptr++; - break; - case 'm': - // Set graphics mode (colors) - I64 m; - Bool isBright = FALSE; - for (m = 0; m <= ansi_param_count; m++) { - if (ansi_code[m] <= 10) { - switch (ansi_code[m]) { - case 0: - term.current_bgcolor = BLACK; // should be BG FG for full reset - term.current_color = WHITE; // should be BG FG for full reset - isBright = FALSE; - break; // reset - case 1: isBright = TRUE; break; - case 2: isBright = FALSE; break; - } - } - else if ((ansi_code[m] >= 30 && ansi_code[m] <= 39) || (ansi_code[m] >= 90 && ansi_code[m] <= 97)) { - // Set foreground color - // SysLog("ansi_code[%d] = %d\n", m, ansi_code[m]); - if(!isBright){ - switch (ansi_code[m]) { - case 30: - term.current_color = BLACK; - break; - case 31: - term.current_color = RED; - break; - case 32: - term.current_color = GREEN; - break; - case 33: - term.current_color = YELLOW; - break; - case 34: - term.current_color = BLUE; - break; - case 35: - term.current_color = PURPLE; - break; - case 36: - term.current_color = CYAN; - break; - case 37: - term.current_color = WHITE; - break; - case 39: - term.current_color = WHITE; - break; - default: break; - } - } - else { - switch (ansi_code[m]) { - case 90: - case 30: - term.current_color = DKGRAY; - break; - case 91: - case 31: - term.current_color = LTRED; - break; - case 92: - case 32: - term.current_color = LTGREEN; - break; - case 93: - case 33: - term.current_color = YELLOW; - break; - case 94: - case 34: - term.current_color = LTBLUE; - break; - case 95: - case 35: - term.current_color = LTPURPLE; - break; - case 96: - case 36: - term.current_color = LTCYAN; - break; - case 97: - case 37: - term.current_color = LTGRAY; - break; - case 39: - term.current_color = WHITE; - break; - default: break; - } - } - } - // this is a dumb approach, just do a CatPrint or something - // until we properly catch the `;` it will stay buggy - else if ((ansi_code[m] >= 40 && ansi_code[m] <= 49) || (ansi_code[m] >= 100 && ansi_code[m] <= 107)) { - // Set background color - // SysLog("ansi_code[%d] = %d\n", m, ansi_code[m]); - if(!isBright){ - switch (ansi_code[m]) { - case 40: - term.current_bgcolor = BLACK; - break; - case 41: - term.current_bgcolor = RED; - break; - case 42: - term.current_bgcolor = GREEN; - break; - case 43: - term.current_bgcolor = YELLOW; - break; - case 44: - term.current_bgcolor = BLUE; - break; - case 45: - term.current_bgcolor = PURPLE; - break; - case 46: - term.current_bgcolor = CYAN; - break; - case 47: - term.current_bgcolor = WHITE; - break; - case 49: - // reset - term.current_bgcolor = BLACK; - break; - default: break; - } - } - else { - switch (ansi_code[m]) { - case 100: - case 40: - term.current_bgcolor = DKGRAY; - break; - case 101: - case 41: - term.current_bgcolor = LTRED; - break; - case 102: - case 42: - term.current_bgcolor = LTGREEN; - break; - case 103: - case 43: - term.current_bgcolor = YELLOW; - break; - case 104: - case 44: - term.current_bgcolor = LTBLUE; - break; - case 105: - case 45: - term.current_bgcolor = LTPURPLE; - break; - case 106: - case 46: - term.current_bgcolor = LTCYAN; - break; - case 107: - case 47: - term.current_bgcolor = LTGRAY; - break; - case 49: - // reset - term.current_bgcolor = BLACK; - default: break; - } - } - } - } - ptr++; - break; - case 'A': - // Cursor Up - SysLog("Cursor Up\n"); - term.cursor_y -= ansi_code[0]; - ptr++; - break; - case 'B': - // Cursor Down - SysLog("Cursor Down\n"); - term.cursor_y += ansi_code[0]; - ptr++; - break; - case 'C': - // Cursor Right - // SysLog("Cursor Right %d %d\n", ansi_param_count, ansi_code[0]); - term.cursor_x += ansi_code[0]; - // NOTE: this has been "fixed" since we now change the window's background color - // if we just move the cursor, - // you dont get the colored background since we skip over it directly - // I64 C; - // for (C = 0; C < ansi_code[0]; C++) { - // " "; - // } - ptr++; - break; - case 'D': - // Cursor Left - SysLog("Cursor Left\n"); - term.cursor_x -= ansi_code[0]; - ptr++; - break; - case 'E': - // Cursor Next Line - SysLog("Cursor Next Line\n"); - term.cursor_x = 0; - term.cursor_y++; - if (term.cursor_y >= term.window_height) { - // scroll - } - ptr++; - break; - case 'F': - // Cursor Previous Line - SysLog("Cursor Previous Line\n"); - term.cursor_x = 0; - term.cursor_y -= ansi_code[0]; - if (term.cursor_y < 0) { - term.cursor_y = 0; // prevent y from going below 0 - } - ptr++; - break; - case 'G': - // Cursor Horizontal Absolute - SysLog("Cursor Horizontal Absolute\n"); - term.cursor_x = ansi_code[0]; - ptr++; - break; - case 'H': - case 'f': - I64 row = 1, col = 1; // default values - // Parse the row number - if(ansi_code[0] != 1) - row = ansi_code[0]; - if(ansi_code[1] != 1) - col = ansi_code[1]; - - // TODO: This is a hack, dont skip row 0, col 0 (maybe?) - if (row == 0 && col == 0) { - ptr++; - break; - } - // SysLog("H or f AFTER row:%d, col:%d, cnt:%d\n", row, col, ansi_param_count); - - if (row > term.window_height) - row = term.window_height - 1; - if (col > term.window_width) - col = term.window_width - 1; - // "$$CM,0,0$$"; - term.cursor_x = col-1; - term.cursor_y = row-1; - ptr++; - break; - case 'J': - // SysLog("J code, %d %d\n", ansi_param_count, ansi_code[0]); - // Erase in Display - if (ansi_code[0] == 0) { - // Erase from cursor to end of display - // DocDelToNum(Fs->display_doc, Fs->display_doc->cur_entry->line_num); - } else if (ansi_code[0] == 1) { - // Erase from cursor to beginning of display - // DocDelToEntry(Fs->display_doc, Fs->display_doc->cur_entry, FALSE); - } else if (ansi_code[0] == 2) { - // Erase entire display - // DocClear(term.doc); - // DCFill(dc,BLACK); - // term.cursor_x = 0; - // term.cursor_y = 0; - // redraw_needed = TRUE; - } - ptr++; - break; - case 'K': - // TODO: I have no idea if this actually works - SysLog("K code\n"); - // Erase in Line - // CDocEntry *cur_entry = Fs->display_doc->cur_entry; - // CDocEntry *next_entry = cur_entry->next; - - // // Delete the current entry - // if (!(cur_entry->de_flags & (DOCEF_HOLD | DOCEF_FILTER_SKIP))) { - // Fs->display_doc->cur_entry = next_entry; - // Fs->display_doc->cur_col = next_entry->min_col; - // DocEntryDel(Fs->display_doc, cur_entry); - // } - - // // Create a new entry (line) in its place - // CDocEntry *new_entry = DocEntryNewTag(Fs->display_doc, cur_entry, ""); - // DocInsEntry(Fs->display_doc, new_entry); - - ptr++; - break; - case 'L': - SysLog("L code\n"); - ptr++; - break; - case 'S': - // TODO: Scroll Up - SysLog("Scroll Up"); - ptr++; - break; - case 'T': - // TODO: Scroll Down - SysLog("Scroll Down"); - ptr++; - break; - case 'M': - SysLog("Case M\n"); - term.cursor_y--; - ptr++; - break; - case '?': - ptr++; - I64 code = 0; - - while (IsDigit(*ptr)) { - code = code * 10 + (*ptr - '0'); - ptr++; - } - switch (code) { - case 25: - if (*ptr == 'l') DocCursor(OFF); // Hide cursor - if (*ptr == 'h') DocCursor(ON); // Show cursor - ptr++; // Move past 'l' or 'h' - break; - case 47: - if (*ptr == 'l') SysLog("code 47l\n"); // restore screen - if (*ptr == 'h') SysLog("code 47h\n"); // save screen - ptr++; // Move past 'l' or 'h' - break; - case 1049: - if (*ptr == 'l') SysLog("code 1049l\n"); // enables the alternative buffer - if (*ptr == 'h') SysLog("code 1049h\n"); // disables the alternative buffer - ptr++; // Move past 'l' or 'h' - break; - default: - ptr++; - break; - } - break; - case 's': - SysLog("SaveCurrentCursorPosition\n"); - ptr++; - break; - case 'u': - SysLog("RestoreCurrentCursorPosition\n"); - ptr++; - break; - case 'r': - // self.restoreCursorPosition(); - SysLog("r case \n"); - ptr++; - break; - case 'h': - case 'l': - // TODO: Handle 'h' (set mode) or 'l' (reset mode) codes - SysLog("h or l case \n"); - ptr++; // Skip 'h' or 'l' - break; - case '=': - SysLog("ScreenMode attempt\n"); - ptr++; - break; - default: - if(!IsDigit(*ptr)) { - SysLog("Unknown code: %c\n", *ptr); - } - ptr++; - break; - } - } - } - else { - // Print the received character - HandleControlCodes(*ptr); - ptr++; - } - } - if (redraw_needed) { - // TerminalDrawIt(term.task, term.task); - // DocClear(term.doc); - - // DCFill(,RED); - // term.cursor_x = 0; - // term.cursor_y = 0; - redraw_needed = FALSE; - } + ANSIParse; } else { "Error: Connection closed by the remote host.\n"; break; @@ -720,7 +772,8 @@ U0 Telnet(U8 *host, U16 port=TELNET_PORT) { return; } term.sock_ready = 1; // Signal that the socket is ready - term.doc = Fs->display_doc; + term.dc = DCNew(term.window_width, term.window_height); + term.doc = Fs->put_doc; // Spawn a task to receive data from the socket term.task = Spawn(&TerminalTask, NULL, "Telnet"); @@ -746,14 +799,38 @@ U0 Telnet(U8 *host, U16 port=TELNET_PORT) { (Fs->win_height - 1), Fs); DocClear; - // WinFocus(term.task); - // DocClear(Fs->border_doc, TRUE); - // probably should use word wrap? - // DocPrint(, "$$WW,1$$"); - // DocCursor(OFF); - // term.dc = DCAlias; + // SplashScreen + term.buffer_len = LoadSplashScreen("Art/TelnetSplash.ans"); + if (term.buffer_len > 0) { + term.buffer[term.buffer_len] = '\0'; + // parse the buffer + ANSIParse; + } + else { + "Error: Could not load splash screen.\n"; + } + + // PopUp if no host is specified + if (host == NULL) { + try + { + while (host == NULL) { + CHostForm form; + TelnetPrompt(&form); + host = form.host; + port = form.port; + DocClear; + break; + } + } + catch + PutExcept; + } + "$$BG,GREEN$$$$WHITE$$Connected$$FG$$$$BG$$\n"; + Sleep(1000); + DocClear; I64 sc; // https://theasciicode.com.ar/ascii-control-characters/escape-ascii-code-27.html diff --git a/src/Home/Telnet/TelnetHelpers.ZC b/src/Home/Telnet/TelnetHelpers.ZC index 22800b25..ef3a090e 100644 --- a/src/Home/Telnet/TelnetHelpers.ZC +++ b/src/Home/Telnet/TelnetHelpers.ZC @@ -1,3 +1,7 @@ +Cd(__DIR__); +#include "TelnetClass" +#include "TelnetNegotiation" + U8 IsDigit(U8 ch) { return '0' <= ch <= '9'; } @@ -13,6 +17,87 @@ U0 TelnetPrompt(CHostForm *form) { PopUpForm(form); } +I64 ANSIArtLoad(U8 *filename, U8 *buffer) { + CFile *file = FOpen(filename, "rb"); + if (!file) { + PrintErr("Failed to open file"); + return -1; + } + + // Allocate memory for the buffer based on file size + *buffer = MAlloc(file->de.size); + if (!(*buffer)) { + PrintErr("Failed to allocate memory for the buffer"); + FClose(file); + return -1; + } + + // Calculate the number of full blocks to read based on file size and block size + I64 full_blocks = file->de.size / BLK_SIZE; + I64 remaining_bytes = file->de.size % BLK_SIZE; + + SysLog("File size: %d, Number of full blocks: %d, Remaining bytes: %d\n", file->de.size, full_blocks, remaining_bytes); + + // Read the full blocks into the buffer + I64 i, blocks_read = 0; + for (i = 0; i < full_blocks; i++) { + blocks_read += FBlkRead(file, buffer + i * BLK_SIZE, i, 1); + } + + // Check if there are any remaining bytes in the last block + if (remaining_bytes != 0) { + // Read the remaining bytes + U8 temp_buffer[BLK_SIZE]; + if (FBlkRead(file, temp_buffer, full_blocks, 1)) { + blocks_read++; + MemCopy(buffer + full_blocks * BLK_SIZE, temp_buffer, remaining_bytes); + } + } + + FClose(file); + + if (blocks_read != (full_blocks + (remaining_bytes != 0))) { + PrintErr("Failed to read all the blocks"); + return -1; + } + + return file->de.size; // Return the number of bytes read +} + +public I64 ANSIArtBrowser() +{ + CDirEntry *tmpde1 = NULL, *tmpde2; + CDoc *doc = DocNew; + I64 res = 0; + I64 res2 = 0; + + DocPrint(doc, "$$LTBLUE$$\n\n"); + + tmpde1 = FilesFind("Art/*.ans"); + + if (tmpde1) + { + while (tmpde1) + { + tmpde2 = tmpde1->next; + res++; + + DocPrint(doc, " $$MU,\"%d.%s\",LE=0x%X$$\n", res, tmpde1->name, tmpde1->name); + DirEntryDel(tmpde1); + + tmpde1 = tmpde2; + } + } + + DocPrint(doc, "\n\n$$BT+CX,\"CANCEL\",LE=0$$\n\n"); + res2 = PopUpMenu(doc); + DocDel(doc); + + + return MStrPrint("%Q%Q", "Art/", res2); +} + + // Placeholder for the full ANSI text styling // if (ansi_code[m] <= 10) {