OSDN Git Service

[Refactor] #40233 Moved display_store() from store.c to display-store.c/h
[hengband/hengband.git] / src / io / chuukei.c
1 /*!
2     @file chuukei.c
3     @brief 中継機能の実装
4     @date 2014/01/02
5     @author
6     2014 Deskull rearranged comment for Doxygen.
7  */
8
9 #include "io/chuukei.h"
10 #include "cmd-io/cmd-dump.h"
11 #include "cmd-visual/cmd-draw.h"
12 #include "core/asking-player.h"
13 #include "io/files-util.h"
14 #include "io/inet.h"
15 #include "io/signal-handlers.h"
16 #include "term/gameterm.h"
17 #include "util/angband-files.h"
18 #include "view/display-messages.h"
19 #ifdef JP
20 #include "locale/japanese.h"
21 #endif
22
23 #include <ctype.h>
24 #include <stdarg.h>
25 #include <stdio.h>
26 #ifdef WINDOWS
27 #include <windows.h>
28 #endif
29
30 #ifdef CHUUKEI
31 #if defined(WINDOWS)
32 #include <winsock.h>
33 #else
34 #include <arpa/inet.h>
35 #include <netdb.h>
36 #include <netinet/in.h>
37 #include <sys/socket.h>
38 #include <sys/time.h>
39 #include <sys/types.h>
40
41 #include <setjmp.h>
42 #include <signal.h>
43 #endif
44
45 #define MAX_HOSTNAME 256
46 #endif
47
48 #define RINGBUF_SIZE 1024 * 1024
49 #define FRESH_QUEUE_SIZE 4096
50 #ifdef WINDOWS
51 #define WAIT 100
52 #else
53 #define WAIT 100 * 1000 /* ブラウズ側のウエイト(us単位) */
54 #endif
55 #define DEFAULT_DELAY 50
56 #define RECVBUF_SIZE 1024
57
58 #ifdef CHUUKEI
59 bool chuukei_server;
60 bool chuukei_client;
61 char *server_name;
62 int server_port;
63 #endif
64
65 static long epoch_time; /* バッファ開始時刻 */
66 static int browse_delay; /* 表示するまでの時間(100ms単位)(この間にラグを吸収する) */
67 #ifdef CHUUKEI
68 static int sd; /* ソケットのファイルディスクリプタ */
69 static long time_diff; /* プレイ側との時間のずれ(これを見ながらディレイを調整していく) */
70 static int server_port;
71 static GAME_TEXT server_name[MAX_HOSTNAME];
72 #endif
73
74 static int movie_fd;
75 static int movie_mode;
76
77 #ifdef CHUUKEI
78 #ifdef WINDOWS
79 #define close closesocket
80 #endif
81 #endif
82
83 /* 描画する時刻を覚えておくキュー構造体 */
84 static struct {
85     int time[FRESH_QUEUE_SIZE];
86     int next;
87     int tail;
88 } fresh_queue;
89
90 /* リングバッファ構造体 */
91 static struct {
92     char *buf;
93     int wptr;
94     int rptr;
95     int inlen;
96 } ring;
97
98 /*
99  * Original hooks
100  */
101 static errr (*old_xtra_hook)(int n, int v);
102 static errr (*old_curs_hook)(int x, int y);
103 static errr (*old_bigcurs_hook)(int x, int y);
104 static errr (*old_wipe_hook)(int x, int y, int n);
105 static errr (*old_text_hook)(int x, int y, int n, TERM_COLOR a, concptr s);
106
107 static void disable_chuukei_server(void)
108 {
109     term_type *t = angband_term[0];
110 #ifdef CHUUKEI
111     chuukei_server = FALSE;
112 #endif /* CHUUKEI */
113     t->xtra_hook = old_xtra_hook;
114     t->curs_hook = old_curs_hook;
115     t->bigcurs_hook = old_bigcurs_hook;
116     t->wipe_hook = old_wipe_hook;
117     t->text_hook = old_text_hook;
118 }
119
120 /* ANSI Cによればstatic変数は0で初期化されるが一応初期化する */
121 static errr init_buffer(void)
122 {
123     fresh_queue.next = fresh_queue.tail = 0;
124     ring.wptr = ring.rptr = ring.inlen = 0;
125     fresh_queue.time[0] = 0;
126     ring.buf = malloc(RINGBUF_SIZE);
127     if (ring.buf == NULL)
128         return -1;
129
130     return 0;
131 }
132
133 /* 現在の時間を100ms単位で取得する */
134 static long get_current_time(void)
135 {
136 #ifdef WINDOWS
137     return timeGetTime() / 100;
138 #else
139     struct timeval tv;
140     gettimeofday(&tv, NULL);
141
142     return (tv.tv_sec * 10 + tv.tv_usec / 100000);
143 #endif
144 }
145
146 /* リングバッファ構造体に buf の内容を加える */
147 static errr insert_ringbuf(char *buf)
148 {
149     int len;
150     len = strlen(buf) + 1; /* +1は終端文字分 */
151
152     if (movie_mode) {
153         fd_write(movie_fd, buf, len);
154 #ifdef CHUUKEI
155         if (!chuukei_server)
156             return 0;
157 #else
158         return 0;
159 #endif
160     }
161
162     /* バッファをオーバー */
163     if (ring.inlen + len >= RINGBUF_SIZE) {
164 #ifdef CHUUKEI
165         if (chuukei_server)
166             disable_chuukei_server();
167         else
168             chuukei_client = FALSE;
169
170         prt("送受信バッファが溢れました。サーバとの接続を切断します。", 0, 0);
171         inkey();
172
173         close(sd);
174 #endif
175         return -1;
176     }
177
178     /* バッファの終端までに収まる */
179     if (ring.wptr + len < RINGBUF_SIZE) {
180         memcpy(ring.buf + ring.wptr, buf, len);
181         ring.wptr += len;
182     }
183     /* バッファの終端までに収まらない(ピッタリ収まる場合も含む) */
184     else {
185         int head = RINGBUF_SIZE - ring.wptr; /* 前半 */
186         int tail = len - head; /* 後半 */
187
188         memcpy(ring.buf + ring.wptr, buf, head);
189         memcpy(ring.buf, buf + head, tail);
190         ring.wptr = tail;
191     }
192
193     ring.inlen += len;
194
195     /* Success */
196     return 0;
197 }
198
199 #ifdef CHUUKEI
200 void flush_ringbuf(void)
201 {
202     fd_set fdset;
203     struct timeval tv;
204
205     if (!chuukei_server)
206         return;
207
208     if (ring.inlen == 0)
209         return;
210
211     tv.tv_sec = 0;
212     tv.tv_usec = 0;
213
214     FD_ZERO(&fdset);
215     FD_SET(sd, &fdset);
216
217     while (TRUE) {
218         fd_set tmp_fdset;
219         int result;
220
221         tmp_fdset = fdset;
222
223         /* ソケットにデータを書き込めるかどうか調べる */
224         select(sd + 1, (fd_set *)NULL, &tmp_fdset, (fd_set *)NULL, &tv);
225
226         /* 書き込めなければ戻る */
227         if (FD_ISSET(sd, &tmp_fdset) == 0)
228             break;
229
230         result = send(sd, ring.buf + ring.rptr, ((ring.wptr > ring.rptr) ? ring.wptr : RINGBUF_SIZE) - ring.rptr, 0);
231
232         if (result <= 0) {
233             /* サーバとの接続断? */
234             if (chuukei_server)
235                 disable_chuukei_server();
236
237             prt("サーバとの接続が切断されました。", 0, 0);
238             inkey();
239             close(sd);
240
241             return;
242         }
243
244         ring.rptr += result;
245         ring.inlen -= result;
246
247         if (ring.rptr == RINGBUF_SIZE)
248             ring.rptr = 0;
249         if (ring.inlen == 0)
250             break;
251     }
252 }
253
254 static int read_chuukei_prf(concptr prf_name)
255 {
256     char buf[1024];
257     FILE *fp;
258
259     path_build(buf, sizeof(buf), ANGBAND_DIR_XTRA, prf_name);
260     fp = angband_fopen(buf, "r");
261
262     if (!fp)
263         return -1;
264
265     /* 初期化 */
266     server_port = -1;
267     server_name[0] = 0;
268     browse_delay = DEFAULT_DELAY;
269
270     while (0 == angband_fgets(fp, buf, sizeof(buf))) {
271         /* サーバ名 */
272         if (!strncmp(buf, "server:", 7)) {
273             strncpy(server_name, buf + 7, MAX_HOSTNAME - 1);
274             server_name[MAX_HOSTNAME - 1] = '\0';
275         }
276
277         /* ポート番号 */
278         if (!strncmp(buf, "port:", 5)) {
279             server_port = atoi(buf + 5);
280         }
281
282         /* ディレイ */
283         if (!strncmp(buf, "delay:", 6)) {
284             browse_delay = atoi(buf + 6);
285         }
286     }
287
288     angband_fclose(fp);
289
290     /* prfファイルが完全でない */
291     if (server_port == -1 || server_name[0] == 0)
292         return -1;
293
294     return 0;
295 }
296
297 int connect_chuukei_server(char *prf_name)
298 {
299 #ifdef WINDOWS
300     WSADATA wsaData;
301     WORD wVersionRequested = (WORD)((1) | (1 << 8));
302 #endif
303
304     struct sockaddr_in ask;
305     struct hostent *hp;
306
307     if (read_chuukei_prf(prf_name) < 0) {
308         printf("Wrong prf file\n");
309         return -1;
310     }
311
312     if (init_buffer() < 0) {
313         printf("Malloc error\n");
314         return -1;
315     }
316
317 #ifdef WINDOWS
318     if (WSAStartup(wVersionRequested, &wsaData)) {
319         msg_print("Report: WSAStartup failed.");
320         return -1;
321     }
322 #endif
323
324     printf("server = %s\nport = %d\n", server_name, server_port);
325
326     if ((hp = gethostbyname(server_name)) != NULL) {
327         memset(&ask, 0, sizeof(ask));
328         memcpy(&ask.sin_addr, hp->h_addr_list[0], hp->h_length);
329     } else {
330         if ((ask.sin_addr.s_addr = inet_addr(server_name)) == 0) {
331             printf("Bad hostname\n");
332             return -1;
333         }
334     }
335
336     ask.sin_family = AF_INET;
337     ask.sin_port = htons((unsigned short)server_port);
338
339 #ifndef WINDOWS
340     if ((sd = socket(PF_INET, SOCK_STREAM, 0)) < 0)
341 #else
342     if ((sd = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
343 #endif
344     {
345         printf("Can't create socket\n");
346         return -1;
347     }
348
349     if (connect(sd, (struct sockaddr *)&ask, sizeof(ask)) < 0) {
350         close(sd);
351         printf("Can't connect %s port %d\n", server_name, server_port);
352         return -1;
353     }
354
355     return 0;
356 }
357 #endif /* CHUUKEI */
358
359 /* strが同じ文字の繰り返しかどうか調べる */
360 static bool string_is_repeat(char *str, int len)
361 {
362     char c = str[0];
363     int i;
364
365     if (len < 2)
366         return FALSE;
367 #ifdef JP
368     if (iskanji(c))
369         return FALSE;
370 #endif
371
372     for (i = 1; i < len; i++) {
373 #ifdef JP
374         if (c != str[i] || iskanji(str[i]))
375             return FALSE;
376 #else
377         if (c != str[i])
378             return FALSE;
379 #endif
380     }
381
382     return TRUE;
383 }
384
385 static errr send_text_to_chuukei_server(TERM_LEN x, TERM_LEN y, int len, TERM_COLOR col, concptr str)
386 {
387     char buf[1024];
388     char buf2[1024];
389
390     strncpy(buf2, str, len);
391     buf2[len] = '\0';
392
393     if (len == 1) {
394         sprintf(buf, "s%c%c%c%c", x + 1, y + 1, col, buf2[0]);
395     } else if (string_is_repeat(buf2, len)) {
396         int i;
397         for (i = len; i > 0; i -= 127) {
398             sprintf(buf, "n%c%c%c%c%c", x + 1, y + 1, MIN(i, 127), col, buf2[0]);
399         }
400     } else {
401 #if defined(SJIS) && defined(JP)
402         sjis2euc(buf2);
403 #endif
404         sprintf(buf, "t%c%c%c%c%s", x + 1, y + 1, len, col, buf2);
405     }
406
407     insert_ringbuf(buf);
408
409     return (*old_text_hook)(x, y, len, col, str);
410 }
411
412 static errr send_wipe_to_chuukei_server(int x, int y, int len)
413 {
414     char buf[1024];
415
416     sprintf(buf, "w%c%c%c", x + 1, y + 1, len);
417
418     insert_ringbuf(buf);
419
420     return (*old_wipe_hook)(x, y, len);
421 }
422
423 static errr send_xtra_to_chuukei_server(int n, int v)
424 {
425     char buf[1024];
426
427     if (n == TERM_XTRA_CLEAR || n == TERM_XTRA_FRESH || n == TERM_XTRA_SHAPE) {
428         sprintf(buf, "x%c", n + 1);
429
430         insert_ringbuf(buf);
431
432         if (n == TERM_XTRA_FRESH) {
433             sprintf(buf, "d%ld", get_current_time() - epoch_time);
434             insert_ringbuf(buf);
435         }
436     }
437
438     /* Verify the hook */
439     if (!old_xtra_hook)
440         return -1;
441
442     return (*old_xtra_hook)(n, v);
443 }
444
445 static errr send_curs_to_chuukei_server(int x, int y)
446 {
447     char buf[1024];
448
449     sprintf(buf, "c%c%c", x + 1, y + 1);
450
451     insert_ringbuf(buf);
452
453     return (*old_curs_hook)(x, y);
454 }
455
456 static errr send_bigcurs_to_chuukei_server(int x, int y)
457 {
458     char buf[1024];
459
460     sprintf(buf, "C%c%c", x + 1, y + 1);
461
462     insert_ringbuf(buf);
463
464     return (*old_bigcurs_hook)(x, y);
465 }
466
467 /*
468  * Prepare z-term hooks to call send_*_to_chuukei_server()'s
469  */
470 void prepare_chuukei_hooks(void)
471 {
472     term_type *t0 = angband_term[0];
473
474     /* Save original z-term hooks */
475     old_xtra_hook = t0->xtra_hook;
476     old_curs_hook = t0->curs_hook;
477     old_bigcurs_hook = t0->bigcurs_hook;
478     old_wipe_hook = t0->wipe_hook;
479     old_text_hook = t0->text_hook;
480
481     /* Prepare z-term hooks */
482     t0->xtra_hook = send_xtra_to_chuukei_server;
483     t0->curs_hook = send_curs_to_chuukei_server;
484     t0->bigcurs_hook = send_bigcurs_to_chuukei_server;
485     t0->wipe_hook = send_wipe_to_chuukei_server;
486     t0->text_hook = send_text_to_chuukei_server;
487 }
488
489 /*
490  * Prepare z-term hooks to call send_*_to_chuukei_server()'s
491  */
492 void prepare_movie_hooks(player_type *player_ptr)
493 {
494     char buf[1024];
495     char tmp[80];
496
497     if (movie_mode) {
498         movie_mode = 0;
499 #ifdef CHUUKEI
500         if (!chuukei_server)
501             disable_chuukei_server();
502 #else
503         disable_chuukei_server();
504 #endif
505         fd_close(movie_fd);
506         msg_print(_("録画を終了しました。", "Stopped recording."));
507     } else {
508         sprintf(tmp, "%s.amv", player_ptr->base_name);
509         if (get_string(_("ムービー記録ファイル: ", "Movie file name: "), tmp, 80)) {
510             int fd;
511
512             path_build(buf, sizeof(buf), ANGBAND_DIR_USER, tmp);
513
514             fd = fd_open(buf, O_RDONLY);
515
516             /* Existing file */
517             if (fd >= 0) {
518                 char out_val[160];
519                 (void)fd_close(fd);
520
521                 /* Build query */
522                 (void)sprintf(out_val, _("現存するファイルに上書きしますか? (%s)", "Replace existing file %s? "), buf);
523
524                 /* Ask */
525                 if (!get_check(out_val))
526                     return;
527
528                 movie_fd = fd_open(buf, O_WRONLY | O_TRUNC);
529             } else {
530                 movie_fd = fd_make(buf, 0644);
531             }
532
533             if (!movie_fd) {
534                 msg_print(_("ファイルを開けません!", "Can not open file."));
535                 return;
536             }
537
538             movie_mode = 1;
539 #ifdef CHUUKEI
540             if (!chuukei_server)
541                 prepare_chuukei_hooks();
542 #else
543             prepare_chuukei_hooks();
544 #endif
545             do_cmd_redraw(player_ptr);
546         }
547     }
548 }
549
550 #ifdef CHUUKEI
551 static int handle_timestamp_data(int timestamp)
552 {
553     long current_time = get_current_time();
554
555     /* 描画キューは空かどうか? */
556     if (fresh_queue.tail == fresh_queue.next) {
557         /* バッファリングし始めの時間を保存しておく */
558         epoch_time = current_time;
559         epoch_time += browse_delay;
560         epoch_time -= timestamp;
561         time_diff = current_time - timestamp;
562     }
563
564     /* 描画キューに保存し、保存位置を進める */
565     fresh_queue.time[fresh_queue.tail] = timestamp;
566     fresh_queue.tail++;
567
568     /* キューの最後尾に到達したら先頭に戻す */
569     fresh_queue.tail %= FRESH_QUEUE_SIZE;
570
571     if (fresh_queue.tail == fresh_queue.next) {
572         /* 描画キュー溢れ */
573         prt("描画タイミングキューが溢れました。サーバとの接続を切断します。", 0, 0);
574         inkey();
575         close(sd);
576
577         return -1;
578     }
579
580     /* プレイ側とのディレイを調整 */
581     if (time_diff != current_time - timestamp) {
582         long old_time_diff = time_diff;
583         time_diff = current_time - timestamp;
584         epoch_time -= (old_time_diff - time_diff);
585     }
586
587     /* Success */
588     return 0;
589 }
590 #endif /* CHUUKEI */
591
592 static int handle_movie_timestamp_data(int timestamp)
593 {
594     static int initialized = FALSE;
595
596     /* 描画キューは空かどうか? */
597     if (!initialized) {
598         /* バッファリングし始めの時間を保存しておく */
599         epoch_time = get_current_time();
600         epoch_time += browse_delay;
601         epoch_time -= timestamp;
602         // time_diff = current_time - timestamp;
603         initialized = TRUE;
604     }
605
606     /* 描画キューに保存し、保存位置を進める */
607     fresh_queue.time[fresh_queue.tail] = timestamp;
608     fresh_queue.tail++;
609
610     /* キューの最後尾に到達したら先頭に戻す */
611     fresh_queue.tail %= FRESH_QUEUE_SIZE;
612
613     /* Success */
614     return 0;
615 }
616
617 #ifdef CHUUKEI
618 static int read_sock(void)
619 {
620     static char recv_buf[RECVBUF_SIZE];
621     static int remain_bytes = 0;
622     int recv_bytes;
623     int i;
624
625     /* 前回残ったデータの後につづけて配信サーバからデータ受信 */
626     recv_bytes = recv(sd, recv_buf + remain_bytes, RECVBUF_SIZE - remain_bytes, 0);
627
628     if (recv_bytes <= 0)
629         return -1;
630
631     /* 前回残ったデータ量に今回読んだデータ量を追加 */
632     remain_bytes += recv_bytes;
633
634     for (i = 0; i < remain_bytes; i++) {
635         /* データのくぎり('\0')を探す */
636         if (recv_buf[i] == '\0') {
637             /* 'd'で始まるデータ(タイムスタンプ)の場合は
638                描画キューに保存する処理を呼ぶ */
639             if ((recv_buf[0] == 'd') && (handle_timestamp_data(atoi(recv_buf + 1)) < 0))
640                 return -1;
641
642             /* 受信データを保存 */
643             if (insert_ringbuf(recv_buf) < 0)
644                 return -1;
645
646             /* 次のデータ移行をrecv_bufの先頭に移動 */
647             memmove(recv_buf, recv_buf + i + 1, remain_bytes - i - 1);
648
649             remain_bytes -= (i + 1);
650             i = 0;
651         }
652     }
653
654     return 0;
655 }
656 #endif
657
658 static int read_movie_file(void)
659 {
660     static char recv_buf[RECVBUF_SIZE];
661     static int remain_bytes = 0;
662     int recv_bytes;
663     int i;
664
665     recv_bytes = read(movie_fd, recv_buf + remain_bytes, RECVBUF_SIZE - remain_bytes);
666
667     if (recv_bytes <= 0)
668         return -1;
669
670     /* 前回残ったデータ量に今回読んだデータ量を追加 */
671     remain_bytes += recv_bytes;
672
673     for (i = 0; i < remain_bytes; i++) {
674         /* データのくぎり('\0')を探す */
675         if (recv_buf[i] == '\0') {
676             /* 'd'で始まるデータ(タイムスタンプ)の場合は
677                描画キューに保存する処理を呼ぶ */
678             if ((recv_buf[0] == 'd') && (handle_movie_timestamp_data(atoi(recv_buf + 1)) < 0))
679                 return -1;
680
681             /* 受信データを保存 */
682             if (insert_ringbuf(recv_buf) < 0)
683                 return -1;
684
685             /* 次のデータ移行をrecv_bufの先頭に移動 */
686             memmove(recv_buf, recv_buf + i + 1, remain_bytes - i - 1);
687
688             remain_bytes -= (i + 1);
689             i = 0;
690         }
691     }
692
693     return 0;
694 }
695
696 #ifndef WINDOWS
697 /* Win版の床の中点と壁の豆腐をピリオドとシャープにする。*/
698 static void win2unix(int col, char *buf)
699 {
700     char wall;
701     if (col == 9)
702         wall = '%';
703     else
704         wall = '#';
705
706     while (*buf) {
707 #ifdef JP
708         if (iskanji(*buf)) {
709             buf += 2;
710             continue;
711         }
712 #endif
713         if (*buf == 127)
714             *buf = wall;
715         else if (*buf == 31)
716             *buf = '.';
717         buf++;
718     }
719 }
720 #endif
721
722 static bool get_nextbuf(char *buf)
723 {
724     char *ptr = buf;
725
726     while (TRUE) {
727         *ptr = ring.buf[ring.rptr++];
728         ring.inlen--;
729         if (ring.rptr == RINGBUF_SIZE)
730             ring.rptr = 0;
731         if (*ptr++ == '\0')
732             break;
733     }
734
735     if (buf[0] == 'd')
736         return FALSE;
737
738     return TRUE;
739 }
740
741 /* プレイホストのマップが大きいときクライアントのマップもリサイズする */
742 static void update_term_size(int x, int y, int len)
743 {
744     int ox, oy;
745     int nx, ny;
746     term_get_size(&ox, &oy);
747     nx = ox;
748     ny = oy;
749
750     /* 横方向のチェック */
751     if (x + len > ox)
752         nx = x + len;
753     /* 縦方向のチェック */
754     if (y + 1 > oy)
755         ny = y + 1;
756
757     if (nx != ox || ny != oy)
758         term_resize(nx, ny);
759 }
760
761 static bool flush_ringbuf_client(void)
762 {
763     char buf[1024];
764
765     /* 書くデータなし */
766     if (fresh_queue.next == fresh_queue.tail)
767         return FALSE;
768
769     /* まだ書くべき時でない */
770     if (fresh_queue.time[fresh_queue.next] > get_current_time() - epoch_time)
771         return FALSE;
772
773     /* 時間情報(区切り)が得られるまで書く */
774     while (get_nextbuf(buf)) {
775         char id;
776         int x, y, len;
777         TERM_COLOR col;
778         int i;
779         unsigned char tmp1, tmp2, tmp3, tmp4;
780         char *mesg;
781
782         sscanf(buf, "%c%c%c%c%c", &id, &tmp1, &tmp2, &tmp3, &tmp4);
783         x = tmp1 - 1;
784         y = tmp2 - 1;
785         len = tmp3;
786         col = tmp4;
787         if (id == 's') {
788             col = tmp3;
789             mesg = &buf[4];
790         } else
791             mesg = &buf[5];
792 #ifndef WINDOWS
793         win2unix(col, mesg);
794 #endif
795
796         switch (id) {
797         case 't': /* 通常 */
798 #if defined(SJIS) && defined(JP)
799             euc2sjis(mesg);
800 #endif
801             update_term_size(x, y, len);
802             (void)((*angband_term[0]->text_hook)(x, y, len, (byte)col, mesg));
803             strncpy(&Term->scr->c[y][x], mesg, len);
804             for (i = x; i < x + len; i++) {
805                 Term->scr->a[y][i] = col;
806             }
807             break;
808
809         case 'n': /* 繰り返し */
810             for (i = 1; i < len; i++) {
811                 mesg[i] = mesg[0];
812             }
813             mesg[i] = '\0';
814             update_term_size(x, y, len);
815             (void)((*angband_term[0]->text_hook)(x, y, len, (byte)col, mesg));
816             strncpy(&Term->scr->c[y][x], mesg, len);
817             for (i = x; i < x + len; i++) {
818                 Term->scr->a[y][i] = col;
819             }
820             break;
821
822         case 's': /* 一文字 */
823             update_term_size(x, y, 1);
824             (void)((*angband_term[0]->text_hook)(x, y, 1, (byte)col, mesg));
825             strncpy(&Term->scr->c[y][x], mesg, 1);
826             Term->scr->a[y][x] = col;
827             break;
828
829         case 'w':
830             update_term_size(x, y, len);
831             (void)((*angband_term[0]->wipe_hook)(x, y, len));
832             break;
833
834         case 'x':
835             if (x == TERM_XTRA_CLEAR)
836                 term_clear();
837             (void)((*angband_term[0]->xtra_hook)(x, 0));
838             break;
839
840         case 'c':
841             update_term_size(x, y, 1);
842             (void)((*angband_term[0]->curs_hook)(x, y));
843             break;
844         case 'C':
845             update_term_size(x, y, 1);
846             (void)((*angband_term[0]->bigcurs_hook)(x, y));
847             break;
848         }
849     }
850
851     fresh_queue.next++;
852     if (fresh_queue.next == FRESH_QUEUE_SIZE)
853         fresh_queue.next = 0;
854     return TRUE;
855 }
856
857 #ifdef CHUUKEI
858 void browse_chuukei()
859 {
860     fd_set fdset;
861     struct timeval tv;
862
863     tv.tv_sec = 0;
864     tv.tv_usec = WAIT;
865
866     FD_ZERO(&fdset);
867     FD_SET(sd, &fdset);
868
869     term_clear();
870     term_fresh();
871     term_xtra(TERM_XTRA_REACT, 0);
872
873     while (TRUE) {
874         fd_set tmp_fdset;
875         struct timeval tmp_tv;
876
877         if (flush_ringbuf_client())
878             continue;
879
880         tmp_fdset = fdset;
881         tmp_tv = tv;
882
883         /* ソケットにデータが来ているかどうか調べる */
884         select(sd + 1, &tmp_fdset, (fd_set *)NULL, (fd_set *)NULL, &tmp_tv);
885         if (FD_ISSET(sd, &tmp_fdset) == 0) {
886             term_xtra(TERM_XTRA_FLUSH, 0);
887             continue;
888         }
889
890         if (read_sock() < 0) {
891             chuukei_client = FALSE;
892         }
893
894         /* 接続が切れた状態で書くべきデータがなくなっていたら終了 */
895         if (!chuukei_client && fresh_queue.next == fresh_queue.tail)
896             break;
897     }
898 }
899 #endif /* CHUUKEI */
900
901 void prepare_browse_movie_aux(concptr filename)
902 {
903     movie_fd = fd_open(filename, O_RDONLY);
904
905     browsing_movie = TRUE;
906
907     init_buffer();
908 }
909
910 void prepare_browse_movie(concptr filename)
911 {
912     char buf[1024];
913     path_build(buf, sizeof(buf), ANGBAND_DIR_USER, filename);
914
915     prepare_browse_movie_aux(buf);
916 }
917
918 void browse_movie(void)
919 {
920     term_clear();
921     term_fresh();
922     term_xtra(TERM_XTRA_REACT, 0);
923
924     while (read_movie_file() == 0) {
925         while (fresh_queue.next != fresh_queue.tail) {
926             if (!flush_ringbuf_client()) {
927                 term_xtra(TERM_XTRA_FLUSH, 0);
928
929                 /* ソケットにデータが来ているかどうか調べる */
930 #ifdef WINDOWS
931                 Sleep(WAIT);
932 #else
933                 usleep(WAIT);
934 #endif
935             }
936         }
937     }
938 }