OSDN Git Service

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