OSDN Git Service

[Fix] 属性への免疫を唱えた時のメッセージ
[hengbandforosx/hengbandosx.git] / src / main-win.cpp
1 /*!
2  * @file main-win.cpp
3  * @brief Windows版固有実装(メインエントリポイント含む)
4  * @date 2018/03/16
5  * @author Hengband Team
6  * @details
7  *
8  * <h3>概要</h3>
9  * Windows98かその前後の頃を起点としたAPI実装。
10  * 各種のゲームエンジンは無論、
11  * DirectXといった昨今描画に標準的となったライブラリも用いていない。
12  * タイルの描画処理などについては、現在動作の詳細を検証中。
13  *
14  * <h3>フォーク元の概要</h3>
15  * <p>
16  * Copyright (c) 1997 Ben Harrison, Skirmantas Kligys, and others
17  *
18  * This software may be copied and distributed for educational, research,
19  * and not for profit purposes provided that this copyright and statement
20  * are included in all such copies.
21  * </p>
22  * <p>
23  * This file helps Angband work with Windows computers.
24  *
25  * To use this file, use an appropriate "Makefile" or "Project File",
26  * make sure that "WINDOWS" and/or "WIN32" are defined somewhere, and
27  * make sure to obtain various extra files as described below.
28  *
29  * The official compilation uses the CodeWarrior Pro compiler, which
30  * includes a special project file and precompilable header file.
31  * </p>
32  *
33  * <p>
34  * The "lib/user/pref-win.prf" file contains keymaps, macro definitions,
35  * and/or color redefinitions.
36  * </p>
37  *
38  * <p>
39  * The "lib/user/font-win.prf" contains attr/char mappings for wall.bmp.
40  * </p>
41  *
42  * <p>
43  * The "lib/user/graf-win.prf" contains attr/char mappings for use with the
44  * special bitmap files in "lib/xtra/graf", which are activated by a menu
45  * item.
46  * </p>
47  *
48  * <p>
49  * Compiling this file, and using the resulting executable, requires
50  * several extra files not distributed with the standard Angband code.
51  * All of these extra files can be found in the "ext-win" archive.
52  * </p>
53  *
54  * <p>
55  * The "term_xtra_win_clear()" function should probably do a low-level
56  * clear of the current window, and redraw the borders and other things,
57  * if only for efficiency.
58  * </p>
59  *
60  * <p>
61  * A simpler method is needed for selecting the "tile size" for windows.
62  * </p>
63  *
64  * <p>
65  * The various "warning" messages assume the existance of the "screen.w"
66  * window, I think, and only a few calls actually check for its existance,
67  * this may be okay since "nullptr" means "on top of all windows". (?)  The
68  * user must never be allowed to "hide" the main window, or the "menubar"
69  * will disappear.
70  * </p>
71  *
72  * <p>
73  * Initial framework (and most code) by Ben Harrison (benh@phial.com).
74  *
75  * Original code by Skirmantas Kligys (kligys@scf.usc.edu).
76  *
77  * Additional code by Ross E Becker (beckerr@cis.ohio-state.edu),
78  * and Chris R. Martin (crm7479@tam2000.tamu.edu).
79  * </p>
80  */
81
82 #ifdef WINDOWS
83
84 #include "cmd-io/cmd-save.h"
85 #include "cmd-visual/cmd-draw.h"
86 #include "core/game-play.h"
87 #include "core/player-processor.h"
88 #include "core/score-util.h"
89 #include "core/scores.h"
90 #include "core/special-internal-keys.h"
91 #include "core/stuff-handler.h"
92 #include "core/visuals-reseter.h"
93 #include "core/window-redrawer.h"
94 #include "floor/floor-events.h"
95 #include "game-option/runtime-arguments.h"
96 #include "game-option/special-options.h"
97 #include "io/files-util.h"
98 #include "io/input-key-acceptor.h"
99 #include "io/record-play-movie.h"
100 #include "io/signal-handlers.h"
101 #include "io/write-diary.h"
102 #include "main-win/commandline-win.h"
103 #include "main-win/graphics-win.h"
104 #include "main-win/main-win-bg.h"
105 #include "main-win/main-win-exception.h"
106 #include "main-win/main-win-file-utils.h"
107 #include "main-win/main-win-mci.h"
108 #include "main-win/main-win-menuitem.h"
109 #include "main-win/main-win-music.h"
110 #include "main-win/main-win-sound.h"
111 #include "main-win/main-win-term.h"
112 #include "main-win/main-win-utils.h"
113 #include "main/angband-initializer.h"
114 #include "main/sound-of-music.h"
115 #include "monster-floor/monster-lite.h"
116 #include "save/save.h"
117 #include "system/angband.h"
118 #include "system/player-type-definition.h"
119 #include "system/redrawing-flags-updater.h"
120 #include "system/system-variables.h"
121 #include "term/gameterm.h"
122 #include "term/screen-processor.h"
123 #include "term/term-color-types.h"
124 #include "util/angband-files.h"
125 #include "util/bit-flags-calculator.h"
126 #include "util/enum-converter.h"
127 #include "util/int-char-converter.h"
128 #include "util/string-processor.h"
129 #include "view/display-messages.h"
130 #include "view/display-scores.h"
131 #include "wizard/spoiler-util.h"
132 #include "wizard/wizard-spoiler.h"
133 #include "world/world.h"
134 #include <algorithm>
135 #include <commdlg.h>
136 #include <cstdlib>
137 #include <direct.h>
138 #include <locale>
139 #include <string>
140 #include <string_view>
141 #include <vector>
142
143 /*
144  * Window names
145  */
146 LPCWSTR win_term_name[] = { L"Hengband", L"Term-1", L"Term-2", L"Term-3", L"Term-4", L"Term-5", L"Term-6", L"Term-7" };
147
148 #define MAX_TERM_DATA 8 //!< Maximum number of windows
149
150 static term_data data[MAX_TERM_DATA]; //!< An array of term_data's
151 static bool is_main_term(term_data *td)
152 {
153     return td == &data[0];
154 }
155 static term_data *my_td; //!< Hack -- global "window creation" pointer
156 POINT normsize; //!< Remember normal size of main window when maxmized
157
158 /*
159  * was main window maximized on previous playing
160  */
161 bool win_maximized = false;
162
163 /*
164  * game in progress
165  */
166 bool game_in_progress = false;
167
168 /*
169  * movie in progress
170  */
171 bool movie_in_progress = false;
172
173 /*
174  * note when "open"/"new" become valid
175  */
176 bool initialized = false;
177
178 /*
179  * Saved instance handle
180  */
181 static HINSTANCE hInstance;
182
183 /*
184  * Yellow brush for the cursor
185  */
186 static HBRUSH hbrYellow;
187
188 /*
189  * An icon
190  */
191 static HICON hIcon;
192
193 /* bg */
194 bg_mode current_bg_mode = bg_mode::BG_NONE;
195 #define DEFAULT_BG_FILENAME "bg.bmp"
196 std::filesystem::path wallpaper_path = ""; //!< 壁紙ファイル名。
197
198 /*
199  * Show sub-windows even when Hengband is not in focus
200  */
201 static bool keep_subwindows = true;
202
203 /*
204  * Full path to ANGBAND.INI
205  */
206 static concptr ini_file = nullptr;
207
208 /*
209  * Name of application
210  */
211 static LPCWSTR AppName = L"ANGBAND";
212
213 /*
214  * Name of sub-window type
215  */
216 static LPCWSTR AngList = L"AngList";
217
218 /*
219  * The "complex" color values
220  */
221 static COLORREF win_clr[256];
222
223 /*
224  * Flag for macro trigger with dump ASCII
225  */
226 static bool term_no_press = false;
227
228 /*
229  * Copy and paste
230  */
231 static bool mouse_down = false;
232 static bool paint_rect = false;
233 static TERM_LEN mousex = 0, mousey = 0;
234 static TERM_LEN oldx, oldy;
235
236 /*
237  * Hack -- define which keys are "special"
238  */
239 static bool special_key[256];
240 static bool ignore_key[256];
241
242 /*
243  * Hack -- initialization list for "special_key"
244  */
245 static byte special_key_list[] = {
246     VK_CLEAR, VK_PAUSE, VK_CAPITAL, VK_KANA, VK_JUNJA, VK_FINAL, VK_KANJI, VK_CONVERT, VK_NONCONVERT, VK_ACCEPT, VK_MODECHANGE, VK_PRIOR, VK_NEXT, VK_END,
247     VK_HOME, VK_LEFT, VK_UP, VK_RIGHT, VK_DOWN, VK_SELECT, VK_PRINT, VK_EXECUTE, VK_SNAPSHOT, VK_INSERT, VK_DELETE, VK_HELP, VK_APPS, VK_NUMPAD0, VK_NUMPAD1,
248     VK_NUMPAD2, VK_NUMPAD3, VK_NUMPAD4, VK_NUMPAD5, VK_NUMPAD6, VK_NUMPAD7, VK_NUMPAD8, VK_NUMPAD9, VK_MULTIPLY, VK_ADD, VK_SEPARATOR, VK_SUBTRACT, VK_DECIMAL,
249     VK_DIVIDE, VK_F1, VK_F2, VK_F3, VK_F4, VK_F5, VK_F6, VK_F7, VK_F8, VK_F9, VK_F10, VK_F11, VK_F12, VK_F13, VK_F14, VK_F15, VK_F16, VK_F17, VK_F18, VK_F19,
250     VK_F20, VK_F21, VK_F22, VK_F23, VK_F24, VK_NUMLOCK, VK_SCROLL, VK_ATTN, VK_CRSEL, VK_EXSEL, VK_EREOF, VK_PLAY, VK_ZOOM, VK_NONAME, VK_PA1,
251     0 /* End of List */
252 };
253
254 static byte ignore_key_list[] = {
255     VK_ESCAPE, VK_TAB, VK_SPACE, 'F', 'W', 'O', /*'H',*/ /* these are menu characters.*/
256     VK_SHIFT, VK_CONTROL, VK_MENU, VK_LWIN, VK_RWIN, VK_LSHIFT, VK_RSHIFT, VK_LCONTROL, VK_RCONTROL, VK_LMENU, VK_RMENU, 0 /* End of List */
257 };
258
259 /*!
260  * @brief Validate a file
261  */
262 static void validate_file(const std::filesystem::path &s)
263 {
264     if (std::filesystem::is_regular_file(s)) {
265         return;
266     }
267
268     const auto &file = s.string();
269     quit_fmt(_("必要なファイル[%s]が見あたりません。", "Cannot find required file:\n%s"), file.data());
270 }
271
272 /*!
273  * @brief Validate a directory
274  */
275 static void validate_dir(const std::filesystem::path &s, bool vital)
276 {
277     if (std::filesystem::is_directory(s)) {
278         return;
279     }
280
281     const auto &dir = s.string();
282     if (vital) {
283         quit_fmt(_("必要なディレクトリ[%s]が見あたりません。", "Cannot find required directory:\n%s"), dir.data());
284     } else if (mkdir(dir.data())) {
285         quit_fmt("Unable to create directory:\n%s", dir.data());
286     }
287 }
288
289 /*!
290  * @brief (Windows版固有実装)Get the "size" for a window
291  */
292 static void term_getsize(term_data *td)
293 {
294     if (td->cols < 1) {
295         td->cols = 1;
296     }
297     if (td->rows < 1) {
298         td->rows = 1;
299     }
300
301     TERM_LEN wid = td->cols * td->tile_wid + td->size_ow1 + td->size_ow2;
302     TERM_LEN hgt = td->rows * td->tile_hgt + td->size_oh1 + td->size_oh2;
303
304     RECT rw, rc;
305     if (td->w) {
306         GetWindowRect(td->w, &rw);
307         GetClientRect(td->w, &rc);
308
309         td->size_wid = (rw.right - rw.left) - (rc.right - rc.left) + wid;
310         td->size_hgt = (rw.bottom - rw.top) - (rc.bottom - rc.top) + hgt;
311
312         td->pos_x = rw.left;
313         td->pos_y = rw.top;
314     } else {
315         /* Tempolary calculation */
316         rc.left = 0;
317         rc.right = wid;
318         rc.top = 0;
319         rc.bottom = hgt;
320         AdjustWindowRectEx(&rc, td->dwStyle, TRUE, td->dwExStyle);
321         td->size_wid = rc.right - rc.left;
322         td->size_hgt = rc.bottom - rc.top;
323     }
324 }
325
326 /*!
327  * @brief Write the "prefs" for a single term
328  */
329 static void save_prefs_aux(int i)
330 {
331     term_data *td = &data[i];
332     GAME_TEXT sec_name[128];
333     char buf[1024];
334
335     if (!td->w) {
336         return;
337     }
338
339     wsprintfA(sec_name, "Term-%d", i);
340
341     if (i > 0) {
342         strcpy(buf, td->visible ? "1" : "0");
343         WritePrivateProfileStringA(sec_name, "Visible", buf, ini_file);
344     }
345
346     auto pwchar = td->lf.lfFaceName[0] != '\0' ? td->lf.lfFaceName : _(L"MS ゴシック", L"Courier");
347     WritePrivateProfileStringA(sec_name, "Font", to_multibyte(pwchar).c_str(), ini_file);
348
349     wsprintfA(buf, "%d", td->lf.lfWidth);
350     WritePrivateProfileStringA(sec_name, "FontWid", buf, ini_file);
351     wsprintfA(buf, "%d", td->lf.lfHeight);
352     WritePrivateProfileStringA(sec_name, "FontHgt", buf, ini_file);
353     wsprintfA(buf, "%d", td->lf.lfWeight);
354     WritePrivateProfileStringA(sec_name, "FontWgt", buf, ini_file);
355
356     wsprintfA(buf, "%d", td->tile_wid);
357     WritePrivateProfileStringA(sec_name, "TileWid", buf, ini_file);
358
359     wsprintfA(buf, "%d", td->tile_hgt);
360     WritePrivateProfileStringA(sec_name, "TileHgt", buf, ini_file);
361
362     WINDOWPLACEMENT lpwndpl{};
363     lpwndpl.length = sizeof(WINDOWPLACEMENT);
364     GetWindowPlacement(td->w, &lpwndpl);
365
366     RECT rc = lpwndpl.rcNormalPosition;
367     if (i == 0) {
368         wsprintfA(buf, "%d", normsize.x);
369     } else {
370         wsprintfA(buf, "%d", td->cols);
371     }
372
373     WritePrivateProfileStringA(sec_name, "NumCols", buf, ini_file);
374
375     if (i == 0) {
376         wsprintfA(buf, "%d", normsize.y);
377     } else {
378         wsprintfA(buf, "%d", td->rows);
379     }
380
381     WritePrivateProfileStringA(sec_name, "NumRows", buf, ini_file);
382     if (i == 0) {
383         strcpy(buf, IsZoomed(td->w) ? "1" : "0");
384         WritePrivateProfileStringA(sec_name, "Maximized", buf, ini_file);
385     }
386
387     GetWindowRect(td->w, &rc);
388     wsprintfA(buf, "%d", rc.left);
389     WritePrivateProfileStringA(sec_name, "PositionX", buf, ini_file);
390
391     wsprintfA(buf, "%d", rc.top);
392     WritePrivateProfileStringA(sec_name, "PositionY", buf, ini_file);
393     if (i > 0) {
394         strcpy(buf, td->posfix ? "1" : "0");
395         WritePrivateProfileStringA(sec_name, "PositionFix", buf, ini_file);
396     }
397 }
398
399 /*!
400  * @brief Write the "prefs"
401  * We assume that the windows have all been initialized
402  */
403 static void save_prefs(void)
404 {
405     char buf[128];
406     wsprintfA(buf, "%d", arg_graphics);
407     WritePrivateProfileStringA("Angband", "Graphics", buf, ini_file);
408
409     strcpy(buf, arg_bigtile ? "1" : "0");
410     WritePrivateProfileStringA("Angband", "Bigtile", buf, ini_file);
411
412     strcpy(buf, arg_sound ? "1" : "0");
413     WritePrivateProfileStringA("Angband", "Sound", buf, ini_file);
414     WritePrivateProfileStringA("Angband", "SoundVolumeTableIndex", std::to_string(arg_sound_volume_table_index).data(), ini_file);
415
416     strcpy(buf, arg_music ? "1" : "0");
417     WritePrivateProfileStringA("Angband", "Music", buf, ini_file);
418     strcpy(buf, use_pause_music_inactive ? "1" : "0");
419     WritePrivateProfileStringA("Angband", "MusicVolumeTableIndex", std::to_string(arg_music_volume_table_index).data(), ini_file);
420     WritePrivateProfileStringA("Angband", "MusicPauseInactive", buf, ini_file);
421
422     wsprintfA(buf, "%d", current_bg_mode);
423     WritePrivateProfileStringA("Angband", "BackGround", buf, ini_file);
424     const auto &wallpaper_filename = wallpaper_path.string();
425     WritePrivateProfileStringA("Angband", "BackGroundBitmap", !wallpaper_path.empty() ? wallpaper_filename.data() : DEFAULT_BG_FILENAME, ini_file);
426
427     auto angband_dir_str = ANGBAND_DIR.string();
428     const auto path_length = angband_dir_str.length() - 4; // "\lib" を除く.
429     angband_dir_str = angband_dir_str.substr(0, path_length);
430     const auto savefile_str = savefile.string();
431     const auto savefile_dir_str = savefile_str.substr(0, path_length);
432     if (angband_dir_str == savefile_dir_str) {
433         const auto relative_path = format(".\\%s", (savefile_str.data() + path_length));
434         WritePrivateProfileStringA("Angband", "SaveFile", relative_path.data(), ini_file);
435     } else {
436         WritePrivateProfileStringA("Angband", "SaveFile", savefile_str.data(), ini_file);
437     }
438
439     strcpy(buf, keep_subwindows ? "1" : "0");
440     WritePrivateProfileStringA("Angband", "KeepSubwindows", buf, ini_file);
441
442     for (int i = 0; i < MAX_TERM_DATA; ++i) {
443         save_prefs_aux(i);
444     }
445 }
446
447 /*!
448  * @brief callback for EnumDisplayMonitors API
449  */
450 BOOL CALLBACK monitor_enum_procedure([[maybe_unused]] HMONITOR hMon, [[maybe_unused]] HDC hdcMon, [[maybe_unused]] LPRECT lpMon, LPARAM dwDate)
451 {
452     bool *result = (bool *)dwDate;
453     *result = true;
454     return false;
455 }
456
457 /*!
458  * @brief Load the "prefs" for a single term
459  */
460 static void load_prefs_aux(int i)
461 {
462     term_data *td = &data[i];
463     GAME_TEXT sec_name[128];
464     char tmp[1024];
465
466     wsprintfA(sec_name, "Term-%d", i);
467     if (i > 0) {
468         td->visible = (GetPrivateProfileIntA(sec_name, "Visible", td->visible, ini_file) != 0);
469     }
470
471     GetPrivateProfileStringA(sec_name, "Font", _("MS ゴシック", "Courier"), tmp, 127, ini_file);
472
473     td->font_want = string_make(tmp);
474     int hgt = 15;
475     int wid = 0;
476     td->lf.lfWidth = GetPrivateProfileIntA(sec_name, "FontWid", wid, ini_file);
477     td->lf.lfHeight = GetPrivateProfileIntA(sec_name, "FontHgt", hgt, ini_file);
478     td->lf.lfWeight = GetPrivateProfileIntA(sec_name, "FontWgt", 0, ini_file);
479
480     td->tile_wid = GetPrivateProfileIntA(sec_name, "TileWid", td->lf.lfWidth, ini_file);
481     td->tile_hgt = GetPrivateProfileIntA(sec_name, "TileHgt", td->lf.lfHeight, ini_file);
482
483     td->cols = GetPrivateProfileIntA(sec_name, "NumCols", td->cols, ini_file);
484     td->rows = GetPrivateProfileIntA(sec_name, "NumRows", td->rows, ini_file);
485     normsize.x = td->cols;
486     normsize.y = td->rows;
487
488     if (i == 0) {
489         win_maximized = (GetPrivateProfileIntA(sec_name, "Maximized", win_maximized, ini_file) != 0);
490     }
491
492     int posx = GetPrivateProfileIntA(sec_name, "PositionX", 0, ini_file);
493     int posy = GetPrivateProfileIntA(sec_name, "PositionY", 0, ini_file);
494     // 保存座標がモニタ内の領域にあるかチェック
495     RECT rect = { posx, posy, posx + 128, posy + 128 };
496     bool in_any_monitor = false;
497     ::EnumDisplayMonitors(NULL, &rect, monitor_enum_procedure, (LPARAM)&in_any_monitor);
498     if (in_any_monitor) {
499         // いずれかのモニタに表示可能、ウインドウ位置を復元
500         td->pos_x = posx;
501         td->pos_y = posy;
502     }
503
504     if (i > 0) {
505         td->posfix = (GetPrivateProfileIntA(sec_name, "PositionFix", td->posfix, ini_file) != 0);
506     }
507 }
508
509 /*!
510  * @brief Load the "prefs"
511  */
512 static void load_prefs(void)
513 {
514     arg_graphics = (byte)GetPrivateProfileIntA("Angband", "Graphics", enum2i(graphics_mode::GRAPHICS_NONE), ini_file);
515     arg_bigtile = (GetPrivateProfileIntA("Angband", "Bigtile", false, ini_file) != 0);
516     use_bigtile = arg_bigtile;
517     arg_sound = (GetPrivateProfileIntA("Angband", "Sound", 0, ini_file) != 0);
518     arg_sound_volume_table_index = std::clamp<int>(GetPrivateProfileIntA("Angband", "SoundVolumeTableIndex", 0, ini_file), 0, SOUND_VOLUME_TABLE.size() - 1);
519     arg_music = (GetPrivateProfileIntA("Angband", "Music", 0, ini_file) != 0);
520     arg_music_volume_table_index = std::clamp<int>(GetPrivateProfileIntA("Angband", "MusicVolumeTableIndex", 0, ini_file), 0, main_win_music::VOLUME_TABLE.size() - 1);
521     use_pause_music_inactive = (GetPrivateProfileIntA("Angband", "MusicPauseInactive", 0, ini_file) != 0);
522     current_bg_mode = static_cast<bg_mode>(GetPrivateProfileIntA("Angband", "BackGround", 0, ini_file));
523     char wallpaper_buf[1024]{};
524     GetPrivateProfileStringA("Angband", "BackGroundBitmap", DEFAULT_BG_FILENAME, wallpaper_buf, 1023, ini_file);
525     wallpaper_path = wallpaper_buf;
526     char savefile_buf[1024]{};
527     GetPrivateProfileStringA("Angband", "SaveFile", "", savefile_buf, 1023, ini_file);
528     if (strncmp(".\\", savefile_buf, 2) == 0) {
529         std::string angband_dir_str(ANGBAND_DIR.string());
530         const auto path_length = angband_dir_str.length() - 4; // "\lib" を除く.
531         angband_dir_str = angband_dir_str.substr(0, path_length);
532         char tmp[1024] = "";
533         strncat(tmp, angband_dir_str.data(), path_length);
534         strncat(tmp, savefile_buf + 2, std::string_view(savefile_buf).length() - 2 + path_length);
535         savefile = tmp;
536     } else {
537         savefile = savefile_buf;
538     }
539
540     keep_subwindows = (GetPrivateProfileIntA("Angband", "KeepSubwindows", 0, ini_file) != 0);
541     for (int i = 0; i < MAX_TERM_DATA; ++i) {
542         load_prefs_aux(i);
543     }
544 }
545
546 /*!
547  * @brief Initialize music
548  */
549 static void init_music(void)
550 {
551     // Flag set once "music" has been initialized
552     static bool can_use_music = false;
553
554     if (!can_use_music) {
555         main_win_music::load_music_prefs();
556         can_use_music = true;
557     }
558 }
559
560 /*!
561  * @brief Initialize sound
562  */
563 static void init_sound(void)
564 {
565     // Flag set once "sound" has been initialized
566     static bool can_use_sound = false;
567
568     if (!can_use_sound) {
569         load_sound_prefs();
570         can_use_sound = true;
571     }
572 }
573
574 /*!
575  * @brief Change sound mode
576  * @param new_mode bool
577  */
578 static void change_sound_mode(bool new_mode)
579 {
580     use_sound = new_mode;
581     if (use_sound) {
582         init_sound();
583     }
584 }
585
586 /*!
587  * @brief Initialize background
588  */
589 static void init_background(void)
590 {
591     // Flag set once "background" has been initialized
592     static bool can_use_background = false;
593
594     if (!can_use_background) {
595         load_bg_prefs();
596         can_use_background = true;
597     }
598 }
599
600 /*!
601  * @brief Change background mode
602  * @param new_mode bg_mode
603  * @param show_error trueに設定した場合のみ、エラーダイアログを表示する
604  * @param force_redraw trueの場合、モード変更に関わらずウインドウを再描画する
605  * @retval true success
606  * @retval false failed
607  */
608 static bool change_bg_mode(bg_mode new_mode, bool show_error = false, bool force_redraw = false)
609 {
610     bg_mode old_bg_mode = current_bg_mode;
611     current_bg_mode = new_mode;
612     if (current_bg_mode != bg_mode::BG_NONE) {
613         init_background();
614         if (!load_bg(wallpaper_path)) {
615             current_bg_mode = bg_mode::BG_NONE;
616             if (show_error) {
617                 const auto &wallaper_filename = wallpaper_path.string();
618                 plog_fmt(_("壁紙用ファイル '%s' を読み込めません。", "Can't load the image file '%s'."), wallaper_filename.data());
619             }
620         }
621     } else {
622         delete_bg();
623     }
624
625     const bool mode_changed = (current_bg_mode != old_bg_mode);
626     if (mode_changed || force_redraw) {
627         // 全ウインドウ再描画
628         term_type *old = game_term;
629         for (int i = 0; i < MAX_TERM_DATA; i++) {
630             term_data *td = &data[i];
631             if (td->visible) {
632                 term_activate(&td->t);
633                 term_redraw();
634             }
635         }
636         term_activate(old);
637     }
638
639     return current_bg_mode == new_mode;
640 }
641
642 /*!
643  * @brief Resize a window
644  */
645 static void term_window_resize(term_data *td)
646 {
647     if (!td->w) {
648         return;
649     }
650
651     SetWindowPos(td->w, 0, 0, 0, td->size_wid, td->size_hgt, SWP_NOMOVE | SWP_NOZORDER);
652     if (!td->size_hack) {
653         td->dispose_offscreen();
654         term_activate(&td->t);
655         term_redraw();
656     }
657 }
658
659 /*!
660  * @brief Force the use of a new font for a term_data.
661  * This function may be called before the "window" is ready.
662  * This function returns zero only if everything succeeds.
663  * @note that the "font name" must be capitalized!!!
664  */
665 static errr term_force_font(term_data *td)
666 {
667     if (td->font_id) {
668         DeleteObject(td->font_id);
669     }
670
671     td->font_id = CreateFontIndirectW(&(td->lf));
672     int wid = td->lf.lfWidth;
673     int hgt = td->lf.lfHeight;
674     if (!td->font_id) {
675         return 1;
676     }
677
678     if (!wid || !hgt) {
679         HDC hdcDesktop;
680         HFONT hfOld;
681         TEXTMETRIC tm;
682
683         hdcDesktop = GetDC(HWND_DESKTOP);
684         hfOld = static_cast<HFONT>(SelectObject(hdcDesktop, td->font_id));
685         GetTextMetrics(hdcDesktop, &tm);
686         SelectObject(hdcDesktop, hfOld);
687         ReleaseDC(HWND_DESKTOP, hdcDesktop);
688
689         wid = tm.tmAveCharWidth;
690         hgt = tm.tmHeight;
691     }
692
693     td->font_wid = wid;
694     td->font_hgt = hgt;
695
696     return 0;
697 }
698
699 /*!
700  * @brief Allow the user to change the font for this window.
701  */
702 static void term_change_font(term_data *td)
703 {
704     CHOOSEFONTW cf{};
705     cf.lStructSize = sizeof(cf);
706     cf.Flags = CF_SCREENFONTS | CF_FIXEDPITCHONLY | CF_NOVERTFONTS | CF_INITTOLOGFONTSTRUCT;
707     cf.lpLogFont = &(td->lf);
708
709     if (!ChooseFontW(&cf)) {
710         return;
711     }
712
713     term_force_font(td);
714     td->tile_wid = td->font_wid;
715     td->tile_hgt = td->font_hgt;
716     term_getsize(td);
717     term_window_resize(td);
718 }
719
720 /*!
721  * @brief Allow the user to lock this window.
722  */
723 static void term_window_pos(term_data *td, HWND hWnd)
724 {
725     SetWindowPos(td->w, hWnd, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
726 }
727
728 /*!
729  * @brief Hack -- redraw a term_data
730  */
731 static void term_data_redraw(term_data *td)
732 {
733     term_activate(&td->t);
734     term_redraw();
735     term_activate(term_screen);
736 }
737
738 /*!
739  * @brief termの反転色表示
740  */
741 void term_inversed_area(HWND hWnd, int x, int y, int w, int h)
742 {
743     term_data *td = (term_data *)GetWindowLong(hWnd, 0);
744     int tx = td->size_ow1 + x * td->tile_wid;
745     int ty = td->size_oh1 + y * td->tile_hgt;
746     int tw = w * td->tile_wid - 1;
747     int th = h * td->tile_hgt - 1;
748
749     HDC hdc = td->get_hdc();
750     HBRUSH myBrush = CreateSolidBrush(RGB(255, 255, 255));
751     HBRUSH oldBrush = static_cast<HBRUSH>(SelectObject(hdc, myBrush));
752     HPEN oldPen = static_cast<HPEN>(SelectObject(hdc, GetStockObject(NULL_PEN)));
753
754     PatBlt(hdc, tx, ty, tw, th, PATINVERT);
755
756     SelectObject(hdc, oldBrush);
757     SelectObject(hdc, oldPen);
758
759     RECT rect{ tx, ty, tx + tw, ty + th };
760     td->refresh(&rect);
761 }
762
763 /*!
764  * @brief Windows版ユーザ設定項目実装部(実装必須) /Interact with the User
765  */
766 static errr term_user_win(int n)
767 {
768     (void)n;
769     return 0;
770 }
771
772 /*!
773  * @brief カラーパレットの変更?
774  */
775 static void refresh_color_table()
776 {
777     for (int i = 0; i < 256; i++) {
778         byte rv = angband_color_table[i][1];
779         byte gv = angband_color_table[i][2];
780         byte bv = angband_color_table[i][3];
781         win_clr[i] = PALETTERGB(rv, gv, bv);
782     }
783 }
784
785 /*!
786  * @brief グラフィクスのモード変更
787  */
788 static void change_graphics_mode(graphics_mode mode)
789 {
790     graphics_mode ret = graphic.change_graphics(mode);
791     if (ret != mode) {
792         plog(_("グラフィクスを初期化できません!", "Cannot initialize graphics!"));
793     }
794     arg_graphics = static_cast<byte>(ret);
795     use_graphics = (arg_graphics > 0);
796 }
797
798 /*!
799  * @brief ターミナルのサイズ更新
800  * @details 行数、列数の変更に対応する。
801  * @param td term_dataのポインタ
802  * @param resize_window trueの場合に再計算されたウインドウサイズにリサイズする
803  */
804 static void rebuild_term(term_data *td, bool resize_window = true)
805 {
806     term_type *old = game_term;
807     td->size_hack = true;
808     term_activate(&td->t);
809     term_getsize(td);
810     if (resize_window) {
811         term_window_resize(td);
812     }
813     td->dispose_offscreen();
814     term_resize(td->cols, td->rows);
815     td->size_hack = false;
816     term_activate(old);
817 }
818
819 /*!
820  * @brief React to global changes
821  */
822 static errr term_xtra_win_react(PlayerType *player_ptr)
823 {
824     refresh_color_table();
825
826     const byte current_mode = static_cast<byte>(graphic.get_mode());
827     if (current_mode != arg_graphics) {
828         change_graphics_mode(static_cast<graphics_mode>(arg_graphics));
829         reset_visuals(player_ptr);
830     }
831
832     for (int i = 0; i < MAX_TERM_DATA; i++) {
833         term_data *td = &data[i];
834         if ((td->cols != td->t.wid) || (td->rows != td->t.hgt)) {
835             rebuild_term(td);
836         }
837     }
838
839     return 0;
840 }
841
842 /*!
843  * @brief Process at least one event
844  */
845 static errr term_xtra_win_event(int v)
846 {
847     MSG msg;
848     if (v) {
849         if (GetMessage(&msg, NULL, 0, 0)) {
850             TranslateMessage(&msg);
851             DispatchMessage(&msg);
852         }
853     } else {
854         if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
855             TranslateMessage(&msg);
856             DispatchMessage(&msg);
857         }
858     }
859
860     return 0;
861 }
862
863 /*!
864  * @brief Process all pending events
865  */
866 static errr term_xtra_win_flush(void)
867 {
868     MSG msg;
869     while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
870         TranslateMessage(&msg);
871         DispatchMessage(&msg);
872     }
873
874     return 0;
875 }
876
877 /*!
878  * @brief Hack -- clear the screen
879  * @details
880  * Make this more efficient
881  */
882 static errr term_xtra_win_clear(void)
883 {
884     term_data *td = (term_data *)(game_term->data);
885
886     RECT rc;
887     GetClientRect(td->w, &rc);
888
889     HDC hdc = td->get_hdc();
890     SetBkColor(hdc, RGB(0, 0, 0));
891     SelectObject(hdc, td->font_id);
892     ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL);
893
894     if (current_bg_mode != bg_mode::BG_NONE) {
895         rc.left = 0;
896         rc.top = 0;
897         draw_bg(hdc, &rc);
898     }
899
900     td->refresh();
901     return 0;
902 }
903
904 /*!
905  * @brief Hack -- make a noise
906  */
907 static errr term_xtra_win_noise(void)
908 {
909     MessageBeep(MB_ICONASTERISK);
910     return 0;
911 }
912
913 /*!
914  * @brief Hack -- make a sound
915  */
916 static errr term_xtra_win_sound(int v)
917 {
918     if (!use_sound) {
919         return 1;
920     }
921     return play_sound(v, SOUND_VOLUME_TABLE[arg_sound_volume_table_index]);
922 }
923
924 /*!
925  * @brief Hack -- play a music
926  */
927 static errr term_xtra_win_music(int n, int v)
928 {
929     if (!use_music) {
930         return 1;
931     }
932
933     return main_win_music::play_music(n, v);
934 }
935
936 /*!
937  * @brief Hack -- play a music matches a situation
938  */
939 static errr term_xtra_win_scene(int v)
940 {
941     // TODO 場面に合った壁紙変更対応
942     if (!use_music) {
943         return 1;
944     }
945
946     return main_win_music::play_music_scene(v);
947 }
948
949 /*!
950  * @brief Delay for "x" milliseconds
951  */
952 static int term_xtra_win_delay(int v)
953 {
954     Sleep(v);
955     return 0;
956 }
957
958 /*!
959  * @brief Do a "special thing"
960  * @todo z-termに影響があるのでPlayerTypeの追加は保留
961  */
962 static errr term_xtra_win(int n, int v)
963 {
964     switch (n) {
965     case TERM_XTRA_NOISE: {
966         return term_xtra_win_noise();
967     }
968     case TERM_XTRA_FRESH: {
969         term_data *td = (term_data *)(game_term->data);
970         if (td->w) {
971             UpdateWindow(td->w);
972         }
973         return 0;
974     }
975     case TERM_XTRA_MUSIC_BASIC:
976     case TERM_XTRA_MUSIC_DUNGEON:
977     case TERM_XTRA_MUSIC_QUEST:
978     case TERM_XTRA_MUSIC_TOWN:
979     case TERM_XTRA_MUSIC_MONSTER: {
980         return term_xtra_win_music(n, v);
981     }
982     case TERM_XTRA_MUSIC_MUTE: {
983         return main_win_music::stop_music();
984     }
985     case TERM_XTRA_SCENE: {
986         return term_xtra_win_scene(v);
987     }
988     case TERM_XTRA_SOUND: {
989         return term_xtra_win_sound(v);
990     }
991     case TERM_XTRA_BORED: {
992         return term_xtra_win_event(0);
993     }
994     case TERM_XTRA_EVENT: {
995         return term_xtra_win_event(v);
996     }
997     case TERM_XTRA_FLUSH: {
998         return term_xtra_win_flush();
999     }
1000     case TERM_XTRA_CLEAR: {
1001         return term_xtra_win_clear();
1002     }
1003     case TERM_XTRA_REACT: {
1004         return term_xtra_win_react(p_ptr);
1005     }
1006     case TERM_XTRA_DELAY: {
1007         return term_xtra_win_delay(v);
1008     }
1009     }
1010
1011     return 1;
1012 }
1013
1014 /*!
1015  * @brief Low level graphics (Assumes valid input).
1016  * @details
1017  * Draw a "cursor" at (x,y), using a "yellow box".
1018  */
1019 static errr term_curs_win(int x, int y)
1020 {
1021     term_data *td = (term_data *)(game_term->data);
1022     int tile_wid, tile_hgt;
1023     tile_wid = td->tile_wid;
1024     tile_hgt = td->tile_hgt;
1025
1026     RECT rc{};
1027     rc.left = x * tile_wid + td->size_ow1;
1028     rc.right = rc.left + tile_wid;
1029     rc.top = y * tile_hgt + td->size_oh1;
1030     rc.bottom = rc.top + tile_hgt;
1031
1032     HDC hdc = td->get_hdc();
1033     FrameRect(hdc, &rc, hbrYellow);
1034     td->refresh(&rc);
1035     return 0;
1036 }
1037
1038 /*!
1039  * @brief Low level graphics (Assumes valid input).
1040  * @details
1041  * Draw a "big cursor" at (x,y), using a "yellow box".
1042  */
1043 static errr term_bigcurs_win(int x, int y)
1044 {
1045     term_data *td = (term_data *)(game_term->data);
1046     int tile_wid, tile_hgt;
1047     tile_wid = td->tile_wid;
1048     tile_hgt = td->tile_hgt;
1049
1050     RECT rc{};
1051     rc.left = x * tile_wid + td->size_ow1;
1052     rc.right = rc.left + 2 * tile_wid;
1053     rc.top = y * tile_hgt + td->size_oh1;
1054     rc.bottom = rc.top + tile_hgt;
1055
1056     HDC hdc = td->get_hdc();
1057     FrameRect(hdc, &rc, hbrYellow);
1058     td->refresh(&rc);
1059     return 0;
1060 }
1061
1062 /*!
1063  * @brief Low level graphics (Assumes valid input).
1064  * @details
1065  * Erase a "block" of "n" characters starting at (x,y).
1066  */
1067 static errr term_wipe_win(int x, int y, int n)
1068 {
1069     term_data *td = (term_data *)(game_term->data);
1070     RECT rc{};
1071     rc.left = x * td->tile_wid + td->size_ow1;
1072     rc.right = rc.left + n * td->tile_wid;
1073     rc.top = y * td->tile_hgt + td->size_oh1;
1074     rc.bottom = rc.top + td->tile_hgt;
1075
1076     HDC hdc = td->get_hdc();
1077     SetBkColor(hdc, RGB(0, 0, 0));
1078     SelectObject(hdc, td->font_id);
1079     if (current_bg_mode != bg_mode::BG_NONE) {
1080         draw_bg(hdc, &rc);
1081     } else {
1082         ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL);
1083     }
1084
1085     td->refresh(&rc);
1086     return 0;
1087 }
1088
1089 /*!
1090  * @brief Low level graphics.  Assumes valid input.
1091  * @details
1092  * Draw several ("n") chars, with an attr, at a given location.
1093  *
1094  * All "graphic" data is handled by "term_pict_win()", below.
1095  *
1096  * One would think there is a more efficient method for telling a window
1097  * what color it should be using to draw with, but perhaps simply changing
1098  * it every time is not too inefficient.
1099  */
1100 static errr term_text_win(int x, int y, int n, TERM_COLOR a, concptr s)
1101 {
1102     term_data *td = (term_data *)(game_term->data);
1103     static HBITMAP WALL;
1104     static HBRUSH myBrush, oldBrush;
1105     static HPEN oldPen;
1106     static bool init_done = false;
1107
1108     if (!init_done) {
1109         WALL = LoadBitmapW(hInstance, AppName);
1110         myBrush = CreatePatternBrush(WALL);
1111         init_done = true;
1112     }
1113
1114     RECT rc{ static_cast<LONG>(x * td->tile_wid + td->size_ow1), static_cast<LONG>(y * td->tile_hgt + td->size_oh1),
1115         static_cast<LONG>(rc.left + n * td->tile_wid), static_cast<LONG>(rc.top + td->tile_hgt) };
1116     RECT rc_start = rc;
1117
1118     HDC hdc = td->get_hdc();
1119     SetBkColor(hdc, RGB(0, 0, 0));
1120     SetTextColor(hdc, win_clr[a]);
1121
1122     SelectObject(hdc, td->font_id);
1123     if (current_bg_mode != bg_mode::BG_NONE) {
1124         SetBkMode(hdc, TRANSPARENT);
1125     }
1126
1127     ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL);
1128     if (current_bg_mode != bg_mode::BG_NONE) {
1129         draw_bg(hdc, &rc);
1130     }
1131
1132     rc.left += ((td->tile_wid - td->font_wid) / 2);
1133     rc.right = rc.left + td->font_wid;
1134     rc.top += ((td->tile_hgt - td->font_hgt) / 2);
1135     rc.bottom = rc.top + td->font_hgt;
1136
1137     for (int i = 0; i < n; i++) {
1138 #ifdef JP
1139         if (use_bigtile && *(s + i) == "■"[0] && *(s + i + 1) == "■"[1]) {
1140             rc.right += td->font_wid;
1141             oldBrush = static_cast<HBRUSH>(SelectObject(hdc, myBrush));
1142             oldPen = static_cast<HPEN>(SelectObject(hdc, GetStockObject(NULL_PEN)));
1143             Rectangle(hdc, rc.left, rc.top, rc.right + 1, rc.bottom + 1);
1144             SelectObject(hdc, oldBrush);
1145             SelectObject(hdc, oldPen);
1146             rc.right -= td->font_wid;
1147             i++;
1148             rc.left += 2 * td->tile_wid;
1149             rc.right += 2 * td->tile_wid;
1150         } else if (iskanji(*(s + i))) /* 2バイト文字 */
1151         {
1152             char tmp[] = { *(s + i), *(s + i + 1), '\0' };
1153             to_wchar wc(tmp);
1154             const auto *buf = wc.wc_str();
1155             rc.right += td->font_wid;
1156             if (buf == NULL) {
1157                 ExtTextOutA(hdc, rc.left, rc.top, ETO_CLIPPED, &rc, s + i, 2, NULL);
1158             } else {
1159                 ExtTextOutW(hdc, rc.left, rc.top, ETO_CLIPPED, &rc, buf, wcslen(buf), NULL);
1160             }
1161             rc.right -= td->font_wid;
1162             i++;
1163             rc.left += 2 * td->tile_wid;
1164             rc.right += 2 * td->tile_wid;
1165         } else if (*(s + i) == 127) {
1166             oldBrush = static_cast<HBRUSH>(SelectObject(hdc, myBrush));
1167             oldPen = static_cast<HPEN>(SelectObject(hdc, GetStockObject(NULL_PEN)));
1168             Rectangle(hdc, rc.left, rc.top, rc.right + 1, rc.bottom + 1);
1169             SelectObject(hdc, oldBrush);
1170             SelectObject(hdc, oldPen);
1171             rc.left += td->tile_wid;
1172             rc.right += td->tile_wid;
1173         } else {
1174             ExtTextOutA(hdc, rc.left, rc.top, ETO_CLIPPED, &rc, s + i, 1, NULL);
1175             rc.left += td->tile_wid;
1176             rc.right += td->tile_wid;
1177         }
1178 #else
1179         if (*(s + i) == 127) {
1180             oldBrush = static_cast<HBRUSH>(SelectObject(hdc, myBrush));
1181             oldPen = static_cast<HPEN>(SelectObject(hdc, GetStockObject(NULL_PEN)));
1182             Rectangle(hdc, rc.left, rc.top, rc.right + 1, rc.bottom + 1);
1183             SelectObject(hdc, oldBrush);
1184             SelectObject(hdc, oldPen);
1185             rc.left += td->tile_wid;
1186             rc.right += td->tile_wid;
1187         } else {
1188             ExtTextOutA(hdc, rc.left, rc.top, ETO_CLIPPED, &rc, s + i, 1, NULL);
1189             rc.left += td->tile_wid;
1190             rc.right += td->tile_wid;
1191         }
1192 #endif
1193     }
1194
1195     rc.left = rc_start.left;
1196     rc.top = rc_start.top;
1197     td->refresh(&rc);
1198     return 0;
1199 }
1200
1201 /*!
1202  * @brief Low level graphics.  Assumes valid input.
1203  * @details
1204  * Draw an array of "special" attr/char pairs at the given location.
1205  *
1206  * We use the "term_pict_win()" function for "graphic" data, which are
1207  * encoded by setting the "high-bits" of both the "attr" and the "char"
1208  * data.  We use the "attr" to represent the "row" of the main bitmap,
1209  * and the "char" to represent the "col" of the main bitmap.  The use
1210  * of this function is induced by the "higher_pict" flag.
1211  *
1212  * If "graphics" is not available, we simply "wipe" the given grids.
1213  */
1214 static errr term_pict_win(TERM_LEN x, TERM_LEN y, int n, const TERM_COLOR *ap, concptr cp, const TERM_COLOR *tap, concptr tcp)
1215 {
1216     term_data *td = (term_data *)(game_term->data);
1217     int i;
1218     HDC hdcMask = NULL;
1219     if (!use_graphics) {
1220         return term_wipe_win(x, y, n);
1221     }
1222
1223     const tile_info &infGraph = graphic.get_tile_info();
1224     const bool has_mask = (infGraph.hBitmapMask != NULL);
1225     TERM_LEN w1 = infGraph.CellWidth;
1226     TERM_LEN h1 = infGraph.CellHeight;
1227     TERM_LEN tw1 = infGraph.TileWidth;
1228     TERM_LEN th1 = infGraph.TileHeight;
1229     TERM_LEN w2, h2, tw2 = 0;
1230     w2 = td->tile_wid;
1231     h2 = td->tile_hgt;
1232     tw2 = w2;
1233     if (use_bigtile) {
1234         tw2 *= 2;
1235     }
1236
1237     TERM_LEN x2 = x * w2 + td->size_ow1 + infGraph.OffsetX;
1238     TERM_LEN y2 = y * h2 + td->size_oh1 + infGraph.OffsetY;
1239     HDC hdc = td->get_hdc();
1240     HDC hdcSrc = CreateCompatibleDC(hdc);
1241     HBITMAP hbmSrcOld = static_cast<HBITMAP>(SelectObject(hdcSrc, infGraph.hBitmap));
1242
1243     if (has_mask) {
1244         hdcMask = CreateCompatibleDC(hdc);
1245         SelectObject(hdcMask, infGraph.hBitmapMask);
1246     }
1247
1248     for (i = 0; i < n; i++, x2 += w2) {
1249         TERM_COLOR a = ap[i];
1250         char c = cp[i];
1251         int row = (a & 0x7F);
1252         int col = (c & 0x7F);
1253         TERM_LEN x1 = col * w1;
1254         TERM_LEN y1 = row * h1;
1255
1256         if (has_mask) {
1257             TERM_LEN x3 = (tcp[i] & 0x7F) * w1;
1258             TERM_LEN y3 = (tap[i] & 0x7F) * h1;
1259             tw2 = tw2 * w1 / tw1;
1260             h2 = h2 * h1 / th1;
1261             if ((tw1 == tw2) && (th1 == h2)) {
1262                 BitBlt(hdc, x2, y2, tw2, h2, hdcSrc, x3, y3, SRCCOPY);
1263                 BitBlt(hdc, x2, y2, tw2, h2, hdcMask, x1, y1, SRCAND);
1264                 BitBlt(hdc, x2, y2, tw2, h2, hdcSrc, x1, y1, SRCPAINT);
1265                 continue;
1266             }
1267
1268             SetStretchBltMode(hdc, COLORONCOLOR);
1269             StretchBlt(hdc, x2, y2, tw2, h2, hdcMask, x3, y3, w1, h1, SRCAND);
1270             StretchBlt(hdc, x2, y2, tw2, h2, hdcSrc, x3, y3, w1, h1, SRCPAINT);
1271             if ((x1 != x3) || (y1 != y3)) {
1272                 StretchBlt(hdc, x2, y2, tw2, h2, hdcMask, x1, y1, w1, h1, SRCAND);
1273                 StretchBlt(hdc, x2, y2, tw2, h2, hdcSrc, x1, y1, w1, h1, SRCPAINT);
1274             }
1275
1276             continue;
1277         }
1278
1279         if ((w1 == tw2) && (h1 == h2)) {
1280             BitBlt(hdc, x2, y2, tw2, h2, hdcSrc, x1, y1, SRCCOPY);
1281             continue;
1282         }
1283
1284         SetStretchBltMode(hdc, COLORONCOLOR);
1285         StretchBlt(hdc, x2, y2, tw2, h2, hdcSrc, x1, y1, w1, h1, SRCCOPY);
1286     }
1287
1288     SelectObject(hdcSrc, hbmSrcOld);
1289     DeleteDC(hdcSrc);
1290     if (has_mask) {
1291         SelectObject(hdcMask, hbmSrcOld);
1292         DeleteDC(hdcMask);
1293     }
1294
1295     td->refresh();
1296     return 0;
1297 }
1298
1299 /*!
1300  * @brief Create and initialize a "term_data" given a title
1301  */
1302 static void term_data_link(term_data *td)
1303 {
1304     term_type *t = &td->t;
1305     term_init(t, td->cols, td->rows, FILE_READ_BUFF_SIZE);
1306     t->soft_cursor = true;
1307     t->higher_pict = true;
1308     t->attr_blank = TERM_WHITE;
1309     t->char_blank = ' ';
1310     t->user_hook = term_user_win;
1311     t->xtra_hook = term_xtra_win;
1312     t->curs_hook = term_curs_win;
1313     t->bigcurs_hook = term_bigcurs_win;
1314     t->wipe_hook = term_wipe_win;
1315     t->text_hook = term_text_win;
1316     t->pict_hook = term_pict_win;
1317     t->data = (vptr)(td);
1318 }
1319
1320 /*!
1321  * @brief Create the windows
1322  * @details
1323  * First, instantiate the "default" values, then read the "ini_file"
1324  * to over-ride selected values, then create the windows, and fonts.
1325  *
1326  * Must use SW_SHOW not SW_SHOWNA, since on 256 color display
1327  * must make active to realize the palette.
1328  */
1329 static void init_windows(void)
1330 {
1331     term_data *td;
1332     td = &data[0];
1333     *td = {};
1334     td->name = win_term_name[0];
1335
1336     td->rows = MAIN_TERM_MIN_ROWS;
1337     td->cols = MAIN_TERM_MIN_COLS;
1338     td->visible = true;
1339     td->size_ow1 = 2;
1340     td->size_ow2 = 2;
1341     td->size_oh1 = 2;
1342     td->size_oh2 = 2;
1343     td->pos_x = 7 * 30;
1344     td->pos_y = 7 * 20;
1345     td->posfix = false;
1346
1347     for (int i = 1; i < MAX_TERM_DATA; i++) {
1348         td = &data[i];
1349         *td = {};
1350         td->name = win_term_name[i];
1351         td->rows = TERM_DEFAULT_ROWS;
1352         td->cols = TERM_DEFAULT_COLS;
1353         td->visible = false;
1354         td->size_ow1 = 1;
1355         td->size_ow2 = 1;
1356         td->size_oh1 = 1;
1357         td->size_oh2 = 1;
1358         td->pos_x = (7 - i) * 30;
1359         td->pos_y = (7 - i) * 20;
1360         td->posfix = false;
1361     }
1362
1363     load_prefs();
1364
1365     /* Atrributes of main window */
1366     td = &data[0];
1367     td->dwStyle = (WS_OVERLAPPED | WS_THICKFRAME | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_CAPTION | WS_VISIBLE);
1368     td->dwExStyle = 0;
1369     td->visible = true;
1370
1371     /* Attributes of sub windows */
1372     for (int i = 1; i < MAX_TERM_DATA; i++) {
1373         td = &data[i];
1374         td->dwStyle = (WS_OVERLAPPED | WS_THICKFRAME | WS_SYSMENU);
1375         td->dwExStyle = (WS_EX_TOOLWINDOW);
1376     }
1377
1378     /* Font of each window */
1379     for (int i = 0; i < MAX_TERM_DATA; i++) {
1380         td = &data[i];
1381         wcsncpy(td->lf.lfFaceName, to_wchar(td->font_want).wc_str(), LF_FACESIZE);
1382         td->lf.lfCharSet = _(SHIFTJIS_CHARSET, DEFAULT_CHARSET);
1383         td->lf.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE;
1384         term_force_font(td);
1385         if (!td->tile_wid) {
1386             td->tile_wid = td->font_wid;
1387         }
1388         if (!td->tile_hgt) {
1389             td->tile_hgt = td->font_hgt;
1390         }
1391         term_getsize(td);
1392         term_window_resize(td);
1393     }
1394
1395     /* Create sub windows */
1396     for (int i = MAX_TERM_DATA - 1; i >= 1; --i) {
1397         td = &data[i];
1398
1399         my_td = td;
1400         td->w = CreateWindowExW(
1401             td->dwExStyle, AngList, td->name, td->dwStyle, td->pos_x, td->pos_y, td->size_wid, td->size_hgt, HWND_DESKTOP, NULL, hInstance, NULL);
1402         my_td = NULL;
1403
1404         if (td->w == NULL) {
1405             quit(_("サブウィンドウに作成に失敗しました", "Failed to create sub-window"));
1406             return; // 静的解析対応.
1407         }
1408
1409         td->size_hack = true;
1410         term_getsize(td);
1411         term_window_resize(td);
1412
1413         if (td->visible) {
1414             ShowWindow(td->w, SW_SHOW);
1415         }
1416         td->size_hack = false;
1417
1418         term_data_link(td);
1419         angband_terms[i] = &td->t;
1420
1421         if (td->visible) {
1422             /* Activate the window */
1423             SetActiveWindow(td->w);
1424         }
1425
1426         if (td->posfix) {
1427             term_window_pos(td, HWND_TOPMOST);
1428         } else {
1429             term_window_pos(td, td->w);
1430         }
1431     }
1432
1433     /* Create main window */
1434     td = &data[0];
1435     my_td = td;
1436     td->w = CreateWindowExW(
1437         td->dwExStyle, AppName, _(L"変愚蛮怒", td->name), td->dwStyle,
1438         td->pos_x, td->pos_y, td->size_wid, td->size_hgt, HWND_DESKTOP, NULL, hInstance, NULL);
1439     my_td = NULL;
1440
1441     if (td->w == NULL) {
1442         quit(_("メインウィンドウの作成に失敗しました", "Failed to create main window"));
1443         return; // 静的解析対応.
1444     }
1445
1446     /* Resize */
1447     td->size_hack = true;
1448     term_getsize(td);
1449     term_window_resize(td);
1450     td->size_hack = false;
1451
1452     term_data_link(td);
1453     angband_terms[0] = &td->t;
1454     normsize.x = td->cols;
1455     normsize.y = td->rows;
1456
1457     if (win_maximized) {
1458         ShowWindow(td->w, SW_SHOWMAXIMIZED);
1459     } else {
1460         ShowWindow(td->w, SW_SHOW);
1461     }
1462
1463     SetWindowPos(td->w, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
1464     hbrYellow = CreateSolidBrush(win_clr[TERM_YELLOW]);
1465     (void)term_xtra_win_flush();
1466 }
1467
1468 /*!
1469  * @brief Prepare the menus
1470  */
1471 static void setup_menus(void)
1472 {
1473     HMENU hm = GetMenu(data[0].w);
1474
1475     if (w_ptr->character_generated) {
1476         EnableMenuItem(hm, IDM_FILE_NEW, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
1477         EnableMenuItem(hm, IDM_FILE_OPEN, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
1478         EnableMenuItem(hm, IDM_FILE_SAVE, MF_BYCOMMAND | MF_ENABLED);
1479     } else {
1480         EnableMenuItem(hm, IDM_FILE_NEW, MF_BYCOMMAND | MF_ENABLED);
1481         EnableMenuItem(hm, IDM_FILE_OPEN, MF_BYCOMMAND | MF_ENABLED);
1482         EnableMenuItem(hm, IDM_FILE_SAVE, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
1483     }
1484
1485     for (int i = 0; i < MAX_TERM_DATA; i++) {
1486         EnableMenuItem(hm, IDM_WINDOW_VIS_0 + i, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
1487         CheckMenuItem(hm, IDM_WINDOW_VIS_0 + i, (data[i].visible ? MF_CHECKED : MF_UNCHECKED));
1488         EnableMenuItem(hm, IDM_WINDOW_VIS_0 + i, MF_BYCOMMAND | MF_ENABLED);
1489     }
1490
1491     for (int i = 0; i < MAX_TERM_DATA; i++) {
1492         EnableMenuItem(hm, IDM_WINDOW_FONT_0 + i, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
1493
1494         if (data[i].visible) {
1495             EnableMenuItem(hm, IDM_WINDOW_FONT_0 + i, MF_BYCOMMAND | MF_ENABLED);
1496         }
1497     }
1498
1499     for (int i = 0; i < MAX_TERM_DATA; i++) {
1500         EnableMenuItem(hm, IDM_WINDOW_POS_0 + i, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
1501         CheckMenuItem(hm, IDM_WINDOW_POS_0 + i, (data[i].posfix ? MF_CHECKED : MF_UNCHECKED));
1502         if (data[i].visible) {
1503             EnableMenuItem(hm, IDM_WINDOW_POS_0 + i, MF_BYCOMMAND | MF_ENABLED);
1504         }
1505     }
1506
1507     for (int i = 0; i < MAX_TERM_DATA; i++) {
1508         EnableMenuItem(hm, IDM_WINDOW_I_WID_0 + i, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
1509         if (data[i].visible) {
1510             EnableMenuItem(hm, IDM_WINDOW_I_WID_0 + i, MF_BYCOMMAND | MF_ENABLED);
1511         }
1512     }
1513
1514     for (int i = 0; i < MAX_TERM_DATA; i++) {
1515         EnableMenuItem(hm, IDM_WINDOW_D_WID_0 + i, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
1516         if (data[i].visible) {
1517             EnableMenuItem(hm, IDM_WINDOW_D_WID_0 + i, MF_BYCOMMAND | MF_ENABLED);
1518         }
1519     }
1520
1521     for (int i = 0; i < MAX_TERM_DATA; i++) {
1522         EnableMenuItem(hm, IDM_WINDOW_I_HGT_0 + i, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
1523         if (data[i].visible) {
1524             EnableMenuItem(hm, IDM_WINDOW_I_HGT_0 + i, MF_BYCOMMAND | MF_ENABLED);
1525         }
1526     }
1527
1528     for (int i = 0; i < MAX_TERM_DATA; i++) {
1529         EnableMenuItem(hm, IDM_WINDOW_D_HGT_0 + i, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
1530
1531         if (data[i].visible) {
1532             EnableMenuItem(hm, IDM_WINDOW_D_HGT_0 + i, MF_BYCOMMAND | MF_ENABLED);
1533         }
1534     }
1535     CheckMenuItem(hm, IDM_WINDOW_KEEP_SUBWINDOWS, (keep_subwindows ? MF_CHECKED : MF_UNCHECKED));
1536
1537     CheckMenuItem(hm, IDM_OPTIONS_NO_GRAPHICS, (arg_graphics == enum2i(graphics_mode::GRAPHICS_NONE) ? MF_CHECKED : MF_UNCHECKED));
1538     CheckMenuItem(hm, IDM_OPTIONS_OLD_GRAPHICS, (arg_graphics == enum2i(graphics_mode::GRAPHICS_ORIGINAL) ? MF_CHECKED : MF_UNCHECKED));
1539     CheckMenuItem(hm, IDM_OPTIONS_NEW_GRAPHICS, (arg_graphics == enum2i(graphics_mode::GRAPHICS_ADAM_BOLT) ? MF_CHECKED : MF_UNCHECKED));
1540     CheckMenuItem(hm, IDM_OPTIONS_NEW2_GRAPHICS, (arg_graphics == enum2i(graphics_mode::GRAPHICS_HENGBAND) ? MF_CHECKED : MF_UNCHECKED));
1541     CheckMenuItem(hm, IDM_OPTIONS_BIGTILE, (arg_bigtile ? MF_CHECKED : MF_UNCHECKED));
1542     CheckMenuItem(hm, IDM_OPTIONS_MUSIC, (arg_music ? MF_CHECKED : MF_UNCHECKED));
1543     CheckMenuRadioItem(hm, IDM_OPTIONS_MUSIC_VOLUME_100, IDM_OPTIONS_MUSIC_VOLUME_010,
1544         IDM_OPTIONS_MUSIC_VOLUME_100 + arg_music_volume_table_index, MF_BYCOMMAND);
1545     CheckMenuItem(hm, IDM_OPTIONS_MUSIC_PAUSE_INACTIVE, (use_pause_music_inactive ? MF_CHECKED : MF_UNCHECKED));
1546     CheckMenuItem(hm, IDM_OPTIONS_SOUND, (arg_sound ? MF_CHECKED : MF_UNCHECKED));
1547     CheckMenuRadioItem(hm, IDM_OPTIONS_SOUND_VOLUME_100, IDM_OPTIONS_SOUND_VOLUME_010,
1548         IDM_OPTIONS_SOUND_VOLUME_100 + arg_sound_volume_table_index, MF_BYCOMMAND);
1549     CheckMenuItem(hm, IDM_OPTIONS_NO_BG, ((current_bg_mode == bg_mode::BG_NONE) ? MF_CHECKED : MF_UNCHECKED));
1550     CheckMenuItem(hm, IDM_OPTIONS_BG, ((current_bg_mode == bg_mode::BG_ONE) ? MF_CHECKED : MF_UNCHECKED));
1551     CheckMenuItem(hm, IDM_OPTIONS_PRESET_BG, ((current_bg_mode == bg_mode::BG_PRESET) ? MF_CHECKED : MF_UNCHECKED));
1552     // TODO IDM_OPTIONS_PRESET_BG を有効にする
1553     EnableMenuItem(hm, IDM_OPTIONS_PRESET_BG, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
1554 }
1555
1556 /*!
1557  * @brief Check for double clicked (or dragged) savefile
1558  * @details
1559  * Apparently, Windows copies the entire filename into the first
1560  * piece of the "command line string".  Perhaps we should extract
1561  * the "basename" of that filename and append it to the "save" dir.
1562  * @param savefile_option savefile path
1563  */
1564 static void check_for_save_file(const std::string &savefile_option)
1565 {
1566     if (savefile_option.empty()) {
1567         return;
1568     }
1569
1570     savefile = savefile_option;
1571     validate_file(savefile);
1572     game_in_progress = true;
1573 }
1574
1575 /*!
1576  * @brief Process a menu command
1577  */
1578 static void process_menus(PlayerType *player_ptr, WORD wCmd)
1579 {
1580     if (!initialized) {
1581         plog(_("まだ初期化中です...", "You cannot do that yet..."));
1582         return;
1583     }
1584
1585     term_data *td;
1586     OPENFILENAMEW ofn{};
1587     switch (wCmd) {
1588     case IDM_FILE_NEW: {
1589         if (game_in_progress || movie_in_progress) {
1590             plog(_("プレイ中は新しいゲームを始めることができません!", "You can't start a new game while you're still playing!"));
1591         } else {
1592             game_in_progress = true;
1593             savefile = "";
1594         }
1595
1596         break;
1597     }
1598     case IDM_FILE_OPEN: {
1599         if (game_in_progress || movie_in_progress) {
1600             plog(_("プレイ中はゲームをロードすることができません!", "You can't open a new game while you're still playing!"));
1601         } else {
1602             ofn.lStructSize = sizeof(ofn);
1603             ofn.hwndOwner = data[0].w;
1604             ofn.lpstrFilter = L"Save Files (*.)\0*\0";
1605             ofn.nFilterIndex = 1;
1606             ofn.Flags = OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR | OFN_HIDEREADONLY;
1607             const auto &filename = get_open_filename(&ofn, ANGBAND_DIR_SAVE, savefile, MAIN_WIN_MAX_PATH);
1608             if (filename) {
1609                 savefile = *filename;
1610                 validate_file(savefile);
1611                 game_in_progress = true;
1612             }
1613         }
1614
1615         break;
1616     }
1617     case IDM_FILE_SAVE: {
1618         if (game_in_progress && w_ptr->character_generated) {
1619             if (!can_save) {
1620                 plog(_("今はセーブすることは出来ません。", "You may not do that right now."));
1621                 break;
1622             }
1623
1624             msg_flag = false;
1625             do_cmd_save_game(player_ptr, false);
1626         } else {
1627             plog(_("今、セーブすることは出来ません。", "You may not do that right now."));
1628         }
1629
1630         break;
1631     }
1632     case IDM_FILE_EXIT: {
1633         if (game_in_progress && w_ptr->character_generated) {
1634             if (!can_save) {
1635                 plog(_("今は終了できません。", "You may not do that right now."));
1636                 break;
1637             }
1638
1639             msg_flag = false;
1640             forget_lite(player_ptr->current_floor_ptr);
1641             forget_view(player_ptr->current_floor_ptr);
1642             clear_mon_lite(player_ptr->current_floor_ptr);
1643
1644             term_key_push(SPECIAL_KEY_QUIT);
1645             break;
1646         }
1647
1648         quit(nullptr);
1649         break;
1650     }
1651     case IDM_FILE_SCORE: {
1652         const auto &path = path_build(ANGBAND_DIR_APEX, "scores.raw");
1653         highscore_fd = fd_open(path, O_RDONLY);
1654         if (highscore_fd < 0) {
1655             msg_print("Score file unavailable.");
1656         } else {
1657             screen_save();
1658             term_clear();
1659             display_scores(0, MAX_HISCORES, -1, nullptr);
1660             (void)fd_close(highscore_fd);
1661             highscore_fd = -1;
1662             screen_load();
1663             term_fresh();
1664         }
1665
1666         break;
1667     }
1668     case IDM_FILE_MOVIE: {
1669         if (game_in_progress || movie_in_progress) {
1670             plog(_("プレイ中はムービーをロードすることができません!", "You can't open a movie while you're playing!"));
1671         } else {
1672             ofn.lStructSize = sizeof(ofn);
1673             ofn.hwndOwner = data[0].w;
1674             ofn.lpstrFilter = L"Angband Movie Files (*.amv)\0*.amv\0";
1675             ofn.nFilterIndex = 1;
1676             ofn.Flags = OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR;
1677             const auto &filename = get_open_filename(&ofn, ANGBAND_DIR_USER, savefile, MAIN_WIN_MAX_PATH);
1678             if (filename) {
1679                 savefile = *filename;
1680                 prepare_browse_movie_without_path_build(savefile);
1681                 movie_in_progress = true;
1682             }
1683         }
1684
1685         break;
1686     }
1687     case IDM_WINDOW_VIS_0: {
1688         plog(_("メインウィンドウは非表示にできません!", "You are not allowed to do that!"));
1689         break;
1690     }
1691     case IDM_WINDOW_VIS_1:
1692     case IDM_WINDOW_VIS_2:
1693     case IDM_WINDOW_VIS_3:
1694     case IDM_WINDOW_VIS_4:
1695     case IDM_WINDOW_VIS_5:
1696     case IDM_WINDOW_VIS_6:
1697     case IDM_WINDOW_VIS_7: {
1698         int i = wCmd - IDM_WINDOW_VIS_0;
1699         if ((i < 0) || (i >= MAX_TERM_DATA)) {
1700             break;
1701         }
1702
1703         td = &data[i];
1704         if (!td->visible) {
1705             td->visible = true;
1706             ShowWindow(td->w, SW_SHOW);
1707             term_data_redraw(td);
1708         } else {
1709             td->visible = false;
1710             td->posfix = false;
1711             ShowWindow(td->w, SW_HIDE);
1712         }
1713
1714         break;
1715     }
1716     case IDM_WINDOW_FONT_0:
1717     case IDM_WINDOW_FONT_1:
1718     case IDM_WINDOW_FONT_2:
1719     case IDM_WINDOW_FONT_3:
1720     case IDM_WINDOW_FONT_4:
1721     case IDM_WINDOW_FONT_5:
1722     case IDM_WINDOW_FONT_6:
1723     case IDM_WINDOW_FONT_7: {
1724         int i = wCmd - IDM_WINDOW_FONT_0;
1725         if ((i < 0) || (i >= MAX_TERM_DATA)) {
1726             break;
1727         }
1728
1729         td = &data[i];
1730         term_change_font(td);
1731         break;
1732     }
1733     case IDM_WINDOW_POS_1:
1734     case IDM_WINDOW_POS_2:
1735     case IDM_WINDOW_POS_3:
1736     case IDM_WINDOW_POS_4:
1737     case IDM_WINDOW_POS_5:
1738     case IDM_WINDOW_POS_6:
1739     case IDM_WINDOW_POS_7: {
1740         int i = wCmd - IDM_WINDOW_POS_0;
1741         if ((i < 0) || (i >= MAX_TERM_DATA)) {
1742             break;
1743         }
1744
1745         td = &data[i];
1746         if (!td->posfix && td->visible) {
1747             td->posfix = true;
1748             term_window_pos(td, HWND_TOPMOST);
1749         } else {
1750             td->posfix = false;
1751             term_window_pos(td, data[0].w);
1752         }
1753
1754         break;
1755     }
1756     case IDM_WINDOW_I_WID_0:
1757     case IDM_WINDOW_I_WID_1:
1758     case IDM_WINDOW_I_WID_2:
1759     case IDM_WINDOW_I_WID_3:
1760     case IDM_WINDOW_I_WID_4:
1761     case IDM_WINDOW_I_WID_5:
1762     case IDM_WINDOW_I_WID_6:
1763     case IDM_WINDOW_I_WID_7: {
1764         int i = wCmd - IDM_WINDOW_I_WID_0;
1765         if ((i < 0) || (i >= MAX_TERM_DATA)) {
1766             break;
1767         }
1768
1769         td = &data[i];
1770         td->tile_wid += 1;
1771         term_getsize(td);
1772         term_window_resize(td);
1773         break;
1774     }
1775     case IDM_WINDOW_D_WID_0:
1776     case IDM_WINDOW_D_WID_1:
1777     case IDM_WINDOW_D_WID_2:
1778     case IDM_WINDOW_D_WID_3:
1779     case IDM_WINDOW_D_WID_4:
1780     case IDM_WINDOW_D_WID_5:
1781     case IDM_WINDOW_D_WID_6:
1782     case IDM_WINDOW_D_WID_7: {
1783         int i = wCmd - IDM_WINDOW_D_WID_0;
1784         if ((i < 0) || (i >= MAX_TERM_DATA)) {
1785             break;
1786         }
1787
1788         td = &data[i];
1789         td->tile_wid -= 1;
1790         term_getsize(td);
1791         term_window_resize(td);
1792         break;
1793     }
1794     case IDM_WINDOW_I_HGT_0:
1795     case IDM_WINDOW_I_HGT_1:
1796     case IDM_WINDOW_I_HGT_2:
1797     case IDM_WINDOW_I_HGT_3:
1798     case IDM_WINDOW_I_HGT_4:
1799     case IDM_WINDOW_I_HGT_5:
1800     case IDM_WINDOW_I_HGT_6:
1801     case IDM_WINDOW_I_HGT_7: {
1802         int i = wCmd - IDM_WINDOW_I_HGT_0;
1803         if ((i < 0) || (i >= MAX_TERM_DATA)) {
1804             break;
1805         }
1806
1807         td = &data[i];
1808         td->tile_hgt += 1;
1809         term_getsize(td);
1810         term_window_resize(td);
1811         break;
1812     }
1813     case IDM_WINDOW_D_HGT_0:
1814     case IDM_WINDOW_D_HGT_1:
1815     case IDM_WINDOW_D_HGT_2:
1816     case IDM_WINDOW_D_HGT_3:
1817     case IDM_WINDOW_D_HGT_4:
1818     case IDM_WINDOW_D_HGT_5:
1819     case IDM_WINDOW_D_HGT_6:
1820     case IDM_WINDOW_D_HGT_7: {
1821         int i = wCmd - IDM_WINDOW_D_HGT_0;
1822         if ((i < 0) || (i >= MAX_TERM_DATA)) {
1823             break;
1824         }
1825
1826         td = &data[i];
1827         td->tile_hgt -= 1;
1828         term_getsize(td);
1829         term_window_resize(td);
1830         break;
1831     }
1832     case IDM_WINDOW_KEEP_SUBWINDOWS: {
1833         keep_subwindows = !keep_subwindows;
1834         break;
1835     }
1836     case IDM_OPTIONS_NO_GRAPHICS: {
1837         if (arg_graphics != enum2i(graphics_mode::GRAPHICS_NONE)) {
1838             arg_graphics = enum2i(graphics_mode::GRAPHICS_NONE);
1839             if (game_in_progress) {
1840                 do_cmd_redraw(player_ptr);
1841             }
1842         }
1843         break;
1844     }
1845     case IDM_OPTIONS_OLD_GRAPHICS: {
1846         if (arg_graphics != enum2i(graphics_mode::GRAPHICS_ORIGINAL)) {
1847             arg_graphics = enum2i(graphics_mode::GRAPHICS_ORIGINAL);
1848             if (game_in_progress) {
1849                 do_cmd_redraw(player_ptr);
1850             }
1851         }
1852
1853         break;
1854     }
1855     case IDM_OPTIONS_NEW_GRAPHICS: {
1856         if (arg_graphics != enum2i(graphics_mode::GRAPHICS_ADAM_BOLT)) {
1857             arg_graphics = enum2i(graphics_mode::GRAPHICS_ADAM_BOLT);
1858             if (game_in_progress) {
1859                 do_cmd_redraw(player_ptr);
1860             }
1861         }
1862
1863         break;
1864     }
1865     case IDM_OPTIONS_NEW2_GRAPHICS: {
1866         if (arg_graphics != enum2i(graphics_mode::GRAPHICS_HENGBAND)) {
1867             arg_graphics = enum2i(graphics_mode::GRAPHICS_HENGBAND);
1868             if (game_in_progress) {
1869                 do_cmd_redraw(player_ptr);
1870             }
1871         }
1872
1873         break;
1874     }
1875     case IDM_OPTIONS_BIGTILE: {
1876         td = &data[0];
1877         arg_bigtile = !arg_bigtile;
1878         rebuild_term(td);
1879         break;
1880     }
1881     case IDM_OPTIONS_MUSIC: {
1882         arg_music = !arg_music;
1883         use_music = arg_music;
1884         if (use_music) {
1885             init_music();
1886             if (game_in_progress) {
1887                 select_floor_music(player_ptr);
1888             }
1889         } else {
1890             main_win_music::stop_music();
1891         }
1892         break;
1893     }
1894     case IDM_OPTIONS_MUSIC_VOLUME_100:
1895     case IDM_OPTIONS_MUSIC_VOLUME_090:
1896     case IDM_OPTIONS_MUSIC_VOLUME_080:
1897     case IDM_OPTIONS_MUSIC_VOLUME_070:
1898     case IDM_OPTIONS_MUSIC_VOLUME_060:
1899     case IDM_OPTIONS_MUSIC_VOLUME_050:
1900     case IDM_OPTIONS_MUSIC_VOLUME_040:
1901     case IDM_OPTIONS_MUSIC_VOLUME_030:
1902     case IDM_OPTIONS_MUSIC_VOLUME_020:
1903     case IDM_OPTIONS_MUSIC_VOLUME_010: {
1904         arg_music_volume_table_index = wCmd - IDM_OPTIONS_MUSIC_VOLUME_100;
1905         if (use_music) {
1906             main_win_music::set_music_volume(main_win_music::VOLUME_TABLE[arg_music_volume_table_index]);
1907         }
1908         break;
1909     }
1910     case IDM_OPTIONS_MUSIC_PAUSE_INACTIVE: {
1911         use_pause_music_inactive = !use_pause_music_inactive;
1912         break;
1913     }
1914     case IDM_OPTIONS_OPEN_MUSIC_DIR: {
1915         const auto &path = path_build(ANGBAND_DIR_XTRA_MUSIC, "music.cfg");
1916         open_dir_in_explorer(path.string());
1917         break;
1918     }
1919     case IDM_OPTIONS_SOUND: {
1920         arg_sound = !arg_sound;
1921         change_sound_mode(arg_sound);
1922         break;
1923     }
1924     case IDM_OPTIONS_SOUND_VOLUME_100:
1925     case IDM_OPTIONS_SOUND_VOLUME_090:
1926     case IDM_OPTIONS_SOUND_VOLUME_080:
1927     case IDM_OPTIONS_SOUND_VOLUME_070:
1928     case IDM_OPTIONS_SOUND_VOLUME_060:
1929     case IDM_OPTIONS_SOUND_VOLUME_050:
1930     case IDM_OPTIONS_SOUND_VOLUME_040:
1931     case IDM_OPTIONS_SOUND_VOLUME_030:
1932     case IDM_OPTIONS_SOUND_VOLUME_020:
1933     case IDM_OPTIONS_SOUND_VOLUME_010: {
1934         arg_sound_volume_table_index = wCmd - IDM_OPTIONS_SOUND_VOLUME_100;
1935         break;
1936     }
1937     case IDM_OPTIONS_OPEN_SOUND_DIR: {
1938         const auto &path = path_build(ANGBAND_DIR_XTRA_SOUND, "sound.cfg");
1939         open_dir_in_explorer(path.string());
1940         break;
1941     }
1942     case IDM_OPTIONS_NO_BG: {
1943         change_bg_mode(bg_mode::BG_NONE);
1944         break;
1945     }
1946     case IDM_OPTIONS_PRESET_BG: {
1947         change_bg_mode(bg_mode::BG_PRESET);
1948         break;
1949     }
1950     case IDM_OPTIONS_BG: {
1951         if (change_bg_mode(bg_mode::BG_ONE)) {
1952             break;
1953         }
1954         // 壁紙の設定に失敗した(ファイルが存在しない等)場合、壁紙に使うファイルを選択させる
1955     }
1956         [[fallthrough]];
1957     case IDM_OPTIONS_OPEN_BG: {
1958         ofn.lStructSize = sizeof(ofn);
1959         ofn.hwndOwner = data[0].w;
1960         ofn.lpstrFilter = L"Image Files (*.bmp;*.png;*.jpg;*.jpeg;)\0*.bmp;*.png;*.jpg;*.jpeg;\0";
1961         ofn.nFilterIndex = 1;
1962         ofn.lpstrTitle = _(L"壁紙を選んでね。", L"Choose wall paper.");
1963         ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
1964         const auto &filename = get_open_filename(&ofn, "", wallpaper_path, MAIN_WIN_MAX_PATH);
1965         if (filename) {
1966             wallpaper_path = *filename;
1967             change_bg_mode(bg_mode::BG_ONE, true, true);
1968         }
1969         break;
1970     }
1971     case IDM_DUMP_SCREEN_HTML: {
1972         save_screen_as_html(data[0].w);
1973         break;
1974     }
1975     }
1976 }
1977
1978 /*!
1979  * @brief Add a keypress to the "queue"
1980  */
1981 static errr term_keypress(int k)
1982 {
1983     /* Refuse to enqueue non-keys */
1984     if (!k) {
1985         return -1;
1986     }
1987
1988     /* Store the char, advance the queue */
1989     game_term->key_queue[game_term->key_head++] = (char)k;
1990
1991     /* Circular queue, handle wrap */
1992     if (game_term->key_head == game_term->key_size) {
1993         game_term->key_head = 0;
1994     }
1995
1996     if (game_term->key_head != game_term->key_tail) {
1997         return 0;
1998     }
1999
2000     return 1;
2001 }
2002
2003 /*!
2004  * @brief Add a keypress to the "queue"
2005  * @details マルチバイト文字をkey_queueに追加する。
2006  * @param str マルチバイト文字列
2007  */
2008 static void term_keypress(char *str)
2009 {
2010     if (str) {
2011         char *psrc = str;
2012         while (*psrc) {
2013             term_keypress(*psrc);
2014             ++psrc;
2015         }
2016     }
2017 }
2018
2019 /*!
2020  * @brief キーダウンのハンドラ
2021  */
2022 static bool process_keydown(WPARAM wParam, LPARAM lParam)
2023 {
2024     auto mc = any_bits(static_cast<ushort>(GetKeyState(VK_CONTROL)), 0x8000);
2025     auto ms = any_bits(static_cast<ushort>(GetKeyState(VK_SHIFT)), 0x8000);
2026     auto ma = any_bits(static_cast<ushort>(GetKeyState(VK_MENU)), 0x8000);
2027     term_no_press = ma;
2028     if (special_key[(byte)(wParam)] || (ma && !ignore_key[(byte)(wParam)])) {
2029         bool ext_key = any_bits(static_cast<ulong>(lParam), 0x1000000UL);
2030         bool numpad = false;
2031
2032         term_keypress(31);
2033         if (mc) {
2034             term_keypress('C');
2035         }
2036         if (ms) {
2037             term_keypress('S');
2038         }
2039         if (ma) {
2040             term_keypress('A');
2041         }
2042
2043         int i = LOBYTE(HIWORD(lParam));
2044         term_keypress('x');
2045         switch (wParam) {
2046         case VK_DIVIDE:
2047             term_no_press = true;
2048             [[fallthrough]];
2049         case VK_RETURN:
2050             numpad = ext_key;
2051             break;
2052         case VK_NUMPAD0:
2053         case VK_NUMPAD1:
2054         case VK_NUMPAD2:
2055         case VK_NUMPAD3:
2056         case VK_NUMPAD4:
2057         case VK_NUMPAD5:
2058         case VK_NUMPAD6:
2059         case VK_NUMPAD7:
2060         case VK_NUMPAD8:
2061         case VK_NUMPAD9:
2062         case VK_ADD:
2063         case VK_MULTIPLY:
2064         case VK_SUBTRACT:
2065         case VK_SEPARATOR:
2066         case VK_DECIMAL:
2067             term_no_press = true;
2068             [[fallthrough]];
2069         case VK_CLEAR:
2070         case VK_HOME:
2071         case VK_END:
2072         case VK_PRIOR:
2073         case VK_NEXT:
2074         case VK_INSERT:
2075         case VK_DELETE:
2076         case VK_UP:
2077         case VK_DOWN:
2078         case VK_LEFT:
2079         case VK_RIGHT:
2080             numpad = !ext_key;
2081         }
2082
2083         if (numpad) {
2084             term_keypress('K');
2085         }
2086
2087         term_keypress(hexsym[i / 16]);
2088         term_keypress(hexsym[i % 16]);
2089         term_keypress(13);
2090
2091         return 1;
2092     }
2093
2094     return 0;
2095 }
2096
2097 /*!
2098  * @brief ウィンドウのアクティブ/非アクティブのハンドラ
2099  */
2100 static void handle_app_active(HWND hWnd, UINT uMsg, WPARAM wParam, [[maybe_unused]] LPARAM lParam)
2101 {
2102     switch (uMsg) {
2103     case WM_ACTIVATEAPP: {
2104         if (wParam) {
2105             if (use_pause_music_inactive) {
2106                 main_win_music::resume_music();
2107             }
2108         } else {
2109             if (use_pause_music_inactive) {
2110                 main_win_music::pause_music();
2111             }
2112         }
2113         break;
2114     }
2115     case WM_WINDOWPOSCHANGING: {
2116         if (!IsIconic(hWnd)) {
2117             if (use_pause_music_inactive) {
2118                 main_win_music::resume_music();
2119             }
2120         }
2121         break;
2122     }
2123     }
2124 }
2125
2126 /*!
2127  * @brief ターミナルのサイズをウインドウのサイズに合わせる
2128  * @param td term_dataのポインタ
2129  * @param recalc_window_size trueの場合に行列数からウインドウサイズを再計算し設定する
2130  */
2131 static void fit_term_size_to_window(term_data *td, bool recalc_window_size = false)
2132 {
2133     RECT rc;
2134     ::GetClientRect(td->w, &rc);
2135     int width = rc.right - rc.left;
2136     int height = rc.bottom - rc.top;
2137
2138     TERM_LEN cols = (width - td->size_ow1 - td->size_ow2) / td->tile_wid;
2139     TERM_LEN rows = (height - td->size_oh1 - td->size_oh2) / td->tile_hgt;
2140     if ((td->cols != cols) || (td->rows != rows)) {
2141         td->cols = cols;
2142         td->rows = rows;
2143         if (is_main_term(td) && !IsZoomed(td->w) && !IsIconic(td->w)) {
2144             normsize.x = td->cols;
2145             normsize.y = td->rows;
2146         }
2147
2148         rebuild_term(td, recalc_window_size);
2149
2150         if (!is_main_term(td)) {
2151             RedrawingFlagsUpdater::get_instance().fill_up_sub_flags();
2152             handle_stuff(p_ptr);
2153         }
2154     }
2155 }
2156
2157 /*!
2158  * @brief Windowのリサイズをハンドリング
2159  * @retval true ウインドウメッセージを処理した
2160  * @retval false ウインドウメッセージを処理していない
2161  */
2162 static bool handle_window_resize(term_data *td, UINT uMsg, WPARAM wParam, LPARAM lParam)
2163 {
2164     if (!td) {
2165         return false;
2166     }
2167     if (!td->w) {
2168         return false;
2169     }
2170
2171     switch (uMsg) {
2172     case WM_GETMINMAXINFO: {
2173         const bool is_main = is_main_term(td);
2174         const int min_cols = (is_main) ? MAIN_TERM_MIN_COLS : 20;
2175         const int min_rows = (is_main) ? MAIN_TERM_MIN_ROWS : 3;
2176         const LONG w = min_cols * td->tile_wid + td->size_ow1 + td->size_ow2;
2177         const LONG h = min_rows * td->tile_hgt + td->size_oh1 + td->size_oh2 + 1;
2178         RECT rc{ 0, 0, w, h };
2179         AdjustWindowRectEx(&rc, td->dwStyle, TRUE, td->dwExStyle);
2180
2181         MINMAXINFO *lpmmi = (MINMAXINFO *)lParam;
2182         lpmmi->ptMinTrackSize.x = rc.right - rc.left;
2183         lpmmi->ptMinTrackSize.y = rc.bottom - rc.top;
2184
2185         return true;
2186     }
2187     case WM_EXITSIZEMOVE: {
2188         fit_term_size_to_window(td, true);
2189         return true;
2190     }
2191     case WM_WINDOWPOSCHANGED: {
2192         if (!td->size_hack) {
2193             WINDOWPOS *pos = (WINDOWPOS *)lParam;
2194             if ((pos->flags & (SWP_NOCOPYBITS | SWP_NOSIZE)) == 0) {
2195                 fit_term_size_to_window(td);
2196                 return true;
2197             }
2198         }
2199         break;
2200     }
2201     case WM_SIZE: {
2202         if (td->size_hack) {
2203             break;
2204         }
2205
2206         //!< @todo 二重のswitch文。後で分割する.
2207         switch (wParam) {
2208         case SIZE_MINIMIZED: {
2209             for (int i = 1; i < MAX_TERM_DATA; i++) {
2210                 if (data[i].visible) {
2211                     ShowWindow(data[i].w, SW_HIDE);
2212                 }
2213             }
2214
2215             return true;
2216         }
2217         case SIZE_MAXIMIZED:
2218         case SIZE_RESTORED: {
2219             fit_term_size_to_window(td);
2220
2221             td->size_hack = true;
2222             for (int i = 1; i < MAX_TERM_DATA; i++) {
2223                 if (data[i].visible) {
2224                     ShowWindow(data[i].w, SW_SHOWNA);
2225                 }
2226             }
2227
2228             td->size_hack = false;
2229
2230             return true;
2231         }
2232         }
2233
2234         break;
2235     }
2236     }
2237
2238     return false;
2239 }
2240
2241 /*!
2242  * @brief メインウインドウ用ウインドウプロシージャ
2243  */
2244 LRESULT PASCAL angband_window_procedure(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
2245 {
2246     term_data *td = (term_data *)GetWindowLong(hWnd, 0);
2247
2248     handle_app_active(hWnd, uMsg, wParam, lParam);
2249     if (handle_window_resize(td, uMsg, wParam, lParam)) {
2250         return 0;
2251     }
2252
2253     switch (uMsg) {
2254     case WM_NCCREATE: {
2255         SetWindowLong(hWnd, 0, (LONG)(my_td));
2256         break;
2257     }
2258     case WM_CREATE: {
2259         setup_mci(hWnd);
2260         return 0;
2261     }
2262     case WM_ERASEBKGND: {
2263         return 1;
2264     }
2265     case WM_PAINT: {
2266         PAINTSTRUCT ps;
2267         HDC hdc = BeginPaint(hWnd, &ps);
2268         if (td) {
2269             if (!td->render(ps.rcPaint)) {
2270                 SetBkColor(hdc, RGB(0, 0, 0));
2271                 ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &ps.rcPaint, NULL, 0, NULL);
2272             }
2273         }
2274         EndPaint(hWnd, &ps);
2275         ValidateRect(hWnd, NULL);
2276         return 0;
2277     }
2278     case MM_MCINOTIFY: {
2279         main_win_music::on_mci_notify(wParam, lParam, main_win_music::VOLUME_TABLE[arg_music_volume_table_index]);
2280
2281         return 0;
2282     }
2283     case WM_SYSKEYDOWN:
2284     case WM_KEYDOWN: {
2285         if (process_keydown(wParam, lParam)) {
2286             return 0;
2287         }
2288         break;
2289     }
2290     case WM_CHAR: {
2291         // wParam is WCHAR because using RegisterClassW
2292         if (term_no_press) {
2293             term_no_press = false;
2294         } else {
2295             WCHAR wc[2] = { (WCHAR)wParam, '\0' };
2296             term_keypress(to_multibyte(wc).c_str());
2297         }
2298         return 0;
2299     }
2300     case WM_LBUTTONDOWN: {
2301         if (macro_running()) {
2302             return 0;
2303         }
2304         mousex = std::min(LOWORD(lParam) / td->tile_wid, td->cols - 1);
2305         mousey = std::min(HIWORD(lParam) / td->tile_hgt, td->rows - 1);
2306         mouse_down = true;
2307         oldx = mousex;
2308         oldy = mousey;
2309         return 0;
2310     }
2311     case WM_LBUTTONUP: {
2312         if (!mouse_down) {
2313             return 0;
2314         }
2315         TERM_LEN dx = abs(oldx - mousex) + 1;
2316         TERM_LEN dy = abs(oldy - mousey) + 1;
2317         TERM_LEN ox = (oldx > mousex) ? mousex : oldx;
2318         TERM_LEN oy = (oldy > mousey) ? mousey : oldy;
2319
2320         mouse_down = false;
2321         paint_rect = false;
2322
2323 #ifdef JP
2324         int sz = (dx + 3) * dy;
2325 #else
2326         int sz = (dx + 2) * dy;
2327 #endif
2328         const auto window_size = GlobalAlloc(GHND, sz + 1);
2329         if (window_size == NULL) {
2330             return 0;
2331         }
2332
2333         auto global_lock = static_cast<LPSTR>(GlobalLock(window_size));
2334         for (auto i = 0; (i < dy) && (global_lock != NULL); i++) {
2335 #ifdef JP
2336             const auto &scr = data[0].t.scr->c;
2337
2338             std::vector<char> s(dx + 1);
2339             strncpy(s.data(), &scr[oy + i][ox], dx);
2340
2341             if (ox > 0) {
2342                 if (iskanji(scr[oy + i][ox - 1])) {
2343                     s[0] = ' ';
2344                 }
2345             }
2346
2347             if (ox + dx < data[0].cols) {
2348                 if (iskanji(scr[oy + i][ox + dx - 1])) {
2349                     s[dx - 1] = ' ';
2350                 }
2351             }
2352
2353             for (int j = 0; j < dx; j++) {
2354                 if (s[j] == 127) {
2355                     s[j] = '#';
2356                 }
2357                 *global_lock++ = s[j];
2358             }
2359 #else
2360             for (int j = 0; j < dx; j++) {
2361                 *global_lock++ = data[0].t.scr->c[oy + i][ox + j];
2362             }
2363 #endif
2364             if (dy > 1) {
2365                 *global_lock++ = '\r';
2366                 *global_lock++ = '\n';
2367             }
2368         }
2369
2370         GlobalUnlock(window_size);
2371         if (!OpenClipboard(hWnd)) {
2372             GlobalFree(window_size);
2373             return 0;
2374         }
2375
2376         EmptyClipboard();
2377         if (SetClipboardData(CF_TEXT, window_size) == NULL) {
2378             CloseClipboard();
2379             GlobalFree(window_size);
2380             return 0;
2381         }
2382
2383         CloseClipboard();
2384         term_redraw();
2385         return 0;
2386     }
2387     case WM_MOUSEMOVE: {
2388         if (!mouse_down) {
2389             return 0;
2390         }
2391
2392         int dx, dy;
2393         int cx = std::min(LOWORD(lParam) / td->tile_wid, td->cols - 1);
2394         int cy = std::min(HIWORD(lParam) / td->tile_hgt, td->rows - 1);
2395         int ox, oy;
2396
2397         if (paint_rect) {
2398             dx = abs(oldx - mousex) + 1;
2399             dy = abs(oldy - mousey) + 1;
2400             ox = (oldx > mousex) ? mousex : oldx;
2401             oy = (oldy > mousey) ? mousey : oldy;
2402             term_inversed_area(hWnd, ox, oy, dx, dy);
2403         } else {
2404             paint_rect = true;
2405         }
2406
2407         dx = abs(cx - mousex) + 1;
2408         dy = abs(cy - mousey) + 1;
2409         ox = (cx > mousex) ? mousex : cx;
2410         oy = (cy > mousey) ? mousey : cy;
2411         term_inversed_area(hWnd, ox, oy, dx, dy);
2412
2413         oldx = cx;
2414         oldy = cy;
2415         return 0;
2416     }
2417     case WM_INITMENU: {
2418         setup_menus();
2419         return 0;
2420     }
2421     case WM_CLOSE: {
2422         if (!game_in_progress || !w_ptr->character_generated) {
2423             quit(nullptr);
2424             return 0;
2425         }
2426
2427         if (!can_save) {
2428             plog(_("今は終了できません。", "You may not do that right now."));
2429             return 0;
2430         }
2431
2432         msg_flag = false;
2433         forget_lite(p_ptr->current_floor_ptr);
2434         forget_view(p_ptr->current_floor_ptr);
2435         clear_mon_lite(p_ptr->current_floor_ptr);
2436         term_key_push(SPECIAL_KEY_QUIT);
2437         return 0;
2438     }
2439     case WM_QUERYENDSESSION: {
2440         if (!game_in_progress || !w_ptr->character_generated) {
2441             quit(nullptr);
2442             return 0;
2443         }
2444
2445         msg_flag = false;
2446         if (p_ptr->chp < 0) {
2447             p_ptr->is_dead = false;
2448         }
2449         exe_write_diary(p_ptr, DiaryKind::GAMESTART, 0, _("----ゲーム中断----", "---- Save and Exit Game ----"));
2450
2451         p_ptr->panic_save = 1;
2452         signals_ignore_tstp();
2453         p_ptr->died_from = _("(緊急セーブ)", "(panic save)");
2454         (void)save_player(p_ptr, SaveType::CLOSE_GAME);
2455         quit(nullptr);
2456         return 0;
2457     }
2458     case WM_QUIT: {
2459         quit(nullptr);
2460         return 0;
2461     }
2462     case WM_COMMAND: {
2463         process_menus(p_ptr, LOWORD(wParam));
2464         return 0;
2465     }
2466     case WM_ACTIVATE: {
2467         if (!wParam || HIWORD(lParam)) {
2468             break;
2469         }
2470
2471         for (int i = 1; i < MAX_TERM_DATA; i++) {
2472             if (!data[i].posfix) {
2473                 term_window_pos(&data[i], hWnd);
2474             }
2475         }
2476
2477         SetFocus(hWnd);
2478         return 0;
2479     }
2480     case WM_ACTIVATEAPP: {
2481         if (IsIconic(td->w)) {
2482             break;
2483         }
2484
2485         for (int i = 1; i < MAX_TERM_DATA; i++) {
2486             if (data[i].visible) {
2487                 if (wParam == 1) {
2488                     ShowWindow(data[i].w, SW_SHOWNA);
2489                 } else {
2490                     ShowWindow(data[i].w, SW_HIDE);
2491                 }
2492             }
2493         }
2494     }
2495         [[fallthrough]];
2496     case WM_ENABLE: {
2497         if (wParam == FALSE && keep_subwindows) {
2498             for (int i = 1; i < MAX_TERM_DATA; i++) {
2499                 if (data[i].visible) {
2500                     ShowWindow(data[i].w, SW_SHOWNA);
2501                 }
2502             }
2503         }
2504     }
2505     }
2506
2507     return DefWindowProc(hWnd, uMsg, wParam, lParam);
2508 }
2509
2510 /*!
2511  * @brief サブウインドウ用ウインドウプロシージャ
2512  */
2513 LRESULT PASCAL AngbandListProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
2514 {
2515     term_data *td = (term_data *)GetWindowLong(hWnd, 0);
2516     if (handle_window_resize(td, uMsg, wParam, lParam)) {
2517         return 0;
2518     }
2519
2520     switch (uMsg) {
2521     case WM_NCCREATE: {
2522         SetWindowLong(hWnd, 0, (LONG)(my_td));
2523         break;
2524     }
2525     case WM_CREATE: {
2526         return 0;
2527     }
2528     case WM_ERASEBKGND: {
2529         return 1;
2530     }
2531     case WM_PAINT: {
2532         PAINTSTRUCT ps;
2533         HDC hdc = BeginPaint(hWnd, &ps);
2534         if (td) {
2535             if (!td->render(ps.rcPaint)) {
2536                 SetBkColor(hdc, RGB(0, 0, 0));
2537                 ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &ps.rcPaint, NULL, 0, NULL);
2538             }
2539         }
2540         EndPaint(hWnd, &ps);
2541         return 0;
2542     }
2543     case WM_SYSKEYDOWN:
2544     case WM_KEYDOWN: {
2545         if (process_keydown(wParam, lParam)) {
2546             return 0;
2547         }
2548
2549         break;
2550     }
2551     case WM_CHAR: {
2552         // wParam is WCHAR because using RegisterClassW
2553         if (term_no_press) {
2554             term_no_press = false;
2555         } else {
2556             WCHAR wc[2] = { (WCHAR)wParam, '\0' };
2557             term_keypress(to_multibyte(wc).c_str());
2558         }
2559         return 0;
2560     }
2561     case WM_NCLBUTTONDOWN: {
2562         if (wParam == HTCLOSE) {
2563             wParam = HTSYSMENU;
2564         }
2565
2566         if (wParam == HTSYSMENU) {
2567             if (td->visible) {
2568                 td->visible = false;
2569                 ShowWindow(td->w, SW_HIDE);
2570             }
2571
2572             return 0;
2573         }
2574
2575         break;
2576     }
2577     }
2578
2579     return DefWindowProc(hWnd, uMsg, wParam, lParam);
2580 }
2581
2582 /*!
2583  * @brief Display warning message (see "z-util.c")
2584  */
2585 static void hook_plog(concptr str)
2586 {
2587     if (str) {
2588         MessageBoxW(data[0].w, to_wchar(str).wc_str(), _(L"警告!", L"Warning"), MB_ICONEXCLAMATION | MB_OK);
2589     }
2590 }
2591
2592 /*!
2593  * @brief Display error message and quit (see "z-util.c")
2594  */
2595 static void hook_quit(concptr str)
2596 {
2597     if (str) {
2598         MessageBoxW(data[0].w, to_wchar(str).wc_str(), _(L"エラー!", L"Error"), MB_ICONEXCLAMATION | MB_OK | MB_ICONSTOP);
2599     }
2600
2601     save_prefs();
2602     for (int i = MAX_TERM_DATA - 1; i >= 0; --i) {
2603         term_force_font(&data[i]);
2604         if (data[i].font_want) {
2605             string_free(data[i].font_want);
2606         }
2607         if (data[i].w) {
2608             DestroyWindow(data[i].w);
2609         }
2610         data[i].w = 0;
2611     }
2612
2613     DeleteObject(hbrYellow);
2614     finalize_bg();
2615     graphic.finalize();
2616     finalize_sound();
2617
2618     UnregisterClassW(AppName, hInstance);
2619     if (hIcon) {
2620         DestroyIcon(hIcon);
2621     }
2622
2623     exit(0);
2624 }
2625
2626 /*!
2627  * @brief Init some stuff
2628  */
2629 static void init_stuff()
2630 {
2631     char path[MAIN_WIN_MAX_PATH];
2632     DWORD path_len = GetModuleFileNameA(hInstance, path, MAIN_WIN_MAX_PATH);
2633     strcpy(path + path_len - 4, ".INI");
2634     ini_file = string_make(path);
2635
2636     int i = path_len;
2637     for (; i > 0; i--) {
2638         if (path[i] == '\\') {
2639             break;
2640         }
2641     }
2642
2643     strcpy(path + i + 1, "lib\\");
2644     validate_dir(path, true);
2645     init_file_paths(path);
2646     validate_dir(ANGBAND_DIR_APEX, false);
2647     validate_dir(ANGBAND_DIR_BONE, false);
2648     if (!check_dir(ANGBAND_DIR_EDIT)) {
2649         validate_dir(ANGBAND_DIR_DATA, true);
2650     } else {
2651         validate_dir(ANGBAND_DIR_DATA, false);
2652     }
2653
2654     validate_dir(ANGBAND_DIR_FILE, true);
2655     validate_dir(ANGBAND_DIR_HELP, false);
2656     validate_dir(ANGBAND_DIR_INFO, false);
2657     validate_dir(ANGBAND_DIR_PREF, true);
2658     validate_dir(ANGBAND_DIR_SAVE, false);
2659     validate_dir(ANGBAND_DIR_DEBUG_SAVE, false);
2660     validate_dir(ANGBAND_DIR_USER, true);
2661     validate_dir(ANGBAND_DIR_XTRA, true);
2662     const auto &path_news = path_build(ANGBAND_DIR_FILE, _("news_j.txt", "news.txt"));
2663     validate_file(path_news);
2664
2665     ANGBAND_DIR_XTRA_GRAF = path_build(ANGBAND_DIR_XTRA, "graf");
2666     validate_dir(ANGBAND_DIR_XTRA_GRAF, true);
2667
2668     ANGBAND_DIR_XTRA_SOUND = path_build(ANGBAND_DIR_XTRA, "sound");
2669     validate_dir(ANGBAND_DIR_XTRA_SOUND, false);
2670
2671     ANGBAND_DIR_XTRA_MUSIC = path_build(ANGBAND_DIR_XTRA, "music");
2672     validate_dir(ANGBAND_DIR_XTRA_MUSIC, false);
2673
2674     for (i = 0; special_key_list[i]; ++i) {
2675         special_key[special_key_list[i]] = true;
2676     }
2677
2678     for (i = 0; ignore_key_list[i]; ++i) {
2679         ignore_key[ignore_key_list[i]] = true;
2680     }
2681
2682     ANGBAND_SYS = "win";
2683     if (7 != GetKeyboardType(0)) {
2684         ANGBAND_KEYBOARD = "0";
2685     } else {
2686         switch (GetKeyboardType(1)) {
2687         case 0x0D01:
2688         case 0x0D02:
2689         case 0x0D03:
2690         case 0x0D04:
2691         case 0x0D05:
2692         case 0x0D06:
2693             /* NEC PC-98x1 */
2694             ANGBAND_KEYBOARD = "NEC98";
2695             break;
2696         default:
2697             /* PC/AT */
2698             ANGBAND_KEYBOARD = "JAPAN";
2699         }
2700     }
2701 }
2702
2703 /*!
2704  * @brief 全スポイラー出力を行う
2705  * Create Spoiler files
2706  * @details スポイラー出力処理の成功、失敗に関わらずプロセスを終了する。
2707  */
2708 void create_debug_spoiler(void)
2709 {
2710     init_stuff();
2711     init_angband(p_ptr, true);
2712
2713     switch (output_all_spoilers()) {
2714     case SpoilerOutputResultType::SUCCESSFUL:
2715         fprintf(stdout, "Successfully created a spoiler file.");
2716         break;
2717     case SpoilerOutputResultType::FILE_OPEN_FAILED:
2718         fprintf(stderr, "Cannot create spoiler file.");
2719         break;
2720     case SpoilerOutputResultType::FILE_CLOSE_FAILED:
2721         fprintf(stderr, "Cannot close spoiler file.");
2722         break;
2723     default:
2724         break;
2725     }
2726
2727     quit(nullptr);
2728 }
2729
2730 /*!
2731  * @brief メインウインドウ、サブウインドウのウインドウクラス登録
2732  */
2733 static void register_wndclass(void)
2734 {
2735     WNDCLASSW wc{};
2736     wc.style = CS_CLASSDC;
2737     wc.lpfnWndProc = angband_window_procedure;
2738     wc.cbClsExtra = 0;
2739     wc.cbWndExtra = 4;
2740     wc.hInstance = hInstance;
2741     wc.hIcon = hIcon = LoadIconW(hInstance, AppName);
2742     wc.hCursor = LoadCursor(NULL, IDC_ARROW);
2743     wc.hbrBackground = NULL;
2744     wc.lpszMenuName = AppName;
2745     wc.lpszClassName = AppName;
2746
2747     if (!RegisterClassW(&wc)) {
2748         exit(1);
2749     }
2750
2751     wc.lpfnWndProc = AngbandListProc;
2752     wc.lpszMenuName = NULL;
2753     wc.lpszClassName = AngList;
2754
2755     if (!RegisterClassW(&wc)) {
2756         exit(2);
2757     }
2758 }
2759
2760 /*!
2761  * @brief ゲームのメインルーチン
2762  */
2763 int WINAPI game_main(_In_ HINSTANCE hInst)
2764 {
2765     setlocale(LC_ALL, "ja_JP");
2766     hInstance = hInst;
2767     if (is_already_running()) {
2768         constexpr auto mes = _(L"変愚蛮怒はすでに起動しています。", L"Hengband is already running.");
2769         constexpr auto caption = _(L"エラー!", L"Error");
2770         MessageBoxW(NULL, mes, caption, MB_ICONEXCLAMATION | MB_OK | MB_ICONSTOP);
2771         return 0;
2772     }
2773
2774     command_line.handle();
2775     register_wndclass();
2776
2777     // before term_data initialize
2778     plog_aux = [](concptr str) {
2779         if (str) {
2780             MessageBoxW(NULL, to_wchar(str).wc_str(), _(L"警告!", L"Warning"), MB_ICONEXCLAMATION | MB_OK);
2781         }
2782     };
2783     quit_aux = [](concptr str) {
2784         if (str) {
2785             MessageBoxW(NULL, to_wchar(str).wc_str(), _(L"エラー!", L"Error"), MB_ICONEXCLAMATION | MB_OK | MB_ICONSTOP);
2786         }
2787
2788         UnregisterClassW(AppName, hInstance);
2789         if (hIcon) {
2790             DestroyIcon(hIcon);
2791         }
2792
2793         exit(0);
2794     };
2795     core_aux = quit_aux;
2796
2797     init_stuff();
2798     refresh_color_table();
2799     init_windows();
2800     change_graphics_mode(static_cast<graphics_mode>(arg_graphics));
2801     change_bg_mode(current_bg_mode, true);
2802
2803     // after term_data initialize
2804     plog_aux = hook_plog;
2805     quit_aux = hook_quit;
2806     core_aux = hook_quit;
2807
2808     signals_init();
2809     term_activate(term_screen);
2810     {
2811         TermCenteredOffsetSetter tcos(MAIN_TERM_MIN_COLS, MAIN_TERM_MIN_ROWS);
2812
2813         init_angband(p_ptr, false);
2814         initialized = true;
2815
2816         check_for_save_file(command_line.get_savefile_option());
2817         prt(_("[ファイル] メニューの [新規] または [開く] を選択してください。", "[Choose 'New' or 'Open' from the 'File' menu]"), 23, _(8, 17));
2818         term_fresh();
2819     }
2820
2821     change_sound_mode(arg_sound);
2822     use_music = arg_music;
2823     if (use_music) {
2824         init_music();
2825     }
2826
2827     // ユーザーがゲーム開始を選択するまで待つループ
2828     MSG msg;
2829     while (GetMessage(&msg, NULL, 0, 0)) {
2830         TranslateMessage(&msg);
2831         DispatchMessage(&msg);
2832         if (game_in_progress || movie_in_progress) {
2833             break;
2834         }
2835     }
2836
2837     term_flush();
2838     if (movie_in_progress) {
2839         // selected movie
2840         play_game(p_ptr, false, true);
2841     } else if (savefile.empty()) {
2842         // new game
2843         play_game(p_ptr, true, false);
2844     } else {
2845         // selected savefile
2846         play_game(p_ptr, false, false);
2847     }
2848
2849     quit(nullptr);
2850     return 0;
2851 }
2852
2853 /*!
2854  * @brief (Windows固有)Windowsアプリケーションとしてのエントリポイント
2855  */
2856 int WINAPI WinMain(
2857     _In_ HINSTANCE hInst, _In_opt_ HINSTANCE, _In_ LPSTR, _In_ int)
2858 {
2859     try {
2860         return game_main(hInst);
2861     } catch (const std::exception &e) {
2862         handle_unexpected_exception(e);
2863         return 1;
2864     }
2865 }
2866
2867 #endif /* WINDOWS */