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
104 return null;
105 }
106
107 String cmd = st.nextToken();
108 Message m = null;
109
110
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
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
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
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
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
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
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
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
243 else if ("newgame".equals(cmd))
244 {
245 m = new NewGameMessage();
246
247
248 }
249
250 else if ("endgame".equals(cmd))
251 {
252 m = new EndGameMessage();
253 }
254
255 else if ("ingame".equals(cmd))
256 {
257 m = new IngameMessage();
258 }
259
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
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
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
291 else if ("playernum".equals(cmd))
292 {
293 PlayerNumMessage num = new PlayerNumMessage();
294 num.setSlot(Integer.parseInt(st.nextToken()));
295 m = num;
296 }
297
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
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
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
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
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
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
560 if (s.getSameBlocks())
561 {
562 message.append(" ");
563 String hexstring = Integer.toHexString(m.getSeed()).toUpperCase();
564
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
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
865 if (initString.length() % 2 != 0)
866 {
867 throw new IllegalArgumentException("Invalid initialization string, the length is not even (" + initString + ")");
868 }
869
870
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
886 String pattern = findHashPattern(dec, false);
887
888
889 if (pattern.length() == 0)
890 {
891 pattern = findHashPattern(dec, true);
892 }
893
894
895 if (pattern.length() == 0)
896 {
897 throw new IllegalArgumentException("Invalid initialization string, unable to find the pattern (" + initString + ")");
898 }
899
900
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
914 char[] data = (tetrifast ? TetrifastProtocol.INIT_TOKEN : INIT_TOKEN).substring(0, 10).toCharArray();
915
916
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
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
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
956 char[] data = ((tetrifast ? TetrifastProtocol.INIT_TOKEN : INIT_TOKEN) + " " + nickname + " " + version).toCharArray();
957
958
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 }