OSDN Git Service

Merge pull request #765 from sikabane-works/release/3.0.0Alpha17
[hengbandforosx/hengbandosx.git] / src / io / report.cpp
1 /*!
2  * @file report.c
3  * @brief スコアサーバ転送機能の実装
4  * @date 2014/07/14
5  * @author Hengband Team
6  */
7
8 #include "io/report.h"
9 #include "core/asking-player.h"
10 #include "core/player-redraw-types.h"
11 #include "core/stuff-handler.h"
12 #include "core/turn-compensator.h"
13 #include "core/visuals-reseter.h"
14 #include "dungeon/dungeon.h"
15 #include "game-option/special-options.h"
16 #include "io-dump/character-dump.h"
17 #include "io/inet.h"
18 #include "io/input-key-acceptor.h"
19 #include "mind/mind-elementalist.h"
20 #include "player/player-class.h"
21 #include "player/player-personality.h"
22 #include "player/player-race.h"
23 #include "realm/realm-names-table.h"
24 #include "system/angband-version.h"
25 #include "system/floor-type-definition.h"
26 #include "system/system-variables.h"
27 #include "term/gameterm.h"
28 #include "term/screen-processor.h"
29 #include "util/angband-files.h"
30 #include "view/display-messages.h"
31 #include "world/world.h"
32
33 #ifdef WORLD_SCORE
34
35 #ifdef WINDOWS
36 #define CURL_STATICLIB
37 #endif
38 #include <curl/curl.h>
39
40 concptr screen_dump = NULL;
41
42 /*
43  * internet resource value
44  */
45 #define HTTP_TIMEOUT 30 /*!< デフォルトのタイムアウト時間(秒) / Timeout length (second) */
46
47 #ifdef JP
48 #define SCORE_PATH "http://mars.kmc.gr.jp/~dis/heng_score/register_score.php" /*!< スコア開示URL */
49 #else
50 #define SCORE_PATH "http://moon.kmc.gr.jp/hengband/hengscore-en/score.cgi" /*!< スコア開示URL */
51 #endif
52
53 /*
54  * simple buffer library
55  */
56 typedef struct {
57     size_t max_size;
58     size_t size;
59     size_t read_head;
60     char *data;
61 } BUF;
62
63 #define BUFSIZE (65536) /*!< スコアサーバ転送バッファサイズ */
64
65 /*!
66  * @brief 転送用バッファの確保
67  * @return 確保したバッファの参照ポインタ
68  */
69 static BUF *buf_new(void)
70 {
71     BUF *p;
72     p = static_cast<BUF*>(malloc(sizeof(BUF)));
73     if (!p)
74         return NULL;
75
76     p->size = 0;
77     p->max_size = BUFSIZE;
78     p->data = static_cast<char*>(malloc(BUFSIZE));
79     if (!p->data) {
80         free(p);
81         return NULL;
82     }
83
84     return p;
85 }
86
87 /*!
88  * @brief 転送用バッファの解放
89  * @param b 解放するバッファの参照ポインタ
90  */
91 static void buf_delete(BUF *b)
92 {
93     free(b->data);
94     free(b);
95 }
96
97 /*!
98  * @brief 転送用バッファにデータを追加する
99  * @param buf 追加先バッファの参照ポインタ
100  * @param data 追加元データ
101  * @param size 追加サイズ
102  * @return 追加後のバッファ容量
103  */
104 static int buf_append(BUF *buf, concptr data, size_t size)
105 {
106     while (buf->size + size > buf->max_size) {
107         char *tmp;
108         if ((tmp = static_cast<char*>(malloc(buf->max_size * 2))) == NULL)
109             return -1;
110
111         memcpy(tmp, buf->data, buf->max_size);
112         free(buf->data);
113
114         buf->data = tmp;
115
116         buf->max_size *= 2;
117     }
118     memcpy(buf->data + buf->size, data, size);
119     buf->size += size;
120
121     return buf->size;
122 }
123
124 /*!
125  * @brief 転送用バッファにフォーマット指定した文字列データを追加する
126  * @param buf 追加先バッファの参照ポインタ
127  * @param fmt 文字列フォーマット
128  * @return 追加後のバッファ容量
129  */
130 static int buf_sprintf(BUF *buf, concptr fmt, ...)
131 {
132     int ret;
133     char tmpbuf[8192];
134     va_list ap;
135
136     va_start(ap, fmt);
137 #if defined(HAVE_VSNPRINTF)
138     ret = vsnprintf(tmpbuf, sizeof(tmpbuf), fmt, ap);
139 #else
140     ret = vsprintf(tmpbuf, fmt, ap);
141 #endif
142     va_end(ap);
143
144     if (ret < 0)
145         return -1;
146
147     ret = buf_append(buf, tmpbuf, strlen(tmpbuf));
148     return ret;
149 }
150
151 size_t read_callback(char *buffer, size_t size, size_t nitems, void *userdata)
152 {
153     BUF *buf = static_cast<BUF*>(userdata);
154     const size_t remain = buf->size - buf->read_head;
155     const size_t copy_size = MIN(size * nitems, remain);
156
157     strncpy(buffer, buf->data + buf->read_head, copy_size);
158     buf->read_head += copy_size;
159
160     return copy_size;
161 }
162
163 /*!
164  * @brief HTTPによるダンプ内容伝送
165  * @param url 伝送先URL
166  * @param buf 伝送内容バッファ
167  * @return 送信に成功した場合TRUE、失敗した場合FALSE
168  */
169 static bool http_post(concptr url, BUF *buf)
170 {
171     bool succeeded = FALSE;
172     CURL *curl = curl_easy_init();
173     if (curl == NULL) {
174         return FALSE;
175     }
176
177     struct curl_slist *slist = NULL;
178     slist = curl_slist_append(slist,
179 #ifdef JP
180 #ifdef SJIS
181         "Content-Type: text/plain; charset=SHIFT_JIS"
182 #endif
183 #ifdef EUC
184         "Content-Type: text/plain; charset=EUC-JP"
185 #endif
186 #else
187         "Content-Type: text/plain; charset=ASCII"
188 #endif
189     );
190
191     curl_easy_setopt(curl, CURLOPT_URL, url);
192     curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
193
194     char user_agent[64];
195     snprintf(user_agent, sizeof(user_agent), "Hengband %d.%d.%d", FAKE_VER_MAJOR - 10, FAKE_VER_MINOR, FAKE_VER_PATCH);
196     curl_easy_setopt(curl, CURLOPT_USERAGENT, user_agent);
197
198     curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
199     curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
200
201     curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
202     curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, HTTP_TIMEOUT);
203     curl_easy_setopt(curl, CURLOPT_TIMEOUT, HTTP_TIMEOUT);
204
205     curl_easy_setopt(curl, CURLOPT_POST, 1);
206
207     curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
208     curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 10);
209     curl_easy_setopt(curl, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL);
210
211     buf->read_head = 0;
212     curl_easy_setopt(curl, CURLOPT_READDATA, buf);
213     curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback);
214     curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, buf->size);
215
216     if (curl_easy_perform(curl) == CURLE_OK) {
217         long response_code;
218         curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
219         if (response_code == 200) {
220             succeeded = TRUE;
221         }
222     }
223
224     curl_slist_free_all(slist);
225     curl_easy_cleanup(curl);
226
227     return succeeded;
228 }
229
230 /*!
231  * @brief キャラクタダンプを作って BUFに保存
232  * @param creature_ptr プレーヤーへの参照ポインタ
233  * @param dumpbuf 伝送内容バッファ
234  * @return エラーコード
235  */
236 static errr make_dump(player_type *creature_ptr, BUF *dumpbuf, void (*update_playtime)(void), display_player_pf display_player)
237 {
238     char buf[1024];
239     FILE *fff;
240     GAME_TEXT file_name[1024];
241
242     /* Open a new file */
243     fff = angband_fopen_temp(file_name, 1024);
244     if (!fff) {
245 #ifdef JP
246         msg_format("一時ファイル %s を作成できませんでした。", file_name);
247 #else
248         msg_format("Failed to create temporary file %s.", file_name);
249 #endif
250         msg_print(NULL);
251         return 1;
252     }
253
254     /* 一旦一時ファイルを作る。通常のダンプ出力と共通化するため。 */
255     make_character_dump(creature_ptr, fff, update_playtime, display_player);
256     angband_fclose(fff);
257
258     /* Open for read */
259     fff = angband_fopen(file_name, "r");
260
261     while (fgets(buf, 1024, fff)) {
262         (void)buf_sprintf(dumpbuf, "%s", buf);
263     }
264     angband_fclose(fff);
265     fd_kill(file_name);
266
267     /* Success */
268     return 0;
269 }
270
271 /*!
272  * @brief スクリーンダンプを作成する/ Make screen dump to buffer
273  * @return 作成したスクリーンダンプの参照ポインタ
274  */
275 concptr make_screen_dump(player_type *creature_ptr, void (*process_autopick_file_command)(char *))
276 {
277     static concptr html_head[] = {
278         "<html>\n<body text=\"#ffffff\" bgcolor=\"#000000\">\n",
279         "<pre>",
280         0,
281     };
282     static concptr html_foot[] = {
283         "</pre>\n",
284         "</body>\n</html>\n",
285         0,
286     };
287
288     int wid, hgt;
289     term_get_size(&wid, &hgt);
290
291     /* Alloc buffer */
292     BUF *screen_buf;
293     screen_buf = buf_new();
294     if (screen_buf == NULL)
295         return (NULL);
296
297     bool old_use_graphics = use_graphics;
298     if (old_use_graphics) {
299         /* Clear -more- prompt first */
300         msg_print(NULL);
301
302         use_graphics = FALSE;
303         reset_visuals(creature_ptr, process_autopick_file_command);
304
305         creature_ptr->redraw |= (PR_WIPE | PR_BASIC | PR_EXTRA | PR_MAP | PR_EQUIPPY);
306         handle_stuff(creature_ptr);
307     }
308
309     for (int i = 0; html_head[i]; i++)
310         buf_sprintf(screen_buf, html_head[i]);
311
312     /* Dump the screen */
313     for (int y = 0; y < hgt; y++) {
314         /* Start the row */
315         if (y != 0)
316             buf_sprintf(screen_buf, "\n");
317
318         /* Dump each row */
319         TERM_COLOR a = 0, old_a = 0;
320         SYMBOL_CODE c = ' ';
321         for (int x = 0; x < wid - 1; x++) {
322             int rv, gv, bv;
323             concptr cc = NULL;
324             /* Get the attr/char */
325             (void)(term_what(x, y, &a, &c));
326
327             switch (c) {
328             case '&':
329                 cc = "&amp;";
330                 break;
331             case '<':
332                 cc = "&lt;";
333                 break;
334             case '>':
335                 cc = "&gt;";
336                 break;
337             case '"':
338                 cc = "&quot;";
339                 break;
340             case '\'':
341                 cc = "&#39;";
342                 break;
343 #ifdef WINDOWS
344             case 0x1f:
345                 c = '.';
346                 break;
347             case 0x7f:
348                 c = (a == 0x09) ? '%' : '#';
349                 break;
350 #endif
351             }
352
353             a = a & 0x0F;
354             if ((y == 0 && x == 0) || a != old_a) {
355                 rv = angband_color_table[a][1];
356                 gv = angband_color_table[a][2];
357                 bv = angband_color_table[a][3];
358                 buf_sprintf(screen_buf, "%s<font color=\"#%02x%02x%02x\">", ((y == 0 && x == 0) ? "" : "</font>"), rv, gv, bv);
359                 old_a = a;
360             }
361
362             if (cc)
363                 buf_sprintf(screen_buf, "%s", cc);
364             else
365                 buf_sprintf(screen_buf, "%c", c);
366         }
367     }
368
369     buf_sprintf(screen_buf, "</font>");
370
371     for (int i = 0; html_foot[i]; i++)
372         buf_sprintf(screen_buf, html_foot[i]);
373
374     /* Screen dump size is too big ? */
375     concptr ret;
376     if (screen_buf->size + 1 > SCREEN_BUF_MAX_SIZE) {
377         ret = NULL;
378     } else {
379         /* Terminate string */
380         buf_append(screen_buf, "", 1);
381
382         ret = string_make(screen_buf->data);
383     }
384
385     /* Free buffer */
386     buf_delete(screen_buf);
387
388     if (!old_use_graphics)
389         return ret;
390
391     use_graphics = TRUE;
392     reset_visuals(creature_ptr, process_autopick_file_command);
393
394     creature_ptr->redraw |= (PR_WIPE | PR_BASIC | PR_EXTRA | PR_MAP | PR_EQUIPPY);
395     handle_stuff(creature_ptr);
396     return ret;
397 }
398
399 /*!
400  * @brief スコア転送処理のメインルーチン
401  * @param creature_ptr プレーヤーへの参照ポインタ
402  * @return 正常終了の時0、異常があったら1
403  * @todo メッセージは言語選択の関数マクロで何とかならんか?
404  */
405 errr report_score(player_type *creature_ptr, void (*update_playtime)(void), display_player_pf display_player)
406 {
407     BUF *score;
408     score = buf_new();
409
410     concptr realm1_name;
411     char seikakutmp[128];
412     char title[128];
413     put_version(title);
414 #ifdef JP
415     sprintf(seikakutmp, "%s%s", ap_ptr->title, (ap_ptr->no ? "の" : ""));
416 #else
417     sprintf(seikakutmp, "%s ", ap_ptr->title);
418 #endif
419
420     if (creature_ptr->pclass == CLASS_ELEMENTALIST)
421         realm1_name = get_element_title(creature_ptr->element);
422     else
423         realm1_name = realm_names[creature_ptr->realm1];
424
425     buf_sprintf(score, "name: %s\n", creature_ptr->name);
426     buf_sprintf(score, "version: %s\n", title);
427     buf_sprintf(score, "score: %d\n", calc_score(creature_ptr));
428     buf_sprintf(score, "level: %d\n", creature_ptr->lev);
429     buf_sprintf(score, "depth: %d\n", creature_ptr->current_floor_ptr->dun_level);
430     buf_sprintf(score, "maxlv: %d\n", creature_ptr->max_plv);
431     buf_sprintf(score, "maxdp: %d\n", max_dlv[DUNGEON_ANGBAND]);
432     buf_sprintf(score, "au: %d\n", creature_ptr->au);
433     buf_sprintf(score, "turns: %d\n", turn_real(creature_ptr, current_world_ptr->game_turn));
434     buf_sprintf(score, "sex: %d\n", creature_ptr->psex);
435     buf_sprintf(score, "race: %s\n", rp_ptr->title);
436     buf_sprintf(score, "class: %s\n", cp_ptr->title);
437     buf_sprintf(score, "seikaku: %s\n", seikakutmp);
438     buf_sprintf(score, "realm1: %s\n", realm1_name);
439     buf_sprintf(score, "realm2: %s\n", realm_names[creature_ptr->realm2]);
440     buf_sprintf(score, "killer: %s\n", creature_ptr->died_from);
441     buf_sprintf(score, "-----charcter dump-----\n");
442
443     make_dump(creature_ptr, score, update_playtime, display_player);
444
445     if (screen_dump) {
446         buf_sprintf(score, "-----screen shot-----\n");
447         buf_append(score, screen_dump, strlen(screen_dump));
448     }
449
450     term_clear();
451
452     bool succeeded = FALSE;
453     while (!succeeded) {
454         term_fresh();
455
456         prt(_("スコア送信中...", "Sending the score..."), 0, 0);
457         term_fresh();
458
459         if (http_post(SCORE_PATH, score)) {
460             succeeded = TRUE;
461         } else {
462             prt(_("スコア・サーバへの送信に失敗しました。", "Failed to send to the score server."), 0, 0);
463             (void)inkey();
464
465             if (!get_check_strict(creature_ptr, _("もう一度接続を試みますか? ", "Try again? "), CHECK_NO_HISTORY)) {
466                 break;
467             }
468         }
469     }
470
471     buf_delete(score);
472
473     return succeeded ? 0 : 1;
474 }
475 #else
476 concptr screen_dump = NULL;
477 #endif /* WORLD_SCORE */