1 /* PlayBack.c
2 *
3 * Kevin O'Connor 9/22/97
4 *
5 * Routines neccessary to playback a game recording.
6 */
7 #include "config.h"
8
9 #include INC_MACHINE_ENDIAN
10
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <unistd.h>
14 #include <sys/types.h>
15 #include <time.h>
16 #include <setjmp.h>
17 #include INC_SYS_TIME
18 #include INC_SYS_WAIT
19 #include INC_SYS_RESOURCE
20 #include INC_SYS_SELECT
21 #include <signal.h>
22 #include <fcntl.h>
23 #include <errno.h>
24 #include <pwd.h>
25 #include <string.h>
26 #include <ctype.h>
27 #include <arpa/inet.h>
28
29 #include "Wlib.h"
30 #include "defs.h"
31 #include "struct.h"
32 #include "data.h"
33 #include "packets.h"
34
35 #include "version.h"
36 #include "patchlevel.h"
37
38 #include "censor.h"
39 #include "cowapi.h"
40 #include "defaults.h"
41 #include "enter.h"
42 #include "getship.h"
43 #include "inform.h"
44 #include "input.h"
45 #include "lagmeter.h"
46 #include "map.h"
47 #include "newwin.h"
48 #include "pingstats.h"
49 #include "redraw.h"
50 #include "socket.h"
51 #include "spopt.h"
52 #include "stats.h"
53 #include "defs.h"
54 #include "playerlist.h"
55
56 #include "playback.h"
57
58 #ifdef RECORDGAME
59 extern int opened_info; /* counter for infowin *
60
61 *
62 * * popup, 6/1/93 LAB */
63 #define RETURNBASE 10
64
65 extern jmp_buf env;
66
67 int pbdelay = 200000;
68
69 int *pb_index;
70 int pb_num_index = 0;
71 int pb_goto = 0;
72 int pb_create_index;
73 int pb_index_exists;
74 int pb_num_context = 0;
75 int pb_num_fast_forward = 0;
76 int pb_sequence_count = 0;
77 int pb_stepping = 0; /* non-zero if doing a step */
78 int pb_snapping = 0; /* non-zero if taking camera snapshots */
79
80 const char *INDEX_FORMAT = "%d,%d,%d";
81 const int INDEX_GRANULARITY = 100;
82
83 static int packet_size = 0;
84
85 struct player dummyme;
86
87 struct player *packetsme;
88 struct player *displayme;
89
90 /* We want reverse-playback!!! */
91 #define REVERSE_PLAYBACK
92
93 void pb_read_index();
94 int pb_get_index(int sequence_num, int *jump_actual, int *offset, int *num_context);
95 int pb_index_compare(const void *a, const void *b);
96
97 /* Forward declarations for reverse playback */
98 void rpb_init(void);
99 void rpb_analyze(int diskpos, void *packet);
100 void rpb_dorev(char *buf);
101 static int readFromFile0();
102
103 struct player *packetsme;
104 struct player *displayme;
105
106 int
pbmain(char * name)107 pbmain(char *name)
108 {
109 int i;
110
111 #if 0 /* unused? */
112 int s_type;
113 #endif
114
115 char index_filename[FILENAME_MAX+1];
116 char context_filename[FILENAME_MAX+1];
117
118 strncpy(index_filename, recordFileName, FILENAME_MAX-4);
119 strncpy(context_filename, recordFileName, FILENAME_MAX-4);
120 index_filename[FILENAME_MAX-4] = '\0';
121 context_filename[FILENAME_MAX-4] = '\0';
122 strcat(index_filename, ".idx");
123 strcat(context_filename, ".cxt");
124
125 #ifdef REVERSE_PLAYBACK
126 rpb_init();
127 #endif
128
129 playback = PL_FORWARD;
130 pseudo[0] = defpasswd[0] = '\0';
131
132 i = setjmp(env); /* Error while initializing */
133 if (i >= RETURNBASE)
134 return (i - RETURNBASE); /* Terminate with retcode */
135
136 if (logFileName != NULL)
137 {
138 logFile = fopen(logFileName, "a");
139 if (logFile == NULL)
140 {
141 perror(logFileName);
142 return (1);
143 }
144 }
145
146 for (i = 0; i < 80; i++)
147 {
148 outmessage[i] = '\0';
149 }
150
151 SRANDOM(time(NULL));
152
153 initDefaults(deffile);
154
155 SRANDOM(getpid() * time(NULL));
156
157 newwin(display_host, name);
158
159 resetdefaults();
160 if (censorMessages)
161 initCensoring();
162
163 /* open memory...? */
164 openmem();
165
166 /* Open record file. */
167 recordFile = fopen(recordFileName, "rb");
168 if (recordFile == NULL)
169 {
170 perror(recordFileName);
171 return (1);
172
173 }
174
175 /* Open index files of recording. */
176 pb_index_exists = 1;
177 if (!pb_create_index) {
178 recordIndexFile = fopen(index_filename, "r");
179 if (recordIndexFile == NULL)
180 {
181 perror(index_filename);
182 pb_index_exists = 0;
183 }
184
185 recordContextFile = fopen(context_filename, "rb");
186 if (recordContextFile == NULL)
187 {
188 perror(context_filename);
189 pb_index_exists = 0;
190 }
191 }
192 else
193 pb_index_exists = 0;
194
195
196 /* Create index files if we were told to index the recording. */
197 if (pb_create_index && (recordIndexFile =
198 fopen(index_filename, "wb"))==NULL)
199 {
200 perror("Could not create index file.");
201 exit(1);
202 }
203 if (pb_create_index && (recordContextFile =
204 fopen(context_filename, "wb"))==NULL)
205 {
206 perror("Could not create context file.");
207 exit(1);
208 }
209 if (pb_create_index) {
210 pbdelay = 0;
211 playback = PL_FORWARD;
212 printf("Creating index.\n");
213 }
214
215 me = &dummyme;
216 myship = &(me->p_ship);
217 mystats = &(me->p_stats);
218
219 me->p_x = me->p_y = 50000;
220 getship(myship, CRUISER);
221 shipchange(CRUISER);
222 displayme = me;
223 packetsme = me;
224
225 /* Read the index file. We do this only once. */
226 pb_read_index();
227
228 /* Get first packet from file */
229 readFromFile();
230
231 displayme = packetsme;
232
233 lastm = mctl->mc_current;
234
235 mapAll();
236
237 /* Instructions from getname() */
238 MZERO(mystats, sizeof(struct stats));
239
240 mystats->st_tticks = 1;
241 for (i = 0; i < 95; i++)
242 {
243 mystats->st_keymap[i] = i + 32;
244 mystats->st_keymap[i + 96] = i + 32 + 96;
245
246 #ifdef MOUSE_AS_SHIFT
247 mystats->st_keymap[i + 192] = i + 32;
248 mystats->st_keymap[i + 288] = i + 32;
249 mystats->st_keymap[i + 384] = i + 32;
250 #endif
251 }
252 mystats->st_keymap[95] = 0;
253 mystats->st_flags = ST_MAPMODE + ST_NAMEMODE + ST_SHOWSHIELDS +
254 ST_KEEPPEACE + ST_SHOWLOCAL * 2 + ST_SHOWGLOBAL * 2;
255
256 /* End getname() */
257
258 phaserWindow = booleanDefault("phaserWindow", phaserWindow);
259
260
261 #ifdef AUTOKEY
262 /* autokey.c */
263 autoKeyDefaults();
264 #endif /* AUTOKEY */
265
266 initkeymap();
267
268 /* Set p_hostile to hostile, so if keeppeace is on, the guy starts off * *
269 * hating everyone (like a good fighter should) */
270 me->p_hostile = (FED | ROM | KLI | ORI);
271
272 if (!newDashboard)
273 {
274 char buf[128];
275
276 sprintf(buf,
277 "Maximum: %2d %3d %3d %3d %6d %3d %3d",
278 0, 0, 0, 0, 0, 0, 0);
279 W_WriteText(tstatw, 50, 27, textColor, buf, strlen(buf), W_RegularFont);
280 }
281
282 #ifdef AUTOKEY
283 if (autoKey)
284 {
285 /* XX: changes entire state of display */
286 W_AutoRepeatOff();
287 }
288 #endif
289
290 #ifdef SOUND
291 Init_Sound();
292 #endif
293
294 i = setjmp(env); /* Reentry point of game */
295 if (i >= RETURNBASE)
296 return (i - RETURNBASE); /* Terminate with retcode */
297
298 #if defined(SOUND)
299 #if defined(sgi)
300 Engine_Sound(ENG_OFF); /* Stop engine sound */
301 #else
302 /* text in sound.c:soundrefresh() says engine sound is not supported
303 Abort_Sound(ENGINE_SOUND); */
304 #endif
305 #endif
306
307 #ifdef nodef
308 /* Code from entrywindow() */
309 {
310 run_clock(time(0));
311
312 if (remap[me->p_team] == NOBODY)
313 RedrawPlayerList(); /* When you first login */
314 else
315 UpdatePlayerList(); /* Otherwise */
316
317 }
318 /* End entrywindow() */
319 #endif
320
321 redrawall = 2;
322
323 getship(myship, myship->s_type);
324 shipchange(myship->s_type);
325 enter();
326 calibrate_stats();
327 W_ClearWindow(w);
328
329 me->p_status = PALIVE; /* Put player in game */
330 PlistNoteUpdate(me->p_no);
331
332 if (showStats) /* Default showstats are on.
333 *
334 */
335 W_MapWindow(statwin);
336
337 if (W_IsMapped(lMeter))
338 redrawLMeter();
339
340 if (W_IsMapped(pStats)) /* support ping stuff */
341 redrawPStats();
342
343 #ifdef SOUND
344 #if defined(sgi)
345 Engine_Sound(ENG_ON);
346 #else
347 /* text in sound.c:soundrefresh() says engine sound is not supported
348 Play_Sound(ENGINE_SOUND);
349 */
350 #endif
351 Play_Sound(ENTER_SHIP_SOUND);
352 #endif
353
354 #ifdef HOCKEY_LINES
355 init_hockey_lines();
356 #endif
357
358 while (1)
359 {
360
361 #ifdef nodef
362 fd_set readfds;
363 struct timeval timeout;
364 int xsock = W_Socket();
365
366 timeout.tv_sec = 0;
367 timeout.tv_usec = 0;
368 #endif
369
370 if (keepInfo > 0 && opened_info != -2 && /* 6/1/93 LAB */
371 opened_info < 0 && infomapped)
372 destroyInfo();
373
374 if (keepInfo > 0 && opened_info != -2)
375 opened_info--;
376
377 while (W_EventsQueuedCk())
378 {
379 process_event();
380 /* W_Flush(); */
381 }
382
383 intrupt(NULL);
384 W_Flush();
385 if (!pb_stepping) usleep(pbdelay);
386 if (pb_snapping) W_CameraSnap(w);
387 }
388 }
389
390 void
pbsetspeed(char key)391 pbsetspeed(char key)
392 {
393 #define JUMP_MAX 7
394 static char jump_str[JUMP_MAX] = "";
395 static int jump_idx = 0;
396 static int jump_on = 0;
397 static int tmp_playback;
398 static int tmp_pbdelay = -1;
399
400 int old_playback;
401
402 /* Used at end of function. */
403 old_playback = playback;
404
405 /* Read in sequence number to jump to. */
406 if (jump_on) {
407 switch (key) {
408 case '0': case '1': case '2': case '3': case '4': case '5':
409 case '6': case '7': case '8': case '9':
410
411 /* Ignore further input if we run out of room. */
412 if (jump_idx > JUMP_MAX - 2)
413 break;
414
415 /* Get the next number. */
416 jump_str[jump_idx] = key;
417 jump_idx++;
418 jump_str[jump_idx] = '\0';
419 printf("Jump input: %s\n", jump_str);
420
421 break;
422 case 'J': /* abort jump */
423 jump_on = 0;
424 playback = tmp_playback;
425 printf("Aborting jump.\n");
426 jump_str[0] = '\0';
427 break;
428 default: /* Done entering jump data when non-digit key hit. */
429 jump_str[jump_idx] = '\0';
430
431 /* Convert string to number. */
432 pb_goto = strtol(jump_str, NULL, 10);
433 if (pb_goto < 1)
434 pb_goto = 1;
435
436 jump_on = 0;
437 jump_str[0] = '\0';
438 break;
439 }
440 goto end;
441 }
442
443 if (playback == PL_PAUSE)
444 playback = PL_FORWARD;
445 switch (key)
446 {
447 case 0x8: /* step backward one frame */
448 pb_stepping++;
449 playback = PL_REVERSE;
450 break;
451 case 0xd: /* step forward one frame */
452 pb_stepping++;
453 break;
454 case ' ': /* turn on or off single step */
455 if (old_playback == PL_PAUSE)
456 playback = PL_FORWARD;
457 else
458 pb_stepping++;
459 break;
460 case '0':
461 playback = PL_PAUSE;
462 break;
463 case '1':
464 tmp_pbdelay = pbdelay = 800000;
465 break;
466 case '2':
467 tmp_pbdelay = pbdelay = 400000;
468 break;
469 case '3':
470 tmp_pbdelay = pbdelay = 200000;
471 break;
472 case '4':
473 tmp_pbdelay = pbdelay = 100000;
474 break;
475 case '5':
476 tmp_pbdelay = pbdelay = 50000;
477 break;
478 case '6':
479 tmp_pbdelay = pbdelay = 25000;
480 break;
481 case '7':
482 tmp_pbdelay = pbdelay = 12500;
483 break;
484 case '8':
485 tmp_pbdelay = pbdelay = 6250;
486 break;
487 case '9':
488 case '#':
489 case '!':
490 case '@':
491 case '%':
492 tmp_pbdelay = pbdelay = 0;
493 break;
494 case '<':
495 if (tmp_pbdelay == -1)
496 tmp_pbdelay = pbdelay /= 2;
497 else
498 pbdelay = tmp_pbdelay /= 2;
499 break;
500 case '>':
501 if (tmp_pbdelay == -1)
502 tmp_pbdelay = pbdelay *= 2;
503 else
504 pbdelay = tmp_pbdelay *= 2;
505 break;
506 case 'R':
507 {
508 #ifdef REVERSE_PLAYBACK
509 rpb_init();
510 #endif
511 fseek (recordFile, 0, SEEK_SET);
512 playback = PL_FORWARD;
513 pb_sequence_count = 0;
514 break;
515 }
516 case '(':
517 playback = PL_REVERSE;
518 break;
519 case ')':
520 playback = PL_FORWARD;
521 break;
522 case 'j':
523 printf("Jumping..\n");
524 tmp_playback = playback;
525 playback = PL_PAUSE;
526 jump_on = 1;
527 jump_idx = 0;
528 jump_str[0] = '\0';
529 break;
530 case 's':
531 fprintf(stderr, "toggle-snap\n");
532 pb_snapping = ~pb_snapping;
533 break;
534 }
535
536 end:
537 /* If we are paused, set the delay to something reasonable. */
538 if (playback == PL_PAUSE && old_playback != PL_PAUSE) {
539 tmp_pbdelay = pbdelay;
540 pbdelay = 100000;
541 }
542
543 /* If we were paused, but now are not, reset the original delay. */
544 if (old_playback == PL_PAUSE && playback != PL_PAUSE && tmp_pbdelay != -1) {
545 pbdelay = tmp_pbdelay;
546 tmp_pbdelay = -1;
547 }
548 }
549
550 void
pblockplayer(int who)551 pblockplayer(int who)
552 {
553 me = displayme = &players[who];
554 }
555
556 void
pblockplanet(int pl)557 pblockplanet(int pl)
558 {
559 me = &dummyme;
560 displayme->p_x = planets[pl].pl_x;
561 displayme->p_y = planets[pl].pl_y;
562 displayme = me;
563 }
564
565 int
ckRecordPacket(char packet)566 ckRecordPacket(char packet)
567 {
568 return 1;
569 #if 0
570 switch (packet)
571 {
572 case SP_MESSAGE:
573 case SP_S_MESSAGE:
574 case SP_S_WARNING:
575 case SP_WARNING:
576 /* SP_MOTD */
577
578 case SP_PLAYER_INFO:
579 case SP_PLAYER:
580 case SP_S_PLAYER:
581 case SP_KILLS:
582 case SP_S_KILLS:
583
584 case SP_TORP_INFO:
585 case SP_TORP:
586 case SP_S_TORP:
587 case SP_S_TORP_INFO:
588 case SP_S_8_TORP:
589 case SP_PLASMA_INFO:
590 case SP_PLASMA:
591
592 case SP_PHASER:
593 case SP_S_PHASER:
594
595 case SP_YOU:
596 case SP_S_YOU:
597 case SP_S_YOU_SS:
598
599 case SP_STATUS:
600
601 case SP_PLANET:
602 case SP_S_PLANET:
603 case SP_PLANET_LOC:
604
605 case SP_FLAGS:
606 /* case SP_MASK: */
607 case SP_PSTATUS:
608 case SP_HOSTILE:
609 case SP_STATS:
610 case SP_S_STATS:
611 case SP_PL_LOGIN:
612 case SP_SHIP_CAP:
613
614
615 case SP_SEQUENCE:
616 case SP_SC_SEQUENCE:
617 case SP_S_SEQUENCE:
618
619 return 1;
620 }
621 return 0;
622 #endif
623 }
624
625 extern struct packet_handler handlers[];
626
627 /**
628 * Read the next packet from the record file and call the appropiate handler.
629 *
630 * @param buf Stores the next packet.
631 *
632 * @return 1 if EOF or error, or 0 for success.
633 */
634 int
pb_dopacket(char * buf)635 pb_dopacket(char *buf)
636 {
637 int size, count;
638
639 count = fread(buf, 1, 4, recordFile);
640 if (count < 4)
641 {
642 return 1;
643 }
644
645
646 /* Determine how many more bytes we need to read. */
647 size = handlers[(unsigned char) buf[0]].size;
648 if (size == -1)
649 {
650 if (buf[0] == SP_S_MESSAGE)
651 {
652 /* UGH. SP_S_MESSAGE needs next word to calculate size */
653 count += fread(buf+count, 1, 4, recordFile);
654 if (count < 8)
655 {
656 return 1;
657 }
658 }
659 size = getvpsize(buf);
660 }
661
662 packet_size = size;
663
664 /* Read the rest of the packet. */
665 if (size > count)
666 count += fread(buf+count, 1, size-count, recordFile);
667 if (debug)
668 printf("Reading packet %d\n", buf[0]);
669
670 /* If we couldn't read enough */
671 if (count < size)
672 {
673 return 1;
674 }
675
676 /* Call the packet handler and return success (zero). */
677 (*(handlers[(unsigned char) buf[0]].handler)) (buf
678 #ifdef CORRUPTED_PACKETS
679 ,recordFile
680 #endif
681 );
682 return 0;
683 }
684
685
readFromFile()686 int readFromFile() {
687 int offset = ftell(recordFile);
688 int jump_actual; /* The sequence number we actually jump to. */
689 FILE *tmp_file;
690 int result;
691 int num_fast_forward;
692
693 /* If jumping to a spot in the recording, read the required number of
694 context packets first. */
695 if (pb_goto) {
696 playback = PL_FORWARD;
697 num_fast_forward = pb_get_index(pb_goto, &jump_actual,
698 &offset, &pb_num_context);
699
700 if (pb_num_context > 0) {
701 printf("Reading in %d context packets..\n", pb_num_context);
702 rewind(recordContextFile);
703 tmp_file = recordFile;
704 recordFile = recordContextFile;
705 pb_sequence_count = 0;
706
707 readFromFile0();
708
709 recordFile = tmp_file;
710 }
711
712 pb_sequence_count = jump_actual - 1;
713 pb_num_fast_forward = num_fast_forward;
714
715 #ifdef REVERSE_PLAYBACK
716 rpb_init();
717 #endif
718 }
719
720 /* Get up to the next sequence packet. */
721 fseek(recordFile, offset, SEEK_SET);
722 result = readFromFile0();
723
724 /* If we jumped pause playback and reset state back to normal. */
725 if (pb_goto) {
726 playback = PL_PAUSE;
727 pb_goto = 0;
728
729 printf("Done jumping.\n");
730 }
731
732 return result;
733 }
734
735
readFromFile0()736 static int readFromFile0()
737 {
738 #define MAXPACKETSIZE 128
739 static uint aligned_buf[MAXPACKETSIZE/sizeof(uint)];
740 static int num_context_written = 0;
741
742 char *buf = (char *) &aligned_buf;
743 int diskpos;
744 int sequence_start_pos = -1;
745 int sequence_num_context = 0;
746
747 #if 0 /* unused? */
748 int read_context = 0;
749 #endif
750
751
752 if (playback == PL_PAUSE)
753 return 1;
754 #ifdef REVERSE_PLAYBACK
755 else if (playback == PL_REVERSE) {
756 diskpos = ftell(recordFile);
757 me = packetsme;
758
759 rpb_dorev(buf);
760
761 packetsme = me;
762 me = displayme;
763
764 if (pb_stepping) {
765 playback = PL_PAUSE;
766 pb_stepping = 0;
767 }
768
769 return 1;
770 }
771 #endif
772
773 if (pb_stepping) {
774 playback = PL_PAUSE;
775 pb_stepping = 0;
776 }
777
778 /* Read packets. */
779 while (1) {
780 diskpos = ftell(recordFile);
781 me = packetsme;
782
783 if (sequence_start_pos == -1)
784 sequence_start_pos = diskpos;
785
786 /* Read a packet and call it's handler. */
787 if (pb_dopacket(buf)) {
788 /* End of file reached! */
789 playback = PL_PAUSE;
790 pb_num_fast_forward = 0;
791 packetsme = me;
792 me = displayme;
793 printf("End of file.\n");
794
795 if (pb_create_index)
796 exit(0);
797
798 return 1;
799 }
800
801 #ifdef REVERSE_PLAYBACK
802 if (!pb_create_index && !pb_num_context)
803 rpb_analyze(diskpos, buf);
804 #endif
805
806 /* If we are reading in context packts.. */
807 if (pb_num_context) {
808 pb_num_context--;
809
810 if (pb_num_context < 1) {
811 printf("Done reading context packets.\n");
812 return 1;
813 }
814 }
815
816 /* If we are creating an index of the recording, write packet if necessary */
817 if (pb_create_index && PB_CONTEXT(buf[0]) ) {
818 if (!fwrite(buf, 1, packet_size, recordContextFile)) {
819 perror("Bad write on context file.");
820 exit(1);
821 }
822 num_context_written++;
823 sequence_num_context++;
824 }
825
826 packetsme = me;
827 me = displayme;
828
829 if (buf[0] == SP_SEQUENCE || buf[0] == SP_SC_SEQUENCE
830 || buf[0] == SP_S_SEQUENCE)
831 {
832 pb_sequence_count++;
833
834 if (pb_num_fast_forward > 0)
835 pb_num_fast_forward--;
836
837 /* If we are creating an index of the recording, write out what
838 sequence number we are on, where we are in the file, and how
839 many context packets we have to read back in to get back
840 here. */
841 if ( pb_create_index && !(pb_sequence_count % INDEX_GRANULARITY) ) {
842 fprintf(recordIndexFile, INDEX_FORMAT, pb_sequence_count,
843 sequence_start_pos, num_context_written - sequence_num_context);
844 fprintf(recordIndexFile, "\n");
845 }
846
847 if ( pb_create_index && !(pb_sequence_count % 1000) )
848 printf("Indexed %d sequences.\n", pb_sequence_count);
849
850 /* For testing, cut this short. A recording should be under
851 100k sequences */
852 if (pb_sequence_count > 1000000) {
853 printf("I've seen enough, exiting!\n");
854 exit(0);
855 }
856
857
858 sequence_start_pos = -1;
859 sequence_num_context = 0;
860
861 /* Return 1 if redraw needed. */
862 if (pb_create_index || pb_num_fast_forward)
863 ;
864 else
865 return 1;
866
867 } /* if sequence packet */
868
869 } /* while(1) */
870
871 }
872
pb_read_index()873 void pb_read_index() {
874 const int MAXLINE = 100;
875 char line[MAXLINE+1];
876 int num_lines = 0;
877
878 if (!pb_index_exists) {
879 pb_index = NULL;
880 pb_num_index = 0;
881 return;
882 }
883
884 rewind(recordIndexFile);
885
886 /* Count how many lines we have. */
887 while (fgets(line, MAXLINE, recordIndexFile))
888 num_lines++;
889
890 pb_index = calloc(3*num_lines, sizeof(int));
891 pb_num_index = num_lines;
892
893 rewind(recordIndexFile);
894
895 {
896 int i = 0;
897 while (fscanf(recordIndexFile,
898 INDEX_FORMAT,
899 &pb_index[i*3], /* Sequence num */
900 &pb_index[i*3 + 1], /* offset into file */
901 &pb_index[i*3 + 2]) /* number of context packets */
902 > 0)
903 {
904 i++;
905 }
906 }
907
908 }
909
pb_get_index(int sequence_num,int * p_jump_actual,int * offset,int * num_context)910 int pb_get_index(int sequence_num, int *p_jump_actual, int *offset, int *num_context) {
911 int *found;
912 int jump_actual;
913 int num_left;
914 int first_index_num;
915 int last_index_num;
916
917 /* If the index doesn't exist go to the beginning and fast forward */
918 if (!pb_index_exists){
919 jump_actual = 1;
920 *offset = 0;
921 *num_context = 0;
922 num_left = sequence_num - 1;
923 goto end;
924 }
925
926 first_index_num = pb_index[0];
927 last_index_num = pb_index[(pb_num_index-1) * 3];
928
929 /* Convert the sequence number they want to goto to the closest sequence
930 we have indexed that is earlier. For example, if they want to go to
931 #801 and we index every 100 sequences, look for #700. We go back an
932 extra 100 so we can read in recent packets that contain info like what
933 ships are cloaked, armies carried, etc. */
934 jump_actual = (sequence_num / INDEX_GRANULARITY) * INDEX_GRANULARITY - INDEX_GRANULARITY;
935
936 /* If they go before the first index, take them to the
937 beginning and fast forward. */
938 if (jump_actual < first_index_num) {
939 jump_actual = 1;
940 *offset = 0;
941 *num_context = 0;
942 num_left = sequence_num - 1;
943 goto end;
944 }
945
946 /* If they go too far ahead, take them to the last sequence indexed. */
947 if (jump_actual > last_index_num) {
948 printf("Jumping ahead to last sequence.\n");
949 jump_actual = last_index_num;
950 }
951
952 /* Calculate how many sequences we have to fast forward after jumping ahead. */
953 num_left = sequence_num - jump_actual;
954
955 found = bsearch(&jump_actual, pb_index, pb_num_index, sizeof(int)*3, pb_index_compare);
956 if (found == NULL) {
957 /* Shouldn't happen. */
958 jump_actual = 1;
959 *offset = 0;
960 *num_context = 0;
961 num_left = sequence_num - 1;
962 }
963 else {
964 int *f = (int *)found;
965 *offset = *(f+1);
966 *num_context = *(f+2);
967 }
968
969
970 end:
971 *p_jump_actual = jump_actual;
972 num_left++;
973 return num_left;
974 }
975
pb_index_compare(const void * a,const void * b)976 int pb_index_compare(const void *a, const void *b) {
977 int x = *(int *)a;
978 int y = *(int *)b;
979
980 if (x > y)
981 return 1;
982 else if (x < y)
983 return -1;
984 else
985 return 0;
986
987 }
988
989 #ifdef REVERSE_PLAYBACK
990
991 /**************************** Reverse playback ***************************/
992 /**************************** Reverse playback ***************************/
993 /**************************** Reverse playback ***************************/
994
995 /* Reverse-Playback -
996 * Reverse playback does just what it implies. It allows the client to
997 * play a game recording in reverse. It is a nifty feature useful
998 * for analyzing a game. With forward only playback, if a desired point
999 * is played over, the only way to see that point again would be to reset
1000 * the playback to the very beginning and forward-play the recording until
1001 * the point is re-located. With a long recording this quickly becomes
1002 * infeasible. With reverse recording one can simply forward and reverse
1003 * over interesting points.
1004 *
1005 * That said, no sane person would have implemented this without
1006 * getting paid for it...
1007 *
1008 * Attempting to reset the netrek state to a position in the past is
1009 * a considerable challenge when the input is a dynamic stream.
1010 * I only implemented it because I thought it was an interesting challenge..
1011 * Ok, so I dont document my code very well - I'm not getting paid - so
1012 * here is a brief run-down of how I accomplished this task.
1013 *
1014 * First step is to analyze all incoming packets. Each packet is then
1015 * flagged with a unique handler for each piece of unique data the server
1016 * sent. (IE. the SP_HOSTILE packet contains RPB_PLAYER_HOSTILE data, while
1017 * SP_YOU contains RPB_PLAYER_HOSTILE, RPB_PLAYER_FUEL, RPB_PLAYER_ARMIES,
1018 * and RPB_PLAYER_FLAGS. SP_TORP contains 1 RPB_TORP_POSITION data, while
1019 * SP_S_TORP contains 8 RPB_TORP_POSITIONs)
1020 *
1021 * The next step is to build a table from all these RPB_ server information
1022 * handles. The key to this table is the ability to find the previous
1023 * packet that contained any given RPB_ server information, and be able
1024 * to backtrack through all the packets that the server ever sent that also
1025 * updated that RPB_ data. In addition to being able to access the table
1026 * via RPB_ keys, it must also be accessed via relative disk
1027 * positions. The table is in essence a series of multiply linked
1028 * linked-lists.
1029 * (I implemented the table using a series of singly linked lists that
1030 * are contained within a dynamicly resizing array of linked-list nodes.)
1031 *
1032 * The bad news is, the table uses alot of memory. There is no way
1033 * around this.
1034 * The good news is, any virtual OS should be able to handle this with
1035 * no problems. (Only recently used items are referenced within the
1036 * array, thus the OS can swap out earlier pages of the array.)
1037 *
1038 * Once it comes time to reverse playback, a frame is chosen to be replayed.
1039 * The program then finds all the packets that contributed to that
1040 * frame's information, and one by one re-reads them. As long as
1041 * there are RPB_ handles for each piece of data the server can send,
1042 * reconstructing the frame is trivial.
1043 *
1044 * Notes: This doesn't work for messages.. There is lots of room
1045 * for improvement.. Reverse playback is pretty slow.. (but there
1046 * is no need to reverse only 1 update at a time..) I'm pretty
1047 * sure this will start to exhibit 'weird' effects in high packet loss..
1048 * -Kevin O'Connor 03/17/98
1049 */
1050
1051
1052 /* List of integer handles.
1053 * These handles represent each piece of information the server
1054 * can send within a packet
1055 */
1056 #define RPB_NORMAL_SEQUENCE 0
1057 #define RPB_NORMAL_STATUS 1
1058 #define RPB_NORMAL_MAX 2
1059
1060 #define RPB_PLAYER_OFFSET (RPB_NORMAL_MAX)
1061 #define RPB_PLAYER_INFO 0
1062 #define RPB_PLAYER_LOGIN 1
1063 #define RPB_PLAYER_HOSTILE 2
1064 #define RPB_PLAYER_STATS 3
1065 #define RPB_PLAYER_FLAGS 4
1066 #define RPB_PLAYER_KILLS 5
1067 #define RPB_PLAYER_POSITION 6
1068 #define RPB_PLAYER_FUEL 7
1069 #define RPB_PLAYER_ARMIES 8
1070 #define RPB_PLAYER_STATUS 9
1071 #define RPB_PLAYER_PHASER 10
1072 #define RPB_PLAYER_MAX 11
1073
1074 #define RPB_PLASMA_OFFSET (RPB_PLAYER_OFFSET + RPB_PLAYER_MAX*MAXPLAYER)
1075 #define RPB_PLASMA_POSITION 0
1076 #define RPB_PLASMA_INFO 1
1077 #define RPB_PLASMA_MAX 2
1078
1079 #define RPB_TORP_OFFSET (RPB_PLASMA_OFFSET + RPB_PLASMA_MAX*MAXPLASMA*MAXPLAYER)
1080 #define RPB_TORP_POSITION 0
1081 #define RPB_TORP_INFO 1
1082 #define RPB_TORP_MAX 2
1083
1084 #define RPB_PLANET_OFFSET (RPB_TORP_OFFSET + RPB_TORP_MAX*MAXTORP*MAXPLAYER)
1085 #define RPB_PLANET_INFO 0
1086 #define RPB_PLANET_POSITION 1
1087 #define RPB_PLANET_MAX 2
1088
1089 #define RPB_TOTAL (RPB_PLANET_OFFSET + RPB_PLANET_MAX*MAXPLANETS)
1090
1091 struct LocPacketInfo {
1092 int prev;
1093 uint diskpos;
1094 };
1095
1096 struct LocPacketInfo *header = NULL;
1097 int lastPos[RPB_TOTAL];
1098 int max_alloc = 0;
1099 int current;
1100
1101 /* Number of LocPacketInfo to re-allocate each time an expansion
1102 * is required. This should be on the edge of a page boundry to reduce
1103 * system overhead. */
1104 #define EXPANDCOUNT 512
1105
1106 /* Initialize the data structures used in reverse playback */
1107 void
rpb_init(void)1108 rpb_init(void)
1109 {
1110 int i;
1111 for (i=0; i<RPB_TOTAL; i++)
1112 lastPos[i] = -1;
1113 current = 0;
1114 }
1115
1116 /* Add a server information handler to the current table */
1117 void
rpb_insert(int diskpos,int hdl)1118 rpb_insert(int diskpos, int hdl)
1119 {
1120 if (current >= max_alloc)
1121 {
1122 /* Expand table size */
1123 max_alloc += EXPANDCOUNT;
1124 header = realloc(header, max_alloc * sizeof(struct LocPacketInfo));
1125 }
1126 header[current].prev = lastPos[hdl];
1127 header[current].diskpos = diskpos;
1128 lastPos[hdl] = current;
1129 current++;
1130 }
1131
1132 /* Given a packet, generate a set of integer handles for the data
1133 * contained within the packet, and then add them to the current table */
1134 void
rpb_analyze(int diskpos,void * packet)1135 rpb_analyze(int diskpos, void *packet)
1136 {
1137 int id;
1138
1139 switch (*(char *) packet)
1140 {
1141 case SP_STATUS:
1142 rpb_insert(diskpos, RPB_NORMAL_STATUS);
1143 break;
1144 case SP_SEQUENCE:
1145 case SP_S_SEQUENCE:
1146 case SP_SC_SEQUENCE:
1147 rpb_insert(diskpos, RPB_NORMAL_SEQUENCE);
1148 break;
1149
1150 case SP_PLAYER_INFO:
1151 id = ((struct plyr_info_spacket *)packet)->pnum;
1152 rpb_insert(diskpos, RPB_PLAYER_OFFSET + id * RPB_PLAYER_MAX
1153 + RPB_PLAYER_INFO);
1154 break;
1155 case SP_PL_LOGIN:
1156 id = ((struct plyr_login_spacket *)packet)->pnum;
1157 rpb_insert(diskpos, RPB_PLAYER_OFFSET + id * RPB_PLAYER_MAX
1158 + RPB_PLAYER_LOGIN);
1159 break;
1160 case SP_HOSTILE:
1161 id = ((struct hostile_spacket *)packet)->pnum;
1162 rpb_insert(diskpos, RPB_PLAYER_OFFSET + id * RPB_PLAYER_MAX
1163 + RPB_PLAYER_HOSTILE);
1164 break;
1165 case SP_STATS:
1166 id = ((struct stats_spacket *)packet)->pnum;
1167 rpb_insert(diskpos, RPB_PLAYER_OFFSET + id * RPB_PLAYER_MAX
1168 + RPB_PLAYER_STATS);
1169 break;
1170 case SP_FLAGS:
1171 id = ((struct flags_spacket *)packet)->pnum;
1172 rpb_insert(diskpos, RPB_PLAYER_OFFSET + id * RPB_PLAYER_MAX
1173 + RPB_PLAYER_FLAGS);
1174 break;
1175 case SP_KILLS:
1176 id = ((struct kills_spacket *)packet)->pnum;
1177 rpb_insert(diskpos, RPB_PLAYER_OFFSET + id * RPB_PLAYER_MAX
1178 + RPB_PLAYER_KILLS);
1179 break;
1180 case SP_PLAYER:
1181 id = ((struct player_spacket *)packet)->pnum;
1182 rpb_insert(diskpos, RPB_PLAYER_OFFSET + id * RPB_PLAYER_MAX
1183 + RPB_PLAYER_POSITION);
1184 break;
1185 case SP_YOU:
1186 id = ((struct you_spacket *)packet)->pnum;
1187 rpb_insert(diskpos, RPB_PLAYER_OFFSET + id * RPB_PLAYER_MAX
1188 + RPB_PLAYER_HOSTILE);
1189 rpb_insert(diskpos, RPB_PLAYER_OFFSET + id * RPB_PLAYER_MAX
1190 + RPB_PLAYER_FLAGS);
1191 rpb_insert(diskpos, RPB_PLAYER_OFFSET + id * RPB_PLAYER_MAX
1192 + RPB_PLAYER_FUEL);
1193 rpb_insert(diskpos, RPB_PLAYER_OFFSET + id * RPB_PLAYER_MAX
1194 + RPB_PLAYER_ARMIES);
1195 break;
1196 case SP_PSTATUS:
1197 id = ((struct pstatus_spacket *)packet)->pnum;
1198 rpb_insert(diskpos, RPB_PLAYER_OFFSET + id * RPB_PLAYER_MAX
1199 + RPB_PLAYER_STATUS);
1200 break;
1201 case SP_S_YOU:
1202 id = ((struct youshort_spacket *)packet)->pnum;
1203 rpb_insert(diskpos, RPB_PLAYER_OFFSET + id * RPB_PLAYER_MAX
1204 + RPB_PLAYER_HOSTILE);
1205 rpb_insert(diskpos, RPB_PLAYER_OFFSET + id * RPB_PLAYER_MAX
1206 + RPB_PLAYER_FLAGS);
1207 rpb_insert(diskpos, RPB_PLAYER_OFFSET + id * RPB_PLAYER_MAX
1208 + RPB_PLAYER_ARMIES);
1209 break;
1210 case SP_S_YOU_SS:
1211 if (F_many_self)
1212 id = ((struct youss_spacket *)packet)->pad1;
1213 else
1214 id = me->p_no;
1215 rpb_insert(diskpos, RPB_PLAYER_OFFSET + id * RPB_PLAYER_MAX
1216 + RPB_PLAYER_FUEL);
1217 break;
1218 case SP_S_KILLS:
1219 {
1220 int i, numofkills;
1221
1222 numofkills = ((unsigned char *) packet)[1];
1223
1224 for (i = 0; i < numofkills; i++)
1225 {
1226 id = ((unsigned char *) packet)[i*2+3] >> 2;
1227 rpb_insert(diskpos, RPB_PLAYER_OFFSET + id * RPB_PLAYER_MAX
1228 + RPB_PLAYER_KILLS);
1229 }
1230 }
1231 break;
1232 case SP_S_PLAYER:
1233 {
1234 int i, numofplayers, offset;
1235 unsigned char *sbuf = (unsigned char *) packet;
1236
1237 numofplayers = sbuf[1] & 0x3f;
1238
1239 if (sbuf[1] & 128)
1240 {
1241 offset = 32;
1242 sbuf += 4;
1243 }
1244 else if (sbuf[1] & 64)
1245 {
1246 offset = 0;
1247 if (shortversion == SHORTVERSION) {
1248 if (sbuf[2] == 2) {
1249 sbuf += 8;
1250 } else if (sbuf[2] == 1) {
1251 sbuf += 4;
1252 }
1253 }
1254 }
1255 else
1256 {
1257 offset = 0;
1258 sbuf += 12;
1259 id = me->p_no;
1260 rpb_insert(diskpos, RPB_PLAYER_OFFSET + id * RPB_PLAYER_MAX
1261 + RPB_PLAYER_POSITION);
1262 }
1263
1264 for (i = 0; i < numofplayers; i++)
1265 {
1266 id = (sbuf[i*4] & 0x1f) + offset;
1267 rpb_insert(diskpos, RPB_PLAYER_OFFSET + id * RPB_PLAYER_MAX
1268 + RPB_PLAYER_POSITION);
1269 }
1270 }
1271 break;
1272 case SP_S_STATS:
1273 id = ((struct stats_s_spacket *)packet)->pnum;
1274 rpb_insert(diskpos, RPB_PLAYER_OFFSET + id * RPB_PLAYER_MAX
1275 + RPB_PLAYER_STATS);
1276 break;
1277 case SP_PHASER:
1278 id = ((struct phaser_spacket *)packet)->pnum;
1279 rpb_insert(diskpos, RPB_PLAYER_OFFSET + id * RPB_PLAYER_MAX
1280 + RPB_PLAYER_PHASER);
1281 break;
1282 case SP_S_PHASER:
1283 id = ((struct phaser_s_spacket *)packet)->pnum & 0x3f;
1284 rpb_insert(diskpos, RPB_PLAYER_OFFSET + id * RPB_PLAYER_MAX
1285 + RPB_PLAYER_PHASER);
1286 break;
1287
1288 case SP_PLASMA_INFO:
1289 id = ntohs(((struct plasma_info_spacket *)packet)->pnum);
1290 rpb_insert(diskpos, RPB_PLASMA_OFFSET + id * RPB_PLASMA_MAX
1291 + RPB_PLASMA_INFO);
1292 break;
1293 case SP_PLASMA:
1294 id = ntohs(((struct plasma_spacket *)packet)->pnum);
1295 rpb_insert(diskpos, RPB_PLASMA_OFFSET + id * RPB_PLASMA_MAX
1296 + RPB_PLASMA_POSITION);
1297 break;
1298
1299 case SP_TORP_INFO:
1300 id = ntohs(((struct torp_info_spacket *)packet)->tnum);
1301 rpb_insert(diskpos, RPB_TORP_OFFSET + id * RPB_TORP_MAX
1302 + RPB_TORP_INFO);
1303 break;
1304 case SP_TORP:
1305 id = ntohs(((struct torp_spacket *)packet)->tnum);
1306 rpb_insert(diskpos, RPB_TORP_OFFSET + id * RPB_TORP_MAX
1307 + RPB_TORP_POSITION);
1308 break;
1309 case SP_S_8_TORP:
1310 case SP_S_TORP:
1311 case SP_S_TORP_INFO:
1312 {
1313 int i;
1314
1315 if (*(unsigned char *)packet == SP_S_8_TORP)
1316 id = ((unsigned char *)packet)[1] * 8;
1317 else
1318 id = ((unsigned char *)packet)[2] * 8;
1319 for (i=0; i<8; i++)
1320 {
1321 rpb_insert(diskpos, RPB_TORP_OFFSET + (id+i) * RPB_TORP_MAX
1322 + RPB_TORP_INFO);
1323 rpb_insert(diskpos, RPB_TORP_OFFSET + (id+i) * RPB_TORP_MAX
1324 + RPB_TORP_POSITION);
1325 }
1326 }
1327 break;
1328 case SP_PLANET:
1329 id = ((struct planet_spacket *)packet)->pnum;
1330 rpb_insert(diskpos, RPB_PLANET_OFFSET + id * RPB_PLANET_MAX
1331 + RPB_PLANET_INFO);
1332 break;
1333 case SP_PLANET_LOC:
1334 id = ((struct planet_loc_spacket *)packet)->pnum;
1335 rpb_insert(diskpos, RPB_PLANET_OFFSET + id * RPB_PLANET_MAX
1336 + RPB_PLANET_POSITION);
1337 break;
1338 case SP_S_PLANET:
1339 {
1340 int i, numofplanets;
1341 struct planet_s_spacket *ppacket;
1342
1343 numofplanets = ((unsigned char *)packet)[1];
1344 ppacket = (struct planet_s_spacket *) &(((char *)packet)[2]);
1345
1346 for (i=0; i<numofplanets; i++, ppacket++)
1347 {
1348 id = ppacket->pnum;
1349 rpb_insert(diskpos, RPB_PLANET_OFFSET + id * RPB_PLANET_MAX
1350 + RPB_PLANET_INFO);
1351 }
1352 }
1353 break;
1354 #ifdef nodef
1355 default:
1356 printf("packet type %d", *(unsigned char *)packet);
1357 #endif
1358 }
1359 }
1360
1361 /* silly compare function used by the build in qsort() function */
1362 int
intcomp(const void * a,const void * b)1363 intcomp(const void *a, const void *b)
1364 {
1365 if (*(int *)a < *(int *)b)
1366 return -1;
1367 else if (*(int *)a > *(int *)b)
1368 return 1;
1369 else
1370 return 0;
1371 }
1372
1373 /* Called from the main playback loop. This routine un-does
1374 * a server update. */
1375 void
rpb_dorev(char * buf)1376 rpb_dorev(char *buf)
1377 {
1378 int startpos;
1379 int i;
1380 int temp[RPB_TOTAL];
1381 int last = -1;
1382 /* int min;
1383 int extra = RPB_TOTAL; */
1384
1385 if (lastPos[RPB_NORMAL_SEQUENCE] == -1
1386 || header[lastPos[RPB_NORMAL_SEQUENCE]].prev == -1)
1387 {
1388 playback = PL_PAUSE;
1389 return;
1390 }
1391
1392 startpos = header[lastPos[RPB_NORMAL_SEQUENCE]].prev;
1393
1394 /* min = startpos; */
1395 for (i=0; i<RPB_TOTAL; i++)
1396 while (lastPos[i] > startpos)
1397 {
1398 /* if (lastPos[i] < min)
1399 min = lastPos[i]; */
1400 lastPos[i] = header[lastPos[i]].prev;
1401 }
1402
1403 /* Well, this hack doesn't seem neccessary afterall -
1404 * I'm leaving it as a comment just in case it evenutally is
1405 * required */
1406 #ifdef nodef
1407 /* HACK++ - add in extra me position/sequence packets inline with other
1408 * packets.
1409 * The me position packets are inserted because sp2 torp/player
1410 * packets are based upon the current position of me.
1411 * The sequence packets are inserted just for reference.
1412 * Although these packets are gouped together here, the qsort
1413 * procedure will arrange them inline with the other packets. */
1414 i=lastPos[RPB_PLAYER_OFFSET + RPB_PLAYER_POSITION
1415 + RPB_PLAYER_MAX * me->p_no];
1416 if (i != -1)
1417 do
1418 {
1419 i = header[i].prev;
1420 temp[extra++] = i;
1421 } while (i > min);
1422 i=startpos;
1423 do
1424 {
1425 i = header[i].prev;
1426 temp[extra++] = i;
1427 } while (i > min);
1428 #endif
1429
1430 memcpy(temp, lastPos, sizeof(lastPos));
1431 qsort(temp, RPB_TOTAL, sizeof(int), intcomp);
1432
1433 for (i=0; i<RPB_TOTAL; i++)
1434 if (temp[i] != last)
1435 {
1436 last = temp[i];
1437 fseek(recordFile, header[last].diskpos, SEEK_SET);
1438 pb_dopacket(buf);
1439 }
1440 current = startpos + 1;
1441 pb_sequence_count--;
1442 }
1443 #endif /* REVERSE_PLAYBACK */
1444
1445 #endif /* RECORDGAME */
1446