View Javadoc

1   /***
2    * Jetrix TetriNET Server
3    * Copyright (C) 2001-2005  Emmanuel Bourg
4    *
5    * This program is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU General Public License
7    * as published by the Free Software Foundation; either version 2
8    * of the License, or (at your option) any later version.
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with this program; if not, write to the Free Software
17   * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18   */
19  
20  package net.jetrix.protocols;
21  
22  import java.util.*;
23  
24  import net.jetrix.*;
25  import net.jetrix.winlist.*;
26  import net.jetrix.config.*;
27  import net.jetrix.messages.*;
28  import net.jetrix.messages.channel.*;
29  import net.jetrix.messages.channel.specials.*;
30  import org.apache.commons.lang.StringUtils;
31  
32  /***
33   * Protocol to communicate with TetriNET 1.13 compatible clients.
34   *
35   * @author Emmanuel Bourg
36   * @version $Revision: 863 $, $Date: 2010-08-20 12:26:25 +0200 (ven., 20 août 2010) $
37   */
38  public class TetrinetProtocol extends AbstractProtocol
39  {
40      private static Map<String, String> styles = new HashMap<String, String>();
41  
42      static
43      {
44          styles.put("red", "\u0014");
45          styles.put("black", "\u0004");
46          styles.put("green", "\u000c");
47          styles.put("lightGreen", "\u000e");
48          styles.put("darkBlue", "\u0011");
49          styles.put("blue", "\u0005");
50          styles.put("cyan", "\u0003");
51          styles.put("aqua", "\u0017");
52          styles.put("yellow", "\u0019");
53          styles.put("kaki", "\u0012");
54          styles.put("brown", "\u0010");
55          styles.put("lightGray", "\u000f");
56          styles.put("gray", "\u0006");
57          styles.put("magenta", "\u0008");
58          styles.put("purple", "\u0013");
59          styles.put("b", "\u0002");
60          styles.put("i", "\u0016");
61          styles.put("u", "\u001f");
62          styles.put("white", "\u0018");
63      }
64  
65      private static Map<String, Class> specials = new TreeMap<String, Class>();
66  
67      static
68      {
69          specials.put("cs1", OneLineAddedMessage .class);
70          specials.put("cs2", TwoLinesAddedMessage.class);
71          specials.put("cs4", FourLinesAddedMessage.class);
72          specials.put("a", AddLineMessage.class);
73          specials.put("c", ClearLineMessage.class);
74          specials.put("n", NukeFieldMessage.class);
75          specials.put("r", RandomClearMessage.class);
76          specials.put("s", SwitchFieldsMessage.class);
77          specials.put("b", ClearSpecialsMessage.class);
78          specials.put("g", GravityMessage.class);
79          specials.put("q", BlockQuakeMessage.class);
80          specials.put("o", BlockBombMessage.class);
81      }
82  
83      /*** Initialization token */
84      public static final String INIT_TOKEN = "tetrisstart";
85  
86      /***
87       * Return the name of this protocol
88       */
89      public String getName()
90      {
91          return "tetrinet";
92      }
93  
94      /***
95       * Parse the specified string and return the corresponding server
96       * message for this protocol.
97       */
98      public Message getMessage(String line)
99      {
100         StringTokenizer st = new StringTokenizer(line);
101         if (!st.hasMoreTokens())
102         {
103             // skip empty lines
104             return null;
105         }
106 
107         String cmd = st.nextToken();
108         Message m = null;
109 
110         // f <slot> <field>
111         if ("f".equals(cmd))
112         {
113             FieldMessage field = new FieldMessage();
114             field.setSlot(Integer.parseInt(st.nextToken()));
115             field.setField((st.hasMoreTokens()) ? st.nextToken() : null);
116             m = field;
117             m.setRawMessage(this, line);
118         }
119         // sb <to> <bonus> <from>
120         else if ("sb".equals(cmd))
121         {
122             int to = Integer.parseInt(st.nextToken());
123             String special = st.nextToken();
124             int from = Integer.parseInt(st.nextToken());
125 
126             Class cls = specials.get(special);
127 
128             SpecialMessage spmsg = null;
129             if (specials.keySet().contains(special))
130             {
131                 try
132                 {
133                     spmsg = (SpecialMessage) cls.newInstance();
134                 }
135                 catch (Exception e)
136                 {
137                     e.printStackTrace();
138                 }
139             }
140             else
141             {
142                 throw new IllegalArgumentException("Forged special detected from " + this);
143             }
144 
145             spmsg.setSlot(to);
146             spmsg.setFromSlot(from);
147             m = spmsg;
148             m.setRawMessage(this, line);
149         }
150         // team <slot> teamname
151         else if ("team".equals(cmd))
152         {
153             TeamMessage team = new TeamMessage();
154             team.setSlot(Integer.parseInt(st.nextToken()));
155             if (st.hasMoreTokens())
156             {
157                 team.setName(line.substring("team".length() + 3).trim());
158             }
159             m = team;
160             m.setRawMessage(this, line);
161         }
162         // pline <slot> text
163         else if ("pline".equals(cmd))
164         {
165             if (st.hasMoreTokens())
166             {
167                 String slot = st.nextToken();
168                 if (st.hasMoreTokens())
169                 {
170                     String firstWord = st.nextToken();
171 
172                     if (firstWord.startsWith("/") && !firstWord.startsWith("//"))
173                     {
174                         CommandMessage command = new CommandMessage();
175                         command.setSlot(Integer.parseInt(slot));
176                         command.setCommand(firstWord.substring(1));
177                         command.setText(line.substring(line.indexOf(" ", 9) + 1));
178                         while (st.hasMoreTokens())
179                         {
180                             command.addParameter(st.nextToken());
181                         }
182                         m = command;
183                     }
184                     else
185                     {
186                         PlineMessage pline = new PlineMessage();
187                         pline.setSlot(Integer.parseInt(slot));
188                         pline.setText(line.substring("pline".length() + 3));
189                         m = pline;
190                         m.setRawMessage(this, line);
191                     }
192                 }
193             }
194         }
195         // gmsg <playername> text
196         else if ("gmsg".equals(cmd))
197         {
198             GmsgMessage gmsg = new GmsgMessage();
199             gmsg.setText(line.substring(line.indexOf(" ") + 1));
200             m = gmsg;
201             m.setRawMessage(this, line);
202         }
203         // plineact <slot> emote
204         else if ("plineact".equals(cmd))
205         {
206             PlineActMessage plineAct = new PlineActMessage();
207             plineAct.setSlot(Integer.parseInt(st.nextToken()));
208             plineAct.setText(line.substring("plineact".length() + 3));
209             m = plineAct;
210             m.setRawMessage(this, line);
211         }
212         // startgame <0 = stop | 1 = start> <playernumber>
213         else if ("startgame".equals(cmd))
214         {
215             if ("1".equals(st.nextToken()))
216             {
217                 StartGameMessage start = new StartGameMessage();
218                 start.setSlot(Integer.parseInt(st.nextToken()));
219                 m = start;
220             }
221             else
222             {
223                 StopGameMessage stop = new StopGameMessage();
224                 stop.setSlot(Integer.parseInt(st.nextToken()));
225                 m = stop;
226             }
227         }
228         // pause <0 = resume | 1 = pause> <playernumber>
229         else if ("pause".equals(cmd))
230         {
231             ChannelMessage msg;
232             if ("1".equals(st.nextToken()))
233             {
234                 msg = new PauseMessage();
235             }
236             else
237             {
238                 msg = new ResumeMessage();
239             }
240             m = msg;
241         }
242         // newgame
243         else if ("newgame".equals(cmd))
244         {
245             m = new NewGameMessage();
246 
247             // todo parse the game settings
248         }
249         // endgame
250         else if ("endgame".equals(cmd))
251         {
252             m = new EndGameMessage();
253         }
254         // ingame
255         else if ("ingame".equals(cmd))
256         {
257             m = new IngameMessage();
258         }
259         // playerlost <slot>
260         else if ("playerlost".equals(cmd))
261         {
262             PlayerLostMessage lost = new PlayerLostMessage();
263             lost.setSlot(Integer.parseInt(st.nextToken()));
264             m = lost;
265             m.setRawMessage(this, line);
266         }
267         // lvl <playernumber> <level>
268         else if ("lvl".equals(cmd))
269         {
270             LevelMessage level = new LevelMessage();
271             level.setSlot(Integer.parseInt(st.nextToken()));
272             level.setLevel(Integer.parseInt(st.nextToken()));
273             m = level;
274             m.setRawMessage(this, line);
275         }
276         // clientinfo <name> <version>
277         else if ("clientinfo".equals(cmd))
278         {
279             ClientInfoMessage info = new ClientInfoMessage();
280             if (st.hasMoreTokens())
281             {
282                 info.setName(st.nextToken());
283             }
284             if (st.hasMoreTokens())
285             {
286                 info.setVersion(st.nextToken());
287             }
288             m = info;
289         }
290         // playernum <num>
291         else if ("playernum".equals(cmd))
292         {
293             PlayerNumMessage num = new PlayerNumMessage();
294             num.setSlot(Integer.parseInt(st.nextToken()));
295             m = num;
296         }
297         // playerjoin <num> <name>
298         else if ("playerjoin".equals(cmd))
299         {
300             JoinMessage join = new JoinMessage();
301             join.setSlot(Integer.parseInt(st.nextToken()));
302             join.setName(st.nextToken());
303             join.setRawMessage(this, line);
304             m = join;
305         }
306         // playerleave <num>
307         else if ("playerleave".equals(cmd))
308         {
309             LeaveMessage leave = new LeaveMessage();
310             leave.setSlot(Integer.parseInt(st.nextToken()));
311             leave.setRawMessage(this, line);
312             m = leave;
313         }
314         else if ("noconnecting".equals(cmd))
315         {
316             NoConnectingMessage noconnecting = new NoConnectingMessage();
317             noconnecting.setText(line.substring(cmd.length() + 1));
318             m = noconnecting;
319         }
320         else if ("winlist".equals(cmd))
321         {
322             WinlistMessage winlist = new WinlistMessage();
323             winlist.setRawMessage(this, line);
324             List<Score> scores = new ArrayList<Score>();
325             String[] tokens = line.split(" ");
326             for (int i = 1; i < tokens.length; i++)
327             {
328                 String token = tokens[i];
329                 String[] values = token.split(";");
330 
331                 Score score = new Score();
332                 score.setName(values[0].substring(1));
333                 score.setType(values[0].charAt(0) == 'p' ? Score.TYPE_PLAYER : Score.TYPE_TEAM);
334                 score.setScore(Long.parseLong(values[1]));
335 
336                 scores.add(score);
337             }
338             winlist.setScores(scores);
339 
340             m = winlist;
341         }
342 
343         return m;
344     }
345 
346 
347     /***
348      * Translate the specified message into a string that will be sent
349      * to a client using this protocol.
350      */
351     public String translate(Message m, Locale locale)
352     {
353         if (m instanceof SpecialMessage)            { return translate((SpecialMessage) m); }
354         else if (m instanceof FieldMessage)         { return translate((FieldMessage) m); }
355         else if (m instanceof PlineMessage)         { return translate((PlineMessage) m, locale); }
356         else if (m instanceof LevelMessage)         { return translate((LevelMessage) m); }
357         else if (m instanceof PlayerLostMessage)    { return translate((PlayerLostMessage) m); }
358         else if (m instanceof PlineActMessage)      { return translate((PlineActMessage) m, locale); }
359         else if (m instanceof TeamMessage)          { return translate((TeamMessage) m); }
360         else if (m instanceof JoinMessage)          { return translate((JoinMessage) m, locale); }
361         else if (m instanceof LeaveMessage)         { return translate((LeaveMessage) m, locale); }
362         else if (m instanceof PlayerNumMessage)     { return translate((PlayerNumMessage) m); }
363         else if (m instanceof StartGameMessage)     { return translate((StartGameMessage) m); }
364         else if (m instanceof StopGameMessage)      { return translate((StopGameMessage) m); }
365         else if (m instanceof NewGameMessage)       { return translate((NewGameMessage) m); }
366         else if (m instanceof EndGameMessage)       { return translate((EndGameMessage) m); }
367         else if (m instanceof PauseMessage)         { return translate((PauseMessage) m); }
368         else if (m instanceof ResumeMessage)        { return translate((ResumeMessage) m); }
369         else if (m instanceof IngameMessage)        { return translate((IngameMessage) m); }
370         else if (m instanceof GmsgMessage)          { return translate((GmsgMessage) m, locale); }
371         else if (m instanceof PlayerWonMessage)     { return translate((PlayerWonMessage) m); }
372         else if (m instanceof NoConnectingMessage)  { return translate((NoConnectingMessage) m); }
373         else if (m instanceof SpectatorListMessage) { return translate((SpectatorListMessage) m, locale); }
374         else if (m instanceof SmsgMessage)          { return translate((SmsgMessage) m, locale); }
375         else if (m instanceof WinlistMessage)       { return translate((WinlistMessage) m, locale); }
376         else if (m instanceof NoopMessage)          { return translate((NoopMessage) m); }
377         else if (m instanceof CommandMessage)       { return translate((CommandMessage) m); }
378         else if (m instanceof ClientInfoMessage)    { return translate((ClientInfoMessage) m); }
379         else
380         {
381             return null;
382         }
383     }
384 
385     public String translate(SpecialMessage m)
386     {
387         if (m instanceof LinesAddedMessage)          { return translate((LinesAddedMessage) m); }
388         else if (m instanceof AddLineMessage)        { return translate((AddLineMessage) m); }
389         else if (m instanceof ClearLineMessage)      { return translate((ClearLineMessage) m); }
390         else if (m instanceof ClearSpecialsMessage)  { return translate((ClearSpecialsMessage) m); }
391         else if (m instanceof RandomClearMessage)    { return translate((RandomClearMessage) m); }
392         else if (m instanceof BlockQuakeMessage)     { return translate((BlockQuakeMessage) m); }
393         else if (m instanceof BlockBombMessage)      { return translate((BlockBombMessage) m); }
394         else if (m instanceof GravityMessage)        { return translate((GravityMessage) m); }
395         else if (m instanceof NukeFieldMessage)      { return translate((NukeFieldMessage) m); }
396         else if (m instanceof SwitchFieldsMessage)   { return translate((SwitchFieldsMessage) m); }
397         else
398         {
399             return null;
400         }
401     }
402 
403     public String translate(PlineMessage m, Locale locale)
404     {
405         StringBuilder message = new StringBuilder();
406         message.append("pline ");
407         message.append(m.getSlot());
408         message.append(" ");
409         message.append(applyStyle(m.getText(locale)));
410         return message.toString();
411     }
412 
413     public String translate(PlineActMessage m, Locale locale)
414     {
415         StringBuilder message = new StringBuilder();
416         message.append("plineact ");
417         message.append(m.getSlot());
418         message.append(" ");
419         message.append(m.getText(locale));
420         return message.toString();
421     }
422 
423     public String translate(CommandMessage m)
424     {
425         StringBuilder message = new StringBuilder();
426         message.append("pline ");
427         message.append(m.getSlot());
428         message.append(" /");
429         message.append(m.getCommand());
430 
431         for (int i = 0; i < m.getParameterCount(); i++)
432         {
433             message.append(" ");
434             message.append(m.getParameter(i));
435         }
436         return message.toString();
437     }
438 
439     public String translate(TeamMessage m)
440     {
441         StringBuilder message = new StringBuilder();
442         message.append("team ");
443         message.append(m.getSlot());
444         message.append(" ");
445         if (m.getName() != null)
446         {
447             message.append(m.getName());
448         }
449         return message.toString();
450     }
451 
452     public String translate(JoinMessage m, Locale locale)
453     {
454         if (m.getSlot() == 0)
455         {
456             // spectator joining
457             PlineMessage announce = new PlineMessage();
458             announce.setKey("channel.spectator.join", m.getName());
459             return translate(announce, locale);
460         }
461         else
462         {
463             StringBuilder message = new StringBuilder();
464             message.append("playerjoin ");
465             message.append(m.getSlot());
466             message.append(" ");
467             message.append(m.getName());
468             return message.toString();
469         }
470     }
471 
472     public String translate(LeaveMessage m, Locale locale)
473     {
474         if (m.getSlot() == 0)
475         {
476             // spectator leaving
477             PlineMessage announce = new PlineMessage();
478             announce.setKey("channel.spectator.leave", m.getName());
479             return translate(announce, locale);
480         }
481         else
482         {
483             StringBuilder message = new StringBuilder();
484             message.append("playerleave ");
485             message.append(m.getSlot());
486             return message.toString();
487         }
488     }
489 
490     public String translate(PlayerNumMessage m)
491     {
492         StringBuilder message = new StringBuilder();
493         message.append("playernum ");
494         message.append(m.getSlot());
495         return message.toString();
496     }
497 
498     public String translate(StartGameMessage m)
499     {
500         StringBuilder message = new StringBuilder();
501         message.append("startgame 1 ");
502         message.append(m.getSlot());
503         return message.toString();
504     }
505 
506     public String translate(StopGameMessage m)
507     {
508         StringBuilder message = new StringBuilder();
509         message.append("startgame 0 ");
510         message.append(m.getSlot());
511         return message.toString();
512     }
513 
514     public String translate(NewGameMessage m)
515     {
516         Settings s = m.getSettings();
517         StringBuilder message = new StringBuilder();
518         message.append("newgame ");
519         message.append(s.getStackHeight());
520         message.append(" ");
521         message.append(s.getStartingLevel());
522         message.append(" ");
523         message.append(s.getLinesPerLevel());
524         message.append(" ");
525         message.append(s.getLevelIncrease());
526         message.append(" ");
527         message.append(s.getLinesPerSpecial());
528         message.append(" ");
529         message.append(s.getSpecialAdded());
530         message.append(" ");
531         message.append(s.getSpecialCapacity());
532         message.append(" ");
533 
534         // blocks frequency
535         for (Block block : Block.values())
536         {
537             for (int j = 0; j < s.getOccurancy(block); j++)
538             {
539                 message.append(Integer.toString(block.ordinal() + 1));
540             }
541         }
542 
543         message.append(" ");
544 
545         // specials frequency
546         for (Special special : Special.values())
547         {
548             for (int j = 0; j < s.getOccurancy(special); j++)
549             {
550                 message.append(Integer.toString(special.ordinal() + 1));
551             }
552         }
553 
554         message.append(" ");
555         message.append(s.getAverageLevels() ? "1" : "0");
556         message.append(" ");
557         message.append(s.getClassicRules() ? "1" : "0");
558 
559         // extended parameter for 1.14 clients
560         if (s.getSameBlocks())
561         {
562             message.append(" ");
563             String hexstring = Integer.toHexString(m.getSeed()).toUpperCase();
564             // padding to 8 digits
565             for (int i = hexstring.length(); i < 8; i++)
566             {
567                 message.append("0");
568             }
569 
570             message.append(hexstring);
571         }
572 
573         return message.toString();
574     }
575 
576     public String translate(EndGameMessage m)
577     {
578         return "endgame";
579     }
580 
581     public String translate(PauseMessage m)
582     {
583         return "pause 1 ";
584     }
585 
586     public String translate(ResumeMessage m)
587     {
588         return "pause 0 ";
589     }
590 
591     public String translate(IngameMessage m)
592     {
593         return "ingame";
594     }
595 
596     public String translate(GmsgMessage m, Locale locale)
597     {
598         StringBuilder message = new StringBuilder();
599         message.append("gmsg ");
600         message.append(stripStyle(m.getText(locale)));
601         return message.toString();
602     }
603 
604     public String translate(LevelMessage m)
605     {
606         StringBuilder message = new StringBuilder();
607         message.append("lvl ");
608         message.append(m.getSlot());
609         message.append(" ");
610         message.append(m.getLevel());
611         return message.toString();
612     }
613 
614     public String translate(FieldMessage m)
615     {
616         StringBuilder message = new StringBuilder();
617         message.append("f ");
618         message.append(m.getSlot());
619         if (m.getField() != null)
620         {
621             message.append(" ");
622             message.append(m.getField());
623         }
624         return message.toString();
625     }
626 
627     public String translate(PlayerLostMessage m)
628     {
629         StringBuilder message = new StringBuilder();
630         message.append("playerlost ");
631         message.append(m.getSlot());
632         return message.toString();
633     }
634 
635     public String translate(PlayerWonMessage m)
636     {
637         StringBuilder message = new StringBuilder();
638         message.append("playerwon ");
639         message.append(m.getSlot());
640         return message.toString();
641     }
642 
643     public String translate(NoConnectingMessage m)
644     {
645         StringBuilder message = new StringBuilder();
646         message.append("noconnecting ");
647         message.append(m.getText());
648         return message.toString();
649     }
650 
651     public String translate(LinesAddedMessage m)
652     {
653         StringBuilder message = new StringBuilder();
654         message.append("sb ");
655         message.append(m.getSlot());
656         message.append(" cs").append(m.getLinesAdded()).append(" ");
657         message.append(m.getFromSlot());
658         return message.toString();
659     }
660 
661     public String translate(AddLineMessage m)
662     {
663         StringBuilder message = new StringBuilder();
664         message.append("sb ");
665         message.append(m.getSlot());
666         message.append(" a ");
667         message.append(m.getFromSlot());
668         return message.toString();
669     }
670 
671     public String translate(ClearLineMessage m)
672     {
673         StringBuilder message = new StringBuilder();
674         message.append("sb ");
675         message.append(m.getSlot());
676         message.append(" c ");
677         message.append(m.getFromSlot());
678         return message.toString();
679     }
680 
681     public String translate(NukeFieldMessage m)
682     {
683         StringBuilder message = new StringBuilder();
684         message.append("sb ");
685         message.append(m.getSlot());
686         message.append(" n ");
687         message.append(m.getFromSlot());
688         return message.toString();
689     }
690 
691     public String translate(RandomClearMessage m)
692     {
693         StringBuilder message = new StringBuilder();
694         message.append("sb ");
695         message.append(m.getSlot());
696         message.append(" r ");
697         message.append(m.getFromSlot());
698         return message.toString();
699     }
700 
701     public String translate(SwitchFieldsMessage m)
702     {
703         StringBuilder message = new StringBuilder();
704         message.append("sb ");
705         message.append(m.getSlot());
706         message.append(" s ");
707         message.append(m.getFromSlot());
708         return message.toString();
709     }
710 
711     public String translate(ClearSpecialsMessage m)
712     {
713         StringBuilder message = new StringBuilder();
714         message.append("sb ");
715         message.append(m.getSlot());
716         message.append(" b ");
717         message.append(m.getFromSlot());
718         return message.toString();
719     }
720 
721     public String translate(GravityMessage m)
722     {
723         StringBuilder message = new StringBuilder();
724         message.append("sb ");
725         message.append(m.getSlot());
726         message.append(" g ");
727         message.append(m.getFromSlot());
728         return message.toString();
729     }
730 
731     public String translate(BlockQuakeMessage m)
732     {
733         StringBuilder message = new StringBuilder();
734         message.append("sb ");
735         message.append(m.getSlot());
736         message.append(" q ");
737         message.append(m.getFromSlot());
738         return message.toString();
739     }
740 
741     public String translate(BlockBombMessage m)
742     {
743         StringBuilder message = new StringBuilder();
744         message.append("sb ");
745         message.append(m.getSlot());
746         message.append(" o ");
747         message.append(m.getFromSlot());
748         return message.toString();
749     }
750 
751     public String translate(SpectatorListMessage m, Locale locale)
752     {
753         PlineMessage pline = new PlineMessage();
754         pline.setKey("command.speclist.format", StringUtils.join(m.getSpectators().iterator(), ", "));
755 
756         return translate(pline, locale);
757     }
758 
759     public String translate(SmsgMessage m, Locale locale)
760     {
761         StringBuilder message = new StringBuilder();
762         String name = ((Client) m.getSource()).getUser().getName();
763 
764         PlineMessage pline = new PlineMessage();
765         pline.setKey("channel.spectator.message", name, m.getText());
766         message.append(translate(pline, locale));
767 
768         return message.toString();
769     }
770 
771     public String translate(WinlistMessage m, Locale locale)
772     {
773         StringBuilder message = new StringBuilder();
774         message.append("winlist");
775 
776         for (Score score : m.getScores())
777         {           
778             message.append(" ");
779             message.append(score.getType() == Score.TYPE_PLAYER ? "p" : "t");
780             message.append(score.getName());
781             message.append(";");
782             message.append(score.getScore());
783         }
784         return message.toString();
785     }
786 
787     public String translate(ClientInfoMessage m)
788     {
789         StringBuilder message = new StringBuilder();
790         message.append("clientinfo ");
791         message.append(m.getName());
792         message.append(" ");
793         message.append(m.getVersion());
794         return message.toString();
795     }
796 
797     public String translate(NoopMessage m)
798     {
799         return "";
800     }
801 
802     /***
803      * Return the map of the color and style codes for this protocol.
804      */
805     public Map<String, String> getStyles()
806     {
807         return styles;
808     }
809 
810     public String applyStyle(String text)
811     {
812         // to be optimized later
813         Map<String, String> styles = getStyles();
814         if (styles == null) {
815             return text;
816         }
817 
818         for (String key : styles.keySet())
819         {
820             String value = styles.get(key);
821             if (value == null)
822             {
823                 value = "";
824             }
825             text = text.replaceAll("<" + key + ">", value);
826             text = text.replaceAll("</" + key + ">", value);
827         }
828         return text;
829     }
830 
831     /***
832      * Removes the style tags from the specified text.
833      */
834     public String stripStyle(String text)
835     {
836         Map<String, String> styles = getStyles();
837         if (styles == null) {
838             return text;
839         }
840 
841         for (String key : styles.keySet())
842         {
843             text = text.replaceAll("<" + key + ">", "");
844             text = text.replaceAll("</" + key + ">", "");
845         }
846         return text;
847     }
848 
849     public char getEOL()
850     {
851         return 0xFF;
852     }
853 
854 
855     /***
856      * Decodes TetriNET client initialization string
857      *
858      * @param initString initialization string
859      * @return decoded string
860      * @throws IllegalArgumentException thrown if the string can't be decoded
861      */
862     public static String decode(String initString)
863     {
864         // check the size of the init string
865         if (initString.length() % 2 != 0)
866         {
867             throw new IllegalArgumentException("Invalid initialization string, the length is not even (" + initString + ")");
868         }
869 
870         // parse the hex values from the init string
871         int[] dec = new int[initString.length() / 2];
872 
873         try
874         {
875             for (int i = 0; i < dec.length; i++)
876             {
877                 dec[i] = Integer.parseInt(initString.substring(i * 2, i * 2 + 2), 16);
878             }
879         }
880         catch (NumberFormatException e)
881         {
882             throw new IllegalArgumentException("Invalid initialization string, illegal characters found (" + initString + ")", e);
883         }
884 
885         // find the hash pattern for a tetrinet client
886         String pattern = findHashPattern(dec, false);
887 
888         // find the hash pattern for a tetrifast client
889         if (pattern.length() == 0)
890         {
891             pattern = findHashPattern(dec, true);
892         }
893 
894         // check the size of the pattern found
895         if (pattern.length() == 0)
896         {
897             throw new IllegalArgumentException("Invalid initialization string, unable to find the pattern (" + initString + ")");
898         }
899 
900         // decode the string
901         StringBuilder s = new StringBuilder();
902 
903         for (int i = 1; i < dec.length; i++)
904         {
905             s.append((char) (((dec[i] ^ pattern.charAt((i - 1) % pattern.length())) + 255 - dec[i - 1]) % 255));
906         }
907 
908         return s.toString().replace((char) 0, (char) 255);
909     }
910 
911     private static String findHashPattern(int[] dec, boolean tetrifast)
912     {
913         // the first characters from the decoded string
914         char[] data = (tetrifast ? TetrifastProtocol.INIT_TOKEN : INIT_TOKEN).substring(0, 10).toCharArray();
915 
916         // compute the full hash
917         int[] hash = new int[data.length];
918 
919         for (int i = 0; i < data.length; i++)
920         {
921             hash[i] = ((data[i] + dec[i]) % 255) ^ dec[i + 1];
922         }
923 
924         // find the length of the hash
925         int length = 5;
926 
927         for (int i = 5; i == length && i > 0; i--)
928         {
929             for (int j = 0; j < data.length - length; j++)
930             {
931                 if (hash[j] != hash[j + length])
932                 {
933                     length--;
934                 }
935             }
936         }
937 
938         return new String(hash, 0, length);
939     }
940 
941     /***
942      * Return the initialization string for the specified user.
943      *
944      * @param nickname  the nickname of the client
945      * @param version   the version of the client
946      * @param ip        the IP of the server
947      * @param tetrifast is this a tetrifast client ?
948      */
949     public static String encode(String nickname, String version, byte[] ip, boolean tetrifast)
950     {
951         // compute the pattern
952         int p = 54 * (ip[0] & 0xFF) + 41 * (ip[1] & 0xFF) + 29 * (ip[2] & 0xFF) + 17 * (ip[3] & 0xFF);
953         char[] pattern = String.valueOf(p).toCharArray();
954 
955         // build the string to encode
956         char[] data = ((tetrifast ? TetrifastProtocol.INIT_TOKEN : INIT_TOKEN) + " " + nickname + " " + version).toCharArray();
957 
958         // build the encoded string
959         StringBuilder result = new StringBuilder();
960         char offset = 0x80;
961         result.append(toHex(offset));
962 
963         char previous = offset;
964 
965         for (int i = 0; i < data.length; i++)
966         {
967             char current = (char) (((previous + data[i]) % 255) ^ pattern[i % pattern.length]);
968             result.append(toHex(current));
969             previous = current;
970         }
971 
972         return result.toString().toUpperCase();
973     }
974 
975     /***
976      * Return the hex value of the specified byte on 2 digits.
977      */
978     private static String toHex(char c)
979     {
980         String h = Integer.toHexString(c);
981 
982         return h.length() > 1 ? h : "0" + h;
983     }
984 
985 }