1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 //
24 // Engine initialization
25 //
26 
27 #include "ags/shared/core/platform.h"
28 #include "ags/lib/allegro.h" // allegro_install and _exit
29 #include "ags/engine/ac/asset_helper.h"
30 #include "ags/shared/ac/common.h"
31 #include "ags/engine/ac/character.h"
32 #include "ags/engine/ac/character_extras.h"
33 #include "ags/shared/ac/character_info.h"
34 #include "ags/engine/ac/draw.h"
35 #include "ags/engine/ac/game.h"
36 #include "ags/engine/ac/game_setup.h"
37 #include "ags/shared/ac/game_setup_struct.h"
38 #include "ags/engine/ac/game_state.h"
39 #include "ags/engine/ac/global_character.h"
40 #include "ags/engine/ac/global_game.h"
41 #include "ags/engine/ac/gui.h"
42 #include "ags/engine/ac/lip_sync.h"
43 #include "ags/engine/ac/object_cache.h"
44 #include "ags/engine/ac/path_helper.h"
45 #include "ags/engine/ac/route_finder.h"
46 #include "ags/engine/ac/sys_events.h"
47 #include "ags/engine/ac/room_status.h"
48 #include "ags/engine/ac/speech.h"
49 #include "ags/shared/ac/sprite_cache.h"
50 #include "ags/engine/ac/translation.h"
51 #include "ags/engine/ac/view_frame.h"
52 #include "ags/engine/ac/dynobj/script_object.h"
53 #include "ags/engine/ac/dynobj/script_system.h"
54 #include "ags/shared/core/asset_manager.h"
55 #include "ags/engine/debugging/debug_log.h"
56 #include "ags/engine/debugging/debugger.h"
57 #include "ags/shared/debugging/out.h"
58 #include "ags/engine/device/mouse_w32.h"
59 #include "ags/shared/font/ags_font_renderer.h"
60 #include "ags/shared/font/fonts.h"
61 #include "ags/shared/gfx/image.h"
62 #include "ags/engine/gfx/graphics_driver.h"
63 #include "ags/engine/gfx/gfx_driver_factory.h"
64 #include "ags/engine/gfx/ddb.h"
65 #include "ags/engine/main/config.h"
66 #include "ags/engine/main/game_file.h"
67 #include "ags/engine/main/game_start.h"
68 #include "ags/engine/main/engine.h"
69 #include "ags/engine/main/engine_setup.h"
70 #include "ags/engine/main/graphics_mode.h"
71 #include "ags/engine/main/main.h"
72 #include "ags/engine/platform/base/sys_main.h"
73 #include "ags/engine/platform/base/ags_platform_driver.h"
74 #include "ags/shared/util/directory.h"
75 #include "ags/shared/util/error.h"
76 #include "ags/shared/util/path.h"
77 #include "ags/shared/util/string_utils.h"
78 #include "ags/ags.h"
79 #include "ags/globals.h"
80 
81 namespace AGS3 {
82 
83 using namespace AGS::Shared;
84 using namespace AGS::Engine;
85 
engine_init_backend()86 bool engine_init_backend() {
87 	_G(our_eip) = -199;
88 	// Initialize SDL
89 	Debug::Printf(kDbgMsg_Info, "Initializing backend libs");
90 	if (sys_main_init()) {
91 		const char *user_hint = _G(platform)->GetBackendFailUserHint();
92 		_G(platform)->DisplayAlert("Unable to initialize SDL library.\n\n%s",
93 		                           user_hint);
94 		return false;
95 	}
96 
97 	// Initialize stripped allegro library
98 	if (install_allegro()) {
99 		_G(platform)->DisplayAlert("Internal error: unable to initialize stripped Allegro 4 library.");
100 		return false;
101 	}
102 
103 	_G(platform)->PostBackendInit();
104 	return true;
105 }
106 
winclosehook()107 void winclosehook() {
108 	_G(want_exit) = 1;
109 	_G(abort_engine) = 1;
110 	_G(check_dynamic_sprites_at_exit) = 0;
111 }
112 
engine_setup_window()113 void engine_setup_window() {
114 	Debug::Printf(kDbgMsg_Info, "Setting up window");
115 
116 	_G(our_eip) = -198;
117 	sys_window_set_title(_GP(game).gamename);
118 	sys_window_set_icon();
119 	sys_evt_set_quit_callback(winclosehook);
120 	_G(our_eip) = -197;
121 }
122 
123 // Fills map with game settings, to e.g. let setup application(s)
124 // display correct properties to the user
fill_game_properties(StringOrderMap & map)125 static void fill_game_properties(StringOrderMap &map) {
126 	map["title"] = _GP(game).gamename;
127 	map["guid"] = _GP(game).guid;
128 	map["legacy_uniqueid"] = StrUtil::IntToString(_GP(game).uniqueid);
129 	map["legacy_resolution"] = StrUtil::IntToString(_GP(game).GetResolutionType());
130 	map["legacy_letterbox"] = StrUtil::IntToString(_GP(game).options[OPT_LETTERBOX]);
131 	map["resolution_width"] = StrUtil::IntToString(_GP(game).GetDefaultRes().Width);
132 	map["resolution_height"] = StrUtil::IntToString(_GP(game).GetDefaultRes().Height);
133 	map["resolution_bpp"] = StrUtil::IntToString(_GP(game).GetColorDepth());
134 	map["render_at_screenres"] = StrUtil::IntToString(
135 	                                 _GP(game).options[OPT_RENDERATSCREENRES] == kRenderAtScreenRes_UserDefined ? -1 :
136 	                                 (_GP(game).options[OPT_RENDERATSCREENRES] == kRenderAtScreenRes_Enabled ? 1 : 0));
137 }
138 
139 // Starts up setup application, if capable.
140 // Returns TRUE if should continue running the game, otherwise FALSE.
engine_run_setup(const ConfigTree & cfg,int & app_res)141 bool engine_run_setup(const ConfigTree &cfg, int &app_res) {
142 	app_res = EXIT_NORMAL;
143 #if AGS_PLATFORM_OS_WINDOWS
144 	{
145 		Debug::Printf(kDbgMsg_Info, "Running Setup");
146 
147 		ConfigTree cfg_with_meta = cfg;
148 		fill_game_properties(cfg_with_meta["gameproperties"]);
149 		ConfigTree cfg_out;
150 		SetupReturnValue res = _G(platform)->RunSetup(cfg_with_meta, cfg_out);
151 		if (res != kSetup_Cancel) {
152 			String cfg_file = PreparePathForWriting(GetGameUserConfigDir(), DefaultConfigFileName);
153 			if (cfg_file.IsEmpty()) {
154 				_G(platform)->DisplayAlert("Unable to write into directory '%s'.\n%s",
155 				                           GetGameUserConfigDir().FullDir.GetCStr(), _G(platform)->GetDiskWriteAccessTroubleshootingText());
156 			} else if (!IniUtil::Merge(cfg_file, cfg_out)) {
157 				_G(platform)->DisplayAlert("Unable to write to the configuration file (error code 0x%08X).\n%s",
158 				                           _G(platform)->GetLastSystemError(), _G(platform)->GetDiskWriteAccessTroubleshootingText());
159 			}
160 		}
161 		if (res != kSetup_RunGame)
162 			return false;
163 
164 		// TODO: investigate if the full program restart may (should) be avoided
165 
166 		// Just re-reading the config file seems to cause a caching
167 		// problem on Win9x, so let's restart the process.
168 		sys_main_shutdown();
169 		allegro_exit();
170 		char quotedpath[MAX_PATH];
171 		snprintf(quotedpath, MAX_PATH, "\"%s\"", _G(appPath).GetCStr());
172 		_spawnl(_P_OVERLAY, _G(appPath), quotedpath, NULL);
173 	}
174 #endif
175 	return true;
176 }
177 
engine_force_window()178 void engine_force_window() {
179 	// Force to run in a window, override the config file
180 	// TODO: actually overwrite config tree instead
181 	if (_G(force_window) == 1) {
182 		_GP(usetup).Screen.DisplayMode.Windowed = true;
183 		_GP(usetup).Screen.DisplayMode.ScreenSize.SizeDef = kScreenDef_ByGameScaling;
184 	} else if (_G(force_window) == 2) {
185 		_GP(usetup).Screen.DisplayMode.Windowed = false;
186 		_GP(usetup).Screen.DisplayMode.ScreenSize.SizeDef = kScreenDef_MaxDisplay;
187 	}
188 }
189 
190 // Scans given directory for the AGS game config. If such config exists
191 // and it contains data file name, then returns one.
192 // Otherwise returns empty string.
find_game_data_in_config(const String & path)193 static String find_game_data_in_config(const String &path) {
194 	// First look for config
195 	ConfigTree cfg;
196 	String def_cfg_file = Path::ConcatPaths(path, DefaultConfigFileName);
197 	if (IniUtil::Read(def_cfg_file, cfg)) {
198 		String data_file = INIreadstring(cfg, "misc", "datafile");
199 		Debug::Printf("Found game config: %s", def_cfg_file.GetCStr());
200 		Debug::Printf(" Cfg: data file: %s", data_file.GetCStr());
201 		// Only accept if it's a relative path
202 		if (!data_file.IsEmpty() && Path::IsRelativePath(data_file))
203 			return Path::ConcatPaths(path, data_file);
204 	}
205 	return ""; // not found in config
206 }
207 
208 // Scans for game data in several common locations.
209 // When it does so, it first looks for game config file, which contains
210 // explicit directions to game data in its settings.
211 // If such config is not found, it scans same location for *any* game data instead.
search_for_game_data_file(String & was_searching_in)212 String search_for_game_data_file(String &was_searching_in) {
213 	Debug::Printf("Looking for the game data.\n Cwd: %s\n Path arg: %s",
214 	              Directory::GetCurrentDirectory().GetCStr(),
215 	              _G(cmdGameDataPath).GetCStr());
216 	// 1. From command line argument, which may be a directory or actual file
217 	if (!_G(cmdGameDataPath).IsEmpty()) {
218 		if (Path::IsFile(_G(cmdGameDataPath)))
219 			return _G(cmdGameDataPath); // this path is a file
220 		if (!Path::IsDirectory(_G(cmdGameDataPath)))
221 			return ""; // path is neither file nor directory
222 		was_searching_in = _G(cmdGameDataPath);
223 		Debug::Printf("Searching in (cmd arg): %s", was_searching_in.GetCStr());
224 		// first scan for config
225 		String data_path = find_game_data_in_config(_G(cmdGameDataPath));
226 		if (!data_path.IsEmpty())
227 			return data_path;
228 		// if not found in config, lookup for data in same dir
229 		return FindGameData(_G(cmdGameDataPath));
230 	}
231 
232 	// 2. Look in other known locations
233 	// 2.1. Look for attachment in the running executable
234 	if (!_G(appPath).IsEmpty() && Shared::AssetManager::IsDataFile(_G(appPath))) {
235 		Debug::Printf("Found game data embedded in executable");
236 		was_searching_in = Path::GetDirectoryPath(_G(appPath));
237 		return _G(appPath);
238 	}
239 
240 	// 2.2 Look in current working directory
241 	String cur_dir = Directory::GetCurrentDirectory();
242 	was_searching_in = cur_dir;
243 	Debug::Printf("Searching in (cwd): %s", was_searching_in.GetCStr());
244 	// first scan for config
245 	String data_path = find_game_data_in_config(cur_dir);
246 	if (!data_path.IsEmpty())
247 		return data_path;
248 	// if not found in config, lookup for data in same dir
249 	data_path = FindGameData(cur_dir);
250 	if (!data_path.IsEmpty())
251 		return data_path;
252 
253 	// 2.3 Look in executable's directory (if it's different from current dir)
254 	if (Path::ComparePaths(_G(appDirectory), cur_dir) == 0)
255 		return ""; // no luck
256 	was_searching_in = _G(appDirectory);
257 	Debug::Printf("Searching in (exe dir): %s", was_searching_in.GetCStr());
258 	// first scan for config
259 	data_path = find_game_data_in_config(_G(appDirectory));
260 	if (!data_path.IsEmpty())
261 		return data_path;
262 	// if not found in config, lookup for data in same dir
263 	return FindGameData(_G(appDirectory));
264 }
265 
engine_init_fonts()266 void engine_init_fonts() {
267 	Debug::Printf(kDbgMsg_Info, "Initializing TTF renderer");
268 
269 	init_font_renderer();
270 }
271 
engine_init_mouse()272 void engine_init_mouse() {
273 	int res = minstalled();
274 	if (res < 0)
275 		Debug::Printf(kDbgMsg_Info, "Initializing mouse: failed");
276 	else
277 		Debug::Printf(kDbgMsg_Info, "Initializing mouse: number of buttons reported is %d", res);
278 	_GP(mouse).SetSpeed(_GP(usetup).mouse_speed);
279 }
280 
engine_locate_speech_pak()281 void engine_locate_speech_pak() {
282 	_GP(play).want_speech = -2;
283 
284 	if (!_GP(usetup).no_speech_pack) {
285 		String speech_file = "speech.vox";
286 		String speech_filepath = find_assetlib(speech_file);
287 		if (!speech_filepath.IsEmpty()) {
288 			Debug::Printf("Initializing speech vox");
289 			if (_GP(AssetMgr)->AddLibrary(speech_filepath) != Shared::kAssetNoError) {
290 				_G(platform)->DisplayAlert("Unable to read voice pack, file could be corrupted or of unknown format.\nSpeech voice-over will be disabled.");
291 				return;
292 			}
293 
294 			Debug::Printf(kDbgMsg_Info, "Voice pack found and initialized.");
295 			_GP(play).want_speech = 1;
296 		} else if (Path::ComparePaths(_GP(ResPaths).DataDir, _GP(ResPaths).VoiceDir2) != 0) {
297 			// If we have custom voice directory set, we will enable voice-over even if speech.vox does not exist
298 			Debug::Printf(kDbgMsg_Info, "Voice pack was not found, but explicit voice directory is defined: enabling voice-over.");
299 			_GP(play).want_speech = 1;
300 		}
301 		_GP(ResPaths).SpeechPak.Name = speech_file;
302 		_GP(ResPaths).SpeechPak.Path = speech_filepath;
303 	}
304 }
305 
engine_locate_audio_pak()306 void engine_locate_audio_pak() {
307 	_GP(play).separate_music_lib = 0;
308 	String music_file = _GP(game).GetAudioVOXName();
309 	String music_filepath = find_assetlib(music_file);
310 	if (!music_filepath.IsEmpty()) {
311 		if (_GP(AssetMgr)->AddLibrary(music_filepath) == kAssetNoError) {
312 			Debug::Printf(kDbgMsg_Info, "%s found and initialized.", music_file.GetCStr());
313 			_GP(play).separate_music_lib = 1;
314 			_GP(ResPaths).AudioPak.Name = music_file;
315 			_GP(ResPaths).AudioPak.Path = music_filepath;
316 		} else {
317 			_G(platform)->DisplayAlert("Unable to initialize digital audio pack '%s', file could be corrupt or of unsupported format.",
318 			                           music_file.GetCStr());
319 		}
320 	} else if (Path::ComparePaths(_GP(ResPaths).DataDir, _GP(ResPaths).AudioDir2) != 0) {
321 		Debug::Printf(kDbgMsg_Info, "Audio pack was not found, but explicit audio directory is defined.");
322 	}
323 }
324 
325 // Assign asset locations to the AssetManager
engine_assign_assetpaths()326 void engine_assign_assetpaths() {
327 	_GP(AssetMgr)->AddLibrary(_GP(ResPaths).GamePak.Path, ",audio"); // main pack may have audio bundled too
328 	// The asset filters are currently a workaround for limiting search to certain locations;
329 	// this is both an optimization and to prevent unexpected behavior.
330 	// - empty filter is for regular files
331 	// audio - audio clips
332 	// voice - voice-over clips
333 	// NOTE: we add extra optional directories first because they should have higher priority
334 	// TODO: maybe change AssetManager library order to stack-like later (last added = top priority)?
335 	if (!_GP(ResPaths).DataDir2.IsEmpty() && Path::ComparePaths(_GP(ResPaths).DataDir2, _GP(ResPaths).DataDir) != 0)
336 		_GP(AssetMgr)->AddLibrary(_GP(ResPaths).DataDir2, ",audio,voice"); // dir may have anything
337 	if (!_GP(ResPaths).AudioDir2.IsEmpty() && Path::ComparePaths(_GP(ResPaths).AudioDir2, _GP(ResPaths).DataDir) != 0)
338 		_GP(AssetMgr)->AddLibrary(_GP(ResPaths).AudioDir2, "audio");
339 	if (!_GP(ResPaths).VoiceDir2.IsEmpty() && Path::ComparePaths(_GP(ResPaths).VoiceDir2, _GP(ResPaths).DataDir) != 0)
340 		_GP(AssetMgr)->AddLibrary(_GP(ResPaths).VoiceDir2, "voice");
341 
342 	_GP(AssetMgr)->AddLibrary(_GP(ResPaths).DataDir, ",audio,voice"); // dir may have anything
343 	if (!_GP(ResPaths).AudioPak.Path.IsEmpty())
344 		_GP(AssetMgr)->AddLibrary(_GP(ResPaths).AudioPak.Path, "audio");
345 	if (!_GP(ResPaths).SpeechPak.Path.IsEmpty())
346 		_GP(AssetMgr)->AddLibrary(_GP(ResPaths).SpeechPak.Path, "voice");
347 }
348 
engine_init_keyboard()349 void engine_init_keyboard() {
350 	/* do nothing */
351 }
352 
engine_init_timer()353 void engine_init_timer() {
354 	Debug::Printf(kDbgMsg_Info, "Install timer");
355 
356 	skipMissedTicks();
357 }
358 
engine_init_audio()359 void engine_init_audio() {
360 #if !AGS_PLATFORM_SCUMMVM
361 	if (_GP(usetup).audio_backend != 0) {
362 		Debug::Printf("Initializing audio");
363 		audio_core_init(); // audio core system
364 	}
365 #endif
366 
367 	_G(our_eip) = -181;
368 
369 	if (_GP(usetup).audio_backend == 0) {
370 		// all audio is disabled
371 		// and the voice mode should not go to Voice Only
372 		_GP(play).want_speech = -2;
373 		_GP(play).separate_music_lib = 0;
374 	}
375 }
376 
engine_init_debug()377 void engine_init_debug() {
378 	if ((_G(debug_flags) & (~DBG_DEBUGMODE)) > 0) {
379 		_G(platform)->DisplayAlert("Engine debugging enabled.\n"
380 		                           "\nNOTE: You have selected to enable one or more engine debugging options.\n"
381 		                           "These options cause many parts of the game to behave abnormally, and you\n"
382 		                           "may not see the game as you are used to it. The point is to test whether\n"
383 		                           "the engine passes a point where it is crashing on you normally.\n"
384 		                           "[Debug flags enabled: 0x%02X]", _G(debug_flags));
385 	}
386 }
387 
engine_init_rand()388 void engine_init_rand() {
389 	_GP(play).randseed = g_system->getMillis();
390 	::AGS::g_vm->setRandomNumberSeed(_GP(play).randseed);
391 }
392 
engine_init_pathfinder()393 void engine_init_pathfinder() {
394 	init_pathfinder(_G(loaded_game_file_version));
395 }
396 
engine_pre_init_gfx()397 void engine_pre_init_gfx() {
398 	//Debug::Printf("Initialize gfx");
399 
400 	//_G(platform)->InitialiseAbufAtStartup();
401 }
402 
engine_load_game_data()403 int engine_load_game_data() {
404 	Debug::Printf("Load game data");
405 	_G(our_eip) = -17;
406 	HError err = load_game_file();
407 	if (!err) {
408 		_G(proper_exit) = 1;
409 		display_game_file_error(err);
410 		return EXIT_ERROR;
411 	}
412 	return 0;
413 }
414 
engine_check_register_game()415 int engine_check_register_game() {
416 	if (_G(justRegisterGame)) {
417 		_G(platform)->RegisterGameWithGameExplorer();
418 		_G(proper_exit) = 1;
419 		return EXIT_NORMAL;
420 	}
421 
422 	if (_G(justUnRegisterGame)) {
423 		_G(platform)->UnRegisterGameWithGameExplorer();
424 		_G(proper_exit) = 1;
425 		return EXIT_NORMAL;
426 	}
427 
428 	return 0;
429 }
430 
431 // Setup paths and directories that may be affected by user configuration
engine_init_user_directories()432 void engine_init_user_directories() {
433 	if (!_GP(usetup).user_data_dir.IsEmpty())
434 		Debug::Printf(kDbgMsg_Info, "User data directory: %s", _GP(usetup).user_data_dir.GetCStr());
435 	if (!_GP(usetup).shared_data_dir.IsEmpty())
436 		Debug::Printf(kDbgMsg_Info, "Shared data directory: %s", _GP(usetup).shared_data_dir.GetCStr());
437 
438 	// if end-user specified custom save path, use it
439 	bool res = false;
440 	if (!_GP(usetup).user_data_dir.IsEmpty()) {
441 		res = SetCustomSaveParent(_GP(usetup).user_data_dir);
442 		if (!res) {
443 			Debug::Printf(kDbgMsg_Warn, "WARNING: custom user save path failed, using default system paths");
444 			res = false;
445 		}
446 	}
447 	// if there is no custom path, or if custom path failed, use default system path
448 	if (!res) {
449 		SetSaveGameDirectoryPath(Path::ConcatPaths(UserSavedgamesRootToken, _GP(game).saveGameFolderName));
450 	}
451 }
452 
453 #if AGS_PLATFORM_OS_ANDROID
454 extern char android_base_directory[256];
455 #endif // AGS_PLATFORM_OS_ANDROID
456 
457 // TODO: remake/remove this nonsense
check_write_access()458 int check_write_access() {
459 #if AGS_PLATFORM_SCUMMVM
460 	return true;
461 #else
462 	if (_G(platform)->GetDiskFreeSpaceMB() < 2)
463 		return 0;
464 
465 	_G(our_eip) = -1895;
466 
467 	// The Save Game Dir is the only place that we should write to
468 	String svg_dir = get_save_game_directory();
469 	String tempPath = String::FromFormat("%s""tmptest.tmp", svg_dir.GetCStr());
470 	Stream *temp_s = Shared::File::CreateFile(tempPath);
471 	if (!temp_s)
472 		// TODO: The fallback should be done on all platforms, and there's
473 		// already similar procedure found in SetSaveGameDirectoryPath.
474 		// If Android has extra dirs to fallback to, they should be provided
475 		// by platform driver's method, not right here!
476 #if AGS_PLATFORM_OS_ANDROID
477 	{
478 		put_backslash(android_base_directory);
479 		tempPath.Format("%s""tmptest.tmp", android_base_directory);
480 		temp_s = Shared::File::CreateFile(tempPath);
481 		if (temp_s == NULL) return 0;
482 		else SetCustomSaveParent(android_base_directory);
483 	}
484 #else
485 		return 0;
486 #endif // AGS_PLATFORM_OS_ANDROID
487 
488 	_G(our_eip) = -1896;
489 
490 	temp_s->Write("just to test the drive free space", 30);
491 	delete temp_s;
492 
493 	_G(our_eip) = -1897;
494 
495 	if (File::DeleteFile(tempPath))
496 		return 0;
497 
498 	return 1;
499 #endif
500 }
501 
engine_check_disk_space()502 int engine_check_disk_space() {
503 	Debug::Printf(kDbgMsg_Info, "Checking for disk space");
504 
505 	if (check_write_access() == 0) {
506 		_G(platform)->DisplayAlert("Unable to write in the savegame directory.\n%s", _G(platform)->GetDiskWriteAccessTroubleshootingText());
507 		_G(proper_exit) = 1;
508 		return EXIT_ERROR;
509 	}
510 
511 	return 0;
512 }
513 
engine_check_font_was_loaded()514 int engine_check_font_was_loaded() {
515 	if (!font_first_renderer_loaded()) {
516 		_G(platform)->DisplayAlert("No game fonts found. At least one font is required to run the game.");
517 		_G(proper_exit) = 1;
518 		return EXIT_ERROR;
519 	}
520 
521 	return 0;
522 }
523 
524 // Do the preload graphic if available
show_preload()525 void show_preload() {
526 	RGB temppal[256];
527 	Bitmap *splashsc = BitmapHelper::CreateRawBitmapOwner(load_pcx("preload.pcx", temppal));
528 	if (splashsc != nullptr) {
529 		Debug::Printf("Displaying preload image");
530 		if (splashsc->GetColorDepth() == 8)
531 			set_palette_range(temppal, 0, 255, 0);
532 		if (_G(gfxDriver)->UsesMemoryBackBuffer())
533 			_G(gfxDriver)->GetMemoryBackBuffer()->Clear();
534 
535 		const Rect &view = _GP(play).GetMainViewport();
536 		Bitmap *tsc = BitmapHelper::CreateBitmapCopy(splashsc, _GP(game).GetColorDepth());
537 		if (!_G(gfxDriver)->HasAcceleratedTransform() && view.GetSize() != tsc->GetSize()) {
538 			Bitmap *stretched = new Bitmap(view.GetWidth(), view.GetHeight(), tsc->GetColorDepth());
539 			stretched->StretchBlt(tsc, RectWH(0, 0, view.GetWidth(), view.GetHeight()));
540 			delete tsc;
541 			tsc = stretched;
542 		}
543 		IDriverDependantBitmap *ddb = _G(gfxDriver)->CreateDDBFromBitmap(tsc, false, true);
544 		ddb->SetStretch(view.GetWidth(), view.GetHeight());
545 		_G(gfxDriver)->ClearDrawLists();
546 		_G(gfxDriver)->DrawSprite(0, 0, ddb);
547 		render_to_screen();
548 		_G(gfxDriver)->DestroyDDB(ddb);
549 		delete splashsc;
550 		delete tsc;
551 		_G(platform)->Delay(500);
552 	}
553 }
554 
engine_init_sprites()555 int engine_init_sprites() {
556 	Debug::Printf(kDbgMsg_Info, "Initialize sprites");
557 
558 	HError err = _GP(spriteset).InitFile(SpriteFile::DefaultSpriteFileName, SpriteFile::DefaultSpriteIndexName);
559 	if (!err) {
560 		sys_main_shutdown();
561 		allegro_exit();
562 		_G(proper_exit) = 1;
563 		_G(platform)->DisplayAlert("Could not load sprite set file %s\n%s",
564 		                           SpriteFile::DefaultSpriteFileName,
565 		                           err->FullMessage().GetCStr());
566 		return EXIT_ERROR;
567 	}
568 
569 	return 0;
570 }
571 
engine_init_game_settings()572 void engine_init_game_settings() {
573 	_G(our_eip) = -7;
574 	Debug::Printf("Initialize game settings");
575 
576 	// Setup a text encoding mode depending on the game data hint
577 	set_uformat(U_ASCII);
578 
579 	int ee;
580 
581 	for (ee = 0; ee < MAX_ROOM_OBJECTS + _GP(game).numcharacters; ee++)
582 		_G(actsps)[ee] = nullptr;
583 
584 	for (ee = 0; ee < 256; ee++) {
585 		if (_GP(game).paluses[ee] != PAL_BACKGROUND)
586 			_G(palette)[ee] = _GP(game).defpal[ee];
587 	}
588 
589 	for (ee = 0; ee < _GP(game).numcursors; ee++) {
590 		// The cursor graphics are assigned to mousecurs[] and so cannot
591 		// be removed from memory
592 		if (_GP(game).mcurs[ee].pic >= 0)
593 			_GP(spriteset).Precache(_GP(game).mcurs[ee].pic);
594 
595 		// just in case they typed an invalid view number in the editor
596 		if (_GP(game).mcurs[ee].view >= _GP(game).numviews)
597 			_GP(game).mcurs[ee].view = -1;
598 
599 		if (_GP(game).mcurs[ee].view >= 0)
600 			precache_view(_GP(game).mcurs[ee].view);
601 	}
602 	// may as well preload the character gfx
603 	if (_G(playerchar)->view >= 0)
604 		precache_view(_G(playerchar)->view);
605 
606 	for (ee = 0; ee < MAX_ROOM_OBJECTS; ee++)
607 		_G(objcache)[ee].image = nullptr;
608 
609 	/*  dummygui.guiId = -1;
610 	dummyguicontrol.guin = -1;
611 	dummyguicontrol.objn = -1;*/
612 
613 	_G(our_eip) = -6;
614 	//  _GP(game).chars[0].talkview=4;
615 	//init_language_text(_GP(game).langcodes[0]);
616 
617 	for (ee = 0; ee < MAX_ROOM_OBJECTS; ee++) {
618 		_G(scrObj)[ee].id = ee;
619 		// 64 bit: Using the id instead
620 		// _G(scrObj)[ee].obj = NULL;
621 	}
622 
623 	for (ee = 0; ee < _GP(game).numcharacters; ee++) {
624 		memset(&_GP(game).chars[ee].inv[0], 0, MAX_INV * sizeof(short));
625 		_GP(game).chars[ee].activeinv = -1;
626 		_GP(game).chars[ee].following = -1;
627 		_GP(game).chars[ee].followinfo = 97 | (10 << 8);
628 		_GP(game).chars[ee].idletime = 20; // can be overridden later with SetIdle or summink
629 		_GP(game).chars[ee].idleleft = _GP(game).chars[ee].idletime;
630 		_GP(game).chars[ee].transparency = 0;
631 		_GP(game).chars[ee].baseline = -1;
632 		_GP(game).chars[ee].walkwaitcounter = 0;
633 		_GP(game).chars[ee].z = 0;
634 		_G(charextra)[ee].xwas = INVALID_X;
635 		_G(charextra)[ee].zoom = 100;
636 		if (_GP(game).chars[ee].view >= 0) {
637 			// set initial loop to 0
638 			_GP(game).chars[ee].loop = 0;
639 			// or to 1 if they don't have up/down frames
640 			if (_G(views)[_GP(game).chars[ee].view].loops[0].numFrames < 1)
641 				_GP(game).chars[ee].loop = 1;
642 		}
643 		_G(charextra)[ee].process_idle_this_time = 0;
644 		_G(charextra)[ee].invorder_count = 0;
645 		_G(charextra)[ee].slow_move_counter = 0;
646 		_G(charextra)[ee].animwait = 0;
647 	}
648 	// multiply up gui positions
649 	_G(guibg) = (Bitmap **)malloc(sizeof(Bitmap *) * _GP(game).numgui);
650 	_G(guibgbmp) = (IDriverDependantBitmap **)malloc(sizeof(IDriverDependantBitmap *) * _GP(game).numgui);
651 	for (ee = 0; ee < _GP(game).numgui; ee++) {
652 		_G(guibg)[ee] = nullptr;
653 		_G(guibgbmp)[ee] = nullptr;
654 	}
655 
656 	_G(our_eip) = -5;
657 	for (ee = 0; ee < _GP(game).numinvitems; ee++) {
658 		if (_GP(game).invinfo[ee].flags & IFLG_STARTWITH) _G(playerchar)->inv[ee] = 1;
659 		else _G(playerchar)->inv[ee] = 0;
660 	}
661 	_GP(play).score = 0;
662 	_GP(play).sierra_inv_color = 7;
663 	// copy the value set by the editor
664 	if (_GP(game).options[OPT_GLOBALTALKANIMSPD] >= 0) {
665 		_GP(play).talkanim_speed = _GP(game).options[OPT_GLOBALTALKANIMSPD];
666 		_GP(game).options[OPT_GLOBALTALKANIMSPD] = 1;
667 	} else {
668 		_GP(play).talkanim_speed = -_GP(game).options[OPT_GLOBALTALKANIMSPD] - 1;
669 		_GP(game).options[OPT_GLOBALTALKANIMSPD] = 0;
670 	}
671 	_GP(play).inv_item_wid = 40;
672 	_GP(play).inv_item_hit = 22;
673 	_GP(play).messagetime = -1;
674 	_GP(play).disabled_user_interface = 0;
675 	_GP(play).gscript_timer = -1;
676 	_GP(play).debug_mode = _GP(game).options[OPT_DEBUGMODE];
677 	_GP(play).inv_top = 0;
678 	_GP(play).inv_numdisp = 0;
679 	_GP(play).obsolete_inv_numorder = 0;
680 	_GP(play).text_speed = 15;
681 	_GP(play).text_min_display_time_ms = 1000;
682 	_GP(play).ignore_user_input_after_text_timeout_ms = 500;
683 	_GP(play).ClearIgnoreInput();
684 	_GP(play).lipsync_speed = 15;
685 	_GP(play).close_mouth_speech_time = 10;
686 	_GP(play).disable_antialiasing = 0;
687 	_GP(play).rtint_enabled = false;
688 	_GP(play).rtint_level = 0;
689 	_GP(play).rtint_light = 0;
690 	_GP(play).text_speed_modifier = 0;
691 	_GP(play).text_align = kHAlignLeft;
692 	// Make the default alignment to the right with right-to-left text
693 	if (_GP(game).options[OPT_RIGHTLEFTWRITE])
694 		_GP(play).text_align = kHAlignRight;
695 
696 	_GP(play).speech_bubble_width = get_fixed_pixel_size(100);
697 	_GP(play).bg_frame = 0;
698 	_GP(play).bg_frame_locked = 0;
699 	_GP(play).bg_anim_delay = 0;
700 	_GP(play).anim_background_speed = 0;
701 	_GP(play).silent_midi = 0;
702 	_GP(play).current_music_repeating = 0;
703 	_GP(play).skip_until_char_stops = -1;
704 	_GP(play).get_loc_name_last_time = -1;
705 	_GP(play).get_loc_name_save_cursor = -1;
706 	_GP(play).restore_cursor_mode_to = -1;
707 	_GP(play).restore_cursor_image_to = -1;
708 	_GP(play).ground_level_areas_disabled = 0;
709 	_GP(play).next_screen_transition = -1;
710 	_GP(play).temporarily_turned_off_character = -1;
711 	_GP(play).inv_backwards_compatibility = 0;
712 	_GP(play).gamma_adjustment = 100;
713 	_GP(play).do_once_tokens.resize(0);
714 	_GP(play).music_queue_size = 0;
715 	_GP(play).shakesc_length = 0;
716 	_GP(play).wait_counter = 0;
717 	_GP(play).SetWaitSkipResult(SKIP_NONE);
718 	_GP(play).key_skip_wait = SKIP_NONE;
719 	_GP(play).cur_music_number = -1;
720 	_GP(play).music_repeat = 1;
721 	_GP(play).music_master_volume = 100 + LegacyMusicMasterVolumeAdjustment;
722 	_GP(play).digital_master_volume = 100;
723 	_GP(play).screen_flipped = 0;
724 	_GP(play).cant_skip_speech = user_to_internal_skip_speech((SkipSpeechStyle)_GP(game).options[OPT_NOSKIPTEXT]);
725 	_GP(play).sound_volume = 255;
726 	_GP(play).speech_volume = 255;
727 	_GP(play).normal_font = 0;
728 	_GP(play).speech_font = 1;
729 	_GP(play).speech_text_shadow = 16;
730 	_GP(play).screen_tint = -1;
731 	_GP(play).bad_parsed_word[0] = 0;
732 	_GP(play).swap_portrait_side = 0;
733 	_GP(play).swap_portrait_lastchar = -1;
734 	_GP(play).swap_portrait_lastlastchar = -1;
735 	_GP(play).in_conversation = 0;
736 	_GP(play).skip_display = 3;
737 	_GP(play).no_multiloop_repeat = 0;
738 	_GP(play).in_cutscene = 0;
739 	_GP(play).fast_forward = 0;
740 	_GP(play).totalscore = _GP(game).totalscore;
741 	_GP(play).roomscript_finished = 0;
742 	_GP(play).no_textbg_when_voice = 0;
743 	_GP(play).max_dialogoption_width = get_fixed_pixel_size(180);
744 	_GP(play).no_hicolor_fadein = 0;
745 	_GP(play).bgspeech_game_speed = 0;
746 	_GP(play).bgspeech_stay_on_display = 0;
747 	_GP(play).unfactor_speech_from_textlength = 0;
748 	_GP(play).mp3_loop_before_end = 70;
749 	_GP(play).speech_music_drop = 60;
750 	_GP(play).room_changes = 0;
751 	_GP(play).check_interaction_only = 0;
752 	_GP(play).replay_hotkey_unused = -1;  // StartRecording: not supported.
753 	_GP(play).dialog_options_x = 0;
754 	_GP(play).dialog_options_y = 0;
755 	_GP(play).min_dialogoption_width = 0;
756 	_GP(play).disable_dialog_parser = 0;
757 	_GP(play).ambient_sounds_persist = 0;
758 	_GP(play).screen_is_faded_out = 0;
759 	_GP(play).player_on_region = 0;
760 	_GP(play).top_bar_backcolor = 8;
761 	_GP(play).top_bar_textcolor = 16;
762 	_GP(play).top_bar_bordercolor = 8;
763 	_GP(play).top_bar_borderwidth = 1;
764 	_GP(play).top_bar_ypos = 25;
765 	_GP(play).top_bar_font = -1;
766 	_GP(play).screenshot_width = 160;
767 	_GP(play).screenshot_height = 100;
768 	_GP(play).speech_text_align = kHAlignCenter;
769 	_GP(play).auto_use_walkto_points = 1;
770 	_GP(play).inventory_greys_out = 0;
771 	_GP(play).skip_speech_specific_key = 0;
772 	_GP(play).abort_key = 324;  // Alt+X
773 	_GP(play).fade_to_red = 0;
774 	_GP(play).fade_to_green = 0;
775 	_GP(play).fade_to_blue = 0;
776 	_GP(play).show_single_dialog_option = 0;
777 	_GP(play).keep_screen_during_instant_transition = 0;
778 	_GP(play).read_dialog_option_colour = -1;
779 	_GP(play).speech_portrait_placement = 0;
780 	_GP(play).speech_portrait_x = 0;
781 	_GP(play).speech_portrait_y = 0;
782 	_GP(play).speech_display_post_time_ms = 0;
783 	_GP(play).dialog_options_highlight_color = DIALOG_OPTIONS_HIGHLIGHT_COLOR_DEFAULT;
784 	_GP(play).speech_has_voice = false;
785 	_GP(play).speech_voice_blocking = false;
786 	_GP(play).speech_in_post_state = false;
787 	_GP(play).complete_overlay_on = 0;
788 	_GP(play).text_overlay_on = 0;
789 	_GP(play).narrator_speech = _GP(game).playercharacter;
790 	_GP(play).crossfading_out_channel = 0;
791 	_GP(play).speech_textwindow_gui = _GP(game).options[OPT_TWCUSTOM];
792 	if (_GP(play).speech_textwindow_gui == 0)
793 		_GP(play).speech_textwindow_gui = -1;
794 	strcpy(_GP(play).game_name, _GP(game).gamename);
795 	_GP(play).lastParserEntry[0] = 0;
796 	_GP(play).follow_change_room_timer = 150;
797 	for (ee = 0; ee < MAX_ROOM_BGFRAMES; ee++)
798 		_GP(play).raw_modified[ee] = 0;
799 	_GP(play).game_speed_modifier = 0;
800 	if (_G(debug_flags) & DBG_DEBUGMODE)
801 		_GP(play).debug_mode = 1;
802 	_G(gui_disabled_style) = convert_gui_disabled_style(_GP(game).options[OPT_DISABLEOFF]);
803 	_GP(play).shake_screen_yoff = 0;
804 
805 	memset(&_GP(play).walkable_areas_on[0], 1, MAX_WALK_AREAS + 1);
806 	memset(&_GP(play).script_timers[0], 0, MAX_TIMERS * sizeof(int));
807 	memset(&_GP(play).default_audio_type_volumes[0], -1, MAX_AUDIO_TYPES * sizeof(int));
808 
809 	// reset graphical script vars (they're still used by some games)
810 	for (ee = 0; ee < MAXGLOBALVARS; ee++)
811 		_GP(play).globalvars[ee] = 0;
812 
813 	for (ee = 0; ee < MAXGLOBALSTRINGS; ee++)
814 		_GP(play).globalstrings[ee][0] = 0;
815 
816 	if (!_GP(usetup).translation.IsEmpty())
817 		init_translation(_GP(usetup).translation, "", true);
818 
819 	update_invorder();
820 	_G(displayed_room) = -10;
821 
822 	_G(currentcursor) = 0;
823 	_G(our_eip) = -4;
824 	_G(mousey) = 100; // stop icon bar popping up
825 
826 	// We use same variable to read config and be used at runtime for now,
827 	// so update it here with regards to game design option
828 	_GP(usetup).RenderAtScreenRes =
829 	    (_GP(game).options[OPT_RENDERATSCREENRES] == kRenderAtScreenRes_UserDefined && _GP(usetup).RenderAtScreenRes) ||
830 	    _GP(game).options[OPT_RENDERATSCREENRES] == kRenderAtScreenRes_Enabled;
831 }
832 
engine_setup_scsystem_auxiliary()833 void engine_setup_scsystem_auxiliary() {
834 	// ScriptSystem::aci_version is only 10 chars long
835 	strncpy(_GP(scsystem).aci_version, _G(EngineVersion).LongString.GetCStr(), 10);
836 	if (_GP(usetup).override_script_os >= 0) {
837 		_GP(scsystem).os = _GP(usetup).override_script_os;
838 	} else {
839 		_GP(scsystem).os = _G(platform)->GetSystemOSID();
840 	}
841 }
842 
engine_prepare_to_start_game()843 void engine_prepare_to_start_game() {
844 	Debug::Printf("Prepare to start game");
845 
846 	engine_setup_scsystem_auxiliary();
847 
848 #if AGS_PLATFORM_OS_ANDROID
849 	if (psp_load_latest_savegame)
850 		selectLatestSavegame();
851 #endif
852 }
853 
854 // TODO: move to test unit
855 Bitmap *test_allegro_bitmap;
856 IDriverDependantBitmap *test_allegro_ddb;
allegro_bitmap_test_init()857 void allegro_bitmap_test_init() {
858 	test_allegro_bitmap = nullptr;
859 	// Switched the test off for now
860 	//test_allegro_bitmap = AllegroBitmap::CreateBitmap(320,200,32);
861 }
862 
863 // Define location of the game data either using direct settings or searching
864 // for the available resource packs in common locations.
865 // Returns two paths:
866 // - startup_dir: this is where engine found game config and/or data;
867 // - data_path: full path of the main data pack;
868 // data_path's directory (may or not be eq to startup_dir) should be considered data directory,
869 // and this is where engine look for all game data.
define_gamedata_location_checkall(String & data_path,String & startup_dir)870 HError define_gamedata_location_checkall(String &data_path, String &startup_dir) {
871 	// First try if they provided a startup option
872 	if (!_G(cmdGameDataPath).IsEmpty()) {
873 		// If not a valid path - bail out
874 		if (!Path::IsFileOrDir(_G(cmdGameDataPath)))
875 			return new Error(String::FromFormat("Provided game location is not a valid path.\n Cwd: %s\n Path: %s",
876 			                                    Directory::GetCurrentDirectory().GetCStr(),
877 			                                    _G(cmdGameDataPath).GetCStr()));
878 		// If it's a file, then keep it and proceed
879 		if (Path::IsFile(_G(cmdGameDataPath))) {
880 			Debug::Printf("Using provided game data path: %s", _G(cmdGameDataPath).GetCStr());
881 			startup_dir = Path::GetDirectoryPath(_G(cmdGameDataPath));
882 			data_path = _G(cmdGameDataPath);
883 			return HError::None();
884 		}
885 	}
886 
887 #if AGS_SEARCH_FOR_GAME_ON_LAUNCH
888 	// No direct filepath provided, search in common locations.
889 	data_path = search_for_game_data_file(startup_dir);
890 	if (data_path.IsEmpty()) {
891 		return new Error("Engine was not able to find any compatible game data.",
892 		                 startup_dir.IsEmpty() ? String() : String::FromFormat("Searched in: %s", startup_dir.GetCStr()));
893 	}
894 	data_path = Path::MakeAbsolutePath(data_path);
895 	Debug::Printf(kDbgMsg_Info, "Located game data pak: %s", data_path.GetCStr());
896 	return HError::None();
897 #else
898 	// No direct filepath provided, bail out.
899 	return new Error("The game location was not defined by startup settings.");
900 #endif
901 }
902 
903 // Define location of the game data
define_gamedata_location()904 bool define_gamedata_location() {
905 	String data_path, startup_dir;
906 	HError err = define_gamedata_location_checkall(data_path, startup_dir);
907 	if (!err) {
908 		_G(platform)->DisplayAlert("ERROR: Unable to determine game data.\n%s", err->FullMessage().GetCStr());
909 		main_print_help();
910 		return false;
911 	}
912 
913 	// On success: set all the necessary path and filename settings
914 	_GP(usetup).startup_dir = startup_dir;
915 	_GP(usetup).main_data_file = data_path;
916 	_GP(usetup).main_data_dir = Path::GetDirectoryPath(data_path);
917 	return true;
918 }
919 
920 // Find and preload main game data
engine_init_gamedata()921 bool engine_init_gamedata() {
922 	Debug::Printf(kDbgMsg_Info, "Initializing game data");
923 	// First, find data location
924 	if (!define_gamedata_location())
925 		return false;
926 
927 	// Try init game lib
928 	AssetError asset_err = _GP(AssetMgr)->AddLibrary(_GP(usetup).main_data_file);
929 	if (asset_err != kAssetNoError) {
930 		_G(platform)->DisplayAlert("ERROR: The game data is missing, is of unsupported format or corrupt.\nFile: '%s'", _GP(usetup).main_data_file.GetCStr());
931 		return false;
932 	}
933 
934 	// Pre-load game name and savegame folder names from data file
935 	// TODO: research if that is possible to avoid this step and just
936 	// read the full head game data at this point. This might require
937 	// further changes of the order of initialization.
938 	HError err = preload_game_data();
939 	if (!err) {
940 		display_game_file_error(err);
941 		return false;
942 	}
943 
944 	// Setup _GP(ResPaths), so that we know out main locations further
945 	_GP(ResPaths).GamePak.Path = _GP(usetup).main_data_file;
946 	_GP(ResPaths).GamePak.Name = Path::GetFilename(_GP(usetup).main_data_file);
947 	_GP(ResPaths).DataDir = _GP(usetup).install_dir.IsEmpty() ? _GP(usetup).main_data_dir : Path::MakeAbsolutePath(_GP(usetup).install_dir);
948 	_GP(ResPaths).DataDir2 = Path::MakeAbsolutePath(_GP(usetup).opt_data_dir);
949 	_GP(ResPaths).AudioDir2 = Path::MakeAbsolutePath(_GP(usetup).opt_audio_dir);
950 	_GP(ResPaths).VoiceDir2 = Path::MakeAbsolutePath(_GP(usetup).opt_voice_dir);
951 
952 	Debug::Printf(kDbgMsg_Info, "Startup directory: %s", _GP(usetup).startup_dir.GetCStr());
953 	Debug::Printf(kDbgMsg_Info, "Data directory: %s", _GP(ResPaths).DataDir.GetCStr());
954 	if (!_GP(ResPaths).DataDir2.IsEmpty())
955 		Debug::Printf(kDbgMsg_Info, "Opt data directory: %s", _GP(ResPaths).DataDir2.GetCStr());
956 	if (!_GP(ResPaths).AudioDir2.IsEmpty())
957 		Debug::Printf(kDbgMsg_Info, "Opt audio directory: %s", _GP(ResPaths).AudioDir2.GetCStr());
958 	if (!_GP(ResPaths).VoiceDir2.IsEmpty())
959 		Debug::Printf(kDbgMsg_Info, "Opt voice-over directory: %s", _GP(ResPaths).VoiceDir2.GetCStr());
960 	return true;
961 }
962 
engine_read_config(ConfigTree & cfg)963 void engine_read_config(ConfigTree &cfg) {
964 	if (!_GP(usetup).conf_path.IsEmpty()) {
965 		IniUtil::Read(_GP(usetup).conf_path, cfg);
966 		return;
967 	}
968 
969 	// Read default configuration file
970 	String def_cfg_file = find_default_cfg_file();
971 	IniUtil::Read(def_cfg_file, cfg);
972 
973 	// Disabled on Windows because people were afraid that this config could be mistakenly
974 	// created by some installer and screw up their games. Until any kind of solution is found.
975 	String user_global_cfg_file;
976 	// Read user global configuration file
977 	user_global_cfg_file = find_user_global_cfg_file();
978 	if (Path::ComparePaths(user_global_cfg_file, def_cfg_file) != 0)
979 		IniUtil::Read(user_global_cfg_file, cfg);
980 
981 	// Handle directive to search for the user config inside the game directory;
982 	// this option may come either from command line or default/global config.
983 	_GP(usetup).local_user_conf |= INIreadint(cfg, "misc", "localuserconf", 0) != 0;
984 	if (_GP(usetup).local_user_conf) {
985 		// Test if the file is writeable, if it is then both engine and setup
986 		// applications may actually use it fully as a user config, otherwise
987 		// fallback to default behavior.
988 		_GP(usetup).local_user_conf = File::TestWriteFile(def_cfg_file);
989 	}
990 
991 	// Read user configuration file
992 	String user_cfg_file = find_user_cfg_file();
993 	if (Path::ComparePaths(user_cfg_file, def_cfg_file) != 0 &&
994 	        Path::ComparePaths(user_cfg_file, user_global_cfg_file) != 0)
995 		IniUtil::Read(user_cfg_file, cfg);
996 
997 	// Apply overriding options from mobile port settings
998 	// TODO: normally, those should be instead stored in the same config file in a uniform way
999 	// NOTE: the variable is historically called "ignore" but we use it in "override" meaning here
1000 	if (_G(psp_ignore_acsetup_cfg_file))
1001 		override_config_ext(cfg);
1002 }
1003 
1004 // Gathers settings from all available sources into single ConfigTree
engine_prepare_config(ConfigTree & cfg,const ConfigTree & startup_opts)1005 void engine_prepare_config(ConfigTree &cfg, const ConfigTree &startup_opts) {
1006 	Debug::Printf(kDbgMsg_Info, "Setting up game configuration");
1007 	// Read configuration files
1008 	engine_read_config(cfg);
1009 	// Merge startup options in
1010 	for (const auto &sectn : startup_opts)
1011 		for (const auto &opt : sectn._value)
1012 			cfg[sectn._key][opt._key] = opt._value;
1013 }
1014 
1015 // Applies configuration to the running game
engine_set_config(const ConfigTree cfg)1016 void engine_set_config(const ConfigTree cfg) {
1017 	config_defaults();
1018 	apply_config(cfg);
1019 	post_config();
1020 }
1021 
print_info_needs_game(const std::set<String> & keys)1022 static bool print_info_needs_game(const std::set<String> &keys) {
1023 	return keys.count("all") > 0 || keys.count("config") > 0 || keys.count("configpath") > 0 ||
1024 	       keys.count("data") > 0 || keys.count("filepath") > 0 || keys.count("gameproperties") > 0;
1025 }
1026 
engine_print_info(const std::set<String> & keys,ConfigTree * user_cfg)1027 static void engine_print_info(const std::set<String> &keys, ConfigTree *user_cfg) {
1028 	const bool all = keys.count("all") > 0;
1029 	ConfigTree data;
1030 	if (all || keys.count("engine") > 0) {
1031 		data["engine"]["name"] = get_engine_name();
1032 		data["engine"]["version"] = get_engine_version();
1033 	}
1034 	if (all || keys.count("graphicdriver") > 0) {
1035 		StringV drv;
1036 		AGS::Engine::GetGfxDriverFactoryNames(drv);
1037 		for (size_t i = 0; i < drv.size(); ++i) {
1038 			data["graphicdriver"][String::FromFormat("%zu", i)] = drv[i];
1039 		}
1040 	}
1041 	if (all || keys.count("configpath") > 0) {
1042 		String def_cfg_file = find_default_cfg_file();
1043 		String gl_cfg_file = find_user_global_cfg_file();
1044 		String user_cfg_file = find_user_cfg_file();
1045 		data["configpath"]["default"] = def_cfg_file;
1046 		data["configpath"]["global"] = gl_cfg_file;
1047 		data["configpath"]["user"] = user_cfg_file;
1048 	}
1049 	if ((all || keys.count("config") > 0) && user_cfg) {
1050 		for (const auto &sectn : *user_cfg) {
1051 			String cfg_sectn = String::FromFormat("config@%s", sectn._key.GetCStr());
1052 			for (const auto &opt : sectn._value)
1053 				data[cfg_sectn][opt._key] = opt._value;
1054 		}
1055 	}
1056 	if (all || keys.count("data") > 0) {
1057 		data["data"]["gamename"] = _GP(game).gamename;
1058 		data["data"]["version"] = StrUtil::IntToString(_G(loaded_game_file_version));
1059 		data["data"]["compiledwith"] = _GP(game).compiled_with;
1060 		data["data"]["basepack"] = _GP(ResPaths).GamePak.Path;
1061 	}
1062 	if (all || keys.count("gameproperties") > 0) {
1063 		fill_game_properties(data["gameproperties"]);
1064 	}
1065 	if (all || keys.count("filepath") > 0) {
1066 		data["filepath"]["exe"] = _G(appPath);
1067 		data["filepath"]["cwd"] = Directory::GetCurrentDirectory();
1068 		data["filepath"]["datadir"] = Path::MakePathNoSlash(_GP(ResPaths).DataDir);
1069 		if (!_GP(ResPaths).DataDir2.IsEmpty()) {
1070 			data["filepath"]["datadir2"] = Path::MakePathNoSlash(_GP(ResPaths).DataDir2);
1071 			data["filepath"]["audiodir2"] = Path::MakePathNoSlash(_GP(ResPaths).AudioDir2);
1072 			data["filepath"]["voicedir2"] = Path::MakePathNoSlash(_GP(ResPaths).VoiceDir2);
1073 		}
1074 		data["filepath"]["savegamedir"] = Path::MakePathNoSlash(GetGameUserDataDir().FullDir);
1075 		data["filepath"]["appdatadir"] = Path::MakePathNoSlash(GetGameAppDataDir().FullDir);
1076 	}
1077 	String full;
1078 	IniUtil::WriteToString(full, data);
1079 	_G(platform)->WriteStdOut("%s", full.GetCStr());
1080 }
1081 
1082 // TODO: this function is still a big mess, engine/system-related initialization
1083 // is mixed with game-related data adjustments. Divide it in parts, move game
1084 // data init into either InitGameState() or other game method as appropriate.
initialize_engine(const ConfigTree & startup_opts)1085 int initialize_engine(const ConfigTree &startup_opts) {
1086 	_G(proper_exit) = false;
1087 
1088 	if (_G(engine_pre_init_callback)) {
1089 		_G(engine_pre_init_callback)();
1090 	}
1091 
1092 	//-----------------------------------------------------
1093 	// Install backend
1094 	if (!engine_init_backend())
1095 		return EXIT_ERROR;
1096 
1097 	//-----------------------------------------------------
1098 	// Locate game data and assemble game config
1099 	if (_G(justTellInfo) && !print_info_needs_game(_G(tellInfoKeys))) {
1100 		engine_print_info(_G(tellInfoKeys), nullptr);
1101 		return EXIT_NORMAL;
1102 	}
1103 
1104 	if (!engine_init_gamedata())
1105 		return EXIT_ERROR;
1106 	ConfigTree cfg;
1107 	engine_prepare_config(cfg, startup_opts);
1108 	// Test if need to run built-in setup program (where available)
1109 	if (!_G(justTellInfo) && _G(justRunSetup)) {
1110 		int res;
1111 		if (!engine_run_setup(cfg, res))
1112 			return res;
1113 	}
1114 	// Set up game options from user config
1115 	engine_set_config(cfg);
1116 	engine_force_window();
1117 	if (_G(justTellInfo)) {
1118 		engine_print_info(_G(tellInfoKeys), &cfg);
1119 		return EXIT_NORMAL;
1120 	}
1121 
1122 	_G(our_eip) = -190;
1123 
1124 	//-----------------------------------------------------
1125 	// Init auxiliary data files and other directories, initialize asset manager
1126 	engine_init_user_directories();
1127 
1128 	_G(our_eip) = -191;
1129 
1130 	engine_locate_speech_pak();
1131 
1132 	_G(our_eip) = -192;
1133 
1134 	engine_locate_audio_pak();
1135 
1136 	_G(our_eip) = -193;
1137 
1138 	engine_assign_assetpaths();
1139 
1140 	//-----------------------------------------------------
1141 	// Begin setting up systems
1142 
1143 	_G(our_eip) = -194;
1144 
1145 	engine_init_fonts();
1146 
1147 	_G(our_eip) = -195;
1148 
1149 	engine_init_keyboard();
1150 
1151 	_G(our_eip) = -196;
1152 
1153 	engine_init_mouse();
1154 
1155 	_G(our_eip) = -197;
1156 
1157 	engine_init_timer();
1158 
1159 	_G(our_eip) = -198;
1160 
1161 	engine_init_audio();
1162 
1163 	_G(our_eip) = -199;
1164 
1165 	engine_init_debug();
1166 
1167 	_G(our_eip) = -10;
1168 
1169 	engine_init_rand();
1170 
1171 	engine_init_pathfinder();
1172 
1173 	set_game_speed(40);
1174 
1175 	_G(our_eip) = -20;
1176 	_G(our_eip) = -19;
1177 
1178 	int res = engine_load_game_data();
1179 	if (res != 0)
1180 		return res;
1181 
1182 	res = engine_check_register_game();
1183 	if (res != 0)
1184 		return res;
1185 
1186 	_G(our_eip) = -189;
1187 
1188 	res = engine_check_disk_space();
1189 	if (res != 0)
1190 		return res;
1191 
1192 	// Make sure that at least one font was loaded in the process of loading
1193 	// the game data.
1194 	// TODO: Fold this check into engine_load_game_data()
1195 	res = engine_check_font_was_loaded();
1196 	if (res != 0)
1197 		return res;
1198 
1199 	_G(our_eip) = -179;
1200 
1201 	engine_init_resolution_settings(_GP(game).GetGameRes());
1202 
1203 	// Attempt to initialize graphics mode
1204 	if (!engine_try_set_gfxmode_any(_GP(usetup).Screen))
1205 		return EXIT_ERROR;
1206 
1207 	// Configure game window after renderer was initialized
1208 	engine_setup_window();
1209 
1210 	SetMultitasking(0);
1211 
1212 	sys_window_show_cursor(false); // hide the system cursor
1213 
1214 	show_preload();
1215 
1216 	res = engine_init_sprites();
1217 	if (res != 0)
1218 		return res;
1219 
1220 	engine_init_game_settings();
1221 
1222 	engine_prepare_to_start_game();
1223 
1224 	allegro_bitmap_test_init();
1225 
1226 	initialize_start_and_play_game(_G(override_start_room), _G(loadSaveGameOnStartup));
1227 
1228 	quit("|bye!");
1229 	return EXIT_NORMAL;
1230 }
1231 
engine_try_set_gfxmode_any(const ScreenSetup & setup)1232 bool engine_try_set_gfxmode_any(const ScreenSetup &setup) {
1233 	engine_shutdown_gfxmode();
1234 
1235 	const Size init_desktop = get_desktop_size();
1236 	if (!graphics_mode_init_any(GraphicResolution(_GP(game).GetGameRes(), _GP(game).color_depth * 8),
1237 		setup, ColorDepthOption(_GP(game).GetColorDepth())))
1238 		return false;
1239 
1240 	engine_post_gfxmode_setup(init_desktop);
1241 	return true;
1242 }
1243 
engine_try_switch_windowed_gfxmode()1244 bool engine_try_switch_windowed_gfxmode() {
1245 	if (!_G(gfxDriver) || !_G(gfxDriver)->IsModeSet())
1246 		return false;
1247 
1248 	// Keep previous mode in case we need to revert back
1249 	DisplayMode old_dm = _G(gfxDriver)->GetDisplayMode();
1250 	GameFrameSetup old_frame = graphics_mode_get_render_frame();
1251 
1252 	// Release engine resources that depend on display mode
1253 	engine_pre_gfxmode_release();
1254 
1255 	Size init_desktop = get_desktop_size();
1256 	bool switch_to_windowed = !old_dm.Windowed;
1257 	ActiveDisplaySetting setting = graphics_mode_get_last_setting(switch_to_windowed);
1258 	DisplayMode last_opposite_mode = setting.Dm;
1259 	GameFrameSetup use_frame_setup = setting.FrameSetup;
1260 
1261 	// If there are saved parameters for given mode (fullscreen/windowed)
1262 	// then use them, if there are not, get default setup for the new mode.
1263 	bool res;
1264 	if (last_opposite_mode.IsValid()) {
1265 		res = graphics_mode_set_dm(last_opposite_mode);
1266 	} else {
1267 		// we need to clone from initial config, because not every parameter is set by graphics_mode_get_defaults()
1268 		DisplayModeSetup dm_setup = _GP(usetup).Screen.DisplayMode;
1269 		dm_setup.Windowed = !old_dm.Windowed;
1270 		graphics_mode_get_defaults(dm_setup.Windowed, dm_setup.ScreenSize, use_frame_setup);
1271 		res = graphics_mode_set_dm_any(_GP(game).GetGameRes(), dm_setup, old_dm.ColorDepth, use_frame_setup);
1272 	}
1273 
1274 	// Apply corresponding frame render method
1275 	if (res)
1276 		res = graphics_mode_set_render_frame(use_frame_setup);
1277 
1278 	if (!res) {
1279 		// If failed, try switching back to previous gfx mode
1280 		res = graphics_mode_set_dm(old_dm) &&
1281 		      graphics_mode_set_render_frame(old_frame);
1282 	}
1283 
1284 	if (res) {
1285 		// If succeeded (with any case), update engine objects that rely on
1286 		// active display mode.
1287 		if (_G(gfxDriver)->GetDisplayMode().Windowed)
1288 			init_desktop = get_desktop_size();
1289 		engine_post_gfxmode_setup(init_desktop);
1290 	}
1291 	ags_clear_input_state();
1292 	return res;
1293 }
1294 
engine_on_window_changed(const Size & sz)1295 void engine_on_window_changed(const Size &sz) {
1296 	graphics_mode_on_window_changed(sz);
1297 	on_coordinates_scaling_changed();
1298 	invalidate_screen();
1299 }
1300 
engine_shutdown_gfxmode()1301 void engine_shutdown_gfxmode() {
1302 	if (!_G(gfxDriver))
1303 		return;
1304 
1305 	engine_pre_gfxsystem_shutdown();
1306 	graphics_mode_shutdown();
1307 }
1308 
get_engine_name()1309 const char *get_engine_name() {
1310 	return "Adventure Game Studio run-time engine";
1311 }
1312 
get_engine_version()1313 const char *get_engine_version() {
1314 	return _G(EngineVersion).LongString.GetCStr();
1315 }
1316 
engine_set_pre_init_callback(t_engine_pre_init_callback callback)1317 void engine_set_pre_init_callback(t_engine_pre_init_callback callback) {
1318 	_G(engine_pre_init_callback) = callback;
1319 }
1320 
1321 } // namespace AGS3
1322