OSDN Git Service

[Refactor] #2651 get_random_line() の引数からchar *を削除し、返り値をstd::optional<std::string...
[hengbandforosx/hengbandosx.git] / src / io / files-util.cpp
1 /*!
2  * @brief ファイル入出力管理 / Purpose: code dealing with files (and death)
3  * @date 2014/01/28
4  * @author
5  * <pre>
6  * Copyright (c) 1997 Ben Harrison, James E. Wilson, Robert A. Koeneke
7  * This software may be copied and distributed for educational, research,
8  * and not for profit purposes provided that this copyright and statement
9  * are included in all such copies.  Other copyrights may also apply.
10  * 2014 Deskull rearranged comment for Doxygen.\n
11  * </pre>
12  */
13
14 #include "io/files-util.h"
15 #include "core/asking-player.h"
16 #include "io-dump/character-dump.h"
17 #include "io/input-key-acceptor.h"
18 #include "io/uid-checker.h"
19 #include "monster-race/monster-race.h"
20 #include "monster-race/race-flags1.h"
21 #include "system/monster-race-info.h"
22 #include "system/player-type-definition.h"
23 #include "term/screen-processor.h"
24 #include "term/z-form.h"
25 #include "util/angband-files.h"
26 #include "view/display-messages.h"
27
28 concptr ANGBAND_DIR; //!< Path name: The main "lib" directory This variable is not actually used anywhere in the code
29 concptr ANGBAND_DIR_APEX; //!< High score files (binary) These files may be portable between platforms
30 concptr ANGBAND_DIR_BONE; //!< Bone files for player ghosts (ascii) These files are portable between platforms
31 concptr ANGBAND_DIR_DATA; //!< Binary image files for the "*_info" arrays (binary) These files are not portable between platforms
32 concptr ANGBAND_DIR_EDIT; //!< Textual template files for the "*_info" arrays (ascii) These files are portable between platforms
33 concptr ANGBAND_DIR_SCRIPT; //!< Script files These files are portable between platforms.
34 concptr ANGBAND_DIR_FILE; //!< Various extra files (ascii) These files may be portable between platforms
35 concptr ANGBAND_DIR_HELP; //!< Help files (normal) for the online help (ascii) These files are portable between platforms
36 concptr ANGBAND_DIR_INFO; //!< Help files (spoilers) for the online help (ascii) These files are portable between platforms
37 concptr ANGBAND_DIR_PREF; //!< Default user "preference" files (ascii) These files are rarely portable between platforms
38 concptr ANGBAND_DIR_SAVE; //!< Savefiles for current characters (binary)
39 concptr ANGBAND_DIR_DEBUG_SAVE; //*< Savefiles for debug data
40 concptr ANGBAND_DIR_USER; //!< User "preference" files (ascii) These files are rarely portable between platforms
41 concptr ANGBAND_DIR_XTRA; //!< Various extra files (binary) These files are rarely portable between platforms
42
43 /*
44  * Buffer to hold the current savefile name
45  * 'savefile' holds full path name. 'savefile_base' holds only base name.
46  */
47 char savefile[1024];
48 char savefile_base[40];
49 char debug_savefile[1024];
50
51 /*!
52  * @brief プレイヤーステータスをファイルダンプ出力する
53  * Hack -- Dump a character description file
54  * @param player_ptr プレイヤーへの参照ポインタ
55  * @param name 出力ファイル名
56  * @return エラーコード
57  * @details
58  * Allow the "full" flag to dump additional info,
59  * and trigger its usage from various places in the code.
60  */
61 errr file_character(PlayerType *player_ptr, concptr name)
62 {
63     char buf[1024];
64     path_build(buf, sizeof(buf), ANGBAND_DIR_USER, name);
65     int fd = fd_open(buf, O_RDONLY);
66     if (fd >= 0) {
67         std::string query = _("現存するファイル ", "Replace existing file ");
68         query.append(buf).append(_(" に上書きしますか? ", "? "));
69         (void)fd_close(fd);
70         if (get_check_strict(player_ptr, query, CHECK_NO_HISTORY)) {
71             fd = -1;
72         }
73     }
74
75     FILE *fff = nullptr;
76     if (fd < 0) {
77         fff = angband_fopen(buf, "w");
78     }
79
80     if (!fff) {
81         prt(_("キャラクタ情報のファイルへの書き出しに失敗しました!", "Character dump failed!"), 0, 0);
82         (void)inkey();
83         return -1;
84     }
85
86     make_character_dump(player_ptr, fff);
87     angband_fclose(fff);
88     msg_print(_("キャラクタ情報のファイルへの書き出しに成功しました。", "Character dump successful."));
89     msg_print(nullptr);
90     return 0;
91 }
92
93 /*!
94  * @brief ファイルからランダムに行を一つ取得する
95  * @param file_name ファイル名
96  * @param entry 特定条件時のN:タグヘッダID
97  * @return ファイルから取得した行 (但しファイルがなかったり異常値ならばnullopt)
98  */
99 std::optional<std::string> get_random_line(concptr file_name, int entry)
100 {
101     char filename[1024];
102     path_build(filename, sizeof(filename), ANGBAND_DIR_FILE, file_name);
103     auto *fp = angband_fopen(filename, "r");
104     if (!fp) {
105         return std::nullopt;
106     }
107
108     int test;
109     auto line_num = 0;
110     while (true) {
111         char buf[1024];
112         if (angband_fgets(fp, buf, sizeof(buf)) != 0) {
113             angband_fclose(fp);
114             return std::nullopt;
115         }
116
117         line_num++;
118         if ((buf[0] != 'N') || (buf[1] != ':')) {
119             continue;
120         }
121
122         if (buf[2] == '*') {
123             break;
124         }
125
126         if (buf[2] == 'M') {
127             if (monraces_info[i2enum<MonsterRaceId>(entry)].flags1 & RF1_MALE) {
128                 break;
129             }
130         } else if (buf[2] == 'F') {
131             if (monraces_info[i2enum<MonsterRaceId>(entry)].flags1 & RF1_FEMALE) {
132                 break;
133             }
134         } else if (sscanf(&(buf[2]), "%d", &test) != EOF) {
135             if (test == entry) {
136                 break;
137             }
138         } else {
139             msg_format("Error in line %d of %s!", line_num, file_name);
140             angband_fclose(fp);
141             return std::nullopt;
142         }
143     }
144
145     auto counter = 0;
146     std::string line{};
147     while (true) {
148         char buf[1024];
149         while (true) {
150             if (angband_fgets(fp, buf, sizeof(buf))) {
151                 break;
152             }
153
154             if ((buf[0] == 'N') && (buf[1] == ':')) {
155                 continue;
156             }
157
158             if (buf[0] != '#') {
159                 break;
160             }
161         }
162
163         if (!buf[0]) {
164             break;
165         }
166
167         if (one_in_(counter + 1)) {
168             line = buf;
169         }
170
171         counter++;
172     }
173
174     angband_fclose(fp);
175     if (counter > 0) {
176         return line;
177     }
178
179     return std::nullopt;
180 }
181
182 #ifdef JP
183 /*!
184  * @brief ファイルからランダムに行を一つ取得する(日本語文字列のみ)
185  * @param file_name ファイル名
186  * @param entry 特定条件時のN:タグヘッダID
187  * @param count 試行回数
188  * @return ファイルから取得した行 (但しファイルがなかったり異常値ならばnullopt)
189  * @details
190  */
191 std::optional<std::string> get_random_line_ja_only(concptr file_name, int entry, int count)
192 {
193     std::optional<std::string> line;
194     for (auto i = 0; i < count; i++) {
195         line = get_random_line(file_name, entry);
196         if (!line.has_value()) {
197             return std::nullopt;
198         }
199
200         auto is_kanji = false;
201         for (auto c = line.value().data(); *c != '\0'; c++) {
202             is_kanji |= iskanji(*c);
203         }
204
205         if (is_kanji) {
206             return line;
207         }
208     }
209
210     return line;
211 }
212 #endif
213
214 /*!
215  * @brief ファイル位置をシーク /
216  * @param player_ptr プレイヤーへの参照ポインタ
217  * @param fd ファイルディスクリプタ
218  * @param where ファイルバイト位置
219  * @param flag FALSEならば現ファイルを超えた位置へシーク時エラー、TRUEなら足りない間を0で埋め尽くす
220  * @return エラーコード
221  * @details
222  */
223 static errr counts_seek(PlayerType *player_ptr, int fd, uint32_t where, bool flag)
224 {
225     char temp1[128]{}, temp2[128]{};
226     auto short_pclass = enum2i(player_ptr->pclass);
227 #ifdef SAVEFILE_USE_UID
228     strnfmt(temp1, sizeof(temp1), "%d.%s.%d%d%d", player_ptr->player_uid, savefile_base, short_pclass, player_ptr->ppersonality, player_ptr->age);
229 #else
230     strnfmt(temp1, sizeof(temp1), "%s.%d%d%d", savefile_base, short_pclass, player_ptr->ppersonality, player_ptr->age);
231 #endif
232     for (int i = 0; temp1[i]; i++) {
233         temp1[i] ^= (i + 1) * 63;
234     }
235
236     int seekpoint = 0;
237     uint32_t zero_header[3] = { 0L, 0L, 0L };
238     while (true) {
239         if (fd_seek(fd, seekpoint + 3 * sizeof(uint32_t))) {
240             return 1;
241         }
242         if (fd_read(fd, (char *)(temp2), sizeof(temp2))) {
243             if (!flag) {
244                 return 1;
245             }
246             /* add new name */
247             fd_seek(fd, seekpoint);
248             fd_write(fd, (char *)zero_header, 3 * sizeof(uint32_t));
249             fd_write(fd, (char *)(temp1), sizeof(temp1));
250             break;
251         }
252
253         if (strcmp(temp1, temp2) == 0) {
254             break;
255         }
256
257         seekpoint += 128 + 3 * sizeof(uint32_t);
258     }
259
260     return fd_seek(fd, seekpoint + where * sizeof(uint32_t));
261 }
262
263 /*!
264  * @brief ファイル位置を読み込む
265  * @param player_ptr プレイヤーへの参照ポインタ
266  * @param where ファイルバイト位置
267  * @return エラーコード
268  * @details
269  */
270 uint32_t counts_read(PlayerType *player_ptr, int where)
271 {
272     char buf[1024];
273     path_build(buf, sizeof(buf), ANGBAND_DIR_DATA, _("z_info_j.raw", "z_info.raw"));
274     int fd = fd_open(buf, O_RDONLY);
275
276     uint32_t count = 0;
277     if (counts_seek(player_ptr, fd, where, false) || fd_read(fd, (char *)(&count), sizeof(uint32_t))) {
278         count = 0;
279     }
280
281     (void)fd_close(fd);
282
283     return count;
284 }
285
286 /*!
287  * @brief ファイル位置に書き込む /
288  * @param player_ptr プレイヤーへの参照ポインタ
289  * @param where ファイルバイト位置
290  * @param count 書き込む値
291  * @return エラーコード
292  * @details
293  */
294 errr counts_write(PlayerType *player_ptr, int where, uint32_t count)
295 {
296     char buf[1024];
297     path_build(buf, sizeof(buf), ANGBAND_DIR_DATA, _("z_info_j.raw", "z_info.raw"));
298
299     safe_setuid_grab(player_ptr);
300     int fd = fd_open(buf, O_RDWR);
301     safe_setuid_drop();
302     if (fd < 0) {
303         safe_setuid_grab(player_ptr);
304         fd = fd_make(buf, 0644);
305         safe_setuid_drop();
306     }
307
308     safe_setuid_grab(player_ptr);
309     errr err = fd_lock(fd, F_WRLCK);
310     safe_setuid_drop();
311     if (err) {
312         return 1;
313     }
314
315     counts_seek(player_ptr, fd, where, true);
316     fd_write(fd, (char *)(&count), sizeof(uint32_t));
317     safe_setuid_grab(player_ptr);
318     err = fd_lock(fd, F_UNLCK);
319     safe_setuid_drop();
320
321     if (err) {
322         return 1;
323     }
324
325     (void)fd_close(fd);
326     return 0;
327 }
328
329 /*!
330  * @brief 墓のアスキーアートテンプレを読み込む
331  * @param buf テンプレへのバッファ
332  * @param buf_size バッファの長さ
333  */
334 void read_dead_file(char *buf, size_t buf_size)
335 {
336     path_build(buf, buf_size, ANGBAND_DIR_FILE, _("dead_j.txt", "dead.txt"));
337
338     FILE *fp;
339     fp = angband_fopen(buf, "r");
340     if (!fp) {
341         return;
342     }
343
344     int i = 0;
345     while (angband_fgets(fp, buf, buf_size) == 0) {
346         put_str(buf, i++, 0);
347     }
348
349     angband_fclose(fp);
350 }