/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*/
//
// Game configuration
//
#include "ags/engine/ac/game_setup.h"
#include "ags/shared/ac/game_setup_struct.h"
#include "ags/engine/ac/global_translation.h"
#include "ags/engine/ac/path_helper.h"
#include "ags/shared/ac/sprite_cache.h"
#include "ags/engine/ac/system.h"
#include "ags/shared/core/platform.h"
#include "ags/engine/debugging/debugger.h"
#include "ags/engine/debugging/debug_log.h"
#include "ags/engine/device/mouse_w32.h"
#include "ags/engine/main/config.h"
#include "ags/engine/media/audio/audio_system.h"
#include "ags/engine/platform/base/ags_platform_driver.h"
#include "ags/shared/util/directory.h"
#include "ags/shared/util/ini_util.h"
#include "ags/shared/util/text_stream_reader.h"
#include "ags/shared/util/path.h"
#include "ags/shared/util/string_utils.h"
#include "ags/metaengine.h"
#include "common/config-manager.h"
#include "common/language.h"
namespace AGS3 {
using namespace AGS::Shared;
using namespace AGS::Engine;
// Filename of the default config file, the one found in the game installation
const char *DefaultConfigFileName = "acsetup.cfg";
WindowSetup parse_window_mode(const String &option, bool as_windowed, WindowSetup def_value) {
// "full_window" option means pseudo fullscreen ("borderless fullscreen window")
if (!as_windowed && (option.CompareNoCase("full_window") == 0))
return WindowSetup(kWnd_FullDesktop);
// Check supported options for explicit resolution or scale factor,
// in which case we'll use either a resizing window or a REAL fullscreen mode
const WindowMode exp_wmode = as_windowed ? kWnd_Windowed : kWnd_Fullscreen;
// Note that for "desktop" we return "default" for windowed, this will result
// in referring to the desktop size but resizing in accordance to the scaling style
if (option.CompareNoCase("desktop") == 0)
return as_windowed ? WindowSetup(exp_wmode) : WindowSetup(get_desktop_size(), exp_wmode);
// "Native" means using game resolution as a window size
if (option.CompareNoCase("native") == 0)
return WindowSetup(_GP(game).GetGameRes(), exp_wmode);
// Try parse an explicit resolution type or game scale factor --
size_t at = option.FindChar('x');
if (at == 0) { // try parse as a scale (xN)
int scale = StrUtil::StringToInt(option.Mid(1));
if (scale > 0) return WindowSetup(scale, exp_wmode);
} else if (at != String::NoIndex) {
// else try parse as a "width x height"
Size sz = Size(StrUtil::StringToInt(option.Mid(0, at)),
StrUtil::StringToInt(option.Mid(at + 1)));
if (!sz.IsNull()) return WindowSetup(sz, exp_wmode);
}
// In case of "default" option, or any format mistake, return the default
return def_value;
}
// Legacy screen size definition
enum ScreenSizeDefinition {
kScreenDef_Undefined = -1,
kScreenDef_Explicit, // define by width & height
kScreenDef_ByGameScaling, // define by game scale factor
kScreenDef_MaxDisplay, // set to maximal supported (desktop/device screen size)
kNumScreenDef
};
static ScreenSizeDefinition parse_legacy_screendef(const String &option) {
const char *screen_sz_def_options[kNumScreenDef] = { "explicit", "scaling", "max" };
for (int i = 0; i < kNumScreenDef; ++i) {
if (option.CompareNoCase(screen_sz_def_options[i]) == 0) {
return (ScreenSizeDefinition)i;
}
}
return kScreenDef_Undefined;
}
FrameScaleDef parse_scaling_option(const String &option, FrameScaleDef def_value) {
if (option.CompareNoCase("round") == 0 || option.CompareNoCase("max_round") == 0)
return kFrame_Round;
if (option.CompareNoCase("stretch") == 0)
return kFrame_Stretch;
if (option.CompareNoCase("proportional") == 0)
return kFrame_Proportional;
return def_value;
}
static FrameScaleDef parse_legacy_scaling_option(const String &option, int &scale) {
FrameScaleDef frame = parse_scaling_option(option, kFrame_Undefined);
if (frame == kFrame_Undefined) {
scale = StrUtil::StringToInt(option);
return scale > 0 ? kFrame_Round : kFrame_Undefined;
}
return frame;
}
// Parses legacy filter ID and converts it into current scaling options
bool parse_legacy_frame_config(const String &scaling_option, String &filter_id,
FrameScaleDef &frame, int &scale_factor) {
struct {
String LegacyName;
String CurrentName;
int Scaling;
} legacy_filters[6] = { {"none", "none", -1}, {"max", "StdScale", 0}, {"StdScale", "StdScale", -1},
{"AAx", "Linear", -1}, {"Hq2x", "Hqx", 2}, {"Hq3x", "Hqx", 3} };
for (int i = 0; i < 6; i++) {
if (scaling_option.CompareLeftNoCase(legacy_filters[i].LegacyName) == 0) {
filter_id = legacy_filters[i].CurrentName;
frame = kFrame_Round;
scale_factor = legacy_filters[i].Scaling >= 0 ? legacy_filters[i].Scaling :
scaling_option.Mid(legacy_filters[i].LegacyName.GetLength()).ToInt();
return true;
}
}
return false;
}
String make_scaling_option(FrameScaleDef scale_def) {
switch (scale_def) {
case kFrame_Stretch:
return "stretch";
case kFrame_Proportional:
return "proportional";
default:
return "round";
}
}
uint32_t convert_scaling_to_fp(int scale_factor) {
if (scale_factor >= 0)
return scale_factor <<= kShift;
else
return kUnit / abs(scale_factor);
}
int convert_fp_to_scaling(uint32_t scaling) {
if (scaling == 0)
return 0;
return scaling >= kUnit ? (scaling >> kShift) : -kUnit / (int32_t)scaling;
}
String find_default_cfg_file() {
return Path::ConcatPaths(_GP(usetup).startup_dir, DefaultConfigFileName);
}
String find_user_global_cfg_file() {
return Path::ConcatPaths(GetGlobalUserConfigDir().FullDir, DefaultConfigFileName);
}
String find_user_cfg_file() {
return Path::ConcatPaths(GetGameUserConfigDir().FullDir, DefaultConfigFileName);
}
void config_defaults() {
#if AGS_PLATFORM_OS_WINDOWS
_GP(usetup).Screen.DriverID = "D3D9";
#else
_GP(usetup).Screen.DriverID = "OGL";
#endif
// Defaults for the window style are max resizing window and "fullscreen desktop"
_GP(usetup).Screen.FsSetup = WindowSetup(kWnd_FullDesktop);
_GP(usetup).Screen.WinSetup = WindowSetup(kWnd_Windowed);
}
static void read_legacy_graphics_config(const ConfigTree &cfg) {
// Pre-3.* game resolution setup
int default_res = CfgReadInt(cfg, "misc", "defaultres", kGameResolution_Default);
int screen_res = CfgReadInt(cfg, "misc", "screenres", 0);
if (screen_res > 0 &&
(default_res >= kGameResolution_Default && default_res <= kGameResolution_320x240)) {
_GP(usetup).override_upscale = true; // run low-res game in high-res mode
}
_GP(usetup).Screen.Windowed = CfgReadBoolInt(cfg, "misc", "windowed");
_GP(usetup).Screen.DriverID = CfgReadString(cfg, "misc", "gfxdriver", _GP(usetup).Screen.DriverID);
// Window setup: style and size definition, game frame style
{
String legacy_filter = CfgReadString(cfg, "misc", "gfxfilter");
if (!legacy_filter.IsEmpty()) {
// Legacy scaling config is applied only to windowed setting
int scale_factor = 0;
parse_legacy_frame_config(legacy_filter, _GP(usetup).Screen.Filter.ID, _GP(usetup).Screen.WinGameFrame,
scale_factor);
if (scale_factor > 0)
_GP(usetup).Screen.WinSetup = WindowSetup(scale_factor);
// AGS 3.2.1 and 3.3.0 aspect ratio preferences for fullscreen
if (!_GP(usetup).Screen.Windowed) {
bool allow_borders =
(CfgReadBoolInt(cfg, "misc", "sideborders") || CfgReadBoolInt(cfg, "misc", "forceletterbox") ||
CfgReadBoolInt(cfg, "misc", "prefer_sideborders") || CfgReadBoolInt(cfg, "misc", "prefer_letterbox"));
_GP(usetup).Screen.FsGameFrame = allow_borders ? kFrame_Proportional : kFrame_Stretch;
}
}
// AGS 3.4.0 - 3.4.1-rc uniform scaling option
String uniform_frame_scale = CfgReadString(cfg, "graphics", "game_scale");
if (!uniform_frame_scale.IsEmpty()) {
int src_scale = 1;
FrameScaleDef frame = parse_legacy_scaling_option(uniform_frame_scale, src_scale);
_GP(usetup).Screen.FsGameFrame = frame;
_GP(usetup).Screen.WinGameFrame = frame;
}
// AGS 3.5.* gfx mode with screen definition
const bool is_windowed = CfgReadBoolInt(cfg, "graphics", "windowed");
WindowSetup &ws = is_windowed ? _GP(usetup).Screen.WinSetup : _GP(usetup).Screen.FsSetup;
const WindowMode wm = is_windowed ? kWnd_Windowed : kWnd_Fullscreen;
ScreenSizeDefinition scr_def = parse_legacy_screendef(CfgReadString(cfg, "graphics", "screen_def"));
switch (scr_def) {
case kScreenDef_Explicit:
{
Size sz(
CfgReadInt(cfg, "graphics", "screen_width"),
CfgReadInt(cfg, "graphics", "screen_height"));
ws = WindowSetup(sz, wm);
}
break;
case kScreenDef_ByGameScaling:
{
int src_scale = 0;
is_windowed ?
parse_legacy_scaling_option(CfgReadString(cfg, "graphics", "game_scale_win"), src_scale) :
parse_legacy_scaling_option(CfgReadString(cfg, "graphics", "game_scale_fs"), src_scale);
ws = WindowSetup(src_scale, wm);
}
break;
case kScreenDef_MaxDisplay:
ws = is_windowed ? WindowSetup() : WindowSetup(kWnd_FullDesktop);
break;
default:
break;
}
}
_GP(usetup).Screen.Params.RefreshRate = CfgReadInt(cfg, "misc", "refresh");
_GP(usetup).enable_antialiasing = CfgReadBoolInt(cfg, "misc", "antialias");
}
static void read_legacy_config(const ConfigTree &cfg) {
read_legacy_graphics_config(cfg);
_GP(usetup).SpriteCacheSize = CfgReadInt(cfg, "misc", "cachemax", _GP(usetup).SpriteCacheSize);
}
void override_config_ext(ConfigTree &cfg) {
_G(platform)->ReadConfiguration(cfg);
}
void apply_config(const ConfigTree &cfg) {
// Legacy settings have to be translated into new options;
// they must be read first, to let newer options override them, if ones are present
read_legacy_config(cfg);
{
// Audio options
_GP(usetup).audio_enabled = CfgReadBoolInt(cfg, "sound", "enabled", _GP(usetup).audio_enabled);
_GP(usetup).audio_driver = CfgReadString(cfg, "sound", "driver");
// This option is backwards (usevox is 0 if no_speech_pack)
_GP(usetup).no_speech_pack = !CfgReadBoolInt(cfg, "sound", "usespeech", true);
// Graphics mode and options
_GP(usetup).Screen.DriverID = CfgReadString(cfg, "graphics", "driver", _GP(usetup).Screen.DriverID);
_GP(usetup).Screen.Windowed = CfgReadBoolInt(cfg, "graphics", "windowed", _GP(usetup).Screen.Windowed);
_GP(usetup).Screen.FsSetup =
parse_window_mode(CfgReadString(cfg, "graphics", "fullscreen", "default"), false, _GP(usetup).Screen.FsSetup);
_GP(usetup).Screen.WinSetup =
parse_window_mode(CfgReadString(cfg, "graphics", "window", "default"), true, _GP(usetup).Screen.WinSetup);
_GP(usetup).Screen.Filter.ID = CfgReadString(cfg, "graphics", "filter", "StdScale");
_GP(usetup).Screen.FsGameFrame =
parse_scaling_option(CfgReadString(cfg, "graphics", "game_scale_fs", "proportional"), _GP(usetup).Screen.FsGameFrame);
_GP(usetup).Screen.WinGameFrame =
parse_scaling_option(CfgReadString(cfg, "graphics", "game_scale_win", "round"), _GP(usetup).Screen.WinGameFrame);
_GP(usetup).Screen.Params.RefreshRate = CfgReadInt(cfg, "graphics", "refresh");
// Use ScummVM options to set the vsync flag, if available
if (ConfMan.hasKey("vsync"))
_GP(usetup).Screen.Params.VSync = ConfMan.getBool("vsync");
else
_GP(usetup).Screen.Params.VSync = CfgReadBoolInt(cfg, "graphics", "vsync");
_GP(usetup).RenderAtScreenRes = CfgReadBoolInt(cfg, "graphics", "render_at_screenres");
_GP(usetup).enable_antialiasing = CfgReadBoolInt(cfg, "graphics", "antialias");
_GP(usetup).software_render_driver = CfgReadString(cfg, "graphics", "software_driver");
#ifdef TODO
_GP(usetup).rotation = (ScreenRotation)CfgReadInt(cfg, "graphics", "rotation", _GP(usetup).rotation);
String rotation_str = CfgReadString(cfg, "graphics", "rotation", "unlocked");
_GP(usetup).rotation = StrUtil::ParseEnum(
rotation_str, CstrArr{ "unlocked", "portrait", "landscape" },
_GP(usetup).rotation);
#endif
// Custom paths
_GP(usetup).load_latest_save = CfgReadBoolInt(cfg, "misc", "load_latest_save", _GP(usetup).load_latest_save);
_GP(usetup).user_data_dir = CfgReadString(cfg, "misc", "user_data_dir");
_GP(usetup).shared_data_dir = CfgReadString(cfg, "misc", "shared_data_dir");
_GP(usetup).show_fps = CfgReadBoolInt(cfg, "misc", "show_fps");
// Translation / localization
Common::String translation;
if (!ConfMan.get("language").empty() && ConfMan.isKeyTemporary("language")) {
// Map the language defined in the command-line "language" option to its description
Common::Language lang = Common::parseLanguage(ConfMan.get("language"));
if (lang != Common::Language::UNK_LANG) {
Common::String translationCode = Common::getLanguageCode(lang);
translationCode.toLowercase();
translation = Common::getLanguageDescription(lang);
translation.toLowercase();
// Check if the game actually has such a translation, and set it if it does
// The name of translation files can be anything, but in general they are one of:
// - English name of the language, for example French.tra or Spanish.tra (covered)
// - Translated name of the language, for example polsky.tra or francais.tra (not covered)
// - The language code, for example FR.tra or DE.tra (covered)
// - And these can be combined with a prefix or suffix, for example Nelly_Polish.tra, english2.tra (covered)
Common::StringArray traFileNames = AGSMetaEngine::getGameTranslations(ConfMan.getActiveDomainName());
for (Common::StringArray::iterator iter = traFileNames.begin(); iter != traFileNames.end(); ++iter) {
Common::String traFileName = *iter;
traFileName.toLowercase();
if (traFileName.contains(translation) || traFileName.equals(translationCode)) {
_GP(usetup).translation = *iter;
break;
}
}
}
} else if (ConfMan.getActiveDomain()->tryGetVal("translation", translation) && !translation.empty())
_GP(usetup).translation = translation;
else
_GP(usetup).translation = CfgReadString(cfg, "language", "translation");
// Resource caches and options
_GP(usetup).clear_cache_on_room_change = CfgReadBoolInt(cfg, "misc", "clear_cache_on_room_change", _GP(usetup).clear_cache_on_room_change);
_GP(usetup).SpriteCacheSize = CfgReadInt(cfg, "graphics", "sprite_cache_size", _GP(usetup).SpriteCacheSize);
_GP(usetup).TextureCacheSize = CfgReadInt(cfg, "graphics", "texture_cache_size", _GP(usetup).TextureCacheSize);
// Mouse options
_GP(usetup).mouse_auto_lock = CfgReadBoolInt(cfg, "mouse", "auto_lock");
_GP(usetup).mouse_speed = CfgReadFloat(cfg, "mouse", "speed", 1.f);
if (_GP(usetup).mouse_speed <= 0.f)
_GP(usetup).mouse_speed = 1.f;
const char *mouse_ctrl_options[kNumMouseCtrlOptions] = { "never", "fullscreen", "always" };
String mouse_str = CfgReadString(cfg, "mouse", "control_when", "fullscreen");
for (int i = 0; i < kNumMouseCtrlOptions; ++i) {
if (mouse_str.CompareNoCase(mouse_ctrl_options[i]) == 0) {
_GP(usetup).mouse_ctrl_when = (MouseControlWhen)i;
break;
}
}
_GP(usetup).mouse_ctrl_enabled = CfgReadBoolInt(cfg, "mouse", "control_enabled", _GP(usetup).mouse_ctrl_enabled);
const char *mouse_speed_options[kNumMouseSpeedDefs] = { "absolute", "current_display" };
mouse_str = CfgReadString(cfg, "mouse", "speed_def", "current_display");
for (int i = 0; i < kNumMouseSpeedDefs; ++i) {
if (mouse_str.CompareNoCase(mouse_speed_options[i]) == 0) {
_GP(usetup).mouse_speed_def = (MouseSpeedDef)i;
break;
}
}
// Various system options
_GP(usetup).multitasking = CfgReadInt(cfg, "misc", "background", 0) != 0;
// User's overrides and hacks
_GP(usetup).override_multitasking = CfgReadInt(cfg, "override", "multitasking", -1);
_GP(usetup).override_script_os = -1;
// Looks for the existence of the Linux executable
if (File::IsFile(Path::ConcatPaths(_GP(usetup).startup_dir, "ags64"))) {
_GP(usetup).override_script_os = eOS_Linux;
}
String override_os = CfgReadString(cfg, "override", "os");
if (override_os.CompareNoCase("dos") == 0) {
_GP(usetup).override_script_os = eOS_DOS;
} else if (override_os.CompareNoCase("win") == 0) {
_GP(usetup).override_script_os = eOS_Win;
} else if (override_os.CompareNoCase("linux") == 0) {
_GP(usetup).override_script_os = eOS_Linux;
} else if (override_os.CompareNoCase("mac") == 0) {
_GP(usetup).override_script_os = eOS_Mac;
}
_GP(usetup).override_upscale = CfgReadBoolInt(cfg, "override", "upscale", _GP(usetup).override_upscale);
_GP(usetup).legacysave_assume_dataver = static_cast(CfgReadInt(cfg, "override", "legacysave_assume_dataver", kGameVersion_Undefined));
_GP(usetup).legacysave_let_gui_diff = CfgReadBoolInt(cfg, "override", "legacysave_let_gui_diff");
_GP(usetup).key_save_game = CfgReadInt(cfg, "override", "save_game_key", 0);
_GP(usetup).key_restore_game = CfgReadInt(cfg, "override", "restore_game_key", 0);
}
// Apply logging configuration
apply_debug_config(cfg);
}
void post_config() {
if (_GP(usetup).Screen.DriverID.IsEmpty() || _GP(usetup).Screen.DriverID.CompareNoCase("DX5") == 0)
_GP(usetup).Screen.DriverID = "Software";
// FIXME: this correction is needed at the moment because graphics driver
// implementation requires some filter to be created anyway
_GP(usetup).Screen.Filter.UserRequest = _GP(usetup).Screen.Filter.ID;
if (_GP(usetup).Screen.Filter.ID.IsEmpty() || _GP(usetup).Screen.Filter.ID.CompareNoCase("none") == 0) {
_GP(usetup).Screen.Filter.ID = "StdScale";
}
}
void save_config_file() {
// Translation / localization
if (!_GP(usetup).translation.IsEmpty()) {
ConfMan.getActiveDomain()->setVal("translation", _GP(usetup).translation.GetCStr());
ConfMan.flushToDisk();
} else if (ConfMan.getActiveDomain()->contains("translation")) {
ConfMan.getActiveDomain()->erase("translation");
ConfMan.flushToDisk();
}
// ScummVM doesn't write out other configuration changes
}
} // namespace AGS3