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 §n : 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 §n : *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