OSDN Git Service

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