1 /*
2  * This file is part of EasyRPG Player.
3  *
4  * EasyRPG Player is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * EasyRPG Player is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with EasyRPG Player. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 // Headers
19 
20 #include <algorithm>
21 #include <cstring>
22 #include <cstdlib>
23 #include <iostream>
24 #include <iomanip>
25 #include <fstream>
26 #include <memory>
27 #include <thread>
28 
29 #ifdef _WIN32
30 #  include "platform/windows/utils.h"
31 #  include <windows.h>
32 #  include <shellapi.h>
33 #elif defined(GEKKO)
34 #  include <fat.h>
35 #elif defined(EMSCRIPTEN)
36 #  include <emscripten.h>
37 #elif defined(PSP2)
38 #  include <psp2/kernel/processmgr.h>
39 #elif defined(_3DS)
40 #  include <3ds.h>
41 #elif defined(__SWITCH__)
42 #  include <switch.h>
43 #endif
44 
45 #include "async_handler.h"
46 #include "audio.h"
47 #include "cache.h"
48 #include "rand.h"
49 #include "cmdline_parser.h"
50 #include "dynrpg.h"
51 #include "filefinder.h"
52 #include "filefinder_rtp.h"
53 #include "fileext_guesser.h"
54 #include "game_actors.h"
55 #include "game_battle.h"
56 #include "game_map.h"
57 #include "game_message.h"
58 #include "game_enemyparty.h"
59 #include "game_ineluki.h"
60 #include "game_party.h"
61 #include "game_player.h"
62 #include "game_switches.h"
63 #include "game_screen.h"
64 #include "game_pictures.h"
65 #include "game_system.h"
66 #include "game_variables.h"
67 #include "game_targets.h"
68 #include "graphics.h"
69 #include <lcf/inireader.h>
70 #include "input.h"
71 #include <lcf/ldb/reader.h>
72 #include <lcf/lmt/reader.h>
73 #include <lcf/lsd/reader.h>
74 #include "main_data.h"
75 #include "output.h"
76 #include "player.h"
77 #include <lcf/reader_lcf.h>
78 #include <lcf/reader_util.h>
79 #include "scene_battle.h"
80 #include "scene_logo.h"
81 #include "scene_map.h"
82 #include "utils.h"
83 #include "version.h"
84 #include "game_quit.h"
85 #include "scene_title.h"
86 #include "instrumentation.h"
87 #include "transition.h"
88 #include <lcf/scope_guard.h>
89 #include "baseui.h"
90 #include "game_clock.h"
91 
92 #ifndef EMSCRIPTEN
93 // This is not used on Emscripten.
94 #include "exe_reader.h"
95 #endif
96 
97 namespace Player {
98 	bool exit_flag;
99 	bool reset_flag;
100 	bool debug_flag;
101 	bool hide_title_flag;
102 	bool new_game_flag;
103 	int load_game_id;
104 	int party_x_position;
105 	int party_y_position;
106 	std::vector<int> party_members;
107 	int start_map_id;
108 	bool no_rtp_flag;
109 	std::string rtp_path;
110 	bool no_audio_flag;
111 	bool is_easyrpg_project;
112 	bool mouse_flag;
113 	bool touch_flag;
114 	std::string encoding;
115 	std::string escape_symbol;
116 	uint32_t escape_char;
117 	int engine;
118 	std::string game_title;
119 	int patch;
120 	std::shared_ptr<Meta> meta;
121 	FileExtGuesser::RPG2KFileExtRemap fileext_map;
122 	Translation translation;
123 	int frames;
124 	std::string replay_input_path;
125 	std::string record_input_path;
126 	std::string command_line;
127 	int speed_modifier = 3;
128 	int speed_modifier_plus = 10;
129 	Game_ConfigPlayer player_config;
130 #ifdef EMSCRIPTEN
131 	std::string emscripten_game_name;
132 #endif
133 #ifdef _3DS
134 	bool is_3dsx;
135 #endif
136 }
137 
138 namespace {
139 	// Overwritten by --encoding
140 	std::string forced_encoding;
141 
142 	FileRequestBinding system_request_id;
143 	FileRequestBinding save_request_id;
144 	FileRequestBinding map_request_id;
145 }
146 
Init(int argc,char * argv[])147 void Player::Init(int argc, char *argv[]) {
148 	frames = 0;
149 
150 	// Must be called before the first call to Output
151 	Graphics::Init();
152 
153 	// FIXME: actual command line parsing is too late for setting this,
154 	// it should be refactored after release to not break things now
155 	for (int i = 1; i < argc; ++i) {
156 		if (strcmp(argv[i], "--no-log-color") == 0) {
157 			Output::SetTermColor(false);
158 			break;
159 		}
160 	}
161 
162 	// Display a nice version string
163 	std::stringstream header;
164 	std::string addtl_ver(PLAYER_ADDTL);
165 	header << "EasyRPG Player " << PLAYER_VERSION;
166 	if (!addtl_ver.empty())
167 		header << " " << addtl_ver;
168 	header << " started";
169 	Output::Debug("{}", header.str());
170 
171 	unsigned int header_width = header.str().length();
172 	header.str("");
173 	header << std::setfill('=') << std::setw(header_width) << "=";
174 	Output::Debug("{}", header.str());
175 
176 #ifdef GEKKO
177 	// Init libfat (Mount SD/USB)
178 	if (!fatInitDefault()) {
179 		Output::Error("Couldn't mount any storage medium!");
180 	}
181 #elif defined(_3DS)
182 	romfsInit();
183 #endif
184 
185 #if defined(_WIN32)
186 	WindowsUtils::InitMiniDumpWriter();
187 #endif
188 
189 	Game_Clock::logClockInfo();
190 	Rand::SeedRandomNumberGenerator(time(NULL));
191 
192 #ifdef EMSCRIPTEN
193 	Output::IgnorePause(true);
194 
195 	// Retrieve save directory from persistent storage
196 	EM_ASM(({
197 		FS.mkdir("Save");
198 		FS.mount(Module.EASYRPG_FS, {}, 'Save');
199 		FS.syncfs(true, function(err) {
200 		});
201 	}));
202 #endif
203 
204 	auto cfg = ParseCommandLine(argc, argv);
205 
206 	Main_Data::Init();
207 
208 	DisplayUi.reset();
209 
210 	if(! DisplayUi) {
211 		DisplayUi = BaseUi::CreateUi(SCREEN_TARGET_WIDTH, SCREEN_TARGET_HEIGHT, cfg.video);
212 	}
213 
214 	auto buttons = Input::GetDefaultButtonMappings();
215 	auto directions = Input::GetDefaultDirectionMappings();
216 
217 	Input::Init(std::move(buttons), std::move(directions), replay_input_path, record_input_path);
218 	Input::AddRecordingData(Input::RecordingData::CommandLine, command_line);
219 
220 	player_config = std::move(cfg.player);
221 }
222 
Run()223 void Player::Run() {
224 	Instrumentation::Init("EasyRPG-Player");
225 	Scene::Push(std::make_shared<Scene_Logo>());
226 	Graphics::UpdateSceneCallback();
227 
228 	reset_flag = false;
229 
230 	Game_Clock::ResetFrame(Game_Clock::now());
231 
232 	// Main loop
233 	// libretro invokes the MainLoop through a retro_run-callback
234 #ifndef USE_LIBRETRO
235 	while (Transition::instance().IsActive() || (Scene::instance && Scene::instance->type != Scene::Null)) {
236 #  if defined(_3DS)
237 		if (!aptMainLoop())
238 			Exit();
239 #  elif defined(__SWITCH__)
240 		if(!appletMainLoop())
241 			Exit();
242 #  endif
243 		MainLoop();
244 	}
245 #endif
246 }
247 
MainLoop()248 void Player::MainLoop() {
249 	Instrumentation::FrameScope iframe;
250 
251 	const auto frame_time = Game_Clock::now();
252 	Game_Clock::OnNextFrame(frame_time);
253 
254 	Player::UpdateInput();
255 
256 	int num_updates = 0;
257 	while (Game_Clock::NextGameTimeStep()) {
258 		if (num_updates > 0) {
259 			Player::UpdateInput();
260 		}
261 
262 		Scene::old_instances.clear();
263 		Scene::instance->MainFunction();
264 
265 		++num_updates;
266 	}
267 	if (num_updates == 0) {
268 		// If no logical frames ran, we need to update the system keys only.
269 		Input::UpdateSystem();
270 	}
271 
272 	Player::Draw();
273 
274 	Scene::old_instances.clear();
275 
276 	if (!Transition::instance().IsActive() && Scene::instance->type == Scene::Null) {
277 		Exit();
278 		return;
279 	}
280 
281 	auto frame_limit = DisplayUi->GetFrameLimit();
282 	if (frame_limit == Game_Clock::duration()) {
283 #ifdef EMSCRIPTEN
284 		emscripten_sleep(0);
285 #endif
286 		return;
287 	}
288 
289 	// Still time after graphic update? Yield until it's time for next one.
290 	auto now = Game_Clock::now();
291 	auto next = frame_time + frame_limit;
292 	if (Game_Clock::now() < next) {
293 		iframe.End();
294 		Game_Clock::SleepFor(next - now);
295 	} else {
296 #ifdef EMSCRIPTEN
297 		// Yield back to browser once per frame
298 		emscripten_sleep(0);
299 #endif
300 	}
301 }
302 
Pause()303 void Player::Pause() {
304 	Audio().BGM_Pause();
305 }
306 
Resume()307 void Player::Resume() {
308 	Input::ResetKeys();
309 	Audio().BGM_Resume();
310 	Game_Clock::ResetFrame(Game_Clock::now());
311 }
312 
UpdateInput()313 void Player::UpdateInput() {
314 	// Input Logic:
315 	if (Input::IsSystemTriggered(Input::TOGGLE_FPS)) {
316 		DisplayUi->ToggleShowFps();
317 	}
318 	if (Input::IsSystemTriggered(Input::TAKE_SCREENSHOT)) {
319 		Output::TakeScreenshot();
320 	}
321 	if (Input::IsSystemTriggered(Input::SHOW_LOG)) {
322 		Output::ToggleLog();
323 	}
324 	if (Input::IsSystemTriggered(Input::TOGGLE_ZOOM)) {
325 		DisplayUi->ToggleZoom();
326 	}
327 	float speed = 1.0;
328 	if (Input::IsSystemPressed(Input::FAST_FORWARD)) {
329 		speed = speed_modifier;
330 	}
331 	if (Input::IsSystemPressed(Input::FAST_FORWARD_PLUS)) {
332 		speed = speed_modifier_plus;
333 	}
334 	Game_Clock::SetGameSpeedFactor(speed);
335 
336 	if (Main_Data::game_quit) {
337 		reset_flag |= Main_Data::game_quit->ShouldQuit();
338 	}
339 
340 	// Update Logic:
341 	DisplayUi->ProcessEvents();
342 }
343 
Update(bool update_scene)344 void Player::Update(bool update_scene) {
345 	std::shared_ptr<Scene> old_instance = Scene::instance;
346 
347 	if (exit_flag) {
348 		Scene::PopUntil(Scene::Null);
349 	} else if (reset_flag && !Scene::IsAsyncPending()) {
350 		reset_flag = false;
351 		if (Scene::ReturnToTitleScene()) {
352 			// Fade out music and stop sound effects before returning
353 			Main_Data::game_system->BgmFade(800);
354 			Audio().SE_Stop();
355 			// Do not update this scene until it's properly set up in the next main loop
356 			update_scene = false;
357 		}
358 	}
359 
360 	if (update_scene) {
361 		IncFrame();
362 	}
363 
364 	Audio().Update();
365 	Input::Update();
366 
367 	// Game events can query full screen status and change their behavior, so this needs to
368 	// be a game key and not a system key.
369 	if (Input::IsTriggered(Input::TOGGLE_FULLSCREEN)) {
370 		DisplayUi->ToggleFullscreen();
371 	}
372 
373 	if (Main_Data::game_quit) {
374 		Main_Data::game_quit->Update();
375 	}
376 
377 	auto& transition = Transition::instance();
378 
379 	if (transition.IsActive()) {
380 		transition.Update();
381 	} else {
382 		// If we aren't waiting on a transition, but we are waiting for scene delay.
383 		Scene::instance->UpdateDelayFrames();
384 	}
385 
386 	if (update_scene) {
387 		if (Main_Data::game_ineluki) {
388 			Main_Data::game_ineluki->Update();
389 		}
390 
391 		Scene::instance->Update();
392 	}
393 }
394 
Draw()395 void Player::Draw() {
396 	Graphics::Update();
397 	Graphics::Draw(*DisplayUi->GetDisplaySurface());
398 	DisplayUi->UpdateDisplay();
399 }
400 
IncFrame()401 void Player::IncFrame() {
402 	++frames;
403 	if (Main_Data::game_system) {
404 		Main_Data::game_system->IncFrameCounter();
405 	}
406 }
407 
GetFrames()408 int Player::GetFrames() {
409 	return frames;
410 }
411 
Exit()412 void Player::Exit() {
413 	Graphics::UpdateSceneCallback();
414 #ifdef EMSCRIPTEN
415 	BitmapRef surface = DisplayUi->GetDisplaySurface();
416 	std::string message = "It's now safe to turn off\n      your browser.";
417 
418 	Text::Draw(*surface, 84, DisplayUi->GetHeight() / 2 - 30, *Font::Default(), Color(221, 123, 64, 255), message);
419 	DisplayUi->UpdateDisplay();
420 #endif
421 
422 	Player::ResetGameObjects();
423 	Font::Dispose();
424 	DynRpg::Reset();
425 	Graphics::Quit();
426 	Output::Quit();
427 	FileFinder::Quit();
428 	DisplayUi.reset();
429 
430 #ifdef PSP2
431 	sceKernelExitProcess(0);
432 #elif defined(_3DS)
433 	romfsExit();
434 #endif
435 }
436 
ParseCommandLine(int argc,char * argv[])437 Game_Config Player::ParseCommandLine(int argc, char *argv[]) {
438 #ifdef _3DS
439 	is_3dsx = argc > 0;
440 #endif
441 
442 #if defined(_WIN32) && !defined(__WINRT__)
443 	int argc_w;
444 	LPWSTR *argv_w = CommandLineToArgvW(GetCommandLineW(), &argc_w);
445 #endif
446 
447 	engine = EngineNone;
448 	patch = PatchNone;
449 	debug_flag = false;
450 	hide_title_flag = false;
451 	exit_flag = false;
452 	reset_flag = false;
453 	new_game_flag = false;
454 	load_game_id = -1;
455 	party_x_position = -1;
456 	party_y_position = -1;
457 	start_map_id = -1;
458 	no_rtp_flag = false;
459 	no_audio_flag = false;
460 	is_easyrpg_project = false;
461 	mouse_flag = false;
462 	touch_flag = false;
463 	Game_Battle::battle_test.enabled = false;
464 
465 	std::stringstream ss;
466 	for (int i = 1; i < argc; ++i) {
467 		ss << argv[i] << " ";
468 	}
469 	Output::Debug("CLI: {}", ss.str());
470 	command_line = ss.str();
471 
472 #if defined(_WIN32) && !defined(__WINRT__)
473 	CmdlineParser cp(argc, argv_w);
474 #else
475 	CmdlineParser cp(argc, argv);
476 #endif
477 
478 	auto cfg = Game_Config::Create(cp);
479 
480 	cp.Rewind();
481 	while (!cp.Done()) {
482 		CmdlineArg arg;
483 		long li_value = 0;
484 		if (cp.ParseNext(arg, 0, "window")) {
485 			// Legacy RPG_RT argument - window
486 			cfg.video.fullscreen.Set(false);
487 			continue;
488 		}
489 		if (cp.ParseNext(arg, 0, "--enable-mouse")) {
490 			mouse_flag = true;
491 			continue;
492 		}
493 		if (cp.ParseNext(arg, 0, "--enable-touch")) {
494 			touch_flag = true;
495 			continue;
496 		}
497 		if (cp.ParseNext(arg, 0, {"testplay", "--test-play"})) {
498 			// Legacy RPG_RT argument - testplay
499 			debug_flag = true;
500 			continue;
501 		}
502 		if (cp.ParseNext(arg, 0, {"hidetitle", "--hide-title"})) {
503 			// Legacy RPG_RT argument - hidetitle
504 			hide_title_flag = true;
505 			continue;
506 		}
507 		if(cp.ParseNext(arg, 4, {"battletest", "--battle-test"})) {
508 			// Legacy RPG_RT argument - battletest
509 			Game_Battle::battle_test.enabled = true;
510 			Game_Battle::battle_test.troop_id = 0;
511 
512 			if (arg.NumValues() > 0) {
513 				if (arg.ParseValue(0, li_value)) {
514 					Game_Battle::battle_test.troop_id = li_value;
515 				}
516 			}
517 
518 			if (arg.NumValues() >= 4) {
519 				// 2k3 passes formation, condition and terrain_id as args 5-7
520 				if (arg.ParseValue(1, li_value)) {
521 					Game_Battle::battle_test.formation = static_cast<lcf::rpg::System::BattleFormation>(li_value);
522 				}
523 				if (arg.ParseValue(2, li_value)) {
524 					Game_Battle::battle_test.condition = static_cast<lcf::rpg::System::BattleCondition>(li_value);
525 				}
526 				if (arg.ParseValue(3, li_value)) {
527 					Game_Battle::battle_test.terrain_id = static_cast<lcf::rpg::System::BattleFormation>(li_value);
528 				}
529 			}
530 			continue;
531 		}
532 		if (cp.ParseNext(arg, 1, "--project-path") && arg.NumValues() > 0) {
533 			if (arg.NumValues() > 0) {
534 				auto gamefs = FileFinder::Root().Create(FileFinder::MakeCanonical(arg.Value(0), 0));
535 				if (!gamefs) {
536 					Output::Error("Invalid --project-path {}", arg.Value(0));
537 				}
538 				FileFinder::SetGameFilesystem(gamefs);
539 			}
540 			continue;
541 		}
542 		if (cp.ParseNext(arg, 1, "--save-path")) {
543 			if (arg.NumValues() > 0) {
544 				auto savefs = FileFinder::Root().Create(FileFinder::MakeCanonical(arg.Value(0), 0));
545 				if (!savefs) {
546 					Output::Error("Invalid --save-path {}", arg.Value(0));
547 				}
548 				FileFinder::SetSaveFilesystem(savefs);
549 			}
550 			continue;
551 		}
552 		if (cp.ParseNext(arg, 0, "--new-game")) {
553 			new_game_flag = true;
554 			continue;
555 		}
556 		if (cp.ParseNext(arg, 1, "--load-game-id")) {
557 			if (arg.ParseValue(0, li_value)) {
558 				load_game_id = li_value;
559 			}
560 			continue;
561 		}
562 		/*else if (*it == "--load-game") {
563 			// load game by filename
564 		}
565 		else if (*it == "--database") {
566 			// overwrite database file
567 		}
568 		else if (*it == "--map-tree") {
569 			// overwrite map tree file
570 		}
571 		else if (*it == "--start-map") {
572 			// overwrite start map by filename
573 		}*/
574 		if (cp.ParseNext(arg, 1, "--seed")) {
575 			if (arg.ParseValue(0, li_value)) {
576 				Rand::SeedRandomNumberGenerator(li_value);
577 			}
578 			continue;
579 		}
580 		if (cp.ParseNext(arg, 1, "--start-map-id")) {
581 			if (arg.ParseValue(0, li_value)) {
582 				start_map_id = li_value;
583 			}
584 			continue;
585 		}
586 		if (cp.ParseNext(arg, 2, "--start-position")) {
587 			if (arg.ParseValue(0, li_value)) {
588 				party_x_position = li_value;
589 			}
590 			if (arg.ParseValue(1, li_value)) {
591 				party_y_position = li_value;
592 			}
593 			continue;
594 		}
595 		if (cp.ParseNext(arg, 4, "--start-party")) {
596 			for (int i = 0; i < arg.NumValues(); ++i) {
597 				if (arg.ParseValue(i, li_value)) {
598 					party_members.push_back(li_value);
599 				}
600 			}
601 			continue;
602 		}
603 		if (cp.ParseNext(arg, 1, "--engine")) {
604 			if (arg.NumValues() > 0) {
605 				const auto& v = arg.Value(0);
606 				if (v == "rpg2k" || v == "2000") {
607 					engine = EngineRpg2k;
608 				}
609 				else if (v == "rpg2kv150" || v == "2000v150") {
610 					engine = EngineRpg2k | EngineMajorUpdated;
611 				}
612 				else if (v == "rpg2ke" || v == "2000e") {
613 					engine = EngineRpg2k | EngineMajorUpdated | EngineEnglish;
614 				}
615 				else if (v == "rpg2k3" || v == "2003") {
616 					engine = EngineRpg2k3;
617 				}
618 				else if (v == "rpg2k3v105" || v == "2003v105") {
619 					engine = EngineRpg2k3 | EngineMajorUpdated;
620 				}
621 				else if (v == "rpg2k3e") {
622 					engine = EngineRpg2k3 | EngineMajorUpdated | EngineEnglish;
623 				}
624 			}
625 			continue;
626 		}
627 		if (cp.ParseNext(arg, 1, "--record-input")) {
628 			if (arg.NumValues() > 0) {
629 				record_input_path = arg.Value(0);
630 			}
631 			continue;
632 		}
633 		if (cp.ParseNext(arg, 1, "--replay-input")) {
634 			if (arg.NumValues() > 0) {
635 				replay_input_path = arg.Value(0);
636 			}
637 			continue;
638 		}
639 		if (cp.ParseNext(arg, 1, "--encoding")) {
640 			if (arg.NumValues() > 0) {
641 				forced_encoding = arg.Value(0);
642 			}
643 			continue;
644 		}
645 		if (cp.ParseNext(arg, 0, "--disable-audio")) {
646 			no_audio_flag = true;
647 			continue;
648 		}
649 		if (cp.ParseNext(arg, 0, "--disable-rtp")) {
650 			no_rtp_flag = true;
651 			continue;
652 		}
653 		if (cp.ParseNext(arg, 1, "--rtp-path")) {
654 			if (arg.NumValues() > 0) {
655 				rtp_path = arg.Value(0);
656 			}
657 			continue;
658 		}
659 		if (cp.ParseNext(arg, 2, "--patch")) {
660 			patch |= PatchOverride;
661 			for (int i = 0; i < arg.NumValues(); ++i) {
662 				const auto& v = arg.Value(i);
663 				if (v == "dynrpg") {
664 					patch |= PatchDynRpg;
665 				} else if (v == "maniac") {
666 					patch |= PatchManiac;
667 				}
668 			}
669 			continue;
670 		}
671 		if (cp.ParseNext(arg, 0, "--no-log-color")) {
672 			Output::SetTermColor(false);
673 			continue;
674 		}
675 		if (cp.ParseNext(arg, 0, "--version", 'v')) {
676 			PrintVersion();
677 			exit(0);
678 			break;
679 		}
680 		if (cp.ParseNext(arg, 0, {"--help", "/?"}, 'h')) {
681 			PrintUsage();
682 			exit(0);
683 			break;
684 		}
685 #ifdef EMSCRIPTEN
686 		if (cp.ParseNext(arg, 1, "--game")) {
687 			if (arg.NumValues() > 0) {
688 				emscripten_game_name = arg.Value(0);
689 			}
690 			continue;
691 		}
692 #endif
693 		cp.SkipNext();
694 	}
695 
696 #if defined(_WIN32) && !defined(__WINRT__)
697 	LocalFree(argv_w);
698 #endif
699 
700 	return cfg;
701 }
702 
CreateGameObjects()703 void Player::CreateGameObjects() {
704 	// Load the meta information file.
705 	// Note: This should eventually be split across multiple folders as described in Issue #1210
706 	std::string meta_file = FileFinder::Game().FindFile(META_NAME);
707 	meta.reset(new Meta(meta_file));
708 
709 	// Guess non-standard extensions (for the DB) before loading the encoding
710 	GuessNonStandardExtensions();
711 
712 	GetEncoding();
713 	escape_symbol = lcf::ReaderUtil::Recode("\\", encoding);
714 	if (escape_symbol.empty()) {
715 		Output::Error("Invalid encoding: {}.", encoding);
716 	}
717 	escape_char = Utils::DecodeUTF32(Player::escape_symbol).front();
718 
719 	// Check for translation-related directories and load language names.
720 	translation.InitTranslations();
721 
722 	std::string game_path = FileFinder::GetFullFilesystemPath(FileFinder::Game());
723 	std::string save_path = FileFinder::GetFullFilesystemPath(FileFinder::Save());
724 	if (game_path == save_path) {
725 		Output::DebugStr("Game and Save Directory:");
726 		FileFinder::DumpFilesystem(FileFinder::Game());
727 	} else {
728 		Output::Debug("Game Directory:");
729 		FileFinder::DumpFilesystem(FileFinder::Game());
730 		Output::Debug("SaveDirectory:", save_path);
731 		FileFinder::DumpFilesystem(FileFinder::Save());
732 	}
733 
734 	LoadDatabase();
735 
736 	bool no_rtp_warning_flag = false;
737 	{ // Scope lifetime of variables for ini parsing
738 		std::string ini_file = FileFinder::Game().FindFile(INI_NAME);
739 
740 		auto ini_stream = FileFinder::Game().OpenInputStream(ini_file, std::ios_base::in);
741 		if (ini_stream) {
742 			lcf::INIReader ini(ini_stream);
743 			if (ini.ParseError() != -1) {
744 				std::string title = ini.Get("RPG_RT", "GameTitle", GAME_TITLE);
745 				game_title = lcf::ReaderUtil::Recode(title, encoding);
746 				no_rtp_warning_flag = ini.Get("RPG_RT", "FullPackageFlag", "0") == "1" ? true : no_rtp_flag;
747 			}
748 		}
749 	}
750 
751 	std::stringstream title;
752 	if (!game_title.empty()) {
753 		Output::Debug("Loading game {}", game_title);
754 		title << game_title << " - ";
755 		Input::AddRecordingData(Input::RecordingData::GameTitle, game_title);
756 	} else {
757 		Output::Debug("Could not read game title.");
758 	}
759 	title << GAME_TITLE;
760 	DisplayUi->SetTitle(title.str());
761 
762 	if (no_rtp_warning_flag) {
763 		Output::Debug("Game does not need RTP (FullPackageFlag=1)");
764 	}
765 
766 	if (engine == EngineNone) {
767 		if (lcf::Data::system.ldb_id == 2003) {
768 			engine = EngineRpg2k3;
769 
770 			if (FileFinder::Game().FindFile("ultimate_rt_eb.dll").empty()) {
771 				// Heuristic: Detect if game was converted from 2000 to 2003 and
772 				// no typical 2003 feature was used at all (breaks .flow e.g.)
773 				if (lcf::Data::classes.size() == 1 &&
774 					lcf::Data::classes[0].name.empty() &&
775 					lcf::Data::system.menu_commands.empty() &&
776 					lcf::Data::system.system2_name.empty() &&
777 					lcf::Data::battleranimations.size() == 1 &&
778 					lcf::Data::battleranimations[0].name.empty()) {
779 					engine = EngineRpg2k;
780 					Output::Debug("Using RPG2k Interpreter (heuristic)");
781 				} else {
782 					Output::Debug("Using RPG2k3 Interpreter");
783 				}
784 			} else {
785 				engine |= EngineEnglish | EngineMajorUpdated;
786 				Output::Debug("Using RPG2k3 (English release, v1.11) Interpreter");
787 			}
788 		} else {
789 			engine = EngineRpg2k;
790 			Output::Debug("Using RPG2k Interpreter");
791 			if (lcf::Data::data.version >= 1) {
792 				engine |= EngineEnglish | EngineMajorUpdated;
793 				Output::Debug("RM2k >= v.1.61 (English release) detected");
794 			}
795 		}
796 		if (!(engine & EngineMajorUpdated)) {
797 			if (FileFinder::IsMajorUpdatedTree()) {
798 				engine |= EngineMajorUpdated;
799 				Output::Debug("RPG2k >= v1.50 / RPG2k3 >= v1.05 detected");
800 			} else {
801 				Output::Debug("RPG2k < v1.50 / RPG2k3 < v1.05 detected");
802 			}
803 		}
804 	}
805 	Output::Debug("Engine configured as: 2k={} 2k3={} MajorUpdated={} Eng={}", Player::IsRPG2k(), Player::IsRPG2k3(), Player::IsMajorUpdatedVersion(), Player::IsEnglish());
806 
807 	Main_Data::filefinder_rtp = std::make_unique<FileFinder_RTP>(no_rtp_flag, no_rtp_warning_flag, rtp_path);
808 
809 	if ((patch & PatchOverride) == 0) {
810 		if (!FileFinder::Game().FindFile("dynloader.dll").empty()) {
811 			patch |= PatchDynRpg;
812 			Output::Warning("This game uses DynRPG and will not run properly.");
813 		}
814 
815 		if (!FileFinder::Game().FindFile("accord.dll").empty()) {
816 			patch |= PatchManiac;
817 			Output::Warning("This game uses the Maniac Patch and will not run properly.");
818 		}
819 	}
820 
821 	Output::Debug("Patch configuration: dynrpg={} maniac={}", Player::IsPatchDynRpg(), Player::IsPatchManiac());
822 
823 	// ExFont parsing
824 	Cache::exfont_custom.clear();
825 	// Check for bundled ExFont
826 	auto exfont_stream = FileFinder::OpenImage(".", "ExFont");
827 #ifndef EMSCRIPTEN
828 	if (!exfont_stream) {
829 		// Attempt reading ExFont from RPG_RT.exe (not supported on Emscripten,
830 		// a ExFont can be manually bundled there)
831 		std::string exep = FileFinder::Game().FindFile(EXE_NAME);
832 		if (!exep.empty()) {
833 			auto exesp = FileFinder::Game().OpenInputStream(exep);
834 			if (exesp) {
835 				Output::Debug("Loading ExFont from {}", exep);
836 				EXEReader exe_reader = EXEReader(exesp);
837 				Cache::exfont_custom = exe_reader.GetExFont();
838 			} else {
839 				Output::Debug("ExFont loading failed: {} not readable", exep);
840 			}
841 		} else {
842 			Output::Debug("ExFont loading failed: {} not found", EXE_NAME);
843 		}
844 	}
845 #endif
846 	if (exfont_stream) {
847 		Output::Debug("Using custom ExFont: {}", exfont_stream.GetName());
848 		Cache::exfont_custom = Utils::ReadStream(exfont_stream);
849 	}
850 
851 	ResetGameObjects();
852 
853 	Main_Data::game_ineluki->ExecuteScriptList(FileFinder::Game().FindFile("autorun.script"));
854 }
855 
ResetGameObjects()856 void Player::ResetGameObjects() {
857 	// The init order is important
858 	Main_Data::Cleanup();
859 
860 	Main_Data::game_switches = std::make_unique<Game_Switches>();
861 
862 	auto min_var = lcf::Data::system.easyrpg_variable_min_value;
863 	if (min_var == 0) {
864 		min_var = Player::IsRPG2k3() ? Game_Variables::min_2k3 : Game_Variables::min_2k;
865 	}
866 	auto max_var = lcf::Data::system.easyrpg_variable_max_value;
867 	if (max_var == 0) {
868 		max_var = Player::IsRPG2k3() ? Game_Variables::max_2k3 : Game_Variables::max_2k;
869 	}
870 	Main_Data::game_variables = std::make_unique<Game_Variables>(min_var, max_var);
871 
872 	// Prevent a crash when Game_Map wants to reset the screen content
873 	// because Setup() modified pictures array
874 	Main_Data::game_screen = std::make_unique<Game_Screen>();
875 	Main_Data::game_pictures = std::make_unique<Game_Pictures>();
876 
877 	Main_Data::game_actors = std::make_unique<Game_Actors>();
878 
879 	Game_Map::Init();
880 
881 	Main_Data::game_system = std::make_unique<Game_System>();
882 	Main_Data::game_targets = std::make_unique<Game_Targets>();
883 	Main_Data::game_enemyparty = std::make_unique<Game_EnemyParty>();
884 	Main_Data::game_party = std::make_unique<Game_Party>();
885 	Main_Data::game_player = std::make_unique<Game_Player>();
886 	Main_Data::game_quit = std::make_unique<Game_Quit>();
887 	Main_Data::game_ineluki = std::make_unique<Game_Ineluki>();
888 
889 	DynRpg::Reset();
890 
891 	Game_Clock::ResetFrame(Game_Clock::now());
892 
893 	Main_Data::game_system->ReloadSystemGraphic();
894 
895 	Input::ResetMask();
896 }
897 
DefaultLmuStartFileExists(const FilesystemView & fs)898 static bool DefaultLmuStartFileExists(const FilesystemView& fs) {
899 	// Compute map_id based on command line.
900 	int map_id = Player::start_map_id == -1 ? lcf::Data::treemap.start.party_map_id : Player::start_map_id;
901 	std::string mapName = Game_Map::ConstructMapName(map_id, false);
902 
903 	// Now see if the file exists.
904 	return !fs.FindFile(mapName).empty();
905 }
906 
GuessNonStandardExtensions()907 void Player::GuessNonStandardExtensions() {
908 	// Check all conditions, but check the remap last (since it is potentially slower).
909 	FileExtGuesser::RPG2KNonStandardFilenameGuesser rpg2kRemap;
910 	if (!FileFinder::IsRPG2kProject(FileFinder::Game()) &&
911 		!FileFinder::IsEasyRpgProject(FileFinder::Game())) {
912 
913 		rpg2kRemap = FileExtGuesser::GetRPG2kProjectWithRenames(FileFinder::Game());
914 		if (rpg2kRemap.Empty()) {
915 			// Unlikely to happen because of the game browser only launches valid games
916 			Output::Error("{}\n\n{}\n\n{}\n\n{}","No valid game was found.",
917 				"EasyRPG must be run from a game folder containing\nRPG_RT.ldb and RPG_RT.lmt.",
918 				"This engine only supports RPG Maker 2000 and 2003\ngames.",
919 				"RPG Maker XP, VX, VX Ace and MV are NOT supported.");
920 		}
921 	}
922 
923 	// At this point we haven't yet determined if this is an easyrpg project or not.
924 	// There are several ways to handle this, but we just put 'is_easyrpg_project' in the header
925 	// and calculate it here.
926 	// Try loading EasyRPG project files first, then fallback to normal RPG Maker
927 	std::string edb = FileFinder::Game().FindFile(DATABASE_NAME_EASYRPG);
928 	std::string emt = FileFinder::Game().FindFile(TREEMAP_NAME_EASYRPG);
929 	is_easyrpg_project = !edb.empty() && !emt.empty();
930 
931 	// Non-standard extensions only apply to non-EasyRPG projects
932 	if (!is_easyrpg_project && !rpg2kRemap.Empty()) {
933 		fileext_map = rpg2kRemap.guessExtensions(*meta);
934 	} else {
935 		fileext_map = FileExtGuesser::RPG2KFileExtRemap();
936 	}
937 }
938 
LoadDatabase()939 void Player::LoadDatabase() {
940 	// Load lcf::Database
941 	lcf::Data::Clear();
942 
943 	if (is_easyrpg_project) {
944 		std::string edb = FileFinder::Game().FindFile(DATABASE_NAME_EASYRPG);
945 		auto edb_stream = FileFinder::Game().OpenInputStream(edb, std::ios_base::in);
946 		if (!edb_stream) {
947 			Output::Error("Error loading {}", DATABASE_NAME_EASYRPG);
948 			return;
949 		}
950 
951 		auto db = lcf::LDB_Reader::LoadXml(edb_stream);
952 		if (!db) {
953 			Output::ErrorStr(lcf::LcfReader::GetError());
954 			return;
955 		} else {
956 			lcf::Data::data = std::move(*db);
957 		}
958 
959 		std::string emt = FileFinder::Game().FindFile(TREEMAP_NAME_EASYRPG);
960 		auto emt_stream = FileFinder::Game().OpenInputStream(emt, std::ios_base::in);
961 		if (!emt_stream) {
962 			Output::Error("Error loading {}", TREEMAP_NAME_EASYRPG);
963 			return;
964 		}
965 
966 		auto treemap = lcf::LMT_Reader::LoadXml(emt_stream);
967 		if (!treemap) {
968 			Output::ErrorStr(lcf::LcfReader::GetError());
969 		} else {
970 			lcf::Data::treemap = std::move(*treemap);
971 		}
972 	} else {
973 		// Retrieve the appropriately-renamed files.
974 		std::string ldb_name = fileext_map.MakeFilename(RPG_RT_PREFIX, SUFFIX_LDB);
975 		std::string ldb = FileFinder::Game().FindFile(ldb_name);
976 		std::string lmt_name = fileext_map.MakeFilename(RPG_RT_PREFIX, SUFFIX_LMT);
977 		std::string lmt = FileFinder::Game().FindFile(lmt_name);
978 
979 		auto ldb_stream = FileFinder::Game().OpenInputStream(ldb);
980 		if (!ldb_stream) {
981 			Output::Error("Error loading {}", ldb_name);
982 			return;
983 		}
984 
985 		auto db = lcf::LDB_Reader::Load(ldb_stream, encoding);
986 		if (!db) {
987 			Output::ErrorStr(lcf::LcfReader::GetError());
988 			return;
989 		} else {
990 			lcf::Data::data = std::move(*db);
991 		}
992 
993 		auto lmt_stream = FileFinder::Game().OpenInputStream(lmt);
994 		if (!lmt_stream) {
995 			Output::Error("Error loading {}", lmt_name);
996 			return;
997 		}
998 
999 		auto treemap = lcf::LMT_Reader::Load(lmt_stream, encoding);
1000 		if (!treemap) {
1001 			Output::ErrorStr(lcf::LcfReader::GetError());
1002 			return;
1003 		} else {
1004 			lcf::Data::treemap = std::move(*treemap);
1005 		}
1006 
1007 		if (Input::IsRecording()) {
1008 			ldb_stream.clear();
1009 			ldb_stream.seekg(0, std::ios::beg);
1010 			lmt_stream.clear();
1011 			lmt_stream.seekg(0, std::ios::beg);
1012 			Input::AddRecordingData(Input::RecordingData::Hash,
1013 									fmt::format("ldb {:#08x}", Utils::CRC32(ldb_stream)));
1014 			Input::AddRecordingData(Input::RecordingData::Hash,
1015 						   fmt::format("lmt {:#08x}", Utils::CRC32(lmt_stream)));
1016 		}
1017 
1018 		// Override map extension, if needed.
1019 		if (!DefaultLmuStartFileExists(FileFinder::Game())) {
1020 			FileExtGuesser::GuessAndAddLmuExtension(FileFinder::Game(), *meta, fileext_map);
1021 		}
1022 	}
1023 }
1024 
OnMapSaveFileReady(FileRequestResult *,lcf::rpg::Save save)1025 static void OnMapSaveFileReady(FileRequestResult*, lcf::rpg::Save save) {
1026 	auto map = Game_Map::loadMapFile(Main_Data::game_player->GetMapId());
1027 	Game_Map::SetupFromSave(
1028 			std::move(map),
1029 			std::move(save.map_info),
1030 			std::move(save.boat_location),
1031 			std::move(save.ship_location),
1032 			std::move(save.airship_location),
1033 			std::move(save.foreground_event_execstate),
1034 			std::move(save.panorama),
1035 			std::move(save.common_events));
1036 }
1037 
LoadSavegame(const std::string & save_name,int save_id)1038 void Player::LoadSavegame(const std::string& save_name, int save_id) {
1039 	Output::Debug("Loading Save {}", save_name);
1040 
1041 	bool load_on_map = Scene::instance->type == Scene::Map;
1042 
1043 	if (!load_on_map) {
1044 		Main_Data::game_system->BgmFade(800);
1045 		// We erase the screen now before loading the saved game. This prevents an issue where
1046 		// if the save game has a different system graphic, the load screen would change before
1047 		// transitioning out.
1048 		Transition::instance().InitErase(Transition::TransitionFadeOut, Scene::instance.get(), 6);
1049 	}
1050 
1051 	auto title_scene = Scene::Find(Scene::Title);
1052 	if (title_scene) {
1053 		static_cast<Scene_Title*>(title_scene.get())->OnGameStart();
1054 	}
1055 
1056 	auto save_stream = FileFinder::Save().OpenInputStream(save_name);
1057 	if (!save_stream) {
1058 		Output::Error("Error loading {}", save_name);
1059 		return;
1060 	}
1061 
1062 	std::unique_ptr<lcf::rpg::Save> save = lcf::LSD_Reader::Load(save_stream, encoding);
1063 
1064 	if (!save.get()) {
1065 		Output::ErrorStr(lcf::LcfReader::GetError());
1066 		return;
1067 	}
1068 
1069 	std::stringstream verstr;
1070 	int ver = save->easyrpg_data.version;
1071 	if (ver == 0) {
1072 		verstr << "RPG_RT or EasyRPG Player Pre-0.6.0";
1073 	} else if (ver >= 10000) {
1074 		verstr << "Unknown Engine";
1075 	} else {
1076 		verstr << "EasyRPG Player ";
1077 		char verbuf[64];
1078 		sprintf(verbuf, "%d.%d.%d", ver / 1000 % 10, ver / 100 % 10, ver / 10 % 10);
1079 		verstr << verbuf;
1080 		if (ver % 10 > 0) {
1081 			verstr << "." << ver % 10;
1082 		}
1083 	}
1084 
1085 	Output::Debug("Savegame version {} ({})", ver, verstr.str());
1086 
1087 	if (ver > PLAYER_SAVEGAME_VERSION) {
1088 		Output::Warning("This savegame was created with {} which is newer than the current version of EasyRPG Player ({})",
1089 			verstr.str(), PLAYER_VERSION);
1090 	}
1091 
1092 	// Compatibility hacks for old EasyRPG Player saves.
1093 	if (save->easyrpg_data.version == 0) {
1094 		// Old savegames accidentally wrote animation_type as continuous for all events.
1095 		save->party_location.animation_type = Game_Character::AnimType::AnimType_non_continuous;
1096 		save->boat_location.animation_type = Game_Character::AnimType::AnimType_non_continuous;
1097 		save->ship_location.animation_type = Game_Character::AnimType::AnimType_non_continuous;
1098 		save->airship_location.animation_type = Game_Character::AnimType::AnimType_non_continuous;
1099 	}
1100 
1101 	if (!load_on_map) {
1102 		Scene::PopUntil(Scene::Title);
1103 	}
1104 	Game_Map::Dispose();
1105 
1106 	Main_Data::game_switches->SetData(std::move(save->system.switches));
1107 	Main_Data::game_variables->SetData(std::move(save->system.variables));
1108 	Main_Data::game_system->SetupFromSave(std::move(save->system));
1109 	Main_Data::game_actors->SetSaveData(std::move(save->actors));
1110 	Main_Data::game_party->SetupFromSave(std::move(save->inventory));
1111 	Main_Data::game_screen->SetSaveData(std::move(save->screen));
1112 	Main_Data::game_pictures->SetSaveData(std::move(save->pictures));
1113 	Main_Data::game_targets->SetSaveData(std::move(save->targets));
1114 	Main_Data::game_player->SetSaveData(save->party_location);
1115 
1116 	int map_id = Main_Data::game_player->GetMapId();
1117 
1118 	FileRequestAsync* map = Game_Map::RequestMap(map_id);
1119 	save_request_id = map->Bind([save=std::move(*save)](auto* request) { OnMapSaveFileReady(request, std::move(save)); });
1120 	map->SetImportantFile(true);
1121 
1122 	Main_Data::game_system->ReloadSystemGraphic();
1123 
1124 	map->Start();
1125 	if (!load_on_map) {
1126 		Scene::Push(std::make_shared<Scene_Map>(save_id));
1127 	}
1128 }
1129 
OnMapFileReady(FileRequestResult *)1130 static void OnMapFileReady(FileRequestResult*) {
1131 	int map_id = Player::start_map_id == -1 ?
1132 		lcf::Data::treemap.start.party_map_id : Player::start_map_id;
1133 	int x_pos = Player::party_x_position == -1 ?
1134 		lcf::Data::treemap.start.party_x : Player::party_x_position;
1135 	int y_pos = Player::party_y_position == -1 ?
1136 		lcf::Data::treemap.start.party_y : Player::party_y_position;
1137 	if (Player::party_members.size() > 0) {
1138 		Main_Data::game_party->Clear();
1139 		for (auto& member: Player::party_members) {
1140 			Main_Data::game_party->AddActor(member);
1141 		}
1142 	}
1143 
1144 	Main_Data::game_player->MoveTo(map_id, x_pos, y_pos);
1145 }
1146 
SetupNewGame()1147 void Player::SetupNewGame() {
1148 	Main_Data::game_system->BgmFade(800, true);
1149 	Main_Data::game_system->ResetFrameCounter();
1150 	auto title = Scene::Find(Scene::Title);
1151 	if (title) {
1152 		static_cast<Scene_Title*>(title.get())->OnGameStart();
1153 	}
1154 
1155 	Main_Data::game_system->SetAtbMode(static_cast<Game_System::AtbMode>(lcf::Data::battlecommands.easyrpg_default_atb_mode));
1156 
1157 	Main_Data::game_party->SetupNewGame();
1158 	SetupPlayerSpawn();
1159 	Scene::Push(std::make_shared<Scene_Map>(0));
1160 }
1161 
SetupPlayerSpawn()1162 void Player::SetupPlayerSpawn() {
1163 	int map_id = Player::start_map_id == -1 ?
1164 		lcf::Data::treemap.start.party_map_id : Player::start_map_id;
1165 
1166 	FileRequestAsync* request = Game_Map::RequestMap(map_id);
1167 	map_request_id = request->Bind(&OnMapFileReady);
1168 	request->SetImportantFile(true);
1169 	request->Start();
1170 }
1171 
SetupBattleTest()1172 void Player::SetupBattleTest() {
1173 	BattleArgs args;
1174 	args.troop_id = Game_Battle::battle_test.troop_id;
1175 	args.first_strike = false;
1176 	args.allow_escape = true;
1177 	args.background = ToString(lcf::Data::system.battletest_background);
1178 	args.terrain_id = 1; //Not used in 2k, for 2k3 only used to determine grid layout if formation == terrain.
1179 
1180 	if (Player::IsRPG2k3()) {
1181 		args.formation = Game_Battle::battle_test.formation;
1182 		args.condition = Game_Battle::battle_test.condition;
1183 
1184 		if (args.formation == lcf::rpg::System::BattleFormation_terrain) {
1185 			args.terrain_id = Game_Battle::battle_test.terrain_id;
1186 		}
1187 
1188 		Output::Debug("BattleTest Mode 2k3 troop=({}) background=({}) formation=({}) condition=({}) terrain=({})",
1189 				args.troop_id, args.background.c_str(), args.formation, args.condition, args.terrain_id);
1190 	} else {
1191 		Output::Debug("BattleTest Mode 2k troop=({}) background=({})", args.troop_id, args.background);
1192 	}
1193 
1194 	auto* troop = lcf::ReaderUtil::GetElement(lcf::Data::troops, args.troop_id);
1195 	if (troop == nullptr) {
1196 		Output::Error("BattleTest: Invalid Monster Party ID {}", args.troop_id);
1197 	}
1198 
1199 	if (Game_Battle::battle_test.enabled) {
1200 		Main_Data::game_party->SetupBattleTest();
1201 	}
1202 
1203 	Scene::Push(Scene_Battle::Create(std::move(args)), true);
1204 }
1205 
GetEncoding()1206 std::string Player::GetEncoding() {
1207 	encoding = forced_encoding;
1208 
1209 	// command line > ini > detection > current locale
1210 	if (encoding.empty()) {
1211 		std::string ini = FileFinder::Game().FindFile(INI_NAME);
1212 		auto ini_stream = FileFinder::Game().OpenInputStream(ini);
1213 		if (ini_stream) {
1214 			encoding = lcf::ReaderUtil::GetEncoding(ini_stream);
1215 		}
1216 	}
1217 
1218 	if (encoding.empty() || encoding == "auto") {
1219 		encoding = "";
1220 
1221 		std::string ldb = FileFinder::Game().FindFile(fileext_map.MakeFilename(RPG_RT_PREFIX, SUFFIX_LDB));
1222 		auto ldb_stream = FileFinder::Game().OpenInputStream(ldb);
1223 		if (ldb_stream) {
1224 			auto db = lcf::LDB_Reader::Load(ldb_stream);
1225 			if (db) {
1226 				std::vector<std::string> encodings = lcf::ReaderUtil::DetectEncodings(*db);
1227 
1228 #ifndef EMSCRIPTEN
1229 				for (std::string &enc : encodings) {
1230 					// Heuristic: Check title graphic, system graphic, cursor SE, title BGM
1231 					// Pure ASCII is skipped as it provides no added value
1232 					escape_symbol = lcf::ReaderUtil::Recode("\\", enc);
1233 					if (escape_symbol.empty()) {
1234 						// Bad encoding
1235 						Output::Debug("Bad encoding: {}. Trying next.", enc);
1236 						continue;
1237 					}
1238 					escape_char = Utils::DecodeUTF32(Player::escape_symbol).front();
1239 
1240 					const auto& title_name = db->system.title_name;
1241 					const auto& system_name = db->system.system_name;
1242 					const auto& cursor_se = db->system.cursor_se.name;
1243 					const auto& title_music = db->system.title_music.name;
1244 					int check_max = 0;
1245 					int check_okay = 0;
1246 
1247 					if (db->system.show_title && !Utils::StringIsAscii(title_name)) {
1248 						++check_max;
1249 						check_okay += FileFinder::FindImage("Title", lcf::ReaderUtil::Recode(title_name, enc)).empty() ? 0 : 1;
1250 					}
1251 
1252 					if (!Utils::StringIsAscii(system_name)) {
1253 						++check_max;
1254 						check_okay += FileFinder::FindImage("System", lcf::ReaderUtil::Recode(system_name, enc)).empty() ? 0 : 1;
1255 					}
1256 
1257 					if (!Utils::StringIsAscii(cursor_se)) {
1258 						++check_max;
1259 						check_okay += FileFinder::FindSound(lcf::ReaderUtil::Recode(cursor_se, enc)).empty() ? 0 : 1;
1260 					}
1261 
1262 					if (db->system.show_title && !Utils::StringIsAscii(title_music)) {
1263 						++check_max;
1264 						check_okay += FileFinder::FindMusic(lcf::ReaderUtil::Recode(title_music, enc)).empty() ? 0 : 1;
1265 					}
1266 
1267 					if (check_max == check_okay) {
1268 						// Looks like a good encoding
1269 						encoding = enc;
1270 						break;
1271 					} else {
1272 						Output::Debug("Detected encoding: {}. Files not found ({}/{}). Trying next.", enc, check_okay, check_max);
1273 					}
1274 				}
1275 #endif
1276 				if (!encodings.empty() && encoding.empty()) {
1277 					// No encoding found that matches the files, maybe RTP missing.
1278 					// Use the first one instead
1279 					encoding = encodings.front();
1280 				}
1281 			}
1282 		}
1283 
1284 		escape_symbol = "";
1285 		escape_char = 0;
1286 
1287 		if (!encoding.empty()) {
1288 			Output::Debug("Detected encoding: {}", encoding);
1289 		} else {
1290 			Output::Debug("Encoding not detected");
1291 			encoding = lcf::ReaderUtil::GetLocaleEncoding();
1292 		}
1293 	}
1294 
1295 	return encoding;
1296 }
1297 
PrintVersion()1298 void Player::PrintVersion() {
1299 	std::string additional(PLAYER_ADDTL);
1300 	std::stringstream version;
1301 
1302 	version << PLAYER_VERSION;
1303 
1304 	if (!additional.empty())
1305 		version << " " << additional;
1306 
1307 	std::cout << "EasyRPG Player " << version.str() << std::endl;
1308 }
1309 
PrintUsage()1310 void Player::PrintUsage() {
1311 	std::cout <<
1312 R"(EasyRPG Player - An open source interpreter for RPG Maker 2000/2003 games.
1313 Options:
1314       --battle-test N      Start a battle test with monster party N.
1315       --disable-audio      Disable audio (in case you prefer your own music).
1316       --disable-rtp        Disable support for the Runtime Package (RTP).
1317       --encoding N         Instead of auto detecting the encoding or using
1318                            the one in RPG_RT.ini, the encoding N is used.
1319                            Use "auto" for automatic detection.
1320       --engine ENGINE      Disable auto detection of the simulated engine.
1321                            Possible options:
1322                             rpg2k      - RPG Maker 2000 engine (v1.00 - v1.10)
1323                             rpg2kv150  - RPG Maker 2000 engine (v1.50 - v1.51)
1324                             rpg2ke     - RPG Maker 2000 (English release) engine (v1.61)
1325                             rpg2k3     - RPG Maker 2003 engine (v1.00 - v1.04)
1326                             rpg2k3v105 - RPG Maker 2003 engine (v1.05 - v1.09a)
1327                             rpg2k3e    - RPG Maker 2003 (English release) engine
1328       --fullscreen         Start in fullscreen mode.
1329       --show-fps           Enable frames per second counter.
1330       --fps-render-window  Render the frames per second counter in windowed mode.
1331       --fps-limit          Set a custom frames per second limit. The default is 60 FPS.
1332                            Set to 0 to run with unlimited frames per second.
1333                            This option is not supported on all platforms.
1334       --no-vsync           Disable vertical sync and use fps-limit. Even without
1335                            this option, vsync may not be supported on all platforms.
1336       --enable-mouse       Use mouse click for decision and scroll wheel for lists
1337       --enable-touch       Use one/two finger tap for decision/cancel
1338       --hide-title         Hide the title background image and center the
1339                            command menu.
1340       --load-game-id N     Skip the title scene and load SaveN.lsd
1341                            (N is padded to two digits).
1342       --new-game           Skip the title scene and start a new game directly.
1343       --project-path PATH  Instead of using the working directory the game in
1344                            PATH is used.
1345       --record-input PATH  Record all button input to a log file at PATH.
1346       --replay-input PATH  Replays button presses from an input log generated by
1347                            --record-input.
1348       --save-path PATH     Instead of storing save files in the game directory
1349                            they are stored in PATH. The directory must exist.
1350                            When using the game browser all games will share
1351                            the same save directory!
1352       --seed N             Seeds the random number generator with N.
1353       --start-map-id N     Overwrite the map used for new games and use.
1354                            MapN.lmu instead (N is padded to four digits).
1355                            Incompatible with --load-game-id.
1356       --autobattle-algo A  Which AutoBattle algorithm to use.
1357                            Possible options:
1358                             RPG_RT     - The default RPG_RT compatible algo, including RPG_RT bugs
1359                             RPG_RT+    - The default RPG_RT compatible algo, with bug fixes
1360                             ATTACK     - RPG_RT+ but only physical attacks, no skills
1361       --enemyai-algo A     Which EnemyAI algorithm to use.
1362                            Possible options:
1363                             RPG_RT     - The default RPG_RT compatible algo, including RPG_RT bugs
1364                             RPG_RT+    - The default RPG_RT compatible algo, with bug fixes
1365       --patch PATCH...     Force emulation of engine patches, disabling auto-detection.
1366                            Possible options:
1367                             none       - Disable all patches
1368                             dynrpg     - DynRPG patch by Cherry
1369                             maniac     - Maniac Patch by BingShan
1370       --start-position X Y Overwrite the party start position and move the
1371                            party to position (X, Y).
1372                            Incompatible with --load-game-id.
1373       --start-party A B... Overwrite the starting party members with the actors
1374                            with IDs A, B, C...
1375                            Incompatible with --load-game-id.
1376       --test-play          Enable TestPlay mode.
1377       --window             Start in window mode.
1378   -v, --version            Display program version and exit.
1379   -h, --help               Display this help and exit.
1380 
1381 For compatibility with the legacy RPG Maker runtime the following arguments
1382 are supported:
1383       BattleTest N         Same as --battle-test. When N is not a valid number
1384                            the 4th argument is used as the party id.
1385       HideTitle            Same as --hide-title.
1386       TestPlay             Same as --test-play.
1387       Window               Same as --window.
1388 
1389 Game related parameters (e.g. new-game and load-game-id) don't work correctly when the
1390 startup directory does not contain a valid game (and the game browser loads)
1391 
1392 Alex, EV0001 and the EasyRPG authors wish you a lot of fun!)" << std::endl;
1393 }
1394 
IsCP932()1395 bool Player::IsCP932() {
1396 	return (encoding == "ibm-943_P15A-2003" || encoding == "932");
1397 }
1398 
IsCP949()1399 bool Player::IsCP949() {
1400 	return (encoding == "windows-949-2000" ||
1401 			encoding == "949");
1402 }
1403 
IsBig5()1404 bool Player::IsBig5() {
1405 	return (encoding == "Big5" || encoding == "950");
1406 }
1407 
IsCP936()1408 bool Player::IsCP936() {
1409 	return (encoding == "windows-936-2000" ||
1410 			encoding == "936");
1411 }
1412 
IsCJK()1413 bool Player::IsCJK() {
1414 	return (IsCP932() || IsCP949() || IsBig5() || IsCP936());
1415 }
1416 
IsCP1251()1417 bool Player::IsCP1251() {
1418 	return (encoding == "ibm-5347_P100-1998" ||
1419 			encoding == "windows-1251" || encoding == "1251");
1420 }
1421 
EngineVersion()1422 int Player::EngineVersion() {
1423 	if (IsRPG2k3()) return 2003;
1424 	if (IsRPG2k()) return 2000;
1425 	return 0;
1426 }
1427 
GetEngineVersion()1428 std::string Player::GetEngineVersion() {
1429 	if (EngineVersion() > 0) return std::to_string(EngineVersion());
1430 	return std::string();
1431 }
1432 
1433