You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

461 lines
18 KiB

/**
* @file options.cpp
*
* Load and save options from the diablo.ini file.
*/
#include <cstdint>
#include <fstream>
#include <locale>
#ifdef __ANDROID__
#include "SDL.h"
#include <jni.h>
#endif
#ifdef __vita__
#include <psp2/apputil.h>
#include <psp2/system_param.h>
#endif
#ifdef __3DS__
#include "platform/ctr/locale.hpp"
#endif
#define SI_SUPPORT_IOSTREAMS
#include <SimpleIni.h>
#include "diablo.h"
#include "engine/demomode.h"
#include "options.h"
#include "utils/file_util.h"
#include "utils/language.h"
#include "utils/paths.h"
namespace devilution {
#ifndef DEFAULT_WIDTH
#define DEFAULT_WIDTH 640
#endif
#ifndef DEFAULT_HEIGHT
#define DEFAULT_HEIGHT 480
#endif
#ifndef DEFAULT_AUDIO_SAMPLE_RATE
#define DEFAULT_AUDIO_SAMPLE_RATE 22050
#endif
#ifndef DEFAULT_AUDIO_CHANNELS
#define DEFAULT_AUDIO_CHANNELS 2
#endif
#ifndef DEFAULT_AUDIO_BUFFER_SIZE
#define DEFAULT_AUDIO_BUFFER_SIZE 2048
#endif
#ifndef DEFAULT_AUDIO_RESAMPLING_QUALITY
#define DEFAULT_AUDIO_RESAMPLING_QUALITY 5
#endif
#if defined(VIRTUAL_GAMEPAD) && !defined(USE_SDL1)
#define AUTO_PICKUP_DEFAULT(bValue) true
#else
#define AUTO_PICKUP_DEFAULT(bValue) bValue
#endif
namespace {
std::string GetIniPath()
{
auto path = paths::ConfigPath() + std::string("diablo.ini");
return path;
}
CSimpleIni &GetIni()
{
static CSimpleIni ini;
static bool isIniLoaded = false;
if (!isIniLoaded) {
auto path = GetIniPath();
auto stream = CreateFileStream(path.c_str(), std::fstream::in | std::fstream::binary);
ini.SetSpaces(false);
if (stream)
ini.LoadData(*stream);
isIniLoaded = true;
}
return ini;
}
bool IniChanged = false;
/**
* @brief Checks if a ini entry is changed by comparing value before and after
*/
class IniChangedChecker {
public:
IniChangedChecker(const char *sectionName, const char *keyName)
{
this->sectionName_ = sectionName;
this->keyName_ = keyName;
std::list<CSimpleIni::Entry> values;
if (!GetIni().GetAllValues(sectionName, keyName, values)) {
// No entry found in original ini => new entry => changed
IniChanged = true;
}
const auto *value = GetIni().GetValue(sectionName, keyName);
if (value != nullptr)
oldValue_ = value;
}
~IniChangedChecker()
{
const auto *value = GetIni().GetValue(sectionName_, keyName_);
std::string newValue;
if (value != nullptr)
newValue = value;
if (oldValue_ != newValue)
IniChanged = true;
}
private:
std::string oldValue_;
const char *sectionName_;
const char *keyName_;
};
int GetIniInt(const char *keyname, const char *valuename, int defaultValue)
{
return GetIni().GetLongValue(keyname, valuename, defaultValue);
}
bool GetIniBool(const char *sectionName, const char *keyName, bool defaultValue)
{
return GetIni().GetBoolValue(sectionName, keyName, defaultValue);
}
float GetIniFloat(const char *sectionName, const char *keyName, float defaultValue)
{
return (float)GetIni().GetDoubleValue(sectionName, keyName, defaultValue);
}
void SetIniValue(const char *keyname, const char *valuename, int value)
{
IniChangedChecker changedChecker(keyname, valuename);
GetIni().SetLongValue(keyname, valuename, value);
}
void SetIniValue(const char *keyname, const char *valuename, std::uint8_t value)
{
IniChangedChecker changedChecker(keyname, valuename);
GetIni().SetLongValue(keyname, valuename, value);
}
void SetIniValue(const char *keyname, const char *valuename, std::uint32_t value)
{
IniChangedChecker changedChecker(keyname, valuename);
GetIni().SetLongValue(keyname, valuename, value);
}
void SetIniValue(const char *keyname, const char *valuename, bool value)
{
IniChangedChecker changedChecker(keyname, valuename);
GetIni().SetLongValue(keyname, valuename, value ? 1 : 0);
}
void SetIniValue(const char *keyname, const char *valuename, float value)
{
IniChangedChecker changedChecker(keyname, valuename);
GetIni().SetDoubleValue(keyname, valuename, value);
}
void SaveIni()
{
if (!IniChanged)
return;
auto iniPath = GetIniPath();
auto stream = CreateFileStream(iniPath.c_str(), std::fstream::out | std::fstream::trunc | std::fstream::binary);
GetIni().Save(*stream, true);
IniChanged = false;
}
#if SDL_VERSION_ATLEAST(2, 0, 0)
bool HardwareCursorDefault()
{
#ifdef __ANDROID__
// See https://github.com/diasurgical/devilutionX/issues/2502
return false;
#else
SDL_version v;
SDL_GetVersion(&v);
return SDL_VERSIONNUM(v.major, v.minor, v.patch) >= SDL_VERSIONNUM(2, 0, 12);
#endif
}
#endif
} // namespace
void SetIniValue(const char *sectionName, const char *keyName, const char *value, int len)
{
IniChangedChecker changedChecker(sectionName, keyName);
auto &ini = GetIni();
std::string stringValue(value, len != 0 ? len : strlen(value));
ini.SetValue(sectionName, keyName, stringValue.c_str());
}
bool GetIniValue(const char *sectionName, const char *keyName, char *string, int stringSize, const char *defaultString)
{
const char *value = GetIni().GetValue(sectionName, keyName);
if (value == nullptr) {
strncpy(string, defaultString, stringSize);
return false;
}
strncpy(string, value, stringSize);
return true;
}
/** Game options */
Options sgOptions;
bool sbWasOptionsLoaded = false;
void LoadOptions()
{
sgOptions.Diablo.bIntro = GetIniBool("Diablo", "Intro", true);
sgOptions.Diablo.lastSinglePlayerHero = GetIniInt("Diablo", "LastSinglePlayerHero", 0);
sgOptions.Diablo.lastMultiplayerHero = GetIniInt("Diablo", "LastMultiplayerHero", 0);
sgOptions.Hellfire.bIntro = GetIniBool("Hellfire", "Intro", true);
sgOptions.Hellfire.lastSinglePlayerHero = GetIniInt("Hellfire", "LastSinglePlayerHero", 0);
sgOptions.Hellfire.lastMultiplayerHero = GetIniInt("Hellfire", "LastMultiplayerHero", 0);
sgOptions.Hellfire.startUpGameOption = static_cast<StartUpGameOption>(GetIniInt("Hellfire", "StartUpGameOption", static_cast<int>(StartUpGameOption::None)));
GetIniValue("Hellfire", "SItem", sgOptions.Hellfire.szItem, sizeof(sgOptions.Hellfire.szItem), "");
sgOptions.Audio.nSoundVolume = GetIniInt("Audio", "Sound Volume", VOLUME_MAX);
sgOptions.Audio.nMusicVolume = GetIniInt("Audio", "Music Volume", VOLUME_MAX);
sgOptions.Audio.bWalkingSound = GetIniBool("Audio", "Walking Sound", true);
sgOptions.Audio.bAutoEquipSound = GetIniBool("Audio", "Auto Equip Sound", AUTO_PICKUP_DEFAULT(false));
sgOptions.Audio.bItemPickupSound = GetIniBool("Audio", "Item Pickup Sound", AUTO_PICKUP_DEFAULT(false));
sgOptions.Audio.nSampleRate = GetIniInt("Audio", "Sample Rate", DEFAULT_AUDIO_SAMPLE_RATE);
sgOptions.Audio.nChannels = GetIniInt("Audio", "Channels", DEFAULT_AUDIO_CHANNELS);
sgOptions.Audio.nBufferSize = GetIniInt("Audio", "Buffer Size", DEFAULT_AUDIO_BUFFER_SIZE);
sgOptions.Audio.nResamplingQuality = GetIniInt("Audio", "Resampling Quality", DEFAULT_AUDIO_RESAMPLING_QUALITY);
sgOptions.Graphics.nWidth = GetIniInt("Graphics", "Width", DEFAULT_WIDTH);
sgOptions.Graphics.nHeight = GetIniInt("Graphics", "Height", DEFAULT_HEIGHT);
#ifndef __vita__
sgOptions.Graphics.bFullscreen = GetIniBool("Graphics", "Fullscreen", true);
#else
sgOptions.Graphics.bFullscreen = true;
#endif
#if !defined(USE_SDL1)
sgOptions.Graphics.bUpscale = GetIniBool("Graphics", "Upscale", true);
#else
sgOptions.Graphics.bUpscale = false;
#endif
sgOptions.Graphics.bFitToScreen = GetIniBool("Graphics", "Fit to Screen", true);
GetIniValue("Graphics", "Scaling Quality", sgOptions.Graphics.szScaleQuality, sizeof(sgOptions.Graphics.szScaleQuality), "2");
sgOptions.Graphics.bIntegerScaling = GetIniBool("Graphics", "Integer Scaling", false);
sgOptions.Graphics.bVSync = GetIniBool("Graphics", "Vertical Sync", true);
sgOptions.Graphics.bBlendedTransparancy = GetIniBool("Graphics", "Blended Transparency", true);
sgOptions.Graphics.nGammaCorrection = GetIniInt("Graphics", "Gamma Correction", 100);
sgOptions.Graphics.bColorCycling = GetIniBool("Graphics", "Color Cycling", true);
#if SDL_VERSION_ATLEAST(2, 0, 0)
sgOptions.Graphics.bHardwareCursor = GetIniBool("Graphics", "Hardware Cursor", HardwareCursorDefault());
sgOptions.Graphics.bHardwareCursorForItems = GetIniBool("Graphics", "Hardware Cursor For Items", false);
sgOptions.Graphics.nHardwareCursorMaxSize = GetIniInt("Graphics", "Hardware Cursor Maximum Size", 128);
#endif
sgOptions.Graphics.bFPSLimit = GetIniBool("Graphics", "FPS Limiter", true);
sgOptions.Graphics.bShowFPS = (GetIniInt("Graphics", "Show FPS", 0) != 0);
sgOptions.Gameplay.nTickRate = GetIniInt("Game", "Speed", 20);
sgOptions.Gameplay.bRunInTown = GetIniBool("Game", "Run in Town", true);
sgOptions.Gameplay.bGrabInput = GetIniBool("Game", "Grab Input", false);
sgOptions.Gameplay.bTheoQuest = GetIniBool("Game", "Theo Quest", false);
sgOptions.Gameplay.bCowQuest = GetIniBool("Game", "Cow Quest", false);
sgOptions.Gameplay.bFriendlyFire = GetIniBool("Game", "Friendly Fire", true);
sgOptions.Gameplay.bTestBard = GetIniBool("Game", "Test Bard", false);
sgOptions.Gameplay.bTestBarbarian = GetIniBool("Game", "Test Barbarian", false);
sgOptions.Gameplay.bExperienceBar = GetIniBool("Game", "Experience Bar", false);
sgOptions.Gameplay.bEnemyHealthBar = GetIniBool("Game", "Enemy Health Bar", false);
sgOptions.Gameplay.bAutoGoldPickup = GetIniBool("Game", "Auto Gold Pickup", AUTO_PICKUP_DEFAULT(false));
sgOptions.Gameplay.bAdriaRefillsMana = GetIniBool("Game", "Adria Refills Mana", false);
sgOptions.Gameplay.bAutoEquipWeapons = GetIniBool("Game", "Auto Equip Weapons", AUTO_PICKUP_DEFAULT(true));
sgOptions.Gameplay.bAutoEquipArmor = GetIniBool("Game", "Auto Equip Armor", AUTO_PICKUP_DEFAULT(false));
sgOptions.Gameplay.bAutoEquipHelms = GetIniBool("Game", "Auto Equip Helms", AUTO_PICKUP_DEFAULT(false));
sgOptions.Gameplay.bAutoEquipShields = GetIniBool("Game", "Auto Equip Shields", AUTO_PICKUP_DEFAULT(false));
sgOptions.Gameplay.bAutoEquipJewelry = GetIniBool("Game", "Auto Equip Jewelry", AUTO_PICKUP_DEFAULT(false));
sgOptions.Gameplay.bRandomizeQuests = GetIniBool("Game", "Randomize Quests", true);
sgOptions.Gameplay.bShowMonsterType = GetIniBool("Game", "Show Monster Type", false);
sgOptions.Gameplay.bDisableCripplingShrines = GetIniBool("Game", "Disable Crippling Shrines", false);
sgOptions.Gameplay.bAutoRefillBelt = GetIniBool("Game", "Auto Refill Belt", AUTO_PICKUP_DEFAULT(false));
GetIniValue("Network", "Bind Address", sgOptions.Network.szBindAddress, sizeof(sgOptions.Network.szBindAddress), "0.0.0.0");
sgOptions.Network.nPort = GetIniInt("Network", "Port", 6112);
GetIniValue("Network", "Previous Host", sgOptions.Network.szPreviousHost, sizeof(sgOptions.Network.szPreviousHost), "");
for (size_t i = 0; i < QUICK_MESSAGE_OPTIONS; i++)
GetIniValue("NetMsg", QuickMessages[i].key, sgOptions.Chat.szHotKeyMsgs[i], MAX_SEND_STR_LEN, "");
GetIniValue("Controller", "Mapping", sgOptions.Controller.szMapping, sizeof(sgOptions.Controller.szMapping), "");
sgOptions.Controller.bSwapShoulderButtonMode = GetIniBool("Controller", "Swap Shoulder Button Mode", false);
sgOptions.Controller.bDpadHotkeys = GetIniBool("Controller", "Dpad Hotkeys", false);
sgOptions.Controller.fDeadzone = GetIniFloat("Controller", "deadzone", 0.07F);
#ifdef __vita__
sgOptions.Controller.bRearTouch = GetIniBool("Controller", "Enable Rear Touchpad", true);
#endif
#ifdef __ANDROID__
JNIEnv *env = (JNIEnv *)SDL_AndroidGetJNIEnv();
jobject activity = (jobject)SDL_AndroidGetActivity();
jclass clazz(env->GetObjectClass(activity));
jmethodID method_id = env->GetMethodID(clazz, "getLocale", "()Ljava/lang/String;");
jstring jLocale = (jstring)env->CallObjectMethod(activity, method_id);
const char *cLocale = env->GetStringUTFChars(jLocale, nullptr);
std::string locale = cLocale;
env->ReleaseStringUTFChars(jLocale, cLocale);
env->DeleteLocalRef(jLocale);
env->DeleteLocalRef(activity);
env->DeleteLocalRef(clazz);
#elif defined(__vita__)
int32_t language = SCE_SYSTEM_PARAM_LANG_ENGLISH_US; // default to english
const char *vita_locales[] = {
"ja_JP",
"en_US",
"fr_FR",
"es_ES",
"de_DE",
"it_IT",
"nl_NL",
"pt_PT",
"ru_RU",
"ko_KR",
"zh_TW",
"zh_CN",
"fi_FI",
"sv_SE",
"da_DK",
"no_NO",
"pl_PL",
"pt_BR",
"en_GB",
"tr_TR",
};
SceAppUtilInitParam initParam;
SceAppUtilBootParam bootParam;
memset(&initParam, 0, sizeof(SceAppUtilInitParam));
memset(&bootParam, 0, sizeof(SceAppUtilBootParam));
sceAppUtilInit(&initParam, &bootParam);
sceAppUtilSystemParamGetInt(SCE_SYSTEM_PARAM_ID_LANG, &language);
if (language < 0 || language > SCE_SYSTEM_PARAM_LANG_TURKISH)
language = SCE_SYSTEM_PARAM_LANG_ENGLISH_US; // default to english
std::string locale = std::string(vita_locales[language]);
sceAppUtilShutdown();
#elif defined(__3DS__)
std::string locale = n3ds::GetLocale();
#else
std::string locale = std::locale("").name().substr(0, 5);
#endif
LogVerbose("Prefered locale: {}", locale);
if (!HasTranslation(locale)) {
locale = locale.substr(0, 2);
if (!HasTranslation(locale)) {
locale = "en";
}
}
LogVerbose("Best match locale: {}", locale);
GetIniValue("Language", "Code", sgOptions.Language.szCode, sizeof(sgOptions.Language.szCode), locale.c_str());
5 years ago
keymapper.Load();
if (demo::IsRunning())
demo::OverrideOptions();
sbWasOptionsLoaded = true;
}
void SaveOptions()
{
SetIniValue("Diablo", "Intro", sgOptions.Diablo.bIntro);
SetIniValue("Diablo", "LastSinglePlayerHero", sgOptions.Diablo.lastSinglePlayerHero);
SetIniValue("Diablo", "LastMultiplayerHero", sgOptions.Diablo.lastMultiplayerHero);
SetIniValue("Hellfire", "Intro", sgOptions.Hellfire.bIntro);
SetIniValue("Hellfire", "SItem", sgOptions.Hellfire.szItem);
SetIniValue("Hellfire", "LastSinglePlayerHero", sgOptions.Hellfire.lastSinglePlayerHero);
SetIniValue("Hellfire", "LastMultiplayerHero", sgOptions.Hellfire.lastMultiplayerHero);
SetIniValue("Hellfire", "StartUpGameOption", static_cast<int>(sgOptions.Hellfire.startUpGameOption));
SetIniValue("Audio", "Sound Volume", sgOptions.Audio.nSoundVolume);
SetIniValue("Audio", "Music Volume", sgOptions.Audio.nMusicVolume);
SetIniValue("Audio", "Walking Sound", sgOptions.Audio.bWalkingSound);
SetIniValue("Audio", "Auto Equip Sound", sgOptions.Audio.bAutoEquipSound);
SetIniValue("Audio", "Item Pickup Sound", sgOptions.Audio.bItemPickupSound);
SetIniValue("Audio", "Sample Rate", sgOptions.Audio.nSampleRate);
SetIniValue("Audio", "Channels", sgOptions.Audio.nChannels);
SetIniValue("Audio", "Buffer Size", sgOptions.Audio.nBufferSize);
SetIniValue("Audio", "Resampling Quality", sgOptions.Audio.nResamplingQuality);
SetIniValue("Graphics", "Width", sgOptions.Graphics.nWidth);
SetIniValue("Graphics", "Height", sgOptions.Graphics.nHeight);
#ifndef __vita__
SetIniValue("Graphics", "Fullscreen", sgOptions.Graphics.bFullscreen);
#endif
#if !defined(USE_SDL1)
SetIniValue("Graphics", "Upscale", sgOptions.Graphics.bUpscale);
#endif
SetIniValue("Graphics", "Fit to Screen", sgOptions.Graphics.bFitToScreen);
SetIniValue("Graphics", "Scaling Quality", sgOptions.Graphics.szScaleQuality);
SetIniValue("Graphics", "Integer Scaling", sgOptions.Graphics.bIntegerScaling);
SetIniValue("Graphics", "Vertical Sync", sgOptions.Graphics.bVSync);
SetIniValue("Graphics", "Blended Transparency", sgOptions.Graphics.bBlendedTransparancy);
SetIniValue("Graphics", "Gamma Correction", sgOptions.Graphics.nGammaCorrection);
SetIniValue("Graphics", "Color Cycling", sgOptions.Graphics.bColorCycling);
#if SDL_VERSION_ATLEAST(2, 0, 0)
SetIniValue("Graphics", "Hardware Cursor", sgOptions.Graphics.bHardwareCursor);
SetIniValue("Graphics", "Hardware Cursor For Items", sgOptions.Graphics.bHardwareCursorForItems);
SetIniValue("Graphics", "Hardware Cursor Maximum Size", sgOptions.Graphics.nHardwareCursorMaxSize);
#endif
SetIniValue("Graphics", "FPS Limiter", sgOptions.Graphics.bFPSLimit);
SetIniValue("Graphics", "Show FPS", sgOptions.Graphics.bShowFPS);
SetIniValue("Game", "Speed", sgOptions.Gameplay.nTickRate);
SetIniValue("Game", "Run in Town", sgOptions.Gameplay.bRunInTown);
SetIniValue("Game", "Grab Input", sgOptions.Gameplay.bGrabInput);
SetIniValue("Game", "Theo Quest", sgOptions.Gameplay.bTheoQuest);
SetIniValue("Game", "Cow Quest", sgOptions.Gameplay.bCowQuest);
SetIniValue("Game", "Friendly Fire", sgOptions.Gameplay.bFriendlyFire);
SetIniValue("Game", "Test Bard", sgOptions.Gameplay.bTestBard);
SetIniValue("Game", "Test Barbarian", sgOptions.Gameplay.bTestBarbarian);
SetIniValue("Game", "Experience Bar", sgOptions.Gameplay.bExperienceBar);
SetIniValue("Game", "Enemy Health Bar", sgOptions.Gameplay.bEnemyHealthBar);
SetIniValue("Game", "Auto Gold Pickup", sgOptions.Gameplay.bAutoGoldPickup);
SetIniValue("Game", "Adria Refills Mana", sgOptions.Gameplay.bAdriaRefillsMana);
SetIniValue("Game", "Auto Equip Weapons", sgOptions.Gameplay.bAutoEquipWeapons);
SetIniValue("Game", "Auto Equip Armor", sgOptions.Gameplay.bAutoEquipArmor);
SetIniValue("Game", "Auto Equip Helms", sgOptions.Gameplay.bAutoEquipHelms);
SetIniValue("Game", "Auto Equip Shields", sgOptions.Gameplay.bAutoEquipShields);
SetIniValue("Game", "Auto Equip Jewelry", sgOptions.Gameplay.bAutoEquipJewelry);
SetIniValue("Game", "Randomize Quests", sgOptions.Gameplay.bRandomizeQuests);
SetIniValue("Game", "Show Monster Type", sgOptions.Gameplay.bShowMonsterType);
SetIniValue("Game", "Disable Crippling Shrines", sgOptions.Gameplay.bDisableCripplingShrines);
SetIniValue("Game", "Auto Refill Belt", sgOptions.Gameplay.bAutoRefillBelt);
SetIniValue("Network", "Bind Address", sgOptions.Network.szBindAddress);
SetIniValue("Network", "Port", sgOptions.Network.nPort);
SetIniValue("Network", "Previous Host", sgOptions.Network.szPreviousHost);
for (size_t i = 0; i < QUICK_MESSAGE_OPTIONS; i++)
SetIniValue("NetMsg", QuickMessages[i].key, sgOptions.Chat.szHotKeyMsgs[i]);
SetIniValue("Controller", "Mapping", sgOptions.Controller.szMapping);
SetIniValue("Controller", "Swap Shoulder Button Mode", sgOptions.Controller.bSwapShoulderButtonMode);
SetIniValue("Controller", "Dpad Hotkeys", sgOptions.Controller.bDpadHotkeys);
SetIniValue("Controller", "deadzone", sgOptions.Controller.fDeadzone);
#ifdef __vita__
SetIniValue("Controller", "Enable Rear Touchpad", sgOptions.Controller.bRearTouch);
#endif
SetIniValue("Language", "Code", sgOptions.Language.szCode);
5 years ago
keymapper.Save();
SaveIni();
}
} // namespace devilution